aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Sandulenko2016-09-10 16:55:29 +0200
committerGitHub2016-09-10 16:55:29 +0200
commitb7a8242b9b453d3e965f3d1d085c7456025ac8f8 (patch)
tree8f8268861c777ad9204aee7a12b71d8e18eaec91
parenta46cc8597cdc2ab2aca146ed79c0fcf3f4df5617 (diff)
parenta520a18a0101877d11cd488c5b6f93f5b63892af (diff)
downloadscummvm-rg350-b7a8242b9b453d3e965f3d1d085c7456025ac8f8.tar.gz
scummvm-rg350-b7a8242b9b453d3e965f3d1d085c7456025ac8f8.tar.bz2
scummvm-rg350-b7a8242b9b453d3e965f3d1d085c7456025ac8f8.zip
Merge pull request #818 from dreammaster/xeen
XEEN: Might & Magic engine
-rw-r--r--engines/xeen/character.cpp1840
-rw-r--r--engines/xeen/character.h423
-rw-r--r--engines/xeen/combat.cpp2065
-rw-r--r--engines/xeen/combat.h221
-rw-r--r--engines/xeen/configure.engine3
-rw-r--r--engines/xeen/debugger.cpp85
-rw-r--r--engines/xeen/debugger.h47
-rw-r--r--engines/xeen/detection.cpp196
-rw-r--r--engines/xeen/detection_tables.h67
-rw-r--r--engines/xeen/dialogs.cpp260
-rw-r--r--engines/xeen/dialogs.h115
-rw-r--r--engines/xeen/dialogs_automap.cpp428
-rw-r--r--engines/xeen/dialogs_automap.h45
-rw-r--r--engines/xeen/dialogs_char_info.cpp568
-rw-r--r--engines/xeen/dialogs_char_info.h70
-rw-r--r--engines/xeen/dialogs_control_panel.cpp42
-rw-r--r--engines/xeen/dialogs_control_panel.h43
-rw-r--r--engines/xeen/dialogs_dismiss.cpp95
-rw-r--r--engines/xeen/dialogs_dismiss.h47
-rw-r--r--engines/xeen/dialogs_error.cpp118
-rw-r--r--engines/xeen/dialogs_error.h65
-rw-r--r--engines/xeen/dialogs_exchange.cpp80
-rw-r--r--engines/xeen/dialogs_exchange.h47
-rw-r--r--engines/xeen/dialogs_fight_options.cpp39
-rw-r--r--engines/xeen/dialogs_fight_options.h43
-rw-r--r--engines/xeen/dialogs_info.cpp128
-rw-r--r--engines/xeen/dialogs_info.h47
-rw-r--r--engines/xeen/dialogs_input.cpp275
-rw-r--r--engines/xeen/dialogs_input.h89
-rw-r--r--engines/xeen/dialogs_items.cpp1065
-rw-r--r--engines/xeen/dialogs_items.h91
-rw-r--r--engines/xeen/dialogs_options.cpp232
-rw-r--r--engines/xeen/dialogs_options.h95
-rw-r--r--engines/xeen/dialogs_party.cpp1045
-rw-r--r--engines/xeen/dialogs_party.h111
-rw-r--r--engines/xeen/dialogs_query.cpp160
-rw-r--r--engines/xeen/dialogs_query.h54
-rw-r--r--engines/xeen/dialogs_quests.cpp254
-rw-r--r--engines/xeen/dialogs_quests.h50
-rw-r--r--engines/xeen/dialogs_quick_ref.cpp88
-rw-r--r--engines/xeen/dialogs_quick_ref.h43
-rw-r--r--engines/xeen/dialogs_spells.cpp1030
-rw-r--r--engines/xeen/dialogs_spells.h162
-rw-r--r--engines/xeen/dialogs_whowill.cpp105
-rw-r--r--engines/xeen/dialogs_whowill.h43
-rw-r--r--engines/xeen/events.cpp179
-rw-r--r--engines/xeen/events.h121
-rw-r--r--engines/xeen/files.cpp230
-rw-r--r--engines/xeen/files.h170
-rw-r--r--engines/xeen/font.cpp347
-rw-r--r--engines/xeen/font.h101
-rw-r--r--engines/xeen/interface.cpp2291
-rw-r--r--engines/xeen/interface.h183
-rw-r--r--engines/xeen/interface_map.cpp4445
-rw-r--r--engines/xeen/interface_map.h175
-rw-r--r--engines/xeen/items.cpp29
-rw-r--r--engines/xeen/items.h34
-rw-r--r--engines/xeen/map.cpp1581
-rw-r--r--engines/xeen/map.h437
-rw-r--r--engines/xeen/module.mk53
-rw-r--r--engines/xeen/party.cpp733
-rw-r--r--engines/xeen/party.h204
-rw-r--r--engines/xeen/resources.cpp1590
-rw-r--r--engines/xeen/resources.h569
-rw-r--r--engines/xeen/saves.cpp171
-rw-r--r--engines/xeen/saves.h96
-rw-r--r--engines/xeen/screen.cpp479
-rw-r--r--engines/xeen/screen.h194
-rw-r--r--engines/xeen/scripts.cpp1770
-rw-r--r--engines/xeen/scripts.h341
-rw-r--r--engines/xeen/sound.cpp100
-rw-r--r--engines/xeen/sound.h119
-rw-r--r--engines/xeen/spells.cpp1329
-rw-r--r--engines/xeen/spells.h191
-rw-r--r--engines/xeen/sprites.cpp334
-rw-r--r--engines/xeen/sprites.h116
-rw-r--r--engines/xeen/town.cpp1310
-rw-r--r--engines/xeen/town.h133
-rw-r--r--engines/xeen/worldofxeen/clouds_cutscenes.cpp43
-rw-r--r--engines/xeen/worldofxeen/clouds_cutscenes.h55
-rw-r--r--engines/xeen/worldofxeen/darkside_cutscenes.cpp258
-rw-r--r--engines/xeen/worldofxeen/darkside_cutscenes.h54
-rw-r--r--engines/xeen/worldofxeen/worldofxeen.cpp46
-rw-r--r--engines/xeen/worldofxeen/worldofxeen.h50
-rw-r--r--engines/xeen/xeen.cpp353
-rw-r--r--engines/xeen/xeen.h213
-rw-r--r--engines/xeen/xsurface.cpp31
-rw-r--r--engines/xeen/xsurface.h53
88 files changed, 33530 insertions, 0 deletions
diff --git a/engines/xeen/character.cpp b/engines/xeen/character.cpp
new file mode 100644
index 0000000000..b0502d254a
--- /dev/null
+++ b/engines/xeen/character.cpp
@@ -0,0 +1,1840 @@
+/* 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 "xeen/character.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+XeenItem::XeenItem() {
+ clear();
+}
+
+void XeenItem::clear() {
+ _material = _id = _bonusFlags = 0;
+ _frame = 0;
+}
+
+void XeenItem::synchronize(Common::Serializer &s) {
+ s.syncAsByte(_material);
+ s.syncAsByte(_id);
+ s.syncAsByte(_bonusFlags);
+ s.syncAsByte(_frame);
+}
+
+ElementalCategory XeenItem::getElementalCategory() const {
+ int idx;
+ for (idx = 0; ELEMENTAL_CATEGORIES[idx] < _material; ++idx)
+ ;
+
+ return (ElementalCategory)idx;
+}
+
+AttributeCategory XeenItem::getAttributeCategory() const {
+ int m = _material - 59;
+ int idx;
+ for (idx = 0; ATTRIBUTE_CATEGORIES[idx] < m; ++idx)
+ ;
+
+ return (AttributeCategory)idx;
+}
+
+/*------------------------------------------------------------------------*/
+
+InventoryItems::InventoryItems(Character *character, ItemCategory category):
+ _character(character), _category(category) {
+ resize(INV_ITEMS_TOTAL);
+
+ static const char *const *NAMES[4] = {
+ WEAPON_NAMES, ARMOR_NAMES, ACCESSORY_NAMES, MISC_NAMES
+ };
+ _names = NAMES[category];
+}
+
+void InventoryItems::clear() {
+ for (uint idx = 0; idx < size(); ++idx)
+ operator[](idx).clear();
+}
+
+bool InventoryItems::passRestrictions(int itemId, bool showError) const {
+ CharacterClass charClass = _character->_class;
+
+ switch (charClass) {
+ case CLASS_KNIGHT:
+ case CLASS_PALADIN:
+ return true;
+
+ case CLASS_ARCHER:
+ case CLASS_CLERIC:
+ case CLASS_SORCERER:
+ case CLASS_ROBBER:
+ case CLASS_NINJA:
+ case CLASS_BARBARIAN:
+ case CLASS_DRUID:
+ case CLASS_RANGER: {
+ if (!(ITEM_RESTRICTIONS[itemId + RESTRICTION_OFFSETS[_category]] &
+ (1 << (charClass - CLASS_ARCHER))))
+ return true;
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ Common::String name = _names[itemId];
+ if (showError) {
+ Common::String msg = Common::String::format(NOT_PROFICIENT,
+ CLASS_NAMES[charClass], name.c_str());
+ ErrorScroll::show(Party::_vm, msg, WT_FREEZE_WAIT);
+ }
+
+ return false;
+}
+
+Common::String InventoryItems::getName(int itemIndex) {
+ int id = operator[](itemIndex)._id;
+ return _names[id];
+}
+
+Common::String InventoryItems::getIdentifiedDetails(int itemIndex) {
+ XeenItem &item = operator[](itemIndex);
+
+ Common::String classes;
+ for (int charClass = CLASS_KNIGHT; charClass <= CLASS_RANGER; ++charClass) {
+ if (passRestrictions(charClass, true)) {
+ const char *const name = CLASS_NAMES[charClass];
+ classes += name[0];
+ classes += name[1];
+ classes += " ";
+ }
+ }
+ if (classes.size() == 30)
+ classes = ALL;
+
+ return getAttributes(item, classes);
+}
+
+bool InventoryItems::discardItem(int itemIndex) {
+ XeenItem &item = operator[](itemIndex);
+ XeenEngine *vm = Party::_vm;
+
+ if (item._bonusFlags & ITEMFLAG_CURSED) {
+ ErrorScroll::show(vm, CANNOT_DISCARD_CURSED_ITEM);
+ } else {
+ Common::String itemDesc = getFullDescription(itemIndex, 4);
+ Common::String msg = Common::String::format(PERMANENTLY_DISCARD, itemDesc.c_str());
+
+ if (Confirm::show(vm, msg)) {
+ operator[](itemIndex).clear();
+ sort();
+
+ return true;
+ }
+ }
+
+ return true;
+}
+
+void InventoryItems::sort() {
+ for (uint idx = 0; idx < size(); ++idx) {
+ if (operator[](idx)._id == 0) {
+ // Found empty slot
+ operator[](idx).clear();
+
+ // Scan through the rest of the list to find any item
+ for (uint idx2 = idx + 1; idx2 < size(); ++idx2) {
+ if (operator[](idx2)._id) {
+ // Found an item, so move it into the blank slot
+ operator[](idx) = operator[](idx2);
+ operator[](idx2).clear();
+ break;
+ }
+ }
+ }
+ }
+}
+
+void InventoryItems::removeItem(int itemIndex) {
+ XeenItem &item = operator[](itemIndex);
+ XeenEngine *vm = Party::_vm;
+
+ if (item._bonusFlags & ITEMFLAG_CURSED)
+ ErrorScroll::show(vm, CANNOT_REMOVE_CURSED_ITEM);
+ else
+ item._frame = 0;
+}
+
+XeenEngine *InventoryItems::getVm() {
+ return Party::_vm;
+}
+
+void InventoryItems::equipError(int itemIndex1, ItemCategory category1, int itemIndex2,
+ ItemCategory category2) {
+ XeenEngine *vm = Party::_vm;
+
+ if (itemIndex1 >= 0) {
+ Common::String itemName1 = _character->_items[category1].getName(itemIndex1);
+ Common::String itemName2 = _character->_items[category2].getName(itemIndex2);
+
+ ErrorDialog::show(vm, Common::String::format(REMOVE_X_TO_EQUIP_Y,
+ itemName1.c_str(), itemName2.c_str()));
+ } else {
+ ErrorDialog::show(vm, Common::String::format(EQUIPPED_ALL_YOU_CAN,
+ (itemIndex1 == -1) ? RING : MEDAL));
+ }
+}
+
+void InventoryItems::enchantItem(int itemIndex, int amount) {
+ XeenEngine *vm = Party::_vm;
+ vm->_sound->playFX(21);
+ ErrorScroll::show(vm, Common::String::format(NOT_ENCHANTABLE, SPELL_FAILED));
+}
+
+bool InventoryItems::isFull() const {
+ return operator[](size() - 1)._id != 0;
+}
+
+/*------------------------------------------------------------------------*/
+
+void WeaponItems::equipItem(int itemIndex) {
+ XeenItem &item = operator[](itemIndex);
+
+ if (item._id <= 17) {
+ if (passRestrictions(item._id, false)) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 13 || i._frame == 1) {
+ equipError(itemIndex, CATEGORY_WEAPON, idx, CATEGORY_WEAPON);
+ return;
+ }
+ }
+
+ item._frame = 1;
+ }
+ } else if (item._id >= 30 && item._id <= 33) {
+ if (passRestrictions(item._id, false)) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 4) {
+ equipError(itemIndex, CATEGORY_WEAPON, idx, CATEGORY_WEAPON);
+ return;
+ }
+ }
+
+ item._frame = 4;
+ }
+ } else {
+ if (passRestrictions(item._id, false)) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 13 || i._frame == 1) {
+ equipError(itemIndex, CATEGORY_WEAPON, idx, CATEGORY_WEAPON);
+ return;
+ }
+ }
+
+ for (uint idx = 0; idx < _character->_armor.size(); ++idx) {
+ XeenItem &i = _character->_armor[idx];
+ if (i._frame == 2) {
+ equipError(itemIndex, CATEGORY_WEAPON, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ item._frame = 13;
+ }
+ }
+}
+
+Common::String WeaponItems::getFullDescription(int itemIndex, int displayNum) {
+ XeenItem &i = operator[](itemIndex);
+ Resources &res = *getVm()->_resources;
+
+ return Common::String::format("\f%02u%s%s%s\f%02u%s%s%s", displayNum,
+ !i._bonusFlags ? res._maeNames[i._material].c_str() : "",
+ (i._bonusFlags & ITEMFLAG_BROKEN) ? ITEM_BROKEN : "",
+ (i._bonusFlags & ITEMFLAG_CURSED) ? ITEM_CURSED : "",
+ displayNum,
+ WEAPON_NAMES[i._id],
+ !i._bonusFlags ? "" : BONUS_NAMES[i._bonusFlags & ITEMFLAG_BONUS_MASK],
+ (i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED)) ||
+ !i._bonusFlags ? "\b " : ""
+ );
+}
+
+void WeaponItems::enchantItem(int itemIndex, int amount) {
+ SoundManager &sound = *getVm()->_sound;
+ XeenItem &item = operator[](itemIndex);
+ Character tempCharacter;
+
+ if (item._material == 0 && item._bonusFlags == 0 && item._id != 34) {
+ tempCharacter.makeItem(amount, 0, 1);
+ XeenItem &tempItem = tempCharacter._weapons[0];
+
+ item._material = tempItem._material;
+ item._bonusFlags = tempItem._bonusFlags;
+ sound.playFX(19);
+ } else {
+ InventoryItems::enchantItem(itemIndex, amount);
+ }
+}
+
+/*
+ * Returns a text string listing all the stats/attributes of a given item
+ */
+Common::String WeaponItems::getAttributes(XeenItem &item, const Common::String &classes) {
+ Common::String attrBonus, elemDamage, physDamage, toHit, specialPower;
+ attrBonus = elemDamage = physDamage = toHit = specialPower = FIELD_NONE;
+
+ // First calculate physical damage
+ int minVal = WEAPON_DAMAGE_BASE[item._id];
+ int maxVal = minVal * WEAPON_DAMAGE_MULTIPLIER[item._id];
+
+ if (item._material >= 37 && item._material <= 58) {
+ minVal += METAL_DAMAGE[item._material - 37];
+ maxVal += METAL_DAMAGE[item._material - 37];
+ toHit = Common::String::format("%+d", METAL_DAMAGE_PERCENT[item._material - 37]);
+ }
+
+ physDamage = Common::String::format(DAMAGE_X_TO_Y, minVal, maxVal);
+
+ // Next handle elemental/attribute damage
+ if (item._material < 37) {
+ int damage = ELEMENTAL_DAMAGE[item._material];
+ if (damage > 0) {
+ ElementalCategory elemCategory = item.getElementalCategory();
+ elemDamage = Common::String::format(ELEMENTAL_XY_DAMAGE,
+ damage, ELEMENTAL_NAMES[elemCategory]);
+ }
+ } else if (item._material >= 59) {
+ int bonus = ATTRIBUTE_BONUSES[item._material - 59];
+ AttributeCategory attrCategory = item.getAttributeCategory();
+ attrBonus = Common::String::format(ATTR_XY_BONUS, bonus,
+ ATTRIBUTE_NAMES[attrCategory]);
+ }
+
+ // Handle weapon effective against
+ int effective = item._bonusFlags & ITEMFLAG_BONUS_MASK;
+ if (effective) {
+ specialPower = Common::String::format(EFFECTIVE_AGAINST,
+ EFFECTIVENESS_NAMES[effective]);
+ }
+
+ return Common::String::format(ITEM_DETAILS, classes.c_str(),
+ toHit.c_str(), physDamage.c_str(), elemDamage.c_str(),
+ FIELD_NONE, FIELD_NONE, attrBonus.c_str(), specialPower.c_str()
+ );
+}
+
+/*------------------------------------------------------------------------*/
+
+void ArmorItems::equipItem(int itemIndex) {
+ XeenItem &item = operator[](itemIndex);
+
+ if (item._id <= 7) {
+ if (passRestrictions(item._id, false)) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 9) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ item._frame = 3;
+ }
+ } else if (item._id == 8) {
+ if (passRestrictions(item._id, false)) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 2) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ for (uint idx = 0; idx < _character->_weapons.size(); ++idx) {
+ XeenItem &i = _character->_weapons[idx];
+ if (i._frame == 13) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_WEAPON);
+ return;
+ }
+ }
+
+ item._frame = 2;
+ }
+ } else if (item._id == 9) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 5) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ item._frame = 5;
+ } else if (item._id == 10) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 9) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ item._frame = 9;
+ } else if (item._id <= 12) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 10) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ item._frame = 10;
+ } else {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 6) {
+ equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
+ return;
+ }
+ }
+
+ item._frame = 6;
+ }
+}
+
+Common::String ArmorItems::getFullDescription(int itemIndex, int displayNum) {
+ XeenItem &i = operator[](itemIndex);
+ Resources &res = *getVm()->_resources;
+
+ return Common::String::format("\f%02u%s%s%s\f%02u%s%s", displayNum,
+ !i._bonusFlags ? "" : res._maeNames[i._material].c_str(),
+ (i._bonusFlags & ITEMFLAG_BROKEN) ? ITEM_BROKEN : "",
+ (i._bonusFlags & ITEMFLAG_CURSED) ? ITEM_CURSED : "",
+ displayNum,
+ ARMOR_NAMES[i._id],
+ (i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED)) ||
+ !i._bonusFlags ? "\b " : ""
+ );
+}
+
+void ArmorItems::enchantItem(int itemIndex, int amount) {
+ SoundManager &sound = *getVm()->_sound;
+ XeenItem &item = operator[](itemIndex);
+ Character tempCharacter;
+
+ if (item._material == 0 && item._bonusFlags == 0) {
+ tempCharacter.makeItem(amount, 0, 2);
+ XeenItem &tempItem = tempCharacter._armor[0];
+
+ item._material = tempItem._material;
+ item._bonusFlags = tempItem._bonusFlags;
+ sound.playFX(19);
+ } else {
+ InventoryItems::enchantItem(itemIndex, amount);
+ }
+}
+
+/*
+* Returns a text string listing all the stats/attributes of a given item
+*/
+Common::String ArmorItems::getAttributes(XeenItem &item, const Common::String &classes) {
+ Common::String elemResist, attrBonus, acBonus;
+ elemResist = attrBonus = acBonus = FIELD_NONE;
+
+ if (item._material < 36) {
+ int resistence = ELEMENTAL_RESISTENCES[item._material];
+ if (resistence > 0) {
+ int eCategory = ELEM_FIRE;
+ while (eCategory < ELEM_MAGIC && ELEMENTAL_CATEGORIES[eCategory] < item._material)
+ ++eCategory;
+
+ elemResist = Common::String::format(ATTR_XY_BONUS, resistence,
+ ELEMENTAL_NAMES[eCategory]);
+ }
+ } else if (item._material >= 59) {
+ int bonus = ATTRIBUTE_BONUSES[item._material - 59];
+ AttributeCategory aCategory = item.getAttributeCategory();
+ attrBonus = Common::String::format(ATTR_XY_BONUS, bonus,
+ ATTRIBUTE_NAMES[aCategory]);
+ }
+
+ int strength = ARMOR_STRENGTHS[item._id];
+ if (item._material >= 37 && item._material <= 58) {
+ strength += METAL_LAC[item._material - 37];
+ }
+ acBonus = Common::String::format("%+d", strength);
+
+ return Common::String::format(ITEM_DETAILS, classes.c_str(),
+ FIELD_NONE, FIELD_NONE, FIELD_NONE,
+ elemResist.c_str(), acBonus.c_str(), attrBonus.c_str(), FIELD_NONE);
+}
+
+/*------------------------------------------------------------------------*/
+
+void AccessoryItems::equipItem(int itemIndex) {
+ XeenItem &item = operator[](itemIndex);
+
+ if (item._id == 1) {
+ int count = 0;
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 8)
+ ++count;
+ }
+
+ if (count <= 1)
+ item._frame = 8;
+ else
+ equipError(-1, CATEGORY_ACCESSORY, itemIndex, CATEGORY_ACCESSORY);
+ } else if (item._id == 2) {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 12) {
+ equipError(itemIndex, CATEGORY_ACCESSORY, idx, CATEGORY_ACCESSORY);
+ return;
+ }
+ }
+ } else if (item._id <= 7) {
+ int count = 0;
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 7)
+ ++count;
+ }
+
+ if (count <= 1)
+ item._frame = 7;
+ else
+ equipError(-2, CATEGORY_ACCESSORY, itemIndex, CATEGORY_ACCESSORY);
+ } else {
+ for (uint idx = 0; idx < size(); ++idx) {
+ XeenItem &i = operator[](idx);
+ if (i._frame == 11) {
+ equipError(itemIndex, CATEGORY_ACCESSORY, idx, CATEGORY_ACCESSORY);
+ return;
+ }
+ }
+
+ item._frame = 11;
+ }
+}
+
+Common::String AccessoryItems::getFullDescription(int itemIndex, int displayNum) {
+ XeenItem &i = operator[](itemIndex);
+ Resources &res = *getVm()->_resources;
+
+ return Common::String::format("\f%02u%s%s%s\f%02u%s%s", displayNum,
+ !i._bonusFlags ? "" : res._maeNames[i._material].c_str(),
+ (i._bonusFlags & ITEMFLAG_BROKEN) ? ITEM_BROKEN : "",
+ (i._bonusFlags & ITEMFLAG_CURSED) ? ITEM_CURSED : "",
+ displayNum,
+ ARMOR_NAMES[i._id],
+ (i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED)) ||
+ !i._bonusFlags ? "\b " : ""
+ );
+}
+
+/*
+* Returns a text string listing all the stats/attributes of a given item
+*/
+Common::String AccessoryItems::getAttributes(XeenItem &item, const Common::String &classes) {
+ Common::String elemResist, attrBonus;
+ elemResist = attrBonus = FIELD_NONE;
+
+ if (item._material < 36) {
+ int resistence = ELEMENTAL_RESISTENCES[item._material];
+ if (resistence > 0) {
+ int eCategory = ELEM_FIRE;
+ while (eCategory < ELEM_MAGIC && ELEMENTAL_CATEGORIES[eCategory] < item._material)
+ ++eCategory;
+
+ elemResist = Common::String::format(ATTR_XY_BONUS, resistence,
+ ELEMENTAL_NAMES[eCategory]);
+ }
+ } else if (item._material >= 59) {
+ int bonus = ATTRIBUTE_BONUSES[item._material - 59];
+ AttributeCategory aCategory = item.getAttributeCategory();
+ attrBonus = Common::String::format(ATTR_XY_BONUS, bonus,
+ ATTRIBUTE_NAMES[aCategory]);
+ }
+
+ return Common::String::format(ITEM_DETAILS, classes.c_str(),
+ FIELD_NONE, FIELD_NONE, FIELD_NONE,
+ elemResist.c_str(), FIELD_NONE, attrBonus.c_str(), FIELD_NONE);
+}
+
+/*------------------------------------------------------------------------*/
+
+Common::String MiscItems::getFullDescription(int itemIndex, int displayNum) {
+ XeenItem &i = operator[](itemIndex);
+ Resources &res = *getVm()->_resources;
+
+ return Common::String::format("\f%02u%s%s%s\f%02u%s%s", displayNum,
+ !i._bonusFlags ? "" : res._maeNames[i._material].c_str(),
+ (i._bonusFlags & ITEMFLAG_BROKEN) ? ITEM_BROKEN : "",
+ (i._bonusFlags & ITEMFLAG_CURSED) ? ITEM_CURSED : "",
+ displayNum,
+ ARMOR_NAMES[i._id],
+ (i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED)) ||
+ !i._id ? "\b " : ""
+ );
+}
+
+
+/*
+* Returns a text string listing all the stats/attributes of a given item
+*/
+Common::String MiscItems::getAttributes(XeenItem &item, const Common::String &classes) {
+ Common::String specialPower = FIELD_NONE;
+ Spells &spells = *getVm()->_spells;
+
+ if (item._id) {
+ specialPower = spells._spellNames[MISC_SPELL_INDEX[item._id]];
+ }
+
+ return Common::String::format(ITEM_DETAILS, classes.c_str(),
+ FIELD_NONE, FIELD_NONE, FIELD_NONE, FIELD_NONE, FIELD_NONE,
+ FIELD_NONE, specialPower.c_str());
+}
+/*------------------------------------------------------------------------*/
+
+InventoryItemsGroup::InventoryItemsGroup(InventoryItems &weapons, InventoryItems &armor,
+ InventoryItems &accessories, InventoryItems &misc) {
+ _itemSets[0] = &weapons;
+ _itemSets[1] = &armor;
+ _itemSets[2] = &accessories;
+ _itemSets[3] = &misc;
+}
+
+InventoryItems &InventoryItemsGroup::operator[](ItemCategory category) {
+ return *_itemSets[category];
+}
+
+void InventoryItemsGroup::breakAllItems() {
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ if ((*_itemSets[0])[idx]._id != 34) {
+ (*_itemSets[0])[idx]._bonusFlags |= ITEMFLAG_BROKEN;
+ (*_itemSets[0])[idx]._frame = 0;
+ }
+
+ (*_itemSets[1])[idx]._bonusFlags |= ITEMFLAG_BROKEN;
+ (*_itemSets[2])[idx]._bonusFlags |= ITEMFLAG_BROKEN;
+ (*_itemSets[3])[idx]._bonusFlags |= ITEMFLAG_BROKEN;
+ (*_itemSets[1])[idx]._frame = 0;
+ (*_itemSets[2])[idx]._frame = 0;
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+
+void AttributePair::synchronize(Common::Serializer &s) {
+ s.syncAsByte(_permanent);
+ s.syncAsByte(_temporary);
+}
+
+/*------------------------------------------------------------------------*/
+
+AttributePair::AttributePair() {
+ _temporary = _permanent = 0;
+}
+
+/*------------------------------------------------------------------------*/
+
+Character::Character():
+ _weapons(this), _armor(this), _accessories(this), _misc(this),
+ _items(_weapons, _armor, _accessories, _misc) {
+ clear();
+ _faceSprites = nullptr;
+ _rosterId = -1;
+}
+
+void Character::clear() {
+ _sex = MALE;
+ _race = HUMAN;
+ _xeenSide = 0;
+ _class = CLASS_KNIGHT;
+ _ACTemp = 0;
+ _birthDay = 0;
+ _tempAge = 0;
+ Common::fill(&_skills[0], &_skills[18], 0);
+ Common::fill(&_awards[0], &_awards[128], false);
+ Common::fill(&_spells[0], &_spells[39], 0);
+ _lloydMap = 0;
+ _hasSpells = false;
+ _currentSpell = 0;
+ _quickOption = QUICK_ATTACK;
+ _lloydSide = 0;
+ Common::fill(&_conditions[0], &_conditions[16], 0);
+ _townUnknown = 0;
+ _savedMazeId = 0;
+ _currentHp = 0;
+ _currentSp = 0;
+ _birthYear = 0;
+ _experience = 0;
+ _currentAdventuringSpell = 0;
+ _currentCombatSpell = 0;
+
+ _might._permanent = _might._temporary = 0;
+ _intellect._permanent = _intellect._temporary = 0;
+ _personality._permanent = _personality._temporary = 0;
+ _endurance._permanent = _endurance._temporary = 0;
+ _speed._permanent = _speed._temporary = 0;
+ _accuracy._permanent = _accuracy._temporary = 0;
+ _luck._permanent = _luck._temporary = 0;
+ _fireResistence._permanent = _fireResistence._temporary = 0;
+ _coldResistence._permanent = _coldResistence._temporary = 0;
+ _electricityResistence._permanent = _electricityResistence._temporary = 0;
+ _poisonResistence._permanent = _poisonResistence._temporary = 0;
+ _energyResistence._permanent = _energyResistence._temporary = 0;
+ _magicResistence._permanent = _magicResistence._temporary = 0;
+ _weapons.clear();
+ _armor.clear();
+ _accessories.clear();
+ _misc.clear();
+}
+
+void Character::synchronize(Common::Serializer &s) {
+ char name[16];
+ Common::fill(&name[0], &name[16], '\0');
+ Common::strlcpy(name, _name.c_str(), 16);
+ s.syncBytes((byte *)name, 16);
+
+ if (s.isLoading())
+ _name = Common::String(name);
+
+ s.syncAsByte(_sex);
+ s.syncAsByte(_race);
+ s.syncAsByte(_xeenSide);
+ s.syncAsByte(_class);
+
+ _might.synchronize(s);
+ _intellect.synchronize(s);
+ _personality.synchronize(s);
+ _endurance.synchronize(s);
+ _speed.synchronize(s);
+ _accuracy.synchronize(s);
+ _luck.synchronize(s);
+ s.syncAsByte(_ACTemp);
+ _level.synchronize(s);
+ s.syncAsByte(_birthDay);
+ s.syncAsByte(_tempAge);
+
+ // Synchronize the skill list
+ for (int idx = 0; idx < 18; ++idx)
+ s.syncAsByte(_skills[idx]);
+
+ // Synchronize character awards
+ for (int idx = 0; idx < 64; ++idx) {
+ byte b = (_awards[idx] ? 1 : 0) | (_awards[idx + 64] ? 0x10 : 0);
+ s.syncAsByte(b);
+ if (s.isLoading()) {
+ _awards[idx] = (b & 0xF) != 0;
+ _awards[idx + 64] = (b & 0xF0) != 0;
+ }
+ }
+
+ // Synchronize spell list
+ for (int i = 0; i < MAX_SPELLS_PER_CLASS - 1; ++i)
+ s.syncAsByte(_spells[i]);
+ s.syncAsByte(_lloydMap);
+ s.syncAsByte(_lloydPosition.x);
+ s.syncAsByte(_lloydPosition.y);
+ s.syncAsByte(_hasSpells);
+ s.syncAsByte(_currentSpell);
+ s.syncAsByte(_quickOption);
+
+ for (int i = 0; i < 9; ++i)
+ _weapons[i].synchronize(s);
+ for (int i = 0; i < 9; ++i)
+ _armor[i].synchronize(s);
+ for (int i = 0; i < 9; ++i)
+ _accessories[i].synchronize(s);
+ for (int i = 0; i < 9; ++i)
+ _misc[i].synchronize(s);
+
+ s.syncAsByte(_lloydSide);
+ _fireResistence.synchronize(s);
+ _coldResistence.synchronize(s);
+ _electricityResistence.synchronize(s);
+ _poisonResistence.synchronize(s);
+ _energyResistence.synchronize(s);
+ _magicResistence.synchronize(s);
+
+ for (int i = 0; i < 16; ++i)
+ s.syncAsByte(_conditions[i]);
+
+ s.syncAsUint16LE(_townUnknown);
+ s.syncAsByte(_savedMazeId);
+ s.syncAsUint16LE(_currentHp);
+ s.syncAsUint16LE(_currentSp);
+ s.syncAsUint16LE(_birthYear);
+ s.syncAsUint32LE(_experience);
+ s.syncAsByte(_currentAdventuringSpell);
+ s.syncAsByte(_currentCombatSpell);
+}
+
+Condition Character::worstCondition() const {
+ for (int cond = ERADICATED; cond >= CURSED; --cond) {
+ if (_conditions[cond])
+ return (Condition)cond;
+ }
+
+ return NO_CONDITION;
+}
+
+bool Character::isDisabled() const {
+ Condition condition = worstCondition();
+
+ return condition == ASLEEP || condition == PARALYZED || condition == UNCONSCIOUS
+ || condition == STONED || condition == ERADICATED;
+}
+
+bool Character::isDisabledOrDead() const {
+ Condition condition = worstCondition();
+
+ return condition == ASLEEP || (condition >= PARALYZED && condition <= ERADICATED);
+}
+
+bool Character::isDead() const {
+ Condition condition = worstCondition();
+
+ return condition >= DEAD && condition <= ERADICATED;
+}
+
+int Character::getAge(bool ignoreTemp) const {
+ int year = MIN(Party::_vm->_party->_year - _birthYear, (uint)254);
+
+ return ignoreTemp ? year : year + _tempAge;
+}
+
+int Character::getMaxHP() const {
+ int hp = BASE_HP_BY_CLASS[_class];
+ hp += statBonus(getStat(ENDURANCE));
+ hp += RACE_HP_BONUSES[_race];
+ if (_skills[BODYBUILDER])
+ ++hp;
+ if (hp < 1)
+ hp = 1;
+
+ hp *= getCurrentLevel();
+ hp += itemScan(7);
+
+ return MAX(hp, 0);
+}
+
+int Character::getMaxSP() const {
+ int result = 0;
+ bool flag = false;
+ int amount = 0;
+ Attribute attrib;
+ Skill skill;
+
+ if (!_hasSpells)
+ return 0;
+
+ if (_class == CLASS_SORCERER || _class == CLASS_ARCHER) {
+ attrib = INTELLECT;
+ skill = PRESTIDIGITATION;
+ } else {
+ attrib = PERSONALITY;
+ skill = PRAYER_MASTER;
+ }
+ if (_class == CLASS_DRUID || _class == CLASS_RANGER)
+ skill = ASTROLOGER;
+
+ for (;;) {
+ // Get the base number of spell points
+ result = statBonus(getStat(attrib)) + 3;
+ result += RACE_SP_BONUSES[_race][attrib - 1];
+
+ if (_skills[skill])
+ result += 2;
+ if (result < 1)
+ result = 1;
+
+ // Multiply it by the character's level
+ result *= getCurrentLevel();
+
+ // Classes other than sorcerer, clerics, and druids only get half the SP
+ if (_class != CLASS_SORCERER && _class != CLASS_CLERIC && _class != CLASS_DRUID)
+ result /= 2;
+
+ if (flag || (_class != CLASS_DRUID && _class != CLASS_RANGER))
+ break;
+
+ // Druids and rangers get bonuses averaged on both personality and intellect
+ attrib = INTELLECT;
+ flag = true;
+ amount = result;
+ }
+ if (flag)
+ result = (amount + result) / 2;
+
+ result += itemScan(8);
+ if (result < 0)
+ result = 0;
+
+ return result;
+}
+
+uint Character::getStat(Attribute attrib, bool baseOnly) const {
+ AttributePair attr;
+ int mode = 0;
+
+ switch (attrib) {
+ case MIGHT:
+ attr = _might;
+ break;
+ case INTELLECT:
+ attr = _intellect;
+ mode = 1;
+ break;
+ case PERSONALITY:
+ attr = _personality;
+ mode = 1;
+ break;
+ case ENDURANCE:
+ attr = _endurance;
+ break;
+ case SPEED:
+ attr = _speed;
+ break;
+ case ACCURACY:
+ attr = _accuracy;
+ break;
+ case LUCK:
+ attr = _luck;
+ mode = 2;
+ break;
+ default:
+ return 0;
+ }
+
+ // All the attributes except luck are affected by the character's age
+ if (mode < 2) {
+ int age = getAge(false);
+ int ageIndex = 0;
+ while (AGE_RANGES[ageIndex] <= age)
+ ++ageIndex;
+
+ attr._permanent += AGE_RANGES_ADJUST[mode][ageIndex];
+ }
+
+
+ attr._permanent += itemScan((int)attrib);
+
+ if (!baseOnly) {
+ attr._permanent += conditionMod(attrib);
+ attr._permanent += attr._temporary;
+ }
+
+ return MAX(attr._permanent, (uint)0);
+}
+
+int Character::statColor(int amount, int threshold) {
+ if (amount < 1)
+ return 6;
+ else if (amount > threshold)
+ return 2;
+ else if (amount == threshold)
+ return 15;
+ else if (amount <= (threshold / 4))
+ return 9;
+ else
+ return 32;
+}
+
+int Character::statBonus(uint statValue) const {
+ int idx;
+ for (idx = 0; STAT_VALUES[idx] <= statValue; ++idx)
+ ;
+
+ return STAT_BONUSES[idx];
+}
+
+bool Character::charSavingThrow(DamageType attackType) const {
+ int v, vMax;
+
+ if (attackType == DT_PHYSICAL) {
+ v = statBonus(getStat(LUCK)) + getCurrentLevel();
+ vMax = v + 20;
+ } else {
+ switch (attackType) {
+ case DT_MAGICAL:
+ v = _magicResistence._permanent + _magicResistence._temporary + itemScan(16);
+ break;
+ case DT_FIRE:
+ v = _fireResistence._permanent + _fireResistence._temporary + itemScan(11);
+ break;
+ case DT_ELECTRICAL:
+ v = _electricityResistence._permanent + _electricityResistence._temporary + itemScan(12);
+ break;
+ case DT_COLD:
+ v = _coldResistence._permanent + _coldResistence._temporary + itemScan(13);
+ break;
+ case DT_POISON:
+ v = _poisonResistence._permanent + _poisonResistence._temporary + itemScan(14);
+ break;
+ case DT_ENERGY:
+ v = _energyResistence._permanent + _energyResistence._temporary + itemScan(15);
+ break;
+ default:
+ v = 0;
+ break;
+ }
+
+ vMax = v + 40;
+ }
+
+ return Party::_vm->getRandomNumber(1, vMax) <= v;
+}
+
+bool Character::noActions() {
+ Condition condition = worstCondition();
+
+ switch (condition) {
+ case CURSED:
+ case POISONED:
+ case DISEASED:
+ case INSANE:
+ case IN_LOVE:
+ case DRUNK: {
+ Common::String msg = Common::String::format(IN_NO_CONDITION, _name.c_str());
+ ErrorScroll::show(Party::_vm, msg,
+ Party::_vm->_mode == 17 ? WT_2 : WT_NONFREEZED_WAIT);
+ return true;
+ }
+ default:
+ return false;
+ }
+}
+
+void Character::setAward(int awardId, bool value) {
+ int v = awardId;
+ if (awardId == 73)
+ v = 126;
+ else if (awardId == 81)
+ v = 127;
+
+ _awards[v] = value;
+}
+
+bool Character::hasAward(int awardId) const {
+ int v = awardId;
+ if (awardId == 73)
+ v = 126;
+ else if (awardId == 81)
+ v = 127;
+
+ return _awards[v];
+}
+
+int Character::getArmorClass(bool baseOnly) const {
+ Party &party = *Party::_vm->_party;
+
+ int result = statBonus(getStat(SPEED)) + itemScan(9);
+ if (!baseOnly)
+ result += party._blessed + _ACTemp;
+
+ return MAX(result, 0);
+}
+
+int Character::getThievery() const {
+ int result = getCurrentLevel() * 2;
+
+ if (_class == CLASS_NINJA)
+ result += 15;
+ else if (_class == CLASS_ROBBER)
+ result += 30;
+
+ switch (_race) {
+ case ELF:
+ case GNOME:
+ result += 10;
+ break;
+ case DWARF:
+ result += 5;
+ break;
+ case HALF_ORC:
+ result -= 10;
+ break;
+ default:
+ break;
+ }
+
+ result += itemScan(10);
+
+ // If the character doesn't have a thievery skill, then do'nt allow any result
+ if (!_skills[THIEVERY])
+ result = 0;
+
+ return MAX(result, 0);
+}
+
+uint Character::getCurrentLevel() const {
+ return MAX(_level._permanent + _level._temporary, (uint)0);
+}
+
+int Character::itemScan(int itemId) const {
+ int result = 0;
+
+ for (int accessIdx = 0; accessIdx < 3; ++accessIdx) {
+ switch (accessIdx) {
+ case 0:
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ const XeenItem &item = _weapons[idx];
+
+ if (item._frame && !(item._bonusFlags & 0xC0) && itemId < 11
+ && itemId != 3 && item._material >= 59 && item._material <= 130) {
+ int mIndex = (int)item.getAttributeCategory();
+ if (mIndex > PERSONALITY)
+ ++mIndex;
+
+ if (mIndex == itemId)
+ result += ATTRIBUTE_BONUSES[item._material - 59];
+ }
+ }
+ break;
+
+ case 1:
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ const XeenItem &item = _armor[idx];
+
+ if (item._frame && !(item._bonusFlags & 0xC0)) {
+ if (itemId < 11 && itemId != 3 && item._material >= 59 && item._material <= 130) {
+ int mIndex = (int)item.getAttributeCategory();
+ if (mIndex > PERSONALITY)
+ ++mIndex;
+
+ if (mIndex == itemId)
+ result += ATTRIBUTE_BONUSES[item._material - 59];
+ }
+
+ if (itemId > 10 && item._material < 37) {
+ int mIndex = item.getElementalCategory() + 11;
+
+ if (mIndex == itemId) {
+ result += ELEMENTAL_RESISTENCES[item._material];
+ }
+ }
+
+ if (itemId == 9) {
+ result += ARMOR_STRENGTHS[item._id];
+
+ if (item._material >= 37 && item._material <= 58)
+ result += METAL_LAC[item._material - 37];
+ }
+ }
+ }
+ break;
+
+ case 2:
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ const XeenItem &item = _accessories[idx];
+
+ if (item._frame && !(item._bonusFlags & 0xC0) && itemId < 11 && itemId != 3) {
+ if (item._material >= 59 && item._material <= 130) {
+ int mIndex = (int)item.getAttributeCategory();
+ if (mIndex > PERSONALITY)
+ ++mIndex;
+
+ if (mIndex == itemId) {
+ result += ATTRIBUTE_BONUSES[item._material - 59];
+ }
+ }
+
+ if (itemId > 10 && item._material < 37) {
+ int mIndex = item.getElementalCategory() + 11;
+
+ if (mIndex == itemId)
+ result += ELEMENTAL_RESISTENCES[item._material];
+ }
+ }
+ }
+ break;
+ }
+ };
+
+ return result;
+}
+
+int Character::conditionMod(Attribute attrib) const {
+ if (_conditions[DEAD] || _conditions[STONED] || _conditions[ERADICATED])
+ return 0;
+
+ int v[7];
+ Common::fill(&v[0], &v[7], 0);
+ if (_conditions[CURSED])
+ v[6] -= _conditions[CURSED];
+
+ if (_conditions[INSANE]) {
+ v[2] -= _conditions[INSANE];
+ v[1] -= _conditions[INSANE];
+ v[5] -= _conditions[INSANE];
+ v[0] -= _conditions[INSANE];
+ v[4] -= _conditions[INSANE];
+ }
+
+ if (_conditions[POISONED]) {
+ v[0] -= _conditions[POISONED];
+ v[4] -= _conditions[POISONED];
+ v[5] -= _conditions[POISONED];
+ }
+
+ if (_conditions[DISEASED]) {
+ v[3] -= _conditions[DISEASED];
+ v[2] -= _conditions[DISEASED];
+ v[1] -= _conditions[DISEASED];
+ }
+
+ for (int idx = 0; idx < 7; ++idx) {
+ v[idx] -= _conditions[HEART_BROKEN];
+ v[idx] -= _conditions[IN_LOVE];
+ v[idx] -= _conditions[WEAK];
+ v[idx] -= _conditions[DRUNK];
+ }
+
+ return v[attrib];
+}
+
+void Character::setValue(int id, uint value) {
+ Party &party = *Party::_vm->_party;
+ Scripts &scripts = *Party::_vm->_scripts;
+
+ switch (id) {
+ case 3:
+ // Set character sex
+ _sex = (Sex)value;
+ break;
+ case 4:
+ // Set race
+ _race = (Race)value;
+ break;
+ case 5:
+ // Set class
+ _class = (CharacterClass)value;
+ break;
+ case 8:
+ // Set the current Hp
+ _currentHp = value;
+ break;
+ case 9:
+ // Set the current Sp
+ _currentSp = value;
+ break;
+ case 10:
+ case 77:
+ // Set temporary armor class
+ _ACTemp = value;
+ break;
+ case 11:
+ // Set temporary level
+ _level._temporary = value;
+ break;
+ case 12:
+ // Set the character's temporary age
+ _tempAge = value;
+ break;
+ case 16:
+ // Set character experience
+ _experience = value;
+ break;
+ case 17:
+ // Set party poison resistence
+ party._poisonResistence = value;
+ break;
+ case 18:
+ // Set condition
+ if (value == 16) {
+ // Clear all the conditions
+ Common::fill(&_conditions[CURSED], &_conditions[NO_CONDITION], false);
+ } else if (value == 6) {
+ _conditions[value] = 1;
+ } else {
+ ++_conditions[value];
+ }
+
+ if (value >= DEAD && value <= ERADICATED && _currentHp > 0)
+ _currentHp = 0;
+ break;
+ case 25:
+ // Set time of day in minutes (0-1440)
+ party._minutes = value;
+ break;
+ case 34:
+ // Set party gold
+ party._gold = value;
+ break;
+ case 35:
+ // Set party gems
+ party._gems = value;
+ break;
+ case 37:
+ _might._temporary = value;
+ break;
+ case 38:
+ _intellect._temporary = value;
+ break;
+ case 39:
+ _personality._temporary = value;
+ break;
+ case 40:
+ _endurance._temporary = value;
+ break;
+ case 41:
+ _speed._temporary = value;
+ break;
+ case 42:
+ _accuracy._temporary = value;
+ break;
+ case 43:
+ _luck._temporary = value;
+ break;
+ case 45:
+ _might._permanent = value;
+ break;
+ case 46:
+ _intellect._permanent = value;
+ break;
+ case 47:
+ _personality._permanent = value;
+ break;
+ case 48:
+ _endurance._permanent = value;
+ break;
+ case 49:
+ _speed._permanent = value;
+ break;
+ case 50:
+ _accuracy._permanent = value;
+ break;
+ case 51:
+ _luck._permanent = value;
+ break;
+ case 52:
+ _fireResistence._permanent = value;
+ break;
+ case 53:
+ _electricityResistence._permanent = value;
+ break;
+ case 54:
+ _coldResistence._permanent = value;
+ break;
+ case 55:
+ _poisonResistence._permanent = value;
+ break;
+ case 56:
+ _energyResistence._permanent = value;
+ break;
+ case 57:
+ _magicResistence._permanent = value;
+ break;
+ case 58:
+ _fireResistence._temporary = value;
+ break;
+ case 59:
+ _electricityResistence._temporary = value;
+ break;
+ case 60:
+ _coldResistence._temporary = value;
+ break;
+ case 61:
+ _poisonResistence._temporary = value;
+ break;
+ case 62:
+ _energyResistence._temporary = value;
+ break;
+ case 63:
+ _magicResistence._temporary = value;
+ break;
+ case 64:
+ _level._permanent = value;
+ break;
+ case 65:
+ // Set party food
+ party._food = value;
+ break;
+ case 69:
+ // Set levitate active
+ party._levitateActive = value != 0;
+ break;
+ case 70:
+ party._lightCount = value;
+ break;
+ case 71:
+ party._fireResistence = value;
+ break;
+ case 72:
+ party._electricityResistence = value;
+ break;
+ case 73:
+ party._coldResistence = value;
+ break;
+ case 74:
+ party._walkOnWaterActive = value != 0;
+ party._poisonResistence = value;
+ party._wizardEyeActive = value != 0;
+ party._coldResistence = value;
+ party._electricityResistence = value;
+ party._fireResistence = value;
+ party._lightCount = value;
+ party._levitateActive = value != 0;
+ break;
+ case 76:
+ // Set day of the year (0-99)
+ party._day = value;
+ break;
+ case 79:
+ party._wizardEyeActive = true;
+ break;
+ case 83:
+ scripts._nEdamageType = value;
+ break;
+ case 84:
+ party._mazeDirection = (Direction)value;
+ break;
+ case 85:
+ party._year = value;
+ break;
+ case 94:
+ party._walkOnWaterActive = value != 0;
+ break;
+ default:
+ break;
+ }
+}
+
+bool Character::guildMember() const {
+ Party &party = *Party::_vm->_party;
+
+ if (party._mazeId == 49 && !Party::_vm->_files->_isDarkCc) {
+ return hasAward(5);
+ }
+
+ switch (party._mazeId) {
+ case 29:
+ return hasAward(83);
+ case 31:
+ return hasAward(84);
+ case 33:
+ return hasAward(85);
+ case 35:
+ return hasAward(86);
+ default:
+ return hasAward(87);
+ }
+}
+
+uint Character::experienceToNextLevel() const {
+ uint next = nextExperienceLevel();
+ uint curr = getCurrentExperience();
+ return (curr >= next) ? 0 : next - curr;
+}
+
+uint Character::nextExperienceLevel() const {
+ int shift, base;
+ if (_level._permanent >= 12) {
+ base = _level._permanent - 12;
+ shift = 10;
+ } else {
+ base = 0;
+ shift = _level._permanent - 1;
+ }
+
+ return (base * 1024000) + (CLASS_EXP_LEVELS[_class] << shift);
+}
+
+uint Character::getCurrentExperience() const {
+ int lev = _level._permanent - 1;
+ int shift, base;
+
+ if (lev > 0 && lev < 12)
+ return _experience;
+
+ if (lev >= 12) {
+ base = lev - 12;
+ shift = 10;
+ } else {
+ base = 0;
+ shift = lev - 1;
+ }
+
+ return (base * 1024000) + (CLASS_EXP_LEVELS[_class] << shift) +
+ _experience;
+}
+
+
+int Character::getNumSkills() const {
+ int total = 0;
+ for (int idx = THIEVERY; idx <= DANGER_SENSE; ++idx) {
+ if (_skills[idx])
+ ++total;
+ }
+
+ return total;
+}
+
+int Character::getNumAwards() const {
+ int total = 0;
+ for (int idx = 0; idx < 88; ++idx) {
+ if (hasAward(idx))
+ ++total;
+ }
+
+ return total;
+}
+
+int Character::makeItem(int p1, int itemIndex, int p3) {
+ XeenEngine *vm = Party::_vm;
+ Scripts &scripts = *vm->_scripts;
+
+ if (!p1)
+ return 0;
+
+ int itemId = 0;
+ int v4 = vm->getRandomNumber(100);
+ int v6 = vm->getRandomNumber(p1 < 6 ? 100 : 80);
+ ItemCategory category;
+ int v16 = 0, v14 = 0, miscBonus = 0, miscId = 0, v8 = 0, v12 = 0;
+
+ // Randomly pick a category and item Id
+ if (p3 == 12) {
+ if (scripts._itemType < 35) {
+ category = CATEGORY_WEAPON;
+ itemId = scripts._itemType;
+ } else if (scripts._itemType < 49) {
+ category = CATEGORY_ARMOR;
+ itemId = scripts._itemType - 35;
+ } else if (scripts._itemType < 60) {
+ category = CATEGORY_ACCESSORY;
+ itemId = scripts._itemType - 49;
+ } else {
+ category = CATEGORY_MISC;
+ itemId = scripts._itemType - 60;
+ }
+ } else {
+ switch (p3) {
+ case 1:
+ v4 = 35;
+ break;
+ case 2:
+ v4 = 60;
+ break;
+ case 3:
+ v4 = 100;
+ break;
+ default:
+ break;
+ }
+
+ if (p1 == 1) {
+ if (v4 <= 40) {
+ category = CATEGORY_WEAPON;
+ if (v6 <= 30) {
+ itemId = vm->getRandomNumber(1, 6);
+ } else if (v6 <= 60) {
+ itemId = vm->getRandomNumber(7, 17);
+ } else if (v6 <= 85) {
+ itemId = vm->getRandomNumber(18, 29);
+ } else {
+ itemId = vm->getRandomNumber(30, 33);
+ }
+ } else if (v4 <= 85) {
+ category = CATEGORY_ARMOR;
+ itemId = vm->getRandomNumber(1, 7);
+ } else {
+ category = CATEGORY_MISC;
+ itemId = vm->getRandomNumber(1, 9);
+ }
+ } else if (v4 <= 35) {
+ category = CATEGORY_WEAPON;
+ if (v6 <= 30) {
+ itemId = vm->getRandomNumber(1, 6);
+ } else if (v6 <= 60) {
+ itemId = vm->getRandomNumber(7, 17);
+ } else if (v6 <= 85) {
+ itemId = vm->getRandomNumber(18, 29);
+ } else {
+ itemId = vm->getRandomNumber(30, 33);
+ }
+ } else if (v4 <= 60) {
+ category = CATEGORY_ARMOR;
+ itemId = (v6 > 70) ? 8 : vm->getRandomNumber(1, 7);
+ } else if (v6 <= 10) {
+ category = CATEGORY_ARMOR;
+ itemId = 9;
+ } else if (v6 <= 20) {
+ category = CATEGORY_ARMOR;
+ itemId = 13;
+ } else if (v6 <= 35) {
+ category = CATEGORY_ACCESSORY;
+ itemId = 1;
+ } else if (v6 <= 45) {
+ category = CATEGORY_ARMOR;
+ itemId = 10;
+ } else if (v6 <= 55) {
+ category = CATEGORY_ARMOR;
+ itemId = vm->getRandomNumber(11, 12);
+ } else if (v6 <= 65) {
+ category = CATEGORY_ACCESSORY;
+ itemId = 2;
+ } else if (v6 <= 75) {
+ category = CATEGORY_ACCESSORY;
+ itemId = vm->getRandomNumber(3, 7);
+ } else if (v6 <= 80) {
+ category = CATEGORY_ACCESSORY;
+ itemId = vm->getRandomNumber(8, 10);
+ } else {
+ category = CATEGORY_MISC;
+ itemId = vm->getRandomNumber(1, 9);
+ }
+ }
+
+ XeenItem &newItem = _items[category][itemIndex];
+ newItem.clear();
+ newItem._id = itemId;
+
+ v4 = vm->getRandomNumber(1, 100);
+ switch (category) {
+ case CATEGORY_WEAPON:
+ case CATEGORY_ARMOR:
+ if (p1 != 1) {
+ if (v4 <= 70) {
+ v8 = 3;
+ } else if (v4 <= 98) {
+ v8 = 1;
+ } else {
+ v8 = 2;
+ }
+ }
+ break;
+
+ case CATEGORY_ACCESSORY:
+ if (v4 <= 20) {
+ v8 = 3;
+ } else if (v4 <= 60) {
+ v8 = 1;
+ } else {
+ v8 = 2;
+ }
+ break;
+
+ case CATEGORY_MISC:
+ v8 = 4;
+ break;
+
+ default:
+ break;
+ }
+
+ if (p1 != 1 || category == CATEGORY_MISC) {
+ int rval, mult;
+ switch (v8) {
+ case 1:
+ rval = vm->getRandomNumber(1, 100);
+ if (rval <= 25) {
+ mult = 0;
+ }
+ else if (rval <= 45) {
+ mult = 1;
+ }
+ else if (rval <= 60) {
+ mult = 2;
+ }
+ else if (rval <= 75) {
+ mult = 3;
+ }
+ else if (rval <= 95) {
+ mult = 4;
+ }
+ else {
+ mult = 5;
+ }
+
+ v12 = MAKE_ITEM_ARR1[vm->getRandomNumber(MAKE_ITEM_ARR2[mult][p1][0],
+ MAKE_ITEM_ARR2[mult][p1][1])];
+ break;
+
+ case 2:
+ rval = vm->getRandomNumber(1, 100);
+ if (rval <= 15) {
+ mult = 0;
+ } else if (rval <= 25) {
+ mult = 1;
+ } else if (rval <= 35) {
+ mult = 2;
+ } else if (rval <= 50) {
+ mult = 3;
+ } else if (rval <= 65) {
+ mult = 4;
+ } else if (rval <= 80) {
+ mult = 5;
+ } else if (rval <= 85) {
+ mult = 6;
+ } else if (rval <= 90) {
+ mult = 7;
+ } else if (rval <= 95) {
+ mult = 8;
+ } else {
+ mult = 9;
+ }
+
+ v12 = MAKE_ITEM_ARR1[vm->getRandomNumber(MAKE_ITEM_ARR3[mult][p1][0],
+ MAKE_ITEM_ARR3[mult][p1][1])];
+ break;
+
+ case 3:
+ mult = p1 == 7 || vm->getRandomNumber(1, 100) > 70 ? 1 : 0;
+ v16 = vm->getRandomNumber(MAKE_ITEM_ARR4[mult][p1][0],
+ MAKE_ITEM_ARR4[mult][p1][1]);
+ break;
+
+ case 4:
+ miscBonus = vm->getRandomNumber(MAKE_ITEM_ARR5[p1][0], MAKE_ITEM_ARR5[p1][1]);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ switch (category) {
+ case CATEGORY_WEAPON:
+ if (p1 != 1) {
+ newItem._material = (v14 ? v14 + 58 : 0) + (v16 ? v16 + 36 : 0) + v12;
+ if (vm->getRandomNumber(20) == 10)
+ newItem._bonusFlags = vm->getRandomNumber(1, 6);
+ }
+ break;
+
+ case CATEGORY_ARMOR:
+ case CATEGORY_ACCESSORY:
+ if (p1 != 1) {
+ newItem._material = (v14 ? v14 + 58 : 0) + (v16 ? v16 + 36 : 0) + v12;
+ }
+ break;
+
+ case CATEGORY_MISC:
+ newItem._id = miscId;
+ newItem._bonusFlags = miscBonus;
+ break;
+
+ default:
+ break;
+ }
+
+ return category;
+}
+
+void Character::addHitPoints(int amount) {
+ Interface &intf = *Party::_vm->_interface;
+ Common::fill(&intf._charFX[0], &intf._charFX[MAX_ACTIVE_PARTY], 0);
+
+ if (!isDead()) {
+ int maxHp = getMaxHP();
+ if (_currentHp <= maxHp) {
+ _currentHp = MIN(_currentHp + amount, maxHp);
+ intf.spellFX(this);
+ }
+
+ if (_currentHp > 0)
+ _conditions[UNCONSCIOUS] = 0;
+
+ intf.drawParty(true);
+ }
+
+ Common::fill(&intf._charFX[0], &intf._charFX[MAX_ACTIVE_PARTY], 0);
+}
+
+void Character::subtractHitPoints(int amount) {
+ SoundManager &sound = *Party::_vm->_sound;
+ _currentHp -= amount;
+ bool flag = _currentHp <= 10;
+
+ if (_currentHp < 1) {
+ int v = getMaxHP() + _currentHp;
+ if (v >= 1) {
+ _conditions[UNCONSCIOUS] = 1;
+ sound.playFX(38);;
+ } else {
+ _conditions[DEAD] = 1;
+ flag = true;
+ if (_currentHp > 0)
+ _currentHp = 0;
+ }
+
+ if (flag) {
+ // Check for breaking equipped armor
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ XeenItem &item = _armor[idx];
+ if (item._id && item._frame)
+ item._bonusFlags |= ITEMFLAG_BROKEN;
+ }
+ }
+ }
+}
+
+bool Character::hasSpecialItem() const {
+ for (uint idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ if (_weapons[idx]._id == 34)
+ // Character has Xeen Slayer sword
+ return true;
+ }
+
+ return false;
+}
+
+bool Character::hasMissileWeapon() const {
+ for (uint idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ if (_weapons[idx]._frame == 4) {
+ return !isDisabledOrDead();
+ }
+ }
+
+ return false;
+}
+
+int Character::getClassCategory() const {
+ switch (_class) {
+ case CLASS_ARCHER:
+ case CLASS_SORCERER:
+ return 1;
+
+ case CLASS_DRUID:
+ case CLASS_RANGER:
+ return 2;
+
+ default:
+ return 0;
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/character.h b/engines/xeen/character.h
new file mode 100644
index 0000000000..903d8faabc
--- /dev/null
+++ b/engines/xeen/character.h
@@ -0,0 +1,423 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_CHARACTER_H
+#define XEEN_CHARACTER_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/serializer.h"
+#include "xeen/combat.h"
+#include "xeen/sprites.h"
+
+namespace Xeen {
+
+#define INV_ITEMS_TOTAL 9
+
+enum BonusFlags {
+ ITEMFLAG_BONUS_MASK = 0xBF, ITEMFLAG_CURSED = 0x40, ITEMFLAG_BROKEN = 0x80
+};
+
+enum ItemCategory {
+ CATEGORY_WEAPON = 0, CATEGORY_ARMOR = 1, CATEGORY_ACCESSORY = 2, CATEGORY_MISC = 3,
+ NUM_ITEM_CATEGORIES = 4
+};
+
+enum Sex { MALE = 0, FEMALE = 1, YES_PLEASE = 2 };
+
+enum Race { HUMAN = 0, ELF = 1, DWARF = 2, GNOME = 3, HALF_ORC = 4 };
+
+enum CharacterClass {
+ CLASS_KNIGHT = 0, CLASS_PALADIN = 1, CLASS_ARCHER = 2, CLASS_CLERIC = 3,
+ CLASS_SORCERER = 4, CLASS_ROBBER = 5, CLASS_NINJA = 6, CLASS_BARBARIAN = 7,
+ CLASS_DRUID = 8, CLASS_RANGER = 9, TOTAL_CLASSES = 10, CLASS_12 = 12, CLASS_15 = 15, CLASS_16 = 16
+};
+
+enum Attribute {
+ MIGHT = 0, INTELLECT = 1, PERSONALITY = 2, ENDURANCE = 3, SPEED = 4,
+ ACCURACY = 5, LUCK = 6, TOTAL_ATTRIBUTES = 7
+};
+
+enum Skill {
+ THIEVERY = 0, ARMS_MASTER = 1, ASTROLOGER = 2, BODYBUILDER = 3,
+ CARTOGRAPHER = 4, CRUSADER = 5, DIRECTION_SENSE = 6, LINGUIST = 7,
+ MERCHANT = 8, MOUNTAINEER = 9, NAVIGATOR = 10, PATHFINDER = 11,
+ PRAYER_MASTER = 12, PRESTIDIGITATION = 13, SWIMMING = 14, TRACKING = 15,
+ SPOT_DOORS = 16, DANGER_SENSE = 17
+};
+
+enum Condition {
+ CURSED = 0, HEART_BROKEN = 1, WEAK = 2, POISONED = 3,
+ DISEASED = 4, INSANE = 5, IN_LOVE = 6, DRUNK = 7, ASLEEP = 8,
+ DEPRESSED = 9, CONFUSED = 10, PARALYZED = 11, UNCONSCIOUS = 12,
+ DEAD = 13, STONED = 14, ERADICATED = 15,
+ NO_CONDITION = 16
+};
+
+enum AttributeCategory {
+ ATTR_MIGHT = 0, ATTR_INTELLECT = 1, ATTR_PERSONALITY = 2, ATTR_SPEED = 3,
+ ATTR_ACCURACY = 4, ATTR_LUCK = 5, ATTR_HIT_POINTS = 6, ATTR_SPELL_POINTS = 7,
+ ATTR_ARMOR_CLASS = 8, ATTR_THIEVERY = 9
+};
+
+enum QuickAction {
+ QUICK_ATTACK = 0, QUICK_SPELL = 1, QUICK_BLOCK = 2, QUICK_RUN = 3
+};
+
+class XeenEngine;
+class Character;
+
+class XeenItem {
+public:
+ int _material;
+ uint _id;
+ int _bonusFlags;
+ int _frame;
+public:
+ XeenItem();
+
+ void clear();
+
+ bool empty() const { return _id != 0; }
+
+ void synchronize(Common::Serializer &s);
+
+ ElementalCategory getElementalCategory() const;
+
+ AttributeCategory getAttributeCategory() const;
+};
+
+class InventoryItems : public Common::Array<XeenItem> {
+protected:
+ Character *_character;
+ ItemCategory _category;
+ const char *const *_names;
+
+ XeenEngine *getVm();
+ void equipError(int itemIndex1, ItemCategory category1, int itemIndex2,
+ ItemCategory category2);
+
+ virtual Common::String getAttributes(XeenItem &item, const Common::String &classes) = 0;
+public:
+ InventoryItems(Character *character, ItemCategory category);
+ virtual ~InventoryItems() {}
+
+ void clear();
+
+ /**
+ * Return whether a given item passes class-based usage restrictions
+ */
+ bool passRestrictions(int itemId, bool showError) const;
+
+ /**
+ * Return the bare name of a given inventory item
+ */
+ Common::String getName(int itemIndex);
+
+ virtual Common::String getFullDescription(int itemIndex, int displayNum = 15) = 0;
+
+ Common::String getIdentifiedDetails(int itemIndex);
+
+ /**
+ * Discard an item from the inventory
+ */
+ bool discardItem(int itemIndex);
+
+ virtual void equipItem(int itemIndex) {}
+
+ /**
+ * Un-equips the given item
+ */
+ void removeItem(int itemIndex);
+
+ /**
+ * Sorts the items list, removing any empty item slots to the end of the array
+ */
+ void sort();
+
+ virtual void enchantItem(int itemIndex, int amount);
+
+ /**
+ * Return if the given inventory items list is full
+ */
+ bool isFull() const;
+};
+
+class WeaponItems: public InventoryItems {
+protected:
+ virtual Common::String getAttributes(XeenItem &item, const Common::String &classes);
+public:
+ WeaponItems(Character *character) : InventoryItems(character, CATEGORY_WEAPON) {}
+ virtual ~WeaponItems() {}
+
+ /**
+ * Equip a given weapon
+ */
+ virtual void equipItem(int itemIndex);
+
+ /**
+ * Assembles a full lines description for a specified item for use in
+ * the Items dialog
+ */
+ virtual Common::String getFullDescription(int itemIndex, int displayNum);
+
+ virtual void enchantItem(int itemIndex, int amount);
+};
+
+class ArmorItems : public InventoryItems {
+protected:
+ virtual Common::String getAttributes(XeenItem &item, const Common::String &classes);
+public:
+ ArmorItems(Character *character) : InventoryItems(character, CATEGORY_ARMOR) {}
+ virtual ~ArmorItems() {}
+
+ /**
+ * Equip a given piece of armor
+ */
+ virtual void equipItem(int itemIndex);
+
+ /**
+ * Assembles a full lines description for a specified item for use in
+ * the Items dialog
+ */
+ virtual Common::String getFullDescription(int itemIndex, int displayNum);
+
+ virtual void enchantItem(int itemIndex, int amount);
+};
+
+class AccessoryItems : public InventoryItems {
+protected:
+ virtual Common::String getAttributes(XeenItem &item, const Common::String &classes);
+public:
+ AccessoryItems(Character *character) : InventoryItems(character, CATEGORY_ACCESSORY) {}
+
+ /**
+ * Equip a given accessory
+ */
+ virtual void equipItem(int itemIndex);
+
+ /**
+ * Assembles a full lines description for a specified item for use in
+ * the Items dialog
+ */
+ virtual Common::String getFullDescription(int itemIndex, int displayNum);
+};
+
+class MiscItems : public InventoryItems {
+protected:
+ virtual Common::String getAttributes(XeenItem &item, const Common::String &classes);
+public:
+ MiscItems(Character *character) : InventoryItems(character, CATEGORY_MISC) {}
+ virtual ~MiscItems() {}
+
+ /**
+ * Assembles a full lines description for a specified item for use in
+ * the Items dialog
+ */
+ virtual Common::String getFullDescription(int itemIndex, int displayNum);
+};
+
+class InventoryItemsGroup {
+private:
+ InventoryItems *_itemSets[4];
+public:
+ InventoryItemsGroup(InventoryItems &weapons, InventoryItems &armor,
+ InventoryItems &accessories, InventoryItems &misc);
+
+ InventoryItems &operator[](ItemCategory category);
+
+ /**
+ * Breaks all the items in a given character's inventory
+ */
+ void breakAllItems();
+};
+
+
+class AttributePair {
+public:
+ uint _permanent;
+ uint _temporary;
+public:
+ AttributePair();
+ void synchronize(Common::Serializer &s);
+};
+
+class Character {
+private:
+ /**
+ * Modifies a passed attribute value based on player's condition
+ */
+ int conditionMod(Attribute attrib) const;
+public:
+ Common::String _name;
+ Sex _sex;
+ Race _race;
+ int _xeenSide;
+ CharacterClass _class;
+ AttributePair _might;
+ AttributePair _intellect;
+ AttributePair _personality;
+ AttributePair _endurance;
+ AttributePair _speed;
+ AttributePair _accuracy;
+ AttributePair _luck;
+ int _ACTemp;
+ AttributePair _level;
+ uint _birthDay;
+ int _tempAge;
+ int _skills[18];
+ bool _awards[128];
+ int _spells[39];
+ int _lloydMap;
+ Common::Point _lloydPosition;
+ bool _hasSpells;
+ int8 _currentSpell;
+ QuickAction _quickOption;
+ InventoryItemsGroup _items;
+ WeaponItems _weapons;
+ ArmorItems _armor;
+ AccessoryItems _accessories;
+ MiscItems _misc;
+ int _lloydSide;
+ AttributePair _fireResistence;
+ AttributePair _coldResistence;
+ AttributePair _electricityResistence;
+ AttributePair _poisonResistence;
+ AttributePair _energyResistence;
+ AttributePair _magicResistence;
+ int _conditions[16];
+ int _townUnknown;
+ int _savedMazeId;
+ int _currentHp;
+ int _currentSp;
+ uint _birthYear;
+ uint32 _experience;
+ int _currentAdventuringSpell;
+ int _currentCombatSpell;
+
+ SpriteResource *_faceSprites;
+ int _rosterId;
+public:
+ Character();
+
+ void clear();
+
+ void synchronize(Common::Serializer &s);
+
+ /**
+ * Returns the worst condition the character is suffering from
+ */
+ Condition worstCondition() const;
+
+ /**
+ * Returns whether the given character has a disabling condition, but still alive
+ */
+ bool isDisabled() const;
+
+ /**
+ * Returns whether the given character has a disabling condition, or is dead
+ */
+ bool isDisabledOrDead() const;
+
+ /**
+ * Returns whether the given character has a dead condition
+ */
+ bool isDead() const;
+
+ /**
+ * Get the character's age
+ */
+ int getAge(bool ignoreTemp = false) const;
+
+ int getMaxHP() const;
+
+ int getMaxSP() const;
+
+ /**
+ * Get the effective value of a given stat for the character
+ */
+ uint getStat(Attribute attrib, bool baseOnly = false) const;
+
+ /**
+ * Return the color number to use for a given stat value in the character
+ * info or quick reference dialogs
+ */
+ static int statColor(int amount, int threshold);
+
+ int statBonus(uint statValue) const;
+
+ bool charSavingThrow(DamageType attackType) const;
+
+ bool noActions();
+
+ void setAward(int awardId, bool value);
+
+ bool hasAward(int awardId) const;
+
+ int getArmorClass(bool baseOnly = false) const;
+
+ /**
+ * Returns the thievery skill level, adjusted by class and race
+ */
+ int getThievery() const;
+
+ uint getCurrentLevel() const;
+
+ int itemScan(int itemId) const;
+
+ void setValue(int id, uint value);
+
+ bool guildMember() const;
+
+ uint experienceToNextLevel() const;
+
+ uint nextExperienceLevel() const;
+
+ uint getCurrentExperience() const;
+
+ int getNumSkills() const;
+
+ int getNumAwards() const;
+
+ int makeItem(int p1, int itemIndex, int p3);
+
+ /**
+ * Add hit points to a character
+ */
+ void addHitPoints(int amount);
+
+ /**
+ * Remove hit points fromo the character
+ */
+ void subtractHitPoints(int amount);
+
+ bool hasSpecialItem() const;
+
+ bool hasMissileWeapon() const;
+
+ int getClassCategory() const;
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_CHARACTER_H */
diff --git a/engines/xeen/combat.cpp b/engines/xeen/combat.cpp
new file mode 100644
index 0000000000..3faff3d157
--- /dev/null
+++ b/engines/xeen/combat.cpp
@@ -0,0 +1,2065 @@
+/* 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 "common/algorithm.h"
+#include "common/rect.h"
+#include "xeen/character.h"
+#include "xeen/combat.h"
+#include "xeen/interface.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+static const int MONSTER_GRID_X[48] = {
+ 1, 1, 1, 0, -1, -1, -1, 1, 1, 1, 0, -1,
+ -1, -1, 1, 1, 1, 0, -1, -1, -1, 1, 1, 1,
+ 0, -1, -1, -1, 1, 1, 1, 0, -1, -1, -1, 1,
+ 1, 1, 0, -1, -1, -1, 1, 1, 1, 0, -1, -1
+};
+
+static const int MONSTER_GRID_Y[48] = {
+ 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, -1, 0,
+ 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0
+};
+
+static const int MONSTER_GRID3[48] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ - 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1,
+ 0, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+
+static const int MONSTER_GRID_BITINDEX1[48] = {
+ 1, 1, 1, 2, 3, 3, 3, 1, 1, 1, 2, 3,
+ 3, 3, 1, 1, 1, 2, 3, 3, 3, 1, 1, 1,
+ 0, 3, 3, 3, 1, 1, 1, 0, 3, 3, 3, 1,
+ 1, 1, 0, 3, 3, 3, 1, 1, 1, 0, 3, 3
+};
+
+static const int MONSTER_GRID_BITINDEX2[48] = {
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1,
+ 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static const int ATTACK_TYPE_FX[23] = {
+ 49, 18, 13, 14, 15, 17, 16, 0, 6, 1, 2, 3,
+ 4, 5, 4, 9, 27, 29, 44, 51, 53, 61, 71
+};
+
+static const int MONSTER_SHOOT_POW[7] = { 12, 14, 0, 4, 8, 10, 13 };
+
+static const int COMBAT_SHOOTING[4] = { 1, 1, 2, 3 };
+
+static const int DAMAGE_TYPE_EFFECTS[19] = {
+ 3, 10, 4, 11, 1, 2, 5, 9, 5, 14, 5, 14, 10, 8, 3, 9, 2, 2, 3
+};
+
+static const int POW_WEAPON_VOCS[35] = {
+ 0, 5, 4, 5, 5, 5, 5, 2, 4, 5, 3, 5, 4, 2, 3, 2, 2, 4, 5, 5,
+ 5, 5, 5, 1, 3, 2, 5, 1, 1, 1, 0, 0, 0, 2, 2
+};
+
+static const int MONSTER_ITEM_RANGES[6] = { 10, 20, 50, 100, 100, 100 };
+
+#define monsterSavingThrow(MONINDEX) (_vm->getRandomNumber(1, 50 + (MONINDEX)) <= (MONINDEX))
+
+/*------------------------------------------------------------------------*/
+
+Combat::Combat(XeenEngine *vm): _vm(vm), _missVoc("miss.voc"), _pow1Voc("pow1.voc") {
+ Common::fill(&_attackMonsters[0], &_attackMonsters[26], 0);
+ Common::fill(&_charsArray1[0], &_charsArray1[12], 0);
+ Common::fill(&_monPow[0], &_monPow[12], 0);
+ Common::fill(&_monsterScale[0], &_monsterScale[12], 0);
+ Common::fill(&_elemPow[0], &_elemPow[12], ELEM_FIRE);
+ Common::fill(&_elemScale[0], &_elemScale[12], 0);
+ Common::fill(&_shooting[0], &_shooting[8], 0);
+ Common::fill(&_monsterMap[0][0], &_monsterMap[32][32], 0);
+ Common::fill(&_monsterMoved[0], &_monsterMoved[MAX_NUM_MONSTERS], false);
+ Common::fill(&_rangeAttacking[0], &_rangeAttacking[MAX_NUM_MONSTERS], false);
+ Common::fill(&_gmonHit[0], &_gmonHit[36], 0);
+ Common::fill(&_missedShot[0], &_missedShot[MAX_PARTY_COUNT], 0);
+ _globalCombat = 0;
+ _whosTurn = -1;
+ _itemFlag = false;
+ _monstersAttacking = false;
+ _combatMode = COMBATMODE_0;
+ _monsterIndex = 0;
+ _partyRan = false;
+ _monster2Attack = -1;
+ _whosSpeed = 0;
+ _damageType = DT_PHYSICAL;
+ _oldCharacter = nullptr;
+ _shootType = ST_0;
+ _monsterDamage = 0;
+ _weaponDamage = 0;
+ _weaponDie = _weaponDice = 0;
+ _attackWeapon = nullptr;
+ _attackWeaponId = 0;
+ _hitChanceBonus = 0;
+ _dangerPresent = false;
+ _moveMonsters = false;
+ _rangeType = RT_SINGLE;
+}
+
+void Combat::clear() {
+ Common::fill(&_attackMonsters[0], &_attackMonsters[26], -1);
+}
+
+void Combat::giveCharDamage(int damage, DamageType attackType, int charIndex) {
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Scripts &scripts = *_vm->_scripts;
+ SoundManager &sound = *_vm->_sound;
+ int charIndex1 = charIndex + 1;
+ int selectedIndex1 = 0;
+ int selectedIndex2 = 0;
+ bool breakFlag = false;
+
+ screen.closeWindows();
+
+ int idx = (int)party._activeParty.size();
+ if (!scripts._v2) {
+ for (idx = 0; idx < (int)party._activeParty.size(); ++idx) {
+ Character &c = party._activeParty[idx];
+ Condition condition = c.worstCondition();
+
+ if (!(condition >= UNCONSCIOUS && condition <= ERADICATED)) {
+ if (!selectedIndex1) {
+ selectedIndex1 = idx + 1;
+ } else {
+ selectedIndex2 = idx + 1;
+ break;
+ }
+ }
+ }
+ }
+ if (idx == (int)party._activeParty.size()) {
+ selectedIndex1 = scripts._v2 ? charIndex : 0;
+ goto loop;
+ }
+
+ for (;;) {
+ // The if below is to get around errors due to the
+ // goto I was forced to use when reimplementing this method
+ if (true) {
+ Character &c = party._activeParty[selectedIndex1];
+ c._conditions[ASLEEP] = 0; // Force character to be awake
+
+ int frame = 0, fx = 0;
+ switch (attackType) {
+ case DT_PHYSICAL:
+ fx = 29;
+ break;
+ case DT_MAGICAL:
+ frame = 6;
+ fx = 27;
+ break;
+ case DT_FIRE:
+ damage -= party._fireResistence;
+ frame = 1;
+ fx = 22;
+ break;
+ case DT_ELECTRICAL:
+ damage -= party._electricityResistence;
+ frame = 2;
+ fx = 23;
+ break;
+ case DT_COLD:
+ damage -= party._coldResistence;
+ frame = 3;
+ fx = 24;
+ break;
+ case DT_POISON:
+ damage -= party._poisonResistence;
+ frame = 4;
+ fx = 26;
+ break;
+ case DT_ENERGY:
+ frame = 5;
+ fx = 25;
+ break;
+ case DT_SLEEP:
+ fx = 38;
+ break;
+ default:
+ break;
+ }
+
+ // All attack types other than physical allow for saving
+ // throws to reduce the damage
+ if (attackType != DT_PHYSICAL) {
+ while (c.charSavingThrow(attackType) && damage > 0)
+ damage /= 2;
+ }
+
+ // Draw the attack effect on the character sprite
+ sound.playFX(fx);
+ _powSprites.draw(screen, frame,
+ Common::Point(CHAR_FACES_X[selectedIndex1], 150));
+ screen._windows[33].update();
+
+ // Reduce damage if power shield active, and set it zero
+ // if the damage amount has become negative.. you wouldn't
+ // want attacks healing the characters
+ if (party._powerShield)
+ damage -= party._powerShield;
+ if (damage < 0)
+ damage = 0;
+
+ // TODO: This seems weird.. maybe I've got attack types wrong..
+ // why should attack type 7 (DT_SLEEP) set the dead condition?
+ if (attackType == DT_SLEEP) {
+ damage = c._currentHp;
+ c._conditions[DEAD] = 1;
+ }
+
+ // Subtract the hit points from the character
+ c.subtractHitPoints(damage);
+ }
+
+ if (selectedIndex2) {
+ ++selectedIndex1;
+loop:
+ if ((scripts._v2 ? charIndex1 : (int)party._activeParty.size()) > selectedIndex1)
+ break;
+ }
+
+ // Break check and if not, move to other index
+ if (!selectedIndex2 || breakFlag)
+ break;
+
+ selectedIndex1 = selectedIndex2 - 1;
+ breakFlag = true;
+ }
+}
+
+void Combat::doCharDamage(Character &c, int charNum, int monsterDataIndex) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ MonsterStruct &monsterData = map._monsterData[monsterDataIndex];
+
+ // Attacked characters are automatically woken up
+ c._conditions[ASLEEP] = 0;
+
+ // Figure out the damage amount
+ int damage = 0;
+ for (int idx = 0; idx < monsterData._strikes; ++idx)
+ damage += _vm->getRandomNumber(1, monsterData._dmgPerStrike);
+
+
+ int fx = 29, frame = 0;
+ if (monsterData._attackType) {
+ if (c.charSavingThrow(monsterData._attackType))
+ damage /= 2;
+
+ switch (monsterData._attackType) {
+ case DT_MAGICAL:
+ frame = 6;
+ fx = 27;
+ break;
+ case DT_FIRE:
+ damage -= party._fireResistence;
+ frame = 1;
+ fx = 22;
+ break;
+ case DT_ELECTRICAL:
+ damage -= party._electricityResistence;
+ frame = 2;
+ fx = 23;
+ break;
+ case DT_COLD:
+ damage -= party._coldResistence;
+ frame = 3;
+ fx = 24;
+ break;
+ case DT_POISON:
+ damage -= party._poisonResistence;
+ frame = 4;
+ fx = 26;
+ break;
+ case DT_ENERGY:
+ frame = 5;
+ fx = 25;
+ break;
+ default:
+ break;
+ }
+
+ while (damage > 0 && c.charSavingThrow(monsterData._attackType))
+ damage /= 2;
+ }
+
+ sound.playFX(fx);
+ intf._charPowSprites.draw(screen, frame, Common::Point(CHAR_FACES_X[charNum], 150));
+ screen._windows[33].update();
+
+ damage -= party._powerShield;
+ if (damage > 0 && monsterData._specialAttack && !c.charSavingThrow(DT_PHYSICAL)) {
+ switch (monsterData._specialAttack) {
+ case SA_POISON:
+ if (!++c._conditions[POISONED])
+ c._conditions[POISONED] = -1;
+ sound.playFX(26);
+ break;
+ case SA_DISEASE:
+ if (!++c._conditions[DISEASED])
+ c._conditions[DISEASED] = -1;
+ sound.playFX(26);
+ break;
+ case SA_INSANE:
+ if (!++c._conditions[INSANE])
+ c._conditions[INSANE] = -1;
+ sound.playFX(28);
+ break;
+ case SA_SLEEP:
+ if (!++c._conditions[ASLEEP])
+ c._conditions[ASLEEP] = -1;
+ sound.playFX(36);
+ break;
+ case SA_CURSEITEM:
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ if (c._weapons[idx]._id != 34)
+ c._weapons[idx]._bonusFlags |= ITEMFLAG_CURSED;
+ c._armor[idx]._bonusFlags |= ITEMFLAG_CURSED;
+ c._accessories[idx]._bonusFlags |= ITEMFLAG_CURSED;
+ c._misc[idx]._bonusFlags |= ITEMFLAG_CURSED;;
+ }
+ sound.playFX(37);
+ break;
+ case SA_DRAINSP:
+ c._currentSp = 0;
+ sound.playFX(37);
+ break;
+ case SA_CURSE:
+ if (!++c._conditions[CURSED])
+ c._conditions[CURSED] = -1;
+ sound.playFX(37);
+ break;
+ case SA_PARALYZE:
+ if (!++c._conditions[PARALYZED])
+ c._conditions[PARALYZED] = -1;
+ sound.playFX(37);
+ break;
+ case SA_UNCONSCIOUS:
+ if (!++c._conditions[UNCONSCIOUS])
+ c._conditions[UNCONSCIOUS] = -1;
+ sound.playFX(37);
+ break;
+ case SA_CONFUSE:
+ if (!++c._conditions[CONFUSED])
+ c._conditions[CONFUSED] = -1;
+ sound.playFX(28);
+ break;
+ case SA_BREAKWEAPON:
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ XeenItem &weapon = c._weapons[idx];
+ if (weapon._id != 34 && weapon._id != 0 && weapon._frame != 0) {
+ weapon._bonusFlags |= ITEMFLAG_BROKEN;
+ weapon._frame = 0;
+ }
+ }
+ sound.playFX(37);
+ break;
+ case SA_WEAKEN:
+ if (!++c._conditions[WEAK])
+ c._conditions[WEAK] = -1;
+ sound.playFX(36);
+ break;
+ case SA_ERADICATE:
+ if (!++c._conditions[ERADICATED])
+ c._conditions[ERADICATED] = -1;
+ c._items.breakAllItems();
+ sound.playFX(37);
+
+ if (c._currentHp > 0)
+ c._currentHp = 0;
+ break;
+ case SA_AGING:
+ ++c._tempAge;
+ sound.playFX(37);
+ break;
+ case SA_DEATH:
+ if (!++c._conditions[DEAD])
+ c._conditions[DEAD] = -1;
+ sound.playFX(38);
+ if (c._currentHp > 0)
+ c._currentHp = 0;
+ break;
+ case SA_STONE:
+ if (!++c._conditions[STONED])
+ c._conditions[STONED] = -1;
+ sound.playFX(38);
+ if (c._currentHp > 0)
+ c._currentHp = 0;
+ break;
+
+ default:
+ break;
+ }
+
+ c.subtractHitPoints(damage);
+ }
+
+ events.ipause(2);
+ intf.drawParty(true);
+}
+
+void Combat::moveMonsters() {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+
+ if (!_moveMonsters)
+ return;
+
+ intf._tillMove = 0;
+ if (intf._charsShooting)
+ return;
+
+ Common::fill(&_monsterMap[0][0], &_monsterMap[32][32], 0);
+ Common::fill(&_monsterMoved[0], &_monsterMoved[MAX_NUM_MONSTERS], false);
+ Common::fill(&_rangeAttacking[0], &_rangeAttacking[MAX_NUM_MONSTERS], false);
+ Common::fill(&_gmonHit[0], &_gmonHit[36], -1);
+ _dangerPresent = false;
+
+ for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
+ MazeMonster &monster = map._mobData._monsters[idx];
+ if (monster._position.y < 32) {
+ _monsterMap[monster._position.y][monster._position.x]++;
+ }
+ }
+
+ for (int loopNum = 0; loopNum < 2; ++loopNum) {
+ int arrIndex = -1;
+ for (int yDiff = 3; yDiff >= -3; --yDiff) {
+ for (int xDiff = -3; xDiff <= 3; ++xDiff) {
+ Common::Point pt = party._mazePosition + Common::Point(xDiff, yDiff);
+ ++arrIndex;
+
+ for (int idx = 0; idx < (int)map._mobData._monsters.size(); ++idx) {
+ MazeMonster &monster = map._mobData._monsters[idx];
+ MonsterStruct &monsterData = *monster._monsterData;
+
+ if (pt == monster._position) {
+ _dangerPresent = true;
+ if ((monster._isAttacking || _vm->_mode == MODE_SLEEPING)
+ && !_monsterMoved[idx]) {
+ if (party._mazePosition.x == pt.x || party._mazePosition.y == pt.y) {
+ // Check for range attacks
+ if (monsterData._rangeAttack && !_rangeAttacking[idx]
+ && _attackMonsters[0] != idx && _attackMonsters[1] != idx
+ && _attackMonsters[2] != idx && !monster._damageType) {
+ // Setup monster for attacking
+ setupMonsterAttack(monster._spriteId, pt);
+ _rangeAttacking[idx] = true;
+ }
+ }
+
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ case DIR_SOUTH:
+ if (monsterCanMove(pt, MONSTER_GRID_BITMASK[MONSTER_GRID_BITINDEX1[arrIndex]],
+ MONSTER_GRID_X[arrIndex], MONSTER_GRID_Y[arrIndex], idx)) {
+ // Move the monster
+ moveMonster(idx, Common::Point(MONSTER_GRID_X[arrIndex], MONSTER_GRID_Y[arrIndex]));
+ } else {
+ if (monsterCanMove(pt, MONSTER_GRID_BITMASK[MONSTER_GRID_BITINDEX2[arrIndex]],
+ arrIndex >= 21 && arrIndex <= 27 ? MONSTER_GRID3[arrIndex] : 0,
+ arrIndex >= 21 && arrIndex <= 27 ? 0 : MONSTER_GRID3[arrIndex],
+ idx)) {
+ if (arrIndex >= 21 && arrIndex <= 27) {
+ moveMonster(idx, Common::Point(MONSTER_GRID3[arrIndex], 0));
+ } else {
+ moveMonster(idx, Common::Point(0, MONSTER_GRID3[arrIndex]));
+ }
+ }
+ }
+ break;
+
+ case DIR_EAST:
+ case DIR_WEST:
+ if (monsterCanMove(pt, MONSTER_GRID_BITMASK[MONSTER_GRID_BITINDEX2[arrIndex]],
+ arrIndex >= 21 && arrIndex <= 27 ? MONSTER_GRID3[arrIndex] : 0,
+ arrIndex >= 21 && arrIndex <= 27 ? 0 : MONSTER_GRID3[arrIndex],
+ idx)) {
+ if (arrIndex >= 21 && arrIndex <= 27) {
+ moveMonster(idx, Common::Point(MONSTER_GRID3[arrIndex], 0));
+ } else {
+ moveMonster(idx, Common::Point(0, MONSTER_GRID3[arrIndex]));
+ }
+ } else if (monsterCanMove(pt, MONSTER_GRID_BITMASK[MONSTER_GRID_BITINDEX1[arrIndex]],
+ MONSTER_GRID_X[arrIndex], MONSTER_GRID_Y[arrIndex], idx)) {
+ moveMonster(idx, Common::Point(MONSTER_GRID_X[arrIndex], MONSTER_GRID_Y[arrIndex]));
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ monsterOvercome();
+ if (_monstersAttacking)
+ monstersAttack();
+}
+
+void Combat::monstersAttack() {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ int powNum = -1;
+ MonsterStruct *monsterData = nullptr;
+ OutdoorDrawList &outdoorList = intf._outdoorList;
+ IndoorDrawList &indoorList = intf._indoorList;
+
+ for (int idx = 0; idx < 36; ++idx) {
+ if (_gmonHit[idx] != -1) {
+ monsterData = &map._monsterData[_gmonHit[idx]];
+ powNum = MONSTER_SHOOT_POW[monsterData->_attackType];
+ if (powNum != 12)
+ break;
+ }
+ }
+
+ _powSprites.load(Common::String::format("pow%d.icn", powNum));
+ sound.playFX(ATTACK_TYPE_FX[monsterData->_attackType]);
+
+ for (int charNum = 0; charNum < MAX_PARTY_COUNT; ++charNum) {
+ if (!_shooting[charNum])
+ continue;
+
+ if (map._isOutdoors) {
+ outdoorList._attackImgs1[charNum]._scale = 3;
+ outdoorList._attackImgs2[charNum]._scale = 7;
+ outdoorList._attackImgs3[charNum]._scale = 11;
+ outdoorList._attackImgs4[charNum]._scale = 15;
+ outdoorList._attackImgs1[charNum]._sprites = nullptr;
+ outdoorList._attackImgs2[charNum]._sprites = nullptr;
+ outdoorList._attackImgs3[charNum]._sprites = nullptr;
+ outdoorList._attackImgs4[charNum]._sprites = nullptr;
+
+ switch (_shooting[charNum]) {
+ case 1:
+ outdoorList._attackImgs1[charNum]._sprites = &_powSprites;
+ break;
+ case 2:
+ outdoorList._attackImgs2[charNum]._sprites = &_powSprites;
+ break;
+ default:
+ outdoorList._attackImgs3[charNum]._sprites = &_powSprites;
+ break;
+ }
+ } else {
+ indoorList._attackImgs1[charNum]._scale = 3;
+ indoorList._attackImgs2[charNum]._scale = 7;
+ indoorList._attackImgs3[charNum]._scale = 11;
+ indoorList._attackImgs4[charNum]._scale = 15;
+ indoorList._attackImgs1[charNum]._sprites = nullptr;
+ indoorList._attackImgs2[charNum]._sprites = nullptr;
+ indoorList._attackImgs3[charNum]._sprites = nullptr;
+ indoorList._attackImgs4[charNum]._sprites = nullptr;
+
+ switch (_shooting[charNum]) {
+ case 1:
+ indoorList._attackImgs1[charNum]._sprites = &_powSprites;
+ break;
+ case 2:
+ indoorList._attackImgs2[charNum]._sprites = &_powSprites;
+ break;
+ default:
+ indoorList._attackImgs3[charNum]._sprites = &_powSprites;
+ break;
+ }
+ }
+ }
+
+ // Wait whilst the attacking effect is done
+ do {
+ intf.draw3d(true);
+ events.pollEventsAndWait();
+ } while (!_vm->shouldQuit() && intf._isAttacking);
+
+ endAttack();
+
+ if (_vm->_mode != MODE_COMBAT) {
+ // Combat wasn't previously active, but it is now. Set up
+ // the combat party from the currently active party
+ setupCombatParty();
+ }
+
+ for (int idx = 0; idx < 36; ++idx) {
+ if (_gmonHit[idx] != -1)
+ doMonsterTurn(_gmonHit[idx]);
+ }
+
+ _monstersAttacking = false;
+
+ if (_vm->_mode != MODE_SLEEPING) {
+ for (uint charNum = 0; charNum < party._activeParty.size(); ++charNum) {
+ Condition condition = party._activeParty[charNum].worstCondition();
+
+ if (condition != ASLEEP && (condition < PARALYZED || condition == NO_CONDITION)) {
+ _vm->_mode = MODE_1;
+ break;
+ }
+ }
+ }
+}
+
+void Combat::setupMonsterAttack(int monsterDataIndex, const Common::Point &pt) {
+ Party &party = *_vm->_party;
+
+ for (int idx = 0; idx < 36; ++idx) {
+ if (_gmonHit[idx] != -1) {
+ int result = stopAttack(pt - party._mazePosition);
+ if (result) {
+ _monstersAttacking = true;
+ _gmonHit[idx] = monsterDataIndex;
+
+ if (result != 1) {
+ for (int charNum = 0; charNum < MAX_PARTY_COUNT; ++charNum) {
+ if (!_shooting[charNum]) {
+ _shooting[charNum] = COMBAT_SHOOTING[result - 1];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+bool Combat::monsterCanMove(const Common::Point &pt, int wallShift,
+ int xDiff, int yDiff, int monsterId) {
+ Map &map = *_vm->_map;
+ MazeMonster &monster = map._mobData._monsters[monsterId];
+ MonsterStruct &monsterData = *monster._monsterData;
+
+ Common::Point tempPos = pt;
+ if (map._isOutdoors) {
+ tempPos += Common::Point(xDiff, yDiff);
+ wallShift = 4;
+ }
+ int v = map.mazeLookup(tempPos, wallShift);
+
+ if (!map._isOutdoors) {
+ return v <= map.mazeData()._difficulties._wallNoPass;
+ } else {
+ SurfaceType surfaceType;
+ switch (v) {
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 8:
+ case 11:
+ case 13:
+ case 14:
+ surfaceType = (SurfaceType)map.mazeData()._surfaceTypes[map._currentSurfaceId];
+ if (surfaceType == SURFTYPE_WATER || surfaceType == SURFTYPE_DWATER) {
+ return monsterData._flying || monster._spriteId == 59;
+ } else if (surfaceType == SURFTYPE_SPACE) {
+ return monsterData._flying;
+ } else {
+ return _vm->_files->_isDarkCc || monster._spriteId != 59;
+ }
+ default:
+ return v <= map.mazeData()._difficulties._wallNoPass;
+ }
+ }
+}
+
+void Combat::moveMonster(int monsterId, const Common::Point &moveDelta) {
+ Map &map = *_vm->_map;
+ MazeMonster &monster = map._mobData._monsters[monsterId];
+ Common::Point newPos = monster._position + moveDelta;
+
+ if (_monsterMap[newPos.y][newPos.x] < 3 && !monster._damageType && _moveMonsters) {
+ // Adjust monster's position
+ ++_monsterMap[newPos.y][newPos.x];
+ --_monsterMap[monster._position.y][monster._position.x];
+ monster._position = newPos;
+ _monsterMoved[monsterId] = true;
+ }
+}
+
+void Combat::endAttack() {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ intf._isAttacking = false;
+ IndoorDrawList &indoorList = intf._indoorList;
+ OutdoorDrawList &outdoorList = intf._outdoorList;
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (map._isOutdoors) {
+ outdoorList._attackImgs1[idx]._scale = 0;
+ outdoorList._attackImgs2[idx]._scale = 0;
+ outdoorList._attackImgs3[idx]._scale = 0;
+ outdoorList._attackImgs4[idx]._scale = 0;
+ outdoorList._attackImgs1[idx]._sprites = nullptr;
+ outdoorList._attackImgs2[idx]._sprites = nullptr;
+ outdoorList._attackImgs3[idx]._sprites = nullptr;
+ outdoorList._attackImgs4[idx]._sprites = nullptr;
+ } else {
+ indoorList._attackImgs1[idx]._scale = 0;
+ indoorList._attackImgs2[idx]._scale = 0;
+ indoorList._attackImgs3[idx]._scale = 0;
+ indoorList._attackImgs4[idx]._scale = 0;
+ indoorList._attackImgs1[idx]._sprites = nullptr;
+ indoorList._attackImgs2[idx]._sprites = nullptr;
+ indoorList._attackImgs3[idx]._sprites = nullptr;
+ indoorList._attackImgs4[idx]._sprites = nullptr;
+ }
+ }
+
+ Common::fill(&_shooting[0], &_shooting[MAX_PARTY_COUNT], false);
+}
+
+void Combat::monsterOvercome() {
+ Map &map = *_vm->_map;
+
+ for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
+ MazeMonster &monster = map._mobData._monsters[idx];
+ int dataIndex = monster._spriteId;
+
+ if (monster._damageType != DT_PHYSICAL && monster._damageType != DT_DRAGONSLEEP) {
+ // Do a saving throw for monster
+ if (dataIndex <= _vm->getRandomNumber(1, dataIndex + 50))
+ monster._damageType = 0;
+ }
+ }
+}
+
+void Combat::doMonsterTurn(int monsterId) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ if (_monstersAttacking) {
+ int monsterIndex;
+ switch (_whosTurn - _combatParty.size()) {
+ case 0:
+ monsterIndex = _attackMonsters[0];
+ intf._indoorList[156]._scale = 0;
+ break;
+ case 1:
+ monsterIndex = _attackMonsters[1];
+ intf._indoorList[150]._scale = 0;
+ break;
+ case 2:
+ default:
+ monsterIndex = _attackMonsters[2];
+ intf._indoorList[153]._scale = 0;
+ }
+
+ MazeMonster &monster = map._mobData._monsters[monsterIndex];
+ MonsterStruct &monsterData = *monster._monsterData;
+ if (monster._damageType)
+ return;
+
+ monster._frame = 8;
+ monster._fieldA = 3;
+ monster._field9 = 0;
+ intf.draw3d(true);
+ intf.draw3d(true);
+
+ File f(Common::String::format("%s.voc", monsterData._attackVoc.c_str()));
+ sound.playSample(&f, 0);
+ monsterId = monster._spriteId;
+ }
+
+ MonsterStruct &monsterData = map._monsterData[monsterId];
+ bool flag = false;
+ for (int attackNum = 0; attackNum < monsterData._numberOfAttacks; ++attackNum) {
+ int charNum = -1;
+ bool isHated = false;
+
+ if (monsterData._hatesClass != -1) {
+ if (monsterData._hatesClass == 15)
+ // Monster hates all classes
+ goto loop;
+
+ for (uint charIndex = 0; charIndex < _combatParty.size(); ++charIndex) {
+ Character &c = *_combatParty[charIndex];
+ Condition cond = c.worstCondition();
+ if (cond >= PARALYZED && cond <= ERADICATED)
+ continue;
+
+ isHated = false;
+ switch (monsterData._hatesClass) {
+ case CLASS_KNIGHT:
+ case CLASS_PALADIN:
+ case CLASS_ARCHER:
+ case CLASS_CLERIC:
+ case CLASS_SORCERER:
+ case CLASS_ROBBER:
+ case CLASS_NINJA:
+ case CLASS_BARBARIAN:
+ case CLASS_DRUID:
+ case CLASS_RANGER:
+ isHated = c._class == monsterData._hatesClass;
+ break;
+ case 12:
+ isHated = c._race == DWARF;
+ break;
+ default:
+ break;
+ }
+
+ if (isHated) {
+ charNum = charIndex;
+ break;
+ }
+ }
+ }
+
+ if (!isHated) {
+ // No particularly hated foe, so decide which character to start with
+ switch (_combatParty.size()) {
+ case 1:
+ charNum = 0;
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ charNum = _vm->getRandomNumber(0, _combatParty.size() - 1);
+ break;
+ case 6:
+ if (_vm->getRandomNumber(1, 6) == 6)
+ charNum = 5;
+ else
+ charNum = _vm->getRandomNumber(0, 4);
+ break;
+ }
+ }
+
+ // Attacking loop
+ do {
+ if (!flag) {
+ Condition cond = _combatParty[charNum]->worstCondition();
+
+ if (cond >= PARALYZED && cond <= ERADICATED) {
+ Common::Array<int> ableChars;
+ bool skip = false;
+
+ for (uint idx = 0; idx < _combatParty.size() && !skip; ++idx) {
+ switch (_combatParty[idx]->worstCondition()) {
+ case PARALYZED:
+ case UNCONSCIOUS:
+ if (flag)
+ skip = true;
+ break;
+ case DEAD:
+ case STONED:
+ case ERADICATED:
+ break;
+ default:
+ ableChars.push_back(idx);
+ break;
+ }
+ }
+
+ if (!skip) {
+ if (ableChars.size() == 0) {
+ party._dead = true;
+ _vm->_mode = MODE_1;
+ return;
+ }
+
+ charNum = ableChars[_vm->getRandomNumber(0, ableChars.size() - 1)];
+ }
+ }
+ }
+
+ // Unconditional if to get around goto initialization errors
+ if (true) {
+ Character &c = *_combatParty[charNum];
+ if (monsterData._attackType != DT_PHYSICAL || c._conditions[ASLEEP]) {
+ doCharDamage(c, charNum, monsterId);
+ } else {
+ int v = _vm->getRandomNumber(1, 20);
+ if (v == 1) {
+ sound.playFX(6);
+ } else {
+ if (v == 20)
+ doCharDamage(c, charNum, monsterId);
+ v += monsterData._hitChance / 4 + _vm->getRandomNumber(1,
+ monsterData._hitChance);
+
+ int ac = c.getArmorClass() + (!_charsBlocked[charNum] ? 10 :
+ c.getCurrentLevel() / 2 + 15);
+ if (ac > v) {
+ sound.playFX(6);
+ } else {
+ doCharDamage(c, charNum, monsterId);
+ }
+ }
+ }
+
+ if (flag)
+ break;
+ }
+loop:
+ flag = true;
+ } while (++charNum < (int)_combatParty.size());
+ }
+
+ intf.drawParty(true);
+}
+
+int Combat::stopAttack(const Common::Point &diffPt) {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Direction dir = party._mazeDirection;
+ const Common::Point &mazePos = party._mazePosition;
+
+ if (map._isOutdoors) {
+ if (diffPt.x > 0) {
+ for (int x = 1; x <= diffPt.x; ++x) {
+ int v = map.mazeLookup(Common::Point(mazePos.x + x, mazePos.y), 0, 8);
+ if (v)
+ return 0;
+ }
+ return (dir == DIR_EAST) ? diffPt.x + 1 : 1;
+
+ } else if (diffPt.x < 0) {
+ for (int x = diffPt.x; x < 0; ++x) {
+ int v = map.mazeLookup(Common::Point(mazePos.x + x, mazePos.y), 4);
+ switch (v) {
+ case 0:
+ case 2:
+ case 4:
+ case 5:
+ case 8:
+ case 11:
+ case 13:
+ case 14:
+ break;
+ default:
+ return 0;
+ }
+ }
+ return dir == DIR_WEST ? diffPt.x * -1 + 1 : 1;
+
+ } else if (diffPt.y <= 0) {
+ for (int y = diffPt.y; y < 0; ++y) {
+ int v = map.mazeLookup(Common::Point(mazePos.x, mazePos.y + y), 4);
+ switch (v) {
+ case 0:
+ case 2:
+ case 4:
+ case 5:
+ case 8:
+ case 11:
+ case 13:
+ case 14:
+ break;
+ default:
+ return 0;
+ }
+ }
+ return party._mazeDirection == DIR_SOUTH ? diffPt.y * -1 + 1 : 1;
+
+ } else {
+ for (int y = 1; y <= diffPt.y; ++y) {
+ int v = map.mazeLookup(Common::Point(mazePos.x, mazePos.y + y), 4);
+ switch (v) {
+ case 0:
+ case 2:
+ case 4:
+ case 5:
+ case 8:
+ case 11:
+ case 13:
+ case 14:
+ break;
+ default:
+ return 0;
+ }
+ }
+ return dir == DIR_NORTH ? diffPt.y + 1 : 1;
+ }
+ } else {
+ // Indoors
+ if (diffPt.x > 0) {
+ for (int x = 1; x <= diffPt.x; ++x) {
+ int v = map.mazeLookup(Common::Point(mazePos.x + x, mazePos.y), 0, 8);
+ if (v)
+ return 0;
+ }
+ return dir == DIR_EAST ? diffPt.x + 1 : 1;
+
+ } else if (diffPt.x < 0) {
+ for (int x = diffPt.x; x < 0; ++x) {
+ int v = map.mazeLookup(Common::Point(mazePos.x + x, mazePos.y), 0, 0x800);
+ if (v)
+ return 0;
+ }
+ return dir == DIR_WEST ? diffPt.x * -1 + 1 : 1;
+
+ } else if (diffPt.y <= 0) {
+ for (int y = diffPt.y; y < 0; ++y) {
+ int v = map.mazeLookup(Common::Point(mazePos.x, mazePos.y + y), 0, 0x8000);
+ if (v)
+ return 0;
+ }
+ return dir == DIR_SOUTH ? diffPt.y * -1 + 1 : 1;
+
+ } else {
+ for (int y = 1; y <= diffPt.y; ++y) {
+ int v = map.mazeLookup(Common::Point(mazePos.x, mazePos.y + y), 0, 0x80);
+ if (v)
+ return 0;
+ }
+ return dir == DIR_NORTH ? diffPt.y + 1 : 1;
+ }
+ }
+}
+
+void Combat::setupCombatParty() {
+ Party &party = *_vm->_party;
+
+ _combatParty.clear();
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx)
+ _combatParty.push_back(&party._activeParty[idx]);
+}
+
+void Combat::setSpeedTable() {
+ Map &map = *_vm->_map;
+ Common::Array<int> charSpeeds;
+ bool hasSpeed = _whosSpeed != -1 && _whosSpeed < (int)_speedTable.size();
+ int oldSpeed = hasSpeed ? _speedTable[_whosSpeed] : 0;
+
+ // Set up speeds for party membres
+ int maxSpeed = 0;
+ for (uint charNum = 0; charNum < _combatParty.size(); ++charNum) {
+ Character &c = *_combatParty[charNum];
+ charSpeeds.push_back(c.getStat(SPEED));
+
+ maxSpeed = MAX(charSpeeds[charNum], maxSpeed);
+ }
+
+ // Add in speeds of attacking monsters
+ for (int monsterNum = 0; monsterNum < 3; ++monsterNum) {
+ if (_attackMonsters[monsterNum] != -1) {
+ MazeMonster &monster = map._mobData._monsters[_attackMonsters[monsterNum]];
+ MonsterStruct &monsterData = *monster._monsterData;
+ charSpeeds.push_back(monsterData._speed);
+
+ maxSpeed = MAX(maxSpeed, monsterData._speed);
+ } else {
+ charSpeeds.push_back(0);
+ }
+ }
+
+ // Populate the _speedTable list with the character/monster indexes
+ // in order of attacking speed
+ _speedTable.clear();
+ for (; maxSpeed >= 0; --maxSpeed) {
+ for (uint idx = 0; idx < charSpeeds.size(); ++idx) {
+ if (charSpeeds[idx] == maxSpeed)
+ _speedTable.push_back(idx);
+ }
+ }
+
+ if (hasSpeed) {
+ if (_speedTable[_whosSpeed] != oldSpeed) {
+ for (uint idx = 0; idx < charSpeeds.size(); ++idx) {
+ if (oldSpeed == _speedTable[idx]) {
+ _whosSpeed = idx;
+ break;
+ }
+ }
+ }
+ }
+}
+
+bool Combat::allHaveGone() const {
+ for (uint idx = 0; idx < _charsGone.size(); ++idx) {
+ if (!_charsGone[idx]) {
+ if (idx >= _combatParty.size()) {
+ return false;
+ } else {
+ Condition condition = _combatParty[idx]->worstCondition();
+ if (condition < PARALYZED || condition == NO_CONDITION)
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Combat::charsCantAct() const {
+ for (uint idx = 0; idx < _combatParty.size(); ++idx) {
+ if (!_combatParty[idx]->isDisabledOrDead())
+ return false;
+ }
+
+ return true;
+}
+
+Common::String Combat::getMonsterDescriptions() {
+ Map &map = *_vm->_map;
+ Common::String lines[3];
+
+ // Get names of monsters attacking, if any
+ for (int idx = 0; idx < 3; ++idx) {
+ if (_attackMonsters[idx] != -1) {
+ MazeMonster &monster = map._mobData._monsters[_attackMonsters[idx]];
+ MonsterStruct &monsterData = *monster._monsterData;
+ int textColor = monster.getTextColor();
+
+ Common::String format = "\n\v020\f%2u%s\fd";
+ format.setChar('2' + idx, 3);
+ lines[idx] = Common::String::format(format.c_str(), textColor,
+ monsterData._name.c_str());
+ }
+ }
+
+ if (_monsterIndex == 2 && _attackMonsters[2] != -1) {
+ _monster2Attack = _attackMonsters[2];
+ } if (_monsterIndex == 1 && _attackMonsters[1] != -1) {
+ _monster2Attack = _attackMonsters[1];
+ } else {
+ _monster2Attack = _attackMonsters[0];
+ _monsterIndex = 0;
+ }
+
+ return Common::String::format(COMBAT_DETAILS, lines[0].c_str(),
+ lines[1].c_str(), lines[2].c_str());
+}
+
+void Combat::attack(Character &c, RangeType rangeType) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ int damage = _monsterDamage;
+
+ if (_monster2Attack == -1)
+ return;
+
+ MazeMonster &monster = map._mobData._monsters[_monster2Attack];
+ int monsterDataIndex = monster._spriteId;
+ MonsterStruct &monsterData = map._monsterData[monsterDataIndex];
+
+ if (rangeType) {
+ if (_shootType != ST_1 || _damageType == DT_MAGIC_ARROW) {
+ if (!monsterData._magicResistence || monsterData._magicResistence <=
+ _vm->getRandomNumber(1, 100 + _oldCharacter->getCurrentLevel())) {
+ if (_monsterDamage != 0) {
+ attack2(damage, rangeType);
+ setSpeedTable();
+ } else {
+ switch (_damageType) {
+ case DT_SLEEP:
+ if (monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID) {
+ if (_vm->getRandomNumber(1, 50 + monsterDataIndex) > monsterDataIndex)
+ monster._damageType = DT_SLEEP;
+ }
+ break;
+ case DT_FINGEROFDEATH:
+ if ((monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID)
+ && !monsterSavingThrow(monsterDataIndex)) {
+ damage = MIN(monster._hp, 50);
+ attack2(damage, RT_ALL);
+ setSpeedTable();
+ }
+ break;
+ case DT_HOLYWORD:
+ if (monsterData._monsterType == MONSTER_UNDEAD) {
+ attack2(monster._hp, RT_ALL);
+ setSpeedTable();
+ }
+ break;
+ case DT_MASS_DISTORTION:
+ attack2(MAX(monster._hp / 2, 1), RT_ALL);
+ setSpeedTable();
+ break;
+ case DT_UNDEAD:
+ if (monsterData._monsterType == MONSTER_UNDEAD)
+ damage = 25;
+ else
+ rangeType = RT_ALL;
+ attack2(damage, rangeType);
+ setSpeedTable();
+ break;
+ case DT_BEASTMASTER:
+ if ((monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID)
+ && !monsterSavingThrow(monsterDataIndex)) {
+ monster._damageType = DT_BEASTMASTER;
+ }
+ break;
+ case DT_DRAGONSLEEP:
+ if (monsterData._monsterType == MONSTER_DRAGON && !monsterSavingThrow(monsterDataIndex))
+ monster._damageType = DT_DRAGONSLEEP;
+ break;
+ case DT_GOLEMSTOPPER:
+ if (monsterData._monsterType == MONSTER_GOLEM) {
+ attack2(100, rangeType);
+ setSpeedTable();
+ }
+ break;
+ case DT_HYPNOTIZE:
+ if ((monsterData._monsterType == MONSTER_ANIMAL || monsterData._monsterType == MONSTER_HUMANOID)
+ && !monsterSavingThrow(monsterDataIndex)) {
+ monster._damageType = _damageType;
+ }
+ break;
+ case DT_INSECT_SPRAY:
+ if (monsterData._monsterType == MONSTER_INSECT) {
+ attack2(25, rangeType);
+ setSpeedTable();
+ }
+ break;
+ case DT_MAGIC_ARROW:
+ attack2(8, rangeType);
+ setSpeedTable();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ } else {
+ Common::fill(&_elemPow[0], &_elemPow[PARTY_AND_MONSTERS], ELEM_FIRE);
+ damage = 0;
+
+ for (uint charIndex = 0; charIndex < party._activeParty.size(); ++charIndex) {
+ Character &ch = party._activeParty[charIndex];
+
+ if (_shooting[charIndex] && !_missedShot[charIndex]) {
+ if (!hitMonster(ch, rangeType)) {
+ ++_missedShot[charIndex];
+ } else {
+ damage = _monsterDamage ? _monsterDamage : _weaponDamage;
+ _shooting[charIndex] = 0;
+ attack2(damage, rangeType);
+
+ if (map._isOutdoors) {
+ intf._outdoorList._attackImgs1[charIndex]._scale = 0;
+ intf._outdoorList._attackImgs1[charIndex]._sprites = nullptr;
+ intf._outdoorList._attackImgs2[charIndex]._scale = 0;
+ intf._outdoorList._attackImgs2[charIndex]._sprites = nullptr;
+ intf._outdoorList._attackImgs3[charIndex]._scale = 0;
+ intf._outdoorList._attackImgs3[charIndex]._sprites = nullptr;
+ intf._outdoorList._attackImgs4[charIndex]._scale = 0;
+ intf._outdoorList._attackImgs4[charIndex]._sprites = nullptr;
+ } else {
+ intf._indoorList._attackImgs1[charIndex]._scale = 0;
+ intf._indoorList._attackImgs1[charIndex]._sprites = nullptr;
+ intf._indoorList._attackImgs2[charIndex]._scale = 0;
+ intf._indoorList._attackImgs2[charIndex]._sprites = nullptr;
+ intf._indoorList._attackImgs3[charIndex]._scale = 0;
+ intf._indoorList._attackImgs3[charIndex]._sprites = nullptr;
+ intf._indoorList._attackImgs4[charIndex]._scale = 0;
+ intf._indoorList._attackImgs4[charIndex]._sprites = nullptr;
+ }
+
+ if (_monster2Attack == -1)
+ return;
+ }
+ }
+ }
+ }
+ } else {
+ _damageType = DT_PHYSICAL;
+ int divisor = 0;
+ switch (c._class) {
+ case CLASS_BARBARIAN:
+ divisor = 4;
+ break;
+ case CLASS_KNIGHT:
+ case CLASS_NINJA:
+ divisor = 5;
+ break;
+ case CLASS_PALADIN:
+ case CLASS_ARCHER:
+ case CLASS_ROBBER:
+ case CLASS_RANGER:
+ divisor = 6;
+ break;
+ case CLASS_CLERIC:
+ case CLASS_DRUID:
+ divisor = 7;
+ break;
+ case CLASS_SORCERER:
+ divisor = 8;
+ break;
+ default:
+ break;
+ }
+
+ int numberOfAttacks = c.getCurrentLevel() / divisor + 1;
+ damage = 0;
+
+ while (numberOfAttacks-- > 0) {
+ if (hitMonster(c, RT_SINGLE))
+ damage += getMonsterDamage(c);
+ }
+
+ for (int itemIndex = 0; itemIndex < INV_ITEMS_TOTAL; ++itemIndex) {
+ XeenItem &weapon = c._weapons[itemIndex];
+ if (weapon._frame != 0) {
+ switch (weapon._bonusFlags & ITEMFLAG_BONUS_MASK) {
+ case 1:
+ if (monsterData._monsterType == MONSTER_DRAGON)
+ damage *= 3;
+ break;
+ case 2:
+ if (monsterData._monsterType == MONSTER_UNDEAD)
+ damage *= 3;
+ break;
+ case 3:
+ if (monsterData._monsterType == MONSTER_GOLEM)
+ damage *= 3;
+ break;
+ case 4:
+ if (monsterData._monsterType == MONSTER_INSECT)
+ damage *= 3;
+ break;
+ case 5:
+ if (monsterData._monsterType == MONSTER_0)
+ damage *= 3;
+ break;
+ case 6:
+ if (monsterData._monsterType == MONSTER_ANIMAL)
+ damage *= 3;
+ break;
+ }
+ }
+ }
+
+ attack2(damage, rangeType);
+ setSpeedTable();
+ }
+}
+
+void Combat::attack2(int damage, RangeType rangeType) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ MazeMonster &monster = map._mobData._monsters[_monster2Attack];
+ MonsterStruct &monsterData = *monster._monsterData;
+ bool monsterDied = false;
+
+ if (!isDarkCc && damage && rangeType && monster._spriteId == 89)
+ damage = 0;
+
+ if (!damage) {
+ sound.playSample(&_missVoc, 1);
+ sound.playFX(6);
+ } else {
+ if (!isDarkCc && monster._spriteId == 89)
+ damage += 100;
+ if (monster._damageType == DT_SLEEP || monster._damageType == DT_DRAGONSLEEP)
+ monster._damageType = DT_PHYSICAL;
+
+ if ((!rangeType || !_damageType) && _attackWeaponId != 34) {
+ if (monsterData._phsyicalResistence != 0) {
+ if (monsterData._phsyicalResistence == 100) {
+ damage = 0;
+ } else {
+ // This doesn't seem to have any effect?
+ damage = (damage * 100) / 100;
+ }
+ }
+ }
+
+ if (damage) {
+ _charsArray1[_monsterIndex] = 3;
+ _monPow[_monsterIndex] = _damageType == DT_PHYSICAL && (rangeType == 3 || rangeType == 0);
+ monster._frame = 11;
+ monster._fieldA = 5;
+ }
+
+ int monsterResist = getMonsterResistence(rangeType);
+ damage += monsterResist;
+ if (monsterResist > 0) {
+ _elemPow[_monsterIndex] = _attackWeapon->getElementalCategory();
+ _elemScale[_monsterIndex] = getDamageScale(monsterResist);
+ } else if (rangeType != 3) {
+ _elemPow[_monsterIndex] = ELEM_FIRE;
+ }
+
+ if (rangeType != 0 && rangeType != 3) {
+ monster._effect2 = DAMAGE_TYPE_EFFECTS[_damageType];
+ monster._effect1 = 0;
+ }
+
+ if (rangeType && monsterSavingThrow(monster._spriteId)) {
+ switch (_damageType) {
+ case DT_FINGEROFDEATH:
+ case DT_MASS_DISTORTION:
+ damage = 5;
+ break;
+ case DT_SLEEP:
+ case DT_HOLYWORD:
+ case DT_UNDEAD:
+ case DT_BEASTMASTER:
+ case DT_DRAGONSLEEP:
+ case DT_GOLEMSTOPPER:
+ case DT_HYPNOTIZE:
+ case DT_INSECT_SPRAY:
+ case DT_MAGIC_ARROW:
+ break;
+ default:
+ damage /= 2;
+ break;
+ }
+ }
+
+ if (damage < 1) {
+ sound.playSample(&_missVoc, 1);
+ sound.playFX(6);
+ } else {
+ _monsterScale[_monsterIndex] = getDamageScale(damage);
+ intf.draw3d(true);
+
+ sound.playSample(nullptr, 0);
+ File powVoc(Common::String::format("pow%d.voc",
+ POW_WEAPON_VOCS[_attackWeaponId]));
+ sound.playFX(60 + POW_WEAPON_VOCS[_attackWeaponId]);
+ sound.playSample(&powVoc, 1);
+
+ if (monster._hp > damage) {
+ monster._hp -= damage;
+ } else {
+ monster._hp = 0;
+ monsterDied = true;
+ }
+ }
+ }
+
+ intf.draw3d(true);
+
+ if (monsterDied) {
+ if (!isDarkCc) {
+ if (_monster2Attack == 20 && party._mazeId == 41)
+ party._gameFlags[11] = true;
+ if (_monster2Attack == 8 && party._mazeId == 78) {
+ party._gameFlags[60] = true;
+ party._quests[23] = false;
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx)
+ party._activeParty[idx].setAward(42, true);
+
+ if (_monster2Attack == 27 && party._mazeId == 29)
+ party._gameFlags[104] = true;
+ }
+ }
+
+ giveExperience(monsterData._experience);
+
+ if (party._mazeId != 85) {
+ party._treasure._gold = monsterData._gold;
+ party._treasure._gems = monsterData._gems;
+
+ if (!isDarkCc && monster._spriteId == 89) {
+ party._treasure._weapons[0]._id = 90;
+ party._treasure._weapons[0]._bonusFlags = 0;
+ party._treasure._weapons[0]._material = 0;
+ party._treasure._hasItems = true;
+ party._questItems[8]++;
+ }
+
+ int itemDrop = monsterData._itemDrop;
+ if (itemDrop) {
+ if (MONSTER_ITEM_RANGES[itemDrop] >= _vm->getRandomNumber(1, 100)) {
+ Character tempChar;
+ int category = tempChar.makeItem(itemDrop, 0, 0);
+
+ switch (category) {
+ case CATEGORY_WEAPON:
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ if (party._treasure._weapons[idx]._id == 0) {
+ party._treasure._weapons[idx] = tempChar._weapons[0];
+ party._treasure._hasItems = 1;
+ break;
+ }
+ }
+ break;
+ case CATEGORY_ARMOR:
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ if (party._treasure._armor[idx]._id == 0) {
+ party._treasure._armor[idx] = tempChar._armor[0];
+ party._treasure._hasItems = 1;
+ break;
+ }
+ }
+ break;
+ case CATEGORY_ACCESSORY:
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ if (party._treasure._accessories[idx]._id == 0) {
+ party._treasure._accessories[idx] = tempChar._accessories[0];
+ party._treasure._hasItems = 1;
+ break;
+ }
+ }
+ break;
+ case CATEGORY_MISC:
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ if (party._treasure._accessories[idx]._id == 0) {
+ party._treasure._accessories[idx] = tempChar._accessories[0];
+ party._treasure._hasItems = 1;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ monster._position = Common::Point(0x80, 0x80);
+ _charsArray1[_monsterIndex] = 0;
+ _monster2Attack = -1;
+ intf.draw3d(true);
+
+ if (_attackMonsters[0] != -1) {
+ _monster2Attack = _attackMonsters[0];
+ _monsterIndex = 0;
+ }
+ }
+}
+
+void Combat::block() {
+ _charsBlocked[_whosTurn] = true;
+}
+
+void Combat::quickFight() {
+ Spells &spells = *_vm->_spells;
+ Character *c = _combatParty[_whosTurn];
+
+ switch (c->_quickOption) {
+ case QUICK_ATTACK:
+ attack(*c, RT_SINGLE);
+ break;
+ case QUICK_SPELL:
+ if (c->_currentSpell != -1) {
+ spells.castSpell(c, (MagicSpell)SPELLS_ALLOWED[c->getClassCategory()][c->_currentSpell]);
+ }
+ break;
+ case QUICK_BLOCK:
+ block();
+ break;
+ case QUICK_RUN:
+ run();
+ break;
+ default:
+ break;
+ }
+}
+
+void Combat::run() {
+ Map &map = *_vm->_map;
+ SoundManager &sound = *_vm->_sound;
+
+ if (_vm->getRandomNumber(1, 100) < map.mazeData()._difficulties._chance2Run) {
+ // Remove the character from the combat party
+ _combatParty.remove_at(_whosTurn);
+ setSpeedTable();
+ --_whosSpeed;
+ _whosTurn = -1;
+ _partyRan = true;
+ sound.playFX(51);
+ }
+}
+
+bool Combat::hitMonster(Character &c, RangeType rangeType) {
+ Map &map = *_vm->_map;
+ getWeaponDamage(c, rangeType);
+ int chance = c.statBonus(c.getStat(ACCURACY)) + _hitChanceBonus;
+ int divisor = 0;
+
+ switch (c._class) {
+ case CLASS_KNIGHT:
+ case CLASS_BARBARIAN:
+ divisor = 1;
+ break;
+ case CLASS_PALADIN :
+ case CLASS_ARCHER:
+ case CLASS_ROBBER:
+ case CLASS_NINJA:
+ case CLASS_RANGER:
+ divisor = 2;
+ break;
+ case CLASS_CLERIC:
+ case CLASS_DRUID:
+ divisor = 3;
+ break;
+ case CLASS_SORCERER:
+ divisor = 4;
+ break;
+ default:
+ break;
+ }
+
+ chance += c.getCurrentLevel() / divisor;
+ chance -= c._conditions[CURSED];
+
+ // Add on a random amount
+ int v;
+ do {
+ v = _vm->getRandomNumber(1, 20);
+ chance += v;
+ } while (v == 20);
+
+ assert(_monster2Attack != -1);
+ MazeMonster &monster = map._mobData._monsters[_monster2Attack];
+ MonsterStruct &monsterData = *monster._monsterData;
+
+ if (monster._damageType != DT_PHYSICAL)
+ chance += 20;
+
+ return chance >= (monsterData._accuracy + 10);
+}
+
+void Combat::getWeaponDamage(Character &c, RangeType rangeType) {
+ Party &party = *_vm->_party;
+ _attackWeapon = nullptr;
+ _weaponDie = _weaponDice = 0;
+ _weaponDamage = 0;
+ _hitChanceBonus = 0;
+
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ bool flag;
+ if (rangeType) {
+ flag = c._weapons[idx]._frame == 4;
+ } else {
+ flag = c._weapons[idx]._frame == 1 || c._weapons[idx]._frame == 13;
+ }
+
+ if (flag) {
+ if (!(c._weapons[idx]._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED))) {
+ _attackWeapon = &c._weapons[idx];
+
+ if (c._weapons[idx]._material >= 37 && c._weapons[idx]._material < 59) {
+ _hitChanceBonus = METAL_DAMAGE_PERCENT[c._weapons[idx]._material - 37];
+ _weaponDamage = METAL_DAMAGE[c._weapons[idx]._material - 37];
+ }
+ }
+
+ _hitChanceBonus += party._heroism;
+ _attackWeaponId = c._weapons[idx]._id;
+ _weaponDice = WEAPON_DAMAGE_BASE[_attackWeaponId];
+ _weaponDie = WEAPON_DAMAGE_MULTIPLIER[_attackWeaponId];
+
+ for (int diceIdx = 0; diceIdx < _weaponDice; ++diceIdx)
+ _weaponDamage += _vm->getRandomNumber(1, _weaponDie);
+ }
+ }
+
+ if (_weaponDamage < 1)
+ _weaponDamage = 0;
+ if (!party._difficulty) {
+ _hitChanceBonus += 5;
+ _weaponDamage *= 3;
+ }
+}
+
+int Combat::getMonsterDamage(Character &c) {
+ return MAX(c.statBonus(c.getStat(MIGHT)) + _weaponDamage, 1);
+}
+
+int Combat::getDamageScale(int v) {
+ if (v < 10)
+ return 5;
+ else if (v < 100)
+ return 0;
+ else
+ return 0x8000;
+}
+
+int Combat::getMonsterResistence(RangeType rangeType) {
+ Map &map = *_vm->_map;
+ assert(_monster2Attack != -1);
+ MazeMonster &monster = map._mobData._monsters[_monster2Attack];
+ MonsterStruct &monsterData = *monster._monsterData;
+ int resistence = 0, damage = 0;
+
+ if (rangeType != RT_SINGLE && rangeType != RT_3) {
+ switch (_damageType) {
+ case DT_PHYSICAL:
+ resistence = monsterData._phsyicalResistence;
+ break;
+ case DT_MAGICAL:
+ resistence = monsterData._magicResistence;
+ break;
+ case DT_FIRE:
+ resistence = monsterData._fireResistence;
+ break;
+ case DT_ELECTRICAL:
+ resistence = monsterData._electricityResistence;
+ break;
+ case DT_COLD:
+ resistence = monsterData._coldResistence;
+ break;
+ case DT_POISON:
+ resistence = monsterData._poisonResistence;
+ break;
+ case DT_ENERGY:
+ resistence = monsterData._energyResistence;
+ break;
+ default:
+ break;
+ }
+ } else {
+ int material = !_attackWeapon ? 0 : _attackWeapon->_material;
+ damage = ELEMENTAL_DAMAGE[material];
+
+ if (material != 0) {
+ if (material < 9)
+ resistence = monsterData._fireResistence;
+ else if (material < 16)
+ resistence = monsterData._electricityResistence;
+ else if (material < 21)
+ resistence = monsterData._coldResistence;
+ else if (material < 26)
+ resistence = monsterData._poisonResistence;
+ else if (material < 34)
+ resistence = monsterData._energyResistence;
+ else
+ resistence = monsterData._magicResistence;
+ }
+ }
+
+ if (resistence != 0) {
+ if (resistence == 100)
+ return 0;
+ else
+ return ((100 - resistence) * damage) / 100;
+ }
+
+ return damage;
+}
+
+void Combat::giveExperience(int experience) {
+ Party &party = *_vm->_party;
+ bool inCombat = _vm->_mode == MODE_COMBAT;
+ int count = 0;
+
+ // Two loops: first to figure out how many active characters there are,
+ // and the second to distribute the experience between them
+ for (int loopNum = 0; loopNum < 2; ++loopNum) {
+ for (uint charIndex = 0; charIndex < (inCombat ? _combatParty.size() :
+ party._activeParty.size()); ++charIndex) {
+ Character &c = inCombat ? *_combatParty[charIndex] : party._activeParty[charIndex];
+ Condition condition = c.worstCondition();
+
+ if (condition != DEAD && condition != STONED && condition != ERADICATED) {
+ if (loopNum == 0) {
+ ++count;
+ } else {
+ int exp = experience / count;
+ if (c._level._permanent < 15)
+ exp /= 2;
+ c._experience += exp;
+ }
+ }
+ }
+ }
+}
+
+void Combat::multiAttack(int powNum) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ if (_damageType == DT_POISON_VOLLEY) {
+ _damageType = DT_POISON;
+ _shootType = ST_1;
+ Common::fill(&_shooting[0], &_shooting[6], 1);
+ } else if (powNum == 11) {
+ _shootType = ST_1;
+ bool flag = false;
+
+ if (_damageType == DT_PHYSICAL) {
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ Character &c = party._activeParty[idx];
+ if (c.hasMissileWeapon()) {
+ _shooting[idx] = 1;
+ flag = true;
+ }
+ }
+ } else {
+ _shooting[0] = 1;
+ flag = true;
+ }
+
+ if (!flag) {
+ sound.playFX(21);
+ return;
+ }
+ } else {
+ _shooting[0] = 1;
+ _shootType = ST_0;
+ }
+
+ intf._charsShooting = true;
+ _powSprites.load(Common::String::format("pow%d.icn", powNum));
+ int monsterIndex = _monsterIndex;
+ int monster2Attack = _monster2Attack;
+ bool attackedFlag = false;
+
+ Common::Array<int> attackMonsters;
+ for (int idx = 0; idx < 3; ++idx) {
+ if (_attackMonsters[idx] != -1)
+ attackMonsters.push_back(_attackMonsters[idx]);
+ }
+
+ _monsterIndex = -1;
+ if (_monster2Attack != -1) {
+ _monsterIndex--;
+ if (attackMonsters.empty())
+ attackMonsters.resize(1);
+ attackMonsters[0] = monster2Attack;
+ }
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (_shooting[idx]) {
+ if (map._isOutdoors) {
+ intf._outdoorList._attackImgs1[idx]._scale = 0;
+ intf._outdoorList._attackImgs2[idx]._scale = 4;
+ intf._outdoorList._attackImgs3[idx]._scale = 8;
+ intf._outdoorList._attackImgs4[idx]._scale = 12;
+ intf._outdoorList._attackImgs1[idx]._sprites = &_powSprites;
+ intf._outdoorList._attackImgs2[idx]._sprites = nullptr;
+ intf._outdoorList._attackImgs3[idx]._sprites = nullptr;
+ intf._outdoorList._attackImgs4[idx]._sprites = nullptr;
+ } else {
+ intf._indoorList._attackImgs1[idx]._scale = 0;
+ intf._indoorList._attackImgs2[idx]._scale = 4;
+ intf._indoorList._attackImgs3[idx]._scale = 8;
+ intf._indoorList._attackImgs4[idx]._scale = 12;
+ intf._indoorList._attackImgs1[idx]._sprites = &_powSprites;
+ intf._indoorList._attackImgs2[idx]._sprites = nullptr;
+ intf._indoorList._attackImgs3[idx]._sprites = nullptr;
+ intf._indoorList._attackImgs4[idx]._sprites = nullptr;
+ }
+ }
+ }
+
+ intf.draw3d(true);
+
+ ++_monsterIndex;
+ for (uint monIdx = 0; monIdx < attackMonsters.size(); ++monIdx, ++_monsterIndex) {
+ Common::fill(&_missedShot[0], &_missedShot[8], false);
+ _monster2Attack = attackMonsters[monIdx];
+ attack(*_oldCharacter, RT_GROUP);
+ attackedFlag = true;
+
+ if (_rangeType == RT_SINGLE)
+ // Only single shot, so exit now that the attack is done
+ goto finished;
+ }
+
+ if (attackedFlag && _rangeType == RT_GROUP)
+ // Finished group attack, so exit
+ goto finished;
+
+ if (map._isOutdoors) {
+ map.getCell(7);
+ switch (map._currentWall) {
+ case 1:
+ case 3:
+ case 6:
+ case 7:
+ case 9:
+ case 10:
+ case 12:
+ sound.playFX(46);
+ goto finished;
+ default:
+ break;
+ }
+ } else {
+ int cell = map.getCell(2);
+ if (cell < map.mazeData()._difficulties._wallNoPass) {
+ sound.playFX(46);
+ goto finished;
+ }
+ }
+ if (!intf._isAttacking)
+ goto finished;
+
+ intf.draw3d(true);
+
+ // Start handling second teir of monsters in the back
+ attackMonsters.clear();
+ for (uint idx = 3; idx < 6; ++idx) {
+ if (_attackMonsters[idx] != -1)
+ attackMonsters.push_back(_attackMonsters[idx]);
+ }
+
+ ++_monsterIndex;
+ for (uint monIdx = 0; monIdx < attackMonsters.size(); ++monIdx, ++_monsterIndex) {
+ Common::fill(&_missedShot[0], &_missedShot[8], false);
+ _monster2Attack = attackMonsters[monIdx];
+ attack(*_oldCharacter, RT_GROUP);
+ attackedFlag = true;
+
+ if (_rangeType == RT_SINGLE)
+ // Only single shot, so exit now that the attack is done
+ goto finished;
+ }
+
+ if (attackedFlag && _rangeType == RT_GROUP)
+ // Finished group attack, so exit
+ goto finished;
+
+ if (map._isOutdoors) {
+ map.getCell(14);
+ switch (map._currentWall) {
+ case 1:
+ case 3:
+ case 6:
+ case 7:
+ case 9:
+ case 10:
+ case 12:
+ sound.playFX(46);
+ goto finished;
+ default:
+ break;
+ }
+ } else {
+ int cell = map.getCell(7);
+ if (cell < map.mazeData()._difficulties._wallNoPass) {
+ sound.playFX(46);
+ goto finished;
+ }
+ }
+ if (!intf._isAttacking)
+ goto finished;
+
+ intf.draw3d(true);
+
+ // Start handling third teir of monsters in the back
+ attackMonsters.clear();
+ for (uint idx = 6; idx < 9; ++idx) {
+ if (_attackMonsters[idx] != -1)
+ attackMonsters.push_back(_attackMonsters[idx]);
+ }
+
+ ++_monsterIndex;
+ for (uint monIdx = 0; monIdx < attackMonsters.size(); ++monIdx, ++_monsterIndex) {
+ Common::fill(&_missedShot[0], &_missedShot[8], false);
+ _monster2Attack = attackMonsters[monIdx];
+ attack(*_oldCharacter, RT_GROUP);
+ attackedFlag = true;
+
+ if (_rangeType == RT_SINGLE)
+ // Only single shot, so exit now that the attack is done
+ goto finished;
+ }
+
+ if (attackedFlag && _rangeType == RT_GROUP)
+ // Finished group attack, so exit
+ goto finished;
+
+ if (map._isOutdoors) {
+ map.getCell(27);
+ switch (map._currentWall) {
+ case 1:
+ case 3:
+ case 6:
+ case 7:
+ case 9:
+ case 10:
+ case 12:
+ sound.playFX(46);
+ goto finished;
+ default:
+ break;
+ }
+ } else {
+ int cell = map.getCell(14);
+ if (cell < map.mazeData()._difficulties._wallNoPass) {
+ sound.playFX(46);
+ goto finished;
+ }
+ }
+ if (!intf._isAttacking)
+ goto finished;
+
+ intf.draw3d(true);
+
+ // Fourth tier
+ attackMonsters.clear();
+ for (uint idx = 9; idx < 12; ++idx) {
+ if (_attackMonsters[idx] != -1)
+ attackMonsters.push_back(_attackMonsters[idx]);
+ }
+
+ ++_monsterIndex;
+ for (uint monIdx = 0; monIdx < attackMonsters.size(); ++monIdx, ++_monsterIndex) {
+ Common::fill(&_missedShot[0], &_missedShot[8], false);
+ _monster2Attack = attackMonsters[monIdx];
+ attack(*_oldCharacter, RT_GROUP);
+ attackedFlag = true;
+
+ if (_rangeType == RT_SINGLE)
+ // Only single shot, so exit now that the attack is done
+ goto finished;
+ }
+
+ if (!(attackedFlag && _rangeType == RT_GROUP))
+ goto done;
+
+finished:
+ endAttack();
+done:
+ Common::fill(&_shooting[0], &_shooting[MAX_PARTY_COUNT], 0);
+ _monster2Attack = monster2Attack;
+ _monsterIndex = monsterIndex;
+ party.giveTreasure();
+}
+
+void Combat::shootRangedWeapon() {
+ _rangeType = RT_ALL;
+ _damageType = DT_PHYSICAL;
+ multiAttack(11);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/combat.h b/engines/xeen/combat.h
new file mode 100644
index 0000000000..8a7c676018
--- /dev/null
+++ b/engines/xeen/combat.h
@@ -0,0 +1,221 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_COMBAT_H
+#define XEEN_COMBAT_H
+
+#include "common/scummsys.h"
+#include "common/rect.h"
+#include "xeen/files.h"
+#include "xeen/sprites.h"
+
+namespace Xeen {
+
+#define MAX_NUM_MONSTERS 107
+#define PARTY_AND_MONSTERS 12
+
+enum DamageType {
+ DT_PHYSICAL = 0, DT_MAGICAL = 1, DT_FIRE = 2, DT_ELECTRICAL = 3,
+ DT_COLD = 4, DT_POISON = 5, DT_ENERGY = 6, DT_SLEEP = 7,
+ DT_FINGEROFDEATH = 8, DT_HOLYWORD = 9, DT_MASS_DISTORTION = 10,
+ DT_UNDEAD = 11, DT_BEASTMASTER = 12, DT_DRAGONSLEEP = 13,
+ DT_GOLEMSTOPPER = 14, DT_HYPNOTIZE = 15, DT_INSECT_SPRAY = 16,
+ DT_POISON_VOLLEY = 17, DT_MAGIC_ARROW = 18
+};
+
+enum SpecialAttack {
+ SA_NONE = 0, SA_MAGIC = 1, SA_FIRE = 2, SA_ELEC = 3, SA_COLD = 4,
+ SA_POISON = 5, SA_ENERGY = 6, SA_DISEASE = 7, SA_INSANE = 8,
+ SA_SLEEP = 9, SA_CURSEITEM = 10, SA_INLOVE = 11, SA_DRAINSP = 12,
+ SA_CURSE = 13, SA_PARALYZE = 14, SA_UNCONSCIOUS = 15,
+ SA_CONFUSE = 16, SA_BREAKWEAPON = 17, SA_WEAKEN = 18,
+ SA_ERADICATE = 19, SA_AGING = 20, SA_DEATH = 21, SA_STONE = 22
+};
+
+enum ElementalCategory {
+ ELEM_FIRE = 0, ELEM_ELECTRICITY = 1, ELEM_COLD = 2,
+ ELEM_ACID_POISON = 3, ELEM_ENERGY = 4, ELEM_MAGIC = 5
+};
+
+enum RangeType {
+ RT_SINGLE = 0, RT_GROUP = 1, RT_ALL = 2, RT_3 = 3
+};
+
+enum ShootType {
+ ST_0 = 0, ST_1 = 1
+};
+
+enum CombatMode {
+ COMBATMODE_0 = 0, COMBATMODE_1 = 1, COMBATMODE_2 = 2
+};
+
+class XeenEngine;
+class Character;
+class XeenItem;
+
+class Combat {
+private:
+ XeenEngine *_vm;
+
+ void attack2(int damage, RangeType rangeType);
+
+ bool hitMonster(Character &c, RangeType rangeType);
+
+ void getWeaponDamage(Character &c, RangeType rangeType);
+
+ int getMonsterDamage(Character &c);
+
+ int getDamageScale(int v);
+
+ int getMonsterResistence(RangeType rangeType);
+
+ /**
+ * Distribute experience between active party members
+ */
+ void giveExperience(int experience);
+public:
+ Common::Array<Character *> _combatParty;
+ Common::Array<bool> _charsBlocked;
+ Common::Array<int> _charsGone;
+ SpriteResource _powSprites;
+ int _attackMonsters[26];
+ int _monster2Attack;
+ int _charsArray1[PARTY_AND_MONSTERS];
+ bool _monPow[PARTY_AND_MONSTERS];
+ int _monsterScale[PARTY_AND_MONSTERS];
+ ElementalCategory _elemPow[PARTY_AND_MONSTERS];
+ int _elemScale[PARTY_AND_MONSTERS];
+ int _missedShot[8];
+ Common::Array<int> _speedTable;
+ int _shooting[8];
+ int _globalCombat;
+ int _whosTurn;
+ bool _itemFlag;
+ int _monsterMap[32][32];
+ bool _monsterMoved[MAX_NUM_MONSTERS];
+ bool _rangeAttacking[MAX_NUM_MONSTERS];
+ int _gmonHit[36];
+ bool _monstersAttacking;
+ CombatMode _combatMode;
+ int _monsterIndex;
+ bool _partyRan;
+ int _whosSpeed;
+ DamageType _damageType;
+ Character *_oldCharacter;
+ int _monsterDamage;
+ int _weaponDamage;
+ int _weaponDie, _weaponDice;
+ XeenItem *_attackWeapon;
+ int _attackWeaponId;
+ File _missVoc, _pow1Voc;
+ int _hitChanceBonus;
+ bool _dangerPresent;
+ bool _moveMonsters;
+ RangeType _rangeType;
+ ShootType _shootType;
+public:
+ Combat(XeenEngine *vm);
+
+ void clear();
+
+ void giveCharDamage(int damage, DamageType attackType, int charIndex);
+
+ /**
+ * Do damage to a specific character
+ */
+ void doCharDamage(Character &c, int charNum, int monsterDataIndex);
+
+ void moveMonsters();
+
+ /**
+ * Setup the combat party with a copy of the currently active party
+ */
+ void setupCombatParty();
+
+ void setSpeedTable();
+
+ /**
+ * Returns true if all participants in the combat are disabled
+ */
+ bool allHaveGone() const;
+
+ /**
+ * Returns true if all the characters of the party are disabled
+ */
+ bool charsCantAct() const;
+
+ /**
+ * Return a description of the monsters being faced
+ */
+ Common::String getMonsterDescriptions();
+
+ void attack(Character &c, RangeType rangeType);
+
+ /**
+ * Flag the currently active character as blocking/defending
+ */
+ void block();
+
+ /**
+ * Perform whatever the current combat character's quick action is
+ */
+ void quickFight();
+
+ /**
+ * Current selected character is trying to run away
+ */
+ void run();
+
+ void monstersAttack();
+
+ void setupMonsterAttack(int monsterDataIndex, const Common::Point &pt);
+
+ /**
+ * Determines whether a given monster can move
+ */
+ bool monsterCanMove(const Common::Point &pt, int wallShift,
+ int v1, int v2, int monsterId);
+
+ /**
+ * Moves a monster by a given delta amount if it's a valid move
+ */
+ void moveMonster(int monsterId, const Common::Point &moveDelta);
+
+ void doMonsterTurn(int monsterId);
+
+ void endAttack();
+
+ void monsterOvercome();
+
+ int stopAttack(const Common::Point &diffPt);
+
+ void multiAttack(int powNum);
+
+ /**
+ * Fires off a ranged attack at all oncoming monsters
+ */
+ void shootRangedWeapon();
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_COMBAT_H */
diff --git a/engines/xeen/configure.engine b/engines/xeen/configure.engine
new file mode 100644
index 0000000000..489254f269
--- /dev/null
+++ b/engines/xeen/configure.engine
@@ -0,0 +1,3 @@
+# This file is included from the main "configure" script
+# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps]
+add_engine xeen "World of Xeen" no
diff --git a/engines/xeen/debugger.cpp b/engines/xeen/debugger.cpp
new file mode 100644
index 0000000000..d9b4990e97
--- /dev/null
+++ b/engines/xeen/debugger.cpp
@@ -0,0 +1,85 @@
+/* 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 "common/file.h"
+#include "xeen/xeen.h"
+#include "xeen/debugger.h"
+
+namespace Xeen {
+
+static int strToInt(const char *s) {
+ if (!*s)
+ // No string at all
+ return 0;
+ else if (toupper(s[strlen(s) - 1]) != 'H')
+ // Standard decimal string
+ return atoi(s);
+
+ // Hexadecimal string
+ uint tmp = 0;
+ int read = sscanf(s, "%xh", &tmp);
+ if (read < 1)
+ error("strToInt failed on string \"%s\"", s);
+ return (int)tmp;
+}
+
+/*------------------------------------------------------------------------*/
+
+Debugger::Debugger(XeenEngine *vm) : GUI::Debugger(), _vm(vm) {
+ registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
+ registerCmd("spell", WRAP_METHOD(Debugger, cmdSpell));
+
+ _spellId = -1;
+}
+
+void Debugger::update() {
+ Party &party = *_vm->_party;
+ Spells &spells = *_vm->_spells;
+
+ if (_spellId != -1) {
+ // Cast any specified spell
+ MagicSpell spellId = (MagicSpell)_spellId;
+ _spellId = -1;
+ Character *c = &party._activeParty[0];
+ c->_currentSp = 99;
+ spells.castSpell(c, spellId);
+ }
+
+ onFrame();
+}
+
+bool Debugger::cmdSpell(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: spell <spell-id>");
+ return true;
+ } else {
+ int spellId = strToInt(argv[1]);
+ if (spellId >= MS_AcidSpray && spellId <= MS_WizardEye) {
+ _spellId = spellId;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/debugger.h b/engines/xeen/debugger.h
new file mode 100644
index 0000000000..e7f8ef6b7d
--- /dev/null
+++ b/engines/xeen/debugger.h
@@ -0,0 +1,47 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DEBUGGER_H
+#define XEEN_DEBUGGER_H
+
+#include "common/scummsys.h"
+#include "gui/debugger.h"
+
+namespace Xeen {
+
+class XeenEngine;
+
+class Debugger : public GUI::Debugger {
+private:
+ XeenEngine *_vm;
+ int _spellId;
+
+ bool cmdSpell(int argc, const char **argv);
+public:
+ Debugger(XeenEngine *vm);
+
+ void update();
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DEBUGGER_H */
diff --git a/engines/xeen/detection.cpp b/engines/xeen/detection.cpp
new file mode 100644
index 0000000000..e5b137c394
--- /dev/null
+++ b/engines/xeen/detection.cpp
@@ -0,0 +1,196 @@
+/* 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 "xeen/xeen.h"
+#include "xeen/worldofxeen/worldofxeen.h"
+
+#include "base/plugins.h"
+#include "common/savefile.h"
+#include "engines/advancedDetector.h"
+#include "common/system.h"
+
+#define MAX_SAVES 99
+
+namespace Xeen {
+
+struct XeenGameDescription {
+ ADGameDescription desc;
+
+ int gameID;
+ uint32 features;
+};
+
+uint32 XeenEngine::getGameID() const {
+ return _gameDescription->gameID;
+}
+
+uint32 XeenEngine::getGameFeatures() const {
+ return _gameDescription->features;
+}
+
+uint32 XeenEngine::getFeatures() const {
+ return _gameDescription->desc.flags;
+}
+
+Common::Language XeenEngine::getLanguage() const {
+ return _gameDescription->desc.language;
+}
+
+Common::Platform XeenEngine::getPlatform() const {
+ return _gameDescription->desc.platform;
+}
+
+} // End of namespace Xeen
+
+static const PlainGameDescriptor XeenGames[] = {
+ { "xeen", "Xeen" },
+ { "clouds", "Clouds of Xeen" },
+ { "darkside", "Dark Side of Xeen" },
+ { "worldofxeen", "World of Xeen" },
+ {0, 0}
+};
+
+#include "xeen/detection_tables.h"
+
+class XeenMetaEngine : public AdvancedMetaEngine {
+public:
+ XeenMetaEngine() : AdvancedMetaEngine(Xeen::gameDescriptions, sizeof(Xeen::XeenGameDescription), XeenGames) {
+ _maxScanDepth = 3;
+ }
+
+ virtual const char *getName() const {
+ return "Xeen Engine";
+ }
+
+ virtual const char *getOriginalCopyright() const {
+ return "Xeen Engine (c) ???";
+ }
+
+ virtual bool hasFeature(MetaEngineFeature f) const;
+ virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const;
+ virtual SaveStateList listSaves(const char *target) const;
+ virtual int getMaximumSaveSlot() const;
+ virtual void removeSaveState(const char *target, int slot) const;
+ SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const;
+};
+
+bool XeenMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return
+ (f == kSupportsListSaves) ||
+ (f == kSupportsLoadingDuringStartup) ||
+ (f == kSupportsDeleteSave) ||
+ (f == kSavesSupportMetaInfo) ||
+ (f == kSavesSupportThumbnail);
+}
+
+bool Xeen::XeenEngine::hasFeature(EngineFeature f) const {
+ return
+ (f == kSupportsRTL) ||
+ (f == kSupportsLoadingDuringRuntime) ||
+ (f == kSupportsSavingDuringRuntime);
+}
+
+bool XeenMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
+ const Xeen::XeenGameDescription *gd = (const Xeen::XeenGameDescription *)desc;
+
+ switch (gd->gameID) {
+ case Xeen::GType_Clouds:
+ case Xeen::GType_DarkSide:
+ case Xeen::GType_WorldOfXeen:
+ *engine = new Xeen::WorldOfXeenEngine(syst, gd);
+ break;
+ default:
+ break;
+ }
+
+ return gd != 0;
+}
+
+SaveStateList XeenMetaEngine::listSaves(const char *target) const {
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ Common::StringArray filenames;
+ Common::String saveDesc;
+ Common::String pattern = Common::String::format("%s.0??", target);
+ Xeen::XeenSavegameHeader header;
+
+ filenames = saveFileMan->listSavefiles(pattern);
+ sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order
+
+ SaveStateList saveList;
+ for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
+ const char *ext = strrchr(file->c_str(), '.');
+ int slot = ext ? atoi(ext + 1) : -1;
+
+ if (slot >= 0 && slot < MAX_SAVES) {
+ Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file);
+
+ if (in) {
+ Xeen::XeenEngine::readSavegameHeader(in, header);
+ saveList.push_back(SaveStateDescriptor(slot, header._saveName));
+
+ header._thumbnail->free();
+ delete header._thumbnail;
+ delete in;
+ }
+ }
+ }
+
+ return saveList;
+}
+
+int XeenMetaEngine::getMaximumSaveSlot() const {
+ return MAX_SAVES;
+}
+
+void XeenMetaEngine::removeSaveState(const char *target, int slot) const {
+ Common::String filename = Common::String::format("%s.%03d", target, slot);
+ g_system->getSavefileManager()->removeSavefile(filename);
+}
+
+SaveStateDescriptor XeenMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+ Common::String filename = Common::String::format("%s.%03d", target, slot);
+ Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(filename);
+
+ if (f) {
+ Xeen::XeenSavegameHeader header;
+ Xeen::XeenEngine::readSavegameHeader(f, header);
+ delete f;
+
+ // Create the return descriptor
+ SaveStateDescriptor desc(slot, header._saveName);
+ desc.setThumbnail(header._thumbnail);
+ desc.setSaveDate(header._year, header._month, header._day);
+ desc.setSaveTime(header._hour, header._minute);
+ desc.setPlayTime(header._totalFrames * GAME_FRAME_TIME);
+
+ return desc;
+ }
+
+ return SaveStateDescriptor();
+}
+
+
+#if PLUGIN_ENABLED_DYNAMIC(XEEN)
+ REGISTER_PLUGIN_DYNAMIC(XEEN, PLUGIN_TYPE_ENGINE, XeenMetaEngine);
+#else
+ REGISTER_PLUGIN_STATIC(XEEN, PLUGIN_TYPE_ENGINE, XeenMetaEngine);
+#endif
diff --git a/engines/xeen/detection_tables.h b/engines/xeen/detection_tables.h
new file mode 100644
index 0000000000..03290b96d8
--- /dev/null
+++ b/engines/xeen/detection_tables.h
@@ -0,0 +1,67 @@
+/* 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.
+ *
+ */
+
+namespace Xeen {
+
+static const XeenGameDescription gameDescriptions[] = {
+ {
+ // World of Xeen
+ {
+ "worldofxeen",
+ nullptr,
+ {
+ { "xeen.cc", 0, "0cffbab533d9afe140e69ec93096f43e", 13435646 },
+ { "dark.cc", 0, "df194483ecea6abc0511637d712ced7c", 11217676 },
+ AD_LISTEND
+ },
+ Common::EN_ANY,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO1(GUIO_NONE)
+ },
+ GType_WorldOfXeen,
+ 0
+ },
+
+ {
+ // World of Xeen (German)
+ {
+ "worldofxeen",
+ nullptr,
+ {
+ {"xeen.cc", 0, "f4e4b3ddc43bd102dbe1637f480f1fa1", 13214150},
+ {"dark.cc", 0, "84a86bbbc5f2fe96c0b0325485ed8203", 11173657},
+ AD_LISTEND
+ },
+ Common::DE_DEU,
+ Common::kPlatformDOS,
+ ADGF_NO_FLAGS,
+ GUIO1(GUIO_NONE),
+ },
+ GType_WorldOfXeen,
+ 0
+ },
+
+ { AD_TABLE_END_MARKER, 0, 0 }
+};
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs.cpp b/engines/xeen/dialogs.cpp
new file mode 100644
index 0000000000..4ecd1c775c
--- /dev/null
+++ b/engines/xeen/dialogs.cpp
@@ -0,0 +1,260 @@
+/* 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 "common/scummsys.h"
+#include "xeen/dialogs.h"
+#include "xeen/events.h"
+#include "xeen/resources.h"
+#include "xeen/screen.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void ButtonContainer::saveButtons() {
+ _savedButtons.push(_buttons);
+ clearButtons();
+}
+
+/*
+ * Clears the current list of defined buttons
+ */
+void ButtonContainer::clearButtons() {
+ _buttons.clear();
+}
+
+void ButtonContainer::restoreButtons() {
+ _buttons = _savedButtons.pop();
+}
+
+void ButtonContainer::addButton(const Common::Rect &bounds, int val,
+ SpriteResource *sprites) {
+ _buttons.push_back(UIButton(bounds, val, sprites, true));
+}
+
+void ButtonContainer::addButton(const Common::Rect &bounds, int val) {
+ _buttons.push_back(UIButton(bounds, val, nullptr, false));
+}
+
+void ButtonContainer::addPartyButtons(XeenEngine *vm) {
+ for (uint idx = 0; idx < MAX_ACTIVE_PARTY; ++idx) {
+ addButton(Common::Rect(CHAR_FACES_X[idx], 150, CHAR_FACES_X[idx] + 32, 182),
+ Common::KEYCODE_F1 + idx);
+ }
+}
+
+bool ButtonContainer::checkEvents(XeenEngine *vm) {
+ EventsManager &events = *vm->_events;
+ _buttonValue = 0;
+
+ if (events._leftButton) {
+ // Check whether any button is selected
+ Common::Point pt = events._mousePos;
+
+ for (uint i = 0; i < _buttons.size(); ++i) {
+ if (_buttons[i]._bounds.contains(pt)) {
+ events.debounceMouse();
+
+ _buttonValue = _buttons[i]._value;
+ return true;
+ }
+ }
+ } else if (events.isKeyPending()) {
+ Common::KeyState keyState;
+ events.getKey(keyState);
+
+ _buttonValue = keyState.keycode;
+ if (_buttonValue == Common::KEYCODE_KP8)
+ _buttonValue = Common::KEYCODE_UP;
+ else if (_buttonValue == Common::KEYCODE_KP2)
+ _buttonValue = Common::KEYCODE_DOWN;
+ else if (_buttonValue == Common::KEYCODE_KP_ENTER)
+ _buttonValue = Common::KEYCODE_RETURN;
+
+ _buttonValue |= (keyState.flags << 8);
+ if (_buttonValue)
+ return true;
+ }
+
+ return false;
+}
+
+void ButtonContainer::doScroll(XeenEngine *vm, bool drawFlag, bool doFade) {
+ Screen &screen = *vm->_screen;
+ EventsManager &events = *vm->_events;
+
+ if (vm->getGameID() != GType_Clouds) {
+ if (doFade) {
+ screen.fadeIn(2);
+ }
+ return;
+ }
+
+ const int SCROLL_L[8] = { 29, 23, 15, 251, 245, 233, 207, 185 };
+ const int SCROLL_R[8] = { 165, 171, 198, 218, 228, 245, 264, 281 };
+
+ saveButtons();
+ clearButtons();
+ screen.saveBackground();
+
+ // Load hand vga files
+ SpriteResource *hand[16];
+ for (int i = 0; i < 16; ++i) {
+ Common::String name = Common::String::format("hand%02d.vga", i);
+ hand[i] = new SpriteResource(name);
+ }
+
+ // Load marb vga files
+ SpriteResource *marb[5];
+ for (int i = 1; i < 5; ++i) {
+ Common::String name = Common::String::format("marb%02d.vga", i);
+ marb[i] = new SpriteResource(name);
+ }
+
+ if (drawFlag) {
+ for (int i = 22; i > 0; --i) {
+ events.updateGameCounter();
+ screen.restoreBackground();
+
+ if (i > 0 && i <= 14) {
+ hand[i - 1]->draw(screen, 0);
+ }
+ else {
+ // TODO: Check '800h'.. horizontal reverse maybe?
+ hand[14]->draw(screen, 0, Common::Point(SCROLL_L[i - 14], 0));
+ marb[15]->draw(screen, 0, Common::Point(SCROLL_R[i - 14], 0));
+ }
+
+ if (i <= 20) {
+ marb[i / 5]->draw(screen, i % 5);
+ }
+
+ while (!vm->shouldQuit() && events.timeElapsed() == 0)
+ events.pollEventsAndWait();
+
+ screen._windows[0].update();
+ if (i == 0 && doFade)
+ screen.fadeIn(2);
+ }
+ } else {
+ for (int i = 0; i < 22 && !events.isKeyMousePressed(); ++i) {
+ events.updateGameCounter();
+ screen.restoreBackground();
+
+ if (i < 14) {
+ hand[i]->draw(screen, 0);
+ } else {
+ // TODO: Check '800h'.. horizontal reverse maybe?
+ hand[14]->draw(screen, 0, Common::Point(SCROLL_L[i - 7], 0));
+ marb[15]->draw(screen, 0, Common::Point(SCROLL_R[i - 7], 0));
+ }
+
+ if (i < 20) {
+ marb[i / 5]->draw(screen, i % 5);
+ }
+
+ while (!vm->shouldQuit() && events.timeElapsed() == 0)
+ events.pollEventsAndWait();
+
+ screen._windows[0].update();
+ if (i == 0 && doFade)
+ screen.fadeIn(2);
+ }
+ }
+
+ if (drawFlag) {
+ hand[0]->draw(screen, 0);
+ marb[0]->draw(screen, 0);
+ }
+ else {
+ screen.restoreBackground();
+ }
+
+ screen._windows[0].update();
+ restoreButtons();
+
+ // Free resources
+ for (int i = 1; i < 5; ++i)
+ delete marb[i];
+ for (int i = 0; i < 16; ++i)
+ delete hand[i];
+}
+
+void ButtonContainer::drawButtons(XSurface *surface) {
+ for (uint btnIndex = 0; btnIndex < _buttons.size(); ++btnIndex) {
+ UIButton &btn = _buttons[btnIndex];
+ if (btn._draw) {
+ btn._sprites->draw(*surface, btnIndex * 2,
+ Common::Point(btn._bounds.left, btn._bounds.top));
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+void SettingsBaseDialog::showContents(SpriteResource &title1, bool waitFlag) {
+ _vm->_events->pollEventsAndWait();
+ checkEvents(_vm);
+}
+
+/*------------------------------------------------------------------------*/
+
+void CreditsScreen::show(XeenEngine *vm) {
+ CreditsScreen *dlg = new CreditsScreen(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void CreditsScreen::execute() {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+
+ // Handle drawing the credits screen
+ doScroll(_vm, true, false);
+ screen._windows[GAME_WINDOW].close();
+
+ screen.loadBackground("marb.raw");
+ screen._windows[0].writeString(CREDITS);
+ doScroll(_vm, false, false);
+
+ events.setCursor(0);
+ screen._windows[0].update();
+ clearButtons();
+
+ // Wait for keypress
+ while (!events.isKeyMousePressed())
+ events.pollEventsAndWait();
+
+ doScroll(_vm, true, false);
+}
+
+/*------------------------------------------------------------------------*/
+
+void PleaseWait::show(XeenEngine *vm) {
+ if (vm->_mode != MODE_0) {
+ Window &w = vm->_screen->_windows[9];
+ w.open();
+ w.writeString(PLEASE_WAIT);
+ w.update();
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs.h b/engines/xeen/dialogs.h
new file mode 100644
index 0000000000..51eafa5f54
--- /dev/null
+++ b/engines/xeen/dialogs.h
@@ -0,0 +1,115 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_H
+#define XEEN_DIALOGS_H
+
+#include "common/array.h"
+#include "common/stack.h"
+#include "common/rect.h"
+#include "xeen/sprites.h"
+#include "xeen/xsurface.h"
+
+namespace Xeen {
+
+class XeenEngine;
+
+class UIButton {
+public:
+ Common::Rect _bounds;
+ SpriteResource *_sprites;
+ int _value;
+ bool _draw;
+
+ UIButton(const Common::Rect &bounds, int value, SpriteResource *sprites, bool draw) :
+ _bounds(bounds), _value(value), _sprites(sprites), _draw(draw) {}
+
+ UIButton() : _value(0), _sprites(nullptr), _draw(false) {}
+};
+
+class ButtonContainer {
+private:
+ Common::Stack< Common::Array<UIButton> > _savedButtons;
+protected:
+ Common::Array<UIButton> _buttons;
+ int _buttonValue;
+
+ /**
+ * Draws the scroll in the background
+ */
+ void doScroll(XeenEngine *vm, bool drawFlag, bool doFade);
+
+ bool checkEvents(XeenEngine *vm);
+public:
+ ButtonContainer() : _buttonValue(0) {}
+
+ /**
+ * Saves the current list of buttons
+ */
+ void saveButtons();
+
+ void clearButtons();
+
+ void restoreButtons();
+
+ void addButton(const Common::Rect &bounds, int val, SpriteResource *sprites);
+
+ void addButton(const Common::Rect &bounds, int val);
+
+ void addPartyButtons(XeenEngine *vm);
+
+ /**
+ * Draws the buttons onto the passed surface
+ */
+ void drawButtons(XSurface *surface);
+};
+
+class SettingsBaseDialog : public ButtonContainer {
+protected:
+ XeenEngine *_vm;
+
+ virtual void showContents(SpriteResource &title1, bool mode);
+public:
+ SettingsBaseDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ virtual ~SettingsBaseDialog() {}
+};
+
+class CreditsScreen: public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ CreditsScreen(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+public:
+ static void show(XeenEngine *vm);
+};
+
+class PleaseWait {
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_H */
diff --git a/engines/xeen/dialogs_automap.cpp b/engines/xeen/dialogs_automap.cpp
new file mode 100644
index 0000000000..d9b7e8d6c7
--- /dev/null
+++ b/engines/xeen/dialogs_automap.cpp
@@ -0,0 +1,428 @@
+/* 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 "xeen/dialogs_automap.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+
+void AutoMapDialog::show(XeenEngine *vm) {
+ AutoMapDialog *dlg = new AutoMapDialog(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void AutoMapDialog::execute() {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ int frame2 = intf._overallFrame * 2;
+ bool frameEndFlag = false;
+
+ Common::Point pt = party._mazePosition;
+ Common::Point arrowPt;
+ SpriteResource globalSprites;
+ globalSprites.load("global.icn");
+
+ if (pt.x < 8 && map.mazeData()._surroundingMazes._west == 0) {
+ arrowPt.x = pt.x * 10 + 4;
+ } else if (pt.x > 23) {
+ arrowPt.x = pt.x * 10 + 100;
+ pt.x = 23;
+ } else if (pt.x > 8 && map.mazeData()._surroundingMazes._east == 0) {
+ arrowPt.x = pt.x * 10 + 4;
+ pt.x = 7;
+ } else {
+ arrowPt.x = 74;
+ }
+
+ if (pt.y < 8 && map.mazeData()._surroundingMazes._south == 0) {
+ arrowPt.y = ((15 - pt.y) << 3) + 13;
+ pt.y = 8;
+ } else if (pt.y > 24) {
+ arrowPt.y = ((15 - (pt.y - 24)) << 3) + 13;
+ pt.y = 24;
+ } else if (pt.y >= 8 && map.mazeData()._surroundingMazes._north == 0) {
+ arrowPt.y = ((15 - pt.y) << 3) + 13;
+ pt.y = 8;
+ } else {
+ arrowPt.y = 69;
+ }
+
+ screen._windows[5].open();
+// MazeData &mazeData = map.mazeDataCurrent();
+ bool drawFlag = true;
+ int v;
+
+ events.updateGameCounter();
+ do {
+ if (drawFlag)
+ intf.draw3d(false);
+ screen._windows[5].writeString("\n");
+
+ if (map._isOutdoors) {
+ // Draw outdoors map
+ for (int yp = 38, yDiff = pt.y + 7; pt.y < 166; --yDiff, yp += 8) {
+ for (int xp = 80, xDiff = pt.x - 7; xp < 240; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 0);
+
+ if (map._currentSteppedOn) {
+ map._tileSprites.draw(screen, map.mazeDataCurrent()._surfaceTypes[v],
+ Common::Point(xp, yp));
+ }
+ }
+ }
+
+ for (int yp = 38, yDiff = pt.y + 7; yp < 166; --yDiff, yp += 8) {
+ for (int xp = 80, xDiff = pt.x - 7; xp < 240; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 4);
+ int wallType = map.mazeDataCurrent()._wallTypes[v];
+
+ if (wallType && map._currentSteppedOn)
+ map._tileSprites.draw(screen, wallType, Common::Point(xp, yp));
+ }
+ }
+
+
+ for (int yp = 38, yDiff = pt.y + 7; yp < 166; yp += 8, --yDiff) {
+ for (int xp = 80, xDiff = -7; xp < 240; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 8);
+
+ if (v && map._currentSteppedOn)
+ map._tileSprites.draw(screen, 1, Common::Point(xp, yp));
+ }
+ }
+ } else {
+ // Draw indoors map
+ frame2 = (frame2 + 2) % 8;
+
+ // Draw default ground for all the valid explored areas
+ for (int yp = 38, yDiff = pt.y + 7; yp < 166; yp += 8, --yDiff) {
+ for (int xp = 80, xDiff = pt.x - 7; xp < 240; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 0, 0xffff);
+
+ if (v != INVALID_CELL && map._currentSteppedOn)
+ map._tileSprites.draw(screen, 0, Common::Point(xp, yp));
+ }
+ }
+
+ // Draw thinner ground tiles on the left edge of the map
+ for (int yp = 43, yDiff = pt.y + 7; yp < 171; yp += 8, --yDiff) {
+ v = map.mazeLookup(Common::Point(pt.x - 8, yDiff), 0, 0xffff);
+
+ if (v != INVALID_CELL && map._currentSurfaceId != 0 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, 36 + map.mazeData()._surfaceTypes[
+ map._currentSurfaceId], Common::Point(75, yp));
+ }
+
+ // Draw thin tile portion on top-left corner of map
+ v = map.mazeLookup(Common::Point(pt.x - 8, pt.y + 8), 0, 0xffff);
+ if (v != INVALID_CELL && map._currentSurfaceId != 0 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, 36 + map.mazeData()._surfaceTypes[
+ map._currentSurfaceId], Common::Point(75, 35));
+
+ // Draw any thin tiles at the very top of the map
+ for (int xp = 85, xDiff = pt.x - 7; xp < 245; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, pt.y + 8), 0, 0xffff);
+
+ if (v != INVALID_CELL && map._currentSurfaceId != 0 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, 36 + map.mazeData()._surfaceTypes[
+ map._currentSurfaceId], Common::Point(xp, 35));
+ }
+
+ // Draw the default ground tiles
+ for (int yp = 43, yDiff = pt.y + 7; yp < 171; yp += 8, --yDiff) {
+ for (int xp = 85, xDiff = pt.x - 7; xp < 245; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 0, 0xffff);
+
+ if (v != INVALID_CELL && map._currentSurfaceId && map._currentSteppedOn)
+ map._tileSprites.draw(screen, map.mazeData()._surfaceTypes[
+ map._currentSurfaceId], Common::Point(xp, yp));
+ }
+ }
+
+ // Draw walls on left and top edges of map
+ for (int xp = 80, yp = 158, xDiff = pt.x - 7, yDiff = pt.y - 8; xp < 250;
+ xp += 10, yp -= 8, ++xDiff, ++yDiff) {
+ // Draw walls on left edge of map
+ v = map.mazeLookup(Common::Point(pt.x - 8, yDiff), 12);
+
+ int frame;
+ switch (v) {
+ case SURFTYPE_DIRT:
+ frame = 18;
+ break;
+ case SURFTYPE_SNOW:
+ frame = 22;
+ break;
+ case SURFTYPE_SWAMP:
+ case SURFTYPE_CLOUD:
+ frame = 16;
+ break;
+ case SURFTYPE_LAVA:
+ case SURFTYPE_DWATER:
+ frame = 2;
+ break;
+ case SURFTYPE_DESERT:
+ frame = 30;
+ break;
+ case SURFTYPE_ROAD:
+ frame = 32;
+ break;
+ case SURFTYPE_TFLR:
+ frame = 20;
+ break;
+ case SURFTYPE_SKY:
+ frame = 28;
+ break;
+ case SURFTYPE_CROAD:
+ frame = 14;
+ break;
+ case SURFTYPE_SEWER:
+ frame = frame2 + 4;
+ break;
+ case SURFTYPE_SCORCH:
+ frame = 24;
+ break;
+ case SURFTYPE_SPACE:
+ frame = 26;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, frame, Common::Point(70, yp));
+
+ // Draw walls on top edge of map
+ v = map.mazeLookup(Common::Point(xDiff, pt.y + 8), 0);
+
+ switch (v) {
+ case SURFTYPE_DIRT:
+ frame = 19;
+ break;
+ case SURFTYPE_GRASS:
+ frame = 35;
+ break;
+ case SURFTYPE_SNOW:
+ frame = 23;
+ break;
+ case SURFTYPE_SWAMP:
+ case SURFTYPE_CLOUD:
+ frame = 17;
+ break;
+ case SURFTYPE_LAVA:
+ case SURFTYPE_DWATER:
+ frame = 3;
+ break;
+ case SURFTYPE_DESERT:
+ frame = 31;
+ break;
+ case SURFTYPE_ROAD:
+ frame = 33;
+ break;
+ case SURFTYPE_TFLR:
+ frame = 21;
+ break;
+ case SURFTYPE_SKY:
+ frame = 29;
+ break;
+ case SURFTYPE_CROAD:
+ frame = 15;
+ break;
+ case SURFTYPE_SEWER:
+ frame = frame2 + 5;
+ break;
+ case SURFTYPE_SCORCH:
+ frame = 25;
+ break;
+ case SURFTYPE_SPACE:
+ frame = 27;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, frame, Common::Point(xp, 30));
+ }
+
+ // Draw any walls on the cells
+ for (int yCtr = 0, yp = 38, yDiff = pt.y + 7; yCtr < 16; ++yCtr, yp += 8, --yDiff) {
+ for (int xCtr = 0, xp = 80, xDiff = pt.x - 7; xCtr < 16; ++xCtr, xp += 10, ++xDiff) {
+ // Draw the arrow if at the correct position
+ if ((arrowPt.x / 10) == xCtr && (14 - (arrowPt.y / 10)) == yCtr && frameEndFlag) {
+ globalSprites.draw(screen, party._mazeDirection + 1,
+ Common::Point(arrowPt.x + 81, arrowPt.y + 29));
+ }
+
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 12);
+ int frame;
+ switch (v) {
+ case SURFTYPE_DIRT:
+ frame = 18;
+ break;
+ case SURFTYPE_GRASS:
+ frame = 34;
+ break;
+ case SURFTYPE_SNOW:
+ frame = 22;
+ break;
+ case SURFTYPE_SWAMP:
+ case SURFTYPE_CLOUD:
+ frame = 16;
+ break;
+ case SURFTYPE_LAVA:
+ case SURFTYPE_DWATER:
+ frame = 2;
+ break;
+ case SURFTYPE_DESERT:
+ frame = 30;
+ break;
+ case SURFTYPE_ROAD:
+ frame = 32;
+ break;
+ case SURFTYPE_TFLR:
+ frame = 20;
+ break;
+ case SURFTYPE_SKY:
+ frame = 28;
+ break;
+ case SURFTYPE_CROAD:
+ frame = 14;
+ break;
+ case SURFTYPE_SEWER:
+ frame = frame2 + 4;
+ break;
+ case SURFTYPE_SCORCH:
+ frame = 24;
+ break;
+ case SURFTYPE_SPACE:
+ frame = 26;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, frame, Common::Point(xp, yp));
+
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 0);
+ switch (v) {
+ case SURFTYPE_DIRT:
+ frame = 19;
+ break;
+ case SURFTYPE_GRASS:
+ frame = 35;
+ break;
+ case SURFTYPE_SNOW:
+ frame = 23;
+ break;
+ case SURFTYPE_SWAMP:
+ case SURFTYPE_CLOUD:
+ frame = 17;
+ break;
+ case SURFTYPE_LAVA:
+ case SURFTYPE_DWATER:
+ frame = 3;
+ break;
+ case SURFTYPE_DESERT:
+ frame = 31;
+ break;
+ case SURFTYPE_ROAD:
+ frame = 33;
+ break;
+ case SURFTYPE_TFLR:
+ frame = 21;
+ break;
+ case SURFTYPE_SKY:
+ frame = 29;
+ break;
+ case SURFTYPE_CROAD:
+ frame = 15;
+ break;
+ case SURFTYPE_SEWER:
+ frame = frame2 + 5;
+ break;
+ case SURFTYPE_SCORCH:
+ frame = 25;
+ break;
+ case SURFTYPE_SPACE:
+ frame = 27;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && map._currentSteppedOn)
+ map._tileSprites.draw(screen, frame, Common::Point(xp, yp));
+ }
+ }
+
+ // Draw overlay on cells that haven't been stepped on yet
+ for (int yDiff = pt.y + 7, yp = 38; yp < 166; --yDiff, yp += 8) {
+ for (int xp = 80, xDiff = pt.x - 7; xp < 240; xp += 10, ++xDiff) {
+ v = map.mazeLookup(Common::Point(xDiff, yDiff), 0, 0xffff);
+
+ if (v == INVALID_CELL || !map._currentSteppedOn)
+ map._tileSprites.draw(screen, 1, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ screen._windows[5].frame();
+ if (!map._isOutdoors) {
+ map._tileSprites.draw(screen, 52, Common::Point(76, 30));
+ } else if (frameEndFlag) {
+ globalSprites.draw(screen, party._mazeDirection + 1,
+ Common::Point(arrowPt.x + 76, arrowPt.y + 25));
+ }
+
+ if (events.timeElapsed() > 5) {
+ // Set the flag to make the basic arrow blinking effect
+ frameEndFlag = !frameEndFlag;
+ events.updateGameCounter();
+ }
+
+ screen._windows[5].writeString(Common::String::format(MAP_TEXT,
+ map._mazeName.c_str(), party._mazePosition.x,
+ party._mazePosition.y, DIRECTION_TEXT[party._mazeDirection]));
+ screen._windows[5].update();
+ screen._windows[3].update();
+
+ events.pollEvents();
+ drawFlag = false;
+ } while (!_vm->shouldQuit() && !events.isKeyMousePressed());
+
+ events.clearEvents();
+ screen._windows[5].close();
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_automap.h b/engines/xeen/dialogs_automap.h
new file mode 100644
index 0000000000..f20f9b0104
--- /dev/null
+++ b/engines/xeen/dialogs_automap.h
@@ -0,0 +1,45 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_AUTOMAP_H
+#define XEEN_DIALOGS_AUTOMAP_H
+
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class XeenEngine;
+
+class AutoMapDialog: public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ AutoMapDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_AUTOMAP_H */
diff --git a/engines/xeen/dialogs_char_info.cpp b/engines/xeen/dialogs_char_info.cpp
new file mode 100644
index 0000000000..0494c22b9c
--- /dev/null
+++ b/engines/xeen/dialogs_char_info.cpp
@@ -0,0 +1,568 @@
+/* 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 "xeen/dialogs_char_info.h"
+#include "xeen/dialogs_exchange.h"
+#include "xeen/dialogs_items.h"
+#include "xeen/dialogs_quick_ref.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void CharacterInfo::show(XeenEngine *vm, int charIndex) {
+ CharacterInfo *dlg = new CharacterInfo(vm);
+ dlg->execute(charIndex);
+ delete dlg;
+}
+
+void CharacterInfo::execute(int charIndex) {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+
+ bool redrawFlag = true;
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_CHARACTER_INFO;
+ loadDrawStructs();
+ addButtons();
+
+ Character *c = (oldMode != MODE_COMBAT) ? &party._activeParty[charIndex] : combat._combatParty[charIndex];
+ intf.highlightChar(charIndex);
+ Window &w = screen._windows[24];
+ w.open();
+
+ do {
+ if (redrawFlag) {
+ Common::String charDetails = loadCharacterDetails(*c);
+ w.writeString(Common::String::format(CHARACTER_TEMPLATE, charDetails.c_str()));
+ w.drawList(_drawList, 24);
+ w.update();
+ redrawFlag = false;
+ }
+
+ // Wait for keypress, showing a blinking cursor
+ events.updateGameCounter();
+ bool cursorFlag = false;
+ _buttonValue = 0;
+ while (!_vm->shouldQuit() && !_buttonValue) {
+ events.pollEventsAndWait();
+ if (events.timeElapsed() > 4) {
+ cursorFlag = !cursorFlag;
+ events.updateGameCounter();
+ }
+
+ showCursor(cursorFlag);
+ w.update();
+ checkEvents(_vm);
+ }
+ events.clearEvents();
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)(oldMode == MODE_COMBAT ? combat._combatParty.size() : party._activeParty.size())) {
+ charIndex = _buttonValue;
+ c = (oldMode != MODE_COMBAT) ? &party._activeParty[charIndex] : combat._combatParty[charIndex];
+ } else {
+ _vm->_mode = MODE_CHARACTER_INFO;
+ }
+ redrawFlag = true;
+ break;
+
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ if (_cursorCell > 0) {
+ showCursor(false);
+ --_cursorCell;
+ showCursor(true);
+ }
+ w.update();
+ break;
+
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ if (_cursorCell < 20) {
+ showCursor(false);
+ ++_cursorCell;
+ showCursor(true);
+ }
+ w.update();
+ break;
+
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP4:
+ if (_cursorCell >= 5) {
+ showCursor(false);
+ _cursorCell -= 5;
+ showCursor(true);
+ }
+ w.update();
+ break;
+
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP6:
+ if (_cursorCell <= 15) {
+ showCursor(false);
+ _cursorCell += 5;
+ showCursor(true);
+ }
+ w.update();
+ break;
+
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ _buttonValue = _cursorCell + Common::KEYCODE_a;
+ // Deliberate fall-through
+
+ case 1001:
+ case 1002:
+ case 1003:
+ case 1004:
+ case 1005:
+ case 1006:
+ case 1007:
+ case 1008:
+ case 1009:
+ case 1010:
+ case 1011:
+ case 1012:
+ case 1013:
+ case 1014:
+ case 1015:
+ case 1016:
+ case 1017:
+ case 1018:
+ case 1019:
+ case 1020: {
+ showCursor(false);
+ _cursorCell = _buttonValue - 1001;
+ showCursor(true);
+ w.update();
+
+ bool result = expandStat(_cursorCell, *c);
+ _vm->_mode = MODE_COMBAT;
+ if (result)
+ redrawFlag = true;
+ break;
+ }
+
+ case Common::KEYCODE_e:
+ if (oldMode == MODE_COMBAT) {
+ ErrorScroll::show(_vm, EXCHANGING_IN_COMBAT, WT_FREEZE_WAIT);
+ } else {
+ _vm->_mode = oldMode;
+ ExchangeDialog::show(_vm, c, charIndex);
+ _vm->_mode = MODE_CHARACTER_INFO;
+ redrawFlag = true;
+ }
+ break;
+
+ case Common::KEYCODE_i:
+ _vm->_mode = oldMode;
+ _vm->_combat->_itemFlag = _vm->_mode == MODE_COMBAT;
+ c = ItemsDialog::show(_vm, c, ITEMMODE_CHAR_INFO);
+
+ if (!c) {
+ party._stepped = true;
+ goto exit;
+ }
+
+ _vm->_mode = MODE_CHARACTER_INFO;
+ break;
+
+ case Common::KEYCODE_q:
+ QuickReferenceDialog::show(_vm);
+ redrawFlag = true;
+ break;
+
+ case Common::KEYCODE_ESCAPE:
+ goto exit;
+ }
+ } while (!_vm->shouldQuit());
+exit:
+ w.close();
+ intf.unhighlightChar();
+ _vm->_mode = oldMode;
+ _vm->_combat->_itemFlag = false;
+}
+
+void CharacterInfo::loadDrawStructs() {
+ _drawList[0] = DrawStruct(0, 2, 16);
+ _drawList[1] = DrawStruct(2, 2, 39);
+ _drawList[2] = DrawStruct(4, 2, 62);
+ _drawList[3] = DrawStruct(6, 2, 85);
+ _drawList[4] = DrawStruct(8, 2, 108);
+ _drawList[5] = DrawStruct(10, 53, 16);
+ _drawList[6] = DrawStruct(12, 53, 39);
+ _drawList[7] = DrawStruct(14, 53, 62);
+ _drawList[8] = DrawStruct(16, 53, 85);
+ _drawList[9] = DrawStruct(18, 53, 108);
+ _drawList[10] = DrawStruct(20, 104, 16);
+ _drawList[11] = DrawStruct(22, 104, 39);
+ _drawList[12] = DrawStruct(24, 104, 62);
+ _drawList[13] = DrawStruct(26, 104, 85);
+ _drawList[14] = DrawStruct(28, 104, 108);
+ _drawList[15] = DrawStruct(30, 169, 16);
+ _drawList[16] = DrawStruct(32, 169, 39);
+ _drawList[17] = DrawStruct(34, 169, 62);
+ _drawList[18] = DrawStruct(36, 169, 85);
+ _drawList[19] = DrawStruct(38, 169, 108);
+ _drawList[20] = DrawStruct(40, 277, 3);
+ _drawList[21] = DrawStruct(42, 277, 35);
+ _drawList[22] = DrawStruct(44, 277, 67);
+ _drawList[23] = DrawStruct(46, 277, 99);
+
+ _iconSprites.load("view.icn");
+ for (int idx = 0; idx < 24; ++idx)
+ _drawList[idx]._sprites = &_iconSprites;
+}
+
+void CharacterInfo::addButtons() {
+ addButton(Common::Rect(10, 24, 34, 44), 1001, &_iconSprites);
+ addButton(Common::Rect(10, 47, 34, 67), 1002, &_iconSprites);
+ addButton(Common::Rect(10, 70, 34, 90), 1003, &_iconSprites);
+ addButton(Common::Rect(10, 93, 34, 113), 1004, &_iconSprites);
+ addButton(Common::Rect(10, 116, 34, 136), 1005, &_iconSprites);
+ addButton(Common::Rect(61, 24, 85, 44), 1006, &_iconSprites);
+ addButton(Common::Rect(61, 47, 85, 67), 1007, &_iconSprites);
+ addButton(Common::Rect(61, 70, 85, 90), 1008, &_iconSprites);
+ addButton(Common::Rect(61, 93, 85, 113), 1009, &_iconSprites);
+ addButton(Common::Rect(61, 116, 85, 136), 1010, &_iconSprites);
+ addButton(Common::Rect(112, 24, 136, 44), 1011, &_iconSprites);
+ addButton(Common::Rect(112, 47, 136, 67), 1012, &_iconSprites);
+ addButton(Common::Rect(112, 70, 136, 90), 1013, &_iconSprites);
+ addButton(Common::Rect(112, 93, 136, 113), 1014, &_iconSprites);
+ addButton(Common::Rect(112, 116, 136, 136), 1015, &_iconSprites);
+ addButton(Common::Rect(177, 24, 201, 44), 1016, &_iconSprites);
+ addButton(Common::Rect(177, 47, 201, 67), 1017, &_iconSprites);
+ addButton(Common::Rect(177, 70, 201, 90), 1018, &_iconSprites);
+ addButton(Common::Rect(177, 93, 201, 113), 1019, &_iconSprites);
+ addButton(Common::Rect(177, 116, 201, 136), 1020, &_iconSprites);
+ addButton(Common::Rect(285, 11, 309, 31), Common::KEYCODE_i, &_iconSprites);
+ addButton(Common::Rect(285, 43, 309, 63), Common::KEYCODE_q, &_iconSprites);
+ addButton(Common::Rect(285, 75, 309, 95), Common::KEYCODE_e, &_iconSprites);
+ addButton(Common::Rect(285, 107, 309, 127), Common::KEYCODE_ESCAPE, &_iconSprites);
+ addPartyButtons(_vm);
+}
+
+Common::String CharacterInfo::loadCharacterDetails(const Character &c) {
+ Condition condition = c.worstCondition();
+ Party &party = *_vm->_party;
+ int foodVal = party._food / party._activeParty.size() / 3;
+
+ int totalResist =
+ c._fireResistence._permanent + c.itemScan(11) + c._fireResistence._temporary +
+ c._coldResistence._permanent + c.itemScan(13) + c._coldResistence._temporary +
+ c._electricityResistence._permanent + c.itemScan(12) + c._electricityResistence._temporary +
+ c._poisonResistence._permanent + c.itemScan(14) + c._poisonResistence._temporary +
+ c._energyResistence._permanent + c.itemScan(15) + c._energyResistence._temporary +
+ c._magicResistence._permanent + c.itemScan(16) + c._magicResistence._temporary;
+
+ return Common::String::format(CHARACTER_DETAILS,
+ PARTY_GOLD, c._name.c_str(), SEX_NAMES[c._sex],
+ RACE_NAMES[c._race], CLASS_NAMES[c._class],
+ c.statColor(c.getStat(MIGHT), c.getStat(MIGHT, true)), c.getStat(MIGHT),
+ c.statColor(c.getStat(ACCURACY), c.getStat(ACCURACY, true)), c.getStat(ACCURACY),
+ c.statColor(c._currentHp, c.getMaxHP()), c._currentHp,
+ c.getCurrentExperience(),
+ c.statColor(c.getStat(INTELLECT), c.getStat(INTELLECT, true)), c.getStat(INTELLECT),
+ c.statColor(c.getStat(LUCK), c.getStat(LUCK, true)), c.getStat(LUCK),
+ c.statColor(c._currentSp, c.getMaxSP()), c._currentSp,
+ party._gold,
+ c.statColor(c.getStat(PERSONALITY), c.getStat(PERSONALITY, true)), c.getStat(PERSONALITY),
+ c.statColor(c.getAge(), c.getAge(true)), c.getAge(),
+ totalResist,
+ party._gems,
+ c.statColor(c.getStat(ENDURANCE), c.getStat(ENDURANCE, true)), c.getStat(ENDURANCE),
+ c.statColor(c.getCurrentLevel(), c._level._permanent), c.getCurrentLevel(),
+ c.getNumSkills(),
+ foodVal, (foodVal == 1) ? ' ' : 's',
+ c.statColor(c.getStat(SPEED), c.getStat(SPEED, true)), c.getStat(SPEED),
+ c.statColor(c.getArmorClass(), c.getArmorClass(true)), c.getArmorClass(),
+ c.getNumAwards(),
+ CONDITION_COLORS[condition], CONDITION_NAMES[condition],
+ condition == NO_CONDITION && party._blessed ? PLUS_14 : "",
+ condition == NO_CONDITION && party._powerShield ? PLUS_14 : "",
+ condition == NO_CONDITION && party._holyBonus ? PLUS_14 : "",
+ condition == NO_CONDITION && party._heroism ? PLUS_14 : ""
+ );
+}
+
+void CharacterInfo::showCursor(bool flag) {
+ Screen &screen = *_vm->_screen;
+ const int CURSOR_X[5] = { 9, 60, 111, 176, 0 };
+ const int CURSOR_Y[5] = { 23, 46, 69, 92, 115 };
+
+ if (_cursorCell < 20) {
+ _iconSprites.draw(screen, flag ? 49 : 48,
+ Common::Point(CURSOR_X[_cursorCell / 5], CURSOR_Y[_cursorCell % 5]));
+ }
+}
+
+bool CharacterInfo::expandStat(int attrib, const Character &c) {
+ const int STAT_POS[2][20] = {
+ {
+ 61, 61, 61, 61, 61, 112, 112, 112, 112, 112,
+ 177, 177, 177, 177, 177, 34, 34, 34, 34, 34
+ }, {
+ 24, 47, 70, 93, 116, 24, 47, 70, 93, 116,
+ 24, 47, 70, 93, 116, 24, 47, 70, 93, 116
+ }
+ };
+ assert(attrib < 20);
+ Common::Rect bounds(STAT_POS[0][attrib], STAT_POS[1][attrib],
+ STAT_POS[0][attrib] + 143, STAT_POS[1][attrib] + 52);
+ Party &party = *_vm->_party;
+ uint stat1, stat2;
+ uint idx;
+ Common::String msg;
+
+ switch (attrib) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ // Basic attributes
+ stat1 = c.getStat((Attribute)attrib, false);
+ stat2 = c.getStat((Attribute)attrib, true);
+ idx = 0;
+ while (STAT_VALUES[idx] <= stat1)
+ ++idx;
+
+ msg = Common::String::format(CURRENT_MAXIMUM_RATING_TEXT, STAT_NAMES[attrib],
+ stat1, stat2, RATING_TEXT[idx]);
+ break;
+
+ case 7:
+ // Age
+ stat1 = c.getAge(false);
+ stat2 = c.getAge(true);
+ msg = Common::String::format(AGE_TEXT, STAT_NAMES[attrib],
+ stat1, stat2, c._birthDay, c._birthYear);
+ break;
+
+ case 8: {
+ // Level
+ const int CLASS_ATTACK_GAINS[10] = { 5, 6, 6, 7, 8, 6, 5, 4, 7, 6 };
+ idx = c.getCurrentLevel() / CLASS_ATTACK_GAINS[c._class] + 1;
+
+ msg = Common::String::format(LEVEL_TEXT, STAT_NAMES[attrib],
+ c.getCurrentLevel(), c._level._permanent,
+ idx, idx > 1 ? "s" : "",
+ c._level._permanent);
+ break;
+ }
+
+ case 9:
+ // Armor Class
+ stat1 = c.getArmorClass(false);
+ stat2 = c.getArmorClass(true);
+ msg = Common::String::format(CURRENT_MAXIMUM_TEXT, STAT_NAMES[attrib],
+ stat1, stat2);
+ bounds.setHeight(42);
+ break;
+
+ case 10:
+ // Hit Points
+ stat1 = c._currentHp;
+ stat2 = c.getMaxHP();
+ msg = Common::String::format(CURRENT_MAXIMUM_TEXT, STAT_NAMES[attrib],
+ stat1, stat2);
+ bounds.setHeight(42);
+ break;
+
+ case 11:
+ // Spell Points
+ stat1 = c._currentSp;
+ stat2 = c.getMaxSP();
+ msg = Common::String::format(CURRENT_MAXIMUM_TEXT, STAT_NAMES[attrib],
+ stat1, stat2);
+ bounds.setHeight(42);
+ break;
+
+ case 12:
+ // Resistences
+ msg = Common::String::format(RESISTENCES_TEXT, STAT_NAMES[attrib],
+ c._fireResistence._permanent + c.itemScan(11) + c._fireResistence._temporary,
+ c._coldResistence._permanent + c.itemScan(13) + c._coldResistence._temporary,
+ c._electricityResistence._permanent + c.itemScan(12) + c._electricityResistence._temporary,
+ c._poisonResistence._permanent + c.itemScan(14) + c._poisonResistence._temporary,
+ c._energyResistence._permanent + c.itemScan(15) + c._energyResistence._temporary,
+ c._magicResistence._permanent + c.itemScan(16) + c._magicResistence._temporary);
+ bounds.setHeight(80);
+ break;
+
+ case 13: {
+ // Skills
+ Common::String lines[20];
+ int numLines = c.getNumSkills();
+ if (numLines > 0) {
+ for (int skill = THIEVERY; skill <= DANGER_SENSE; ++skill) {
+ if (c._skills[skill]) {
+ if (skill == THIEVERY) {
+ lines[0] = Common::String::format("\n\t020%s%u",
+ SKILL_NAMES[THIEVERY], c.getThievery());
+ } else {
+ lines[skill] = Common::String::format("\n\t020%s", SKILL_NAMES[skill]);
+ }
+ }
+ }
+ } else {
+ lines[0] = NONE;
+ numLines = 1;
+ }
+
+ msg = Common::String::format("\x2\x3""c%s\x3l%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+ STAT_NAMES[attrib], 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[17].c_str(), lines[6].c_str(), lines[7].c_str(), lines[8].c_str(),
+ lines[9].c_str(), lines[10].c_str(), lines[11].c_str(), lines[12].c_str(),
+ lines[13].c_str(), lines[16].c_str(), lines[14].c_str(), lines[15].c_str());
+
+ bounds.top -= (numLines / 2) * 8;
+ bounds.setHeight(numLines * 9 + 26);
+ if (bounds.bottom >= SCREEN_HEIGHT)
+ bounds.moveTo(bounds.left, SCREEN_HEIGHT - bounds.height() - 1);
+ break;
+ }
+
+ case 14:
+ // Awards
+ error("AwardsDialog::show");
+ return false;
+
+ case 15:
+ // Experience
+ stat1 = c.getCurrentExperience();
+ stat2 = c.experienceToNextLevel();
+ msg = Common::String::format(EXPERIENCE_TEXT,
+ STAT_NAMES[attrib], stat1,
+ stat2 == 0 ? ELIGIBLE : Common::String::format("%d", stat2).c_str()
+ );
+ bounds.setHeight(43);
+ break;
+
+ case 16:
+ // Gold
+ msg = Common::String::format(IN_PARTY_IN_BANK, STAT_NAMES[attrib],
+ party._gold, party._bankGold);
+ bounds.setHeight(43);
+ break;
+
+ case 17:
+ // Gems
+ msg = Common::String::format(IN_PARTY_IN_BANK, STAT_NAMES[attrib],
+ party._gems, party._bankGems);
+ bounds.setHeight(43);
+ break;
+
+ case 18: {
+ // Food
+ int food = (party._food / party._activeParty.size()) / 3;
+ msg = Common::String::format(FOOD_TEXT, STAT_NAMES[attrib],
+ party._food, food, food != 1 ? "s" : "");
+ break;
+ }
+
+ case 19: {
+ // Conditions
+ Common::String lines[20];
+ int total = 0;
+ for (int condition = CURSED; condition <= ERADICATED; ++condition) {
+ if (c._conditions[condition]) {
+ if (condition >= UNCONSCIOUS) {
+ lines[condition] = Common::String::format("\n\t020%s",
+ CONDITION_NAMES[condition]);
+ } else {
+ lines[condition] = Common::String::format("\n\t020%s\t095-%d",
+ CONDITION_NAMES[condition], c._conditions[condition]);
+ }
+
+ ++total;
+ }
+ }
+
+ Condition condition = c.worstCondition();
+ if (condition == NO_CONDITION) {
+ lines[0] = Common::String::format("\n\t020%s", GOOD);
+ ++total;
+ }
+
+ if (party._blessed)
+ lines[16] = Common::String::format(BLESSED, party._blessed);
+ if (party._powerShield)
+ lines[17] = Common::String::format(POWER_SHIELD, party._powerShield);
+ if (party._holyBonus)
+ lines[18] = Common::String::format(HOLY_BONUS, party._holyBonus);
+ if (party._heroism)
+ lines[19] = Common::String::format(HEROISM, party._heroism);
+
+ msg = Common::String::format("\x2\x3""c%s\x3l%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\x1",
+ STAT_NAMES[attrib], 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(), lines[9].c_str(), lines[10].c_str(),
+ lines[11].c_str(), lines[12].c_str(), lines[13].c_str(),
+ lines[14].c_str(), lines[15].c_str(), lines[16].c_str(),
+ lines[17].c_str(), lines[18].c_str(), lines[19].c_str()
+ );
+
+ bounds.top -= ((total - 1) / 2) * 8;
+ bounds.setHeight(total * 9 + 26);
+ if (bounds.bottom >= SCREEN_HEIGHT)
+ bounds.moveTo(bounds.left, SCREEN_HEIGHT - bounds.height() - 1);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ // Write the data for the stat display
+ Window &w = _vm->_screen->_windows[28];
+ w.setBounds(bounds);
+ w.open();
+ w.writeString(msg);
+ w.update();
+
+ // Wait for a user key/click
+ EventsManager &events = *_vm->_events;
+ while (!_vm->shouldQuit() && !events.isKeyMousePressed())
+ events.pollEventsAndWait();
+ events.clearEvents();
+
+ w.close();
+ return false;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_char_info.h b/engines/xeen/dialogs_char_info.h
new file mode 100644
index 0000000000..90b35a9f6b
--- /dev/null
+++ b/engines/xeen/dialogs_char_info.h
@@ -0,0 +1,70 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_CHAR_INFO_H
+#define XEEN_DIALOGS_CHAR_INFO_H
+
+#include "xeen/dialogs.h"
+#include "xeen/party.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+class CharacterInfo : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+ DrawStruct _drawList[24];
+ int _cursorCell;
+
+ CharacterInfo(XeenEngine *vm) : ButtonContainer(), _vm(vm), _cursorCell(0) {}
+
+ void execute(int charIndex);
+
+ /**
+ * Load the draw structure list with frame numbers and positions
+ */
+ void loadDrawStructs();
+
+ /**
+ * Set up the button list for the dialog
+ */
+ void addButtons();
+
+ /**
+ * Return a string containing the details of the character
+ */
+ Common::String loadCharacterDetails(const Character &c);
+
+ /**
+ * Cursor display handling
+ */
+ void showCursor(bool flag);
+
+ bool expandStat(int attrib, const Character &c);
+public:
+ static void show(XeenEngine *vm, int charIndex);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_CHAR_INFO_H */
diff --git a/engines/xeen/dialogs_control_panel.cpp b/engines/xeen/dialogs_control_panel.cpp
new file mode 100644
index 0000000000..7e8f89cc39
--- /dev/null
+++ b/engines/xeen/dialogs_control_panel.cpp
@@ -0,0 +1,42 @@
+/* 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 "xeen/dialogs_control_panel.h"
+#include "xeen/party.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+int ControlPanel::show(XeenEngine *vm) {
+ ControlPanel *dlg = new ControlPanel(vm);
+ int result = dlg->execute();
+ delete dlg;
+
+ return result;
+}
+
+int ControlPanel::execute() {
+ error("TODO: ControlPanel");
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_control_panel.h b/engines/xeen/dialogs_control_panel.h
new file mode 100644
index 0000000000..16c3781789
--- /dev/null
+++ b/engines/xeen/dialogs_control_panel.h
@@ -0,0 +1,43 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_CONTROL_PANEL_H
+#define XEEN_DIALOGS_CONTROL_PANEL_H
+
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class ControlPanel : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ ControlPanel(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute();
+public:
+ static int show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_CONTROL_PANEL_H */
diff --git a/engines/xeen/dialogs_dismiss.cpp b/engines/xeen/dialogs_dismiss.cpp
new file mode 100644
index 0000000000..9323b46429
--- /dev/null
+++ b/engines/xeen/dialogs_dismiss.cpp
@@ -0,0 +1,95 @@
+/* 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 "xeen/dialogs_dismiss.h"
+#include "xeen/party.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void Dismiss::show(XeenEngine *vm) {
+ Dismiss *dlg = new Dismiss(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void Dismiss::execute() {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ loadButtons();
+
+ Window &w = screen._windows[31];
+ w.open();
+ _iconSprites.draw(w, 0, Common::Point(225, 120));
+ w.update();
+
+ bool breakFlag = false;
+ while (!_vm->shouldQuit() && !breakFlag) {
+ do {
+ events.updateGameCounter();
+ intf.draw3d(false);
+ w.frame();
+ w.writeString("\r");
+ _iconSprites.draw(w, 0, Common::Point(225, 120));
+ screen._windows[3].update();
+ w.update();
+
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ } while (!_vm->shouldQuit() && !_buttonValue && events.timeElapsed() == 0);
+ } while (!_vm->shouldQuit() && !_buttonValue);
+
+ if (_buttonValue >= Common::KEYCODE_F1 && _buttonValue <= Common::KEYCODE_F6) {
+ _buttonValue -= Common::KEYCODE_F1;
+
+ if (_buttonValue < (int)party._activeParty.size()) {
+ if (party._activeParty.size() == 1) {
+ w.close();
+ ErrorScroll::show(_vm, CANT_DISMISS_LAST_CHAR, WT_NONFREEZED_WAIT);
+ w.open();
+ } else {
+ // Remove the character from the party
+ party._activeParty.remove_at(_buttonValue);
+ breakFlag = true;
+ }
+ break;
+ }
+ } else if (_buttonValue == Common::KEYCODE_ESCAPE) {
+ breakFlag = true;
+ }
+ }
+}
+
+void Dismiss::loadButtons() {
+ _iconSprites.load("esc.icn");
+ addButton(Common::Rect(225, 120, 249, 140), Common::KEYCODE_ESCAPE, &_iconSprites);
+ addButton(Common::Rect(16, 16, 48, 48), Common::KEYCODE_1);
+ addButton(Common::Rect(117, 16, 149, 48), Common::KEYCODE_2);
+ addButton(Common::Rect(16, 59, 48, 91), Common::KEYCODE_3);
+ addButton(Common::Rect(117, 59, 149, 91), Common::KEYCODE_4);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_dismiss.h b/engines/xeen/dialogs_dismiss.h
new file mode 100644
index 0000000000..ec40e87f7c
--- /dev/null
+++ b/engines/xeen/dialogs_dismiss.h
@@ -0,0 +1,47 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_DISMISS_H
+#define XEEN_DIALOGS_DISMISS_H
+
+#include "xeen/dialogs.h"
+#include "xeen/party.h"
+
+namespace Xeen {
+
+class Dismiss : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ Dismiss(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+
+ void loadButtons();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_DISMISS_H */
diff --git a/engines/xeen/dialogs_error.cpp b/engines/xeen/dialogs_error.cpp
new file mode 100644
index 0000000000..7204bad8fe
--- /dev/null
+++ b/engines/xeen/dialogs_error.cpp
@@ -0,0 +1,118 @@
+/* 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 "common/scummsys.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/events.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void ErrorDialog::show(XeenEngine *vm, const Common::String &msg, ErrorWaitType waitType) {
+ ErrorDialog *dlg = new ErrorDialog(vm);
+ dlg->execute(msg, waitType);
+ delete dlg;
+}
+
+void ErrorDialog::execute(const Common::String &msg, ErrorWaitType waitType) {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ Window &w = screen._windows[6];
+
+ w.open();
+ w.writeString(msg);
+ w.update();
+
+ switch (waitType) {
+ case WT_FREEZE_WAIT:
+ while (!_vm->shouldQuit() && !events.isKeyMousePressed())
+ events.pollEventsAndWait();
+
+ events.clearEvents();
+ break;
+ case WT_3:
+ if (w._enabled || _vm->_mode == MODE_17) {
+ warning("TODO: sub_26D8F");
+ break;
+ }
+ // Deliberate fall-through
+ case WT_NONFREEZED_WAIT:
+ do {
+ events.updateGameCounter();
+ _vm->_interface->draw3d(true);
+
+ events.wait(1, true);
+ if (checkEvents(_vm))
+ break;
+ } while (!_vm->shouldQuit() && !_buttonValue);
+ break;
+ case WT_2:
+ warning("TODO: sub_26D8F");
+ break;
+ default:
+ break;
+ }
+
+ w.close();
+}
+
+/*------------------------------------------------------------------------*/
+
+void ErrorScroll::show(XeenEngine *vm, const Common::String &msg, ErrorWaitType waitType) {
+ Common::String s = Common::String::format("\x3""c\v010\t000%s", msg.c_str());
+ ErrorDialog::show(vm, s, waitType);
+}
+
+/*------------------------------------------------------------------------*/
+
+void CantCast::show(XeenEngine *vm, int spellId, int componentNum) {
+ CantCast *dlg = new CantCast(vm);
+ dlg->execute(spellId, componentNum);
+ delete dlg;
+}
+
+void CantCast::execute(int spellId, int componentNum) {
+ EventsManager &events = *_vm->_events;
+ SoundManager &sound = *_vm->_sound;
+ Spells &spells = *_vm->_spells;
+ Window &w = _vm->_screen->_windows[6];
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_FF;
+
+ sound.playFX(21);
+ w.open();
+ w.writeString(Common::String::format(NOT_ENOUGH_TO_CAST,
+ SPELL_CAST_COMPONENTS[componentNum - 1],
+ spells._spellNames[spellId].c_str()
+ ));
+ w.update();
+
+ do {
+ events.pollEventsAndWait();
+ } while (!_vm->shouldQuit() && !events.isKeyMousePressed());
+ events.clearEvents();
+
+ w.close();
+ _vm->_mode = oldMode;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_error.h b/engines/xeen/dialogs_error.h
new file mode 100644
index 0000000000..46efdb1683
--- /dev/null
+++ b/engines/xeen/dialogs_error.h
@@ -0,0 +1,65 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_ERROR_H
+#define XEEN_DIALOGS_ERROR_H
+
+#include "xeen/dialogs.h"
+#include "xeen/character.h"
+
+namespace Xeen {
+
+enum ErrorWaitType { WT_FREEZE_WAIT = 0, WT_NONFREEZED_WAIT = 1,
+ WT_2 = 2, WT_3 = 3 };
+
+class ErrorDialog : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ ErrorDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute(const Common::String &msg, ErrorWaitType waitType);
+public:
+ static void show(XeenEngine *vm, const Common::String &msg,
+ ErrorWaitType waitType = WT_FREEZE_WAIT);
+};
+
+class ErrorScroll {
+public:
+ static void show(XeenEngine *vm, const Common::String &msg,
+ ErrorWaitType waitType = WT_FREEZE_WAIT);
+};
+
+class CantCast: public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ CantCast(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute(int spellId, int componentNum);
+public:
+ static void show(XeenEngine *vm, int spellId, int componentNum);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_ERROR_H */
diff --git a/engines/xeen/dialogs_exchange.cpp b/engines/xeen/dialogs_exchange.cpp
new file mode 100644
index 0000000000..37da4e0963
--- /dev/null
+++ b/engines/xeen/dialogs_exchange.cpp
@@ -0,0 +1,80 @@
+/* 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 "xeen/dialogs_exchange.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void ExchangeDialog::show(XeenEngine *vm, Character *&c, int &charIndex) {
+ ExchangeDialog *dlg = new ExchangeDialog(vm);
+ dlg->execute(c, charIndex);
+ delete dlg;
+}
+
+void ExchangeDialog::execute(Character *&c, int &charIndex) {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ loadButtons();
+
+ Window &w = screen._windows[31];
+ w.open();
+ w.writeString(EXCHANGE_WITH_WHOM);
+ _iconSprites.draw(w, 0, Common::Point(225, 120));
+ w.update();
+
+ while (!_vm->shouldQuit()) {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+
+ if (_buttonValue >= Common::KEYCODE_F1 && _buttonValue <= Common::KEYCODE_F6) {
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ SWAP(party._activeParty[charIndex], party._activeParty[_buttonValue]);
+
+ charIndex = _buttonValue;
+ c = &party._activeParty[charIndex];
+ break;
+ }
+ } else if (_buttonValue == Common::KEYCODE_ESCAPE) {
+ break;
+ }
+ }
+
+ w.close();
+ intf.drawParty(true);
+ intf.highlightChar(charIndex);
+}
+
+void ExchangeDialog::loadButtons() {
+ _iconSprites.load("esc.icn");
+ addButton(Common::Rect(225, 120, 249, 245), Common::KEYCODE_ESCAPE, &_iconSprites);
+ addButton(Common::Rect(16, 16, 48, 48), Common::KEYCODE_1);
+ addButton(Common::Rect(117, 16, 149, 48), Common::KEYCODE_2);
+ addButton(Common::Rect(16, 59, 48, 91), Common::KEYCODE_3);
+ addButton(Common::Rect(117, 59, 149, 91), Common::KEYCODE_4);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_exchange.h b/engines/xeen/dialogs_exchange.h
new file mode 100644
index 0000000000..e8c4a2dfb1
--- /dev/null
+++ b/engines/xeen/dialogs_exchange.h
@@ -0,0 +1,47 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_EXCHANGE_H
+#define XEEN_DIALOGS_EXCHANGE_H
+
+#include "xeen/dialogs.h"
+#include "xeen/party.h"
+
+namespace Xeen {
+
+class ExchangeDialog : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ ExchangeDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute(Character *&c, int &charIndex);
+
+ void loadButtons();
+public:
+ static void show(XeenEngine *vm, Character *&c, int &charIndex);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_EXCHANGE_H */
diff --git a/engines/xeen/dialogs_fight_options.cpp b/engines/xeen/dialogs_fight_options.cpp
new file mode 100644
index 0000000000..c84e754dc2
--- /dev/null
+++ b/engines/xeen/dialogs_fight_options.cpp
@@ -0,0 +1,39 @@
+/* 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 "xeen/dialogs_fight_options.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void FightOptions::show(XeenEngine *vm) {
+ FightOptions *dlg = new FightOptions(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void FightOptions::execute() {
+ error("TODO: FightOptions");
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_fight_options.h b/engines/xeen/dialogs_fight_options.h
new file mode 100644
index 0000000000..7b058bc6e9
--- /dev/null
+++ b/engines/xeen/dialogs_fight_options.h
@@ -0,0 +1,43 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_FIGHT_OPTIONS_H
+#define XEEN_DIALOGS_FIGHT_OPTIONS_H
+
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class FightOptions : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ FightOptions(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_FIGHT_OPTIONS_H */
diff --git a/engines/xeen/dialogs_info.cpp b/engines/xeen/dialogs_info.cpp
new file mode 100644
index 0000000000..56755630da
--- /dev/null
+++ b/engines/xeen/dialogs_info.cpp
@@ -0,0 +1,128 @@
+/* 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 "xeen/dialogs_info.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void InfoDialog::show(XeenEngine *vm) {
+ InfoDialog *dlg = new InfoDialog(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void InfoDialog::execute() {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+
+ protectionText();
+ Common::String statusText = "";
+ for (uint idx = 0; idx < _lines.size(); ++idx)
+ statusText += _lines[idx];
+
+ Common::String gameName;
+ if (_vm->getGameID() == GType_Swords)
+ gameName = SWORDS_GAME_TEXT;
+ else if (_vm->getGameID() == GType_Clouds)
+ gameName = CLOUDS_GAME_TEXT;
+ else if (_vm->getGameID() == GType_DarkSide)
+ gameName = DARKSIDE_GAME_TEXT;
+ else
+ gameName = WORLD_GAME_TEXT;
+
+ // Form the display message
+ int hour = party._minutes / 60;
+ Common::String details = Common::String::format(GAME_INFORMATION,
+ gameName.c_str(), WEEK_DAY_STRINGS[party._day % 10],
+ (hour > 12) ? hour - 12 : (!hour ? 12 : hour),
+ party._minutes % 60, (hour > 11) ? 'p' : 'a',
+ party._day, party._year, statusText.c_str());
+
+ Window &w = screen._windows[28];
+ w.setBounds(Common::Rect(88, 20, 248, 112));
+ w.open();
+
+ do {
+ events.updateGameCounter();
+ intf.draw3d(false);
+ w.frame();
+ w.writeString(details);
+ w.update();
+
+ events.wait(1, true);
+ } while (!_vm->shouldQuit() && !events.isKeyMousePressed());
+
+ events.clearEvents();
+ w.close();
+}
+
+void InfoDialog::protectionText() {
+ Party &party = *_vm->_party;
+// Common::StringArray _lines;
+ const char *const AA_L024 = "\x3l\n\x9""024";
+ const char *const AA_R124 = "\x3r\x9""124";
+
+ if (party._lightCount) {
+ _lines.push_back(Common::String::format(LIGHT_COUNT_TEXT, party._lightCount));
+ }
+
+ if (party._fireResistence) {
+ _lines.push_back(Common::String::format(FIRE_RESISTENCE_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124, party._fireResistence));
+ }
+
+ if (party._electricityResistence) {
+ _lines.push_back(Common::String::format(ELECRICITY_RESISTENCE_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124, party._electricityResistence));
+ }
+
+ if (party._coldResistence) {
+ _lines.push_back(Common::String::format(COLD_RESISTENCE_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124, party._coldResistence));
+ }
+
+ if (party._poisonResistence) {
+ _lines.push_back(Common::String::format(POISON_RESISTENCE_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124, party._poisonResistence));
+ }
+
+ if (party._clairvoyanceActive) {
+ _lines.push_back(Common::String::format(CLAIRVOYANCE_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124));
+ }
+
+ if (party._levitateActive) {
+ _lines.push_back(Common::String::format(LEVITATE_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124));
+ }
+
+ if (party._walkOnWaterActive) {
+ _lines.push_back(Common::String::format(WALK_ON_WATER_TEXT,
+ _lines.size() == 0 ? 10 : 1, AA_L024, AA_R124));
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_info.h b/engines/xeen/dialogs_info.h
new file mode 100644
index 0000000000..66b915788b
--- /dev/null
+++ b/engines/xeen/dialogs_info.h
@@ -0,0 +1,47 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_INFO_H
+#define XEEN_DIALOGS_INFO_H
+
+#include "common/str-array.h"
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class InfoDialog : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ Common::StringArray _lines;
+
+ InfoDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+
+ void protectionText();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_INFO_H */
diff --git a/engines/xeen/dialogs_input.cpp b/engines/xeen/dialogs_input.cpp
new file mode 100644
index 0000000000..40b8847d14
--- /dev/null
+++ b/engines/xeen/dialogs_input.cpp
@@ -0,0 +1,275 @@
+/* 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 "xeen/dialogs_input.h"
+#include "xeen/scripts.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+int Input::show(XeenEngine *vm, Window *window, Common::String &line,
+ uint maxLen, int maxWidth, bool isNumeric) {
+ Input *dlg = new Input(vm, window);
+ int result = dlg->getString(line, maxLen, maxWidth, isNumeric);
+ delete dlg;
+
+ return result;
+}
+
+int Input::getString(Common::String &line, uint maxLen, int maxWidth, bool isNumeric) {
+ _vm->_noDirectionSense = true;
+ Common::String msg = Common::String::format("\x3""l\t000\x4%03d\x3""c", maxWidth);
+ _window->writeString(msg);
+ _window->update();
+
+ while (!_vm->shouldQuit()) {
+ Common::KeyCode keyCode = doCursor(msg);
+
+ bool refresh = false;
+ if ((keyCode == Common::KEYCODE_BACKSPACE || keyCode == Common::KEYCODE_DELETE)
+ && line.size() > 0) {
+ line.deleteLastChar();
+ refresh = true;
+ } else if (line.size() < maxLen && (line.size() > 0 || keyCode != Common::KEYCODE_SPACE)
+ && ((isNumeric && keyCode >= Common::KEYCODE_0 && keyCode < Common::KEYCODE_9) ||
+ (!isNumeric && keyCode >= Common::KEYCODE_SPACE && keyCode < Common::KEYCODE_DELETE))) {
+ line += (char)keyCode;
+ refresh = true;
+ } else if (keyCode == Common::KEYCODE_RETURN || keyCode == Common::KEYCODE_KP_ENTER) {
+ break;
+ } else if (keyCode == Common::KEYCODE_ESCAPE) {
+ line = "";
+ break;
+ }
+
+ if (refresh) {
+ msg = Common::String::format("\x3""l\t000\x4%03d\x3""c%s", maxWidth, line.c_str());
+ _window->writeString(msg);
+ _window->update();
+ }
+ }
+
+ _vm->_noDirectionSense = false;
+ return line.size();
+}
+
+Common::KeyCode Input::doCursor(const Common::String &msg) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+
+ bool oldUpDoorText = intf._upDoorText;
+ byte oldTillMove = intf._tillMove;
+ intf._upDoorText = false;
+ intf._tillMove = 0;
+
+ bool flag = !_vm->_startupWindowActive && !screen._windows[25]._enabled
+ && _vm->_mode != MODE_FF && _vm->_mode != MODE_17;
+
+ Common::KeyCode ch = Common::KEYCODE_INVALID;
+ while (!_vm->shouldQuit()) {
+ events.updateGameCounter();
+
+ if (flag)
+ intf.draw3d(false);
+ _window->writeString(msg);
+ _window->update();
+
+ if (flag)
+ screen._windows[3].update();
+
+ events.wait(1, true);
+ if (events.isKeyPending()) {
+ Common::KeyState keyState;
+ events.getKey(keyState);
+ ch = keyState.keycode;
+ break;
+ }
+ }
+
+ _window->writeString("");
+ _window->update();
+
+ intf._tillMove = oldTillMove;
+ intf._upDoorText = oldUpDoorText;
+
+ return ch;
+}
+
+/*------------------------------------------------------------------------*/
+
+StringInput::StringInput(XeenEngine *vm): Input(vm, &vm->_screen->_windows[6]) {
+}
+
+int StringInput::show(XeenEngine *vm, bool type, const Common::String &msg1,
+ const Common::String &msg2, int opcode) {
+ StringInput *dlg = new StringInput(vm);
+ int result = dlg->execute(type, msg1, msg2, opcode);
+ delete dlg;
+
+ return result;
+}
+
+int StringInput::execute(bool type, const Common::String &expected,
+ const Common::String &title, int opcode) {
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ Scripts &scripts = *_vm->_scripts;
+ Window &w = screen._windows[6];
+ SoundManager &sound = *_vm->_sound;
+ int result = 0;
+
+ w.open();
+ w.writeString(Common::String::format("\r\x03""c%s\v024\t000", title.c_str()));
+ w.update();
+
+ Common::String line;
+ if (getString(line, 30, 200, false)) {
+ if (type) {
+ if (line == intf._interfaceText) {
+ result = true;
+ } else if (line == expected) {
+ result = (opcode == 55) ? -1 : 1;
+ }
+ } else {
+ // Load in the mirror list
+ File f(Common::String::format("%smirr.txt",
+ _vm->_files->_isDarkCc ? "dark" : "xeen"));
+ MirrorEntry me;
+ scripts._mirror.clear();
+ while (me.synchronize(f))
+ scripts._mirror.push_back(me);
+
+ for (uint idx = 0; idx < scripts._mirror.size(); ++idx) {
+ if (line == scripts._mirror[idx]._name) {
+ result = idx;
+ sound.playFX(_vm->_files->_isDarkCc ? 35 : 61);
+ break;
+ }
+ }
+ }
+ }
+
+ w.close();
+ return result;
+}
+
+/*------------------------------------------------------------------------*/
+
+NumericInput::NumericInput(XeenEngine *vm, int window) : Input(vm, &vm->_screen->_windows[window]) {
+}
+
+int NumericInput::show(XeenEngine *vm, int window, int maxLength, int maxWidth) {
+ NumericInput *dlg = new NumericInput(vm, window);
+ int result = dlg->execute(maxLength, maxWidth);
+ delete dlg;
+
+ return result;
+}
+
+int NumericInput::execute(int maxLength, int maxWidth) {
+ Common::String line;
+
+ if (getString(line, maxLength, maxWidth, true))
+ return atoi(line.c_str());
+ else
+ return 0;
+}
+
+/*------------------------------------------------------------------------*/
+
+int Choose123::show(XeenEngine *vm, int numOptions) {
+ assert(numOptions <= 3);
+ Choose123 *dlg = new Choose123(vm);
+ int result = dlg->execute(numOptions);
+ delete dlg;
+
+ return result;
+}
+
+int Choose123::execute(int numOptions) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ Town &town = *_vm->_town;
+
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_DIALOG_123;
+
+ loadButtons(numOptions);
+ _iconSprites.draw(screen, 7, Common::Point(232, 74));
+ drawButtons(&screen);
+ screen._windows[34].update();
+
+ int result = -1;
+ while (result == -1) {
+ do {
+ events.updateGameCounter();
+ int delay;
+ if (town.isActive()) {
+ town.drawTownAnim(true);
+ delay = 3;
+ } else {
+ intf.draw3d(true);
+ delay = 1;
+ }
+
+ events.wait(delay, true);
+ if (_vm->shouldQuit())
+ return 0;
+ } while (!_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_ESCAPE:
+ result = 0;
+ break;
+ case Common::KEYCODE_1:
+ case Common::KEYCODE_2:
+ case Common::KEYCODE_3: {
+ int v = _buttonValue - Common::KEYCODE_1 + 1;
+ if (v <= numOptions)
+ result = v;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ _vm->_mode = oldMode;
+ intf.mainIconsPrint();
+
+ return result;
+}
+
+void Choose123::loadButtons(int numOptions) {
+ _iconSprites.load("choose.icn");
+
+ if (numOptions >= 1)
+ addButton(Common::Rect(235, 75, 259, 95), Common::KEYCODE_1, &_iconSprites);
+ if (numOptions >= 2)
+ addButton(Common::Rect(260, 75, 284, 95), Common::KEYCODE_2, &_iconSprites);
+ if (numOptions >= 3)
+ addButton(Common::Rect(286, 75, 311, 95), Common::KEYCODE_3, &_iconSprites);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_input.h b/engines/xeen/dialogs_input.h
new file mode 100644
index 0000000000..e824d17f57
--- /dev/null
+++ b/engines/xeen/dialogs_input.h
@@ -0,0 +1,89 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_STRING_INPUT_H
+#define XEEN_DIALOGS_STRING_INPUT_H
+
+#include "common/keyboard.h"
+#include "xeen/dialogs.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+class Input : public ButtonContainer {
+private:
+ /**
+ * Draws the cursor and waits until the user presses a key
+ */
+ Common::KeyCode doCursor(const Common::String &msg);
+protected:
+ XeenEngine *_vm;
+ Window *_window;
+
+ /**
+ * Allows the user to enter a string
+ */
+ int getString(Common::String &line, uint maxLen, int maxWidth, bool isNumeric);
+
+ Input(XeenEngine *vm, Window *window) : _vm(vm), _window(window) {}
+public:
+ static int show(XeenEngine *vm, Window *window, Common::String &line,
+ uint maxLen, int maxWidth, bool isNumeric = false);
+};
+
+class StringInput : public Input {
+protected:
+ StringInput(XeenEngine *vm);
+
+ int execute(bool type, const Common::String &expected,
+ const Common::String &title, int opcode);
+public:
+ static int show(XeenEngine *vm, bool type, const Common::String &msg1,
+ const Common::String &msg2, int opcode);
+};
+
+class NumericInput : public Input {
+private:
+ NumericInput(XeenEngine *vm, int window);
+
+ int execute(int maxLength, int maxWidth);
+public:
+ static int show(XeenEngine *vm, int window, int maxLength, int maxWidth);
+};
+
+class Choose123 : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ Choose123(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute(int numOptions);
+
+ void loadButtons(int numOptions);
+public:
+ static int show(XeenEngine *vm, int numOptions);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_STRING_INPUT_H */
diff --git a/engines/xeen/dialogs_items.cpp b/engines/xeen/dialogs_items.cpp
new file mode 100644
index 0000000000..4ad9e240bd
--- /dev/null
+++ b/engines/xeen/dialogs_items.cpp
@@ -0,0 +1,1065 @@
+/* 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 "xeen/dialogs_items.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/dialogs_quests.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+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;
+ Screen &screen = *_vm->_screen;
+
+ 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_BLACKSMITH;
+ int itemIndex = -1;
+ Common::StringArray lines;
+ int arr[40];
+ int actionIndex = -1;
+
+ events.setCursor(0);
+ loadButtons(mode, c);
+
+ screen._windows[29].open();
+ screen._windows[30].open();
+
+ enum { REDRAW_NONE, REDRAW_TEXT, REDRAW_FULL } redrawFlag = REDRAW_FULL;
+ while (!_vm->shouldQuit()) {
+ if (redrawFlag == REDRAW_FULL) {
+ if ((mode != ITEMMODE_CHAR_INFO || category != CATEGORY_MISC) && mode != ITEMMODE_ENCHANT
+ && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD) {
+ _buttons[8]._bounds.moveTo(148, _buttons[8]._bounds.top);
+ _buttons[9]._draw = false;
+ } else if (mode == ITEMMODE_RECHARGE) {
+ _buttons[4]._value = Common::KEYCODE_r;
+ } else if (mode == ITEMMODE_ENCHANT) {
+ _buttons[4]._value = Common::KEYCODE_e;
+ } else if (mode == ITEMMODE_TO_GOLD) {
+ _buttons[4]._value = Common::KEYCODE_g;
+ } else {
+ _buttons[8]._bounds.moveTo(0, _buttons[8]._bounds.top);
+ _buttons[9]._draw = true;
+ _buttons[9]._value = Common::KEYCODE_u;
+ }
+
+ // 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(ITEMS_DIALOG_TEXT1,
+ BTN_SELL, BTN_IDENTIFY, BTN_FIX);
+ } else if (mode != ITEMMODE_ENCHANT && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD) {
+ msg = Common::String::format(ITEMS_DIALOG_TEXT1,
+ category == 3 ? BTN_USE : BTN_EQUIP,
+ BTN_REMOVE, BTN_DISCARD, BTN_QUEST);
+ } else if (mode == ITEMMODE_ENCHANT) {
+ msg = Common::String::format(ITEMS_DIALOG_TEXT2, BTN_ENCHANT);
+ } else if (mode == ITEMMODE_RECHARGE) {
+ msg = Common::String::format(ITEMS_DIALOG_TEXT2, BTN_RECHARGE);
+ } else {
+ msg = Common::String::format(ITEMS_DIALOG_TEXT2, BTN_GOLD);
+ }
+
+ screen._windows[29].writeString(msg);
+ drawButtons(&screen);
+
+ Common::fill(&arr[0], &arr[40], 0);
+ itemIndex = -1;
+ }
+
+ if (redrawFlag == REDRAW_TEXT || redrawFlag == REDRAW_FULL) {
+ lines.clear();
+
+ if (mode == ITEMMODE_CHAR_INFO || category != 3) {
+ _iconSprites.draw(screen, 8, Common::Point(148, 109));
+ }
+ if (mode != ITEMMODE_ENCHANT && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD) {
+ _iconSprites.draw(screen, 10, Common::Point(182, 109));
+ _iconSprites.draw(screen, 12, Common::Point(216, 109));
+ _iconSprites.draw(screen, 14, Common::Point(250, 109));
+ }
+
+ switch (mode) {
+ case ITEMMODE_CHAR_INFO:
+ _iconSprites.draw(screen, 9, Common::Point(148, 109));
+ break;
+ case ITEMMODE_BLACKSMITH:
+ _iconSprites.draw(screen, 11, Common::Point(182, 109));
+ break;
+ case ITEMMODE_REPAIR:
+ _iconSprites.draw(screen, 15, Common::Point(250, 109));
+ break;
+ case ITEMMODE_IDENTIFY:
+ _iconSprites.draw(screen, 13, Common::Point(216, 109));
+ break;
+ default:
+ break;
+ }
+
+ for (int idx = 0; idx < 9; ++idx) {
+ _itemsDrawList[idx]._y = 10 + idx * 9;
+
+ switch (category) {
+ case CATEGORY_WEAPON:
+ case CATEGORY_ARMOR:
+ case CATEGORY_ACCESSORY: {
+ XeenItem &i = (category == CATEGORY_WEAPON) ? c->_weapons[idx] :
+ ((category == CATEGORY_ARMOR) ? c->_armor[idx] : c->_accessories[idx]);
+
+ if (i._id) {
+ if (mode == ITEMMODE_CHAR_INFO || mode == ITEMMODE_8
+ || mode == ITEMMODE_ENCHANT || mode == ITEMMODE_RECHARGE) {
+ lines.push_back(Common::String::format(ITEMS_DIALOG_LINE1,
+ arr[idx], idx + 1,
+ c->_items[category].getFullDescription(idx, arr[idx]).c_str()));
+ } else {
+ lines.push_back(Common::String::format(ITEMS_DIALOG_LINE2,
+ arr[idx], idx + 1,
+ c->_items[category].getFullDescription(idx, arr[idx]).c_str(),
+ calcItemCost(c, idx, mode,
+ mode == ITEMMODE_TO_GOLD ? 1 : startingChar->_skills[MERCHANT],
+ category)
+ ));
+ }
+
+ DrawStruct &ds = _itemsDrawList[idx];
+ ds._sprites = &_equipSprites;
+ if (c->_weapons.passRestrictions(i._id, true))
+ ds._frame = i._frame;
+ else
+ ds._frame = 14;
+ } else if (_itemsDrawList[idx]._sprites == nullptr) {
+ lines.push_back(NO_ITEMS_AVAILABLE);
+ }
+ break;
+ }
+
+ case CATEGORY_MISC: {
+ XeenItem &i = c->_misc[idx];
+ _itemsDrawList[idx]._sprites = nullptr;
+
+ if (i._material == 0) {
+ // No item
+ if (idx == 0) {
+ lines.push_back(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(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;
+ }
+ }
+ while (lines.size() < INV_ITEMS_TOTAL)
+ lines.push_back("");
+
+ // Draw out overall text and the list of items
+ switch (mode) {
+ case ITEMMODE_CHAR_INFO:
+ case ITEMMODE_8:
+ screen._windows[30].writeString(Common::String::format(X_FOR_THE_Y,
+ category == CATEGORY_MISC ? "\x3l" : "\x3c",
+ CATEGORY_NAMES[category], c->_name.c_str(), CLASS_NAMES[c->_class],
+ category == CATEGORY_MISC ? 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()
+ ));
+
+ case ITEMMODE_BLACKSMITH: {
+ // Original uses var in this block that's never set?!
+ const int v1 = 0;
+ screen._windows[30].writeString(Common::String::format(AVAILABLE_GOLD_COST,
+ CATEGORY_NAMES[category],
+ v1 ? "" : "s", 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()
+ ));
+ break;
+ }
+
+ case ITEMMODE_2:
+ case ITEMMODE_RECHARGE:
+ case ITEMMODE_ENCHANT:
+ case ITEMMODE_REPAIR:
+ case ITEMMODE_IDENTIFY:
+ case ITEMMODE_TO_GOLD:
+ screen._windows[30].writeString(Common::String::format(X_FOR_Y,
+ CATEGORY_NAMES[category], startingChar->_name.c_str(),
+ (mode == ITEMMODE_RECHARGE || mode == ITEMMODE_ENCHANT) ? CHARGES : 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()
+ ));
+ break;
+
+ case ITEMMODE_3:
+ case ITEMMODE_5:
+ screen._windows[30].writeString(Common::String::format(X_FOR_Y_GOLD,
+ CATEGORY_NAMES[category], c->_name.c_str(), party._gold, 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()
+ ));
+ break;
+
+ default:
+ break;
+ }
+
+ // Draw the glyphs for the items
+ screen._windows[0].drawList(_itemsDrawList, INV_ITEMS_TOTAL);
+ screen._windows[0].update();
+ }
+
+ redrawFlag = REDRAW_NONE;
+
+ if (itemIndex != -1) {
+ switch (mode) {
+ case ITEMMODE_BLACKSMITH:
+ actionIndex = 0;
+ break;
+ case ITEMMODE_2:
+ 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
+ screen._windows[30].close();
+ screen._windows[29].close();
+ c = startingChar;
+ break;
+ }
+
+ // Otherwise, result and continue showing dialog
+ actionIndex = -1;
+ }
+
+ // Wait for a selection
+ _buttonValue = 0;
+ while (!_vm->shouldQuit() && !_buttonValue) {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ }
+ if (_vm->shouldQuit())
+ return nullptr;
+
+ // Handle escaping out of dialog
+ if (_buttonValue == Common::KEYCODE_ESCAPE) {
+ if (mode == ITEMMODE_8)
+ continue;
+ break;
+ }
+
+ // Handle other selections
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ 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
+ redrawFlag = REDRAW_TEXT;
+ Character *newChar = _vm->_mode == MODE_COMBAT ?
+ combat._combatParty[_buttonValue] : &party._activeParty[_buttonValue];
+
+ if (mode == ITEMMODE_BLACKSMITH) {
+ _oldCharacter = newChar;
+ startingChar = newChar;
+ c = &_itemsCharacter;
+ } else if (mode != ITEMMODE_2 && mode != ITEMMODE_REPAIR
+ && mode != ITEMMODE_IDENTIFY && itemIndex != -1) {
+ InventoryItems &destItems = newChar->_items[category];
+ XeenItem &destItem = destItems[INV_ITEMS_TOTAL - 1];
+ InventoryItems &srcItems = c->_items[category];
+ XeenItem &srcItem = srcItems[itemIndex];
+
+ if (srcItem._bonusFlags & ITEMFLAG_CURSED)
+ ErrorScroll::show(_vm, CANNOT_REMOVE_CURSED_ITEM);
+ else if (destItems[INV_ITEMS_TOTAL - 1]._id)
+ ErrorScroll::show(_vm, Common::String::format(
+ CATEGORY_BACKPACK_IS_FULL[category], c->_name.c_str()));
+ else {
+ destItem = srcItem;
+ srcItem.clear();
+ destItem._frame = 0;
+
+ srcItems.sort();
+ destItems.sort();
+ }
+
+ continue;
+ }
+
+ c = newChar;
+ startingChar = newChar;
+ intf.highlightChar(_buttonValue);
+ }
+ }
+ break;
+
+ case Common::KEYCODE_1:
+ case Common::KEYCODE_2:
+ case Common::KEYCODE_3:
+ case Common::KEYCODE_4:
+ case Common::KEYCODE_5:
+ case Common::KEYCODE_6:
+ case Common::KEYCODE_7:
+ case Common::KEYCODE_8:
+ case Common::KEYCODE_9:
+ // Select an item
+ if (mode == ITEMMODE_3)
+ break;
+
+ _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;
+ }
+
+ redrawFlag = REDRAW_TEXT;
+ }
+ break;
+
+ case Common::KEYCODE_a:
+ // Armor category
+ category = CATEGORY_ARMOR;
+ redrawFlag = REDRAW_FULL;
+ break;
+
+ case Common::KEYCODE_b:
+ // Buy
+ if (mode != ITEMMODE_CHAR_INFO && mode != ITEMMODE_RECHARGE &&
+ mode != ITEMMODE_ENCHANT && mode != ITEMMODE_TO_GOLD) {
+ mode = ITEMMODE_BLACKSMITH;
+ c = &_itemsCharacter;
+ redrawFlag = REDRAW_FULL;
+ }
+ break;
+
+ case Common::KEYCODE_c:
+ // Accessories category
+ category = CATEGORY_ACCESSORY;
+ redrawFlag = REDRAW_FULL;
+ break;
+
+ case Common::KEYCODE_d:
+ if (mode == ITEMMODE_CHAR_INFO)
+ actionIndex = 3;
+ break;
+
+ case Common::KEYCODE_e:
+ if (mode == ITEMMODE_CHAR_INFO || mode == ITEMMODE_ENCHANT ||
+ mode == ITEMMODE_TO_GOLD) {
+ if (category != CATEGORY_MISC) {
+ actionIndex = mode == ITEMMODE_ENCHANT ? 4 : 0;
+ }
+ }
+ break;
+
+ case Common::KEYCODE_f:
+ if (mode != ITEMMODE_CHAR_INFO && mode != ITEMMODE_RECHARGE &&
+ mode != ITEMMODE_ENCHANT && mode != ITEMMODE_TO_GOLD) {
+ mode = ITEMMODE_REPAIR;
+ c = startingChar;
+ redrawFlag = REDRAW_TEXT;
+ }
+ break;
+
+ case Common::KEYCODE_g:
+ if (mode == ITEMMODE_TO_GOLD)
+ actionIndex = 6;
+ break;
+
+ case Common::KEYCODE_i:
+ if (mode != ITEMMODE_CHAR_INFO && mode != ITEMMODE_RECHARGE &&
+ mode != ITEMMODE_ENCHANT && mode != ITEMMODE_TO_GOLD) {
+ mode = ITEMMODE_IDENTIFY;
+ c = startingChar;
+ redrawFlag = REDRAW_TEXT;
+ }
+ break;
+
+ case Common::KEYCODE_n:
+ // Misc category
+ category = CATEGORY_MISC;
+ redrawFlag = REDRAW_TEXT;
+ break;
+
+ case Common::KEYCODE_q:
+ if (mode == ITEMMODE_CHAR_INFO) {
+ Quests::show(_vm);
+ redrawFlag = REDRAW_TEXT;
+ }
+ break;
+
+ case Common::KEYCODE_r:
+ if (mode == ITEMMODE_CHAR_INFO)
+ actionIndex = 1;
+ else if (mode == ITEMMODE_RECHARGE)
+ actionIndex = 5;
+ break;
+
+ case Common::KEYCODE_s:
+ if (mode != ITEMMODE_CHAR_INFO && mode != ITEMMODE_RECHARGE &&
+ mode != ITEMMODE_ENCHANT && mode != ITEMMODE_TO_GOLD) {
+ mode = ITEMMODE_2;
+ c = startingChar;
+ redrawFlag = REDRAW_TEXT;
+ }
+ break;
+
+ case Common::KEYCODE_u:
+ if (mode == ITEMMODE_CHAR_INFO && category == CATEGORY_MISC)
+ actionIndex = 2;
+ break;
+
+ case Common::KEYCODE_w:
+ // Weapons category
+ category = CATEGORY_WEAPON;
+ redrawFlag = REDRAW_TEXT;
+ break;
+ }
+ }
+
+ intf.drawParty(true);
+ if (updateStock)
+ charData2BlackData();
+
+ return c;
+}
+
+void ItemsDialog::loadButtons(ItemsMode mode, Character *&c) {
+ _iconSprites.load(Common::String::format("%s.icn",
+ (mode == ITEMMODE_CHAR_INFO) ? "items" : "buy"));
+ _equipSprites.load("equip.icn");
+
+ if (mode == ITEMMODE_ENCHANT || mode == ITEMMODE_RECHARGE || mode == ITEMMODE_TO_GOLD) {
+ // Enchant button list
+ addButton(Common::Rect(12, 109, 36, 129), Common::KEYCODE_w, &_iconSprites);
+ addButton(Common::Rect(46, 109, 70, 129), Common::KEYCODE_a, &_iconSprites);
+ addButton(Common::Rect(80, 109, 104, 129), Common::KEYCODE_c, &_iconSprites);
+ addButton(Common::Rect(114, 109, 138, 129), Common::KEYCODE_n, &_iconSprites);
+ addButton(Common::Rect(148, 109, 172, 129), Common::KEYCODE_e, &_iconSprites);
+ addButton(Common::Rect(284, 109, 308, 129), Common::KEYCODE_ESCAPE, &_iconSprites);
+ addButton(Common::Rect(148, 109, 172, 129), Common::KEYCODE_u, &_iconSprites);
+ addButton(Common::Rect(8, 20, 263, 28), Common::KEYCODE_1);
+ addButton(Common::Rect(8, 29, 263, 37), Common::KEYCODE_2);
+ addButton(Common::Rect(8, 38, 263, 46), Common::KEYCODE_3);
+ addButton(Common::Rect(8, 47, 263, 55), Common::KEYCODE_4);
+ addButton(Common::Rect(8, 56, 263, 64), Common::KEYCODE_5);
+ addButton(Common::Rect(8, 65, 263, 73), Common::KEYCODE_6);
+ addButton(Common::Rect(8, 74, 263, 82), Common::KEYCODE_7);
+ addButton(Common::Rect(8, 83, 263, 91), Common::KEYCODE_8);
+ addButton(Common::Rect(8, 92, 263, 100), Common::KEYCODE_9);
+ } else {
+ addButton(Common::Rect(12, 109, 36, 129), Common::KEYCODE_w, &_iconSprites);
+ addButton(Common::Rect(46, 109, 70, 129), Common::KEYCODE_a, &_iconSprites);
+ addButton(Common::Rect(80, 109, 104, 129), Common::KEYCODE_c, &_iconSprites);
+ addButton(Common::Rect(114, 109, 138, 129), Common::KEYCODE_n, &_iconSprites);
+ addButton(Common::Rect(148, 109, 172, 129), Common::KEYCODE_e, &_iconSprites);
+ addButton(Common::Rect(182, 109, 206, 129), Common::KEYCODE_r, &_iconSprites);
+ addButton(Common::Rect(216, 109, 240, 129), Common::KEYCODE_d, &_iconSprites);
+ addButton(Common::Rect(250, 109, 274, 129), Common::KEYCODE_q, &_iconSprites);
+ addButton(Common::Rect(284, 109, 308, 129), Common::KEYCODE_ESCAPE, &_iconSprites);
+ addButton(Common::Rect(8, 20, 263, 28), Common::KEYCODE_1);
+ addButton(Common::Rect(8, 29, 263, 37), Common::KEYCODE_2);
+ addButton(Common::Rect(8, 38, 263, 46), Common::KEYCODE_3);
+ addButton(Common::Rect(8, 47, 263, 55), Common::KEYCODE_4);
+ addButton(Common::Rect(8, 56, 263, 64), Common::KEYCODE_5);
+ addButton(Common::Rect(8, 65, 263, 73), Common::KEYCODE_6);
+ addButton(Common::Rect(8, 74, 263, 82), Common::KEYCODE_7);
+ addButton(Common::Rect(8, 83, 263, 91), Common::KEYCODE_8);
+ addButton(Common::Rect(8, 92, 263, 100), Common::KEYCODE_9);
+ addPartyButtons(_vm);
+ }
+
+ if (mode == ITEMMODE_BLACKSMITH) {
+ _oldCharacter = c;
+ c = &_itemsCharacter;
+ blackData2CharData();
+
+ _buttons[4]._value = Common::KEYCODE_b;
+ _buttons[5]._value = Common::KEYCODE_s;
+ _buttons[6]._value = Common::KEYCODE_i;
+ _buttons[7]._value = Common::KEYCODE_f;
+
+ setEquipmentIcons();
+ } else {
+ _buttons[4]._value = Common::KEYCODE_e;
+ _buttons[5]._value = Common::KEYCODE_r;
+ _buttons[6]._value = Common::KEYCODE_d;
+ _buttons[7]._value = Common::KEYCODE_q;
+ }
+}
+
+void ItemsDialog::blackData2CharData() {
+ Party &party = *_vm->_party;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ int slotIndex = 0;
+ while (party._mazeId != (int)BLACKSMITH_MAP_IDS[isDarkCc][slotIndex] && slotIndex < 4)
+ ++slotIndex;
+ if (slotIndex == 4)
+ slotIndex = 0;
+
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ _itemsCharacter._weapons[idx] = party._blacksmithWeapons[isDarkCc][idx];
+ _itemsCharacter._armor[idx] = party._blacksmithArmor[isDarkCc][idx];
+ _itemsCharacter._accessories[idx] = party._blacksmithAccessories[isDarkCc][idx];
+ _itemsCharacter._misc[idx] = party._blacksmithMisc[isDarkCc][idx];
+ }
+}
+
+void ItemsDialog::charData2BlackData() {
+ Party &party = *_vm->_party;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ int slotIndex = 0;
+ while (party._mazeId != (int)BLACKSMITH_MAP_IDS[isDarkCc][slotIndex] && slotIndex < 4)
+ ++slotIndex;
+ if (slotIndex == 4)
+ slotIndex = 0;
+
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ party._blacksmithWeapons[isDarkCc][idx] = _itemsCharacter._weapons[idx];
+ party._blacksmithArmor[isDarkCc][idx] = _itemsCharacter._armor[idx];
+ party._blacksmithAccessories[isDarkCc][idx] = _itemsCharacter._accessories[idx];
+ party._blacksmithMisc[isDarkCc][idx] = _itemsCharacter._misc[idx];
+ }
+}
+
+void ItemsDialog::setEquipmentIcons() {
+ for (int typeIndex = 0; typeIndex < 4; ++typeIndex) {
+ for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
+ switch (typeIndex) {
+ case 0: {
+ 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 1: {
+ XeenItem &i = _itemsCharacter._armor[idx];
+ if (i._id <= 7)
+ i._frame = 3;
+ 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 2: {
+ 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;
+
+ switch (mode) {
+ case ITEMMODE_BLACKSMITH:
+ level = 0;
+ break;
+ case ITEMMODE_2:
+ 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: {
+ // 0=Weapons, 1=Armor, 2=Accessories
+ XeenItem &i = (mode == 0) ? c->_weapons[itemIndex] :
+ (mode == 1 ? c->_armor[itemIndex] : c->_accessories[itemIndex]);
+ amount1 = (mode == 0) ? WEAPON_BASE_COSTS[i._id] :
+ (mode == 1 ? ARMOR_BASE_COSTS[i._id] : ACCESSORY_BASE_COSTS[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 *= METAL_BASE_MULTIPLIERS[i._material - 37];
+ break;
+ }
+ }
+
+ if (i._material < 37)
+ amount2 = ELEMENTAL_DAMAGE[i._material] * 100;
+ else if (i._material > 58)
+ amount3 = METAL_BASE_MULTIPLIERS[i._material] * 100;
+
+ switch (mode) {
+ case ITEMMODE_BLACKSMITH:
+ case ITEMMODE_2:
+ case ITEMMODE_REPAIR:
+ case ITEMMODE_IDENTIFY:
+ case ITEMMODE_TO_GOLD:
+ result = (amount1 + amount2 + amount3 + amount4) / ITEM_SKILL_DIVISORS[level];
+ if (!result)
+ result = 1;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+
+ case 3: {
+ // Misc
+ XeenItem &i = c->_misc[itemIndex];
+ amount1 = MISC_MATERIAL_COSTS[i._material];
+ amount4 = MISC_BASE_COSTS[i._id];
+
+ switch (mode) {
+ case ITEMMODE_BLACKSMITH:
+ case ITEMMODE_2:
+ case ITEMMODE_REPAIR:
+ case ITEMMODE_IDENTIFY:
+ case ITEMMODE_TO_GOLD:
+ result = (amount1 + amount2 + amount3 + amount4) / ITEM_SKILL_DIVISORS[level];
+ if (!result)
+ result = 1;
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return (mode == ITEMMODE_CHAR_INFO) ? 0 : result;
+}
+
+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;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Spells &spells = *_vm->_spells;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ XeenItem *itemCategories[4] = { &c._weapons[0], &c._armor[0], &c._accessories[0], &c._misc[0] };
+ XeenItem *items = itemCategories[category];
+ if (!items[0]._id)
+ // Inventory is empty
+ return category == CATEGORY_MISC ? 0 : 2;
+
+ Window &w = screen._windows[11];
+ SpriteResource escSprites;
+ if (itemIndex < 0 || itemIndex > 8) {
+ saveButtons();
+
+ escSprites.load("esc.icn");
+ addButton(Common::Rect(235, 111, 259, 131), Common::KEYCODE_ESCAPE, &escSprites);
+ addButton(Common::Rect(8, 20, 263, 28), Common::KEYCODE_1);
+ addButton(Common::Rect(8, 29, 263, 37), Common::KEYCODE_2);
+ addButton(Common::Rect(8, 38, 263, 46), Common::KEYCODE_3);
+ addButton(Common::Rect(8, 47, 263, 55), Common::KEYCODE_4);
+ addButton(Common::Rect(8, 56, 263, 64), Common::KEYCODE_5);
+ addButton(Common::Rect(8, 65, 263, 73), Common::KEYCODE_6);
+ addButton(Common::Rect(8, 74, 263, 82), Common::KEYCODE_7);
+ addButton(Common::Rect(8, 83, 263, 91), Common::KEYCODE_8);
+ addButton(Common::Rect(8, 92, 263, 100), Common::KEYCODE_9);
+
+ w.open();
+ w.writeString(Common::String::format(WHICH_ITEM, ITEM_ACTIONS[actionIndex]));
+ _iconSprites.draw(screen, 0, Common::Point(235, 111));
+ w.update();
+
+ while (!_vm->shouldQuit()) {
+ while (!_buttonValue) {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ if (_vm->shouldQuit())
+ 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();
+ restoreButtons();
+ }
+
+ if (itemIndex != -1) {
+ XeenItem &item = c._items[category][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, 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(IN_NO_CONDITION, c._name.c_str()));
+ break;
+ default:
+ if (combat._itemFlag) {
+ ErrorScroll::show(_vm, USE_ITEM_IN_COMBAT);
+ } else if (i._id && (i._bonusFlags & ITEMFLAG_BONUS_MASK)
+ && !(i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED))) {
+ int charges = (i._bonusFlags & ITEMFLAG_BONUS_MASK) - 1;
+ i._bonusFlags = charges;
+ _oldCharacter = &c;
+
+ screen._windows[30].close();
+ screen._windows[29].close();
+ screen._windows[24].close();
+ spells.castItemSpell(i._id);
+
+ if (!charges) {
+ // Ran out of charges, so make item disappear
+ c._items[category][itemIndex].clear();
+ c._items[category].sort();
+ }
+ } else {
+ ErrorScroll::show(_vm, Common::String::format(NO_SPECIAL_ABILITIES,
+ c._items[category].getFullDescription(itemIndex).c_str()
+ ));
+ }
+ }
+ }
+ break;
+ case 3:
+ c._items[category].discardItem(itemIndex);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case ITEMMODE_BLACKSMITH: {
+ InventoryItems &invItems = _oldCharacter->_items[category];
+ if (invItems[INV_ITEMS_TOTAL - 1]._id) {
+ // If the last slot is in use, it means the list is full
+ ErrorScroll::show(_vm, Common::String::format(BACKPACK_IS_FULL,
+ _oldCharacter->_name.c_str()));
+ } else {
+ int cost = calcItemCost(_oldCharacter, itemIndex, mode, 0, category);
+ Common::String desc = c._items[category].getFullDescription(itemIndex);
+ if (Confirm::show(_vm, Common::String::format(BUY_X_FOR_Y_GOLD,
+ desc.c_str(), cost))) {
+ if (party.subtract(0, cost, 0, WT_FREEZE_WAIT)) {
+ if (isDarkCc) {
+ sound.playSample(0, 0);
+ File f("choice2.voc");
+ sound.playSample(&f, 0);
+ }
+
+ // Add entry to the end of the list
+ _oldCharacter->_items[category][8] = c._items[category][itemIndex];
+ _oldCharacter->_items[category][8]._frame = 0;
+ c._items[category].clear();
+ c._items[category].sort();
+ _oldCharacter->_items[category].sort();
+ }
+ }
+ }
+ return 0;
+ }
+
+ case ITEMMODE_2: {
+ bool noNeed;
+ switch (category) {
+ case CATEGORY_WEAPON:
+ noNeed = (item._bonusFlags & ITEMFLAG_CURSED) || item._id == 34;
+ break;
+ default:
+ noNeed = item._bonusFlags & ITEMFLAG_CURSED;
+ break;
+ }
+
+ if (noNeed) {
+ ErrorScroll::show(_vm, Common::String::format(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(SELL_X_FOR_Y_GOLD,
+ desc.c_str(), 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;
+ }
+ }
+ return 0;
+ }
+
+ case ITEMMODE_RECHARGE:
+ if (category != CATEGORY_MISC || c._misc[itemIndex]._material > 9
+ || c._misc[itemIndex]._id == 53 || c._misc[itemIndex]._id == 0) {
+ sound.playFX(21);
+ ErrorScroll::show(_vm, Common::String::format(NOT_RECHARGABLE, SPELL_FAILED));
+ } else {
+ int charges = MIN(63, _vm->getRandomNumber(1, 6) +
+ (c._misc[itemIndex]._bonusFlags & ITEMFLAG_BONUS_MASK));
+ sound.playFX(20);
+
+ c._misc[itemIndex]._bonusFlags = (c._misc[itemIndex]._bonusFlags
+ & ~ITEMFLAG_BONUS_MASK) | charges;
+ }
+ return 2;
+
+ case ITEMMODE_ENCHANT: {
+ int amount = _vm->getRandomNumber(1, _oldCharacter->getCurrentLevel() / 5 + 1);
+ amount = MIN(amount, 5);
+ _oldCharacter->_items[category].enchantItem(itemIndex, amount);
+ break;
+ }
+
+ case ITEMMODE_REPAIR:
+ if (!(item._bonusFlags & ITEMFLAG_BROKEN)) {
+ ErrorScroll::show(_vm, ITEM_NOT_BROKEN);
+ } else {
+ int cost = calcItemCost(&c, itemIndex, mode, actionIndex, category);
+ Common::String msg = Common::String::format(FIX_IDENTIFY_GOLD,
+ FIX_IDENTIFY[0],
+ c._items[category].getFullDescription(itemIndex).c_str(),
+ cost);
+
+ if (Confirm::show(_vm, msg) && party.subtract(0, cost, 0)) {
+ item._bonusFlags &= ~ITEMFLAG_BROKEN;
+ }
+ }
+ break;
+
+ case ITEMMODE_IDENTIFY: {
+ int cost = calcItemCost(&c, itemIndex, mode, actionIndex, category);
+ Common::String msg = Common::String::format(FIX_IDENTIFY_GOLD,
+ FIX_IDENTIFY[1],
+ c._items[category].getFullDescription(itemIndex).c_str(),
+ cost);
+
+ if (Confirm::show(_vm, msg) && party.subtract(0, cost, 0)) {
+ Common::String details = c._items[category].getIdentifiedDetails(itemIndex);
+ Common::String desc = c._items[category].getFullDescription(itemIndex);
+ Common::String str = Common::String::format(IDENTIFY_ITEM_MSG,
+ desc.c_str(), details.c_str());
+
+ Window &win = screen._windows[14];
+ win.open();
+ win.writeString(str);
+ win.update();
+
+ saveButtons();
+ clearButtons();
+
+ while (!_vm->shouldQuit() && !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;
+ }
+ }
+
+ intf._charsShooting = false;
+ combat.moveMonsters();
+ combat._whosTurn = -1;
+ return true;
+}
+
+void ItemsDialog::itemToGold(Character &c, int itemIndex, ItemCategory category,
+ ItemsMode mode) {
+ XeenItem &item = c._items[category][itemIndex];
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ if (category == CATEGORY_WEAPON && item._id == 34) {
+ sound.playFX(21);
+ ErrorScroll::show(_vm, Common::String::format("\v012\t000\x03c%s",
+ SPELL_FAILED));
+ } else if (item._id != 0) {
+ // 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();
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_items.h b/engines/xeen/dialogs_items.h
new file mode 100644
index 0000000000..6069ca823b
--- /dev/null
+++ b/engines/xeen/dialogs_items.h
@@ -0,0 +1,91 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_ITEMS_H
+#define XEEN_DIALOGS_ITEMS_H
+
+#include "xeen/dialogs.h"
+#include "xeen/party.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+enum ItemsMode {
+ ITEMMODE_CHAR_INFO = 0, ITEMMODE_BLACKSMITH = 1, ITEMMODE_2 = 2, ITEMMODE_3 = 3,
+ ITEMMODE_RECHARGE = 4, ITEMMODE_5 = 5, ITEMMODE_ENCHANT = 6, ITEMMODE_COMBAT = 7, ITEMMODE_8 = 8,
+ ITEMMODE_REPAIR = 9, ITEMMODE_IDENTIFY = 10, ITEMMODE_TO_GOLD = 11
+};
+
+class ItemsDialog : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+ SpriteResource _equipSprites;
+ Character _itemsCharacter;
+ Character *_oldCharacter;
+ DrawStruct _itemsDrawList[INV_ITEMS_TOTAL];
+
+ ItemsDialog(XeenEngine *vm) : ButtonContainer(),
+ _vm(vm), _oldCharacter(nullptr) {}
+
+ Character *execute(Character *c, ItemsMode mode);
+
+ /**
+ * Load the buttons for the dialog
+ */
+ void loadButtons(ItemsMode mode, Character *&c);
+
+ /**
+ * Loads the temporary _itemsCharacter character with the item set
+ * the given blacksmith has available, so the user can "view" the
+ * set as if it were a standard character's inventory
+ */
+ void blackData2CharData();
+
+ /**
+ * Saves the inventory from the temporary _itemsCharacter character back into the
+ * blacksmith storage, so changes in blacksmith inventory remain persistent
+ */
+ void charData2BlackData();
+
+ /**
+ * Sets the equipment icon to use for each item for display
+ */
+ void setEquipmentIcons();
+
+ /**
+ * Calculate the cost of an item
+ */
+ int calcItemCost(Character *c, int itemIndex, ItemsMode mode, int skillLevel,
+ ItemCategory category);
+
+ int doItemOptions(Character &c, int actionIndex, int itemIndex,
+ ItemCategory category, ItemsMode mode);
+
+ void itemToGold(Character &c, int itemIndex, ItemCategory category, ItemsMode mode);
+public:
+ static Character *show(XeenEngine *vm, Character *c, ItemsMode mode);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_ITEMS_H */
diff --git a/engines/xeen/dialogs_options.cpp b/engines/xeen/dialogs_options.cpp
new file mode 100644
index 0000000000..757634a2ad
--- /dev/null
+++ b/engines/xeen/dialogs_options.cpp
@@ -0,0 +1,232 @@
+/* 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 "common/scummsys.h"
+#include "xeen/dialogs_options.h"
+#include "xeen/resources.h"
+
+namespace Xeen {
+
+void OptionsMenu::show(XeenEngine *vm) {
+ OptionsMenu *menu;
+
+ switch (vm->getGameID()) {
+ case GType_Clouds:
+ menu = new CloudsOptionsMenu(vm);
+ break;
+ case GType_DarkSide:
+ menu = new DarkSideOptionsMenu(vm);
+ break;
+ case GType_WorldOfXeen:
+ menu = new WorldOptionsMenu(vm);
+ break;
+ default:
+ error("Unsupported game");
+ break;
+ }
+
+ menu->execute();
+ delete menu;
+}
+
+void OptionsMenu::execute() {
+ SpriteResource special("special.icn");
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+
+ File newBright("newbrigh.m");
+ _vm->_sound->playSong(newBright);
+
+ screen._windows[GAME_WINDOW].setBounds(Common::Rect(72, 25, 248, 175));
+
+ Common::String title1, title2;
+ startup(title1, title2);
+ SpriteResource title1Sprites(title1), title2Sprites(title2);
+
+ bool firstTime = true, doFade = true;
+ while (!_vm->shouldQuit()) {
+ setBackground(doFade);
+ events.setCursor(0);
+
+ if (firstTime) {
+ firstTime = false;
+ warning("TODO: Read existing save file");
+ }
+
+ showTitles1(title1Sprites);
+ showTitles2();
+
+ clearButtons();
+ setupButtons(&title2Sprites);
+ openWindow();
+
+ while (!_vm->shouldQuit()) {
+ // Show the dialog with a continually animating background
+ while (!_vm->shouldQuit() && !_buttonValue)
+ showContents(title1Sprites, true);
+ if (_vm->shouldQuit())
+ return;
+
+ // Handle keypress
+ int key = toupper(_buttonValue);
+ _buttonValue = 0;
+
+ if (key == 'C' || key == 'V') {
+ // Show credits
+ CreditsScreen::show(_vm);
+ break;
+ } else if (key == 27) {
+ // Hide the options menu
+ break;
+ }
+ }
+ }
+}
+
+void OptionsMenu::showTitles1(SpriteResource &sprites) {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+
+ int frameNum = 0;
+ while (!_vm->shouldQuit() && !events.isKeyMousePressed()) {
+ events.updateGameCounter();
+
+ frameNum = (frameNum + 1) % (_vm->getGameID() == GType_WorldOfXeen ? 5 : 10);
+ screen.restoreBackground();
+ sprites.draw(screen, frameNum);
+
+ events.wait(4, true);
+ }
+}
+
+void OptionsMenu::showTitles2() {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ SoundManager &sound = *_vm->_sound;
+
+ File voc("elect.voc");
+ SpriteResource titleSprites("title2b.raw");
+ SpriteResource kludgeSprites("kludge.int");
+ SpriteResource title2Sprites[8] = {
+ SpriteResource("title2b.int"), SpriteResource("title2c.int"),
+ SpriteResource("title2d.int"), SpriteResource("title2e.int"),
+ SpriteResource("title2f.int"), SpriteResource("title2g.int"),
+ SpriteResource("title2h.int"), SpriteResource("title2i.int"),
+ };
+
+ kludgeSprites.draw(screen, 0);
+ screen.saveBackground();
+ sound.playSample(&voc, 0);
+
+ for (int i = 0; i < 30 && !_vm->shouldQuit(); ++i) {
+ events.updateGameCounter();
+ screen.restoreBackground();
+ title2Sprites[i / 4].draw(screen, i % 4);
+ screen._windows[0].update();
+
+ if (i == 19)
+ sound.playSample(nullptr, 0);
+
+ while (!_vm->shouldQuit() && events.timeElapsed() < 2)
+ events.pollEventsAndWait();
+ }
+
+ screen.restoreBackground();
+ screen._windows[0].update();
+}
+
+void OptionsMenu::setupButtons(SpriteResource *buttons) {
+ addButton(Common::Rect(124, 87, 124 + 53, 87 + 10), 'S');
+ addButton(Common::Rect(126, 98, 126 + 47, 98 + 10), 'L');
+ addButton(Common::Rect(91, 110, 91 + 118, 110 + 10), 'C');
+ addButton(Common::Rect(85, 121, 85 + 131, 121 + 10), 'O');
+}
+
+void WorldOptionsMenu::setupButtons(SpriteResource *buttons) {
+ addButton(Common::Rect(93, 53, 93 + 134, 53 + 20), 'S', buttons);
+ addButton(Common::Rect(93, 78, 93 + 134, 78 + 20), 'L', buttons);
+ addButton(Common::Rect(93, 103, 93 + 134, 103 + 20), 'C', buttons);
+ addButton(Common::Rect(93, 128, 93 + 134, 128 + 20), 'O', buttons);
+}
+
+/*------------------------------------------------------------------------*/
+
+void CloudsOptionsMenu::startup(Common::String &title1, Common::String &title2) {
+ title1 = "title1.int";
+ title2 = "title1a.int";
+}
+
+/*------------------------------------------------------------------------*/
+
+void DarkSideOptionsMenu::startup(Common::String &title1, Common::String &title2) {
+ title1 = "title2.int";
+ title2 = "title2a.int";
+}
+
+void WorldOptionsMenu::startup(Common::String &title1, Common::String &title2) {
+ title1 = "world.int";
+ title2 = "start.icn";
+
+ Screen &screen = *_vm->_screen;
+ screen.fadeOut(4);
+ screen.loadPalette("dark.pal");
+ _vm->_events->clearEvents();
+}
+
+void WorldOptionsMenu::setBackground(bool doFade) {
+ Screen &screen = *_vm->_screen;
+ screen.loadBackground("world.raw");
+ screen.saveBackground();
+
+ if (doFade)
+ screen.fadeIn(4);
+}
+
+void WorldOptionsMenu::openWindow() {
+ _vm->_screen->_windows[GAME_WINDOW].open();
+}
+
+void WorldOptionsMenu::showContents(SpriteResource &title1, bool waitFlag) {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ events.updateGameCounter();
+
+ // Draw the background frame in a continous cycle
+ _bgFrame = (_bgFrame + 1) % 5;
+ title1.draw(screen._windows[0], _bgFrame);
+
+ // Draw the basic frame for the optitons menu and title text
+ screen._windows[GAME_WINDOW].frame();
+ screen._windows[GAME_WINDOW].writeString(OPTIONS_TITLE);
+
+ drawButtons(&screen._windows[0]);
+
+ if (waitFlag) {
+ screen._windows[0].update();
+
+ while (!_vm->shouldQuit() && !_buttonValue && events.timeElapsed() < 3) {
+ checkEvents(_vm);
+ }
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_options.h b/engines/xeen/dialogs_options.h
new file mode 100644
index 0000000000..bb4aea9a36
--- /dev/null
+++ b/engines/xeen/dialogs_options.h
@@ -0,0 +1,95 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_OPTIONS_H
+#define XEEN_DIALOGS_OPTIONS_H
+
+#include "xeen/xeen.h"
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class OptionsMenu : public SettingsBaseDialog {
+private:
+ void execute();
+protected:
+ OptionsMenu(XeenEngine *vm) : SettingsBaseDialog(vm) {}
+protected:
+ virtual void startup(Common::String &title1, Common::String &title2) = 0;
+
+ virtual void setBackground(bool doFade) {}
+
+ virtual void showTitles1(SpriteResource &sprites);
+
+ virtual void showTitles2();
+
+ virtual void setupButtons(SpriteResource *buttons);
+
+ virtual void openWindow() {}
+public:
+ virtual ~OptionsMenu() {}
+
+ static void show(XeenEngine *vm);
+};
+
+class CloudsOptionsMenu : public OptionsMenu {
+protected:
+ virtual void startup(Common::String &title1, Common::String &title2);
+public:
+ CloudsOptionsMenu(XeenEngine *vm) : OptionsMenu(vm) {}
+
+ virtual ~CloudsOptionsMenu() {}
+};
+
+class DarkSideOptionsMenu : public OptionsMenu {
+protected:
+ virtual void startup(Common::String &title1, Common::String &title2);
+public:
+ DarkSideOptionsMenu(XeenEngine *vm) : OptionsMenu(vm) {}
+
+ virtual ~DarkSideOptionsMenu() {}
+};
+
+class WorldOptionsMenu : public DarkSideOptionsMenu {
+private:
+ int _bgFrame;
+protected:
+ virtual void startup(Common::String &title1, Common::String &title2);
+
+ virtual void setBackground(bool doFade);
+
+ virtual void showTitles2() {}
+
+ virtual void setupButtons(SpriteResource *buttons);
+
+ virtual void openWindow();
+
+ virtual void showContents(SpriteResource &title1, bool mode);
+public:
+ WorldOptionsMenu(XeenEngine *vm) : DarkSideOptionsMenu(vm), _bgFrame(0) {}
+
+ virtual ~WorldOptionsMenu() {}
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_H */
diff --git a/engines/xeen/dialogs_party.cpp b/engines/xeen/dialogs_party.cpp
new file mode 100644
index 0000000000..a03e2d763d
--- /dev/null
+++ b/engines/xeen/dialogs_party.cpp
@@ -0,0 +1,1045 @@
+/* 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 "common/scummsys.h"
+#include "xeen/dialogs_char_info.h"
+#include "xeen/dialogs_party.h"
+#include "xeen/dialogs_input.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/character.h"
+#include "xeen/events.h"
+#include "xeen/party.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+PartyDialog::PartyDialog(XeenEngine *vm) : ButtonContainer(),
+ PartyDrawer(vm), _vm(vm) {
+ initDrawStructs();
+}
+
+void PartyDialog::show(XeenEngine *vm) {
+ PartyDialog *dlg = new PartyDialog(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void PartyDialog::execute() {
+ EventsManager &events = *_vm->_events;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ bool modeFlag = false;
+ int startingChar = 0;
+
+ loadButtons();
+ setupBackground();
+
+ while (!_vm->shouldQuit()) {
+ _vm->_mode = MODE_1;
+
+ // Build up a list of available characters in the Roster that are on the
+ // same side of Xeen as the player is currently on
+ _charList.clear();
+ for (int i = 0; i < XEEN_TOTAL_CHARACTERS; ++i) {
+ Character &player = party._roster[i];
+ if (player._name.empty() || player._xeenSide != (map._loadDarkSide ? 1 : 0))
+ continue;
+
+ _charList.push_back(i);
+ }
+
+ Window &w = screen._windows[11];
+ w.open();
+ setupFaces(startingChar, false);
+ w.writeString(Common::String::format(PARTY_DIALOG_TEXT, _partyDetails.c_str()));
+ w.drawList(&_faceDrawStructs[0], 4);
+
+ _uiSprites.draw(w, 0, Common::Point(16, 100));
+ _uiSprites.draw(w, 2, Common::Point(52, 100));
+ _uiSprites.draw(w, 4, Common::Point(87, 100));
+ _uiSprites.draw(w, 6, Common::Point(122, 100));
+ _uiSprites.draw(w, 8, Common::Point(157, 100));
+ _uiSprites.draw(w, 10, Common::Point(192, 100));
+ screen.loadPalette("mm4.pal");
+
+ if (modeFlag) {
+ screen._windows[0].update();
+ events.setCursor(0);
+ screen.fadeIn(4);
+ } else {
+ if (_vm->getGameID() == GType_DarkSide) {
+ screen.fadeOut(4);
+ screen._windows[0].update();
+ }
+
+ doScroll(_vm, false, false);
+ events.setCursor(0);
+
+ if (_vm->getGameID() == GType_DarkSide) {
+ screen.fadeIn(4);
+ }
+ }
+
+ bool breakFlag = false;
+ while (!_vm->shouldQuit() && !breakFlag) {
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ } while (!_vm->shouldQuit() && !_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_ESCAPE:
+ case Common::KEYCODE_SPACE:
+ case Common::KEYCODE_e:
+ case Common::KEYCODE_x:
+ if (party._activeParty.size() == 0) {
+ ErrorScroll::show(_vm, NO_ONE_TO_ADVENTURE_WITH);
+ } else {
+ if (_vm->_mode != MODE_0) {
+ for (int idx = 4; idx >= 0; --idx) {
+ events.updateGameCounter();
+ screen.frameWindow(idx);
+ w.update();
+
+ while (events.timeElapsed() < 1)
+ events.pollEventsAndWait();
+ }
+ }
+
+ w.close();
+ party._mazeId = party._priorMazeId;
+
+ party.copyPartyToRoster();
+ _vm->_saves->writeCharFile();
+ return;
+ }
+ break;
+
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ // Show character info
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size())
+ CharacterInfo::show(_vm, _buttonValue);
+ break;
+
+ case Common::KEYCODE_1:
+ case Common::KEYCODE_2:
+ case Common::KEYCODE_3:
+ case Common::KEYCODE_4:
+ _buttonValue -= Common::KEYCODE_1 - 7;
+ if ((_buttonValue - 7 + startingChar) < (int)_charList.size()) {
+ // Check if the selected character is already in the party
+ uint idx = 0;
+ for (; idx < party._activeParty.size(); ++idx) {
+ if (_charList[_buttonValue - 7 + startingChar] ==
+ party._activeParty[idx]._rosterId)
+ break;
+ }
+
+ // Only add the character if they're not already in the party
+ if (idx == party._activeParty.size()) {
+ if (party._activeParty.size() == MAX_ACTIVE_PARTY) {
+ sound.playFX(21);
+ ErrorScroll::show(_vm, YOUR_PARTY_IS_FULL);
+ } else {
+ // Add the character to the active party
+ party._activeParty.push_back(party._roster[
+ _charList[_buttonValue - 7 + startingChar]]);
+ startingCharChanged(startingChar);
+ }
+ }
+ }
+ break;
+
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ // Up arrow
+ if (startingChar > 0) {
+ startingChar -= 4;
+ startingCharChanged(startingChar);
+ }
+ break;
+
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ // Down arrow
+ if (startingChar < ((int)_charList.size() - 4)) {
+ startingChar += 4;
+ startingCharChanged(startingChar);
+ }
+ break;
+
+ case Common::KEYCODE_c:
+ // Create
+ if (_charList.size() == XEEN_TOTAL_CHARACTERS) {
+ ErrorScroll::show(_vm, YOUR_ROSTER_IS_FULL);
+ } else {
+ screen.fadeOut(4);
+ w.close();
+
+ createChar();
+
+ party.copyPartyToRoster();
+ _vm->_saves->writeCharFile();
+ screen.fadeOut(4);
+ modeFlag = true;
+ breakFlag = true;
+ }
+ break;
+
+ case Common::KEYCODE_d:
+ // Delete character
+ if (_charList.size() > 0) {
+ int charButtonValue = selectCharacter(true, startingChar);
+ if (charButtonValue != 0) {
+ int charIndex = charButtonValue - Common::KEYCODE_1 + startingChar;
+ Character &c = party._roster[_charList[charIndex]];
+ if (c.hasSpecialItem()) {
+ ErrorScroll::show(_vm, HAS_SLAYER_SWORD);
+ } else {
+ Common::String msg = Common::String::format(SURE_TO_DELETE_CHAR,
+ c._name.c_str(), CLASS_NAMES[c._class]);
+ if (Confirm::show(_vm, msg)) {
+ // If the character is in the party, remove it
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (party._activeParty[idx]._rosterId == c._rosterId) {
+ party._activeParty.remove_at(idx);
+ break;
+ }
+ }
+
+ // Empty the character in the roster
+ c.clear();
+
+ // Rebuild the character list
+ _charList.clear();
+ for (int idx = 0; idx < XEEN_TOTAL_CHARACTERS; ++idx) {
+ Character &ch = party._roster[idx];
+ if (!ch._name.empty() && ch._savedMazeId == party._priorMazeId) {
+ _charList.push_back(idx);
+ }
+ }
+
+ startingCharChanged(startingChar);
+ }
+ }
+ }
+ }
+ break;
+
+ case Common::KEYCODE_r:
+ // Remove character
+ if (party._activeParty.size() > 0) {
+ int charButtonValue = selectCharacter(false, startingChar);
+ if (charButtonValue != 0) {
+ party.copyPartyToRoster();
+ party._activeParty.remove_at(charButtonValue - Common::KEYCODE_F1);
+ }
+ startingCharChanged(startingChar);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
+void PartyDialog::loadButtons() {
+ _uiSprites.load("inn.icn");
+ addButton(Common::Rect(16, 100, 40, 120), Common::KEYCODE_UP, &_uiSprites);
+ addButton(Common::Rect(52, 100, 76, 120), Common::KEYCODE_DOWN, &_uiSprites);
+ addButton(Common::Rect(87, 100, 111, 120), Common::KEYCODE_d, &_uiSprites);
+ addButton(Common::Rect(122, 100, 146, 120), Common::KEYCODE_r, &_uiSprites);
+ addButton(Common::Rect(157, 100, 181, 120), Common::KEYCODE_c, &_uiSprites);
+ addButton(Common::Rect(192, 100, 216, 120), Common::KEYCODE_x, &_uiSprites);
+ addButton(Common::Rect(0, 0, 0, 0), Common::KEYCODE_ESCAPE);
+}
+
+void PartyDialog::initDrawStructs() {
+ _faceDrawStructs[0] = DrawStruct(0, 0, 0);
+ _faceDrawStructs[1] = DrawStruct(0, 101, 0);
+ _faceDrawStructs[2] = DrawStruct(0, 0, 43);
+ _faceDrawStructs[3] = DrawStruct(0, 101, 43);
+}
+
+void PartyDialog::setupBackground() {
+ _vm->_screen->loadBackground("back.raw");
+ _vm->_interface->assembleBorder();
+}
+
+void PartyDialog::setupFaces(int firstDisplayChar, bool updateFlag) {
+ Party &party = *_vm->_party;
+ Common::String charNames[4];
+ Common::String charRaces[4];
+ Common::String charSex[4];
+ Common::String charClasses[4];
+ int posIndex;
+ int charId;
+
+ // Reset the button areas for the display character images
+ while (_buttons.size() > 7)
+ _buttons.remove_at(7);
+ addButton(Common::Rect(16, 16, 48, 48), Common::KEYCODE_1);
+ addButton(Common::Rect(117, 16, 149, 48), Common::KEYCODE_2);
+ addButton(Common::Rect(59, 59, 91, 91), Common::KEYCODE_3);
+ addButton(Common::Rect(117, 59, 151, 91), Common::KEYCODE_4);
+
+
+ for (posIndex = 0; posIndex < 4; ++posIndex) {
+ charId = (firstDisplayChar + posIndex) >= (int)_charList.size() ? -1 :
+ _charList[firstDisplayChar + posIndex];
+ bool isInParty = party.isInParty(charId);
+
+ if (charId == -1) {
+ while ((int)_buttons.size() >(7 + posIndex))
+ _buttons.remove_at(_buttons.size() - 1);
+ break;
+ }
+
+ Common::Rect &b = _buttons[7 + posIndex]._bounds;
+ b.moveTo((posIndex & 1) ? 117 : 16, b.top);
+ Character &ps = party._roster[_charList[firstDisplayChar + posIndex]];
+ charNames[posIndex] = isInParty ? IN_PARTY : ps._name;
+ charRaces[posIndex] = RACE_NAMES[ps._race];
+ charSex[posIndex] = SEX_NAMES[ps._sex];
+ charClasses[posIndex] = CLASS_NAMES[ps._class];
+ }
+
+ drawParty(updateFlag);
+
+ // Set up the sprite set to use for each face
+ for (posIndex = 0; posIndex < 4; ++posIndex) {
+ if ((firstDisplayChar + posIndex) >= (int)_charList.size())
+ _faceDrawStructs[posIndex]._sprites = nullptr;
+ else
+ _faceDrawStructs[posIndex]._sprites = party._roster[
+ _charList[firstDisplayChar + posIndex]]._faceSprites;
+ }
+
+ _partyDetails = Common::String::format(PARTY_DETAILS,
+ charNames[0].c_str(), charRaces[0].c_str(), charSex[0].c_str(), charClasses[0].c_str(),
+ charNames[1].c_str(), charRaces[1].c_str(), charSex[1].c_str(), charClasses[1].c_str(),
+ charNames[2].c_str(), charRaces[2].c_str(), charSex[2].c_str(), charClasses[2].c_str(),
+ charNames[3].c_str(), charRaces[3].c_str(), charSex[3].c_str(), charClasses[3].c_str()
+ );
+}
+
+void PartyDialog::startingCharChanged(int firstDisplayChar) {
+ Window &w = _vm->_screen->_windows[11];
+
+ setupFaces(firstDisplayChar, true);
+ w.writeString(Common::String::format(PARTY_DIALOG_TEXT, _partyDetails.c_str()));
+ w.drawList(_faceDrawStructs, 4);
+
+ _uiSprites.draw(w, 0, Common::Point(16, 100));
+ _uiSprites.draw(w, 2, Common::Point(52, 100));
+ _uiSprites.draw(w, 4, Common::Point(87, 100));
+ _uiSprites.draw(w, 6, Common::Point(122, 100));
+ _uiSprites.draw(w, 8, Common::Point(157, 100));
+ _uiSprites.draw(w, 10, Common::Point(192, 100));
+
+ w.update();
+}
+
+void PartyDialog::createChar() {
+ EventsManager &events = *_vm->_events;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Window &w = screen._windows[0];
+ SpriteResource dice, icons;
+ Common::Array<int> freeCharList;
+ int classId;
+ int selectedClass = 0;
+ bool hasFadedIn = false;
+ bool restartFlag = true;
+ uint attribs[TOTAL_ATTRIBUTES];
+ bool allowedClasses[TOTAL_CLASSES];
+ Race race;
+ Sex sex;
+ Common::String msg;
+ int charIndex;
+
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_4;
+ dice.load("dice.vga");
+ icons.load("create.raw");
+
+ _dicePos[0] = Common::Point(20, 17);
+ _dicePos[1] = Common::Point(112, 35);
+ _dicePos[2] = Common::Point(61, 50);
+ _diceFrame[0] = 0;
+ _diceFrame[1] = 2;
+ _diceFrame[2] = 4;
+ _diceInc[0] = Common::Point(10, -10);
+ _diceInc[1] = Common::Point(-10, -10);
+ _diceInc[2] = Common::Point(-10, 10);
+
+ // Add buttons
+ saveButtons();
+ addButton(Common::Rect(132, 98, 156, 118), Common::KEYCODE_r, &icons);
+ addButton(Common::Rect(132, 128, 156, 148), Common::KEYCODE_c, &icons);
+ addButton(Common::Rect(132, 158, 156, 178), Common::KEYCODE_ESCAPE, &icons);
+ addButton(Common::Rect(86, 98, 110, 118), Common::KEYCODE_UP, &icons);
+ addButton(Common::Rect(86, 120, 110, 140), Common::KEYCODE_DOWN, &icons);
+ addButton(Common::Rect(168, 19, 192, 39), Common::KEYCODE_n, nullptr);
+ addButton(Common::Rect(168, 43, 192, 63), Common::KEYCODE_i, nullptr);
+ addButton(Common::Rect(168, 67, 192, 87), Common::KEYCODE_p, nullptr);
+ addButton(Common::Rect(168, 91, 192, 111), Common::KEYCODE_e, nullptr);
+ addButton(Common::Rect(168, 115, 192, 135), Common::KEYCODE_s, nullptr);
+ addButton(Common::Rect(168, 139, 192, 159), Common::KEYCODE_a, nullptr);
+ addButton(Common::Rect(168, 163, 192, 183), Common::KEYCODE_l, nullptr);
+ addButton(Common::Rect(227, 19, 139, 29), 1000, nullptr);
+ addButton(Common::Rect(227, 30, 139, 40), 1001, nullptr);
+ addButton(Common::Rect(227, 41, 139, 51), 1002, nullptr);
+ addButton(Common::Rect(227, 52, 139, 62), 1003, nullptr);
+ addButton(Common::Rect(227, 63, 139, 73), 1004, nullptr);
+ addButton(Common::Rect(227, 74, 139, 84), 1005, nullptr);
+ addButton(Common::Rect(227, 85, 139, 95), 1006, nullptr);
+ addButton(Common::Rect(227, 96, 139, 106), 1007, nullptr);
+ addButton(Common::Rect(227, 107, 139, 117), 1008, nullptr);
+ addButton(Common::Rect(227, 118, 139, 128), 1009, nullptr);
+
+ // Load the background
+ screen.loadBackground("create.raw");
+ events.setCursor(0);
+
+ while (!_vm->shouldQuit()) {
+ classId = -1;
+
+ if (restartFlag) {
+ // Build up list of roster slot indexes that are free
+ freeCharList.clear();
+ for (uint idx = 0; idx < XEEN_TOTAL_CHARACTERS; ++idx) {
+ if (party._roster[idx]._name.empty())
+ freeCharList.push_back(idx);
+ }
+ charIndex = 0;
+ //bool flag9 = true;
+
+ if (freeCharList.size() == XEEN_TOTAL_CHARACTERS)
+ break;
+
+ // Get and race and sex for the given character
+ race = (Race)((freeCharList[charIndex] / 4) % 5);
+ sex = (Sex)(freeCharList[charIndex] & 1);
+
+ // Randomly determine attributes, and which classes they allow
+ throwDice(attribs, allowedClasses);
+
+ // Set up display of the rolled character details
+ selectedClass = newCharDetails(attribs, allowedClasses,
+ race, sex, classId, selectedClass, msg);
+
+ // Draw the screen
+ icons.draw(w, 10, Common::Point(168, 19));
+ icons.draw(w, 12, Common::Point(168, 43));
+ icons.draw(w, 14, Common::Point(168, 67));
+ icons.draw(w, 16, Common::Point(168, 91));
+ icons.draw(w, 18, Common::Point(168, 115));
+ icons.draw(w, 20, Common::Point(168, 139));
+ icons.draw(w, 22, Common::Point(168, 163));
+ for (int idx = 0; idx < 9; ++idx)
+ icons.draw(w, 24 + idx * 2, Common::Point(227, 19 + 11 * idx));
+
+ for (int idx = 0; idx < 7; ++idx)
+ icons.draw(w, 50 + idx, Common::Point(195, 31 + 24 * idx));
+
+ icons.draw(w, 57, Common::Point(62, 148));
+ icons.draw(w, 58, Common::Point(62, 158));
+ icons.draw(w, 59, Common::Point(62, 168));
+ icons.draw(w, 61, Common::Point(220, 19));
+ icons.draw(w, 64, Common::Point(220, 155));
+ icons.draw(w, 65, Common::Point(220, 170));
+
+ party._roster[freeCharList[charIndex]]._faceSprites->draw(
+ w, 0, Common::Point(27, 102));
+
+ icons.draw(w, 0, Common::Point(132, 98));
+ icons.draw(w, 2, Common::Point(132, 128));
+ icons.draw(w, 4, Common::Point(132, 158));
+ icons.draw(w, 6, Common::Point(86, 98));
+ icons.draw(w, 8, Common::Point(86, 120));
+
+ w.writeString(msg);
+ w.update();
+
+ // Draw the arrow for the selected class, if applicable
+ if (selectedClass != -1)
+ printSelectionArrow(icons, selectedClass);
+
+ // Draw the dice
+ drawDice(dice);
+ if (!hasFadedIn) {
+ screen.fadeIn(4);
+ hasFadedIn = true;
+ }
+
+ restartFlag = false;
+ }
+
+ // Animate the dice until a user action occurs
+ _buttonValue = 0;
+ while (!_vm->shouldQuit() && !_buttonValue)
+ drawDice(dice);
+
+ // Handling for different actions
+ switch (_buttonValue) {
+ case Common::KEYCODE_UP:
+ if (charIndex == 0)
+ continue;
+
+ race = (Race)((freeCharList[charIndex] / 4) % 5);
+ sex = (Sex)(freeCharList[charIndex] & 1);
+ break;
+
+ case Common::KEYCODE_DOWN:
+ if (++charIndex == (int)freeCharList.size()) {
+ --charIndex;
+ continue;
+ } else {
+ race = (Race)((freeCharList[charIndex] / 4) % 5);
+ sex = (Sex)(freeCharList[charIndex] & 1);
+ }
+ break;
+
+ case Common::KEYCODE_PAGEUP:
+ for (int tempClass = selectedClass - 1; tempClass >= 0; --tempClass) {
+ if (allowedClasses[tempClass]) {
+ selectedClass = tempClass;
+ break;
+ }
+ }
+
+ printSelectionArrow(icons, selectedClass);
+ continue;
+
+ case Common::KEYCODE_PAGEDOWN:
+ break;
+
+ case Common::KEYCODE_m:
+ case Common::KEYCODE_i:
+ case Common::KEYCODE_p:
+ case Common::KEYCODE_e:
+ case Common::KEYCODE_s:
+ case Common::KEYCODE_a:
+ case Common::KEYCODE_l: {
+ Attribute srcAttrib, destAttrib;
+ if (_buttonValue == Common::KEYCODE_m)
+ srcAttrib = MIGHT;
+ else if (_buttonValue == Common::KEYCODE_i)
+ srcAttrib = INTELLECT;
+ else if (_buttonValue == Common::KEYCODE_p)
+ srcAttrib = PERSONALITY;
+ else if (_buttonValue == Common::KEYCODE_e)
+ srcAttrib = ENDURANCE;
+ else if (_buttonValue == Common::KEYCODE_s)
+ srcAttrib = SPEED;
+ else if (_buttonValue == Common::KEYCODE_a)
+ srcAttrib = ACCURACY;
+ else
+ srcAttrib = LUCK;
+
+ _vm->_mode = MODE_86;
+ icons.draw(w, srcAttrib * 2 + 11, Common::Point(
+ _buttons[srcAttrib + 5]._bounds.left, _buttons[srcAttrib + 5]._bounds.top));
+ w.update();
+
+ int destAttribVal = exchangeAttribute(srcAttrib + 1);
+ if (destAttribVal) {
+ destAttrib = (Attribute)(destAttribVal - 1);
+ icons.draw(w, destAttrib * 2 + 11, Common::Point(
+ _buttons[destAttrib + 10]._bounds.left,
+ _buttons[destAttrib + 10]._bounds.top));
+ w.update();
+
+ SWAP(attribs[srcAttrib], attribs[destAttrib]);
+ checkClass(attribs, allowedClasses);
+ classId = -1;
+ selectedClass = newCharDetails(attribs, allowedClasses,
+ race, sex, classId, selectedClass, msg);
+ } else {
+ icons.draw(w, srcAttrib * 2 + 10, Common::Point(
+ _buttons[srcAttrib + 5]._bounds.left,
+ _buttons[srcAttrib + 5]._bounds.top));
+ w.update();
+ _vm->_mode = MODE_SLEEPING;
+ continue;
+ }
+ break;
+ }
+
+ case 1000:
+ case 1001:
+ case 1002:
+ case 1003:
+ case 1004:
+ case 1005:
+ case 1006:
+ case 1007:
+ case 1008:
+ case 1009:
+ if (allowedClasses[_buttonValue - 1000]) {
+ selectedClass = classId = _buttonValue - 1000;
+ }
+ break;
+
+ case Common::KEYCODE_c: {
+ _vm->_mode = MODE_FF;
+ bool result = saveCharacter(party._roster[freeCharList[charIndex]],
+ classId, race, sex, attribs);
+ _vm->_mode = MODE_4;
+
+ if (result)
+ restartFlag = true;
+ continue;
+ }
+
+ case Common::KEYCODE_RETURN:
+ classId = selectedClass;
+ break;
+
+ case Common::KEYCODE_SPACE:
+ case Common::KEYCODE_r:
+ // Re-roll the attributes
+ throwDice(attribs, allowedClasses);
+ classId = -1;
+ break;
+
+ default:
+ // For all other keypresses, skip the code below the switch
+ // statement, and go to wait for the next key
+ continue;
+ }
+
+ if (_buttonValue != Common::KEYCODE_PAGEDOWN) {
+ selectedClass = newCharDetails(attribs, allowedClasses,
+ race, sex, classId, selectedClass, msg);
+
+ for (int idx = 0; idx < 7; ++idx)
+ icons.draw(w, 10 + idx * 2, Common::Point(168, 19 + idx * 24));
+ for (int idx = 0; idx < 10; ++idx)
+ icons.draw(w, 24 + idx * 2, Common::Point(227, 19 + idx * 11));
+ for (int idx = 0; idx < 8; ++idx)
+ icons.draw(w, 50 + idx, Common::Point(195, 31 + idx * 24));
+
+ icons.draw(w, 57, Common::Point(62, 148));
+ icons.draw(w, 58, Common::Point(62, 158));
+ icons.draw(w, 59, Common::Point(62, 168));
+ icons.draw(w, 61, Common::Point(220, 19));
+ icons.draw(w, 64, Common::Point(220, 155));
+ icons.draw(w, 65, Common::Point(220, 170));
+
+ party._roster[freeCharList[charIndex]]._faceSprites->draw(w, 0,
+ Common::Point(27, 102));
+
+ icons.draw(w, 0, Common::Point(132, 98));
+ icons.draw(w, 2, Common::Point(132, 128));
+ icons.draw(w, 4, Common::Point(132, 158));
+ icons.draw(w, 6, Common::Point(86, 98));
+ icons.draw(w, 8, Common::Point(86, 120));
+
+ w.writeString(msg);
+ w.update();
+
+ if (selectedClass != -1) {
+ printSelectionArrow(icons, selectedClass);
+ continue;
+ }
+ }
+
+ // Move to next available class, or if the code block above resulted in
+ // selectedClass being -1, move to select the first available class
+ for (int tempClass = selectedClass + 1; tempClass <= CLASS_RANGER; ++tempClass) {
+ if (allowedClasses[tempClass]) {
+ selectedClass = tempClass;
+ break;
+ }
+ }
+
+ printSelectionArrow(icons, selectedClass);
+ } while (!_vm->shouldQuit() && _buttonValue != Common::KEYCODE_ESCAPE);
+
+ _vm->_mode = oldMode;
+}
+
+int PartyDialog::selectCharacter(bool isDelete, int firstDisplayChar) {
+ EventsManager &events = *_vm->_events;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Window &w = screen._windows[28];
+
+ SpriteResource iconSprites;
+ iconSprites.load("esc.icn");
+
+ w.setBounds(Common::Rect(50, isDelete ? 112 : 76, 266, isDelete ? 148 : 112));
+ w.open();
+ w.writeString(Common::String::format(REMOVE_OR_DELETE_WHICH,
+ REMOVE_DELETE[isDelete ? 1 : 0]));
+ iconSprites.draw(w, 0, Common::Point(225, isDelete ? 120 : 84));
+ w.update();
+
+ saveButtons();
+ addButton(Common::Rect(225, isDelete ? 120 : 84, 249, isDelete ? 140 : 104),
+ Common::KEYCODE_ESCAPE, &iconSprites);
+ addButton(Common::Rect(16, 16, 48, 48), Common::KEYCODE_1);
+ addButton(Common::Rect(117, 16, 149, 48), Common::KEYCODE_2);
+ addButton(Common::Rect(16, 59, 48, 91), Common::KEYCODE_3);
+ addButton(Common::Rect(117, 59, 149, 91), Common::KEYCODE_4);
+ addPartyButtons(_vm);
+
+ int result = -1, v;
+ while (!_vm->shouldQuit() && result == -1) {
+ _buttonValue = 0;
+ while (!_vm->shouldQuit() && !_buttonValue) {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ }
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_ESCAPE:
+ result = 0;
+ break;
+
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ if (!isDelete) {
+ v = _buttonValue - Common::KEYCODE_F1;
+ if (v < (int)party._activeParty.size())
+ result = _buttonValue;
+ }
+ break;
+
+ case Common::KEYCODE_1:
+ case Common::KEYCODE_2:
+ case Common::KEYCODE_3:
+ case Common::KEYCODE_4:
+ if (isDelete) {
+ v = _buttonValue - Common::KEYCODE_1;
+ if ((firstDisplayChar + v) < (int)_charList.size())
+ result = _buttonValue;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ w.close();
+ restoreButtons();
+ return result == -1 ? 0 : result;
+}
+
+void PartyDialog::throwDice(uint attribs[TOTAL_ATTRIBUTES], bool allowedClasses[TOTAL_CLASSES]) {
+ bool repeat = true;
+ do {
+ // Default all the attributes to zero
+ Common::fill(&attribs[0], &attribs[TOTAL_ATTRIBUTES], 0);
+
+ // Assign random amounts to each attribute
+ for (int idx1 = 0; idx1 < 3; ++idx1) {
+ for (int idx2 = 0; idx2 < TOTAL_ATTRIBUTES; ++idx2) {
+ attribs[idx1] += _vm->getRandomNumber(10, 79) / 10;
+ }
+ }
+
+ // Check which classes are allowed based on the rolled attributes
+ checkClass(attribs, allowedClasses);
+
+ // Only exit if the attributes allow for at least one class
+ for (int idx = 0; idx < TOTAL_CLASSES; ++idx) {
+ if (allowedClasses[idx])
+ repeat = false;
+ }
+ } while (repeat);
+}
+
+void PartyDialog::checkClass(const uint attribs[TOTAL_ATTRIBUTES], bool allowedClasses[TOTAL_CLASSES]) {
+ allowedClasses[CLASS_KNIGHT] = attribs[MIGHT] >= 15;
+ allowedClasses[CLASS_PALADIN] = attribs[MIGHT] >= 13
+ && attribs[PERSONALITY] >= 13 && attribs[ENDURANCE] >= 13;
+ allowedClasses[CLASS_ARCHER] = attribs[INTELLECT] >= 13 && attribs[ACCURACY] >= 13;
+ allowedClasses[CLASS_CLERIC] = attribs[PERSONALITY] >= 13;
+ allowedClasses[CLASS_SORCERER] = attribs[INTELLECT] >= 13;
+ allowedClasses[CLASS_ROBBER] = attribs[LUCK] >= 13;
+ allowedClasses[CLASS_NINJA] = attribs[SPEED] >= 13 && attribs[ACCURACY] >= 13;
+ allowedClasses[CLASS_BARBARIAN] = attribs[ENDURANCE] >= 15;
+ allowedClasses[CLASS_DRUID] = attribs[INTELLECT] >= 15 && attribs[PERSONALITY] >= 15;
+ allowedClasses[CLASS_RANGER] = attribs[INTELLECT] >= 12 && attribs[PERSONALITY] >= 12
+ && attribs[ENDURANCE] >= 12 && attribs[SPEED] >= 12;
+}
+
+int PartyDialog::newCharDetails(const uint attribs[TOTAL_ATTRIBUTES],
+ bool allowedClasses[TOTAL_CLASSES], Race race, Sex sex, int classId,
+ int selectedClass, Common::String &msg) {
+ int foundClass = -1;
+ Common::String skillStr, classStr, raceSkillStr;
+
+ // If a selected class is provided, set the default skill for that class
+ if (classId != -1 && NEW_CHAR_SKILLS[classId] != -1) {
+ const char *skillP = SKILL_NAMES[NEW_CHAR_SKILLS[classId]];
+ skillStr = Common::String(skillP, skillP + NEW_CHAR_SKILLS_LEN[classId]);
+ }
+
+ // If a class is provided, set the class name
+ if (classId != -1) {
+ classStr = Common::String::format("\t062\v168%s", CLASS_NAMES[classId]);
+ }
+
+ // Set up default skill for the race, if any
+ if (NEW_CHAR_RACE_SKILLS[race] != -1) {
+ raceSkillStr = SKILL_NAMES[NEW_CHAR_RACE_SKILLS[race]];
+ }
+
+ // Set up color to use for each skill string to be displayed, based
+ // on whether each class is allowed or not for the given attributes
+ int classColors[TOTAL_CLASSES];
+ Common::fill(&classColors[0], &classColors[TOTAL_CLASSES], 0);
+ for (int classNum = CLASS_KNIGHT; classNum <= CLASS_RANGER; ++classNum) {
+ if (allowedClasses[classNum]) {
+ if (classId == -1 && (foundClass == -1 || foundClass < classNum))
+ foundClass = classNum;
+ classColors[classNum] = 4;
+ }
+ }
+
+ // Return stats details and character class
+ msg = Common::String::format(NEW_CHAR_STATS, RACE_NAMES[race], SEX_NAMES[sex],
+ attribs[MIGHT], attribs[INTELLECT], attribs[PERSONALITY],
+ attribs[ENDURANCE], attribs[SPEED], attribs[ACCURACY], attribs[LUCK],
+ classColors[CLASS_KNIGHT], classColors[CLASS_PALADIN],
+ classColors[CLASS_ARCHER], classColors[CLASS_CLERIC],
+ classColors[CLASS_SORCERER], classColors[CLASS_ROBBER],
+ classColors[CLASS_NINJA], classColors[CLASS_BARBARIAN],
+ classColors[CLASS_DRUID], classColors[CLASS_RANGER],
+ skillStr.c_str(), raceSkillStr.c_str(), classStr.c_str()
+ );
+ return classId == -1 ? foundClass : selectedClass;
+}
+
+void PartyDialog::printSelectionArrow(SpriteResource &icons, int selectedClass) {
+ Window &w = _vm->_screen->_windows[0];
+ icons.draw(w, 61, Common::Point(220, 19));
+ icons.draw(w, 63, Common::Point(220, selectedClass * 11 + 21));
+ w.update();
+}
+
+void PartyDialog::drawDice(SpriteResource &dice) {
+ EventsManager &events = *_vm->_events;
+ Window &w = _vm->_screen->_windows[32];
+ dice.draw(w, 7, Common::Point(12, 11));
+
+ for (int diceNum = 0; diceNum < 3; ++diceNum) {
+ _diceFrame[diceNum] = (_diceFrame[diceNum] + 1) % 7;
+ _dicePos[diceNum] += _diceInc[diceNum];
+
+ if (_dicePos[diceNum].x < 13) {
+ _dicePos[diceNum].x = 13;
+ _diceInc[diceNum].x *= -1;
+ } else if (_dicePos[diceNum].x >= 163) {
+ _dicePos[diceNum].x = 163;
+ _diceInc[diceNum].x *= -1;
+ }
+
+ if (_dicePos[diceNum].y < 12) {
+ _dicePos[diceNum].y = 12;
+ _diceInc[diceNum].y *= -1;
+ } else if (_dicePos[diceNum].y >= 93) {
+ _dicePos[diceNum].y = 93;
+ _diceInc[diceNum].y *= -1;
+ }
+
+ dice.draw(w, _diceFrame[diceNum], _dicePos[diceNum]);
+ }
+
+ w.update();
+
+ // Wait for keypress
+ events.wait(1, true);
+ checkEvents(_vm);
+}
+
+int PartyDialog::exchangeAttribute(int srcAttr) {
+ EventsManager &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ SpriteResource icons;
+ icons.load("create2.icn");
+
+ saveButtons();
+ addButton(Common::Rect(118, 58, 142, 78), Common::KEYCODE_ESCAPE, &icons);
+ addButton(Common::Rect(168, 19, 192, 39), Common::KEYCODE_m);
+ addButton(Common::Rect(168, 43, 192, 63), Common::KEYCODE_i);
+ addButton(Common::Rect(168, 67, 192, 87), Common::KEYCODE_p);
+ addButton(Common::Rect(168, 91, 192, 111), Common::KEYCODE_e);
+ addButton(Common::Rect(168, 115, 192, 135), Common::KEYCODE_s);
+ addButton(Common::Rect(168, 139, 192, 159), Common::KEYCODE_a);
+ addButton(Common::Rect(168, 163, 192, 183), Common::KEYCODE_l);
+
+ Window &w = screen._windows[26];
+ w.open();
+ w.writeString(Common::String::format(EXCHANGE_ATTR_WITH, STAT_NAMES[srcAttr - 1]));
+ icons.draw(w, 0, Common::Point(118, 58));
+ w.update();
+
+ int result = 0;
+ bool breakFlag = false;
+ while (!_vm->shouldQuit() && !breakFlag) {
+ // Wait for an action
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ } while (!_vm->shouldQuit() && !_buttonValue);
+
+ Attribute destAttr;
+ switch (_buttonValue) {
+ case Common::KEYCODE_m:
+ destAttr = MIGHT;
+ break;
+ case Common::KEYCODE_i:
+ destAttr = INTELLECT;
+ break;
+ case Common::KEYCODE_p:
+ destAttr = PERSONALITY;
+ break;
+ case Common::KEYCODE_e:
+ destAttr = ENDURANCE;
+ break;
+ case Common::KEYCODE_s:
+ destAttr = SPEED;
+ break;
+ case Common::KEYCODE_a:
+ destAttr = ACCURACY;
+ break;
+ case Common::KEYCODE_l:
+ destAttr = LUCK;
+ break;
+ case Common::KEYCODE_ESCAPE:
+ result = 0;
+ breakFlag = true;
+ continue;
+ default:
+ continue;
+ }
+
+ if ((srcAttr - 1) != destAttr) {
+ result = destAttr + 1;
+ break;
+ }
+ }
+
+ w.close();
+ _buttonValue = 0;
+ restoreButtons();
+
+ return result;
+}
+
+bool PartyDialog::saveCharacter(Character &c, int classId,
+ Race race, Sex sex, uint attribs[TOTAL_ATTRIBUTES]) {
+ if (classId == -1) {
+ ErrorScroll::show(_vm, SELECT_CLASS_BEFORE_SAVING);
+ return false;
+ }
+
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Window &w = screen._windows[6];
+ Common::String name;
+ int result;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ saveButtons();
+ w.writeString(NAME_FOR_NEW_CHARACTER);
+
+ result = Input::show(_vm, &w, name, 10, 200);
+ w.close();
+ restoreButtons();
+ if (!result)
+ return false;
+
+ // Save new character details
+ c.clear();
+ c._name = name;
+ c._savedMazeId = party._priorMazeId;
+ c._xeenSide = map._loadDarkSide;
+ c._sex = sex;
+ c._race = race;
+ c._class = (CharacterClass)classId;
+ c._level._permanent = isDarkCc ? 5 : 1;
+
+ c._might._permanent = attribs[MIGHT];
+ c._intellect._permanent = attribs[INTELLECT];
+ c._personality._permanent = attribs[PERSONALITY];
+ c._endurance._permanent = attribs[ENDURANCE];
+ c._speed._permanent = attribs[SPEED];
+ c._accuracy._permanent = attribs[ACCURACY];
+ c._luck._permanent = attribs[LUCK];
+
+ c._magicResistence._permanent = RACE_MAGIC_RESISTENCES[race];
+ c._fireResistence._permanent = RACE_FIRE_RESISTENCES[race];
+ c._electricityResistence._permanent = RACE_ELECTRIC_RESISTENCES[race];
+ c._coldResistence._permanent = RACE_COLD_RESISTENCES[race];
+ c._energyResistence._permanent = RACE_ENERGY_RESISTENCES[race];
+ c._poisonResistence._permanent = RACE_POISON_RESISTENCES[race];
+
+ c._birthYear = party._year - 18;
+ c._birthDay = party._day;
+ c._hasSpells = false;
+ c._currentSpell = -1;
+
+ // Set up any default spells for the character's class
+ for (int idx = 0; idx < 4; ++idx) {
+ if (NEW_CHARACTER_SPELLS[c._class][idx] != -1) {
+ c._hasSpells = true;
+ c._currentSpell = NEW_CHARACTER_SPELLS[c._class][idx];
+ c._spells[c._currentSpell] = 1;
+ }
+ }
+
+ int classSkill = NEW_CHAR_SKILLS[c._class];
+ if (classSkill != -1)
+ c._skills[classSkill] = 1;
+
+ int raceSkill = NEW_CHAR_RACE_SKILLS[c._race];
+ if (raceSkill != -1)
+ c._skills[raceSkill] = 1;
+
+ c._currentHp = c.getMaxHP();
+ c._currentSp = c.getMaxSP();
+ return true;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_party.h b/engines/xeen/dialogs_party.h
new file mode 100644
index 0000000000..17ec4fd300
--- /dev/null
+++ b/engines/xeen/dialogs_party.h
@@ -0,0 +1,111 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_PARTY_H
+#define XEEN_DIALOGS_PARTY_H
+
+#include "common/array.h"
+#include "xeen/dialogs.h"
+#include "xeen/interface.h"
+#include "xeen/screen.h"
+#include "xeen/sprites.h"
+
+namespace Xeen {
+
+class PartyDialog : public ButtonContainer, public PartyDrawer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _uiSprites;
+ DrawStruct _faceDrawStructs[4];
+ Common::String _partyDetails;
+ Common::Array<int> _charList;
+ int _diceFrame[3];
+ Common::Point _dicePos[3];
+ Common::Point _diceInc[3];
+
+ PartyDialog(XeenEngine *vm);
+
+ void execute();
+
+ void loadButtons();
+
+ void initDrawStructs();
+
+ void setupBackground();
+
+ /**
+ * Sets up the faces from the avaialble roster for display in the party dialog
+ */
+ void setupFaces(int firstDisplayChar, bool updateFlag);
+
+ void startingCharChanged(int firstDisplayChar);
+
+ void createChar();
+
+ int selectCharacter(bool isDelete, int firstDisplayChar);
+
+ /**
+ * Roll up some random values for the attributes, and return both them as
+ * well as a list of classes that the attributes meet the requirements for
+ */
+ void throwDice(uint attribs[TOTAL_ATTRIBUTES], bool allowedClasses[TOTAL_CLASSES]);
+
+ /**
+ * Set a list of flags for which classes the passed attribute set meet the
+ * minimum requirements of
+ */
+ void checkClass(const uint attribs[TOTAL_ATTRIBUTES], bool allowedClasses[TOTAL_CLASSES]);
+
+ /**
+ * Return details of the generated character
+ */
+ int newCharDetails(const uint attribs[TOTAL_ATTRIBUTES],
+ bool allowedClasses[TOTAL_CLASSES], Race race, Sex sex, int classId,
+ int selectedClass, Common::String &msg);
+
+ /**
+ * Print the selection arrow to indicate the selected class
+ */
+ void printSelectionArrow(SpriteResource &icons, int selectedClass);
+
+ /**
+ * Print the dice animation
+ */
+ void drawDice(SpriteResource &dice);
+
+ /**
+ * Exchanging two attributes for the character being rolled
+ */
+ int exchangeAttribute(int srcAttr);
+
+ /**
+ * Saves the rolled character into the roster
+ */
+ bool saveCharacter(Character &c, int classId, Race race,
+ Sex sex, uint attribs[TOTAL_ATTRIBUTES]);
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_PARTY_H */
diff --git a/engines/xeen/dialogs_query.cpp b/engines/xeen/dialogs_query.cpp
new file mode 100644
index 0000000000..abaddafe69
--- /dev/null
+++ b/engines/xeen/dialogs_query.cpp
@@ -0,0 +1,160 @@
+/* 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 "xeen/dialogs_query.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+bool Confirm::show(XeenEngine *vm, const Common::String &msg, int mode) {
+ Confirm *dlg = new Confirm(vm);
+ bool result = dlg->execute(msg, mode);
+ delete dlg;
+
+ return result;
+}
+
+bool Confirm::execute(const Common::String &msg, int mode) {
+ Screen &screen = *_vm->_screen;
+ EventsManager &events = *_vm->_events;
+ SpriteResource confirmSprites;
+ bool result = false;
+
+ confirmSprites.load("confirm.icn");
+ addButton(Common::Rect(129, 112, 153, 122), Common::KEYCODE_y, &confirmSprites);
+ addButton(Common::Rect(185, 112, 209, 122), Common::KEYCODE_n, &confirmSprites);
+
+ Window &w = screen._windows[mode ? 22 : 21];
+ w.open();
+
+ if (!mode) {
+ confirmSprites.draw(w, 0, Common::Point(129, 112));
+ confirmSprites.draw(w, 2, Common::Point(185, 112));
+ _buttons[0]._bounds.moveTo(129, 112);
+ _buttons[1]._bounds.moveTo(185, 112);
+ } else {
+ if (mode & 0x80) {
+ clearButtons();
+ } else {
+ confirmSprites.draw(w, 0, Common::Point(120, 133));
+ confirmSprites.draw(w, 2, Common::Point(176, 133));
+ _buttons[0]._bounds.moveTo(120, 133);
+ _buttons[1]._bounds.moveTo(176, 133);
+ }
+ }
+
+ w.writeString(msg);
+ w.update();
+
+ events.clearEvents();
+ while (!_vm->shouldQuit()) {
+ while (!_vm->shouldQuit() && !_buttonValue) {
+ events.pollEvents();
+ checkEvents(_vm);
+ }
+
+ if ((mode & 0x80) || _buttonValue == Common::KEYCODE_ESCAPE
+ || _buttonValue == Common::KEYCODE_n)
+ break;
+
+ if (_buttonValue == Common::KEYCODE_y) {
+ result = true;
+ break;
+ }
+ }
+
+ w.close();
+ return result;
+}
+
+/*------------------------------------------------------------------------*/
+
+bool YesNo::show(XeenEngine *vm, bool type, bool townFlag) {
+ YesNo *dlg = new YesNo(vm);
+ bool result = dlg->execute(type, townFlag);
+ delete dlg;
+
+ return result;
+}
+
+bool YesNo::execute(bool type, bool townFlag) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+ Town &town = *_vm->_town;
+ SpriteResource confirmSprites;
+ //int numFrames;
+ bool result = false;
+
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = oldMode == MODE_7 ? MODE_8 : MODE_7;
+
+ if (!type) {
+ confirmSprites.load("confirm.icn");
+ res._globalSprites.draw(screen, 7, Common::Point(232, 74));
+ confirmSprites.draw(screen, 0, Common::Point(235, 75));
+ confirmSprites.draw(screen, 2, Common::Point(260, 75));
+ screen._windows[34].update();
+
+ addButton(Common::Rect(235, 75, 259, 95), Common::KEYCODE_y, &confirmSprites);
+ addButton(Common::Rect(260, 75, 284, 95), Common::KEYCODE_n, &confirmSprites);
+
+ intf._face1State = map._headData[party._mazePosition.y][party._mazePosition.x]._left;
+ intf._face2State = map._headData[party._mazePosition.y][party._mazePosition.x]._right;
+ }
+
+ while (!_vm->shouldQuit()) {
+ events.updateGameCounter();
+
+ if (town.isActive()) {
+ town.drawTownAnim(townFlag);
+ //numFrames = 3;
+ } else {
+ intf.draw3d(true);
+ //numFrames = 1;
+ }
+
+ events.wait(3, true);
+ checkEvents(_vm);
+ if (!_buttonValue)
+ continue;
+
+ if (type || _buttonValue == Common::KEYCODE_y) {
+ result = true;
+ break;
+ } else if (_buttonValue == Common::KEYCODE_n || _buttonValue == Common::KEYCODE_ESCAPE)
+ break;
+ }
+
+ intf._face1State = intf._face2State = 2;
+ _vm->_mode = oldMode;
+
+ if (!type)
+ intf.mainIconsPrint();
+
+ return result;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_query.h b/engines/xeen/dialogs_query.h
new file mode 100644
index 0000000000..96ae488b97
--- /dev/null
+++ b/engines/xeen/dialogs_query.h
@@ -0,0 +1,54 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_QUERY_H
+#define XEEN_DIALOGS_QUERY_H
+
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class Confirm : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ Confirm(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ bool execute(const Common::String &msg, int mode);
+public:
+ static bool show(XeenEngine *vm, const Common::String &msg, int mode = 0);
+};
+
+class YesNo : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ YesNo(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ bool execute(bool type, bool townFlag);
+public:
+ static bool show(XeenEngine *vm, bool type, bool townFlag = false);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_QUERY_H */
diff --git a/engines/xeen/dialogs_quests.cpp b/engines/xeen/dialogs_quests.cpp
new file mode 100644
index 0000000000..284e15781b
--- /dev/null
+++ b/engines/xeen/dialogs_quests.cpp
@@ -0,0 +1,254 @@
+/* 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 "common/scummsys.h"
+#include "xeen/dialogs_quests.h"
+#include "xeen/events.h"
+#include "xeen/party.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+#define MAX_DIALOG_LINES 128
+
+void Quests::show(XeenEngine *vm) {
+ Quests *dlg = new Quests(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void Quests::execute() {
+ EventsManager &events = *_vm->_events;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Mode oldMode = _vm->_mode;
+ int count = 0;
+ bool headerShown = false;
+ int topRow = 0;
+
+ addButtons();
+ loadQuestNotes();
+
+ enum { QUEST_ITEMS, CURRENT_QUESTS, AUTO_NOTES } mode = QUEST_ITEMS;
+ bool windowFlag;
+ if (screen._windows[29]._enabled) {
+ windowFlag = false;
+ } else {
+ screen._windows[29].open();
+ screen._windows[30].open();
+ windowFlag = true;
+ }
+
+ screen._windows[29].writeString(QUESTS_DIALOG_TEXT);
+ drawButtons(&screen);
+
+ while (!_vm->shouldQuit()) {
+ Common::String lines[MAX_DIALOG_LINES];
+
+ switch (mode) {
+ case QUEST_ITEMS:
+ for (int idx = 0; idx < TOTAL_QUEST_ITEMS; ++idx)
+ lines[idx] = "\b \b*";
+
+ count = 0;
+ headerShown = false;
+ for (int idx = 0; idx < TOTAL_QUEST_ITEMS; ++idx) {
+ if (party._questItems[idx]) {
+ if (!count && !headerShown && idx < 35) {
+ lines[count++] = CLOUDS_OF_XEEN_LINE;
+ }
+ if (idx >= 35 && !headerShown) {
+ lines[count++] = DARKSIDE_OF_XEEN_LINE;
+ headerShown = true;
+ }
+
+ switch (idx) {
+ case 17:
+ case 26:
+ case 79:
+ case 80:
+ case 81:
+ case 82:
+ case 83:
+ case 84:
+ lines[count++] = Common::String::format("%d %s%c",
+ party._questItems[idx], QUEST_ITEM_NAMES[idx],
+ party._questItems[idx] == 1 ? ' ' : 's');
+ break;
+ default:
+ lines[count++] = QUEST_ITEM_NAMES[idx];
+ break;
+ }
+ }
+ }
+
+ if (count == 0) {
+ screen._windows[30].writeString(NO_QUEST_ITEMS);
+ } else {
+ screen._windows[30].writeString(Common::String::format(QUEST_ITEMS_DATA,
+ lines[topRow].c_str(), lines[topRow + 1].c_str(),
+ lines[topRow + 2].c_str(), lines[topRow + 3].c_str(),
+ lines[topRow + 4].c_str(), lines[topRow + 5].c_str(),
+ lines[topRow + 6].c_str(), lines[topRow + 7].c_str(),
+ lines[topRow + 8].c_str()
+ ));
+ }
+ break;
+
+ case CURRENT_QUESTS:
+ for (int idx = 0; idx < TOTAL_QUEST_ITEMS; ++idx)
+ lines[idx] = "";
+
+ count = 0;
+ headerShown = false;
+ for (int idx = 0; idx < TOTAL_QUEST_FLAGS; ++idx) {
+ if (party._quests[idx]) {
+ if (!count && !headerShown && idx < 29) {
+ lines[count++] = CLOUDS_OF_XEEN_LINE;
+ }
+ if (idx > 28 && !headerShown) {
+ lines[count++] = DARKSIDE_OF_XEEN_LINE;
+ headerShown = true;
+ }
+
+ lines[count++] = _questNotes[idx];
+ }
+ }
+
+ if (count == 0)
+ lines[1] = NO_CURRENT_QUESTS;
+
+ screen._windows[30].writeString(Common::String::format(CURRENT_QUESTS_DATA,
+ lines[topRow].c_str(), lines[topRow + 1].c_str(), lines[topRow + 2].c_str()));
+ break;
+
+ case AUTO_NOTES:
+ for (int idx = 0; idx < MAX_DIALOG_LINES; ++idx)
+ lines[idx] = "";
+
+ count = 0;
+ headerShown = false;
+ for (int idx = 0; idx < MAX_DIALOG_LINES; ++idx) {
+ if (party._worldFlags[idx]) {
+ if (!count && !headerShown && idx < 72) {
+ lines[count++] = CLOUDS_OF_XEEN_LINE;
+ }
+ if (idx >= 72 && !headerShown) {
+ lines[count++] = DARKSIDE_OF_XEEN_LINE;
+ headerShown = true;
+ }
+
+ lines[count++] = _questNotes[idx + 56];
+ }
+ }
+
+ if (count == 0)
+ lines[1] = NO_AUTO_NOTES;
+
+ screen._windows[30].writeString(Common::String::format(AUTO_NOTES_DATA,
+ lines[topRow].c_str(), lines[topRow + 1].c_str(),
+ lines[topRow + 2].c_str(), lines[topRow + 3].c_str(),
+ lines[topRow + 4].c_str(), lines[topRow + 5].c_str(),
+ lines[topRow + 6].c_str(), lines[topRow + 7].c_str(),
+ lines[topRow + 8].c_str()
+ ));
+ break;
+ }
+
+ screen._windows[30].writeString("\v000\t000");
+ screen._windows[24].update();
+
+ // Key handling
+ _buttonValue = 0;
+ while (!_vm->shouldQuit() && !_buttonValue) {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ }
+
+ if (_buttonValue == Common::KEYCODE_ESCAPE)
+ break;
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_a:
+ mode = AUTO_NOTES;
+ topRow = 0;
+ break;
+ case Common::KEYCODE_i:
+ mode = QUEST_ITEMS;
+ topRow = 0;
+ break;
+ case Common::KEYCODE_q:
+ mode = CURRENT_QUESTS;
+ topRow = 0;
+ break;
+ case Common::KEYCODE_HOME:
+ topRow = 0;
+ break;
+ case Common::KEYCODE_END:
+ topRow = count - 1;
+ break;
+ case Common::KEYCODE_PAGEUP:
+ topRow = MAX(topRow - 3, 0);
+ break;
+ case Common::KEYCODE_PAGEDOWN:
+ topRow = CLIP(topRow + 3, 0, count - 1);
+ break;
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ topRow = MAX(topRow - 1, 0);
+ break;
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ topRow = CLIP(topRow + 1, 0, count - 1);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (windowFlag) {
+ screen._windows[30].close();
+ screen._windows[29].close();
+ }
+ _vm->_mode = oldMode;
+}
+
+void Quests::addButtons() {
+ _iconSprites.load("quest.icn");
+
+
+ addButton(Common::Rect(12, 109, 36, 129), Common::KEYCODE_i, &_iconSprites);
+ addButton(Common::Rect(80, 109, 104, 129), Common::KEYCODE_q, &_iconSprites);
+ addButton(Common::Rect(148, 109, 172, 129), Common::KEYCODE_a, &_iconSprites);
+ addButton(Common::Rect(216, 109, 240, 129), Common::KEYCODE_UP, &_iconSprites);
+ addButton(Common::Rect(250, 109, 274, 129), Common::KEYCODE_DOWN, &_iconSprites);
+ addButton(Common::Rect(284, 109, 308, 129), Common::KEYCODE_ESCAPE, &_iconSprites);
+}
+
+void Quests::loadQuestNotes() {
+ File f("qnotes.bin", *_vm->_files->_sideArchives[_vm->getGameID() == GType_Clouds ? 0 : 1]);
+ while (f.pos() < f.size())
+ _questNotes.push_back(f.readString());
+ f.close();
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_quests.h b/engines/xeen/dialogs_quests.h
new file mode 100644
index 0000000000..234accded6
--- /dev/null
+++ b/engines/xeen/dialogs_quests.h
@@ -0,0 +1,50 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_QUESTS_H
+#define XEEN_DIALOGS_QUESTS_H
+
+#include "common/str-array.h"
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class Quests : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+ Common::StringArray _questNotes;
+
+ Quests(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+
+ void addButtons();
+
+ void loadQuestNotes();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_QUESTS_H */
diff --git a/engines/xeen/dialogs_quick_ref.cpp b/engines/xeen/dialogs_quick_ref.cpp
new file mode 100644
index 0000000000..e9ffbd482c
--- /dev/null
+++ b/engines/xeen/dialogs_quick_ref.cpp
@@ -0,0 +1,88 @@
+/* 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 "xeen/dialogs_quick_ref.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+void QuickReferenceDialog::show(XeenEngine *vm) {
+ QuickReferenceDialog *dlg = new QuickReferenceDialog(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void QuickReferenceDialog::execute() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Common::String lines[8];
+
+ events.setCursor(0);
+
+ for (uint idx = 0; idx < (combat._globalCombat == 2 ? combat._combatParty.size() :
+ party._activeParty.size()); ++idx) {
+ Character &c = combat._globalCombat == 2 ? *combat._combatParty[idx] :
+ party._activeParty[idx];
+ Condition condition = c.worstCondition();
+ lines[idx] = Common::String::format(QUICK_REF_LINE,
+ idx * 10 + 24, idx + 1, c._name.c_str(),
+ CLASS_NAMES[c._class][0], CLASS_NAMES[c._class][1], CLASS_NAMES[c._class][2],
+ c.statColor(c.getCurrentLevel(), c._level._permanent), c._level._permanent,
+ c.statColor(c._currentHp, c.getMaxHP()), c._currentHp,
+ c.statColor(c._currentSp, c.getMaxSP()), c._currentSp,
+ c.statColor(c.getArmorClass(), c.getArmorClass(true)), c.getArmorClass(),
+ CONDITION_COLORS[condition],
+ CONDITION_NAMES[condition][0], CONDITION_NAMES[condition][1],
+ CONDITION_NAMES[condition][2], CONDITION_NAMES[condition][3]
+ );
+ }
+
+ int food = (party._food / party._activeParty.size()) / 3;
+ Common::String msg = Common::String::format(QUICK_REFERENCE,
+ 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(),
+ party._gold, party._gems,
+ food, food == 1 ? "" : "s"
+ );
+
+ Window &w = screen._windows[24];
+ bool windowOpen = w._enabled;
+ if (!windowOpen)
+ w.open();
+ w.writeString(msg);
+ w.update();
+
+ // Wait for a key/mouse press
+ events.clearEvents();
+ while (!_vm->shouldQuit() && !events.isKeyMousePressed())
+ events.pollEventsAndWait();
+ events.clearEvents();
+
+ if (!windowOpen)
+ w.close();
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_quick_ref.h b/engines/xeen/dialogs_quick_ref.h
new file mode 100644
index 0000000000..0c1b8e3f91
--- /dev/null
+++ b/engines/xeen/dialogs_quick_ref.h
@@ -0,0 +1,43 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_QUICK_REF_H
+#define XEEN_DIALOGS_QUICK_REF_H
+
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class QuickReferenceDialog : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ QuickReferenceDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_QUICK_REF_H */
diff --git a/engines/xeen/dialogs_spells.cpp b/engines/xeen/dialogs_spells.cpp
new file mode 100644
index 0000000000..e7bbbf5cbf
--- /dev/null
+++ b/engines/xeen/dialogs_spells.cpp
@@ -0,0 +1,1030 @@
+/* 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 "xeen/dialogs_spells.h"
+#include "xeen/dialogs_input.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/resources.h"
+#include "xeen/spells.h"
+#include "xeen/sprites.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+Character *SpellsDialog::show(XeenEngine *vm, ButtonContainer *priorDialog,
+ Character *c, int isCasting) {
+ SpellsDialog *dlg = new SpellsDialog(vm);
+ Character *result = dlg->execute(priorDialog, c, isCasting);
+ delete dlg;
+
+ return result;
+}
+
+Character *SpellsDialog::execute(ButtonContainer *priorDialog, Character *c, int isCasting) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Spells &spells = *_vm->_spells;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ loadButtons();
+
+ int castingCopy = isCasting;
+ isCasting &= 0x7f;
+ int selection = -1;
+ int topIndex = 0;
+ int newSelection;
+ screen._windows[25].open();
+
+ do {
+ if (!isCasting) {
+ if (!c->guildMember()) {
+ sound.playSample(nullptr, 0);
+ intf._overallFrame = 5;
+ File f(isDarkCc ? "skull1.voc" : "guild11.voc");
+ sound.playSample(&f, 1);
+ break;
+ }
+
+ Common::String title = Common::String::format(BUY_SPELLS, c->_name.c_str());
+ Common::String msg = Common::String::format(GUILD_OPTIONS,
+ title.c_str(), XeenEngine::printMil(party._gold).c_str());
+ screen._windows[10].writeString(msg);
+
+ warning("TODO: Sprite draw using previously used button sprites");
+ }
+
+ _spells.clear();
+ const char *errorMsg = setSpellText(c, castingCopy);
+ screen._windows[25].writeString(Common::String::format(SPELLS_FOR,
+ errorMsg == nullptr ? SPELL_LINES_0_TO_9 : "",
+ c->_name.c_str()));
+
+ // Setup and write out spell list
+ const char *names[10];
+ int colors[10];
+ Common::String emptyStr = "";
+ Common::fill(&names[0], &names[10], emptyStr.c_str());
+ Common::fill(&colors[0], &colors[10], 9);
+
+ for (int idx = 0; idx < 10; ++idx) {
+ if ((topIndex + idx) < (int)_spells.size()) {
+ names[idx] = _spells[topIndex + idx]._name.c_str();
+ colors[idx] = _spells[topIndex + idx]._color;
+ }
+ }
+
+ if (selection >= topIndex && selection < (topIndex + 10))
+ colors[selection - topIndex] = 15;
+ if (_spells.size() == 0)
+ names[0] = errorMsg;
+
+ screen._windows[37].writeString(Common::String::format(SPELLS_DIALOG_SPELLS,
+ colors[0], names[0], colors[1], names[1], colors[2], names[2],
+ colors[3], names[3], colors[4], names[4], colors[5], names[5],
+ colors[6], names[6], colors[7], names[7], colors[8], names[8],
+ colors[9], names[9],
+ isCasting ? SPELL_PTS : GOLD,
+ isCasting ? c->_currentSp : party._gold
+ ));
+
+ _scrollSprites.draw(screen, 4, Common::Point(39, 26));
+ _scrollSprites.draw(screen, 0, Common::Point(187, 26));
+ _scrollSprites.draw(screen, 2, Common::Point(187, 111));
+ if (isCasting)
+ _scrollSprites.draw(screen._windows[25], 5, Common::Point(132, 123));
+
+ screen._windows[25].update();
+
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ } while (!_vm->shouldQuit() && !_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ if (_vm->_mode != MODE_COMBAT) {
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ c = &party._activeParty[_buttonValue];
+ spells._lastCaster = _buttonValue;
+ intf.highlightChar(_buttonValue);
+
+ if (_vm->_mode == MODE_17) {
+ screen._windows[10].writeString(Common::String::format(GUILD_OPTIONS,
+ XeenEngine::printMil(party._gold).c_str(), GUILD_TEXT, c->_name.c_str()));
+ } else {
+ int category;
+ switch (c->_class) {
+ case CLASS_ARCHER:
+ case CLASS_SORCERER:
+ category = 1;
+ break;
+ case CLASS_DRUID:
+ case CLASS_RANGER:
+ category = 2;
+ break;
+ default:
+ category = 0;
+ break;
+ }
+
+ int spellIndex = (c->_currentSpell == -1) ? 39 : c->_currentSpell;
+ int spellId = SPELLS_ALLOWED[category][spellIndex];
+ screen._windows[10].writeString(Common::String::format(CAST_SPELL_DETAILS,
+ c->_name.c_str(), spells._spellNames[spellId].c_str(),
+ spells.calcSpellPoints(spellId, c->getCurrentLevel()),
+ SPELL_GEM_COST[spellId], c->_currentSp));
+ }
+
+ if (priorDialog != nullptr)
+ priorDialog->drawButtons(&screen);
+ screen._windows[10].update();
+ }
+ }
+ break;
+
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ case Common::KEYCODE_s:
+ if (selection != -1)
+ _buttonValue = Common::KEYCODE_ESCAPE;
+ break;
+
+ case Common::KEYCODE_ESCAPE:
+ selection = -1;
+ _buttonValue = Common::KEYCODE_ESCAPE;
+ break;
+
+ case Common::KEYCODE_0:
+ case Common::KEYCODE_1:
+ case Common::KEYCODE_2:
+ case Common::KEYCODE_3:
+ case Common::KEYCODE_4:
+ case Common::KEYCODE_5:
+ case Common::KEYCODE_6:
+ case Common::KEYCODE_7:
+ case Common::KEYCODE_8:
+ case Common::KEYCODE_9:
+ newSelection = topIndex + ((_buttonValue == Common::KEYCODE_0) ? 9 :
+ (_buttonValue - Common::KEYCODE_1));
+
+ if (newSelection < (int)_spells.size()) {
+ int expenseFactor = 0;
+ int category = 0;
+
+ switch (c->_class) {
+ case CLASS_PALADIN:
+ expenseFactor = 1;
+ category = 0;
+ break;
+ case CLASS_ARCHER:
+ expenseFactor = 1;
+ category = 1;
+ break;
+ case CLASS_CLERIC:
+ category = 0;
+ break;
+ case CLASS_SORCERER:
+ category = 1;
+ break;
+ case CLASS_DRUID:
+ category = 2;
+ break;
+ case CLASS_RANGER:
+ expenseFactor = 1;
+ category = 2;
+ break;
+ default:
+ break;
+ }
+
+ int spellIndex = _spells[newSelection]._spellIndex;
+ int spellId = SPELLS_ALLOWED[category][spellIndex];
+ int spellCost = spells.calcSpellCost(spellId, expenseFactor);
+
+ if (isCasting) {
+ selection = newSelection;
+ } else {
+ Common::String spellName = _spells[newSelection]._name;
+ Common::String msg = (castingCopy & 0x80) ?
+ Common::String::format(SPELLS_PRESS_A_KEY, spellName.c_str()) :
+ Common::String::format(SPELLS_PURCHASE, spellName.c_str(), spellCost);
+
+ if (Confirm::show(_vm, msg, castingCopy + 1)) {
+ if (party.subtract(0, spellCost, 0, WT_FREEZE_WAIT)) {
+ ++c->_spells[spellIndex];
+ sound.playSample(nullptr, 0);
+ intf._overallFrame = 0;
+ File f(isDarkCc ? "guild12.voc" : "parrot2.voc");
+ sound.playSample(&f, 1);
+ } else {
+ sound.playFX(21);
+ }
+ }
+ }
+ }
+ break;
+
+ case Common::KEYCODE_PAGEUP:
+ case Common::KEYCODE_KP9:
+ topIndex = MAX((int)topIndex - 10, 0);
+ break;
+
+ case Common::KEYCODE_PAGEDOWN:
+ case Common::KEYCODE_KP3:
+ topIndex = MIN(topIndex + 10, (((int)_spells.size() - 1) / 10) * 10);
+ break;
+
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ if (topIndex > 0)
+ --topIndex;
+ break;
+
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ if (topIndex < ((int)_spells.size() - 10))
+ ++topIndex;
+ break;
+ }
+ } while (!_vm->shouldQuit() && _buttonValue != Common::KEYCODE_ESCAPE);
+
+ screen._windows[25].close();
+
+ if (_vm->shouldQuit())
+ selection = -1;
+ if (isCasting && selection != -1)
+ c->_currentSpell = _spells[selection]._spellIndex;
+
+ return c;
+}
+
+void SpellsDialog::loadButtons() {
+ _iconSprites.load("main.icn");
+ _scrollSprites.load("scroll.icn");
+ addButton(Common::Rect(187, 26, 198, 36), Common::KEYCODE_UP, &_scrollSprites);
+ addButton(Common::Rect(187, 111, 198, 121), Common::KEYCODE_DOWN, &_scrollSprites);
+ addButton(Common::Rect(40, 28, 187, 36), Common::KEYCODE_1);
+ addButton(Common::Rect(40, 37, 187, 45), Common::KEYCODE_2);
+ addButton(Common::Rect(40, 46, 187, 54), Common::KEYCODE_3);
+ addButton(Common::Rect(40, 55, 187, 63), Common::KEYCODE_4);
+ addButton(Common::Rect(40, 64, 187, 72), Common::KEYCODE_5);
+ addButton(Common::Rect(40, 73, 187, 81), Common::KEYCODE_6);
+ addButton(Common::Rect(40, 82, 187, 90), Common::KEYCODE_7);
+ addButton(Common::Rect(40, 91, 187, 99), Common::KEYCODE_8);
+ addButton(Common::Rect(40, 100, 187, 108), Common::KEYCODE_9);
+ addButton(Common::Rect(40, 109, 187, 117), Common::KEYCODE_0);
+ addButton(Common::Rect(174, 123, 198, 133), Common::KEYCODE_ESCAPE);
+ addButton(Common::Rect(187, 35, 198, 73), Common::KEYCODE_PAGEUP);
+ addButton(Common::Rect(187, 74, 198, 112), Common::KEYCODE_PAGEDOWN);
+ addButton(Common::Rect(132, 123, 168, 133), Common::KEYCODE_s);
+ addPartyButtons(_vm);
+}
+
+const char *SpellsDialog::setSpellText(Character *c, int isCasting) {
+ Party &party = *_vm->_party;
+ Spells &spells = *_vm->_spells;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ int expenseFactor = 0;
+ int currLevel = c->getCurrentLevel();
+ int category;
+
+ if ((isCasting & 0x7f) == 0) {
+ switch (c->_class) {
+ case CLASS_PALADIN:
+ expenseFactor = 1;
+ category = 0;
+ break;
+ case CLASS_ARCHER:
+ expenseFactor = 1;
+ category = 1;
+ break;
+ case CLASS_CLERIC:
+ category = 0;
+ break;
+ case CLASS_SORCERER:
+ category = 1;
+ break;
+ case CLASS_DRUID:
+ category = 2;
+ break;
+ case CLASS_RANGER:
+ expenseFactor = 1;
+ category = 2;
+ break;
+ default:
+ category = -1;
+ break;
+ }
+
+ if (category != -1) {
+ if (party._mazeId == 49 || party._mazeId == 37) {
+ for (uint spellId = 0; spellId < 76; ++spellId) {
+ int idx = 0;
+ while (idx < MAX_SPELLS_PER_CLASS && SPELLS_ALLOWED[category][idx] == spellId)
+ ++idx;
+
+ // Handling if the spell is appropriate for the character's class
+ if (idx < MAX_SPELLS_PER_CLASS) {
+ if (!c->_spells[idx] || (isCasting & 0x80)) {
+ int cost = spells.calcSpellCost(SPELLS_ALLOWED[category][idx], expenseFactor);
+ _spells.push_back(SpellEntry(Common::String::format("\x3l%s\x3r\x9""000%u",
+ spells._spellNames[SPELLS_ALLOWED[category][idx]].c_str(), cost),
+ idx, spellId));
+ }
+ }
+ }
+ } else if (isDarkCc) {
+ int groupIndex = (party._mazeId - 29) / 2;
+ for (int spellId = DARK_SPELL_RANGES[groupIndex][0];
+ spellId < DARK_SPELL_RANGES[groupIndex][1]; ++spellId) {
+ int idx = 0;
+ while (idx < 40 && SPELLS_ALLOWED[category][idx] ==
+ DARK_SPELL_OFFSETS[category][spellId]);
+
+ if (idx < 40) {
+ if (!c->_spells[idx] || (isCasting & 0x80)) {
+ int cost = spells.calcSpellCost(SPELLS_ALLOWED[category][idx], expenseFactor);
+ _spells.push_back(SpellEntry(Common::String::format("\x3l%s\x3r\x9""000%u",
+ spells._spellNames[SPELLS_ALLOWED[category][idx]].c_str(), cost),
+ idx, spellId));
+ }
+ }
+ }
+ } else {
+ for (int spellId = 0; spellId < 20; ++spellId) {
+ int idx = 0;
+ while (CLOUDS_SPELL_OFFSETS[party._mazeId - 29][spellId] !=
+ (int)SPELLS_ALLOWED[category][idx] && idx < 40) ;
+
+ if (idx < 40) {
+ if (!c->_spells[idx] || (isCasting & 0x80)) {
+ int cost = spells.calcSpellCost(SPELLS_ALLOWED[category][idx], expenseFactor);
+ _spells.push_back(SpellEntry(Common::String::format("\x3l%s\x3r\x9""000%u",
+ spells._spellNames[SPELLS_ALLOWED[category][idx]].c_str(), cost),
+ idx, spellId));
+ }
+ }
+ }
+ }
+ }
+
+ if (c->getMaxSP() == 0)
+ return NOT_A_SPELL_CASTER;
+
+ } else if ((isCasting & 0x7f) == 1) {
+ switch (c->_class) {
+ case CLASS_ARCHER:
+ case CLASS_SORCERER:
+ category = 1;
+ break;
+ case CLASS_DRUID:
+ case CLASS_RANGER:
+ category = 2;
+ break;
+ case CLASS_PALADIN:
+ case CLASS_CLERIC:
+ default:
+ category = 0;
+ break;
+ }
+
+ if (c->getMaxSP() == 0) {
+ return NOT_A_SPELL_CASTER;
+ } else {
+ for (int spellIndex = 0; spellIndex < (MAX_SPELLS_PER_CLASS - 1); ++spellIndex) {
+ if (c->_spells[spellIndex]) {
+ int spellId = SPELLS_ALLOWED[category][spellIndex];
+ int gemCost = SPELL_GEM_COST[spellId];
+ int spCost = spells.calcSpellPoints(spellId, currLevel);
+
+ Common::String msg = Common::String::format("\x3l%s\x3r\x9""000%u/%u",
+ spells._spellNames[spellId].c_str(), spCost, gemCost);
+ _spells.push_back(SpellEntry(msg, spellIndex, spellId));
+ }
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+/*------------------------------------------------------------------------*/
+
+int CastSpell::show(XeenEngine *vm) {
+ Combat &combat = *vm->_combat;
+ Interface &intf = *vm->_interface;
+ Party &party = *vm->_party;
+ Spells &spells = *vm->_spells;
+ int charNum;
+
+ // Get which character is doing the casting
+ if (vm->_mode == MODE_COMBAT) {
+ charNum = combat._whosTurn;
+ } else if (spells._lastCaster >= 0 && spells._lastCaster < (int)party._activeParty.size()) {
+ charNum = spells._lastCaster;
+ } else {
+ for (charNum = (int)party._activeParty.size() - 1; charNum >= 0; --charNum) {
+ if (party._activeParty[charNum]._hasSpells) {
+ spells._lastCaster = charNum;
+ break;
+ }
+ }
+ }
+
+ Character *c = &party._activeParty[charNum];
+ intf.highlightChar(charNum);
+
+ CastSpell *dlg = new CastSpell(vm);
+ int spellId = dlg->execute(c);
+ delete dlg;
+
+ return spellId;
+}
+
+int CastSpell::show(XeenEngine *vm, Character *&c) {
+ CastSpell *dlg = new CastSpell(vm);
+ int spellId = dlg->execute(c);
+ delete dlg;
+
+ return spellId;
+}
+
+int CastSpell::execute(Character *&c) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Spells &spells = *_vm->_spells;
+ Window &w = screen._windows[10];
+
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_3;
+
+ w.open();
+ loadButtons();
+
+ int spellId = -1;
+ bool redrawFlag = true;
+ do {
+ if (redrawFlag) {
+ int category = c->getClassCategory();
+ int spellIndex = c->_currentSpell != -1 ? c->_currentSpell : 39;
+ spellId = SPELLS_ALLOWED[category][spellIndex];
+ int gemCost = SPELL_GEM_COST[spellId];
+ int spCost = spells.calcSpellPoints(spellId, c->getCurrentLevel());
+
+ w.writeString(Common::String::format(CAST_SPELL_DETAILS,
+ c->_name.c_str(), spells._spellNames[spellId].c_str(),
+ spCost, gemCost, c->_currentSp));
+ drawButtons(&screen);
+ w.update();
+
+ redrawFlag = false;
+ }
+
+ events.updateGameCounter();
+ intf.draw3d(true);
+
+ // Wait for event or time expiry
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ } while (!_vm->shouldQuit() && events.timeElapsed() < 1 && !_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ // Only allow changing character if the party is not in combat
+ if (oldMode != MODE_COMBAT) {
+ _vm->_mode = oldMode;
+ _buttonValue -= Common::KEYCODE_F1;
+
+ if (_buttonValue < (int)party._activeParty.size()) {
+ c = &party._activeParty[_buttonValue];
+ intf.highlightChar(_buttonValue);
+ redrawFlag = true;
+ break;
+ }
+ }
+ break;
+
+ case Common::KEYCODE_ESCAPE:
+ spellId = -1;
+ break;
+
+ case Common::KEYCODE_c:
+ // Cast spell - return the selected spell Id to be cast
+ if (c->_currentSpell != -1 && !c->noActions())
+ _buttonValue = Common::KEYCODE_ESCAPE;
+ break;
+
+ case Common::KEYCODE_n:
+ // Select new spell
+ _vm->_mode = oldMode;
+ c = SpellsDialog::show(_vm, this, c, 1);
+ redrawFlag = true;
+ break;
+
+ default:
+ break;
+ }
+ } while (!_vm->shouldQuit() && _buttonValue != Common::KEYCODE_ESCAPE);
+
+ w.close();
+ intf.unhighlightChar();
+
+ if (_vm->shouldQuit())
+ spellId = -1;
+
+ _vm->_mode = oldMode;
+ return spellId;
+}
+
+void CastSpell::loadButtons() {
+ _iconSprites.load("cast.icn");
+ addButton(Common::Rect(234, 108, 259, 128), Common::KEYCODE_c, &_iconSprites);
+ addButton(Common::Rect(261, 108, 285, 128), Common::KEYCODE_n, &_iconSprites);
+ addButton(Common::Rect(288, 108, 312, 128), Common::KEYCODE_ESCAPE, &_iconSprites);
+ addPartyButtons(_vm);
+}
+
+/*------------------------------------------------------------------------*/
+
+Character *SpellOnWho::show(XeenEngine *vm, int spellId) {
+ SpellOnWho *dlg = new SpellOnWho(vm);
+ int result = dlg->execute(spellId);
+ delete dlg;
+
+ if (result == -1)
+ return nullptr;
+
+ Combat &combat = *vm->_combat;
+ Party &party = *vm->_party;
+ return combat._combatMode == 2 ? combat._combatParty[result] :
+ &party._activeParty[result];
+}
+
+int SpellOnWho::execute(int spellId) {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Spells &spells = *_vm->_spells;
+ Window &w = screen._windows[16];
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_3;
+ int result = 999;
+
+ w.open();
+ w.writeString(ON_WHO);
+ w.update();
+ addPartyButtons(_vm);
+
+ while (result == 999) {
+ do {
+ events.updateGameCounter();
+ intf.draw3d(true);
+
+ do {
+ events.pollEventsAndWait();
+ if (_vm->shouldQuit())
+ return -1;
+
+ checkEvents(_vm);
+ } while (!_buttonValue && events.timeElapsed() < 1);
+ } while (!_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_ESCAPE:
+ result = -1;
+ spells.addSpellCost(*combat._oldCharacter, spellId);
+ break;
+
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)(combat._combatMode == 2 ? combat._combatParty.size() :
+ party._activeParty.size())) {
+ result = _buttonValue;
+ }
+ break;
+ }
+ }
+
+ w.close();
+ _vm->_mode = oldMode;
+ return result;
+}
+
+/*------------------------------------------------------------------------*/
+
+int SelectElement::show(XeenEngine *vm, int spellId) {
+ SelectElement *dlg = new SelectElement(vm);
+ int result = dlg->execute(spellId);
+ delete dlg;
+
+ return result;
+}
+
+int SelectElement::execute(int spellId) {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ Spells &spells = *_vm->_spells;
+ Window &w = screen._windows[15];
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_3;
+ int result = 999;
+
+ loadButtons();
+
+ w.open();
+ w.writeString(WHICH_ELEMENT1);
+ drawButtons(&screen);
+ w.update();
+
+ while (result == 999) {
+ do {
+ events.updateGameCounter();
+ intf.draw3d(true);
+ w.frame();
+ w.writeString(WHICH_ELEMENT2);
+ drawButtons(&screen);
+ w.update();
+
+ do {
+ events.pollEventsAndWait();
+ if (_vm->shouldQuit())
+ return -1;
+
+ checkEvents(_vm);
+ } while (!_buttonValue && events.timeElapsed() < 1);
+ } while (!_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_ESCAPE:
+ result = -1;
+ spells.addSpellCost(*combat._oldCharacter, spellId);
+ break;
+
+ case Common::KEYCODE_a:
+ result = DT_POISON;
+ break;
+ case Common::KEYCODE_c:
+ result = DT_COLD;
+ break;
+ case Common::KEYCODE_e:
+ result = DT_ELECTRICAL;
+ break;
+ case Common::KEYCODE_f:
+ result = DT_FIRE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ w.close();
+ _vm->_mode = oldMode;
+ return result;
+}
+
+void SelectElement::loadButtons() {
+ _iconSprites.load("element.icn");
+ addButton(Common::Rect(60, 92, 84, 112), Common::KEYCODE_f, &_iconSprites);
+ addButton(Common::Rect(90, 92, 114, 112), Common::KEYCODE_e, &_iconSprites);
+ addButton(Common::Rect(120, 92, 144, 112), Common::KEYCODE_c, &_iconSprites);
+ addButton(Common::Rect(150, 92, 174, 112), Common::KEYCODE_a, &_iconSprites);
+}
+
+/*------------------------------------------------------------------------*/
+
+void NotWhileEngaged::show(XeenEngine *vm, int spellId) {
+ NotWhileEngaged *dlg = new NotWhileEngaged(vm);
+ dlg->execute(spellId);
+ delete dlg;
+}
+
+void NotWhileEngaged::execute(int spellId) {
+ EventsManager &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ Spells &spells = *_vm->_spells;
+ Window &w = screen._windows[6];
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_3;
+
+ w.open();
+ w.writeString(Common::String::format(CANT_CAST_WHILE_ENGAGED,
+ spells._spellNames[spellId].c_str()));
+ w.update();
+
+ while (!_vm->shouldQuit() && !events.isKeyMousePressed())
+ events.pollEventsAndWait();
+ events.clearEvents();
+
+ w.close();
+ _vm->_mode = oldMode;
+}
+
+/*------------------------------------------------------------------------*/
+
+bool LloydsBeacon::show(XeenEngine *vm) {
+ LloydsBeacon *dlg = new LloydsBeacon(vm);
+ bool result = dlg->execute();
+ delete dlg;
+
+ return result;
+}
+
+bool LloydsBeacon::execute() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Window &w = screen._windows[10];
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ Character &c = *combat._oldCharacter;
+
+ loadButtons();
+
+ if (!c._lloydMap) {
+ // No destination previously set, so have a default ready
+ if (isDarkCc) {
+ c._lloydSide = 1;
+ c._lloydPosition = Common::Point(25, 21);
+ c._lloydMap = 29;
+ } else {
+ c._lloydSide = 0;
+ c._lloydPosition = Common::Point(18, 4);
+ c._lloydMap = 28;
+ }
+ }
+
+ // Open up the text file for the destination map and read in it's name
+ File textFile(Common::String::format("%s%c%03d.txt",
+ c._lloydSide == 0 ? "xeen" : "dark",
+ c._lloydMap >= 100 ? 'x' : '0',
+ c._lloydMap));
+ Common::String mapName = textFile.readString();
+ textFile.close();
+
+ // Display the dialog
+ w.open();
+ w.writeString(Common::String::format(LLOYDS_BEACON,
+ mapName.c_str(), c._lloydPosition.x, c._lloydPosition.y));
+ drawButtons(&screen);
+ w.update();
+
+ bool result = true;
+ do {
+ do {
+ events.updateGameCounter();
+ intf.draw3d(true);
+
+ do {
+ events.pollEventsAndWait();
+ if (_vm->shouldQuit())
+ return true;
+
+ checkEvents(_vm);
+ } while (!_buttonValue && events.timeElapsed() < 1);
+ } while (!_buttonValue);
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_r:
+ if (!isDarkCc && c._lloydMap >= 75 && c._lloydMap <= 78 && !party._cloudsEnd) {
+ result = false;
+ } else {
+ sound.playFX(51);
+ map._loadDarkSide = isDarkCc;
+ if (c._lloydMap != party._mazeId || c._lloydSide != (isDarkCc ? 1 : 0)) {
+ map.load(c._lloydMap);
+ }
+
+ party._mazePosition = c._lloydPosition;
+ }
+
+ _buttonValue = Common::KEYCODE_ESCAPE;
+ break;
+
+ case Common::KEYCODE_s:
+ case Common::KEYCODE_t:
+ sound.playFX(20);
+ c._lloydMap = party._mazeId;
+ c._lloydPosition = party._mazePosition;
+ c._lloydSide = isDarkCc ? 1 : 0;
+
+ _buttonValue = Common::KEYCODE_ESCAPE;
+ break;
+ }
+ } while (_buttonValue != Common::KEYCODE_ESCAPE);
+
+ w.close();
+ return result;
+}
+
+void LloydsBeacon::loadButtons() {
+ _iconSprites.load("lloyds.icn");
+
+ addButton(Common::Rect(281, 108, 305, 128), Common::KEYCODE_r, &_iconSprites);
+ addButton(Common::Rect(242, 108, 266, 128), Common::KEYCODE_t, &_iconSprites);
+}
+
+/*------------------------------------------------------------------------*/
+
+int Teleport::show(XeenEngine *vm) {
+ Teleport *dlg = new Teleport(vm);
+ int result = dlg->execute();
+ delete dlg;
+
+ return result;
+}
+
+int Teleport::execute() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Window &w = screen._windows[6];
+ Common::String num;
+
+ w.open();
+ w.writeString(Common::String::format(HOW_MANY_SQUARES,
+ DIRECTION_TEXT[party._mazeDirection]));
+ w.update();
+ int lineSize = Input::show(_vm, &w, num, 1, 200, true);
+ w.close();
+
+ if (!lineSize)
+ return -1;
+ int numSquares = atoi(num.c_str());
+ Common::Point pt = party._mazePosition;
+ int v;
+
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ pt.y += numSquares;
+ break;
+ case DIR_EAST:
+ pt.x += numSquares;
+ break;
+ case DIR_SOUTH:
+ pt.y -= numSquares;
+ break;
+ case DIR_WEST:
+ pt.x -= numSquares;
+ break;
+ default:
+ break;
+ }
+
+ v = map.mazeLookup(pt, map._isOutdoors ? 0xF : 0xFFFF, 0);
+
+ if ((v != (map._isOutdoors ? 0 : INVALID_CELL)) &&
+ (!map._isOutdoors || v == SURFTYPE_DWATER)) {
+ party._mazePosition = pt;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+int TownPortal::show(XeenEngine *vm) {
+ TownPortal *dlg = new TownPortal(vm);
+ int townNumber = dlg->execute();
+ delete dlg;
+
+ return townNumber;
+}
+
+int TownPortal::execute() {
+ Map &map = *_vm->_map;
+ Screen &screen = *_vm->_screen;
+ Window &w = screen._windows[20];
+ Common::String townNames[5];
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_FF;
+
+ // Build up a lsit of the names of the towns on the current side of Xeen
+ for (int idx = 0; idx < 5; ++idx) {
+ File f(Common::String::format("%s%04d.txt",
+ map._sideTownPortal ? "dark" : "xeen",
+ TOWN_MAP_NUMBERS[map._sideTownPortal][idx]));
+ townNames[idx] = f.readString();
+ f.close();
+ }
+
+ w.open();
+ w.writeString(Common::String::format(TOWN_PORTAL,
+ townNames[0].c_str(), townNames[1].c_str(), townNames[2].c_str(),
+ townNames[3].c_str(), townNames[4].c_str()
+ ));
+ w.update();
+
+ // Get the town number
+ int townNumber;
+ Common::String num;
+ do {
+ int result = Input::show(_vm, &w, num, 1, 160, true);
+ townNumber = !result ? 0 : atoi(num.c_str());
+ } while (townNumber > 5);
+
+ w.close();
+ _vm->_mode = oldMode;
+
+ return townNumber;
+}
+
+/*------------------------------------------------------------------------*/
+
+void IdentifyMonster::show(XeenEngine *vm) {
+ IdentifyMonster *dlg = new IdentifyMonster(vm);
+ dlg->execute();
+ delete dlg;
+}
+
+void IdentifyMonster::execute() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Window &w = screen._windows[17];
+ Common::String monsterDesc[3];
+
+ for (int monIndex = 0; monIndex < 3; ++monIndex) {
+ if (combat._attackMonsters[monIndex] == -1)
+ continue;
+
+ MazeMonster &monster = map._mobData._monsters[combat._attackMonsters[monIndex]];
+ MonsterStruct &monsterData = *monster._monsterData;
+
+ monsterDesc[monIndex] = Common::String::format(MONSTER_DETAILS,
+ monsterData._name.c_str(),
+ _vm->printK2(monster._hp).c_str(),
+ monsterData._accuracy, monsterData._numberOfAttacks,
+ MONSTER_SPECIAL_ATTACKS[monsterData._specialAttack]
+ );
+ }
+
+ sound.playFX(20);
+ w.open();
+ w.writeString(Common::String::format(IDENTIFY_MONSTERS,
+ monsterDesc[0].c_str(), monsterDesc[1].c_str(), monsterDesc[2].c_str()));
+ w.update();
+
+ do {
+ events.updateGameCounter();
+ intf.draw3d(false);
+ w.frame();
+ screen._windows[3].update();
+
+ events.wait(1);
+ } while (!events.isKeyMousePressed());
+
+ w.close();
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_spells.h b/engines/xeen/dialogs_spells.h
new file mode 100644
index 0000000000..35b2708f7a
--- /dev/null
+++ b/engines/xeen/dialogs_spells.h
@@ -0,0 +1,162 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_SPELLS_H
+#define XEEN_DIALOGS_SPELLS_H
+
+#include "common/array.h"
+#include "xeen/dialogs.h"
+#include "xeen/party.h"
+
+namespace Xeen {
+
+struct SpellEntry {
+ Common::String _name;
+ int _spellIndex;
+ int _spellId;
+ int _color;
+
+ SpellEntry(const Common::String &name, int spellIndex, int spellId) :
+ _name(name), _spellIndex(spellIndex), _spellId(spellId), _color(9) {}
+};
+
+class SpellsDialog : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+ SpriteResource _scrollSprites;
+ Common::Array<SpellEntry> _spells;
+
+ SpellsDialog(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ Character *execute(ButtonContainer *priorDialog, Character *c, int isCasting);
+
+ void loadButtons();
+
+ const char *setSpellText(Character *c, int isCasting);
+public:
+ static Character *show(XeenEngine *vm, ButtonContainer *priorDialog,
+ Character *c, int isCasting);
+};
+
+class CastSpell : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ CastSpell(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute(Character *&c);
+
+ void loadButtons();
+public:
+ static int show(XeenEngine *vm);
+ static int show(XeenEngine *vm, Character *&c);
+};
+
+class SpellOnWho : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ SpellOnWho(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute(int spellId);
+public:
+ static Character *show(XeenEngine *vm, int spellId);
+};
+
+class SelectElement : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ SelectElement(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute(int spellId);
+
+ void loadButtons();
+public:
+ static int show(XeenEngine *vm, int spellId);
+};
+
+class NotWhileEngaged : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ NotWhileEngaged(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute(int spellId);
+public:
+ static void show(XeenEngine *vm, int spellId);
+};
+
+class LloydsBeacon : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ LloydsBeacon(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ bool execute();
+
+ void loadButtons();
+public:
+ static bool show(XeenEngine *vm);
+};
+
+class Teleport : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ Teleport(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute();
+public:
+ static int show(XeenEngine *vm);
+};
+
+class TownPortal : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ TownPortal(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute();
+public:
+ static int show(XeenEngine *vm);
+};
+
+class IdentifyMonster : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ IdentifyMonster(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ void execute();
+public:
+ static void show(XeenEngine *vm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_SPELLS_H */
diff --git a/engines/xeen/dialogs_whowill.cpp b/engines/xeen/dialogs_whowill.cpp
new file mode 100644
index 0000000000..fd72154cba
--- /dev/null
+++ b/engines/xeen/dialogs_whowill.cpp
@@ -0,0 +1,105 @@
+/* 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 "xeen/dialogs_whowill.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+int WhoWill::show(XeenEngine *vm, int message, int action, bool type) {
+ WhoWill *dlg = new WhoWill(vm);
+ int result = dlg->execute(message, action, type);
+ delete dlg;
+
+ return result;
+}
+
+int WhoWill::execute(int message, int action, bool type) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Scripts &scripts = *_vm->_scripts;
+ Town &town = *_vm->_town;
+ int numFrames;
+
+ if (party._activeParty.size() <= 1)
+ // Unless there's at least two characters, just return the first one
+ return 1;
+
+ screen._windows[38].close();
+ screen._windows[12].close();
+
+ Common::String actionStr = type ? map._events._text[action] : WHO_WILL_ACTIONS[action];
+ Common::String msg = Common::String::format(WHO_WILL, actionStr.c_str(),
+ WHO_ACTIONS[message], party._activeParty.size());
+
+ screen._windows[36].open();
+ screen._windows[36].writeString(msg);
+ screen._windows[36].update();
+
+ intf._face1State = map._headData[party._mazePosition.y][party._mazePosition.x]._left;
+ intf._face2State = map._headData[party._mazePosition.y][party._mazePosition.x]._right;
+
+ while (!_vm->shouldQuit()) {
+ events.updateGameCounter();
+
+ if (screen._windows[11]._enabled) {
+ town.drawTownAnim(0);
+ screen._windows[36].frame();
+ numFrames = 3;
+ } else {
+ intf.draw3d(false);
+ screen._windows[36].frame();
+ screen._windows[3].update();
+ numFrames = 1;
+ }
+
+ events.wait(numFrames, true);
+ checkEvents(_vm);
+ if (!_buttonValue)
+ continue;
+
+ if (_buttonValue == 27) {
+ _buttonValue = 0;
+ break;
+ } else if (_buttonValue >= Common::KEYCODE_F1 && _buttonValue <= Common::KEYCODE_F6) {
+ _buttonValue -= Common::KEYCODE_F1 - 1;
+ if (_buttonValue > (int)party._activeParty.size())
+ continue;
+
+ if (party._activeParty[_buttonValue - 1].noActions())
+ continue;
+
+ scripts._whoWill = _buttonValue;
+ break;
+ }
+ }
+
+ intf._face1State = intf._face2State = 2;
+ screen._windows[36].close();
+ return _buttonValue;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/dialogs_whowill.h b/engines/xeen/dialogs_whowill.h
new file mode 100644
index 0000000000..8080c36ddb
--- /dev/null
+++ b/engines/xeen/dialogs_whowill.h
@@ -0,0 +1,43 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_DIALOGS_WHOWHILL_H
+#define XEEN_DIALOGS_WHOWHILL_H
+
+#include "xeen/dialogs.h"
+
+namespace Xeen {
+
+class WhoWill : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+
+ WhoWill(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ int execute(int message, int action, bool type);
+public:
+ static int show(XeenEngine *vm, int message, int action, bool type);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_DIALOGS_WHOWHILL_H */
diff --git a/engines/xeen/events.cpp b/engines/xeen/events.cpp
new file mode 100644
index 0000000000..e50d689d9f
--- /dev/null
+++ b/engines/xeen/events.cpp
@@ -0,0 +1,179 @@
+/* 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 "common/scummsys.h"
+#include "graphics/cursorman.h"
+#include "common/events.h"
+#include "common/endian.h"
+#include "engines/util.h"
+#include "xeen/xeen.h"
+#include "xeen/events.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+EventsManager::EventsManager(XeenEngine *vm) : _vm(vm),
+ _frameCounter(0), _priorFrameCounterTime(0), _gameCounter(0),
+ _leftButton(false), _rightButton(false), _sprites("mouse.icn"),
+ _keyCode(Common::KEYCODE_INVALID) {
+ Common::fill(&_gameCounters[0], &_gameCounters[6], 0);
+}
+
+EventsManager::~EventsManager() {
+}
+
+void EventsManager::setCursor(int cursorId) {
+ XSurface cursor;
+ _sprites.draw(cursor, cursorId);
+
+ CursorMan.replaceCursor(cursor.getPixels(), cursor.w, cursor.h, 0, 0, 0);
+ showCursor();
+}
+
+void EventsManager::showCursor() {
+ CursorMan.showMouse(true);
+}
+
+void EventsManager::hideCursor() {
+ CursorMan.showMouse(false);
+}
+
+bool EventsManager::isCursorVisible() {
+ return CursorMan.isVisible();
+}
+
+void EventsManager::pollEvents() {
+ uint32 timer = g_system->getMillis();
+ if (timer >= (_priorFrameCounterTime + GAME_FRAME_TIME)) {
+ _priorFrameCounterTime = timer;
+ nextFrame();
+ }
+
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ case Common::EVENT_RTL:
+ return;
+ case Common::EVENT_KEYDOWN:
+ // Check for debugger
+ if (event.kbd.keycode == Common::KEYCODE_d && (event.kbd.flags & Common::KBD_CTRL)) {
+ // Attach to the debugger
+ _vm->_debugger->attach();
+ _vm->_debugger->onFrame();
+ } else {
+ _keyCode = event.kbd.keycode;
+ }
+ break;
+ case Common::EVENT_MOUSEMOVE:
+ _mousePos = event.mouse;
+ break;
+ case Common::EVENT_LBUTTONDOWN:
+ _leftButton = true;
+ return;
+ case Common::EVENT_LBUTTONUP:
+ _leftButton = false;
+ return;
+ case Common::EVENT_RBUTTONDOWN:
+ _rightButton = true;
+ return;
+ case Common::EVENT_RBUTTONUP:
+ _rightButton = false;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void EventsManager::pollEventsAndWait() {
+ pollEvents();
+ g_system->delayMillis(10);
+}
+
+void EventsManager::clearEvents() {
+ _keyCode = Common::KEYCODE_INVALID;
+ _leftButton = _rightButton = false;
+
+}
+
+void EventsManager::debounceMouse() {
+ while (_leftButton && !_vm->shouldQuit()) {
+ pollEventsAndWait();
+ }
+}
+bool EventsManager::getKey(Common::KeyState &key) {
+ if (_keyCode == Common::KEYCODE_INVALID) {
+ return false;
+ } else {
+ key = _keyCode;
+ _keyCode = Common::KEYCODE_INVALID;
+ return true;
+ }
+}
+
+bool EventsManager::isKeyPending() const {
+ return _keyCode != Common::KEYCODE_INVALID;
+}
+
+bool EventsManager::isKeyMousePressed() {
+ bool result = _leftButton || _rightButton || isKeyPending();
+ debounceMouse();
+ clearEvents();
+
+ return result;
+}
+
+bool EventsManager::wait(uint numFrames, bool interruptable) {
+ while (!_vm->shouldQuit() && timeElapsed() < numFrames) {
+ pollEventsAndWait();
+ if (interruptable && (_leftButton || _rightButton || isKeyPending()))
+ return true;
+ }
+
+ return false;
+}
+
+void EventsManager::ipause(uint amount) {
+ updateGameCounter();
+ do {
+ _vm->_interface->draw3d(true);
+ pollEventsAndWait();
+ } while (!_vm->shouldQuit() && timeElapsed() < amount);
+}
+
+void EventsManager::nextFrame() {
+ ++_frameCounter;
+
+ // Allow debugger to update
+ _vm->_debugger->update();
+
+ // Update the screen
+ _vm->_screen->update();
+}
+
+/*------------------------------------------------------------------------*/
+
+GameEvent::GameEvent() {
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/events.h b/engines/xeen/events.h
new file mode 100644
index 0000000000..7847ecd81c
--- /dev/null
+++ b/engines/xeen/events.h
@@ -0,0 +1,121 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_EVENTS_H
+#define XEEN_EVENTS_H
+
+#include "common/scummsys.h"
+#include "common/events.h"
+#include "xeen/sprites.h"
+
+namespace Xeen {
+
+#define GAME_FRAME_RATE (1000 / 18.2)
+
+class XeenEngine;
+
+class EventsManager {
+private:
+ XeenEngine *_vm;
+ uint32 _frameCounter;
+ uint32 _priorFrameCounterTime;
+ uint32 _gameCounter;
+ uint32 _gameCounters[6];
+ Common::KeyCode _keyCode;
+ SpriteResource _sprites;
+
+ /**
+ * Handles moving to the next game frame
+ */
+ void nextFrame();
+public:
+ bool _leftButton, _rightButton;
+ Common::Point _mousePos;
+public:
+ EventsManager(XeenEngine *vm);
+
+ ~EventsManager();
+
+ /*
+ * Set the cursor
+ */
+ void setCursor(int cursorId);
+
+ /**
+ * Show the mouse cursor
+ */
+ void showCursor();
+
+ /**
+ * Hide the mouse cursor
+ */
+ void hideCursor();
+
+ /**
+ * Returns if the mouse cursor is visible
+ */
+ bool isCursorVisible();
+
+ void pollEvents();
+
+ void pollEventsAndWait();
+
+ void clearEvents();
+
+ void debounceMouse();
+
+ bool getKey(Common::KeyState &key);
+
+ bool isKeyPending() const;
+
+ /**
+ * Returns true if a key or mouse press is pending
+ */
+ bool isKeyMousePressed();
+
+ void updateGameCounter() { _gameCounter = _frameCounter; }
+ void timeMark1() { _gameCounters[1] = _frameCounter; }
+ void timeMark2() { _gameCounters[2] = _frameCounter; }
+ void timeMark3() { _gameCounters[3] = _frameCounter; }
+ void timeMark4() { _gameCounters[4] = _frameCounter; }
+ void timeMark5() { _gameCounters[5] = _frameCounter; }
+
+ uint32 timeElapsed() const { return _frameCounter - _gameCounter; }
+ uint32 timeElapsed1() const { return _frameCounter - _gameCounters[1]; }
+ uint32 timeElapsed2() const { return _frameCounter - _gameCounters[2]; }
+ uint32 timeElapsed3() const { return _frameCounter - _gameCounters[3]; }
+ uint32 timeElapsed4() const { return _frameCounter - _gameCounters[4]; }
+ uint32 timeElapsed5() const { return _frameCounter - _gameCounters[5]; }
+
+ bool wait(uint numFrames, bool interruptable = false);
+
+ void ipause(uint amount);
+};
+
+class GameEvent {
+public:
+ GameEvent();
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_EVENTS_H */
diff --git a/engines/xeen/files.cpp b/engines/xeen/files.cpp
new file mode 100644
index 0000000000..bcee6bf9f6
--- /dev/null
+++ b/engines/xeen/files.cpp
@@ -0,0 +1,230 @@
+/* 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 "common/scummsys.h"
+#include "common/archive.h"
+#include "common/memstream.h"
+#include "common/textconsole.h"
+#include "xeen/xeen.h"
+#include "xeen/files.h"
+
+namespace Xeen {
+
+uint16 BaseCCArchive::convertNameToId(const Common::String &resourceName) {
+ if (resourceName.empty())
+ return 0xffff;
+
+ Common::String name = resourceName;
+ name.toUppercase();
+
+ // Check if a resource number is being directly specified
+ if (name.size() == 4) {
+ char *endPtr;
+ uint16 num = (uint16)strtol(name.c_str(), &endPtr, 16);
+ if (!*endPtr)
+ return num;
+ }
+
+ const byte *msgP = (const byte *)name.c_str();
+ int total = *msgP++;
+ for (; *msgP; total += *msgP++) {
+ // Rotate the bits in 'total' right 7 places
+ total = (total & 0x007F) << 9 | (total & 0xFF80) >> 7;
+ }
+
+ return total;
+}
+
+void BaseCCArchive::loadIndex(Common::SeekableReadStream *stream) {
+ int count = stream->readUint16LE();
+
+ // Read in the data for the archive's index
+ byte *rawIndex = new byte[count * 8];
+ stream->read(rawIndex, count * 8);
+
+ // Decrypt the index
+ int ah = 0xac;
+ for (int i = 0; i < count * 8; ++i) {
+ rawIndex[i] = (byte)(((rawIndex[i] << 2 | rawIndex[i] >> 6) + ah) & 0xff);
+ ah += 0x67;
+ }
+
+ // Extract the index data into entry structures
+ _index.reserve(count);
+ const byte *entryP = &rawIndex[0];
+ for (int i = 0; i < count; ++i, entryP += 8) {
+ CCEntry entry;
+ entry._id = READ_LE_UINT16(entryP);
+ entry._offset = READ_LE_UINT32(entryP + 2) & 0xffffff;
+ entry._size = READ_LE_UINT16(entryP + 5);
+ assert(!entryP[7]);
+
+ _index.push_back(entry);
+ }
+
+ delete[] rawIndex;
+}
+
+bool BaseCCArchive::hasFile(const Common::String &name) const {
+ CCEntry ccEntry;
+ return getHeaderEntry(name, ccEntry);
+}
+
+bool BaseCCArchive::getHeaderEntry(const Common::String &resourceName, CCEntry &ccEntry) const {
+ uint16 id = convertNameToId(resourceName);
+
+ // Loop through the index
+ for (uint i = 0; i < _index.size(); ++i) {
+ if (_index[i]._id == id) {
+ ccEntry = _index[i];
+ return true;
+ }
+ }
+
+ // Could not find an entry
+ return false;
+}
+
+const Common::ArchiveMemberPtr BaseCCArchive::getMember(const Common::String &name) const {
+ if (!hasFile(name))
+ return Common::ArchiveMemberPtr();
+
+ return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
+}
+
+int BaseCCArchive::listMembers(Common::ArchiveMemberList &list) const {
+ // CC files don't maintain the original filenames, so we can't list it
+ return 0;
+}
+
+/*------------------------------------------------------------------------*/
+
+CCArchive::CCArchive(const Common::String &filename, bool encoded):
+ BaseCCArchive(), _filename(filename), _encoded(encoded) {
+ File f(filename);
+ loadIndex(&f);
+}
+
+CCArchive::CCArchive(const Common::String &filename, const Common::String &prefix,
+ bool encoded): BaseCCArchive(), _filename(filename),
+ _prefix(prefix), _encoded(encoded) {
+ _prefix.toLowercase();
+ File f(filename);
+ loadIndex(&f);
+}
+
+CCArchive::~CCArchive() {
+}
+
+bool CCArchive::getHeaderEntry(const Common::String &resourceName, CCEntry &ccEntry) const {
+ Common::String resName = resourceName;
+
+ if (!_prefix.empty() && resName.contains('|')) {
+ resName.toLowercase();
+ Common::String prefix = _prefix + "|";
+
+ if (!strncmp(resName.c_str(), prefix.c_str(), prefix.size()))
+ // Matching CC prefix, so strip it off and allow processing to
+ // continue onto the base getHeaderEntry method
+ resName = Common::String(resName.c_str() + prefix.size());
+ else
+ // Not matching prefix, so don't allow a match
+ return false;
+ }
+
+ return BaseCCArchive::getHeaderEntry(resName, ccEntry);
+}
+
+Common::SeekableReadStream *CCArchive::createReadStreamForMember(const Common::String &name) const {
+ CCEntry ccEntry;
+
+ if (getHeaderEntry(name, ccEntry)) {
+ // Open the correct CC file
+ Common::File f;
+ if (!f.open(_filename))
+ error("Could not open CC file");
+
+ // Read in the data for the specific resource
+ f.seek(ccEntry._offset);
+ byte *data = (byte *)malloc(ccEntry._size);
+ f.read(data, ccEntry._size);
+
+ if (_encoded) {
+ // Decrypt the data
+ for (int i = 0; i < ccEntry._size; ++i)
+ data[i] ^= 0x35;
+ }
+
+ // Return the data as a stream
+ return new Common::MemoryReadStream(data, ccEntry._size, DisposeAfterUse::YES);
+ }
+
+ return nullptr;
+}
+
+/*------------------------------------------------------------------------*/
+
+FileManager::FileManager(XeenEngine *vm) {
+ Common::File f;
+ int sideNum = 0;
+
+ _isDarkCc = vm->getGameID() == GType_DarkSide;
+ _sideArchives[0] = _sideArchives[1] = nullptr;
+
+ if (vm->getGameID() != GType_DarkSide) {
+ _sideArchives[0] = new CCArchive("xeen.cc", "xeen", true);
+ SearchMan.add("xeen", _sideArchives[0]);
+ sideNum = 1;
+ }
+
+ if (vm->getGameID() == GType_DarkSide || vm->getGameID() == GType_WorldOfXeen) {
+ _sideArchives[sideNum] = new CCArchive("dark.cc", "dark", true);
+ SearchMan.add("dark", _sideArchives[sideNum]);
+ }
+
+ SearchMan.add("intro", new CCArchive("intro.cc", "intro", true));
+}
+
+/*------------------------------------------------------------------------*/
+
+void File::openFile(const Common::String &filename) {
+ if (!Common::File::open(filename))
+ error("Could not open file - %s", filename.c_str());
+}
+
+void File::openFile(const Common::String &filename, Common::Archive &archive) {
+ if (!Common::File::open(filename, archive))
+ error("Could not open file - %s", filename.c_str());
+}
+
+Common::String File::readString() {
+ Common::String result;
+ char c;
+
+ while (pos() < size() && (c = (char)readByte()) != '\0')
+ result += c;
+
+ return result;
+}
+
+
+} // End of namespace Xeen
diff --git a/engines/xeen/files.h b/engines/xeen/files.h
new file mode 100644
index 0000000000..fc75031a72
--- /dev/null
+++ b/engines/xeen/files.h
@@ -0,0 +1,170 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_FILES_H
+#define XEEN_FILES_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/file.h"
+#include "common/serializer.h"
+#include "graphics/surface.h"
+#include "xeen/xsurface.h"
+
+namespace Xeen {
+
+class XeenEngine;
+class CCArchive;
+
+#define SYNC_AS(SUFFIX,STREAM,TYPE,SIZE) \
+ template<typename T> \
+ void syncAs ## SUFFIX(T &val, Version minVersion = 0, Version maxVersion = kLastVersion) { \
+ if (_version < minVersion || _version > maxVersion) \
+ return; \
+ if (_loadStream) \
+ val = static_cast<TYPE>(_loadStream->read ## STREAM()); \
+ else { \
+ TYPE tmp = (TYPE)val; \
+ _saveStream->write ## STREAM(tmp); \
+ } \
+ _bytesSynced += SIZE; \
+ }
+/*
+ * Main resource manager
+ */
+class FileManager {
+public:
+ bool _isDarkCc;
+ CCArchive *_sideArchives[2];
+public:
+ /**
+ * Instantiates the resource manager
+ */
+ FileManager(XeenEngine *vm);
+
+ void setGameCc(bool isDarkCc) { _isDarkCc = isDarkCc; }
+};
+
+/**
+ * Derived file class
+ */
+class File : public Common::File {
+public:
+ File() : Common::File() {}
+ File(const Common::String &filename) { openFile(filename); }
+ File(const Common::String &filename, Common::Archive &archive) {
+ openFile(filename, archive);
+ }
+ virtual ~File() {}
+
+ /**
+ * Opens the given file, throwing an error if it can't be opened
+ */
+ void openFile(const Common::String &filename);
+
+ /**
+ * Opens the given file, throwing an error if it can't be opened
+ */
+ void openFile(const Common::String &filename, Common::Archive &archive);
+
+ Common::String readString();
+};
+
+class XeenSerializer : public Common::Serializer {
+private:
+ Common::SeekableReadStream *_in;
+public:
+ XeenSerializer(Common::SeekableReadStream *in, Common::WriteStream *out) :
+ Common::Serializer(in, out), _in(in) {}
+
+ SYNC_AS(Sint8, Byte, int8, 1)
+
+ bool finished() const { return _in != nullptr && _in->pos() >= _in->size(); }
+};
+
+/**
+* Details of a single entry in a CC file index
+*/
+struct CCEntry {
+ uint16 _id;
+ uint32 _offset;
+ uint16 _size;
+
+ CCEntry() : _id(0), _offset(0), _size(0) {}
+ CCEntry(uint16 id, uint32 offset, uint32 size)
+ : _id(id), _offset(offset), _size(size) {
+ }
+};
+
+/**
+* Base Xeen CC file implementation
+*/
+class BaseCCArchive : public Common::Archive {
+protected:
+ Common::Array<CCEntry> _index;
+
+ /**
+ * Load the index of a given CC file
+ */
+ void loadIndex(Common::SeekableReadStream *stream);
+
+ /**
+ * Given a resource name, returns whether an entry exists, and returns
+ * the header index data for that entry
+ */
+ virtual bool getHeaderEntry(const Common::String &resourceName, CCEntry &ccEntry) const;
+public:
+ /**
+ * Hash a given filename to produce the Id that represents it
+ */
+ static uint16 convertNameToId(const Common::String &resourceName);
+public:
+ BaseCCArchive() {}
+
+ // Archive implementation
+ virtual bool hasFile(const Common::String &name) const;
+ virtual int listMembers(Common::ArchiveMemberList &list) const;
+ virtual const Common::ArchiveMemberPtr getMember(const Common::String &name) const;
+};
+
+/**
+* Xeen CC file implementation
+*/
+class CCArchive : public BaseCCArchive {
+private:
+ Common::String _filename;
+ Common::String _prefix;
+ bool _encoded;
+protected:
+ virtual bool getHeaderEntry(const Common::String &resourceName, CCEntry &ccEntry) const;
+public:
+ CCArchive(const Common::String &filename, bool encoded);
+ CCArchive(const Common::String &filename, const Common::String &prefix, bool encoded);
+ virtual ~CCArchive();
+
+ // Archive implementation
+ virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_FILES_H */
diff --git a/engines/xeen/font.cpp b/engines/xeen/font.cpp
new file mode 100644
index 0000000000..87e16b5bdd
--- /dev/null
+++ b/engines/xeen/font.cpp
@@ -0,0 +1,347 @@
+/* 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 "common/endian.h"
+#include "xeen/font.h"
+#include "xeen/resources.h"
+
+namespace Xeen {
+
+FontSurface::FontSurface() : XSurface(), _fontData(nullptr), _bgColor(DEFAULT_BG_COLOR),
+ _fontReduced(false),_fontJustify(JUSTIFY_NONE), _msgWraps(false) {
+ setTextColor(0);
+}
+
+FontSurface::FontSurface(int wv, int hv) : XSurface(wv, hv), _fontData(nullptr), _msgWraps(false),
+ _bgColor(DEFAULT_BG_COLOR), _fontReduced(false), _fontJustify(JUSTIFY_NONE) {
+ create(w, h);
+ setTextColor(0);
+}
+
+void FontSurface::writeSymbol(int symbolId) {
+ const byte *srcP = &SYMBOLS[symbolId][0];
+
+ for (int yp = 0; yp < FONT_HEIGHT; ++yp) {
+ byte *destP = (byte *)getBasePtr(_writePos.x, _writePos.y + yp);
+
+ for (int xp = 0; xp < FONT_WIDTH; ++xp, ++destP) {
+ byte b = *srcP++;
+ if (b)
+ *destP = b;
+ }
+ }
+
+ _writePos.x += 8;
+}
+
+const char *FontSurface::writeString(const Common::String &s, const Common::Rect &bounds) {
+ _displayString = s.c_str();
+ assert(_fontData);
+
+ for (;;) {
+ const char *msgStartP = _displayString;
+ _msgWraps = false;
+
+ // Get the size of the string that can be displayed on the line
+ int xp = _fontJustify == JUSTIFY_CENTER ? bounds.left : _writePos.x;
+ while (!getNextCharWidth(xp)) {
+ if (xp >= bounds.right) {
+ --_displayString;
+ _msgWraps = true;
+ break;
+ }
+ }
+
+ // Get the end point of the text that can be displayed
+ const char *displayEnd = _displayString;
+ _displayString = msgStartP;
+
+ if (_msgWraps && _fontJustify != JUSTIFY_RIGHT && xp >= bounds.right) {
+ // Need to handle justification of text
+ // First, move backwards to find the end of the previous word
+ // for a convenient point to break the line at
+ const char *endP = displayEnd;
+ while (endP > _displayString && (*endP & 0x7f) != ' ')
+ --endP;
+
+ if (endP == _displayString) {
+ // There was no word breaks at all in the string
+ --displayEnd;
+ if (_fontJustify == JUSTIFY_NONE && _writePos.x != bounds.left) {
+ // Move to the next line
+ if (!newLine(bounds))
+ continue;
+ // Ran out of space to display string
+ break;
+ }
+ } else {
+ // Found word break, find end of previous word
+ while (endP > _displayString && (*endP & 0x7f) == ' ')
+ --endP;
+
+ displayEnd = endP;
+ }
+ }
+
+ // Justification adjustment
+ if (_fontJustify != JUSTIFY_NONE) {
+ // Figure out the width of the selected portion of the string
+ int totalWidth = 0;
+ while (!getNextCharWidth(totalWidth)) {
+ if (_displayString > displayEnd) {
+ if (*displayEnd == ' ') {
+ // Don't include any ending space as part of the total
+ totalWidth -= _fontReduced ? 4 : 5;
+ }
+ break;
+ }
+ }
+
+ // Reset starting position back to the start of the string portion
+ _displayString = msgStartP;
+
+ if (_fontJustify == JUSTIFY_RIGHT) {
+ // Right aligned
+ if (_writePos.x == bounds.left)
+ _writePos.x = bounds.right;
+ _writePos.x -= totalWidth + 1;
+ } else {
+ // Center aligned
+ if (_writePos.x == bounds.left)
+ _writePos.x = (bounds.left + bounds.right + 1 - totalWidth) / 2;
+ else
+ _writePos.x = (_writePos.x * 2 - totalWidth) / 2;
+ }
+ }
+
+ // Main character display loop
+ while (_displayString <= displayEnd) {
+ char c = getNextChar();
+
+ if (c == ' ') {
+ _writePos.x += _fontReduced ? 3 : 4;
+ } else if (c == '\r') {
+ fillRect(bounds, _bgColor);
+ addDirtyRect(bounds);
+ _writePos = Common::Point(bounds.left, bounds.top);
+ } else if (c == 1) {
+ // Turn off reduced font mode
+ _fontReduced = false;
+ } else if (c == 2) {
+ // Turn on reduced font mode
+ _fontReduced = true;
+ } else if (c == 3) {
+ // Justify text
+ c = getNextChar();
+ if (c == 'r')
+ _fontJustify = JUSTIFY_RIGHT;
+ else if (c == 'c')
+ _fontJustify = JUSTIFY_CENTER;
+ else
+ _fontJustify = JUSTIFY_NONE;
+ } else if (c == 4) {
+ // Draw an empty box of a given width
+ int wv = fontAtoi();
+ Common::Point pt = _writePos;
+ if (_fontJustify == JUSTIFY_RIGHT)
+ pt.x -= wv;
+
+ Common::Rect r(pt.x, pt.y, pt.x + wv, pt.y + (_fontReduced ? 9 : 10));
+ fillRect(r, _bgColor);
+ } else if (c == 5) {
+ continue;
+ } else if (c == 6) {
+ // Non-breakable space
+ writeChar(' ', bounds);
+ } else if (c == 7) {
+ // Set text background color
+ int bgColor = fontAtoi();
+ _bgColor = (bgColor < 0 || bgColor > 255) ? DEFAULT_BG_COLOR : bgColor;
+ } else if (c == 8) {
+ // Draw a character outline
+ c = getNextChar();
+ if (c == ' ') {
+ c = '\0';
+ _writePos.x -= 3;
+ } else {
+ if (c == 6)
+ c = ' ';
+ byte charSize = _fontData[0x1000 + (int)c + (_fontReduced ? 0x80 : 0)];
+ _writePos.x -= charSize;
+ }
+
+ if (_writePos.x < bounds.left)
+ _writePos.x = bounds.left;
+
+ if (c) {
+ int oldX = _writePos.x;
+ byte oldColor[4];
+ Common::copy(&_textColors[0], &_textColors[4], &oldColor[0]);
+
+ _textColors[1] = _textColors[2] = _textColors[3] = _bgColor;
+ writeChar(c, bounds);
+
+ Common::copy(&oldColor[0], &oldColor[4], &_textColors[0]);
+ _writePos.x = oldX;
+ }
+ } else if (c == 9) {
+ // Skip x position
+ int xAmount = fontAtoi();
+ _writePos.x = MIN(bounds.left + xAmount, (int)bounds.right);
+ } else if (c == 10) {
+ // Newline
+ if (newLine(bounds))
+ break;
+ } else if (c == 11) {
+ // Set y position
+ int yp = fontAtoi();
+ _writePos.y = MIN(bounds.top + yp, (int)bounds.bottom);
+ } else if (c == 12) {
+ // Set text colors
+ int idx = fontAtoi(2);
+ if (idx < 0)
+ idx = 0;
+ setTextColor(idx);
+ } else if (c < ' ') {
+ // End of string or invalid command
+ _displayString = nullptr;
+ break;
+ } else {
+ // Standard character - write it out
+ writeChar(c, bounds);
+ }
+ }
+
+ if (!_displayString)
+ break;
+ if ( _displayString > displayEnd && _fontJustify != JUSTIFY_RIGHT && _msgWraps
+ && newLine(bounds))
+ break;
+ }
+
+ return _displayString;
+}
+
+char FontSurface::getNextChar() {
+ return *_displayString++ & 0x7f;
+}
+
+bool FontSurface::getNextCharWidth(int &total) {
+ char c = getNextChar();
+
+ if (c > ' ') {
+ total += _fontData[0x1000 + (int)c + (_fontReduced ? 0x80 : 0)];
+ return false;
+ } else if (c == ' ') {
+ total += 4;
+ return false;
+ } else if (c == 8) {
+ c = getNextChar();
+ if (c == ' ') {
+ total -= 2;
+ return false;
+ } else {
+ _displayString -= 2;
+ return true;
+ }
+ } else if (c == 12) {
+ c = getNextChar();
+ if (c != 'd')
+ getNextChar();
+ return false;
+ } else {
+ --_displayString;
+ return true;
+ }
+}
+
+bool FontSurface::newLine(const Common::Rect &bounds) {
+ // Move past any spaces currently being pointed to
+ while ((*_displayString & 0x7f) == ' ')
+ ++_displayString;
+
+ _msgWraps = false;
+ _writePos.x = bounds.left;
+
+ int hv = _fontReduced ? 9 : 10;
+ _writePos.y += hv;
+
+ return ((_writePos.y + hv - 1) > bounds.bottom);
+}
+
+int FontSurface::fontAtoi(int len) {
+ int total = 0;
+ for (int i = 0; i < len; ++i) {
+ char c = getNextChar();
+ if (c == ' ')
+ c = '0';
+
+ int digit = c - '0';
+ if (digit < 0 || digit > 9)
+ return -1;
+
+ total = total * 10 + digit;
+ }
+
+ return total;
+}
+
+void FontSurface::setTextColor(int idx) {
+ const byte *colP = &TEXT_COLORS[idx][0];
+ Common::copy(colP, colP + 4, &_textColors[0]);
+}
+
+void FontSurface::writeChar(char c, const Common::Rect &clipRect) {
+ // Get y position, handling kerning
+ int y = _writePos.y;
+ if (c == 'g' || c == 'p' || c == 'q' || c == 'y')
+ ++y;
+
+ // Get pointers into font data and surface to write pixels to
+ int charIndex = (int)c + (_fontReduced ? 0x80 : 0);
+ const byte *srcP = &_fontData[charIndex * 16];
+
+ for (int yp = 0; yp < FONT_HEIGHT; ++yp, ++y) {
+ uint16 lineData = READ_LE_UINT16(srcP); srcP += 2;
+ byte *destP = (byte *)getBasePtr(_writePos.x, y);
+
+ // Ignore line if it's outside the clipping rect
+ if (y < clipRect.top || y >= clipRect.bottom)
+ continue;
+ const byte *lineStart = (const byte *)getBasePtr(clipRect.left, y);
+ const byte *lineEnd = (const byte *)getBasePtr(clipRect.right, y);
+
+ for (int xp = 0; xp < FONT_WIDTH; ++xp, ++destP) {
+ int colIndex = lineData & 3;
+ lineData >>= 2;
+
+ if (colIndex && destP >= lineStart && destP < lineEnd)
+ *destP = _textColors[colIndex];
+ }
+ }
+
+ addDirtyRect(Common::Rect(_writePos.x, _writePos.y, _writePos.x + FONT_WIDTH,
+ _writePos.y + FONT_HEIGHT));
+ _writePos.x += _fontData[0x1000 + charIndex];
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/font.h b/engines/xeen/font.h
new file mode 100644
index 0000000000..021205d5e5
--- /dev/null
+++ b/engines/xeen/font.h
@@ -0,0 +1,101 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_FONT_H
+#define XEEN_FONT_H
+
+#include "xeen/xsurface.h"
+
+namespace Xeen {
+
+#define FONT_WIDTH 8
+#define FONT_HEIGHT 8
+#define DEFAULT_BG_COLOR 0x99
+
+enum Justify { JUSTIFY_NONE = 0, JUSTIFY_CENTER = 1, JUSTIFY_RIGHT = 2 };
+
+class FontSurface: public XSurface {
+private:
+ const char *_displayString;
+ bool _msgWraps;
+
+ /**
+ * Return the next pending character to display
+ */
+ char getNextChar();
+
+ /**
+ * Return the width of a given character
+ */
+ bool getNextCharWidth(int &total);
+
+ /**
+ * Handles moving to the next line of the given bounded area
+ */
+ bool newLine(const Common::Rect &bounds);
+
+ /**
+ * Extract a number of a given maximum length from the string
+ */
+ int fontAtoi(int len = 3);
+
+ /**
+ * Set the text colors based on the specified index in the master text colors list
+ */
+ void setTextColor(int idx);
+
+ /**
+ * Wrie a character to the surface
+ */
+ void writeChar(char c, const Common::Rect &clipRect);
+public:
+ const byte *_fontData;
+ Common::Point _writePos;
+ byte _textColors[4];
+ byte _bgColor;
+ bool _fontReduced;
+ Justify _fontJustify;
+public:
+ FontSurface();
+ FontSurface(int wv, int hv);
+ virtual ~FontSurface() {}
+
+ /**
+ * Draws a symbol to the surface.
+ * @param symbolId Symbol number from 0 to 19
+ */
+ void writeSymbol(int symbolId);
+
+ /**
+ * Write a string to the surface
+ * @param s String to display
+ * @param clipRect Window bounds to display string within
+ * @returns Any string remainder that couldn't be displayed
+ * @remarks Note that bounds is just used for wrapping purposes. Unless
+ * justification is set, the message will be written at _writePos
+ */
+ const char *writeString(const Common::String &s, const Common::Rect &clipRect);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_FONT_H */
diff --git a/engines/xeen/interface.cpp b/engines/xeen/interface.cpp
new file mode 100644
index 0000000000..9f9df190f7
--- /dev/null
+++ b/engines/xeen/interface.cpp
@@ -0,0 +1,2291 @@
+/* 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 "xeen/interface.h"
+#include "xeen/dialogs_automap.h"
+#include "xeen/dialogs_char_info.h"
+#include "xeen/dialogs_control_panel.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/dialogs_fight_options.h"
+#include "xeen/dialogs_info.h"
+#include "xeen/dialogs_items.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/dialogs_quests.h"
+#include "xeen/dialogs_quick_ref.h"
+#include "xeen/dialogs_spells.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+#include "xeen/dialogs_party.h"
+
+namespace Xeen {
+
+PartyDrawer::PartyDrawer(XeenEngine *vm): _vm(vm) {
+ _restoreSprites.load("restorex.icn");
+ _hpSprites.load("hpbars.icn");
+ _dseFace.load("dse.fac");
+ _hiliteChar = -1;
+}
+
+void PartyDrawer::drawParty(bool updateFlag) {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+ bool inCombat = _vm->_mode == MODE_COMBAT;
+ _restoreSprites.draw(screen, 0, Common::Point(8, 149));
+
+ // Handle drawing the party faces
+ uint partyCount = inCombat ? combat._combatParty.size() : party._activeParty.size();
+ for (uint idx = 0; idx < partyCount; ++idx) {
+ Character &ps = inCombat ? *combat._combatParty[idx] : party._activeParty[idx];
+ Condition charCondition = ps.worstCondition();
+ int charFrame = FACE_CONDITION_FRAMES[charCondition];
+
+ SpriteResource *sprites = (charFrame > 4) ? &_dseFace : ps._faceSprites;
+ if (charFrame > 4)
+ charFrame -= 5;
+
+ sprites->draw(screen, charFrame, Common::Point(CHAR_FACES_X[idx], 150));
+ }
+
+ for (uint idx = 0; idx < partyCount; ++idx) {
+ Character &ps = inCombat ? *combat._combatParty[idx] : party._activeParty[idx];
+
+ // Draw the Hp bar
+ int maxHp = ps.getMaxHP();
+ int frame;
+ if (ps._currentHp < 1)
+ frame = 4;
+ else if (ps._currentHp > maxHp)
+ frame = 3;
+ else if (ps._currentHp == maxHp)
+ frame = 0;
+ else if (ps._currentHp < (maxHp / 4))
+ frame = 2;
+ else
+ frame = 1;
+
+ _hpSprites.draw(screen, frame, Common::Point(HP_BARS_X[idx], 182));
+ }
+
+ if (_hiliteChar != -1)
+ res._globalSprites.draw(screen, 8, Common::Point(CHAR_FACES_X[_hiliteChar] - 1, 149));
+
+ if (updateFlag)
+ screen._windows[33].update();
+}
+
+void PartyDrawer::highlightChar(int charId) {
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+
+ if (charId != _hiliteChar && _hiliteChar != HILIGHT_CHAR_DISABLED) {
+ // Handle deselecting any previusly selected char
+ if (_hiliteChar != -1) {
+ res._globalSprites.draw(screen, 9 + _hiliteChar,
+ Common::Point(CHAR_FACES_X[_hiliteChar] - 1, 149));
+ }
+
+ // Highlight new character
+ res._globalSprites.draw(screen, 8, Common::Point(CHAR_FACES_X[charId] - 1, 149));
+ _hiliteChar = charId;
+ screen._windows[33].update();
+ }
+}
+
+void PartyDrawer::unhighlightChar() {
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+
+ if (_hiliteChar != -1) {
+ res._globalSprites.draw(screen, _hiliteChar + 9,
+ Common::Point(CHAR_FACES_X[_hiliteChar] - 1, 149));
+ _hiliteChar = -1;
+ screen._windows[33].update();
+ }
+}
+
+void PartyDrawer::resetHighlight() {
+ _hiliteChar = -1;
+}
+/*------------------------------------------------------------------------*/
+
+Interface::Interface(XeenEngine *vm) : ButtonContainer(), InterfaceMap(vm),
+ PartyDrawer(vm), _vm(vm) {
+ _buttonsLoaded = false;
+ _intrIndex1 = 0;
+ _steppingFX = 0;
+ _falling = false;
+ _blessedUIFrame = 0;
+ _powerShieldUIFrame = 0;
+ _holyBonusUIFrame = 0;
+ _heroismUIFrame = 0;
+ _flipUIFrame = 0;
+ _face1UIFrame = 0;
+ _face2UIFrame = 0;
+ _levitateUIFrame = 0;
+ _spotDoorsUIFrame = 0;
+ _dangerSenseUIFrame = 0;
+ _face1State = _face2State = 0;
+ _upDoorText = false;
+ _tillMove = 0;
+ Common::fill(&_charFX[0], &_charFX[MAX_ACTIVE_PARTY], 0);
+
+ initDrawStructs();
+}
+
+void Interface::initDrawStructs() {
+ _mainList[0] = DrawStruct(7, 232, 74);
+ _mainList[1] = DrawStruct(0, 235, 75);
+ _mainList[2] = DrawStruct(2, 260, 75);
+ _mainList[3] = DrawStruct(4, 286, 75);
+ _mainList[4] = DrawStruct(6, 235, 96);
+ _mainList[5] = DrawStruct(8, 260, 96);
+ _mainList[6] = DrawStruct(10, 286, 96);
+ _mainList[7] = DrawStruct(12, 235, 117);
+ _mainList[8] = DrawStruct(14, 260, 117);
+ _mainList[9] = DrawStruct(16, 286, 117);
+ _mainList[10] = DrawStruct(20, 235, 148);
+ _mainList[11] = DrawStruct(22, 260, 148);
+ _mainList[12] = DrawStruct(24, 286, 148);
+ _mainList[13] = DrawStruct(26, 235, 169);
+ _mainList[14] = DrawStruct(28, 260, 169);
+ _mainList[15] = DrawStruct(30, 286, 169);
+}
+
+void Interface::setup() {
+ _borderSprites.load("border.icn");
+ _spellFxSprites.load("spellfx.icn");
+ _fecpSprites.load("fecp.brd");
+ _blessSprites.load("bless.icn");
+ _charPowSprites.load("charpow.icn");
+ _uiSprites.load("inn.icn");
+
+ Party &party = *_vm->_party;
+ party.loadActiveParty();
+ party._newDay = party._minutes < 300;
+}
+
+void Interface::startup() {
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+ _iconSprites.load("main.icn");
+
+ animate3d();
+ if (_vm->_map->_isOutdoors) {
+ setIndoorsMonsters();
+ setIndoorsObjects();
+ } else {
+ setOutdoorsMonsters();
+ setOutdoorsObjects();
+ }
+ draw3d(false);
+
+ res._globalSprites.draw(screen._windows[1], 5, Common::Point(232, 9));
+ drawParty(false);
+
+ _mainList[0]._sprites = &res._globalSprites;
+ for (int i = 1; i < 16; ++i)
+ _mainList[i]._sprites = &_iconSprites;
+
+ setMainButtons();
+
+ _tillMove = false;
+}
+
+void Interface::mainIconsPrint() {
+ Screen &screen = *_vm->_screen;
+ screen._windows[38].close();
+ screen._windows[12].close();
+ screen._windows[0].drawList(_mainList, 16);
+ screen._windows[34].update();
+}
+
+void Interface::setMainButtons(bool combatMode) {
+ clearButtons();
+
+ addButton(Common::Rect(235, 75, 259, 95), Common::KEYCODE_s, &_iconSprites);
+ addButton(Common::Rect(260, 75, 284, 95), Common::KEYCODE_c, &_iconSprites);
+ addButton(Common::Rect(286, 75, 310, 95), Common::KEYCODE_r, &_iconSprites);
+ addButton(Common::Rect(235, 96, 259, 116), Common::KEYCODE_b, &_iconSprites);
+ addButton(Common::Rect(260, 96, 284, 116), Common::KEYCODE_d, &_iconSprites);
+ addButton(Common::Rect(286, 96, 310, 116), Common::KEYCODE_v, &_iconSprites);
+ addButton(Common::Rect(235, 117, 259, 137), Common::KEYCODE_m, &_iconSprites);
+ addButton(Common::Rect(260, 117, 284, 137), Common::KEYCODE_i, &_iconSprites);
+ addButton(Common::Rect(286, 117, 310, 137), Common::KEYCODE_q, &_iconSprites);
+ addButton(Common::Rect(109, 137, 122, 147), Common::KEYCODE_TAB, &_iconSprites);
+ addButton(Common::Rect(235, 148, 259, 168), Common::KEYCODE_LEFT, &_iconSprites);
+ addButton(Common::Rect(260, 148, 284, 168), Common::KEYCODE_UP, &_iconSprites);
+ addButton(Common::Rect(286, 148, 310, 168), Common::KEYCODE_RIGHT, &_iconSprites);
+ addButton(Common::Rect(235, 169, 259, 189), (Common::KBD_CTRL << 16) |Common::KEYCODE_LEFT, &_iconSprites);
+ addButton(Common::Rect(260, 169, 284, 189), Common::KEYCODE_DOWN, &_iconSprites);
+ addButton(Common::Rect(286, 169, 310, 189), (Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT, &_iconSprites);
+ addButton(Common::Rect(236, 11, 308, 69), Common::KEYCODE_EQUALS);
+ addButton(Common::Rect(239, 27, 312, 37), Common::KEYCODE_1);
+ addButton(Common::Rect(239, 37, 312, 47), Common::KEYCODE_2);
+ addButton(Common::Rect(239, 47, 312, 57), Common::KEYCODE_3);
+ addPartyButtons(_vm);
+
+ if (combatMode) {
+ _buttons[0]._value = Common::KEYCODE_f;
+ _buttons[1]._value = Common::KEYCODE_c;
+ _buttons[2]._value = Common::KEYCODE_a;
+ _buttons[3]._value = Common::KEYCODE_u;
+ _buttons[4]._value = Common::KEYCODE_r;
+ _buttons[5]._value = Common::KEYCODE_b;
+ _buttons[6]._value = Common::KEYCODE_o;
+ _buttons[7]._value = Common::KEYCODE_i;
+ _buttons[16]._value = 0;
+ }
+}
+
+void Interface::perform() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Scripts &scripts = *_vm->_scripts;
+ SoundManager &sound = *_vm->_sound;
+ Spells &spells = *_vm->_spells;
+ const Common::Rect WAIT_BOUNDS(8, 8, 224, 140);
+
+ events.updateGameCounter();
+ draw3d(true);
+
+ // Wait for a frame or a user event
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+
+ if (events._leftButton && WAIT_BOUNDS.contains(events._mousePos))
+ _buttonValue = Common::KEYCODE_SPACE;
+ } while (!_buttonValue && events.timeElapsed() < 1 && !_vm->_party->_partyDead);
+
+ if (!_buttonValue && !_vm->_party->_partyDead)
+ return;
+
+ if (_buttonValue == Common::KEYCODE_SPACE) {
+ int lookupId = map.mazeLookup(party._mazePosition,
+ WALL_SHIFTS[party._mazeDirection][2]);
+
+ bool eventsFlag = true;
+ switch (lookupId) {
+ case 1:
+ if (!map._isOutdoors) {
+ scripts.openGrate(13, 1);
+ eventsFlag = _buttonValue != 0;
+ }
+
+ case 6:
+ // Open grate being closed
+ if (!map._isOutdoors) {
+ scripts.openGrate(9, 0);
+ eventsFlag = _buttonValue != 0;
+ }
+ break;
+ case 9:
+ // Closed grate being opened
+ if (!map._isOutdoors) {
+ scripts.openGrate(6, 0);
+ eventsFlag = _buttonValue != 0;
+ }
+ break;
+ case 13:
+ if (!map._isOutdoors) {
+ scripts.openGrate(1, 1);
+ eventsFlag = _buttonValue != 0;
+ }
+ break;
+ default:
+ break;
+ }
+ if (eventsFlag) {
+ scripts.checkEvents();
+ if (_vm->shouldQuit())
+ return;
+ }
+ }
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_TAB:
+ // Stop mosters doing any movement
+ combat._moveMonsters = false;
+ if (ControlPanel::show(_vm) == -1) {
+ _vm->_quitMode = 2;
+ } else {
+ combat._moveMonsters = 1;
+ }
+ break;
+
+ case Common::KEYCODE_SPACE:
+ case Common::KEYCODE_w:
+ // Wait one turn
+ chargeStep();
+ combat.moveMonsters();
+ _upDoorText = false;
+ _flipDefaultGround = !_flipDefaultGround;
+ _flipGround = !_flipGround;
+
+ stepTime();
+ break;
+
+ case (Common::KBD_CTRL << 16) | Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP4:
+ if (checkMoveDirection((Common::KBD_CTRL << 16) | Common::KEYCODE_LEFT)) {
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ --party._mazePosition.x;
+ break;
+ case DIR_SOUTH:
+ ++party._mazePosition.x;
+ break;
+ case DIR_EAST:
+ ++party._mazePosition.y;
+ break;
+ case DIR_WEST:
+ --party._mazePosition.y;
+ break;
+ default:
+ break;
+ }
+
+ chargeStep();
+ _isAnimReset = true;
+ party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
+ _flipSky = !_flipSky;
+ stepTime();
+ }
+ break;
+
+ case (Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP6:
+ if (checkMoveDirection((Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT)) {
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ ++party._mazePosition.x;
+ break;
+ case DIR_SOUTH:
+ --party._mazePosition.x;
+ break;
+ case DIR_EAST:
+ --party._mazePosition.y;
+ break;
+ case DIR_WEST:
+ ++party._mazePosition.y;
+ break;
+ default:
+ break;
+ }
+
+ chargeStep();
+ _isAnimReset = true;
+ party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
+ _flipSky = !_flipSky;
+ stepTime();
+ }
+ break;
+
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP7:
+ party._mazeDirection = (Direction)((int)party._mazeDirection - 1);
+ _isAnimReset = true;
+ party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
+ _flipSky = !_flipSky;
+ stepTime();
+ break;
+
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP9:
+ party._mazeDirection = (Direction)((int)party._mazeDirection + 1);
+ _isAnimReset = true;
+ party._mazeDirection = (Direction)((int)party._mazeDirection & 3);
+ _flipSky = !_flipSky;
+ stepTime();
+ break;
+
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ if (checkMoveDirection(Common::KEYCODE_UP)) {
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ ++party._mazePosition.y;
+ break;
+ case DIR_SOUTH:
+ --party._mazePosition.y;
+ break;
+ case DIR_EAST:
+ ++party._mazePosition.x;
+ break;
+ case DIR_WEST:
+ --party._mazePosition.x;
+ break;
+ default:
+ break;
+ }
+
+ chargeStep();
+ stepTime();
+ }
+ break;
+
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ if (checkMoveDirection(Common::KEYCODE_DOWN)) {
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ --party._mazePosition.y;
+ break;
+ case DIR_SOUTH:
+ ++party._mazePosition.y;
+ break;
+ case DIR_EAST:
+ --party._mazePosition.x;
+ break;
+ case DIR_WEST:
+ ++party._mazePosition.x;
+ break;
+ default:
+ break;
+ }
+
+ chargeStep();
+ stepTime();
+ }
+ break;
+
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ CharacterInfo::show(_vm, _buttonValue);
+ if (party._stepped)
+ combat.moveMonsters();
+ }
+ break;
+
+ case Common::KEYCODE_EQUALS:
+ case Common::KEYCODE_KP_EQUALS:
+ // Toggle minimap
+ combat._moveMonsters = false;
+ party._automapOn = !party._automapOn;
+ combat._moveMonsters = true;
+ break;
+
+ case Common::KEYCODE_b:
+ chargeStep();
+
+ if (map.getCell(2) < map.mazeData()._difficulties._wallNoPass
+ && !map._isOutdoors) {
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ ++party._mazePosition.y;
+ break;
+ case DIR_EAST:
+ ++party._mazePosition.x;
+ break;
+ case DIR_SOUTH:
+ --party._mazePosition.y;
+ break;
+ case DIR_WEST:
+ --party._mazePosition.x;
+ break;
+ default:
+ break;
+ }
+ chargeStep();
+ stepTime();
+ } else {
+ bash(party._mazePosition, party._mazeDirection);
+ }
+ break;
+
+ case Common::KEYCODE_c: {
+ // Cast spell
+ if (_tillMove) {
+ combat.moveMonsters();
+ draw3d(true);
+ }
+
+ int result = 0;
+ Character *c = &party._activeParty[(spells._lastCaster < 0 ||
+ spells._lastCaster >= (int)party._activeParty.size()) ?
+ (int)party._activeParty.size() - 1 : spells._lastCaster];
+ do {
+ int spellId = CastSpell::show(_vm, c);
+ if (spellId == -1 || c == nullptr)
+ break;
+
+ result = spells.castSpell(c, (MagicSpell)spellId);
+ } while (result != -1);
+
+ if (result == 1) {
+ chargeStep();
+ doStepCode();
+ }
+ break;
+ }
+
+ case Common::KEYCODE_i:
+ // Show Info dialog
+ combat._moveMonsters = false;
+ InfoDialog::show(_vm);
+ combat._moveMonsters = true;
+ break;
+
+ case Common::KEYCODE_m:
+ // Show map dialog
+ AutoMapDialog::show(_vm);
+ break;
+
+ case Common::KEYCODE_q:
+ // Show the quick reference dialog
+ QuickReferenceDialog::show(_vm);
+ break;
+
+ case Common::KEYCODE_r:
+ // Rest
+ rest();
+ break;
+
+ case Common::KEYCODE_s:
+ // Shoot
+ if (!party.canShoot()) {
+ sound.playFX(21);
+ } else {
+ if (_tillMove) {
+ combat.moveMonsters();
+ draw3d(true);
+ }
+
+ if (combat._attackMonsters[0] != -1 || combat._attackMonsters[1] != -1
+ || combat._attackMonsters[2] != -1) {
+ if ((_vm->_mode == MODE_1 || _vm->_mode == MODE_SLEEPING)
+ && !combat._monstersAttacking && !_charsShooting) {
+ doCombat();
+ }
+ }
+
+ combat.shootRangedWeapon();
+ chargeStep();
+ doStepCode();
+ }
+ break;
+
+ case Common::KEYCODE_v:
+ // Show the quests dialog
+ Quests::show(_vm);
+ break;
+
+ case Common::KEYCODE_x:
+ // ****DEBUG***
+ PartyDialog::show(_vm); //***DEBUG****
+ default:
+ break;
+ }
+}
+
+void Interface::chargeStep() {
+ if (!_vm->_party->_partyDead) {
+ _vm->_party->changeTime(_vm->_map->_isOutdoors ? 10 : 1);
+ if (_tillMove) {
+ _vm->_combat->moveMonsters();
+ }
+
+ _tillMove = 3;
+ }
+}
+
+void Interface::stepTime() {
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ doStepCode();
+
+ if (++party._ctr24 == 24)
+ party._ctr24 = 0;
+
+ if (_buttonValue != Common::KEYCODE_SPACE && _buttonValue != Common::KEYCODE_w) {
+ _steppingFX ^= 1;
+ sound.playFX(_steppingFX + 7);
+ }
+
+ _upDoorText = false;
+ _flipDefaultGround = !_flipDefaultGround;
+ _flipGround = !_flipGround;
+}
+
+void Interface::doStepCode() {
+ Combat &combat = *_vm->_combat;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Scripts &scripts = *_vm->_scripts;
+ int damage = 0;
+
+ party._stepped = true;
+ _upDoorText = false;
+
+ map.getCell(2);
+ int surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
+
+ switch (surfaceId) {
+ case SURFTYPE_SPACE:
+ // Wheeze.. can't breathe in space! Explosive decompression, here we come
+ party._partyDead = true;
+ break;
+ case SURFTYPE_LAVA:
+ // It burns, it burns!
+ damage = 100;
+ party._damageType = DT_FIRE;
+ break;
+ case SURFTYPE_SKY:
+ // We can fly, we can.. oh wait, we can't!
+ damage = 100;
+ party._damageType = DT_PHYSICAL;
+ _falling = true;
+ break;
+ case SURFTYPE_DESERT:
+ // Without navigation skills, simulate getting lost by adding extra time
+ if (map._isOutdoors && !party.checkSkill(NAVIGATOR))
+ party.addTime(170);
+ break;
+ case SURFTYPE_CLOUD:
+ if (!party._levitateActive) {
+ party._damageType = DT_PHYSICAL;
+ _falling = true;
+ damage = 100;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (_vm->_files->_isDarkCc && party._gameFlags[374]) {
+ _falling = false;
+ } else {
+ if (_falling)
+ startFalling(false);
+
+ if ((party._mazePosition.x & 16) || (party._mazePosition.y & 16)) {
+ if (map._isOutdoors)
+ map.getNewMaze();
+ }
+
+ if (damage) {
+ _flipGround = !_flipGround;
+ draw3d(true);
+
+ int oldVal = scripts._v2;
+ scripts._v2 = 0;
+ combat.giveCharDamage(damage, combat._damageType, 0);
+
+ scripts._v2 = oldVal;
+ _flipGround = !_flipGround;
+ } else if (party._partyDead) {
+ draw3d(true);
+ }
+ }
+}
+
+void Interface::startFalling(bool flag) {
+ Combat &combat = *_vm->_combat;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Scripts &scripts = *_vm->_scripts;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ if (isDarkCc && party._gameFlags[374]) {
+ _falling = 0;
+ return;
+ }
+
+ _falling = false;
+ draw3d(true);
+ _falling = 2;
+ draw3d(false);
+
+ if (flag) {
+ if (!isDarkCc || party._fallMaze != 0) {
+ party._mazeId = party._fallMaze;
+ party._mazePosition = party._fallPosition;
+ }
+ }
+
+ _falling = true;
+ map.load(party._mazeId);
+ if (flag) {
+ if (((party._mazePosition.x & 16) || (party._mazePosition.y & 16)) &&
+ map._isOutdoors) {
+ map.getNewMaze();
+ }
+ }
+
+ if (isDarkCc) {
+ switch (party._mazeId - 25) {
+ case 0:
+ case 26:
+ case 27:
+ case 28:
+ case 29:
+ party._mazeId = 24;
+ party._mazePosition = Common::Point(11, 9);
+ break;
+ case 1:
+ case 30:
+ case 31:
+ case 32:
+ case 33:
+ party._mazeId = 12;
+ party._mazePosition = Common::Point(6, 15);
+ break;
+ case 2:
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ case 51:
+ case 52:
+ case 53:
+ party._mazeId = 15;
+ party._mazePosition = Common::Point(4, 12);
+ party._mazeDirection = DIR_SOUTH;
+ break;
+ case 40:
+ case 41:
+ party._mazeId = 14;
+ party._mazePosition = Common::Point(8, 3);
+ break;
+ case 44:
+ case 45:
+ party._mazeId = 1;
+ party._mazePosition = Common::Point(8, 7);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ case 49:
+ party._mazeId = 12;
+ party._mazePosition = Common::Point(11, 13);
+ party._mazeDirection = DIR_SOUTH;
+ break;
+ case 57:
+ case 58:
+ case 59:
+ party._mazeId = 5;
+ party._mazePosition = Common::Point(12, 7);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ case 60:
+ party._mazeId = 6;
+ party._mazePosition = Common::Point(12, 3);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ default:
+ party._mazeId = 23;
+ party._mazePosition = Common::Point(12, 10);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ }
+ } else {
+ if (party._mazeId > 89 && party._mazeId < 113) {
+ party._mazeId += 168;
+ } else {
+ switch (party._mazeId - 25) {
+ case 0:
+ party._mazeId = 89;
+ party._mazePosition = Common::Point(2, 14);
+ break;
+ case 1:
+ party._mazeId = 109;
+ party._mazePosition = Common::Point(13, 14);
+ break;
+ case 2:
+ party._mazeId = 112;
+ party._mazePosition = Common::Point(13, 3);
+ break;
+ case 3:
+ party._mazeId = 92;
+ party._mazePosition = Common::Point(2, 3);
+ break;
+ case 12:
+ case 13:
+ party._mazeId = 14;
+ party._mazePosition = Common::Point(10, 2);
+ break;
+ case 16:
+ case 17:
+ case 18:
+ party._mazeId = 4;
+ party._mazePosition = Common::Point(5, 14);
+ break;
+ case 20:
+ case 21:
+ case 22:
+ party._mazeId = 21;
+ party._mazePosition = Common::Point(9, 11);
+ break;
+ case 24:
+ case 25:
+ case 26:
+ party._mazeId = 1;
+ party._mazePosition = Common::Point(10, 4);
+ break;
+ case 28:
+ case 29:
+ case 30:
+ case 31:
+ party._mazeId = 26;
+ party._mazePosition = Common::Point(12, 10);
+ break;
+ case 32:
+ case 33:
+ case 34:
+ case 35:
+ party._mazeId = 3;
+ party._mazePosition = Common::Point(4, 9);
+ break;
+ case 36:
+ case 37:
+ case 38:
+ case 39:
+ party._mazeId = 16;
+ party._mazePosition = Common::Point(2, 7);
+ break;
+ case 40:
+ case 41:
+ case 42:
+ case 43:
+ party._mazeId = 23;
+ party._mazePosition = Common::Point(10, 9);
+ break;
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ party._mazeId = 13;
+ party._mazePosition = Common::Point(2, 10);
+ break;
+ case 103:
+ case 104:
+ map._loadDarkSide = false;
+ party._mazeId = 8;
+ party._mazePosition = Common::Point(11, 15);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ case 105:
+ party._mazeId = 24;
+ party._mazePosition = Common::Point(11, 9);
+ break;
+ case 106:
+ party._mazeId = 12;
+ party._mazePosition = Common::Point(6, 15);
+ break;
+ case 107:
+ party._mazeId = 15;
+ party._mazePosition = Common::Point(4, 12);
+ break;
+ default:
+ party._mazeId = 29;
+ party._mazePosition = Common::Point(25, 21);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ }
+ }
+ }
+
+ _flipGround ^= 1;
+ draw3d(true);
+ int tempVal = scripts._v2;
+ scripts._v2 = 0;
+ combat.giveCharDamage(party._fallDamage, DT_PHYSICAL, 0);
+ scripts._v2 = tempVal;
+
+ _flipGround ^= 1;
+}
+
+bool Interface::checkMoveDirection(int key) {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ Direction dir = party._mazeDirection;
+
+ switch (key) {
+ case (Common::KBD_CTRL << 16) | Common::KEYCODE_LEFT:
+ party._mazeDirection = (party._mazeDirection == DIR_NORTH) ? DIR_WEST :
+ (Direction)(party._mazeDirection - 1);
+ break;
+ case (Common::KBD_CTRL << 16) | Common::KEYCODE_RIGHT:
+ party._mazeDirection = (party._mazeDirection == DIR_WEST) ? DIR_NORTH :
+ (Direction)(party._mazeDirection + 1);
+ break;
+ case Common::KEYCODE_DOWN:
+ party._mazeDirection = (Direction)((int)party._mazeDirection ^ 2);
+ break;
+ default:
+ break;
+ }
+
+ map.getCell(7);
+ int startSurfaceId = map._currentSurfaceId;
+ int surfaceId;
+
+ if (map._isOutdoors) {
+ party._mazeDirection = dir;
+
+ switch (map._currentWall) {
+ case 5:
+ if (_vm->_files->_isDarkCc)
+ goto check;
+
+ // Deliberate FAll-through
+ case 0:
+ case 2:
+ case 4:
+ case 8:
+ case 11:
+ case 13:
+ case 14:
+ surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
+ if (surfaceId == SURFTYPE_WATER) {
+ if (party.checkSkill(SWIMMING) || party._walkOnWaterActive)
+ return true;
+ } else if (surfaceId == SURFTYPE_DWATER) {
+ if (party._walkOnWaterActive)
+ return true;
+ } else if (surfaceId != SURFTYPE_SPACE) {
+ return true;
+ }
+
+ sound.playFX(21);
+ return false;
+
+ case 1:
+ case 7:
+ case 9:
+ case 10:
+ case 12:
+ check:
+ if (party.checkSkill(MOUNTAINEER))
+ return true;
+
+ sound.playFX(21);
+ return false;
+
+ default:
+ break;
+ }
+ } else {
+ surfaceId = map.getCell(2);
+ if (surfaceId >= map.mazeData()._difficulties._wallNoPass) {
+ party._mazeDirection = dir;
+ sound.playFX(46);
+ return false;
+ } else {
+ party._mazeDirection = dir;
+
+ if (startSurfaceId == SURFTYPE_SWAMP || party.checkSkill(SWIMMING) ||
+ party._walkOnWaterActive) {
+ sound.playFX(46);
+ return false;
+ } else {
+ if (_buttonValue == Common::KEYCODE_UP && _wo[107]) {
+ _openDoor = true;
+ sound.playFX(47);
+ draw3d(true);
+ _openDoor = false;
+ }
+ return true;
+ }
+ }
+ }
+
+ return true;
+}
+
+void Interface::rest() {
+ EventsManager &events = *_vm->_events;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+
+ map.cellFlagLookup(party._mazePosition);
+
+ if ((map._currentCantRest || (map.mazeData()._mazeFlags & RESTRICTION_REST))
+ && _vm->_mode != MODE_12) {
+ ErrorScroll::show(_vm, TOO_DANGEROUS_TO_REST, WT_NONFREEZED_WAIT);
+ } else {
+ // Check whether any character is in danger of dying
+ bool dangerFlag = false;
+ for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
+ for (int attrib = MIGHT; attrib <= LUCK; ++attrib) {
+ if (party._activeParty[charIdx].getStat((Attribute)attrib) < 1)
+ dangerFlag = true;
+ }
+ }
+
+ if (dangerFlag) {
+ if (!Confirm::show(_vm, SOME_CHARS_MAY_DIE))
+ return;
+ }
+
+ // Mark all the players as being asleep
+ for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
+ party._activeParty[charIdx]._conditions[ASLEEP] = 1;
+ }
+ drawParty(true);
+
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_SLEEPING;
+
+ if (oldMode == MODE_12) {
+ party.changeTime(8 * 60);
+ } else {
+ for (int idx = 0; idx < 10; ++idx) {
+ chargeStep();
+ draw3d(true);
+
+ if (_vm->_mode == MODE_1) {
+ _vm->_mode = oldMode;
+ return;
+ }
+ }
+
+ party.changeTime(map._isOutdoors ? 380 : 470);
+ }
+
+ if (_vm->getRandomNumber(1, 20) == 1) {
+ // Show dream
+ screen.saveBackground();
+ screen.fadeOut(4);
+ events.hideCursor();
+
+ screen.loadBackground("scene1.raw");
+ screen._windows[0].update();
+ screen.fadeIn(4);
+
+ events.updateGameCounter();
+ while (!_vm->shouldQuit() && events.timeElapsed() < 7)
+ events.pollEventsAndWait();
+
+ File f("dreams2.voc");
+ sound.playSample(&f, 1);
+ while (!_vm->shouldQuit() && sound.playSample(1, 0))
+ events.pollEventsAndWait();
+ f.close();
+
+ f.openFile("laff1.voc");
+ sound.playSample(&f, 1);
+ while (!_vm->shouldQuit() && sound.playSample(1, 0))
+ events.pollEventsAndWait();
+ f.close();
+
+ events.updateGameCounter();
+ while (!_vm->shouldQuit() && events.timeElapsed() < 7)
+ events.pollEventsAndWait();
+
+ screen.fadeOut(4);
+ events.setCursor(0);
+ screen.restoreBackground();
+ screen._windows[0].update();
+
+ screen.fadeIn(4);
+ }
+
+ party.resetTemps();
+
+ // Wake up the party
+ bool starving = false;
+ int foodConsumed = 0;
+ for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
+ Character &c = party._activeParty[charIdx];
+ c._conditions[ASLEEP] = 0;
+
+ if (party._food == 0) {
+ starving = true;
+ } else {
+ party._rested = true;
+ Condition condition = c.worstCondition();
+
+ if (condition < DEAD || condition > ERADICATED) {
+ --party._food;
+ ++foodConsumed;
+ party._heroism = 0;
+ party._holyBonus = 0;
+ party._powerShield = 0;
+ party._blessed = 0;
+ c._conditions[UNCONSCIOUS] = 0;
+ c._currentHp = c.getMaxHP();
+ c._currentSp = c.getMaxSP();
+ }
+ }
+ }
+
+ drawParty(true);
+ _vm->_mode = oldMode;
+ doStepCode();
+ draw3d(true);
+
+ ErrorScroll::show(_vm, Common::String::format(REST_COMPLETE,
+ starving ? PARTY_IS_STARVING : HIT_SPELL_POINTS_RESTORED,
+ foodConsumed));
+ party.checkPartyDead();
+ }
+}
+
+void Interface::bash(const Common::Point &pt, Direction direction) {
+ EventsManager &events = *_vm->_events;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+
+ if (map._isOutdoors)
+ return;
+
+ sound.playFX(31);
+
+ uint charNum1 = 0, charNum2 = 0;
+ for (uint charIdx = 0; charIdx < party._activeParty.size(); ++charIdx) {
+ Character &c = party._activeParty[charIdx];
+ Condition condition = c.worstCondition();
+
+ if (!(condition == ASLEEP || (condition >= PARALYZED &&
+ condition <= ERADICATED))) {
+ if (charNum1) {
+ charNum2 = charIdx + 1;
+ break;
+ } else {
+ charNum1 = charIdx + 1;
+ }
+ }
+ }
+
+ party._activeParty[charNum1 - 1].subtractHitPoints(2);
+ _charPowSprites.draw(screen._windows[0], 0,
+ Common::Point(CHAR_FACES_X[charNum1 - 1], 150));
+ screen._windows[0].update();
+
+ if (charNum2) {
+ party._activeParty[charNum2 - 1].subtractHitPoints(2);
+ _charPowSprites.draw(screen._windows[0], 0,
+ Common::Point(CHAR_FACES_X[charNum2 - 1], 150));
+ screen._windows[0].update();
+ }
+
+ int cell = map.mazeLookup(Common::Point(pt.x + SCREEN_POSITIONING_X[direction][7],
+ pt.y + SCREEN_POSITIONING_Y[direction][7]), 0, 0xffff);
+ if (cell != INVALID_CELL) {
+ int v = map.getCell(2);
+
+ if (v == 7) {
+ ++_wo[207];
+ ++_wo[267];
+ ++_wo[287];
+ } else if (v == 14) {
+ ++_wo[267];
+ ++_wo[287];
+ } else if (v == 15) {
+ ++_wo[287];
+ } else {
+ int might = party._activeParty[charNum1 - 1].getStat(MIGHT) +
+ _vm->getRandomNumber(1, 30);
+ if (charNum2)
+ might += party._activeParty[charNum2 - 1].getStat(MIGHT);
+
+ int bashThreshold = (v == 9) ? map.mazeData()._difficulties._bashGrate :
+ map.mazeData()._difficulties._bashWall;
+ if (might >= bashThreshold) {
+ // Remove the wall on the current cell, and the reverse wall
+ // on the cell we're bashing through to
+ map.setWall(pt, direction, 3);
+ switch (direction) {
+ case DIR_NORTH:
+ map.setWall(Common::Point(pt.x, pt.y + 1), DIR_SOUTH, 3);
+ break;
+ case DIR_EAST:
+ map.setWall(Common::Point(pt.x + 1, pt.y), DIR_WEST, 3);
+ break;
+ case DIR_SOUTH:
+ map.setWall(Common::Point(pt.x, pt.y - 1), DIR_NORTH, 3);
+ break;
+ case DIR_WEST:
+ map.setWall(Common::Point(pt.x - 1, pt.y), DIR_EAST, 3);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ party.checkPartyDead();
+ events.ipause(2);
+ drawParty(true);
+}
+
+void Interface::draw3d(bool updateFlag, bool skipDelay) {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Scripts &scripts = *_vm->_scripts;
+
+ if (screen._windows[11]._enabled)
+ return;
+
+ _flipUIFrame = (_flipUIFrame + 1) % 4;
+ if (_flipUIFrame == 0)
+ _flipWater = !_flipWater;
+ if (_tillMove && (_vm->_mode == MODE_1 || _vm->_mode == MODE_COMBAT) &&
+ !combat._monstersAttacking && combat._moveMonsters) {
+ if (--_tillMove == 0)
+ combat.moveMonsters();
+ }
+
+ // Draw the map
+ drawMap();
+
+ // Draw the minimap
+ drawMiniMap();
+
+ if (_falling == 1)
+ handleFalling();
+
+ if (_falling == 2) {
+ screen.saveBackground(1);
+ }
+
+ assembleBorder();
+
+ // Draw any on-screen text if flagged to do so
+ if (_upDoorText && combat._attackMonsters[0] == -1) {
+ screen._windows[3].writeString(_screenText);
+ }
+
+ if (updateFlag) {
+ screen._windows[1].update();
+ screen._windows[3].update();
+ }
+
+ if (combat._attackMonsters[0] != -1 || combat._attackMonsters[1] != -1
+ || combat._attackMonsters[2] != -1) {
+ if ((_vm->_mode == MODE_1 || _vm->_mode == MODE_SLEEPING) &&
+ !combat._monstersAttacking && !_charsShooting && combat._moveMonsters) {
+ doCombat();
+ if (scripts._eventSkipped)
+ scripts.checkEvents();
+ }
+ }
+
+ party._stepped = false;
+ if (_vm->_mode == MODE_9) {
+ // TODO: Save current scripts data?
+ }
+
+ if (!skipDelay)
+ events.wait(2);
+}
+
+void Interface::handleFalling() {
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Window &w = screen._windows[3];
+ File voc1("scream.voc");
+ File voc2("unnh.voc");
+ saveFall();
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ party._activeParty[idx]._faceSprites->draw(screen._windows[0], 4,
+ Common::Point(CHAR_FACES_X[idx], 150));
+ }
+
+ screen._windows[33].update();
+ sound.playFX(11);
+ sound.playSample(&voc1, 0);
+
+ for (int idx = 0, incr = 2; idx < 133; ++incr, idx += incr) {
+ fall(idx);
+ assembleBorder();
+ w.update();
+ }
+
+ fall(132);
+ assembleBorder();
+ w.update();
+
+ sound.playSample(nullptr, 0);
+ sound.playSample(&voc2, 0);
+ sound.playFX(31);
+
+ fall(127);
+ assembleBorder();
+ w.update();
+
+ fall(132);
+ assembleBorder();
+ w.update();
+
+ fall(129);
+ assembleBorder();
+ w.update();
+
+ fall(132);
+ assembleBorder();
+ w.update();
+
+ shake(10);
+}
+
+void Interface::saveFall() {
+ // TODO
+}
+
+void Interface::fall(int v) {
+ // TODO
+}
+
+void Interface::shake(int time) {
+ // TODO
+}
+
+void Interface::drawMiniMap() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+ Window &window1 = screen._windows[1];
+
+ if (screen._windows[2]._enabled || screen._windows[10]._enabled)
+ return;
+ if (!party._automapOn && !party._wizardEyeActive) {
+ // Draw the Might & Magic logo
+ res._globalSprites.draw(window1, 5, Common::Point(232, 9));
+ return;
+ }
+
+ int v, frame;
+ int frame2 = _overallFrame * 2;
+ bool eyeActive = party._wizardEyeActive;
+ if (party._automapOn)
+ party._wizardEyeActive = false;
+
+ if (map._isOutdoors) {
+ res._globalSprites.draw(window1, 15, Common::Point(237, 12));
+
+ for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
+ for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
+ 4);
+ frame = map.mazeDataCurrent()._surfaceTypes[v];
+
+ if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, frame, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
+ for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
+ 4);
+ frame = map.mazeData()._wallTypes[v];
+
+ if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, frame + 16, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
+ for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
+ 4);
+
+ if (v != -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, v + 32, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ // Draw the direction arrow
+ res._globalSprites.draw(window1, party._mazeDirection + 1,
+ Common::Point(267, 36));
+ }
+ else {
+ frame2 = (frame2 + 2) % 8;
+
+ // First draw the default surface bases for each cell to show
+ for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
+ for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
+ 0, 0xffff);
+
+ if (v != INVALID_CELL && (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, 0, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ // Draw correct surface bases for revealed tiles
+ for (int rowNum = 0, yp = 17, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
+ for (int colNum = 0, xp = 242, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
+ 0, 0xffff);
+ int surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
+
+ if (v != INVALID_CELL && map._currentSurfaceId &&
+ (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, surfaceId + 36, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ v = map.mazeLookup(Common::Point(party._mazePosition.x - 4, party._mazePosition.y + 4), 0xffff, 0);
+ if (v != INVALID_CELL && map._currentSurfaceId &&
+ (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1,
+ map.mazeData()._surfaceTypes[map._currentSurfaceId] + 36,
+ Common::Point(232, 9));
+ }
+
+ // Handle drawing surface sprites partially clipped at the left edge
+ for (int rowNum = 0, yp = 17, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, --yDiff, yp += 8) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x - 4, party._mazePosition.y + yDiff),
+ 0, 0xffff);
+
+ if (v != INVALID_CELL && map._currentSurfaceId &&
+ (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1,
+ map.mazeData()._surfaceTypes[map._currentSurfaceId] + 36,
+ Common::Point(232, yp));
+ }
+ }
+
+ // Handle drawing surface sprites partially clipped at the top edge
+ for (int colNum = 0, xp = 242, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, ++xDiff, xp += 8) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + 4),
+ 0, 0xffff);
+
+ if (v != INVALID_CELL && map._currentSurfaceId &&
+ (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1,
+ map.mazeData()._surfaceTypes[map._currentSurfaceId] + 36,
+ Common::Point(xp, 9));
+ }
+ }
+
+ //
+ for (int idx = 0, xp = 237, yp = 60, xDiff = -3; idx < MINIMAP_SIZE;
+ ++idx, ++xDiff, xp += 10, yp -= 8) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x - 4, party._mazePosition.y - 3 + idx),
+ 12, 0xffff);
+
+ switch (v) {
+ case 1:
+ frame = 18;
+ break;
+ case 3:
+ frame = 22;
+ break;
+ case 4:
+ case 13:
+ frame = 16;
+ break;
+ case 5:
+ case 8:
+ frame = 2;
+ break;
+ case 6:
+ frame = 30;
+ break;
+ case 7:
+ frame = 32;
+ break;
+ case 9:
+ frame = 24;
+ break;
+ case 10:
+ frame = 28;
+ break;
+ case 11:
+ frame = 14;
+ break;
+ case 12:
+ frame = frame2 + 4;
+ break;
+ case 14:
+ frame = 24;
+ break;
+ case 15:
+ frame = 26;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive))
+ map._tileSprites.draw(window1, frame, Common::Point(222, yp));
+
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x - 3 + idx, party._mazePosition.y + 4),
+ 0);
+
+ switch (v) {
+ case 1:
+ frame = 19;
+ break;
+ case 2:
+ frame = 35;
+ break;
+ case 3:
+ frame = 23;
+ break;
+ case 4:
+ case 13:
+ frame = 17;
+ break;
+ case 5:
+ case 8:
+ frame = 3;
+ break;
+ case 6:
+ frame = 31;
+ break;
+ case 7:
+ frame = 33;
+ break;
+ case 9:
+ frame = 21;
+ break;
+ case 10:
+ frame = 29;
+ break;
+ case 11:
+ frame = 15;
+ break;
+ case 12:
+ frame = frame2 + 5;
+ break;
+ case 14:
+ frame = 25;
+ break;
+ case 15:
+ frame = 27;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive))
+ map._tileSprites.draw(window1, frame, Common::Point(xp, 4));
+ }
+
+ // Draw the front/back walls of cells in the minimap
+ for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE;
+ ++rowNum, --yDiff, yp += 8) {
+ for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE;
+ ++colNum, ++xDiff, xp += 10) {
+ if (colNum == 4 && rowNum == 4) {
+ // Center of the minimap. Draw the direction arrow
+ res._globalSprites.draw(window1, party._mazeDirection + 1,
+ Common::Point(272, 40));
+ }
+
+ v = map.mazeLookup(Common::Point(party._mazePosition.x + xDiff,
+ party._mazePosition.y + yDiff), 12, 0xffff);
+ switch (v) {
+ case 1:
+ frame = 18;
+ break;
+ case 3:
+ frame = 22;
+ break;
+ case 4:
+ case 13:
+ frame = 16;
+ break;
+ case 5:
+ case 8:
+ frame = 2;
+ break;
+ case 6:
+ frame = 30;
+ break;
+ case 7:
+ frame = 32;
+ break;
+ case 9:
+ frame = 20;
+ break;
+ case 10:
+ frame = 28;
+ break;
+ case 11:
+ frame = 14;
+ break;
+ case 12:
+ frame = frame2 + 4;
+ break;
+ case 14:
+ frame = 24;
+ break;
+ case 15:
+ frame = 26;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (frame != -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, frame, Common::Point(xp, yp));
+ }
+
+ v = map.mazeLookup(Common::Point(party._mazePosition.x + xDiff,
+ party._mazePosition.y + yDiff), 12, 0xffff);
+ switch (v) {
+ case 1:
+ frame = 19;
+ break;
+ case 2:
+ frame = 35;
+ break;
+ case 3:
+ frame = 23;
+ break;
+ case 4:
+ case 13:
+ frame = 17;
+ break;
+ case 5:
+ case 8:
+ frame = 3;
+ break;
+ case 6:
+ frame = 31;
+ break;
+ case 7:
+ frame = 33;
+ break;
+ case 9:
+ frame = 21;
+ break;
+ case 10:
+ frame = 29;
+ break;
+ case 11:
+ frame = 15;
+ break;
+ case 12:
+ frame = frame2 + 5;
+ break;
+ case 14:
+ frame = 25;
+ break;
+ case 15:
+ frame = 27;
+ break;
+ default:
+ frame = -1;
+ break;
+ }
+
+ if (v == -1 && (map._currentSteppedOn || party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, frame, Common::Point(xp, yp));
+ }
+ }
+ }
+
+ // Draw the top of blocked/wall cells on the map
+ for (int rowNum = 0, yp = 12, yDiff = 3; rowNum < MINIMAP_SIZE; ++rowNum, yp += 8, --yDiff) {
+ for (int colNum = 0, xp = 237, xDiff = -3; colNum < MINIMAP_SIZE; ++colNum, xp += 10, ++xDiff) {
+ v = map.mazeLookup(
+ Common::Point(party._mazePosition.x + xDiff, party._mazePosition.y + yDiff),
+ 0, 0xffff);
+
+ if (v == INVALID_CELL || (!map._currentSteppedOn && !party._wizardEyeActive)) {
+ map._tileSprites.draw(window1, 1, Common::Point(xp, yp));
+ }
+ }
+ }
+ }
+
+ // Draw outer rectangle around the automap
+ res._globalSprites.draw(window1, 6, Common::Point(223, 3));
+ party._wizardEyeActive = eyeActive;
+}
+
+void Interface::assembleBorder() {
+ Combat &combat = *_vm->_combat;
+ Resources &res = *_vm->_resources;
+ Screen &screen = *_vm->_screen;
+
+ // Draw the outer frame
+ res._globalSprites.draw(screen._windows[0], 0, Common::Point(8, 8));
+
+ // Draw the animating bat character on the left screen edge to indicate
+ // that the party is being levitated
+ _borderSprites.draw(screen._windows[0], _vm->_party->_levitateActive ? _levitateUIFrame + 16 : 16,
+ Common::Point(0, 82));
+ _levitateUIFrame = (_levitateUIFrame + 1) % 12;
+
+ // Draw UI element to indicate whether can spot hidden doors
+ _borderSprites.draw(screen,
+ (_thinWall && _vm->_party->checkSkill(SPOT_DOORS)) ? _spotDoorsUIFrame + 28 : 28,
+ Common::Point(194, 91));
+ _spotDoorsUIFrame = (_spotDoorsUIFrame + 1) % 12;
+
+ // Draw UI element to indicate whether can sense danger
+ _borderSprites.draw(screen,
+ (combat._dangerPresent && _vm->_party->checkSkill(DANGER_SENSE)) ? _spotDoorsUIFrame + 40 : 40,
+ Common::Point(107, 9));
+ _dangerSenseUIFrame = (_dangerSenseUIFrame + 1) % 12;
+
+ // Handle the face UI elements for indicating clairvoyance status
+ _face1UIFrame = (_face1UIFrame + 1) % 4;
+ if (_face1State == 0)
+ _face1UIFrame += 4;
+ else if (_face1State == 2)
+ _face1UIFrame = 0;
+
+ _face2UIFrame = (_face2UIFrame + 1) % 4 + 12;
+ if (_face2State == 0)
+ _face2UIFrame += 252;
+ else if (_face2State == 2)
+ _face2UIFrame = 0;
+
+ if (!_vm->_party->_clairvoyanceActive) {
+ _face1UIFrame = 0;
+ _face2UIFrame = 8;
+ }
+
+ _borderSprites.draw(screen, _face1UIFrame, Common::Point(0, 32));
+ _borderSprites.draw(screen,
+ screen._windows[10]._enabled || screen._windows[2]._enabled ?
+ 52 : _face2UIFrame,
+ Common::Point(215, 32));
+
+ // Draw resistence indicators
+ if (!screen._windows[10]._enabled && !screen._windows[2]._enabled
+ && screen._windows[38]._enabled) {
+ _fecpSprites.draw(screen, _vm->_party->_fireResistence ? 1 : 0,
+ Common::Point(2, 2));
+ _fecpSprites.draw(screen, _vm->_party->_electricityResistence ? 3 : 2,
+ Common::Point(219, 2));
+ _fecpSprites.draw(screen, _vm->_party->_coldResistence ? 5 : 4,
+ Common::Point(2, 134));
+ _fecpSprites.draw(screen, _vm->_party->_poisonResistence ? 7 : 6,
+ Common::Point(219, 134));
+ } else {
+ _fecpSprites.draw(screen, _vm->_party->_fireResistence ? 9 : 8,
+ Common::Point(8, 8));
+ _fecpSprites.draw(screen, _vm->_party->_electricityResistence ? 10 : 11,
+ Common::Point(219, 8));
+ _fecpSprites.draw(screen, _vm->_party->_coldResistence ? 12 : 13,
+ Common::Point(8, 134));
+ _fecpSprites.draw(screen, _vm->_party->_poisonResistence ? 14 : 15,
+ Common::Point(219, 134));
+ }
+
+ // Draw UI element for blessed
+ _blessSprites.draw(screen, 16, Common::Point(33, 137));
+ if (_vm->_party->_blessed) {
+ _blessedUIFrame = (_blessedUIFrame + 1) % 4;
+ _blessSprites.draw(screen, _blessedUIFrame, Common::Point(33, 137));
+ }
+
+ // Draw UI element for power shield
+ if (_vm->_party->_powerShield) {
+ _powerShieldUIFrame = (_powerShieldUIFrame + 1) % 4;
+ _blessSprites.draw(screen, _powerShieldUIFrame + 4,
+ Common::Point(55, 137));
+ }
+
+ // Draw UI element for holy bonus
+ if (_vm->_party->_holyBonus) {
+ _holyBonusUIFrame = (_holyBonusUIFrame + 1) % 4;
+ _blessSprites.draw(screen, _holyBonusUIFrame + 8, Common::Point(160, 137));
+ }
+
+ // Draw UI element for heroism
+ if (_vm->_party->_heroism) {
+ _heroismUIFrame = (_heroismUIFrame + 1) % 4;
+ _blessSprites.draw(screen, _heroismUIFrame + 12, Common::Point(182, 137));
+ }
+
+ // Draw direction character if direction sense is active
+ if (_vm->_party->checkSkill(DIRECTION_SENSE) && !_vm->_noDirectionSense) {
+ const char *dirText = DIRECTION_TEXT_UPPER[_vm->_party->_mazeDirection];
+ Common::String msg = Common::String::format(
+ "\002""08\003""c\013""139\011""116%c\014""d\001", *dirText);
+ screen._windows[0].writeString(msg);
+ }
+
+ // Draw view frame
+ if (screen._windows[12]._enabled)
+ screen._windows[12].frame();
+}
+
+void Interface::doCombat() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ Scripts &scripts = *_vm->_scripts;
+ Spells &spells = *_vm->_spells;
+ SoundManager &sound = *_vm->_sound;
+ bool upDoorText = _upDoorText;
+ bool reloadMap = false;
+
+ _upDoorText = false;
+ combat._combatMode = COMBATMODE_2;
+ _vm->_mode = MODE_COMBAT;
+
+ _iconSprites.load("combat.icn");
+ for (int idx = 1; idx < 16; ++idx)
+ _mainList[idx]._sprites = &_iconSprites;
+
+ // Set the combat buttons
+ setMainButtons(true);
+ mainIconsPrint();
+
+ combat._combatParty.clear();
+ combat._charsGone.clear();
+ combat._charsBlocked.clear();
+ combat._charsArray1[0] = 0;
+ combat._charsArray1[1] = 0;
+ combat._charsArray1[2] = 0;
+ combat._monstersAttacking = 0;
+ combat._partyRan = false;
+
+ // Set up the combat party
+ combat.setupCombatParty();
+ combat.setSpeedTable();
+
+ // Initialize arrays for character/monster states
+ combat._charsGone.resize(combat._speedTable.size());
+ combat._charsBlocked.resize(combat._speedTable.size());
+ Common::fill(&combat._charsGone[0], &combat._charsGone[0] + combat._speedTable.size(), 0);
+ Common::fill(&combat._charsBlocked[0], &combat._charsBlocked[0] + combat._speedTable.size(), false);
+
+ combat._whosSpeed = -1;
+ combat._whosTurn = -1;
+ resetHighlight();
+
+ nextChar();
+
+ if (!party._dead) {
+ combat.setSpeedTable();
+
+ if (_tillMove) {
+ combat.moveMonsters();
+ draw3d(true);
+ }
+
+ Window &w = screen._windows[2];
+ w.open();
+ bool breakFlag = false;
+
+ while (!_vm->shouldQuit() && !breakFlag) {
+ highlightChar(combat._whosTurn);
+ combat.setSpeedTable();
+
+ // Write out the description of the monsters being battled
+ w.writeString(combat.getMonsterDescriptions());
+ _iconSprites.draw(screen, 32, Common::Point(233, combat._monsterIndex * 10 + 27),
+ 0x8010000);
+ w.update();
+
+ // Wait for keypress
+ int index = 0;
+ do {
+ events.updateGameCounter();
+ draw3d(true);
+
+ if (++index == 5 && combat._attackMonsters[0] != -1) {
+ MazeMonster &monster = map._mobData._monsters[combat._monster2Attack];
+ MonsterStruct &monsterData = *monster._monsterData;
+ sound.playFX(monsterData._fx);
+ }
+
+ do {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ } while (!_vm->shouldQuit() && events.timeElapsed() < 1 && !_buttonValue);
+ } while (!_vm->shouldQuit() && !_buttonValue);
+ if (_vm->shouldQuit())
+ return;
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_TAB:
+ // Show the control panel
+ if (ControlPanel::show(_vm) == 2) {
+ reloadMap = true;
+ breakFlag = true;
+ } else {
+ highlightChar(combat._whosTurn);
+ }
+ break;
+
+ case Common::KEYCODE_1:
+ case Common::KEYCODE_2:
+ case Common::KEYCODE_3:
+ _buttonValue -= Common::KEYCODE_1;
+ if (combat._attackMonsters[_buttonValue] != -1) {
+ combat._monster2Attack = combat._attackMonsters[_buttonValue];
+ combat._monsterIndex = _buttonValue;
+ }
+ break;
+
+ case Common::KEYCODE_a:
+ // Attack
+ combat.attack(*combat._combatParty[combat._whosTurn], RT_SINGLE);
+ nextChar();
+ break;
+
+ case Common::KEYCODE_b:
+ // Block
+ combat.block();
+ nextChar();
+ break;
+
+ case Common::KEYCODE_c: {
+ // Cast spell
+ int spellId = CastSpell::show(_vm);
+ if (spellId != -1) {
+ Character *c = combat._combatParty[combat._whosTurn];
+ spells.castSpell(c, (MagicSpell)spellId);
+ nextChar();
+ } else {
+ highlightChar(combat._combatParty[combat._whosTurn]->_rosterId);
+ }
+ break;
+ }
+
+ case Common::KEYCODE_f:
+ // Quick Fight
+ combat.quickFight();
+ nextChar();
+ break;
+
+ case Common::KEYCODE_i:
+ // Info dialog
+ InfoDialog::show(_vm);
+ highlightChar(combat._whosTurn);
+ break;
+
+ case Common::KEYCODE_o:
+ // Fight Options
+ FightOptions::show(_vm);
+ highlightChar(combat._whosTurn);
+ break;
+
+ case Common::KEYCODE_q:
+ // Quick Reference dialog
+ QuickReferenceDialog::show(_vm);
+ highlightChar(combat._whosTurn);
+ break;
+
+ case Common::KEYCODE_r:
+ // Run from combat
+ combat.run();
+ nextChar();
+
+ if (_vm->_mode == MODE_1) {
+ warning("TODO: loss of treasure");
+ party.moveToRunLocation();
+ breakFlag = true;
+ }
+ break;
+
+ case Common::KEYCODE_u: {
+ int whosTurn = combat._whosTurn;
+ ItemsDialog::show(_vm, combat._combatParty[combat._whosTurn], ITEMMODE_COMBAT);
+ if (combat._whosTurn == whosTurn) {
+ highlightChar(combat._whosTurn);
+ } else {
+ combat._whosTurn = whosTurn;
+ nextChar();
+ }
+ break;
+ }
+
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ // Show character info
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)combat._combatParty.size()) {
+ CharacterInfo::show(_vm, _buttonValue);
+ }
+ highlightChar(combat._whosTurn);
+ break;
+
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_RIGHT:
+ // Rotate party direction left or right
+ if (_buttonValue == Common::KEYCODE_LEFT) {
+ party._mazeDirection = (party._mazeDirection == DIR_NORTH) ?
+ DIR_WEST : (Direction)((int)party._mazeDirection - 1);
+ } else {
+ party._mazeDirection = (party._mazeDirection == DIR_WEST) ?
+ DIR_NORTH : (Direction)((int)party._mazeDirection + 1);
+ }
+
+ _flipSky ^= 1;
+ if (_tillMove)
+ combat.moveMonsters();
+ party._stepped = true;
+ break;
+ }
+
+ // Handling for if the combat turn is complete
+ if (combat.allHaveGone()) {
+ Common::fill(&combat._charsGone[0], &combat._charsGone[combat._charsGone.size()], false);
+ Common::fill(&combat._charsBlocked[0], &combat._charsBlocked[combat._charsBlocked.size()], false);
+ combat.setSpeedTable();
+ combat._whosTurn = -1;
+ combat._whosSpeed = -1;
+ nextChar();
+
+ for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
+ MazeMonster &monster = map._mobData._monsters[idx];
+ if (monster._spriteId == 53) {
+ warning("TODO: Monster 53's HP is altered here?!");
+ }
+ }
+
+ combat.moveMonsters();
+ setIndoorsMonsters();
+ party.changeTime(1);
+ }
+
+ if (combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1
+ && combat._attackMonsters[2] == -1) {
+ party.changeTime(1);
+ draw3d(true);
+
+ if (combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1
+ && combat._attackMonsters[2] == -1)
+ break;
+ }
+
+ party.checkPartyDead();
+ if (party._dead || _vm->_mode != MODE_COMBAT)
+ break;
+ }
+
+ _vm->_mode = MODE_1;
+ if (combat._partyRan && (combat._attackMonsters[0] != -1 ||
+ combat._attackMonsters[1] != -1 || combat._attackMonsters[2] != -1)) {
+ party.checkPartyDead();
+ if (!party._dead) {
+ party.moveToRunLocation();
+
+ for (uint idx = 0; idx < combat._combatParty.size(); ++idx) {
+ Character &c = *combat._combatParty[idx];
+ if (c.isDisabled())
+ c._conditions[DEAD] = 1;
+ }
+ }
+ }
+
+ w.close();
+ events.clearEvents();
+
+ _vm->_mode = MODE_COMBAT;
+ draw3d(true);
+ party.giveTreasure();
+ _vm->_mode = MODE_1;
+ party._stepped = true;
+ unhighlightChar();
+
+ combat.setupCombatParty();
+ drawParty(true);
+ }
+
+ _iconSprites.load("main.icn");
+ for (int idx = 1; idx < 16; ++idx)
+ _mainList[idx]._sprites = &_iconSprites;
+
+ setMainButtons();
+ mainIconsPrint();
+ combat._monster2Attack = -1;
+
+ if (upDoorText) {
+ map.cellFlagLookup(party._mazePosition);
+ if (map._currentIsEvent)
+ scripts.checkEvents();
+ }
+
+ if (reloadMap) {
+ sound.playFX(51);
+ map._loadDarkSide = _vm->getGameID() != GType_WorldOfXeen;
+ map.load(_vm->getGameID() == GType_WorldOfXeen ? 28 : 29);
+ party._mazeDirection = _vm->getGameID() == GType_WorldOfXeen ?
+ DIR_EAST : DIR_SOUTH;
+ }
+
+ combat._combatMode = COMBATMODE_1;
+}
+
+void Interface::nextChar() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+
+ if (combat.allHaveGone())
+ return;
+ if ((combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1 &&
+ combat._attackMonsters[2] == -1) || combat._combatParty.size() == 0) {
+ _vm->_mode = MODE_1;
+ return;
+ }
+
+ // Loop for potentially multiple monsters attacking until it's time
+ // for one of the party's turn
+ for (;;) {
+ // Check if party is dead
+ party.checkPartyDead();
+ if (party._dead) {
+ _vm->_mode = MODE_1;
+ break;
+ }
+
+ int idx;
+ for (idx = 0; idx < (int)combat._speedTable.size(); ++idx) {
+ if (combat._whosTurn != -1) {
+ combat._charsGone[combat._whosTurn] = true;
+ }
+
+ combat._whosSpeed = (combat._whosSpeed + 1) % combat._speedTable.size();
+ combat._whosTurn = combat._speedTable[combat._whosSpeed];
+ if (combat.allHaveGone()) {
+ idx = -1;
+ break;
+ }
+
+ if (combat._whosTurn < (int)combat._combatParty.size()) {
+ // If it's a party member, only allow them to become active if
+ // they're still conscious
+ if (combat._combatParty[idx]->isDisabledOrDead())
+ continue;
+ }
+
+ break;
+ }
+
+ if (idx == -1) {
+ if (!combat.charsCantAct())
+ return;
+
+ combat.setSpeedTable();
+ combat._whosTurn = -1;
+ combat._whosSpeed = -1;
+ Common::fill(&combat._charsGone[0], &combat._charsGone[0] + combat._charsGone.size(), 0);
+ continue;
+ }
+
+ if (combat._whosTurn < (int)combat._combatParty.size()) {
+ // It's a party character's turn now, so highlight the character
+ if (!combat.allHaveGone()) {
+ highlightChar(combat._whosTurn);
+ }
+ break;
+ } else {
+ // It's a monster's turn to attack
+ combat.doMonsterTurn(0);
+ if (!party._dead) {
+ party.checkPartyDead();
+ if (party._dead)
+ break;
+ }
+ }
+ }
+}
+
+void Interface::spellFX(Character *c) {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+
+ // Ensure there's no alraedy running effect for the given character
+ uint charIndex;
+ for (charIndex = 0; charIndex < party._activeParty.size(); ++charIndex) {
+ if (&party._activeParty[charIndex] == c)
+ break;
+ }
+ if (charIndex == party._activeParty.size() || _charFX[charIndex])
+ return;
+
+ if (screen._windows[12]._enabled)
+ screen._windows[12].close();
+
+ if (combat._combatMode == COMBATMODE_2) {
+ for (uint idx = 0; idx < combat._combatParty.size(); ++idx) {
+ if (combat._combatParty[idx]->_rosterId == c->_rosterId) {
+ charIndex = idx;
+ break;
+ }
+ }
+ }
+
+ int tillMove = _tillMove;
+ _tillMove = 0;
+ sound.playFX(20);
+
+ for (int frameNum = 0; frameNum < 4; ++frameNum) {
+ events.updateGameCounter();
+ _spellFxSprites.draw(screen, frameNum, Common::Point(
+ CHAR_FACES_X[charIndex], 150));
+
+ if (!screen._windows[11]._enabled)
+ draw3d(false);
+
+ screen._windows[0].update();
+ events.wait(screen._windows[11]._enabled ? 2 : 1);
+ }
+
+ drawParty(true);
+ _tillMove = tillMove;
+}
+
+
+} // End of namespace Xeen
diff --git a/engines/xeen/interface.h b/engines/xeen/interface.h
new file mode 100644
index 0000000000..074d7fdafb
--- /dev/null
+++ b/engines/xeen/interface.h
@@ -0,0 +1,183 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_INTERFACE_H
+#define XEEN_INTERFACE_H
+
+#include "common/scummsys.h"
+#include "xeen/dialogs.h"
+#include "xeen/interface_map.h"
+#include "xeen/party.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+class XeenEngine;
+
+#define MINIMAP_SIZE 7
+#define HILIGHT_CHAR_DISABLED -2
+
+/**
+ * Class responsible for drawing the images of the characters in the party
+ */
+class PartyDrawer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _dseFace;
+ SpriteResource _hpSprites;
+ SpriteResource _restoreSprites;
+ int _hiliteChar;
+public:
+ PartyDrawer(XeenEngine *vm);
+
+ void drawParty(bool updateFlag);
+
+ void highlightChar(int charId);
+
+ void unhighlightChar();
+
+ void resetHighlight();
+};
+
+/**
+ * Implements the main in-game interface
+ */
+class Interface: public ButtonContainer, public InterfaceMap, public PartyDrawer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _uiSprites;
+ SpriteResource _iconSprites;
+ SpriteResource _borderSprites;
+ SpriteResource _spellFxSprites;
+ SpriteResource _fecpSprites;
+ SpriteResource _blessSprites;
+ DrawStruct _mainList[16];
+
+ bool _buttonsLoaded;
+ int _steppingFX;
+ int _blessedUIFrame;
+ int _powerShieldUIFrame;
+ int _holyBonusUIFrame;
+ int _heroismUIFrame;
+ int _flipUIFrame;
+
+ void initDrawStructs();
+
+ void loadSprites();
+
+ void setupBackground();
+
+ void setMainButtons(bool combatMode = false);
+
+ void chargeStep();
+
+ /**
+ * Handles incrementing game time
+ */
+ void stepTime();
+
+ void doStepCode();
+
+ /**
+ * Check movement in the given direction
+ */
+ bool checkMoveDirection(int key);
+
+ /**
+ * Handle doing the falling
+ */
+ void handleFalling();
+
+ void saveFall();
+
+ void fall(int v);
+
+ /**
+ * Shake the screen
+ */
+ void shake(int time);
+
+ /**
+ * Draw the minimap
+ */
+ void drawMiniMap();
+
+ /**
+ * Select next character or monster to be attacking
+ */
+ void nextChar();
+public:
+ int _intrIndex1;
+ Common::String _interfaceText;
+ int _falling;
+ int _face1State, _face2State;
+ int _face1UIFrame, _face2UIFrame;
+ int _spotDoorsUIFrame;
+ int _dangerSenseUIFrame;
+ int _levitateUIFrame;
+ bool _upDoorText;
+ Common::String _screenText;
+ byte _tillMove;
+ int _charFX[6];
+public:
+ Interface(XeenEngine *vm);
+
+ virtual ~Interface() {}
+
+ void setup();
+
+ void manageCharacters(bool soundPlayed);
+
+ void startup();
+
+ void mainIconsPrint();
+
+ /**
+ * Start the party falling
+ */
+ void startFalling(bool v);
+
+ /**
+ * Waits for a keypress or click, whilst still allowing the game scene to
+ * be animated.
+ */
+ void perform();
+
+ void rest();
+
+ void bash(const Common::Point &pt, Direction direction);
+
+ void draw3d(bool updateFlag, bool skipDelay = false);
+
+ /**
+ * Draw the display borders
+ */
+ void assembleBorder();
+
+ void doCombat();
+
+ void spellFX(Character *c);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_INTERFACE_H */
diff --git a/engines/xeen/interface_map.cpp b/engines/xeen/interface_map.cpp
new file mode 100644
index 0000000000..af39f38c28
--- /dev/null
+++ b/engines/xeen/interface_map.cpp
@@ -0,0 +1,4445 @@
+/* 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 "xeen/interface.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+OutdoorDrawList::OutdoorDrawList() : _sky1(_data[0]), _sky2(_data[1]),
+ _groundSprite(_data[2]), _attackImgs1(&_data[124]), _attackImgs2(&_data[95]),
+ _attackImgs3(&_data[76]), _attackImgs4(&_data[53]), _groundTiles(&_data[3]) {
+ _data[0] = DrawStruct(0, 8, 8);
+ _data[1] = DrawStruct(1, 8, 25);
+ _data[2] = DrawStruct(0, 8, 67);
+ _data[3] = DrawStruct(0, 8, 67);
+ _data[4] = DrawStruct(0, 38, 67);
+ _data[5] = DrawStruct(0, 84, 67);
+ _data[6] = DrawStruct(0, 134, 67);
+ _data[7] = DrawStruct(0, 117, 67);
+ _data[8] = DrawStruct(0, 117, 67);
+ _data[9] = DrawStruct(0, 103, 67);
+ _data[10] = DrawStruct(0, 8, 73);
+ _data[11] = DrawStruct(0, 8, 73);
+ _data[12] = DrawStruct(0, 30, 73);
+ _data[13] = DrawStruct(0, 181, 73);
+ _data[14] = DrawStruct(0, 154, 73);
+ _data[15] = DrawStruct(0, 129, 73);
+ _data[16] = DrawStruct(0, 87, 73);
+ _data[17] = DrawStruct(0, 8, 81);
+ _data[18] = DrawStruct(0, 8, 81);
+ _data[19] = DrawStruct(0, 202, 81);
+ _data[20] = DrawStruct(0, 145, 81);
+ _data[21] = DrawStruct(0, 63, 81);
+ _data[22] = DrawStruct(0, 8, 93);
+ _data[23] = DrawStruct(0, 169, 93);
+ _data[24] = DrawStruct(0, 31, 93);
+ _data[25] = DrawStruct(0, 8, 109);
+ _data[26] = DrawStruct(0, 201, 109);
+ _data[27] = DrawStruct(0, 8, 109);
+ _data[28] = DrawStruct(1, -64, 61, 14, SPRFLAG_SCENE_CLIPPED);
+ _data[29] = DrawStruct(1, -40, 61, 14, 0);
+ _data[30] = DrawStruct(1, -16, 61, 14, 0);
+ _data[31] = DrawStruct(1, 8, 61, 14, 0);
+ _data[32] = DrawStruct(1, 128, 61, 14, SPRFLAG_HORIZ_FLIPPED | SPRFLAG_SCENE_CLIPPED);
+ _data[33] = DrawStruct(1, 104, 61, 14, SPRFLAG_HORIZ_FLIPPED);
+ _data[34] = DrawStruct(1, 80, 61, 14, SPRFLAG_HORIZ_FLIPPED);
+ _data[35] = DrawStruct(1, 56, 61, 14, SPRFLAG_HORIZ_FLIPPED);
+ _data[36] = DrawStruct(1, 32, 61, 14, 0);
+ _data[37] = DrawStruct(0, -9, 61, 14, 0);
+ _data[38] = DrawStruct(0, -58, 61, 14, 0);
+ _data[39] = DrawStruct(0, 40, 61, 14, 0);
+ _data[40] = DrawStruct(0, -82, 61, 14, 0);
+ _data[41] = DrawStruct(0, 64, 61, 14, 0);
+ _data[42] = DrawStruct(0, -41, 61, 14, 0);
+ _data[43] = DrawStruct(0, -26, 61, 14, 0);
+ _data[44] = DrawStruct(0, -34, 61, 14, 0);
+ _data[45] = DrawStruct(0, -16, 61, 14, 0);
+ _data[46] = DrawStruct(0, 23, 61, 14, 0);
+ _data[47] = DrawStruct(0, 16, 61, 14, 0);
+ _data[48] = DrawStruct(0, -58, 61, 14, 0);
+ _data[49] = DrawStruct(0, 40, 61, 14, 0);
+ _data[50] = DrawStruct(0, -17, 61, 14, 0);
+ _data[51] = DrawStruct(0, -1, 58, 14, 0);
+ _data[52] = DrawStruct(0, -9, 58, 14, 0);
+ _data[53] = DrawStruct(0, 72, 58, 12, 0);
+ _data[54] = DrawStruct(0, 72, 58, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[55] = DrawStruct(0, 69, 63, 12, 0);
+ _data[56] = DrawStruct(0, 75, 63, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[57] = DrawStruct(0, 73, 53, 12, 0);
+ _data[58] = DrawStruct(0, 71, 53, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[59] = DrawStruct(0, 80, 57, 12, 0);
+ _data[60] = DrawStruct(0, 64, 57, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[61] = DrawStruct(2, -11, 54, 8, 0);
+ _data[62] = DrawStruct(1, -21, 54, 11, 0);
+ _data[63] = DrawStruct(2, 165, 54, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[64] = DrawStruct(1, 86, 54, 11, SPRFLAG_HORIZ_FLIPPED);
+ _data[65] = DrawStruct(1, 33, 54, 11, 0);
+ _data[66] = DrawStruct(0, -8, 54, 12, 0);
+ _data[67] = DrawStruct(0, -73, 54, 12, 0);
+ _data[68] = DrawStruct(0, 57, 54, 12, 0);
+ _data[69] = DrawStruct(0, -65, 54, 12, 0);
+ _data[70] = DrawStruct(0, -81, 54, 12, 0);
+ _data[71] = DrawStruct(0, 49, 54, 12, 0);
+ _data[72] = DrawStruct(0, 65, 54, 12, 0);
+ _data[73] = DrawStruct(0, -24, 54, 12, 0);
+ _data[74] = DrawStruct(0, 9, 50, 12, 0);
+ _data[75] = DrawStruct(0, -8, 50, 12, 0);
+ _data[76] = DrawStruct(0, 72, 53, 8, 0);
+ _data[77] = DrawStruct(0, 72, 53, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[78] = DrawStruct(0, 77, 58, 8, 0);
+ _data[79] = DrawStruct(0, 67, 58, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[80] = DrawStruct(0, 81, 47, 8, 0);
+ _data[81] = DrawStruct(0, 63, 47, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[82] = DrawStruct(0, 94, 52, 8, 0);
+ _data[83] = DrawStruct(0, 50, 52, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[84] = DrawStruct(2, 8, 40);
+ _data[85] = DrawStruct(2, 146, 40, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[86] = DrawStruct(1, 32, 40, 6, 0);
+ _data[87] = DrawStruct(0, -7, 30, 7, 0);
+ _data[88] = DrawStruct(0, -112, 30, 7, SPRFLAG_SCENE_CLIPPED);
+ _data[89] = DrawStruct(0, 98, 30, 7, SPRFLAG_SCENE_CLIPPED);
+ _data[90] = DrawStruct(0, -112, 30, 8, SPRFLAG_SCENE_CLIPPED);
+ _data[91] = DrawStruct(0, 98, 30, 8, SPRFLAG_SCENE_CLIPPED);
+ _data[92] = DrawStruct(0, -38, 30, 8, 0);
+ _data[93] = DrawStruct(0, 25, 30, 8, 0);
+ _data[94] = DrawStruct(0, -7, 30, 8, 0);
+ _data[95] = DrawStruct(0, 72, 48, 4, 0);
+ _data[96] = DrawStruct(0, 72, 48, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[97] = DrawStruct(0, 85, 53, 4, 0);
+ _data[98] = DrawStruct(0, 59, 53, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[99] = DrawStruct(0, 89, 41, 4, 0);
+ _data[100] = DrawStruct(0, 55, 41, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[101] = DrawStruct(0, 106, 47, 4, 0);
+ _data[102] = DrawStruct(0, 38, 47, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[103] = DrawStruct(0, 8, 24);
+ _data[104] = DrawStruct(0, 169, 24, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[105] = DrawStruct(1, 32, 24);
+ _data[106] = DrawStruct(0, -23, 40, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[107] = DrawStruct(0, 200, 40, 0, SPRFLAG_HORIZ_FLIPPED | SPRFLAG_SCENE_CLIPPED);
+ _data[108] = DrawStruct(0, 8, 47);
+ _data[109] = DrawStruct(0, 169, 47, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[110] = DrawStruct(1, -56, -4, 0x8000, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[111] = DrawStruct(0, -5, 2, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[112] = DrawStruct(0, -67, 2, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[113] = DrawStruct(0, 44, 73);
+ _data[114] = DrawStruct(0, 44, 73);
+ _data[115] = DrawStruct(0, 58, 14, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[116] = DrawStruct(0, 169, 73);
+ _data[117] = DrawStruct(0, 169, 73);
+ _data[118] = DrawStruct(0, -5, 14, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[119] = DrawStruct(0, 110, 73);
+ _data[120] = DrawStruct(0, 110, 73);
+ _data[121] = DrawStruct(0, -5, 14, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[122] = DrawStruct(0, 110, 73);
+ _data[123] = DrawStruct(0, 110, 73);
+ _data[124] = DrawStruct(0, 72, 43);
+ _data[125] = DrawStruct(0, 72, 43, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[126] = DrawStruct(0, 93, 48);
+ _data[127] = DrawStruct(0, 51, 48, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[128] = DrawStruct(0, 97, 36);
+ _data[129] = DrawStruct(0, 47, 36, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[130] = DrawStruct(0, 118, 42);
+ _data[131] = DrawStruct(0, 26, 42, 0, SPRFLAG_HORIZ_FLIPPED);
+}
+
+/*------------------------------------------------------------------------*/
+
+IndoorDrawList::IndoorDrawList() :
+ _sky1(_data[0]), _sky2(_data[1]), _ground(_data[2]), _horizon(_data[28]),
+ _swl_0F1R(_data[146]), _swl_0F1L(_data[144]), _swl_1F1R(_data[134]),
+ _swl_1F1L(_data[133]), _swl_2F2R(_data[110]), _swl_2F1R(_data[109]),
+ _swl_2F1L(_data[108]), _swl_2F2L(_data[107]), _swl_3F1R(_data[ 78]),
+ _swl_3F2R(_data[ 77]), _swl_3F3R(_data[ 76]), _swl_3F4R(_data[ 75]),
+ _swl_3F1L(_data[ 74]), _swl_3F2L(_data[ 73]), _swl_3F3L(_data[ 72]),
+ _swl_3F4L(_data[ 71]), _swl_4F4R(_data[ 33]), _swl_4F3R(_data[ 34]),
+ _swl_4F2R(_data[ 35]), _swl_4F1R(_data[ 36]), _swl_4F1L(_data[ 32]),
+ _swl_4F2L(_data[ 31]), _swl_4F3L(_data[ 30]), _swl_4F4L(_data[ 29]),
+ _fwl_4F4R(_data[ 45]), _fwl_4F3R(_data[ 44]), _fwl_4F2R(_data[ 43]),
+ _fwl_4F1R(_data[ 42]), _fwl_4F( _data[ 41]), _fwl_4F1L(_data[ 40]),
+ _fwl_4F2L(_data[ 39]), _fwl_4F3L(_data[ 38]), _fwl_4F4L(_data[ 37]),
+ _fwl_2F1R(_data[121]), _fwl_2F( _data[120]), _fwl_2F1L(_data[119]),
+ _fwl_3F2R(_data[ 91]), _fwl_3F1R(_data[ 90]), _fwl_3F( _data[ 89]),
+ _fwl_3F1L(_data[ 88]), _fwl_3F2L(_data[ 87]), _fwl_1F( _data[147]),
+ _fwl_1F1R(_data[145]), _fwl_1F1L(_data[143]),
+ _groundTiles(&_data[3]),
+ _objects0(_data[149]), _objects1(_data[125]), _objects2(_data[126]),
+ _objects3(_data[127]), _objects4(_data[97]), _objects5(_data[98]),
+ _objects6(_data[99]), _objects7(_data[55]), _objects8(_data[56]),
+ _objects9(_data[58]), _objects10(_data[57]), _objects11(_data[59]),
+ _attackImgs1(&_data[162]), _attackImgs2(&_data[135]),
+ _attackImgs3(&_data[111]), _attackImgs4(&_data[79]) {
+ // Setup draw structure positions
+ _data[0] = DrawStruct(0, 8, 8);
+ _data[1] = DrawStruct(1, 8, 25);
+ _data[2] = DrawStruct(0, 8, 67);
+ _data[3] = DrawStruct(0, 8, 67);
+ _data[4] = DrawStruct(0, 38, 67);
+ _data[5] = DrawStruct(0, 84, 67);
+ _data[6] = DrawStruct(0, 134, 67);
+ _data[7] = DrawStruct(0, 117, 67);
+ _data[8] = DrawStruct(0, 117, 67);
+ _data[9] = DrawStruct(0, 103, 67);
+ _data[10] = DrawStruct(0, 8, 73);
+ _data[11] = DrawStruct(0, 8, 73);
+ _data[12] = DrawStruct(0, 30, 73);
+ _data[13] = DrawStruct(0, 181, 73);
+ _data[14] = DrawStruct(0, 154, 73);
+ _data[15] = DrawStruct(0, 129, 73);
+ _data[16] = DrawStruct(0, 87, 73);
+ _data[17] = DrawStruct(0, 8, 81);
+ _data[18] = DrawStruct(0, 8, 81);
+ _data[19] = DrawStruct(0, 202, 81);
+ _data[20] = DrawStruct(0, 145, 81);
+ _data[21] = DrawStruct(0, 63, 81);
+ _data[22] = DrawStruct(0, 8, 93);
+ _data[23] = DrawStruct(0, 169, 93);
+ _data[24] = DrawStruct(0, 31, 93);
+ _data[25] = DrawStruct(0, 8, 109);
+ _data[26] = DrawStruct(0, 201, 109);
+ _data[27] = DrawStruct(0, 8, 109);
+ _data[28] = DrawStruct(7, 8, 64);
+ _data[29] = DrawStruct(22, 32, 60);
+ _data[30] = DrawStruct(20, 56, 60);
+ _data[31] = DrawStruct(18, 80, 60);
+ _data[32] = DrawStruct(16, 104, 60);
+ _data[33] = DrawStruct(23, 152, 60, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[34] = DrawStruct(21, 144, 60, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[35] = DrawStruct(19, 131, 60, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[36] = DrawStruct(17, 120, 60, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[37] = DrawStruct(14, 8, 60);
+ _data[38] = DrawStruct(12, 32, 60);
+ _data[39] = DrawStruct(10, 56, 60);
+ _data[40] = DrawStruct(14, 80, 60);
+ _data[41] = DrawStruct(14, 104, 60);
+ _data[42] = DrawStruct(14, 128, 60);
+ _data[43] = DrawStruct(14, 152, 60);
+ _data[44] = DrawStruct(8, 176, 60);
+ _data[45] = DrawStruct(8, 200, 60);
+ _data[46] = DrawStruct(0, -64, 61, 14, 0);
+ _data[47] = DrawStruct(0, -40, 61, 14, 0);
+ _data[48] = DrawStruct(0, -16, 61, 14, 0);
+ _data[49] = DrawStruct(0, 8, 61, 14, 0);
+ _data[50] = DrawStruct(0, 32, 61, 14, 0);
+ _data[51] = DrawStruct(0, 56, 61, 14, 0);
+ _data[52] = DrawStruct(0, 80, 61, 14, 0);
+ _data[53] = DrawStruct(0, 104, 61, 14, 0);
+ _data[54] = DrawStruct(0, 128, 61, 14, 0);
+ _data[55] = DrawStruct(0, -9, 58, 14, 0);
+ _data[56] = DrawStruct(0, -34, 58, 14, 0);
+ _data[57] = DrawStruct(0, 16, 58, 14, 0);
+ _data[58] = DrawStruct(0, -58, 58, 14, 0);
+ _data[59] = DrawStruct(0, 40, 58, 14, 0);
+ _data[60] = DrawStruct(0, -41, 58, 14, 0);
+ _data[61] = DrawStruct(0, -26, 58, 14, 0);
+ _data[62] = DrawStruct(0, -34, 58, 14, 0);
+ _data[63] = DrawStruct(0, -16, 58, 14, 0);
+ _data[64] = DrawStruct(0, 23, 58, 14, 0);
+ _data[65] = DrawStruct(0, 16, 58, 14, 0);
+ _data[66] = DrawStruct(0, -58, 58, 14, 0);
+ _data[67] = DrawStruct(0, 40, 58, 14, 0);
+ _data[68] = DrawStruct(0, -17, 58, 14, 0);
+ _data[69] = DrawStruct(0, -1, 58, 14, 0);
+ _data[70] = DrawStruct(0, -9, 58, 14, 0);
+ _data[71] = DrawStruct(14, 8, 58);
+ _data[72] = DrawStruct(12, 8, 55);
+ _data[73] = DrawStruct(10, 32, 52);
+ _data[74] = DrawStruct(14, 88, 52);
+ _data[75] = DrawStruct(14, 128, 52, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[76] = DrawStruct(14, 152, 52, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[77] = DrawStruct(0, 176, 55, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[78] = DrawStruct(0, 200, 58, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[79] = DrawStruct(0, 72, 58, 12, 0);
+ _data[80] = DrawStruct(0, 72, 58, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[81] = DrawStruct(0, 69, 63, 12, 0);
+ _data[82] = DrawStruct(0, 75, 63, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[83] = DrawStruct(0, 73, 53, 12, 0);
+ _data[84] = DrawStruct(0, 71, 53, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[85] = DrawStruct(0, 80, 57, 12, 0);
+ _data[86] = DrawStruct(0, 64, 57, 12, SPRFLAG_HORIZ_FLIPPED);
+ _data[87] = DrawStruct(7, -24, 52, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[88] = DrawStruct(7, 32, 52);
+ _data[89] = DrawStruct(7, 88, 52);
+ _data[90] = DrawStruct(0, 144, 52);
+ _data[91] = DrawStruct(0, 200, 52, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[92] = DrawStruct(0, -79, 52, 11, SPRFLAG_SCENE_CLIPPED);
+ _data[93] = DrawStruct(0, -27, 52, 11, 0);
+ _data[94] = DrawStruct(0, 32, 52, 11, 0);
+ _data[95] = DrawStruct(0, 89, 52, 11, 0);
+ _data[96] = DrawStruct(0, 145, 52, 11, SPRFLAG_SCENE_CLIPPED);
+ _data[97] = DrawStruct(0, -8, 50, 12, 0);
+ _data[98] = DrawStruct(0, -65, 50, 12, 0);
+ _data[99] = DrawStruct(0, 49, 50, 12, 0);
+ _data[100] = DrawStruct(0, -65, 50, 12, 0);
+ _data[101] = DrawStruct(0, -81, 50, 12, 0);
+ _data[102] = DrawStruct(0, 49, 50, 12, 0);
+ _data[103] = DrawStruct(0, 65, 50, 12, 0);
+ _data[104] = DrawStruct(0, -24, 50, 12, 0);
+ _data[105] = DrawStruct(0, 9, 50, 12, 0);
+ _data[106] = DrawStruct(0, -8, 50, 12, 0);
+ _data[107] = DrawStruct(7, 8, 48);
+ _data[108] = DrawStruct(7, 64, 40);
+ _data[109] = DrawStruct(6, 144, 40, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[110] = DrawStruct(6, 200, 48, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[111] = DrawStruct(0, 72, 53, 8, 0);
+ _data[112] = DrawStruct(0, 72, 53, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[113] = DrawStruct(0, 77, 58, 8, 0);
+ _data[114] = DrawStruct(0, 67, 58, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[115] = DrawStruct(0, 81, 47, 8, 0);
+ _data[116] = DrawStruct(0, 63, 47, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[117] = DrawStruct(0, 94, 52, 8, 0);
+ _data[118] = DrawStruct(0, 50, 52, 8, SPRFLAG_HORIZ_FLIPPED);
+ _data[119] = DrawStruct(6, -40, 40, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[120] = DrawStruct(6, 64, 40);
+ _data[121] = DrawStruct(0, 168, 40, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[122] = DrawStruct(0, -72, 40, 6, SPRFLAG_SCENE_CLIPPED);
+ _data[123] = DrawStruct(0, 32, 40, 6, 0);
+ _data[124] = DrawStruct(0, 137, 40, 6, SPRFLAG_SCENE_CLIPPED);
+ _data[125] = DrawStruct(0, -7, 25, 7, 0);
+ _data[126] = DrawStruct(0, -112, 25, 7, SPRFLAG_SCENE_CLIPPED);
+ _data[127] = DrawStruct(0, 98, 25, 7, SPRFLAG_SCENE_CLIPPED);
+ _data[128] = DrawStruct(0, -112, 29, 8, SPRFLAG_SCENE_CLIPPED);
+ _data[129] = DrawStruct(0, 98, 29, 8, SPRFLAG_SCENE_CLIPPED);
+ _data[130] = DrawStruct(0, -38, 29, 8, 0);
+ _data[131] = DrawStruct(0, 25, 29, 8, 0);
+ _data[132] = DrawStruct(0, -7, 29, 8, 0);
+ _data[133] = DrawStruct(6, 32, 24);
+ _data[134] = DrawStruct(0, 168, 24, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[135] = DrawStruct(0, 72, 48, 4, 0);
+ _data[136] = DrawStruct(0, 72, 48, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[137] = DrawStruct(0, 85, 53, 4, 0);
+ _data[138] = DrawStruct(0, 59, 53, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[139] = DrawStruct(0, 89, 41, 4, 0);
+ _data[140] = DrawStruct(0, 55, 41, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[141] = DrawStruct(0, 106, 47, 4, 0);
+ _data[142] = DrawStruct(0, 38, 47, 4, SPRFLAG_HORIZ_FLIPPED);
+ _data[143] = DrawStruct(0, -136, 24, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[144] = DrawStruct(0, 8, 12);
+ _data[145] = DrawStruct(0, 32, 24);
+ _data[146] = DrawStruct(0, 200, 12, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[147] = DrawStruct(0, 200, 24, 0, SPRFLAG_SCENE_CLIPPED);
+ _data[148] = DrawStruct(0, 32, 24);
+ _data[149] = DrawStruct(0, -5, 2, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[150] = DrawStruct(0, -67, 10, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[151] = DrawStruct(0, 44, 73);
+ _data[152] = DrawStruct(0, 44, 73);
+ _data[153] = DrawStruct(0, 58, 14, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[154] = DrawStruct(0, 169, 73);
+ _data[155] = DrawStruct(0, 169, 73);
+ _data[156] = DrawStruct(0, -5, 14, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[157] = DrawStruct(0, 110, 73);
+ _data[158] = DrawStruct(0, 110, 73);
+ _data[159] = DrawStruct(0, -5, 14, 0, SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED);
+ _data[160] = DrawStruct(0, 110, 73);
+ _data[161] = DrawStruct(0, 110, 73);
+ _data[162] = DrawStruct(0, 72, 43);
+ _data[163] = DrawStruct(0, 72, 43, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[164] = DrawStruct(0, 93, 48);
+ _data[165] = DrawStruct(0, 51, 48, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[166] = DrawStruct(0, 97, 36);
+ _data[167] = DrawStruct(0, 47, 36, 0, SPRFLAG_HORIZ_FLIPPED);
+ _data[168] = DrawStruct(0, 118, 42);
+ _data[169] = DrawStruct(0, 26, 42, 0, SPRFLAG_HORIZ_FLIPPED);
+}
+
+/*------------------------------------------------------------------------*/
+
+InterfaceMap::InterfaceMap(XeenEngine *vm): _vm(vm) {
+ Common::fill(&_wp[0], &_wp[20], 0);
+ Common::fill(&_wo[0], &_wo[308], 0);
+ _overallFrame = 0;
+ _flipWater = false;
+ _flipGround = false;
+ _flipSky = false;
+ _flipDefaultGround = false;
+ _isAttacking = false;
+ _charsShooting = false;
+ _objNumber = 0;
+ _combatFloatCounter = 0;
+ _thinWall = false;
+ _isAnimReset = false;
+ _overallFrame = 0;
+ _openDoor = false;
+}
+
+void InterfaceMap::drawMap() {
+ Combat &combat = *_vm->_combat;
+ Map &map = *_vm->_map;
+ Scripts &scripts = *_vm->_scripts;
+
+ const int COMBAT_POS_X[3][2] = { { 102, 134 }, { 36, 67 }, { 161, 161 } };
+ const int INDOOR_INDEXES[3] = { 157, 151, 154 };
+ const int OUTDOOR_INDEXES[3] = { 119, 113, 116 };
+ const int COMBAT_OFFSET_X[4] = { 8, 6, 4, 2 };
+
+ MazeObject &objObject = map._mobData._objects[_objNumber];
+ Direction partyDirection = _vm->_party->_mazeDirection;
+ int objNum = _objNumber - 1;
+
+ // Loop to update the frame numbers for each maze object, applying the animation frame
+ // limits as specified by the map's _animationInfo listing
+ for (uint idx = 0; idx < map._mobData._objects.size(); ++idx) {
+ MazeObject &mazeObject = map._mobData._objects[idx];
+ AnimationEntry &animEntry = map._animationInfo[mazeObject._spriteId];
+ int directionIndex = DIRECTION_ANIM_POSITIONS[mazeObject._direction][partyDirection];
+
+ if (_isAnimReset) {
+ mazeObject._frame = animEntry._frame1._frames[directionIndex];
+ } else {
+ ++mazeObject._frame;
+ if ((int)idx == objNum && scripts._animCounter > 0 && (
+ objObject._spriteId == (_vm->_files->_isDarkCc ? 15 : 16) ||
+ objObject._spriteId == 58 || objObject._spriteId == 73)) {
+ if (mazeObject._frame > 4 || mazeObject._spriteId == 58)
+ mazeObject._frame = 1;
+ }
+ else if (mazeObject._frame >= animEntry._frame2._frames[directionIndex]) {
+ mazeObject._frame = animEntry._frame1._frames[directionIndex];
+ }
+ }
+
+ mazeObject._flipped = animEntry._flipped._flags[directionIndex];
+ }
+
+ if (map._isOutdoors) {
+ // Outdoors drawing
+ for (int idx = 0; idx < 44; ++idx)
+ _outdoorList[OUTDOOR_DRAWSTRCT_INDEXES[idx]]._frame = -1;
+
+ if (combat._monstersAttacking) {
+ for (int idx = 0; idx < 8; ++idx) {
+ if (_outdoorList._attackImgs4[idx]._sprites)
+ _outdoorList._attackImgs4[idx]._frame = 0;
+ else if (_outdoorList._attackImgs3[idx]._sprites)
+ _outdoorList._attackImgs3[idx]._frame = 1;
+ else if (_outdoorList._attackImgs2[idx]._sprites)
+ _outdoorList._attackImgs2[idx]._frame = 2;
+ else if (_outdoorList._attackImgs1[idx]._sprites)
+ _outdoorList._attackImgs1[idx]._frame = 0;
+ }
+ } else if (_charsShooting) {
+ for (int idx = 0; idx < 8; ++idx) {
+ if (_outdoorList._attackImgs1[idx]._sprites)
+ _outdoorList._attackImgs1[idx]._frame = 0;
+ else if (_outdoorList._attackImgs2[idx]._sprites)
+ _outdoorList._attackImgs2[idx]._frame = 1;
+ else if (_outdoorList._attackImgs3[idx]._sprites)
+ _outdoorList._attackImgs3[idx]._frame = 2;
+ else if (_outdoorList._attackImgs4[idx]._sprites)
+ _outdoorList._attackImgs1[idx]._frame = 0;
+ }
+ }
+
+ _isAnimReset = false;
+ int attackMon2 = combat._attackMonsters[2];
+
+ for (int idx = 0; idx < 3; ++idx) {
+ DrawStruct &ds1 = _outdoorList[OUTDOOR_INDEXES[idx] + 1];
+ DrawStruct &ds2 = _outdoorList[OUTDOOR_INDEXES[idx]];
+ ds1._sprites = nullptr;
+ ds2._sprites = nullptr;
+
+ if (combat._charsArray1[idx]) {
+ int vIndex = combat._attackMonsters[1] && !attackMon2 ? 1 : 0;
+ combat._charsArray1[idx]--;
+
+ if (combat._monPow[idx]) {
+ ds2._x = COMBAT_POS_X[idx][vIndex];
+ ds2._frame = 0;
+ ds2._scale = combat._monsterScale[idx];
+
+ if (ds2._scale == 0x8000) {
+ ds2._x /= 3;
+ ds2._y = 60;
+ } else {
+ ds2._y = 73;
+ }
+
+ ds2._flags = SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED;
+ ds2._sprites = &_charPowSprites;
+ }
+
+ if (combat._elemPow[idx]) {
+ ds1._x = COMBAT_POS_X[idx][vIndex] + COMBAT_OFFSET_X[idx];
+ ds1._frame = combat._elemPow[idx];
+ ds1._scale = combat._elemScale[idx];
+
+ if (ds1._scale == 0x8000)
+ ds1._x /= 3;
+ ds1._flags = SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED;
+ ds1._sprites = &_charPowSprites;
+ }
+ }
+ }
+
+ setOutdoorsMonsters();
+ setOutdoorsObjects();
+
+ _outdoorList[123]._sprites = nullptr;
+ _outdoorList[122]._sprites = nullptr;
+ _outdoorList[121]._sprites = nullptr;
+
+ int monsterIndex;
+ if (combat._attackMonsters[0] != -1 && map._mobData._monsters[combat._attackMonsters[0]]._frame >= 8) {
+ _outdoorList[121] = _outdoorList[118];
+ _outdoorList[122] = _outdoorList[119];
+ _outdoorList[123] = _outdoorList[120];
+ _outdoorList[118]._sprites = nullptr;
+ _outdoorList[119]._sprites = nullptr;
+ _outdoorList[120]._sprites = nullptr;
+ monsterIndex = 1;
+ } else if (combat._attackMonsters[1] != -1 && map._mobData._monsters[combat._attackMonsters[1]]._frame >= 8) {
+ _outdoorList[121] = _outdoorList[112];
+ _outdoorList[122] = _outdoorList[113];
+ _outdoorList[123] = _outdoorList[114];
+ _outdoorList[112]._sprites = nullptr;
+ _outdoorList[113]._sprites = nullptr;
+ _outdoorList[124]._sprites = nullptr;
+ monsterIndex = 2;
+ } else if (combat._attackMonsters[2] != -1 && map._mobData._monsters[combat._attackMonsters[2]]._frame >= 8) {
+ _outdoorList[121] = _outdoorList[115];
+ _outdoorList[122] = _outdoorList[116];
+ _outdoorList[123] = _outdoorList[117];
+ _outdoorList[115]._sprites = nullptr;
+ _outdoorList[116]._sprites = nullptr;
+ _outdoorList[117]._sprites = nullptr;
+ monsterIndex = 3;
+ } else {
+ monsterIndex = 0;
+ }
+
+ drawOutdoors();
+
+ switch (monsterIndex) {
+ case 1:
+ _outdoorList[118] = _outdoorList[121];
+ _outdoorList[119] = _outdoorList[122];
+ _outdoorList[120] = _outdoorList[123];
+ break;
+ case 2:
+ _outdoorList[112] = _outdoorList[121];
+ _outdoorList[113] = _outdoorList[122];
+ _outdoorList[114] = _outdoorList[123];
+ break;
+ case 3:
+ _outdoorList[115] = _outdoorList[121];
+ _outdoorList[116] = _outdoorList[122];
+ _outdoorList[117] = _outdoorList[123];
+ break;
+ default:
+ break;
+ }
+ } else {
+ // Indoor drawing
+ // Default all the parts of draw struct not to be drawn by default
+ for (int idx = 3; idx < _indoorList.size(); ++idx)
+ _indoorList[idx]._frame = -1;
+
+ if (combat._monstersAttacking) {
+ for (int idx = 0; idx < 96; ++idx) {
+ if (_indoorList[79 + idx]._sprites != nullptr) {
+ _indoorList[79 + idx]._frame = 0;
+ }
+ else if (_indoorList[111 + idx]._sprites != nullptr) {
+ _indoorList[111 + idx]._frame = 1;
+ }
+ else if (_indoorList[135 + idx]._sprites != nullptr) {
+ _indoorList[135 + idx]._frame = 2;
+ }
+ else if (_indoorList[162 + idx]._sprites != nullptr) {
+ _indoorList[162 + idx]._frame = 0;
+ }
+ }
+ } else if (_charsShooting) {
+ for (int idx = 0; idx < 8; ++idx) {
+ if (_indoorList._attackImgs1[idx]._sprites != nullptr) {
+ _indoorList._attackImgs1[idx]._frame = 0;
+ } else if (_indoorList._attackImgs2[idx]._sprites != nullptr) {
+ _indoorList._attackImgs2[idx]._frame = 1;
+ } else if (_indoorList._attackImgs3[idx]._sprites != nullptr) {
+ _indoorList._attackImgs3[idx]._frame = 2;
+ } else if (_indoorList._attackImgs4[idx]._sprites != nullptr) {
+ _indoorList._attackImgs4[idx]._frame = 0;
+ }
+ }
+ }
+
+ setMazeBits();
+ _isAnimReset = false;
+
+ // Code in the original that's not being used
+ //MazeObject &objObject = map._mobData._objects[_objNumber - 1];
+
+ for (int idx = 0; idx < 3; ++idx) {
+ DrawStruct &ds1 = _indoorList[INDOOR_INDEXES[idx]];
+ DrawStruct &ds2 = _indoorList[INDOOR_INDEXES[idx] + 1];
+ ds1._sprites = nullptr;
+ ds2._sprites = nullptr;
+
+ if (combat._charsArray1[idx]) {
+ int posIndex = combat._attackMonsters[1] && !combat._attackMonsters[2] ? 1 : 0;
+ --combat._charsArray1[idx];
+
+ if (combat._monPow[idx]) {
+ ds1._x = COMBAT_POS_X[idx][posIndex];
+ ds1._frame = 0;
+ ds1._scale = combat._monsterScale[idx];
+ if (ds1._scale == 0x8000) {
+ ds1._x /= 3;
+ ds1._y = 60;
+ } else {
+ ds1._y = 73;
+ }
+
+ ds1._flags = SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED;
+ ds1._sprites = &_charPowSprites;
+ }
+
+ if (combat._elemPow[idx]) {
+ ds2._x = COMBAT_POS_X[idx][posIndex] + COMBAT_OFFSET_X[idx];
+ ds2._frame = combat._elemPow[idx];
+ ds2._scale = combat._elemScale[idx];
+ if (ds2._scale == 0x8000)
+ ds2._x /= 3;
+ ds2._flags = SPRFLAG_4000 | SPRFLAG_SCENE_CLIPPED;
+ ds2._sprites = &_charPowSprites;
+ }
+ }
+ }
+
+ setIndoorsMonsters();
+ setIndoorsObjects();
+ setIndoorsWallPics();
+
+ _indoorList[161]._sprites = nullptr;
+ _indoorList[160]._sprites = nullptr;
+ _indoorList[159]._sprites = nullptr;
+
+ // Handle attacking monsters
+ int monsterIndex = 0;
+ if (combat._attackMonsters[0] != -1 && map._mobData._monsters[combat._attackMonsters[0]]._frame >= 8) {
+ _indoorList[159] = _indoorList[156];
+ _indoorList[160] = _indoorList[157];
+ _indoorList[161] = _indoorList[158];
+ _indoorList[158]._sprites = nullptr;
+ _indoorList[156]._sprites = nullptr;
+ _indoorList[157]._sprites = nullptr;
+ monsterIndex = 1;
+ } else if (combat._attackMonsters[1] != -1 && map._mobData._monsters[combat._attackMonsters[1]]._frame >= 8) {
+ _indoorList[159] = _indoorList[150];
+ _indoorList[160] = _indoorList[151];
+ _indoorList[161] = _indoorList[152];
+ _indoorList[152]._sprites = nullptr;
+ _indoorList[151]._sprites = nullptr;
+ _indoorList[150]._sprites = nullptr;
+ monsterIndex = 2;
+ } else if (combat._attackMonsters[2] != -1 && map._mobData._monsters[combat._attackMonsters[2]]._frame >= 8) {
+ _indoorList[159] = _indoorList[153];
+ _indoorList[160] = _indoorList[154];
+ _indoorList[161] = _indoorList[155];
+ _indoorList[153]._sprites = nullptr;
+ _indoorList[154]._sprites = nullptr;
+ _indoorList[155]._sprites = nullptr;
+ monsterIndex = 3;
+ }
+
+ drawIndoors();
+
+ switch (monsterIndex) {
+ case 1:
+ _indoorList[156] = _indoorList[159];
+ _indoorList[157] = _indoorList[160];
+ _indoorList[158] = _indoorList[161];
+ break;
+ case 2:
+ _indoorList[150] = _indoorList[159];
+ _indoorList[151] = _indoorList[160];
+ _indoorList[152] = _indoorList[161];
+ break;
+ case 3:
+ _indoorList[153] = _indoorList[159];
+ _indoorList[154] = _indoorList[160];
+ _indoorList[155] = _indoorList[161];
+ break;
+ default:
+ break;
+ }
+ }
+
+ animate3d();
+}
+
+void InterfaceMap::animate3d() {
+ Combat &combat = *_vm->_combat;
+ Map &map = *_vm->_map;
+ _overallFrame = (_overallFrame + 1) % 5;
+ _combatFloatCounter = (_combatFloatCounter + 1) % 8;
+
+ for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
+ MazeMonster &monster = map._mobData._monsters[idx];
+ if (!monster._damageType) {
+ if (monster._frame < 8) {
+ MonsterStruct &monsterData = *monster._monsterData;
+ if (!monsterData._loopAnimation) {
+ // Monster isn't specially looped, so cycle through the 8 frames
+ monster._frame = (monster._frame + 1) % 8;
+ } else if (!monster._field9) {
+ monster._frame = (monster._frame + 1) % 8;
+ if (monster._frame == 0) {
+ monster._field9 ^= 1;
+ monster._frame = 6;
+ }
+ } else {
+ if (monster._frame)
+ --monster._frame;
+ if (monster._frame == 0)
+ monster._field9 = 0;
+ }
+ } else if (monster._frame == 11) {
+ --monster._fieldA;
+ if (monster._fieldA == 0)
+ monster._frame = 0;
+ } else {
+ ++monster._frame;
+ if (monster._frame == 11) {
+ --monster._frame;
+ monster._frame = monster._fieldA ? 10 : 0;
+ }
+ }
+ }
+
+ // Block 2
+ if (monster._effect2) {
+ if (monster._effect1) {
+ if (monster._effect1 & 0x80) {
+ if (monster._effect3)
+ --monster._effect3;
+ if (monster._effect3 == 0)
+ monster._effect1 ^= 0x80;
+ } else {
+ monster._effect3 = (monster._effect3 + 1) % 3;
+ if (monster._effect3 == 0) {
+ monster._effect1 ^= 0x80;
+ monster._effect3 = 2;
+ }
+ }
+ }
+ } else {
+ monster._effect3 = (monster._effect3 + 1) % 8;
+ if (monster._effect3 == 0) {
+ MonsterStruct &monsterData = *monster._monsterData;
+ monster._effect1 = monster._effect2 = monsterData._animationEffect;
+ }
+ }
+ }
+
+ DrawStruct *combatImgs1 = map._isOutdoors ? _outdoorList._attackImgs1 : _indoorList._attackImgs1;
+ DrawStruct *combatImgs2 = map._isOutdoors ? _outdoorList._attackImgs2 : _indoorList._attackImgs2;
+ DrawStruct *combatImgs3 = map._isOutdoors ? _outdoorList._attackImgs3 : _indoorList._attackImgs3;
+ DrawStruct *combatImgs4 = map._isOutdoors ? _outdoorList._attackImgs4 : _indoorList._attackImgs4;
+
+ if (combat._monstersAttacking) {
+ for (int idx = 0; idx < 8; ++idx) {
+ if (combatImgs1[idx]._sprites) {
+ combatImgs1[idx]._sprites = nullptr;
+ combat._shooting[idx] = false;
+ } else if (combatImgs2[idx]._sprites) {
+ combatImgs1[idx]._sprites = combatImgs2[idx]._sprites;
+ combatImgs2[idx]._sprites = nullptr;
+ } else if (combatImgs3[idx]._sprites) {
+ combatImgs2[idx]._sprites = combatImgs3[idx]._sprites;
+ combatImgs3[idx]._sprites = nullptr;
+ } else if (combatImgs4[idx]._sprites) {
+ combatImgs3[idx]._sprites = combatImgs4[idx]._sprites;
+ combatImgs4[idx]._sprites = nullptr;
+ }
+ }
+ } else if (_charsShooting) {
+ for (int idx = 0; idx < 8; ++idx) {
+ if (combatImgs4[idx]._sprites) {
+ combatImgs4[idx]._sprites = nullptr;
+ } else if (combatImgs3[idx]._sprites) {
+ combatImgs4[idx]._sprites = combatImgs3[idx]._sprites;
+ combatImgs3[idx]._sprites = nullptr;
+ } else if (combatImgs2[idx]._sprites) {
+ combatImgs3[idx]._sprites = combatImgs2[idx]._sprites;
+ combatImgs2[idx]._sprites = nullptr;
+ } else if (combatImgs1[idx]._sprites) {
+ combatImgs2[idx]._sprites = combatImgs1[idx]._sprites;
+ combatImgs1[idx]._sprites = nullptr;
+ }
+ }
+ }
+
+ for (uint idx = 0; idx < map._mobData._wallItems.size(); ++idx) {
+ MazeWallItem &wallItem = map._mobData._wallItems[idx];
+ wallItem._frame = (wallItem._frame + 1) % wallItem._sprites->size();
+ }
+}
+
+void InterfaceMap::setMazeBits() {
+ Common::fill(&_wo[0], &_wo[308], 0);
+
+ switch (_vm->_map->getCell(0) - 1) {
+ case 0:
+ ++_wo[125];
+ break;
+ case 1:
+ ++_wo[69];
+ break;
+ case 2:
+ ++_wo[185];
+ break;
+ case 3:
+ case 12:
+ ++_wo[105];
+ break;
+ case 4:
+ case 7:
+ ++_wo[25];
+ break;
+ case 5:
+ ++_wo[225];
+ break;
+ case 6:
+ ++_wo[205];
+ break;
+ case 8:
+ ++_wo[145];
+ break;
+ case 9:
+ ++_wo[305];
+ break;
+ case 10:
+ ++_wo[245];
+ break;
+ case 11:
+ ++_wo[165];
+ break;
+ case 13:
+ ++_wo[265];
+ break;
+ case 14:
+ ++_wo[285];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(1) - 1) {
+ case 1:
+ ++_wo[72];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[28];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(2) - 1) {
+ case 0:
+ ++_wo[127];
+ break;
+ case 1:
+ ++_wo[71];
+ break;
+ case 2:
+ ++_wo[187];
+ break;
+ case 3:
+ case 12:
+ ++_wo[107];
+ break;
+ case 4:
+ case 7:
+ ++_wo[27];
+ break;
+ case 5:
+ ++_wo[227];
+ break;
+ case 6:
+ ++_wo[207];
+ break;
+ case 8:
+ ++_wo[147];
+ break;
+ case 9:
+ ++_wo[307];
+ break;
+ case 10:
+ ++_wo[247];
+ break;
+ case 11:
+ ++_wo[167];
+ break;
+ case 13:
+ ++_wo[267];
+ break;
+ case 14:
+ ++_wo[287];
+ break;
+ default:
+ break;
+ }
+
+ _vm->_party->handleLight();
+
+ switch (_vm->_map->getCell(3) - 1) {
+ case 1:
+ ++_wo[73];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[29];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(4) - 1) {
+ case 0:
+ ++_wo[126];
+ break;
+ case 1:
+ ++_wo[70];
+ break;
+ case 2:
+ ++_wo[186];
+ break;
+ case 3:
+ case 12:
+ ++_wo[106];
+ break;
+ case 4:
+ case 7:
+ ++_wo[26];
+ break;
+ case 5:
+ ++_wo[226];
+ break;
+ case 6:
+ ++_wo[206];
+ case 8:
+ ++_wo[146];
+ break;
+ case 9:
+ ++_wo[306];
+ break;
+ case 10:
+ ++_wo[246];
+ break;
+ break;
+ case 11:
+ ++_wo[166];
+ break;
+ case 13:
+ ++_wo[266];
+ break;
+ case 14:
+ ++_wo[286];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(5) - 1) {
+ case 0:
+ ++_wo[122];
+ break;
+ case 1:
+ ++_wo[64];
+ break;
+ case 2:
+ ++_wo[182];
+ break;
+ case 3:
+ case 12:
+ ++_wo[102];
+ break;
+ case 4:
+ case 7:
+ ++_wo[20];
+ break;
+ case 5:
+ ++_wo[222];
+ break;
+ case 6:
+ ++_wo[202];
+ break;
+ case 8:
+ ++_wo[142];
+ break;
+ case 9:
+ ++_wo[302];
+ break;
+ case 10:
+ ++_wo[242];
+ break;
+ case 11:
+ ++_wo[162];
+ break;
+ case 13:
+ ++_wo[262];
+ break;
+ case 14:
+ ++_wo[282];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(6) - 1) {
+ case 1:
+ ++_wo[67];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[23];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(7) - 1) {
+ case 0:
+ ++_wo[124];
+ break;
+ case 1:
+ ++_wo[66];
+ break;
+ case 2:
+ ++_wo[184];
+ break;
+ case 3:
+ case 12:
+ ++_wo[104];
+ break;
+ case 4:
+ case 7:
+ ++_wo[22];
+ break;
+ case 5:
+ ++_wo[224];
+ break;
+ case 6:
+ ++_wo[204];
+ break;
+ case 8:
+ ++_wo[144];
+ break;
+ case 9:
+ ++_wo[304];
+ break;
+ case 10:
+ ++_wo[244];
+ break;
+ case 11:
+ ++_wo[164];
+ break;
+ case 13:
+ ++_wo[264];
+ break;
+ case 14:
+ ++_wo[284];
+ break;
+ default:
+ break;
+ }
+
+ _thinWall = (_vm->_map->_currentWall != INVALID_CELL) && _wo[27];
+
+ switch (_vm->_map->getCell(8) - 1) {
+ case 1:
+ ++_wo[68];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[24];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(9) - 1) {
+ case 0:
+ ++_wo[123];
+ break;
+ case 1:
+ ++_wo[65];
+ break;
+ case 2:
+ ++_wo[183];
+ break;
+ case 3:
+ case 12:
+ ++_wo[103];
+ break;
+ case 4:
+ case 7:
+ ++_wo[21];
+ break;
+ case 5:
+ ++_wo[223];
+ break;
+ case 6:
+ ++_wo[203];
+ break;
+ case 8:
+ ++_wo[143];
+ break;
+ case 9:
+ ++_wo[303];
+ break;
+ case 10:
+ ++_wo[243];
+ break;
+ case 11:
+ ++_wo[163];
+ break;
+ case 13:
+ ++_wo[263];
+ break;
+ case 14:
+ ++_wo[283];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(10) - 1) {
+ case 0:
+ ++_wo[117];
+ break;
+ case 1:
+ ++_wo[55];
+ break;
+ case 2:
+ ++_wo[177];
+ break;
+ case 3:
+ case 12:
+ ++_wo[97];
+ break;
+ case 4:
+ case 7:
+ ++_wo[11];
+ break;
+ case 5:
+ ++_wo[217];
+ break;
+ case 6:
+ ++_wo[197];
+ break;
+ case 8:
+ ++_wo[137];
+ break;
+ case 9:
+ ++_wo[297];
+ break;
+ case 10:
+ ++_wo[237];
+ case 11:
+ ++_wo[157];
+ break;
+ case 13:
+ ++_wo[257];
+ break;
+ case 14:
+ ++_wo[277];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(11) - 1) {
+ case 1:
+ ++_wo[60];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[16];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(12) - 1) {
+ case 0:
+ ++_wo[118];
+ break;
+ case 1:
+ ++_wo[56];
+ break;
+ case 2:
+ ++_wo[178];
+ break;
+ case 3:
+ case 12:
+ ++_wo[98];
+ break;
+ case 4:
+ case 7:
+ ++_wo[12];
+ break;
+ case 5:
+ ++_wo[218];
+ break;
+ case 6:
+ ++_wo[198];
+ break;
+ case 8:
+ ++_wo[138];
+ break;
+ case 9:
+ ++_wo[298];
+ break;
+ case 10:
+ ++_wo[238];
+ break;
+ case 11:
+ ++_wo[158];
+ break;
+ case 13:
+ ++_wo[258];
+ break;
+ case 14:
+ ++_wo[278];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(13) - 1) {
+ case 1:
+ ++_wo[61];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[17];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(14) - 1) {
+ case 0:
+ ++_wo[121];
+ break;
+ case 1:
+ ++_wo[59];
+ break;
+ case 2:
+ ++_wo[181];
+ break;
+ case 3:
+ case 12:
+ ++_wo[101];
+ break;
+ case 4:
+ case 7:
+ ++_wo[15];
+ break;
+ case 5:
+ ++_wo[221];
+ break;
+ case 6:
+ ++_wo[201];
+ break;
+ case 8:
+ ++_wo[141];
+ break;
+ case 9:
+ ++_wo[301];
+ break;
+ case 10:
+ ++_wo[241];
+ break;
+ case 11:
+ ++_wo[161];
+ break;
+ case 13:
+ ++_wo[261];
+ break;
+ case 14:
+ ++_wo[281];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(15) - 1) {
+ case 1:
+ ++_wo[63];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[19];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(16) - 1) {
+ case 0:
+ ++_wo[120];
+ break;
+ case 1:
+ ++_wo[58];
+ break;
+ case 2:
+ ++_wo[180];
+ break;
+ case 3:
+ case 12:
+ ++_wo[100];
+ break;
+ case 4:
+ case 7:
+ ++_wo[14];
+ break;
+ case 5:
+ ++_wo[220];
+ break;
+ case 6:
+ ++_wo[200];
+ break;
+ case 8:
+ ++_wo[140];
+ break;
+ case 9:
+ ++_wo[300];
+ break;
+ case 10:
+ ++_wo[240];
+ break;
+ case 11:
+ ++_wo[160];
+ break;
+ case 13:
+ ++_wo[260];
+ break;
+ case 14:
+ ++_wo[280];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(17) - 1) {
+ case 1:
+ ++_wo[62];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[18];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(18) - 1) {
+ case 0:
+ ++_wo[119];
+ break;
+ case 1:
+ ++_wo[57];
+ break;
+ case 2:
+ ++_wo[179];
+ break;
+ case 3:
+ case 12:
+ ++_wo[99];
+ break;
+ case 4:
+ case 7:
+ ++_wo[13];
+ break;
+ case 5:
+ ++_wo[219];
+ break;
+ case 6:
+ ++_wo[199];
+ break;
+ case 8:
+ ++_wo[139];
+ break;
+ case 9:
+ ++_wo[299];
+ break;
+ case 10:
+ ++_wo[239];
+ break;
+ case 11:
+ ++_wo[159];
+ break;
+ case 13:
+ ++_wo[259];
+ break;
+ case 14:
+ ++_wo[279];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(19) - 1) {
+ case 0:
+ ++_wo[108];
+ break;
+ case 1:
+ ++_wo[78];
+ break;
+ case 2:
+ ++_wo[168];
+ case 3:
+ case 12:
+ ++_wo[88];
+ break;
+ case 4:
+ case 7:
+ ++_wo[34];
+ break;
+ case 5:
+ ++_wo[208];
+ break;
+ case 6:
+ ++_wo[188];
+ break;
+ case 8:
+ ++_wo[128];
+ break;
+ case 9:
+ ++_wo[288];
+ break;
+ case 10:
+ ++_wo[228];
+ break;
+ case 11:
+ ++_wo[148];
+ break;
+ case 13:
+ ++_wo[248];
+ break;
+ case 14:
+ ++_wo[268];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(20) - 1) {
+ case 1:
+ ++_wo[76];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[32];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(21) - 1) {
+ case 0:
+ ++_wo[109];
+ break;
+ case 1:
+ ++_wo[44];
+ break;
+ case 2:
+ ++_wo[169];
+ break;
+ case 3:
+ case 12:
+ ++_wo[89];
+ break;
+ case 4:
+ case 7:
+ ++_wo[0];
+ break;
+ case 5:
+ ++_wo[209];
+ break;
+ case 6:
+ ++_wo[189];
+ break;
+ case 8:
+ ++_wo[129];
+ break;
+ case 9:
+ ++_wo[289];
+ break;
+ case 10:
+ ++_wo[229];
+ break;
+ case 11:
+ ++_wo[149];
+ break;
+ case 13:
+ ++_wo[249];
+ break;
+ case 14:
+ ++_wo[269];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(22) - 1) {
+ case 1:
+ ++_wo[74];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[30];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(23) - 1) {
+ case 0:
+ ++_wo[110];
+ break;
+ case 1:
+ ++_wo[45];
+ break;
+ case 2:
+ ++_wo[170];
+ break;
+ case 3:
+ case 12:
+ ++_wo[90];
+ break;
+ case 4:
+ case 7:
+ ++_wo[1];
+ break;
+ case 5:
+ ++_wo[210];
+ break;
+ case 6:
+ ++_wo[190];
+ break;
+ case 8:
+ ++_wo[130];
+ break;
+ case 9:
+ ++_wo[290];
+ break;
+ case 10:
+ ++_wo[230];
+ break;
+ case 11:
+ ++_wo[150];
+ break;
+ case 13:
+ ++_wo[250];
+ break;
+ case 14:
+ ++_wo[270];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(24) - 1) {
+ case 1:
+ ++_wo[52];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[8];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(25) - 1) {
+ case 0:
+ ++_wo[111];
+ break;
+ case 1:
+ ++_wo[46];
+ break;
+ case 2:
+ ++_wo[171];
+ break;
+ case 3:
+ case 12:
+ ++_wo[91];
+ break;
+ case 4:
+ case 7:
+ ++_wo[2];
+ break;
+ case 5:
+ ++_wo[211];
+ break;
+ case 6:
+ ++_wo[191];
+ break;
+ case 8:
+ ++_wo[131];
+ break;
+ case 9:
+ ++_wo[291];
+ break;
+ case 10:
+ ++_wo[231];
+ break;
+ case 11:
+ ++_wo[151];
+ break;
+ case 13:
+ ++_wo[251];
+ break;
+ case 14:
+ ++_wo[271];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(26) - 1) {
+ case 1:
+ ++_wo[51];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[7];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(27) - 1) {
+ case 0:
+ ++_wo[116];
+ break;
+ case 1:
+ ++_wo[50];
+ break;
+ case 2:
+ ++_wo[176];
+ break;
+ case 3:
+ case 12:
+ ++_wo[96];
+ break;
+ case 4:
+ case 7:
+ ++_wo[6];
+ break;
+ case 5:
+ ++_wo[216];
+ break;
+ case 6:
+ ++_wo[196];
+ break;
+ case 8:
+ ++_wo[136];
+ break;
+ case 9:
+ ++_wo[296];
+ break;
+ case 10:
+ ++_wo[236];
+ break;
+ case 11:
+ ++_wo[156];
+ break;
+ case 13:
+ ++_wo[256];
+ break;
+ case 14:
+ ++_wo[276];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(28) - 1) {
+ case 1:
+ ++_wo[53];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[9];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(29) - 1) {
+ case 0:
+ ++_wo[115];
+ break;
+ case 1:
+ ++_wo[49];
+ break;
+ case 2:
+ ++_wo[175];
+ break;
+ case 3:
+ case 12:
+ ++_wo[95];
+ break;
+ case 4:
+ case 7:
+ ++_wo[5];
+ break;
+ case 5:
+ ++_wo[215];
+ break;
+ case 6:
+ ++_wo[195];
+ break;
+ case 8:
+ ++_wo[135];
+ break;
+ case 9:
+ ++_wo[295];
+ break;
+ case 10:
+ ++_wo[235];
+ break;
+ case 11:
+ ++_wo[155];
+ break;
+ case 13:
+ ++_wo[255];
+ break;
+ case 14:
+ ++_wo[275];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(30) - 1) {
+ case 1:
+ ++_wo[54];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[10];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(31) - 1) {
+ case 0:
+ ++_wo[114];
+ break;
+ case 1:
+ ++_wo[48];
+ break;
+ case 2:
+ ++_wo[174];
+ break;
+ case 3:
+ case 12:
+ ++_wo[94];
+ break;
+ case 4:
+ case 7:
+ ++_wo[4];
+ break;
+ case 5:
+ ++_wo[214];
+ break;
+ case 6:
+ ++_wo[194];
+ break;
+ case 8:
+ ++_wo[134];
+ break;
+ case 9:
+ ++_wo[294];
+ break;
+ case 10:
+ ++_wo[234];
+ break;
+ case 11:
+ ++_wo[154];
+ break;
+ case 13:
+ ++_wo[254];
+ break;
+ case 14:
+ ++_wo[274];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(32) - 1) {
+ case 1:
+ ++_wo[75];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[31];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(33) - 1) {
+ case 0:
+ ++_wo[112];
+ break;
+ case 1:
+ ++_wo[47];
+ break;
+ case 2:
+ ++_wo[172];
+ break;
+ case 3:
+ case 12:
+ ++_wo[92];
+ break;
+ case 4:
+ case 7:
+ ++_wo[3];
+ break;
+ case 5:
+ ++_wo[212];
+ break;
+ case 6:
+ ++_wo[192];
+ break;
+ case 8:
+ ++_wo[132];
+ break;
+ case 9:
+ ++_wo[292];
+ break;
+ case 10:
+ ++_wo[232];
+ break;
+ case 11:
+ ++_wo[152];
+ break;
+ case 13:
+ ++_wo[252];
+ break;
+ case 14:
+ ++_wo[272];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(34) - 1) {
+ case 1:
+ ++_wo[77];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[33];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(35) - 1) {
+ case 0:
+ ++_wo[113];
+ break;
+ case 1:
+ ++_wo[79];
+ break;
+ case 2:
+ ++_wo[173];
+ break;
+ case 3:
+ case 12:
+ ++_wo[93];
+ break;
+ case 4:
+ case 7:
+ ++_wo[35];
+ break;
+ case 5:
+ ++_wo[213];
+ break;
+ case 6:
+ ++_wo[193];
+ break;
+ case 8:
+ ++_wo[133];
+ break;
+ case 9:
+ ++_wo[293];
+ break;
+ case 10:
+ ++_wo[233];
+ break;
+ case 11:
+ ++_wo[153];
+ break;
+ case 13:
+ ++_wo[253];
+ break;
+ case 14:
+ ++_wo[273];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(36) - 1) {
+ case 1:
+ ++_wo[83];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[39];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(37) - 1) {
+ case 1:
+ ++_wo[82];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[38];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(38) - 1) {
+ case 1:
+ ++_wo[81];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[37];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(39) - 1) {
+ case 1:
+ ++_wo[80];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[36];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(40) - 1) {
+ case 1:
+ ++_wo[84];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[40];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(41) - 1) {
+ case 1:
+ ++_wo[85];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[41];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(42) - 1) {
+ case 1:
+ ++_wo[86];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[42];
+ break;
+ default:
+ break;
+ }
+
+ switch (_vm->_map->getCell(43) - 1) {
+ case 1:
+ ++_wo[87];
+ break;
+ case 0:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ ++_wo[43];
+ break;
+ default:
+ break;
+ }
+}
+
+void InterfaceMap::setIndoorsMonsters() {
+ Combat &combat = *_vm->_combat;
+ Map &map = *_vm->_map;
+ Common::Point mazePos = _vm->_party->_mazePosition;
+ Direction dir = _vm->_party->_mazeDirection;
+
+ combat.clear();
+ for (uint monsterIdx = 0; monsterIdx < map._mobData._monsters.size(); ++monsterIdx) {
+ MazeMonster &monster = map._mobData._monsters[monsterIdx];
+ SpriteResource *sprites = monster._sprites;
+ int frame = monster._frame;
+
+ if (frame >= 8) {
+ sprites = monster._attackSprites;
+ frame -= 8;
+ }
+
+ // The following long sequence sets up monsters in the various positions
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][2]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][2])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[0] == -1) {
+ combat._attackMonsters[0] = monsterIdx;
+ setMonsterSprite(_indoorList[156], monster, sprites, frame, INDOOR_MONSTERS_Y[0]);
+ } else if (combat._attackMonsters[1] == -1) {
+ combat._attackMonsters[1] = monsterIdx;
+ setMonsterSprite(_indoorList[150], monster, sprites, frame, INDOOR_MONSTERS_Y[0]);
+ } else if (combat._attackMonsters[2] == -1) {
+ combat._attackMonsters[2] = monsterIdx;
+ setMonsterSprite(_indoorList[153], monster, sprites, frame, INDOOR_MONSTERS_Y[0]);
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][7]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][7])) {
+ monster._isAttacking = true;
+ if (!_wo[27]) {
+ if (combat._attackMonsters[3] == -1) {
+ combat._attackMonsters[3] = monsterIdx;
+ setMonsterSprite(_indoorList[132], monster, sprites, frame, INDOOR_MONSTERS_Y[1]);
+ } else if (combat._attackMonsters[4] == -1) {
+ combat._attackMonsters[4] = monsterIdx;
+ setMonsterSprite(_indoorList[130], monster, sprites, frame, INDOOR_MONSTERS_Y[1]);
+ } else if (combat._attackMonsters[2] == -1) {
+ combat._attackMonsters[5] = monsterIdx;
+ setMonsterSprite(_indoorList[131], monster, sprites, frame, INDOOR_MONSTERS_Y[1]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][5]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][5])) {
+ if (_wo[27] && _wo[25]) {
+ } else if (_wo[27] && _wo[28]) {
+ } else if (_wo[23] & _wo[25]) {
+ } else if (_wo[23] && _wo[28]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[12] == -1) {
+ combat._attackMonsters[12] = monsterIdx;
+ setMonsterSprite(_indoorList[128], monster, sprites, frame, INDOOR_MONSTERS_Y[1]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][9]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][9])) {
+ if (_wo[27] && _wo[26]) {
+ } else if (_wo[27] && _wo[29]) {
+ } else if (_wo[24] & _wo[26]) {
+ } else if (_wo[24] && _wo[29]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[13] == -1) {
+ combat._attackMonsters[13] = monsterIdx;
+ setMonsterSprite(_indoorList[129], monster, sprites, frame, INDOOR_MONSTERS_Y[1]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][14]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][14])) {
+ monster._isAttacking = true;
+
+ if (!_wo[22] && !_wo[27]) {
+ if (combat._attackMonsters[6] == -1) {
+ combat._attackMonsters[6] = monsterIdx;
+ setMonsterSprite(_indoorList[106], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ } else if (combat._attackMonsters[7] == -1) {
+ combat._attackMonsters[7] = monsterIdx;
+ setMonsterSprite(_indoorList[104], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ } else if (combat._attackMonsters[8] == -1) {
+ combat._attackMonsters[8] = monsterIdx;
+ setMonsterSprite(_indoorList[105], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][12]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][12])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[22] & _wo[20]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[14] == -1) {
+ combat._attackMonsters[14] = monsterIdx;
+ setMonsterSprite(_indoorList[100], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ } else if (combat._attackMonsters[20] == -1) {
+ combat._attackMonsters[20] = monsterIdx;
+ setMonsterSprite(_indoorList[101], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][16]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][16])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[22] & _wo[21]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[15] == -1) {
+ combat._attackMonsters[15] = monsterIdx;
+ setMonsterSprite(_indoorList[102], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ } else if (combat._attackMonsters[21] == -1) {
+ combat._attackMonsters[21] = monsterIdx;
+ setMonsterSprite(_indoorList[103], monster, sprites, frame, INDOOR_MONSTERS_Y[2]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][27]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][27])) {
+ if (!_wo[27] && !_wo[22] && !_wo[15]) {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[9] == -1) {
+ combat._attackMonsters[9] = monsterIdx;
+ setMonsterSprite(_indoorList[70], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[10] == -1) {
+ combat._attackMonsters[10] = monsterIdx;
+ setMonsterSprite(_indoorList[68], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[11] == -1) {
+ combat._attackMonsters[11] = monsterIdx;
+ setMonsterSprite(_indoorList[69], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][25]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][25])) {
+ if (_wo[27] || _wo[22]) {
+ } else if (_wo[15] && _wo[17]) {
+ } else if (_wo[15] && _wo[12]) {
+ } else if (_wo[12] && _wo[7]) {
+ } else if (_wo[17] && _wo[7]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[16] == -1) {
+ combat._attackMonsters[16] = monsterIdx;
+ setMonsterSprite(_indoorList[62], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[22] == -1) {
+ combat._attackMonsters[22] = monsterIdx;
+ setMonsterSprite(_indoorList[60], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[24] == -1) {
+ combat._attackMonsters[24] = monsterIdx;
+ setMonsterSprite(_indoorList[61], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][23]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][23])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (_wo[12] || _wo[8]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[18] == -1) {
+ combat._attackMonsters[18] = monsterIdx;
+ setMonsterSprite(_indoorList[66], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][29]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][29])) {
+ if (_wo[27] || _wo[22]) {
+ } else if (_wo[15] && _wo[19]) {
+ } else if (_wo[15] && _wo[14]) {
+ } else if (_wo[14] && _wo[9]) {
+ } else if (_wo[19] && _wo[9]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[17] == -1) {
+ combat._attackMonsters[17] = monsterIdx;
+ setMonsterSprite(_indoorList[65], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[23] == -1) {
+ combat._attackMonsters[23] = monsterIdx;
+ setMonsterSprite(_indoorList[63], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[25] == -1) {
+ combat._attackMonsters[25] = monsterIdx;
+ setMonsterSprite(_indoorList[64], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ }
+ }
+ }
+
+ if (monster._position.x == (mazePos.x + SCREEN_POSITIONING_X[dir][31]) &&
+ monster._position.y == (mazePos.y + SCREEN_POSITIONING_Y[dir][31])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (_wo[14] || _wo[10]) {
+ } else {
+ monster._isAttacking = true;
+
+ if (combat._attackMonsters[19] == -1) {
+ combat._attackMonsters[19] = monsterIdx;
+ setMonsterSprite(_indoorList[67], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[23] == -1) {
+ combat._attackMonsters[23] = monsterIdx;
+ setMonsterSprite(_indoorList[63], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ } else if (combat._attackMonsters[25] == -1) {
+ combat._attackMonsters[25] = monsterIdx;
+ setMonsterSprite(_indoorList[64], monster, sprites, frame, INDOOR_MONSTERS_Y[3]);
+ }
+ }
+ }
+ }
+
+ _indoorList[153]._x += 58;
+ _indoorList[131]._x += 25;
+ _indoorList[105]._x += 9;
+ _indoorList[69]._x--;
+ _indoorList[61]._x -= 26;
+ _indoorList[64]._x += 23;
+ _indoorList[66]._x -= 58;
+ _indoorList[67]._x += 40;
+ _indoorList[100]._x -= 65;
+ _indoorList[101]._x -= 85;
+ _indoorList[102]._x += 49;
+ _indoorList[103]._x += 65;
+ _indoorList[128]._x -= 112;
+ _indoorList[129]._x += 98;
+
+ if (combat._attackMonsters[1] != -1 && combat._attackMonsters[2] == -1) {
+ _indoorList[156]._x += 31;
+ _indoorList[150]._x -= 36;
+ } else {
+ _indoorList[156]._x -= 5;
+ _indoorList[150]._x -= 67;
+ }
+
+ if (combat._attackMonsters[4] != -1 && combat._attackMonsters[5] == -1) {
+ _indoorList[132]._x += 8;
+ _indoorList[130]._x -= 23;
+ } else {
+ _indoorList[132]._x -= 7;
+ _indoorList[130]._x -= 38;
+ }
+
+ if (combat._attackMonsters[7] != -1 && combat._attackMonsters[8] == -1) {
+ _indoorList[104]._x -= 16;
+ } else {
+ _indoorList[106]._x -= 8;
+ _indoorList[104]._x -= 24;
+ }
+
+ if (combat._attackMonsters[10] != -1 && combat._attackMonsters[11] == -1) {
+ _indoorList[70]._x -= 5;
+ _indoorList[68]._x -= 13;
+ } else {
+ _indoorList[70]._x -= 9;
+ _indoorList[68]._x -= 17;
+ }
+
+ if (combat._attackMonsters[22] == -1 && combat._attackMonsters[24] == -1) {
+ _indoorList[62]._x -= 27;
+ _indoorList[60]._x -= 37;
+ } else {
+ _indoorList[62]._x -= 34;
+ _indoorList[60]._x -= 41;
+ }
+
+ if (combat._attackMonsters[23] != -1 && combat._attackMonsters[25] == -1) {
+ _indoorList[65]._x += 20;
+ _indoorList[63]._x -= 12;
+ } else {
+ _indoorList[65]._x += 16;
+ _indoorList[63]._x -= 16;
+ }
+}
+
+void InterfaceMap::setMonsterSprite(DrawStruct &drawStruct, MazeMonster &monster, SpriteResource *sprites,
+ int frame, int defaultY) {
+ MonsterStruct &monsterData = *monster._monsterData;
+ bool flying = monsterData._flying;
+
+ drawStruct._frame = frame;
+ drawStruct._sprites = sprites;
+ drawStruct._y = defaultY;
+
+ if (flying) {
+ drawStruct._x = COMBAT_FLOAT_X[_combatFloatCounter];
+ drawStruct._y = COMBAT_FLOAT_Y[_combatFloatCounter];
+ } else {
+ drawStruct._x = 0;
+ }
+
+ drawStruct._flags &= ~0xFFF;
+ if (monster._effect2)
+ drawStruct._flags = MONSTER_EFFECT_FLAGS[monster._effect2][monster._effect3];
+}
+
+void InterfaceMap::setIndoorsObjects() {
+ Common::Point mazePos = _vm->_party->_mazePosition;
+ Direction dir = _vm->_party->_mazeDirection;
+ Common::Point pt;
+ _objNumber = 0;
+
+ Common::Array<MazeObject> &objects = _vm->_map->_mobData._objects;
+ for (uint idx = 0; idx < objects.size(); ++idx) {
+ MazeObject &mazeObject = objects[idx];
+
+ // Determine which half of the X/Y lists to use
+ int listOffset;
+ if (_vm->_files->_isDarkCc) {
+ listOffset = mazeObject._spriteId == 47 ? 1 : 0;
+ } else {
+ listOffset = mazeObject._spriteId == 113 ? 1 : 0;
+ }
+
+ // Position 1
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][2]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][2]) == mazeObject._position.y
+ && _indoorList._objects0._frame == -1) {
+ _indoorList._objects0._x = INDOOR_OBJECT_X[listOffset][0];
+ _indoorList._objects0._y = MAP_OBJECT_Y[listOffset][0];
+ _indoorList._objects0._frame = mazeObject._frame;
+ _indoorList._objects0._sprites = mazeObject._sprites;
+ _indoorList._objects0._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects0._flags |= SPRFLAG_HORIZ_FLIPPED;
+ _objNumber = idx;
+ }
+
+ // Position 2
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][7]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][7]) == mazeObject._position.y
+ && !_wo[27] && _indoorList._objects1._frame == -1) {
+ _indoorList._objects1._x = INDOOR_OBJECT_X[listOffset][1];
+ _indoorList._objects1._y = MAP_OBJECT_Y[listOffset][1];
+ _indoorList._objects1._frame = mazeObject._frame;
+ _indoorList._objects1._sprites = mazeObject._sprites;
+ _indoorList._objects1._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects1._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ // Position 3
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][5]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][5]) == mazeObject._position.y) {
+ if (_wo[27] && _wo[25]) {
+ } else if (_wo[27] && _wo[28]) {
+ } else if (_wo[23] && _wo[25]) {
+ } else if (_wo[23] && _wo[28]) {
+ } else if (_indoorList._objects2._frame == -1) {
+ _indoorList._objects2._x = INDOOR_OBJECT_X[listOffset][2];
+ _indoorList._objects2._y = MAP_OBJECT_Y[listOffset][2];
+ _indoorList._objects2._frame = mazeObject._frame;
+ _indoorList._objects2._sprites = mazeObject._sprites;
+ _indoorList._objects2._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects2._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 4
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][9]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][9]) == mazeObject._position.y) {
+ if (_wo[27] && _wo[26]) {
+ } else if (_wo[27] && _wo[29]) {
+ } else if (_wo[24] && _wo[26]) {
+ } else if (_wo[24] && _wo[29]) {
+ } else if (_indoorList._objects3._frame == -1) {
+ _indoorList._objects3._x = INDOOR_OBJECT_X[listOffset][3];
+ _indoorList._objects3._y = MAP_OBJECT_Y[listOffset][3];
+ _indoorList._objects3._frame = mazeObject._frame;
+ _indoorList._objects3._sprites = mazeObject._sprites;
+ _indoorList._objects3._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects3._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 5
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][14]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][14]) == mazeObject._position.y) {
+ if (!_wo[22] && !_wo[27] && _indoorList._objects4._frame == -1) {
+ _indoorList._objects4._x = INDOOR_OBJECT_X[listOffset][4];
+ _indoorList._objects4._y = MAP_OBJECT_Y[listOffset][4];
+ _indoorList._objects4._frame = mazeObject._frame;
+ _indoorList._objects4._sprites = mazeObject._sprites;
+ _indoorList._objects4._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects4._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 6
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][12]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][12]) == mazeObject._position.y) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_indoorList._objects5._frame == -1) {
+ _indoorList._objects5._x = INDOOR_OBJECT_X[listOffset][5];
+ _indoorList._objects5._y = MAP_OBJECT_Y[listOffset][5];
+ _indoorList._objects5._frame = mazeObject._frame;
+ _indoorList._objects5._sprites = mazeObject._sprites;
+ _indoorList._objects5._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects5._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 7
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][16]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][16]) == mazeObject._position.y) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_indoorList._objects6._frame == -1) {
+ _indoorList._objects6._x = INDOOR_OBJECT_X[listOffset][6];
+ _indoorList._objects6._y = MAP_OBJECT_Y[listOffset][6];
+ _indoorList._objects6._frame = mazeObject._frame;
+ _indoorList._objects6._sprites = mazeObject._sprites;
+ _indoorList._objects6._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects6._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 8
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][27]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][27]) == mazeObject._position.y) {
+ if (!_wo[27] && !_wo[22] && !_wo[15] && _indoorList._objects7._frame == -1) {
+ _indoorList._objects7._x = INDOOR_OBJECT_X[listOffset][7];
+ _indoorList._objects7._y = MAP_OBJECT_Y[listOffset][7];
+ _indoorList._objects7._frame = mazeObject._frame;
+ _indoorList._objects7._sprites = mazeObject._sprites;
+ _indoorList._objects7._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects7._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 9
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][25]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][25]) == mazeObject._position.y) {
+ if (_wo[27] || _wo[22]) {
+ } else if (_wo[15] && _wo[17]) {
+ } else if (_wo[15] && _wo[12]) {
+ } else if (_wo[12] && _wo[7]) {
+ } else if (_wo[17] && _wo[7]) {
+ } else if (_indoorList._objects8._frame == -1) {
+ _indoorList._objects8._x = INDOOR_OBJECT_X[listOffset][8];
+ _indoorList._objects8._y = MAP_OBJECT_Y[listOffset][8];
+ _indoorList._objects8._frame = mazeObject._frame;
+ _indoorList._objects8._sprites = mazeObject._sprites;
+ _indoorList._objects8._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects8._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Position 10
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][23]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][23]) == mazeObject._position.y) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (!_wo[12] && !_wo[8] && _indoorList._objects9._frame == -1) {
+ _indoorList._objects9._x = INDOOR_OBJECT_X[listOffset][10];
+ _indoorList._objects9._y = MAP_OBJECT_Y[listOffset][10];
+ _indoorList._objects9._frame = mazeObject._frame;
+ _indoorList._objects9._sprites = mazeObject._sprites;
+ _indoorList._objects9._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects9._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Block 11
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][29]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][29]) == mazeObject._position.y) {
+ if (_wo[27]) {
+ } else if (_wo[15] && _wo[19]) {
+ } else if (_wo[15] && _wo[14]) {
+ } else if (_wo[14] && _wo[9]) {
+ } else if (_wo[19] && _wo[9]) {
+ } else if (_indoorList._objects10._frame == -1) {
+ _indoorList._objects10._x = INDOOR_OBJECT_X[listOffset][9];
+ _indoorList._objects10._y = MAP_OBJECT_Y[listOffset][9];
+ _indoorList._objects10._frame = mazeObject._frame;
+ _indoorList._objects10._sprites = mazeObject._sprites;
+ _indoorList._objects10._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects10._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+
+ // Block 12
+ if ((mazePos.x + SCREEN_POSITIONING_X[dir][31]) == mazeObject._position.x
+ && (mazePos.y + SCREEN_POSITIONING_Y[dir][31]) == mazeObject._position.y) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (!_wo[14] && !_wo[10] && _indoorList._objects11._frame == -1) {
+ _indoorList._objects11._x = INDOOR_OBJECT_X[listOffset][11];
+ _indoorList._objects11._y = MAP_OBJECT_Y[listOffset][11];
+ _indoorList._objects11._frame = mazeObject._frame;
+ _indoorList._objects11._sprites = mazeObject._sprites;
+ _indoorList._objects11._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (mazeObject._flipped)
+ _indoorList._objects11._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+ }
+}
+
+void InterfaceMap::setIndoorsWallPics() {
+ Map &map = *_vm->_map;
+ const Common::Point &mazePos = _vm->_party->_mazePosition;
+ Direction dir = _vm->_party->_mazeDirection;
+
+ Common::fill(&_wp[0], &_wp[20], -1);
+
+ for (uint idx = 0; idx < map._mobData._wallItems.size(); ++idx) {
+ MazeWallItem &wallItem = map._mobData._wallItems[idx];
+ if (wallItem._direction != dir)
+ continue;
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][2]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][2])) {
+ if (_wp[1] == -1) {
+ _indoorList[148]._frame = wallItem._frame;
+ _indoorList[148]._sprites = wallItem._sprites;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][7]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][7])) {
+ if (!_wo[27] && _wp[1] == -1) {
+ _indoorList[123]._frame = wallItem._frame;
+ _indoorList[123]._sprites = wallItem._sprites;
+ _wp[4] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][5]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][5])) {
+ if (_wo[27] && _wo[25]) {
+ } else if (_wo[27] && _wo[28]) {
+ } else if (_wo[23] && _wo[25]) {
+ } else if (_wo[23] && _wo[28]) {
+ } else if (_wp[3] == -1) {
+ _indoorList[122]._frame = wallItem._frame;
+ _indoorList[122]._sprites = wallItem._sprites;
+ _wp[3] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][9]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][9])) {
+ if (_wo[27] && _wo[26]) {
+ } else if (_wo[27] && _wo[29]) {
+ } else if (_wo[24] && _wo[26]) {
+ } else if (_wo[24] && _wo[29]) {
+ } else if (_wp[5] == -1) {
+ _indoorList[124]._frame = wallItem._frame;
+ _indoorList[124]._sprites = wallItem._sprites;
+ _wp[5] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][14]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][14])) {
+ if (!_wo[22] && !_wo[27] && !_wp[8]) {
+ _indoorList[94]._frame = wallItem._frame;
+ _indoorList[94]._sprites = wallItem._sprites;
+ _wp[8] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][12]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][12])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_wp[7] == -1) {
+ _indoorList[93]._frame = wallItem._frame;
+ _indoorList[93]._sprites = wallItem._sprites;
+ _wp[7] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][16]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][16])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_wp[9] == -1) {
+ _indoorList[95]._frame = wallItem._frame;
+ _indoorList[95]._sprites = wallItem._sprites;
+ _wp[9] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][12]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][12])) {
+ if (_wo[27]) {
+ } else if (_wo[25] && _wo[28]) {
+ } else if (_wo[20] && _wo[16]) {
+ } else if (_wp[6] == -1) {
+ _indoorList[92]._frame = wallItem._frame;
+ _indoorList[92]._sprites = wallItem._sprites;
+ _wp[6] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][16]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][16])) {
+ if (!_wo[26] && !_wo[29] && !_wo[21] && !_wo[18] && _wp[10] == -1) {
+ _indoorList[96]._frame = wallItem._frame;
+ _indoorList[96]._sprites = wallItem._sprites;
+ _wp[10] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][27]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][27])) {
+ if (!_wo[27] && !_wo[22] && !_wo[15] && _wp[15] == -1) {
+ _indoorList[50]._frame = wallItem._frame;
+ _indoorList[50]._sprites = wallItem._sprites;
+ _wp[15] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][25]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][25])) {
+ if (_wo[27]) {
+ } else if (_wo[27] && _wo[22]) {
+ } else if (_wo[15] && _wo[17]) {
+ } else if (_wo[15] && _wo[12]) {
+ } else if (_wo[12] && _wo[7]) {
+ } else if (_wo[17] && _wo[7]) {
+ } else if (_wp[14] == -1) {
+ _indoorList[49]._frame = wallItem._frame;
+ _indoorList[49]._sprites = wallItem._sprites;
+ _wp[14] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][23]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][23])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (_wo[12] && _wo[8]) {
+ } else if (_wp[13] == -1) {
+ _indoorList[48]._frame = wallItem._frame;
+ _indoorList[48]._sprites = wallItem._sprites;
+ _wp[13] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][29]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][29])) {
+ if (_wo[27] || _wo[22]) {
+ } else if (_wo[15] && _wo[19]) {
+ } else if (_wo[15] && _wo[14]) {
+ } else if (_wo[14] && _wo[9]) {
+ } else if (_wo[19] && _wo[9]) {
+ } else if (_wp[16] == -1) {
+ _indoorList[51]._frame = wallItem._frame;
+ _indoorList[51]._sprites = wallItem._sprites;
+ _wp[16] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][31]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][31])) {
+ if (_wo[27]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (!_wo[14] && !_wo[10] && _wp[17] == -1) {
+ _indoorList[52]._frame = wallItem._frame;
+ _indoorList[52]._sprites = wallItem._sprites;
+ _wp[17] = idx;
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][23]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][23])) {
+ if (!_wo[27] && !_wo[20] && !_wo[12] && !_wo[23] && !_wo[8] && !_wo[30]) {
+ if (_wp[12] == -1) {
+ _indoorList[47]._frame = wallItem._frame;
+ _indoorList[47]._sprites = wallItem._sprites;
+ _wp[12] = idx;
+ }
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][31]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][31])) {
+ if (!_wo[27] && !_wo[21] && !_wo[14] && !_wo[24] && !_wo[10] && !_wo[31]) {
+ if (_wp[18] == -1) {
+ _indoorList[53]._frame = wallItem._frame;
+ _indoorList[53]._sprites = wallItem._sprites;
+ _wp[18] = idx;
+ }
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][23]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][23])) {
+ if (!_wo[25] && !_wo[28] && !_wo[20] && !_wo[11] && !_wo[16] && !_wo[30] && !_wo[32]) {
+ if (_wp[11] == -1) {
+ _indoorList[46]._frame = wallItem._frame;
+ _indoorList[46]._sprites = wallItem._sprites;
+ _wp[11] = idx;
+ }
+ }
+ }
+
+ if (mazePos.x == (wallItem._position.x + SCREEN_POSITIONING_X[dir][31]) &&
+ mazePos.y == (wallItem._position.y + SCREEN_POSITIONING_Y[dir][31])) {
+ if (!_wo[26] && !_wo[20] && !_wo[21] && !_wo[13] && !_wo[18] && !_wo[31] && !_wo[33]) {
+ if (_wp[19] == -1) {
+ _indoorList[54]._frame = wallItem._frame;
+ _indoorList[54]._sprites = wallItem._sprites;
+ _wp[19] = idx;
+ }
+ }
+ }
+ }
+}
+
+void InterfaceMap::setOutdoorsMonsters() {
+ Combat &combat = *_vm->_combat;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Direction dir = party._mazeDirection;
+ Common::Point pt = party._mazePosition;
+
+ for (uint idx = 0; idx < map._mobData._monsters.size(); ++idx) {
+ MazeMonster &monster = map._mobData._monsters[idx];
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][2]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][2])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[0] == -1) {
+ _outdoorList[118]._frame = idx;
+ combat._attackMonsters[0] = idx;
+ } else if (combat._attackMonsters[1] == -1) {
+ _outdoorList[112]._frame = idx;
+ combat._attackMonsters[1] = idx;
+ } else if (combat._attackMonsters[2] == -1) {
+ _outdoorList[115]._frame = idx;
+ combat._attackMonsters[2] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][7]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][7])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[3] == -1) {
+ _outdoorList[94]._frame = idx;
+ combat._attackMonsters[3] = idx;
+ } else if (combat._attackMonsters[4] == -1) {
+ _outdoorList[92]._frame = idx;
+ combat._attackMonsters[4] = idx;
+ } else if (combat._attackMonsters[5] == -1) {
+ _outdoorList[93]._frame = idx;
+ combat._attackMonsters[5] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][5]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][5])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[12] == -1) {
+ _outdoorList[90]._frame = idx;
+ combat._attackMonsters[12] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][9]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][9])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[13] == -1) {
+ _outdoorList[91]._frame = idx;
+ combat._attackMonsters[13] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][14]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][14])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[6] == -1) {
+ _outdoorList[75]._frame = idx;
+ combat._attackMonsters[6] = idx;
+ } else if (combat._attackMonsters[7] == -1) {
+ _outdoorList[73]._frame = idx;
+ combat._attackMonsters[7] = idx;
+ } else if (combat._attackMonsters[8] == -1) {
+ _outdoorList[74]._frame = idx;
+ combat._attackMonsters[8] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][12]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][12])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[14] == -1) {
+ _outdoorList[69]._frame = idx;
+ combat._attackMonsters[14] = idx;
+ } else if (combat._attackMonsters[20] == -1) {
+ _outdoorList[70]._frame = idx;
+ combat._attackMonsters[20] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][16]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][16])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[15] == -1) {
+ _outdoorList[71]._frame = idx;
+ combat._attackMonsters[15] = idx;
+ } else if (combat._attackMonsters[21] == -1) {
+ _outdoorList[72]._frame = idx;
+ combat._attackMonsters[21] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][27]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][27])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[9] == -1) {
+ _outdoorList[52]._frame = idx;
+ combat._attackMonsters[9] = idx;
+ } else if (combat._attackMonsters[10] == -1) {
+ _outdoorList[50]._frame = idx;
+ combat._attackMonsters[10] = idx;
+ } else if (combat._attackMonsters[11] == -1) {
+ _outdoorList[51]._frame = idx;
+ combat._attackMonsters[11] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][25]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][25])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[16] == -1) {
+ _outdoorList[44]._frame = idx;
+ combat._attackMonsters[16] = idx;
+ } else if (combat._attackMonsters[22] == -1) {
+ _outdoorList[42]._frame = idx;
+ combat._attackMonsters[22] = idx;
+ } else if (combat._attackMonsters[24] == -1) {
+ _outdoorList[43]._frame = idx;
+ combat._attackMonsters[24] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][23]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][23])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[18] == -1) {
+ _outdoorList[48]._frame = idx;
+ combat._attackMonsters[18] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][29]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][29])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[17] == -1) {
+ _outdoorList[47]._frame = idx;
+ combat._attackMonsters[17] = idx;
+ } else if (combat._attackMonsters[23] == -1) {
+ _outdoorList[45]._frame = idx;
+ combat._attackMonsters[23] = idx;
+ } else if (combat._attackMonsters[25] == -1) {
+ _outdoorList[46]._frame = idx;
+ combat._attackMonsters[25] = idx;
+ }
+ }
+
+ if (monster._position.x == (pt.x + SCREEN_POSITIONING_X[dir][31]) &&
+ monster._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][31])) {
+ monster._isAttacking = true;
+ if (combat._attackMonsters[19] == -1) {
+ _outdoorList[49]._frame = idx;
+ combat._attackMonsters[19] = idx;
+ }
+ }
+ }
+
+ _outdoorList[115]._x = 58;
+ _outdoorList[93]._x = 25;
+ _outdoorList[74]._x = 9;
+ _outdoorList[51]._x = -1;
+ _outdoorList[43]._x = -26;
+ _outdoorList[46]._x = 23;
+ _outdoorList[48]._x = -58;
+ _outdoorList[49]._x = 40;
+ _outdoorList[69]._x = -65;
+ _outdoorList[70]._x = -85;
+ _outdoorList[71]._x = 49;
+ _outdoorList[72]._x = 65;
+ _outdoorList[90]._x = -112;
+ _outdoorList[91]._x = 98;
+
+ if (combat._attackMonsters[1] != -1 && combat._attackMonsters[2] == -1) {
+ _outdoorList[118]._x = 31;
+ _outdoorList[112]._x = -36;
+ } else {
+ _outdoorList[118]._x = -5;
+ _outdoorList[112]._x = -67;
+ }
+ if (combat._attackMonsters[4] != -1 && combat._attackMonsters[5] == -1) {
+ _outdoorList[94]._x = 8;
+ _outdoorList[92]._x = -23;
+ } else {
+ _outdoorList[94]._x = -7;
+ _outdoorList[92]._x = -38;
+ }
+ if (combat._attackMonsters[7] != -1 && combat._attackMonsters[8] == -1) {
+ _outdoorList[75]._x = 0;
+ _outdoorList[73]._x = -16;
+ } else {
+ _outdoorList[75]._x = -8;
+ _outdoorList[73]._x = -24;
+ }
+ if (combat._attackMonsters[10] != -1 && combat._attackMonsters[11] == -1) {
+ _outdoorList[52]._x = -5;
+ _outdoorList[50]._x = -13;
+ } else {
+ _outdoorList[52]._x = -9;
+ _outdoorList[50]._x = -17;
+ }
+ if (combat._attackMonsters[22] != -1 && combat._attackMonsters[24] == -1) {
+ _outdoorList[44]._x = -27;
+ _outdoorList[42]._x = -37;
+ } else {
+ _outdoorList[44]._x = -34;
+ _outdoorList[42]._x = -41;
+ }
+ if (combat._attackMonsters[23] != -1 && combat._attackMonsters[25] == -1) {
+ _outdoorList[47]._x = 20;
+ _outdoorList[45]._x = -12;
+ } else {
+ _outdoorList[47]._x = 16;
+ _outdoorList[45]._x = -16;
+ }
+
+ for (int idx = 0; idx < 26; ++idx) {
+ DrawStruct &ds = _outdoorList[OUTDOOR_MONSTER_INDEXES[idx]];
+
+ if (ds._frame != -1) {
+ ds._flags &= ~0xfff;
+
+ // TODO: Double-check.. this section looks *weird*
+ MazeMonster &monster = map._mobData._monsters[ds._frame];
+ MonsterStruct &monsterData = *monster._monsterData;
+
+ ds._frame = monster._frame;
+
+ if (monster._effect2) {
+ ds._flags = MONSTER_EFFECT_FLAGS[monster._effect2 - 1][monster._effect3];
+ }
+
+ if (monster._frame > 7) {
+ monster._frame -= 8;
+ ds._sprites = monster._attackSprites;
+ } else {
+ ds._sprites = monster._sprites;
+ }
+
+ ds._y = OUTDOOR_MONSTERS_Y[idx];
+
+ if (monsterData._flying) {
+ ds._x += COMBAT_FLOAT_X[_combatFloatCounter];
+ ds._y += COMBAT_FLOAT_Y[_combatFloatCounter];
+ }
+ }
+ }
+ // TODO
+}
+
+void InterfaceMap::setOutdoorsObjects() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ const Common::Point &pt = party._mazePosition;
+ Direction dir = party._mazeDirection;
+ int posIndex;
+
+ for (uint idx = 0; idx < map._mobData._objects.size(); ++idx) {
+ MazeObject &obj = map._mobData._objects[idx];
+
+ if (_vm->_files->_isDarkCc) {
+ posIndex = obj._spriteId == 47 ? 1 : 0;
+ } else {
+ posIndex = obj._spriteId == 113 ? 1 : 0;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][2]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][2]) &&
+ _outdoorList[111]._frame == -1) {
+ DrawStruct &ds = _outdoorList[111];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][0];
+ ds._y = MAP_OBJECT_Y[posIndex][0];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ _objNumber = idx;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][7]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][7]) &&
+ _outdoorList[87]._frame == -1) {
+ DrawStruct &ds = _outdoorList[87];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][1];
+ ds._y = MAP_OBJECT_Y[posIndex][1];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][5]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][5]) &&
+ _outdoorList[88]._frame == -1) {
+ DrawStruct &ds = _outdoorList[88];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][2];
+ ds._y = MAP_OBJECT_Y[posIndex][2];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][9]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][9]) &&
+ _outdoorList[89]._frame == -1) {
+ DrawStruct &ds = _outdoorList[89];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][3];
+ ds._y = MAP_OBJECT_Y[posIndex][3];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][14]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][14]) &&
+ _outdoorList[66]._frame == -1) {
+ DrawStruct &ds = _outdoorList[66];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][4];
+ ds._y = MAP_OBJECT_Y[posIndex][4];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][12]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][12]) &&
+ _outdoorList[67]._frame == -1) {
+ DrawStruct &ds = _outdoorList[67];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][5];
+ ds._y = MAP_OBJECT_Y[posIndex][5];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][16]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][16]) &&
+ _outdoorList[68]._frame == -1) {
+ DrawStruct &ds = _outdoorList[68];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][6];
+ ds._y = MAP_OBJECT_Y[posIndex][6];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][27]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][27]) &&
+ _outdoorList[37]._frame == -1) {
+ DrawStruct &ds = _outdoorList[37];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][7];
+ ds._y = MAP_OBJECT_Y[posIndex][7];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][25]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][25]) &&
+ _outdoorList[38]._frame == -1) {
+ DrawStruct &ds = _outdoorList[38];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][8];
+ ds._y = MAP_OBJECT_Y[posIndex][8];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][23]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][23]) &&
+ _outdoorList[40]._frame == -1) {
+ DrawStruct &ds = _outdoorList[40];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][10];
+ ds._y = MAP_OBJECT_Y[posIndex][10];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][29]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][29]) &&
+ _outdoorList[39]._frame == -1) {
+ DrawStruct &ds = _outdoorList[39];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][9];
+ ds._y = MAP_OBJECT_Y[posIndex][9];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+
+ if (obj._position.x == (pt.x + SCREEN_POSITIONING_X[dir][31]) &&
+ obj._position.y == (pt.y + SCREEN_POSITIONING_Y[dir][31]) &&
+ _outdoorList[41]._frame == -1) {
+ DrawStruct &ds = _outdoorList[41];
+ ds._x = OUTDOOR_OBJECT_X[posIndex][11];
+ ds._y = MAP_OBJECT_Y[posIndex][11];
+ ds._frame = obj._frame;
+ ds._sprites = obj._sprites;
+
+ ds._flags &= ~SPRFLAG_HORIZ_FLIPPED;
+ if (obj._flipped)
+ ds._flags |= SPRFLAG_HORIZ_FLIPPED;
+ }
+ }
+}
+
+void InterfaceMap::drawIndoors() {
+ Map &map = *_vm->_map;
+ int surfaceId;
+
+ // Draw any surface tiles on top of the default ground
+ for (int cellIndex = 0; cellIndex < 25; ++cellIndex) {
+ map.getCell(DRAW_NUMBERS[cellIndex]);
+
+ DrawStruct &drawStruct = _indoorList._groundTiles[cellIndex];
+ SpriteResource &sprites = map._surfaceSprites[map._currentSurfaceId];
+ drawStruct._sprites = sprites.empty() ? (SpriteResource *)nullptr : &sprites;
+
+ surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
+ if (surfaceId == SURFTYPE_WATER || surfaceId == SURFTYPE_LAVA ||
+ surfaceId == SURFTYPE_SEWER) {
+ drawStruct._frame = DRAW_FRAMES[cellIndex][_flipWater ? 1 : 0];
+ drawStruct._flags = _flipWater ? SPRFLAG_HORIZ_FLIPPED : 0;
+ } else {
+ drawStruct._frame = DRAW_FRAMES[cellIndex][_flipGround ? 1 : 0];
+ drawStruct._flags = _flipGround ? SPRFLAG_HORIZ_FLIPPED : 0;
+ }
+ }
+
+ if (!_wo[27] && !_wo[20] && !_wo[23] && !_wo[12] && !_wo[8] && !_wo[30]) {
+ if (_wo[39])
+ _indoorList._swl_4F4L._frame = 22;
+ else if (_wo[83])
+ _indoorList._swl_4F4L._frame = 46;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[17] && !_wo[12] && !_wo[8]) {
+ if (_wo[38])
+ _indoorList._swl_4F3L._frame = 20;
+ else if (_wo[82])
+ _indoorList._swl_4F3L._frame = 44;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[15] && !_wo[2] && !_wo[7]) {
+ if (_wo[37])
+ _indoorList._swl_4F2L._frame = 18;
+ else if (_wo[81])
+ _indoorList._swl_4F2L._frame = 42;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[15] && !_wo[6]) {
+ if (_wo[36])
+ _indoorList._swl_4F1L._frame = 16;
+ else if (_wo[80])
+ _indoorList._swl_4F1L._frame = 40;
+ }
+
+ if (!_wo[27] && !_wo[21] && !_wo[24] && !_wo[14] && !_wo[10] && !_wo[31]) {
+ if (_wo[43])
+ _indoorList._swl_4F4R._frame = 23;
+ else if (_wo[87])
+ _indoorList._swl_4F4R._frame = 47;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[19] && !_wo[14] && !_wo[10]) {
+ if (_wo[42])
+ _indoorList._swl_4F3R._frame = 21;
+ else if (_wo[86])
+ _indoorList._swl_4F3R._frame = 45;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[15] && !_wo[5] && !_wo[9]) {
+ if (_wo[41])
+ _indoorList._swl_4F2R._frame = 19;
+ else if (_wo[85])
+ _indoorList._swl_4F2R._frame = 43;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[15] && !_wo[6]) {
+ if (_wo[40])
+ _indoorList._swl_4F1R._frame = 17;
+ else if (_wo[84])
+ _indoorList._swl_4F1R._frame = 41;
+ }
+
+ if (!_wo[25] && !_wo[28] && !_wo[20] && !_wo[11] &&
+ !_wo[16] && !_wo[30] && !_wo[32]) {
+ if (_wo[88])
+ _indoorList._fwl_4F4L._frame = 7;
+ else if (_wo[78])
+ _indoorList._fwl_4F4L._frame = 16;
+ else if (_wo[148])
+ _indoorList._fwl_4F4L._frame = _overallFrame + 1;
+ else if (_wo[108])
+ _indoorList._fwl_4F4L._frame = 8;
+ else if (_wo[168])
+ _indoorList._fwl_4F4L._frame = 10;
+ else if (_wo[128])
+ _indoorList._fwl_4F4L._frame = 9;
+ else if (_wo[34])
+ _indoorList._fwl_4F4L._frame = 0;
+ else if (_wo[188])
+ _indoorList._fwl_4F4L._frame = 15;
+ else if (_wo[208])
+ _indoorList._fwl_4F4L._frame = 14;
+ else if (_wo[228])
+ _indoorList._fwl_4F4L._frame = 6;
+ else if (_wo[248])
+ _indoorList._fwl_4F4L._frame = 11;
+ else if (_wo[268])
+ _indoorList._fwl_4F4L._frame = 12;
+ else if (_wo[288])
+ _indoorList._fwl_4F4L._frame = 13;
+ }
+
+ if (!_wo[26] && !_wo[29] && !_wo[21] && !_wo[13] && !_wo[18] && !_wo[31] && !_wo[33]) {
+ if (_wo[93])
+ _indoorList._fwl_4F4R._frame = 7;
+ else if (_wo[79])
+ _indoorList._fwl_4F4R._frame = 16;
+ else if (_wo[153])
+ _indoorList._fwl_4F4R._frame = _overallFrame + 1;
+ else if (_wo[113])
+ _indoorList._fwl_4F4R._frame = 8;
+ else if (_wo[173])
+ _indoorList._fwl_4F4R._frame = 10;
+ else if (_wo[133])
+ _indoorList._fwl_4F4R._frame = 9;
+ else if (_wo[35])
+ _indoorList._fwl_4F4R._frame = 0;
+ else if (_wo[79])
+ _indoorList._fwl_4F4R._frame = 15;
+ else if (_wo[213])
+ _indoorList._fwl_4F4R._frame = 14;
+ else if (_wo[233])
+ _indoorList._fwl_4F4R._frame = 6;
+ else if (_wo[253])
+ _indoorList._fwl_4F4R._frame = 11;
+ else if (_wo[273])
+ _indoorList._fwl_4F4R._frame = 12;
+ else if (_wo[293])
+ _indoorList._fwl_4F4R._frame = 13;
+ }
+
+ if (!_wo[25] && !_wo[28] && !_wo[20] && !_wo[11] && !_wo[16] && !_wo[30]) {
+ if (_wo[32])
+ _indoorList._swl_3F4L._frame = 14;
+ else if (_wo[76])
+ _indoorList._swl_3F4L._frame = 38;
+ }
+
+ if (!_wo[26] && !_wo[29] && !_wo[21] && !_wo[13] && !_wo[18] && !_wo[31]) {
+ if (_wo[33])
+ _indoorList._swl_3F1R._frame = 15;
+ else if (_wo[77])
+ _indoorList._swl_3F1R._frame = 39;
+ }
+
+ if (_wo[28] && _wo[27]) {
+ } else if (_wo[28] && _wo[12]) {
+ } else if (_wo[28] && _wo[23]) {
+ } else if (_wo[28] && _wo[8]) {
+ } else if (_wo[25] && _wo[27]) {
+ } else if (_wo[25] && _wo[12]) {
+ } else if (_wo[25] && _wo[23]) {
+ } else if (_wo[25] && _wo[8]) {
+ } else if (_wo[11] && _wo[27]) {
+ } else if (_wo[11] && _wo[12]) {
+ } else if (_wo[11] && _wo[23]) {
+ } else if (_wo[11] && _wo[8]) {
+ } else if (_wo[17] && _wo[27]) {
+ } else if (_wo[17] && _wo[12]) {
+ } else if (_wo[17] && _wo[23]) {
+ } else if (_wo[17] && _wo[8]) {
+ } else if (_wo[20]) {
+ } else if (_wo[30]) {
+ _indoorList._swl_3F3L._frame = 12;
+ } else if (_wo[74]) {
+ _indoorList._swl_3F3L._frame = 36;
+ }
+
+ if (_wo[29] && _wo[27]) {
+ } else if (_wo[29] && _wo[14]) {
+ } else if (_wo[29] && _wo[24]) {
+ } else if (_wo[29] && _wo[10]) {
+ } else if (_wo[26] && _wo[27]) {
+ } else if (_wo[26] && _wo[14]) {
+ } else if (_wo[26] && _wo[24]) {
+ } else if (_wo[26] && _wo[10]) {
+ } else if (_wo[13] && _wo[27]) {
+ } else if (_wo[13] && _wo[14]) {
+ } else if (_wo[13] && _wo[24]) {
+ } else if (_wo[13] && _wo[10]) {
+ } else if (_wo[19] && _wo[27]) {
+ } else if (_wo[19] && _wo[24]) {
+ } else if (_wo[19] && _wo[10]) {
+ } else if (_wo[21]) {
+ } else if (_wo[31]) {
+ _indoorList._swl_3F2R._frame = 13;
+ } else if (_wo[75]) {
+ _indoorList._swl_3F2R._frame = 37;
+ }
+
+ if (!_wo[27] && !_wo[20] && !_wo[12] && !_wo[23] && !_wo[8] && !_wo[30]) {
+ if (_wo[89])
+ _indoorList._fwl_4F3L._frame = 7;
+ else if (_wo[44])
+ _indoorList._fwl_4F3L._frame = 16;
+ else if (_wo[149])
+ _indoorList._fwl_4F3L._frame = _overallFrame + 1;
+ else if (_wo[109])
+ _indoorList._fwl_4F3L._frame = 8;
+ else if (_wo[169])
+ _indoorList._fwl_4F3L._frame = 10;
+ else if (_wo[129])
+ _indoorList._fwl_4F3L._frame = 9;
+ else if (_wo[0])
+ _indoorList._fwl_4F3L._frame = 0;
+ else if (_wo[189])
+ _indoorList._fwl_4F3L._frame = 15;
+ else if (_wo[209])
+ _indoorList._fwl_4F3L._frame = 14;
+ else if (_wo[229])
+ _indoorList._fwl_4F3L._frame = 6;
+ else if (_wo[249])
+ _indoorList._fwl_4F3L._frame = 11;
+ else if (_wo[269])
+ _indoorList._fwl_4F3L._frame = 12;
+ else if (_wo[289])
+ _indoorList._fwl_4F3L._frame = 13;
+ }
+
+ if (_wo[22] && _wo[20]) {
+ } else if (_wo[22] && _wo[23]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (_wo[12]) {
+ } else if (_wo[8]) {
+ } else if (_wo[90]) {
+ _indoorList._fwl_4F2L._frame = 7;
+ } else if (_wo[45]) {
+ _indoorList._fwl_4F2L._frame = 16;
+ } else if (_wo[150]) {
+ _indoorList._fwl_4F2L._frame = _overallFrame + 1;
+ } else if (_wo[110]) {
+ _indoorList._fwl_4F2L._frame = 8;
+ } else if (_wo[170]) {
+ _indoorList._fwl_4F2L._frame = 10;
+ } else if (_wo[130]) {
+ _indoorList._fwl_4F2L._frame = 9;
+ } else if (_wo[1]) {
+ _indoorList._fwl_4F2L._frame = 0;
+ } else if (_wo[190]) {
+ _indoorList._fwl_4F2L._frame = 15;
+ } else if (_wo[210]) {
+ _indoorList._fwl_4F2L._frame = 14;
+ } else if (_wo[230]) {
+ _indoorList._fwl_4F2L._frame = 6;
+ } else if (_wo[250]) {
+ _indoorList._fwl_4F2L._frame = 11;
+ } else if (_wo[270]) {
+ _indoorList._fwl_4F2L._frame = 12;
+ } else if (_wo[290]) {
+ _indoorList._fwl_4F2L._frame = 13;
+ }
+
+ if (_wo[15] && _wo[17]) {
+ } else if (_wo[15] && _wo[12]) {
+ } else if (_wo[12] && _wo[7]) {
+ } else if (_wo[17] && _wo[7]) {
+ } else if (_wo[91]) {
+ _indoorList._fwl_4F1L._frame = 7;
+ } else if (_wo[46]) {
+ _indoorList._fwl_4F1L._frame = 16;
+ } else if (_wo[151]) {
+ _indoorList._fwl_4F1L._frame = _overallFrame + 1;
+ } else if (_wo[111]) {
+ _indoorList._fwl_4F1L._frame = 8;
+ } else if (_wo[171]) {
+ _indoorList._fwl_4F1L._frame = 10;
+ } else if (_wo[131]) {
+ _indoorList._fwl_4F1L._frame = 9;
+ } else if (_wo[2]) {
+ _indoorList._fwl_4F1L._frame = 0;
+ } else if (_wo[191]) {
+ _indoorList._fwl_4F1L._frame = 15;
+ } else if (_wo[211]) {
+ _indoorList._fwl_4F1L._frame = 14;
+ } else if (_wo[231]) {
+ _indoorList._fwl_4F1L._frame = 6;
+ } else if (_wo[251]) {
+ _indoorList._fwl_4F1L._frame = 11;
+ } else if (_wo[271]) {
+ _indoorList._fwl_4F1L._frame = 12;
+ } else if (_wo[291]) {
+ _indoorList._fwl_4F1L._frame = 13;
+ }
+
+ if (!_wo[27] && !_wo[21] && !_wo[14] && !_wo[24] && !_wo[10] && !_wo[31]) {
+ if (_wo[92]) {
+ _indoorList._fwl_4F3R._frame = 7;
+ } else if (_wo[47]) {
+ _indoorList._fwl_4F3R._frame = 16;
+ } else if (_wo[152]) {
+ _indoorList._fwl_4F3R._frame = _overallFrame + 1;
+ } else if (_wo[112]) {
+ _indoorList._fwl_4F3R._frame = 8;
+ } else if (_wo[172]) {
+ _indoorList._fwl_4F3R._frame = 10;
+ } else if (_wo[132]) {
+ _indoorList._fwl_4F3R._frame = 9;
+ } else if (_wo[3]) {
+ _indoorList._fwl_4F3R._frame = 0;
+ } else if (_wo[192]) {
+ _indoorList._fwl_4F3R._frame = 15;
+ } else if (_wo[212]) {
+ _indoorList._fwl_4F3R._frame = 14;
+ } else if (_wo[232]) {
+ _indoorList._fwl_4F3R._frame = 6;
+ } else if (_wo[252]) {
+ _indoorList._fwl_4F3R._frame = 11;
+ } else if (_wo[272]) {
+ _indoorList._fwl_4F3R._frame = 12;
+ } else if (_wo[292]) {
+ _indoorList._fwl_4F3R._frame = 13;
+ }
+ }
+
+ if (_wo[22] && _wo[21]) {
+ } else if (_wo[22] && _wo[24]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (_wo[14] || _wo[10]) {
+ } else if (_wo[94]) {
+ _indoorList._fwl_4F2R._frame = 7;
+ } else if (_wo[48]) {
+ _indoorList._fwl_4F2R._frame = 16;
+ } else if (_wo[154]) {
+ _indoorList._fwl_4F2R._frame = _overallFrame + 1;
+ } else if (_wo[114]) {
+ _indoorList._fwl_4F2R._frame = 8;
+ } else if (_wo[174]) {
+ _indoorList._fwl_4F2R._frame = 10;
+ } else if (_wo[134]) {
+ _indoorList._fwl_4F2R._frame = 9;
+ } else if (_wo[4]) {
+ _indoorList._fwl_4F2R._frame = 0;
+ } else if (_wo[194]) {
+ _indoorList._fwl_4F2R._frame = 15;
+ } else if (_wo[214]) {
+ _indoorList._fwl_4F2R._frame = 14;
+ } else if (_wo[234]) {
+ _indoorList._fwl_4F2R._frame = 6;
+ } else if (_wo[254]) {
+ _indoorList._fwl_4F2R._frame = 11;
+ } else if (_wo[274]) {
+ _indoorList._fwl_4F2R._frame = 12;
+ } else if (_wo[294]) {
+ _indoorList._fwl_4F2R._frame = 13;
+ }
+
+ if (_wo[15] && _wo[19]) {
+ } else if (_wo[15] && _wo[14]) {
+ } else if (_wo[14] && _wo[9]) {
+ } else if (_wo[19] && _wo[9]) {
+ } else if (_wo[95]) {
+ _indoorList._fwl_4F1R._frame = 7;
+ } else if (_wo[49]) {
+ _indoorList._fwl_4F1R._frame = 16;
+ } else if (_wo[155]) {
+ _indoorList._fwl_4F1R._frame = _overallFrame + 1;
+ } else if (_wo[115]) {
+ _indoorList._fwl_4F1R._frame = 8;
+ } else if (_wo[175]) {
+ _indoorList._fwl_4F1R._frame = 10;
+ } else if (_wo[135]) {
+ _indoorList._fwl_4F1R._frame = 9;
+ } else if (_wo[5]) {
+ _indoorList._fwl_4F1R._frame = 0;
+ } else if (_wo[195]) {
+ _indoorList._fwl_4F1R._frame = 15;
+ } else if (_wo[215]) {
+ _indoorList._fwl_4F1R._frame = 14;
+ } else if (_wo[235]) {
+ _indoorList._fwl_4F1R._frame = 6;
+ } else if (_wo[255]) {
+ _indoorList._fwl_4F1R._frame = 11;
+ } else if (_wo[275]) {
+ _indoorList._fwl_4F1R._frame = 12;
+ } else if (_wo[295]) {
+ _indoorList._fwl_4F1R._frame = 13;
+ }
+
+ if (_wo[27] || _wo[22] || _wo[15] || _wo[96]) {
+ } else if (_wo[50]) {
+ _indoorList._fwl_4F._frame = 16;
+ } else if (_wo[156]) {
+ _indoorList._fwl_4F._frame = _overallFrame + 1;
+ } else if (_wo[116]) {
+ _indoorList._fwl_4F._frame = 8;
+ } else if (_wo[176]) {
+ _indoorList._fwl_4F._frame = 10;
+ } else if (_wo[136]) {
+ _indoorList._fwl_4F._frame = 9;
+ } else if (_wo[6]) {
+ _indoorList._fwl_4F._frame = 0;
+ } else if (_wo[196]) {
+ _indoorList._fwl_4F._frame = 15;
+ } else if (_wo[216]) {
+ _indoorList._fwl_4F._frame = 14;
+ } else if (_wo[236]) {
+ _indoorList._fwl_4F._frame = 6;
+ } else if (_wo[256]) {
+ _indoorList._fwl_4F._frame = 11;
+ } else if (_wo[276]) {
+ _indoorList._fwl_4F._frame = 12;
+ } else if (_wo[296]) {
+ _indoorList._fwl_4F._frame = 13;
+ }
+
+ if (!_wo[27] && !_wo[22] && !_wo[15]) {
+ if (_wo[7])
+ _indoorList._swl_3F1L._frame = 8;
+ else if (_wo[51])
+ _indoorList._swl_3F1L._frame = 32;
+ }
+
+ if (_wo[22] && _wo[23]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[17] && _wo[23]) {
+ } else if (_wo[17] && _wo[20]) {
+ } else if (_wo[8]) {
+ _indoorList._swl_3F2L._frame = 10;
+ } else if (_wo[52]) {
+ _indoorList._swl_3F2L._frame = 34;
+ }
+
+ if (_wo[27] || _wo[22] || _wo[15]) {
+ } else if (_wo[9]) {
+ _indoorList._swl_3F4R._frame = 9;
+ } else if (_wo[53]) {
+ _indoorList._swl_3F4R._frame = 33;
+ }
+
+ if (_wo[22] && _wo[24]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[19] && _wo[24]) {
+ } else if (_wo[19] && _wo[21]) {
+ } else if (_wo[14]) {
+ } else if (_wo[10]) {
+ _indoorList._swl_3F3R._frame = 11;
+ } else if (_wo[54]) {
+ _indoorList._swl_3F3R._frame = 35;
+ }
+
+ if (_wo[25] || _wo[28] || _wo[20] || _wo[16]) {
+ } else if (_wo[97]) {
+ _indoorList._fwl_3F2L._frame = 24;
+ } else if (_wo[55]) {
+ _indoorList._fwl_3F2L._frame = 33;
+ } else if (_wo[137]) {
+ _indoorList._fwl_3F2L._frame = 26;
+ } else if (_wo[157]) {
+ _indoorList._fwl_3F2L._frame = _overallFrame + 18;
+ } else if (_wo[117]) {
+ _indoorList._fwl_3F2L._frame = 25;
+ } else if (_wo[177]) {
+ _indoorList._fwl_3F2L._frame = 27;
+ } else if (_wo[11]) {
+ _indoorList._fwl_3F2L._frame = 17;
+ } else if (_wo[197]) {
+ _indoorList._fwl_3F2L._frame = 32;
+ } else if (_wo[217]) {
+ _indoorList._fwl_3F2L._frame = 31;
+ } else if (_wo[237]) {
+ _indoorList._fwl_3F2L._frame = 23;
+ } else if (_wo[257]) {
+ _indoorList._fwl_3F2L._frame = 28;
+ } else if (_wo[277]) {
+ _indoorList._fwl_3F2L._frame = 29;
+ } else if (_wo[297]) {
+ _indoorList._fwl_3F2L._frame = 30;
+ }
+
+ if (_wo[22] && _wo[23]) {
+ } else if (_wo[22] && _wo[20]) {
+ } else if (_wo[23] && _wo[17]) {
+ } else if (_wo[20] && _wo[17]) {
+ } else if (_wo[98]) {
+ _indoorList._fwl_3F1L._frame = 24;
+ } else if (_wo[56]) {
+ _indoorList._fwl_3F1L._frame = 33;
+ } else if (_wo[178]) {
+ _indoorList._fwl_3F1L._frame = 27;
+ } else if (_wo[118]) {
+ _indoorList._fwl_3F1L._frame = 25;
+ } else if (_wo[158]) {
+ _indoorList._fwl_3F1L._frame = _overallFrame + 18;
+ } else if (_wo[138]) {
+ _indoorList._fwl_3F1L._frame = 26;
+ } else if (_wo[12]) {
+ _indoorList._fwl_3F1L._frame = 17;
+ } else if (_wo[198]) {
+ _indoorList._fwl_3F1L._frame = 32;
+ } else if (_wo[218]) {
+ _indoorList._fwl_3F1L._frame = 31;
+ } else if (_wo[238]) {
+ _indoorList._fwl_3F1L._frame = 23;
+ } else if (_wo[258]) {
+ _indoorList._fwl_3F1L._frame = 28;
+ } else if (_wo[278]) {
+ _indoorList._fwl_3F1L._frame = 29;
+ } else if (_wo[298]) {
+ _indoorList._fwl_3F1L._frame = 30;
+ }
+
+ if (_wo[26] || _wo[29] || _wo[21] || _wo[18]) {
+ } else if (_wo[99]) {
+ _indoorList._fwl_3F2R._frame = 24;
+ } else if (_wo[57]) {
+ _indoorList._fwl_3F2R._frame = 33;
+ } else if (_wo[139]) {
+ _indoorList._fwl_3F2R._frame = 26;
+ } else if (_wo[159]) {
+ _indoorList._fwl_3F2R._frame = _overallFrame + 18;
+ } else if (_wo[119]) {
+ _indoorList._fwl_3F2R._frame = 25;
+ } else if (_wo[179]) {
+ _indoorList._fwl_3F2R._frame = 27;
+ } else if (_wo[13]) {
+ _indoorList._fwl_3F2R._frame = 17;
+ } else if (_wo[199]) {
+ _indoorList._fwl_3F2R._frame = 32;
+ } else if (_wo[219]) {
+ _indoorList._fwl_3F2R._frame = 31;
+ } else if (_wo[239]) {
+ _indoorList._fwl_3F2R._frame = 23;
+ } else if (_wo[259]) {
+ _indoorList._fwl_3F2R._frame = 28;
+ } else if (_wo[279]) {
+ _indoorList._fwl_3F2R._frame = 29;
+ } else if (_wo[299]) {
+ _indoorList._fwl_3F2R._frame = 30;
+ }
+
+ if (_wo[22] && _wo[24]) {
+ } else if (_wo[22] && _wo[21]) {
+ } else if (_wo[24] && _wo[19]) {
+ } else if (_wo[21] && _wo[19]) {
+ } else if (_wo[100]) {
+ _indoorList._fwl_3F1R._frame = 24;
+ } else if (_wo[58]) {
+ _indoorList._fwl_3F1R._frame = 33;
+ } else if (_wo[140]) {
+ _indoorList._fwl_3F1R._frame = 26;
+ } else if (_wo[160]) {
+ _indoorList._fwl_3F1R._frame = _overallFrame + 18;
+ } else if (_wo[120]) {
+ _indoorList._fwl_3F1R._frame = 25;
+ } else if (_wo[180]) {
+ _indoorList._fwl_3F1R._frame = 27;
+ } else if (_wo[14]) {
+ _indoorList._fwl_3F1R._frame = 17;
+ } else if (_wo[200]) {
+ _indoorList._fwl_3F1R._frame = 32;
+ } else if (_wo[220]) {
+ _indoorList._fwl_3F1R._frame = 31;
+ } else if (_wo[240]) {
+ _indoorList._fwl_3F1R._frame = 23;
+ } else if (_wo[260]) {
+ _indoorList._fwl_3F1R._frame = 28;
+ } else if (_wo[280]) {
+ _indoorList._fwl_3F1R._frame = 29;
+ } else if (_wo[300]) {
+ _indoorList._fwl_3F1R._frame = 30;
+ }
+
+ if (_wo[22] || _wo[27]) {
+ } else if (_wo[101]) {
+ _indoorList._fwl_3F._frame = 24;
+ } else if (_wo[59]) {
+ _indoorList._fwl_3F._frame = 33;
+ } else if (_wo[141]) {
+ _indoorList._fwl_3F._frame = 26;
+ } else if (_wo[161]) {
+ _indoorList._fwl_3F._frame = _overallFrame + 18;
+ } else if (_wo[121]) {
+ _indoorList._fwl_3F._frame = 25;
+ } else if (_wo[181]) {
+ _indoorList._fwl_3F._frame = 27;
+ } else if (_wo[15]) {
+ _indoorList._fwl_3F._frame = 17;
+ } else if (_wo[201]) {
+ _indoorList._fwl_3F._frame = 32;
+ } else if (_wo[221]) {
+ _indoorList._fwl_3F._frame = 31;
+ } else if (_wo[241]) {
+ _indoorList._fwl_3F._frame = 23;
+ } else if (_wo[261]) {
+ _indoorList._fwl_3F._frame = 28;
+ } else if (_wo[281]) {
+ _indoorList._fwl_3F._frame = 29;
+ } else if (_wo[301]) {
+ _indoorList._fwl_3F._frame = 30;
+ }
+
+ if (_wo[25] || _wo[28] || _wo[20]) {
+ } else if (_wo[16]) {
+ _indoorList._swl_2F2L._frame = 6;
+ } else if (_wo[60]) {
+ _indoorList._swl_2F2L._frame = 30;
+ }
+
+ if (_wo[27] || _wo[22]) {
+ } else if (_wo[17]) {
+ _indoorList._swl_2F1L._frame = 4;
+ } else if (_wo[61]) {
+ _indoorList._swl_2F1L._frame = 28;
+ }
+
+ if (_wo[26] || _wo[29] || _wo[21]) {
+ } else if (_wo[18]) {
+ _indoorList._swl_2F2R._frame = 7;
+ } else if (_wo[62]) {
+ _indoorList._swl_2F2R._frame = 31;
+ }
+
+ if (_wo[27] || _wo[22]) {
+ } else if (_wo[19]) {
+ _indoorList._swl_2F1R._frame = 5;
+ } else if (_wo[63]) {
+ _indoorList._swl_2F1R._frame = 29;
+ }
+
+ if (_wo[27] && _wo[25]) {
+ } else if (_wo[27] && _wo[28]) {
+ } else if (_wo[23] & _wo[25]) {
+ } else if (_wo[23] && _wo[28]) {
+ } else if (_wo[102]) {
+ _indoorList._fwl_2F1L._frame = 7;
+ } else if (_wo[64]) {
+ _indoorList._fwl_2F1L._frame = 16;
+ } else if (_wo[182]) {
+ _indoorList._fwl_2F1L._frame = 10;
+ } else if (_wo[122]) {
+ _indoorList._fwl_2F1L._frame = 8;
+ } else if (_wo[142]) {
+ _indoorList._fwl_2F1L._frame = 9;
+ } else if (_wo[162]) {
+ _indoorList._fwl_2F1L._frame = _overallFrame + 1;
+ } else if (_wo[20]) {
+ _indoorList._fwl_2F1L._frame = 0;
+ } else if (_wo[202]) {
+ _indoorList._fwl_2F1L._frame = 15;
+ } else if (_wo[222]) {
+ _indoorList._fwl_2F1L._frame = 14;
+ } else if (_wo[242]) {
+ _indoorList._fwl_2F1L._frame = 6;
+ } else if (_wo[262]) {
+ _indoorList._fwl_2F1L._frame = 11;
+ } else if (_wo[282]) {
+ _indoorList._fwl_2F1L._frame = 12;
+ } else if (_wo[302]) {
+ _indoorList._fwl_2F1L._frame = 13;
+ }
+
+ if (_wo[27] && _wo[26]) {
+ } else if (_wo[27] && _wo[29]) {
+ } else if (_wo[24] && _wo[26]) {
+ } else if (_wo[24] && _wo[29]) {
+ } else if (_wo[103]) {
+ _indoorList._fwl_2F1R._frame = 7;
+ } else if (_wo[65]) {
+ _indoorList._fwl_2F1R._frame = 16;
+ } else if (_wo[183]) {
+ _indoorList._fwl_2F1R._frame = 10;
+ } else if (_wo[123]) {
+ _indoorList._fwl_2F1R._frame = 8;
+ } else if (_wo[143]) {
+ _indoorList._fwl_2F1R._frame = 9;
+ } else if (_wo[163]) {
+ _indoorList._fwl_2F1R._frame = _overallFrame + 1;
+ } else if (_wo[21]) {
+ _indoorList._fwl_2F1R._frame = 0;
+ } else if (_wo[203]) {
+ _indoorList._fwl_2F1R._frame = 15;
+ } else if (_wo[223]) {
+ _indoorList._fwl_2F1R._frame = 14;
+ } else if (_wo[243]) {
+ _indoorList._fwl_2F1R._frame = 6;
+ } else if (_wo[263]) {
+ _indoorList._fwl_2F1R._frame = 11;
+ } else if (_wo[283]) {
+ _indoorList._fwl_2F1R._frame = 12;
+ } else if (_wo[303]) {
+ _indoorList._fwl_2F1R._frame = 13;
+ }
+
+ if (_wo[27]) {
+ } else if (_wo[104]) {
+ _indoorList._fwl_2F._frame = 7;
+ } else if (_wo[66]) {
+ _indoorList._fwl_2F._frame = 16;
+ } else if (_wo[184]) {
+ _indoorList._fwl_2F._frame = 10;
+ } else if (_wo[124]) {
+ _indoorList._fwl_2F._frame = 8;
+ } else if (_wo[144]) {
+ _indoorList._fwl_2F._frame = 9;
+ } else if (_wo[164]) {
+ _indoorList._fwl_2F._frame = _overallFrame + 1;
+ } else if (_wo[22]) {
+ _indoorList._fwl_2F._frame = 0;
+ } else if (_wo[204]) {
+ _indoorList._fwl_2F._frame = 15;
+ } else if (_wo[224]) {
+ _indoorList._fwl_2F._frame = 14;
+ } else if (_wo[244]) {
+ _indoorList._fwl_2F._frame = 6;
+ } else if (_wo[264]) {
+ _indoorList._fwl_2F._frame = 11;
+ } else if (_wo[284]) {
+ _indoorList._fwl_2F._frame = 12;
+ } else if (_wo[304]) {
+ _indoorList._fwl_2F._frame = 13;
+ }
+
+ if (_wo[27]) {
+ } else if (_wo[23]) {
+ _indoorList._swl_1F1L._frame = 2;
+ } else if (_wo[67]) {
+ _indoorList._swl_1F1L._frame = 26;
+ }
+
+ if (_wo[27]) {
+ } else if (_wo[24]) {
+ _indoorList._swl_1F1R._frame = 3;
+ } else if (_wo[68]) {
+ _indoorList._swl_1F1R._frame = 27;
+ }
+
+ if (_wo[28]) {
+ } else if (_wo[105] || _wo[25] || _wo[165] || _wo[125] || _wo[185] || _wo[145]) {
+ _indoorList._fwl_1F1L._frame = 0;
+ _indoorList._fwl_1F1L._sprites = &map._wallSprites._fwl1;
+ } else if (_wo[69]) {
+ _indoorList._fwl_1F1L._frame = 9;
+ _indoorList._fwl_1F1L._sprites = &map._wallSprites._fwl2;
+ }
+
+ if (_wo[29]) {
+ } else if (_wo[106] || _wo[26] || _wo[166] || _wo[126] || _wo[186] || _wo[146]) {
+ _indoorList._fwl_1F._frame = 0;
+ _indoorList._fwl_1F._sprites = &map._wallSprites._fwl1;
+ } else if (_wo[70]) {
+ _indoorList._fwl_1F._frame = 9;
+ _indoorList._fwl_1F._sprites = &map._wallSprites._fwl2;
+ }
+
+ if (_wo[107]) {
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ if (!_openDoor)
+ _indoorList._fwl_1F1R._frame = 0;
+ else
+ _indoorList._fwl_1F1R._frame = map.mazeData()._wallKind ? 1 : 10;
+ } else if (_wo[71]) {
+ _indoorList._fwl_1F1R._frame = 9;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[167]) {
+ _indoorList._fwl_1F1R._frame = _overallFrame + 1;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[127]) {
+ _indoorList._fwl_1F1R._frame = 1;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[147]) {
+ _indoorList._fwl_1F1R._frame = 2;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[187]) {
+ _indoorList._fwl_1F1R._frame = 3;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[27]) {
+ _indoorList._fwl_1F1R._frame = 0;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl1;
+ } else if (_wo[207]) {
+ _indoorList._fwl_1F1R._frame = 8;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[227]) {
+ _indoorList._fwl_1F1R._frame = 7;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[247]) {
+ _indoorList._fwl_1F1R._frame = 6;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl1;
+ } else if (_wo[267]) {
+ _indoorList._fwl_1F1R._frame = 4;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[287]) {
+ _indoorList._fwl_1F1R._frame = 5;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ } else if (_wo[307]) {
+ _indoorList._fwl_1F1R._frame = 6;
+ _indoorList._fwl_1F1R._sprites = &map._wallSprites._fwl2;
+ }
+
+ if (_wo[28]) {
+ _indoorList._swl_0F1L._frame = 0;
+ } else if (_wo[72]) {
+ _indoorList._swl_0F1L._frame = 24;
+ }
+
+ if (_wo[29]) {
+ _indoorList._swl_0F1R._frame = 1;
+ } else if (_wo[73]) {
+ _indoorList._swl_0F1R._frame = 25;
+ }
+
+ map.cellFlagLookup(_vm->_party->_mazePosition);
+
+ assert(map._currentSky < 2);
+ _indoorList[0]._sprites = &map._skySprites[map._currentSky];
+ _indoorList[0]._flags = _flipSky ? SPRFLAG_HORIZ_FLIPPED : 0;
+
+ if (_openDoor) {
+ Common::Point pt(
+ _vm->_party->_mazePosition.x + SCREEN_POSITIONING_X[
+ _vm->_party->_mazeDirection][_vm->_party->_mazePosition.x],
+ _vm->_party->_mazePosition.y + SCREEN_POSITIONING_Y[
+ _vm->_party->_mazeDirection][_vm->_party->_mazePosition.y]
+ );
+ map.cellFlagLookup(pt);
+
+ _indoorList._sky2._sprites = &map._skySprites[0];
+ } else {
+ _indoorList._sky2._sprites = _indoorList[0]._sprites;
+ }
+
+ _indoorList._sky2._flags = _flipSky ? SPRFLAG_HORIZ_FLIPPED : 0;
+ _indoorList._ground._flags = _flipDefaultGround ? SPRFLAG_HORIZ_FLIPPED : 0;
+ _indoorList._horizon._frame = 7;
+
+ // Finally draw the darn indoor scene
+ _vm->_screen->_windows[3].drawList(&_indoorList[0], _indoorList.size());
+
+ // Check for any character shooting
+ _isAttacking = false;
+ for (uint idx = 0; idx < _vm->_party->_activeParty.size(); ++idx) {
+ if (_vm->_combat->_shooting[idx])
+ _isAttacking = true;
+ }
+
+ _charsShooting = _isAttacking;
+}
+
+void InterfaceMap::drawOutdoors() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ int surfaceId;
+
+ // Draw any surface tiles on top of the default ground
+ for (int cellIndex = 0; cellIndex < 25; ++cellIndex) {
+ map.getCell(cellIndex == 24 ? 2 : DRAW_NUMBERS[cellIndex]);
+
+ DrawStruct &drawStruct = _outdoorList._groundTiles[cellIndex];
+ SpriteResource &sprites = map._surfaceSprites[map._currentSurfaceId];
+ drawStruct._sprites = sprites.empty() ? (SpriteResource *)nullptr : &sprites;
+
+ surfaceId = map.mazeData()._surfaceTypes[map._currentSurfaceId];
+ if (surfaceId == SURFTYPE_DWATER || surfaceId == SURFTYPE_LAVA) {
+ drawStruct._frame = DRAW_FRAMES[cellIndex][_flipWater ? 1 : 0];
+ drawStruct._flags = _flipWater ? SPRFLAG_HORIZ_FLIPPED : 0;
+ } else {
+ drawStruct._frame = DRAW_FRAMES[cellIndex][_flipGround ? 1 : 0];
+ drawStruct._flags = _flipGround ? SPRFLAG_HORIZ_FLIPPED : 0;
+ }
+ }
+
+ party.handleLight();
+
+ // Set up terrain draw entries
+ const int TERRAIN_INDEXES1[9] = { 44, 36, 37, 38, 45, 43, 42, 41, 39 };
+ const int TERRAIN_INDEXES2[5] = { 22, 24, 31, 29, 26 };
+ const int TERRAIN_INDEXES3[3] = { 11, 16, 13 };
+ const int TERRAIN_INDEXES4[5] = { 5, 9, 7, 0, 4 };
+
+ // Loops to set draw entries for the terrain
+ for (int idx = 0; idx < 9; ++idx) {
+ map.getCell(TERRAIN_INDEXES1[idx]);
+ SpriteResource &spr = map._wallSprites._surfaces[map._currentWall];
+ _outdoorList[28 + idx]._sprites = spr.size() == 0 ? (SpriteResource *)nullptr : &spr;
+ }
+ for (int idx = 0; idx < 5; ++idx) {
+ map.getCell(TERRAIN_INDEXES2[idx]);
+ SpriteResource &spr = map._wallSprites._surfaces[map._currentWall];
+ _outdoorList[61 + idx]._sprites = spr.size() == 0 ? (SpriteResource *)nullptr : &spr;
+ }
+ for (int idx = 0; idx < 3; ++idx) {
+ map.getCell(TERRAIN_INDEXES3[idx]);
+ SpriteResource &spr = map._wallSprites._surfaces[map._currentWall];
+ _outdoorList[84 + idx]._sprites = spr.size() == 0 ? (SpriteResource *)nullptr : &spr;
+ }
+ for (int idx = 0; idx < 5; ++idx) {
+ map.getCell(TERRAIN_INDEXES4[idx]);
+ SpriteResource &spr = map._wallSprites._surfaces[map._currentWall];
+ _outdoorList[103 + idx]._sprites = spr.size() == 0 ? (SpriteResource *)nullptr : &spr;
+ }
+
+ map.getCell(1);
+ SpriteResource &surface = map._wallSprites._surfaces[map._currentWall];
+ _outdoorList[108]._sprites = surface.size() == 0 ? (SpriteResource *)nullptr : &surface;
+ _outdoorList[109]._sprites = _outdoorList[108]._sprites;
+ _outdoorList[110]._sprites = _outdoorList[108]._sprites;
+ _outdoorList._sky1._flags = _outdoorList._sky2._flags = _flipSky ? SPRFLAG_HORIZ_FLIPPED : 0;
+ _outdoorList._groundSprite._flags = _flipWater ? SPRFLAG_HORIZ_FLIPPED : 0;
+
+ // Finally render the outdoor scene
+ screen._windows[3].drawList(&_outdoorList[0], _outdoorList.size());
+
+ // Check for any character shooting
+ _isAttacking = false;
+ for (uint idx = 0; idx < _vm->_party->_activeParty.size(); ++idx) {
+ if (_vm->_combat->_shooting[idx])
+ _isAttacking = true;
+ }
+
+ _charsShooting = _isAttacking;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/interface_map.h b/engines/xeen/interface_map.h
new file mode 100644
index 0000000000..2189aacec1
--- /dev/null
+++ b/engines/xeen/interface_map.h
@@ -0,0 +1,175 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_INTERFACE_MAP_H
+#define XEEN_INTERFACE_MAP_H
+
+#include "common/scummsys.h"
+#include "xeen/map.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+class XeenEngine;
+
+class OutdoorDrawList {
+public:
+ DrawStruct _data[132];
+ DrawStruct &_sky1, &_sky2;
+ DrawStruct &_groundSprite;
+ DrawStruct * const _groundTiles;
+ DrawStruct * const _attackImgs1;
+ DrawStruct * const _attackImgs2;
+ DrawStruct * const _attackImgs3;
+ DrawStruct * const _attackImgs4;
+public:
+ OutdoorDrawList();
+
+ DrawStruct &operator[](int idx) {
+ assert(idx < size());
+ return _data[idx];
+ }
+
+ int size() const { return 132; }
+};
+
+class IndoorDrawList {
+public:
+ DrawStruct _data[170];
+ DrawStruct &_sky1, &_sky2;
+ DrawStruct &_ground;
+ DrawStruct &_horizon;
+ DrawStruct * const _groundTiles;
+ DrawStruct &_swl_0F1R, &_swl_0F1L, &_swl_1F1R, &_swl_1F1L,
+ &_swl_2F2R, &_swl_2F1R, &_swl_2F1L, &_swl_2F2L,
+ &_swl_3F1R, &_swl_3F2R, &_swl_3F3R, &_swl_3F4R,
+ &_swl_3F1L, &_swl_3F2L, &_swl_3F3L, &_swl_3F4L,
+ &_swl_4F4R, &_swl_4F3R, &_swl_4F2R, &_swl_4F1R,
+ &_swl_4F1L, &_swl_4F2L, &_swl_4F3L, &_swl_4F4L;
+ DrawStruct &_fwl_4F4R, &_fwl_4F3R, &_fwl_4F2R, &_fwl_4F1R,
+ &_fwl_4F, &_fwl_4F1L, &_fwl_4F2L, &_fwl_4F3L, &_fwl_4F4L;
+ DrawStruct &_fwl_2F1R, &_fwl_2F, &_fwl_2F1L, &_fwl_3F2R,
+ &_fwl_3F1R, &_fwl_3F, &_fwl_3F1L, &_fwl_3F2L;
+ DrawStruct &_fwl_1F, &_fwl_1F1R, &_fwl_1F1L;
+ DrawStruct &_objects0, &_objects1, &_objects2, &_objects3;
+ DrawStruct &_objects4, &_objects5, &_objects6, &_objects7;
+ DrawStruct &_objects8, &_objects9, &_objects10, &_objects11;
+ DrawStruct * const _attackImgs1;
+ DrawStruct * const _attackImgs2;
+ DrawStruct * const _attackImgs3;
+ DrawStruct * const _attackImgs4;
+public:
+ IndoorDrawList();
+
+ DrawStruct &operator[](int idx) {
+ assert(idx < size());
+ return _data[idx];
+ }
+
+ int size() const { return 170; }
+};
+
+class InterfaceMap {
+private:
+ XeenEngine *_vm;
+ int _combatFloatCounter;
+
+ void initDrawStructs();
+
+ /**
+ * Helper method for setIndoorsMonsters to set a draw structure
+ * with the deatils for a given monster
+ */
+ void setMonsterSprite(DrawStruct &drawStruct, MazeMonster &monster,
+ SpriteResource *sprites, int frame, int defaultY);
+protected:
+ int8 _wp[20];
+ byte _wo[308];
+ bool _flipWater;
+ bool _flipGround;
+ bool _flipSky;
+ bool _flipDefaultGround;
+ bool _thinWall;
+ bool _isAnimReset;
+
+ void setMazeBits();
+
+ /**
+ * Handles animation of monsters, wall items, and combat within the 3d
+ * view by cycling the appropriate frame numbers
+ */
+ void animate3d();
+
+ void drawMap();
+public:
+ OutdoorDrawList _outdoorList;
+ IndoorDrawList _indoorList;
+ SpriteResource _charPowSprites;
+ int _objNumber;
+ int _overallFrame;
+ bool _charsShooting;
+ bool _openDoor;
+ bool _isAttacking;
+public:
+ InterfaceMap(XeenEngine *vm);
+
+ virtual ~InterfaceMap() {}
+
+ /**
+ * Set up draw structures for displaying on-screen monsters
+ */
+ void setIndoorsMonsters();
+
+ /**
+ * Set up draw structures for displaying on-screen objects
+ */
+ void setIndoorsObjects();
+
+ /**
+ * Set up draw structures for displaying on-screen wall items
+ */
+ void setIndoorsWallPics();
+
+ /**
+ * Draw the contents of the current 3d view of an indoor map
+ */
+ void drawIndoors();
+
+ /**
+ * Set up the draw structures for displaying monsters on outdoor maps
+ */
+ void setOutdoorsMonsters();
+
+ /**
+ * Set up the draw structures for displaying objects on outdoor maps
+ */
+ void setOutdoorsObjects();
+
+ /**
+ * Draw the contents of the current 3d view of an outdoor map
+ */
+ void drawOutdoors();
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_INTERFACE_MAP_H */
diff --git a/engines/xeen/items.cpp b/engines/xeen/items.cpp
new file mode 100644
index 0000000000..e8c0249803
--- /dev/null
+++ b/engines/xeen/items.cpp
@@ -0,0 +1,29 @@
+/* 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 "xeen/items.h"
+#include "xeen/resources.h"
+
+namespace Xeen {
+
+
+} // End of namespace Xeen
diff --git a/engines/xeen/items.h b/engines/xeen/items.h
new file mode 100644
index 0000000000..bfbd9e4481
--- /dev/null
+++ b/engines/xeen/items.h
@@ -0,0 +1,34 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_ITEMS_H
+#define XEEN_ITEMS_H
+
+#include "xeen/character.h"
+
+namespace Xeen {
+
+
+
+} // End of namespace Xeen
+
+#endif /* XEEN_ITEMS_H */
diff --git a/engines/xeen/map.cpp b/engines/xeen/map.cpp
new file mode 100644
index 0000000000..6816423a19
--- /dev/null
+++ b/engines/xeen/map.cpp
@@ -0,0 +1,1581 @@
+/* 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 "common/serializer.h"
+#include "xeen/map.h"
+#include "xeen/interface.h"
+#include "xeen/resources.h"
+#include "xeen/saves.h"
+#include "xeen/screen.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+const int MAP_GRID_PRIOR_INDEX[] = { 0, 0, 0, 0, 1, 2, 3, 4, 0 };
+
+const int MAP_GRID_PRIOR_DIRECTION[] = { 0, 1, 2, 3, 1, 2, 3, 0, 0 };
+
+const int MAP_GRID_PRIOR_INDEX2[] = { 0, 0, 0, 0, 2, 3, 4, 1, 0 };
+
+const int MAP_GRID_PRIOR_DIRECTION2[] = { 0, 1, 2, 3, 0, 1, 2, 3, 0 };
+
+MonsterStruct::MonsterStruct() {
+ _experience = 0;
+ _hp = 0;
+ _accuracy = 0;
+ _speed = 0;
+ _numberOfAttacks = 0;
+ _hatesClass = CLASS_KNIGHT;
+ _strikes = 0;
+ _dmgPerStrike = 0;
+ _attackType = DT_PHYSICAL;
+ _specialAttack = SA_NONE;
+ _hitChance = 0;
+ _rangeAttack = 0;
+ _monsterType = MONSTER_0;
+ _fireResistence = 0;
+ _electricityResistence = 0;
+ _coldResistence = 0;
+ _poisonResistence = 0;
+ _energyResistence = 0;
+ _magicResistence = 0;
+ _phsyicalResistence = 0;
+ _field29 = 0;
+ _gold = 0;
+ _gems = 0;
+ _itemDrop = 0;
+ _flying = 0;
+ _imageNumber = 0;
+ _loopAnimation = 0;
+ _animationEffect = 0;
+ _fx = 0;
+}
+
+MonsterStruct::MonsterStruct(Common::String name, int experience, int hp, int accuracy,
+ int speed, int numberOfAttacks, CharacterClass hatesClass, int strikes,
+ int dmgPerStrike, DamageType attackType, SpecialAttack specialAttack,
+ int hitChance, int rangeAttack, MonsterType monsterType,
+ int fireResistence, int electricityResistence, int coldResistence,
+ int poisonResistence, int energyResistence, int magicResistence,
+ int phsyicalResistence, int field29, int gold, int gems, int itemDrop,
+ bool flying, int imageNumber, int loopAnimation, int animationEffect,
+ int fx, Common::String attackVoc):
+ _name(name), _experience(experience), _hp(hp), _accuracy(accuracy),
+ _speed(speed), _numberOfAttacks(numberOfAttacks), _hatesClass(hatesClass),
+ _strikes(strikes), _dmgPerStrike(dmgPerStrike), _attackType(attackType),
+ _specialAttack(specialAttack), _hitChance(hitChance), _rangeAttack(rangeAttack),
+ _monsterType(monsterType), _fireResistence(fireResistence),
+ _electricityResistence(electricityResistence), _coldResistence(coldResistence),
+ _poisonResistence(poisonResistence), _energyResistence(energyResistence),
+ _magicResistence(magicResistence), _phsyicalResistence(phsyicalResistence),
+ _field29(field29), _gold(gold), _gems(gems), _itemDrop(itemDrop),
+ _flying(flying), _imageNumber(imageNumber), _loopAnimation(loopAnimation),
+ _animationEffect(animationEffect), _fx(fx), _attackVoc(attackVoc) {
+}
+
+void MonsterStruct::synchronize(Common::SeekableReadStream &s) {
+ char name[16];
+ s.read(name, 16);
+ name[15] = '\0';
+ _name = Common::String(name);
+
+ _experience = s.readUint32LE();
+ _hp = s.readUint16LE();
+ _accuracy = s.readByte();
+ _speed = s.readByte();
+ _numberOfAttacks = s.readByte();
+ _hatesClass = (CharacterClass)s.readByte();
+ _strikes = s.readUint16LE();
+ _dmgPerStrike = s.readByte();
+ _attackType = (DamageType)s.readByte();
+ _specialAttack = (SpecialAttack)s.readByte();
+ _hitChance = s.readByte();
+ _rangeAttack = s.readByte();
+ _monsterType = (MonsterType)s.readByte();
+ _fireResistence = s.readByte();
+ _electricityResistence = s.readByte();
+ _coldResistence = s.readByte();
+ _poisonResistence = s.readByte();
+ _energyResistence = s.readByte();
+ _magicResistence = s.readByte();
+ _phsyicalResistence = s.readByte();
+ _field29 = s.readByte();
+ _gold = s.readUint16LE();
+ _gems = s.readByte();
+ _itemDrop = s.readByte();
+ _flying = s.readByte() != 0;
+ _imageNumber = s.readByte();
+ _loopAnimation = s.readByte();
+ _animationEffect = s.readByte();
+ _fx = s.readByte();
+
+ char attackVoc[10];
+ s.read(attackVoc, 9);
+ attackVoc[9] = '\0';
+ _attackVoc = Common::String(attackVoc);
+}
+
+MonsterData::MonsterData() {
+ push_back(MonsterStruct("", 0, 0, 0, 0, 0, CLASS_KNIGHT, 1, 1, DT_PHYSICAL,
+ SA_NONE, 1, 0, MONSTER_0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, false, 0, 0, 0, 100, "Slime"));
+ push_back(MonsterStruct("Whirlwind", 250000, 1000, 10, 250, 1, CLASS_15, 5,
+ 100, DT_PHYSICAL, SA_CONFUSE, 250, 0, MONSTER_0, 100,
+ 100, 100, 100, 0, 0, 100, 0, 0, 0, 0, false, 1, 0, 0, 176,
+ "airmon"));
+ push_back(MonsterStruct("Annihilator", 1000000, 1500, 40, 200, 12, CLASS_16, 5,
+ 50, DT_ENERGY, SA_NONE, 1, 1, MONSTER_0, 80, 80, 100,
+ 100, 0, 0, 80, 0, 0, 0, 0, false, 2, 0, 0, 102, "alien1"));
+ push_back(MonsterStruct("Autobot", 1000000, 2500, 100, 200, 2, CLASS_16, 5,
+ 100, DT_ENERGY, SA_NONE, 1, 0, MONSTER_0, 50, 50, 100,
+ 100, 0, 0, 50, 0, 0, 0, 0, true, 3, 0, 0, 101, "alien2"));
+ push_back(MonsterStruct("Sewer Stalker", 50000, 250, 30, 25, 1, CLASS_16, 3,
+ 100, DT_PHYSICAL, SA_NONE, 50, 0, MONSTER_ANIMAL, 0,
+ 0, 50, 50, 0, 0, 0, 0, 0, 0, 0, false, 4, 0, 0, 113,
+ "iguana"));
+ push_back(MonsterStruct("Armadillo", 60000, 800, 50, 15, 1, CLASS_16, 100, 6,
+ DT_PHYSICAL, SA_BREAKWEAPON, 60, 0, MONSTER_ANIMAL,
+ 50, 0, 80, 80, 50, 0, 50, 0, 0, 0, 0, false, 5, 1, 0, 113,
+ "unnh"));
+ push_back(MonsterStruct("Barbarian", 5000, 50, 5, 40, 3, CLASS_SORCERER, 1, 20,
+ DT_PHYSICAL, SA_NONE, 20, 1, MONSTER_HUMANOID, 0, 0,
+ 0, 0, 0, 0, 0, 0, 100, 0, 3, false, 6, 0, 0, 100,
+ "barbarch"));
+ push_back(MonsterStruct("Electrapede", 10000, 200, 10, 50, 1, CLASS_PALADIN,
+ 50, 1, DT_ELECTRICAL, SA_PARALYZE, 1, 0,
+ MONSTER_INSECT, 50, 100, 50, 50, 50, 0, 0, 0, 0, 0, 0,
+ false, 7, 1, 0, 107, "centi"));
+ push_back(MonsterStruct("Cleric of Mok", 30000, 125, 10, 40, 1, CLASS_CLERIC,
+ 250, 1, DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_HUMANOID,
+ 10, 100, 10, 10, 10, 10, 0, 0, 0, 10, 0, false, 8, 0, 0,
+ 117, "cleric"));
+ push_back(MonsterStruct("Mok Heretic", 50000, 150, 12, 50, 1, CLASS_CLERIC,
+ 500, 1, DT_MAGICAL, SA_NONE, 1, 1, MONSTER_HUMANOID, 20, 50,
+ 20, 20, 20, 30, 0, 0, 0, 25, 4, false, 8, 0, 0, 117,
+ "cleric"));
+ push_back(MonsterStruct("Mantis Ant", 40000, 300, 30, 40, 2, CLASS_16, 2, 100,
+ DT_PHYSICAL, SA_POISON, 30, 0, MONSTER_INSECT, 0, 0,
+ 0, 100, 0, 0, 30, 0, 0, 0, 0, false, 10, 0, 0, 104,
+ "spell001"));
+ push_back(MonsterStruct("Cloud Dragon", 500000, 2000, 40, 150, 1, CLASS_15,
+ 600, 1, DT_COLD, SA_NONE, 1, 1, MONSTER_DRAGON, 0, 50,
+ 100, 100, 50, 25, 50, 0, 0, 10, 0, false, 11, 0, 0, 140,
+ "tiger1"));
+ push_back(MonsterStruct("Phase Dragon", 2000000, 4000, 80, 200, 1, CLASS_15,
+ 750, 1, DT_COLD, SA_NONE, 1, 1, MONSTER_DRAGON, 0, 50,
+ 100, 100, 80, 50, 50, 0, 0, 20, 0, false, 11, 0, 10, 140,
+ "Begger"));
+ push_back(MonsterStruct("Green Dragon", 500000, 2500, 50, 150, 1, CLASS_15,
+ 500, 1, DT_FIRE, SA_NONE, 1, 1, MONSTER_DRAGON, 100,
+ 50, 0, 100, 50, 25, 50, 0, 0, 10, 0, false, 13, 0, 0, 140,
+ "tiger1"));
+ push_back(MonsterStruct("Energy Dragon", 2000000, 5000, 100, 250, 1, CLASS_15,
+ 1000, 1, DT_ENERGY, SA_NONE, 1, 1, MONSTER_DRAGON, 80,
+ 80, 60, 100, 100, 30, 50, 0, 0, 20, 0, false, 13, 0, 7,
+ 140, "begger"));
+ push_back(MonsterStruct("Dragon Mummy", 2000000, 3000, 30, 100, 1,
+ CLASS_CLERIC, 2000, 2, DT_PHYSICAL, SA_DISEASE, 200,
+ 0, MONSTER_DRAGON, 0, 80, 100, 100, 0, 10, 90, 0, 0, 0,
+ 0, false, 15, 0, 0, 140, "dragmum"));
+ push_back(MonsterStruct("Scraps", 2000000, 3000, 30, 100, 1, CLASS_16, 2000, 2,
+ DT_PHYSICAL, SA_NONE, 200, 0, MONSTER_DRAGON, 0, 80,
+ 100, 100, 0, 10, 90, 0, 0, 0, 0, false, 15, 0, 0, 140,
+ "dragmum"));
+ push_back(MonsterStruct("Earth Blaster", 250000, 1000, 10, 100, 1, CLASS_15, 5,
+ 100, DT_PHYSICAL, SA_NONE, 200, 0, MONSTER_0, 100, 90,
+ 90, 100, 0, 0, 90, 0, 0, 0, 0, false, 17, 0, 0, 100,
+ "earthmon"));
+ push_back(MonsterStruct("Beholder Bat", 10000, 75, 15, 80, 1, CLASS_15, 5, 5,
+ DT_FIRE, SA_NONE, 1, 0, MONSTER_0, 100, 50, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, true, 18, 0, 0, 120, "eyeball"));
+ push_back(MonsterStruct("Fire Blower", 250000, 1000, 20, 60, 1, CLASS_15, 5,
+ 100, DT_FIRE, SA_NONE, 1, 0, MONSTER_0, 100, 50, 0,
+ 100, 50, 0, 50, 0, 0, 0, 0, false, 19, 0, 0, 110, "fire"));
+ push_back(MonsterStruct("Hell Hornet", 50000, 250, 30, 50, 2, CLASS_DRUID, 2,
+ 250, DT_POISON, SA_WEAKEN, 1, 0, MONSTER_INSECT, 50,
+ 50, 50, 100, 50, 0, 50, 0, 0, 0, 0, true, 20, 0, 0, 123,
+ "insect"));
+ push_back(MonsterStruct("Gargoyle", 30000, 150, 35, 30, 2, CLASS_16, 5, 50,
+ DT_PHYSICAL, SA_NONE, 60, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 20, 0, 0, 0, 0, 0, false, 21, 0, 10, 100, "gargrwl"));
+ push_back(MonsterStruct("Giant", 100000, 500, 25, 45, 2, CLASS_16, 100, 5,
+ DT_PHYSICAL, SA_UNCONSCIOUS, 100, 0, MONSTER_0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1000, 0, 5, false, 22, 0, 0, 100,
+ "giant"));
+ push_back(MonsterStruct("Goblin", 1000, 10, 5, 30, 2, CLASS_16, 2, 6,
+ DT_PHYSICAL, SA_NONE, 1, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, false, 25, 0, 0, 131, "gremlin"));
+ push_back(MonsterStruct("Onyx Golem", 1000000, 10000, 50, 100, 1, CLASS_15, 2,
+ 250, DT_MAGICAL, SA_DRAINSP, 1, 0, MONSTER_GOLEM, 100, 100,
+ 100, 100, 100, 100, 50, 0, 0, 100, 0, true, 24, 0, 10,
+ 100, "golem"));
+ push_back(MonsterStruct("Gremlin", 2000, 20, 7, 35, 2, CLASS_16, 2, 10,
+ DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, false, 26, 0, 0, 101, "gremlink"));
+ push_back(MonsterStruct("Gremlin Guard", 3000, 50, 10, 35, 2, CLASS_16, 6, 5,
+ DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, false, 26, 0, 0, 101, "gremlink"));
+ push_back(MonsterStruct("Griffin", 60000, 800, 35, 150, 2, CLASS_KNIGHT, 50, 6,
+ DT_PHYSICAL, SA_NONE, 150, 0, MONSTER_ANIMAL, 0, 0, 0,
+ 0, 0, 80, 0, 0, 0, 0, 0, false, 27, 0, 0, 120, "screech"));
+ push_back(MonsterStruct("Gamma Gazer", 1000000, 5000, 60, 200, 7, CLASS_16, 10,
+ 20, DT_ENERGY, SA_NONE, 1, 0, MONSTER_0, 100, 100, 0,
+ 100, 100, 0, 60, 0, 0, 0, 0, false, 28, 0, 0, 140, "hydra"));
+ push_back(MonsterStruct("Iguanasaurus", 100000, 2500, 20, 30, 1, CLASS_16, 10,
+ 50, DT_PHYSICAL, SA_INSANE, 150, 0, MONSTER_ANIMAL, 50,
+ 50, 50, 50, 50, 0, 20, 0, 0, 0, 0, false, 29, 0, 0, 113,
+ "iguana"));
+ push_back(MonsterStruct("Slayer Knight", 50000, 500, 30, 50, 1, CLASS_PALADIN,
+ 2, 250, DT_PHYSICAL, SA_NONE, 100, 0, MONSTER_HUMANOID,
+ 50, 50, 50, 50, 50, 0, 0, 0, 50, 0, 5, false, 30, 0, 0,
+ 141, "knight"));
+ push_back(MonsterStruct("Death Knight", 100000, 750, 50, 80, 2, CLASS_PALADIN,
+ 2, 250, DT_PHYSICAL, SA_NONE, 150, 0, MONSTER_HUMANOID,
+ 50, 50, 50, 50, 50, 10, 0, 0, 100, 0, 6, false, 30, 0, 0,
+ 141, "knight"));
+ push_back(MonsterStruct("Lava Dweller", 500000, 1500, 30, 40, 1, CLASS_15, 5,
+ 100, DT_FIRE, SA_NONE, 1, 0, MONSTER_0, 100, 100, 0,
+ 100, 50, 0, 50, 0, 0, 0, 0, false, 19, 0, 0, 110, "fire"));
+ push_back(MonsterStruct("Lava Roach", 50000, 500, 20, 70, 1, CLASS_16, 5, 50,
+ DT_FIRE, SA_NONE, 1, 0, MONSTER_INSECT, 100, 100, 0,
+ 100, 0, 0, 0, 0, 0, 0, 0, false, 33, 0, 0, 131, "Phantom"));
+ push_back(MonsterStruct("Power Lich", 200000, 500, 20, 60, 1, CLASS_15, 10, 10,
+ DT_MAGICAL, SA_UNCONSCIOUS, 1, 1, MONSTER_UNDEAD, 0, 0, 0, 0,
+ 0, 80, 70, 0, 0, 0, 0, true, 34, 0, 0, 141, "lich"));
+ push_back(MonsterStruct("Mystic Mage", 100000, 200, 20, 70, 1, CLASS_15, 10,
+ 20, DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_0, 50, 100,
+ 50, 50, 50, 30, 0, 0, 0, 50, 0, true, 35, 0, 0, 163,
+ "monsterb"));
+ push_back(MonsterStruct("Magic Mage", 200000, 300, 25, 80, 1, CLASS_15, 10, 30,
+ DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_0, 50, 100, 50,
+ 50, 50, 50, 0, 0, 0, 75, 0, true, 35, 0, 0, 163,
+ "monsterb"));
+ push_back(MonsterStruct("Minotaur", 250000, 3000, 80, 120, 1, CLASS_16, 100, 4,
+ DT_PHYSICAL, SA_AGING, 150, 0, MONSTER_0, 0, 0, 10, 0,
+ 0, 50, 60, 0, 0, 0, 0, false, 37, 0, 0, 141, "stonegol"));
+ push_back(MonsterStruct("Gorgon", 250000, 4000, 90, 100, 1, CLASS_16, 100, 3,
+ DT_PHYSICAL, SA_STONE, 100, 0, MONSTER_0, 0, 0, 0, 0,
+ 0, 60, 70, 0, 0, 0, 0, false, 37, 0, 0, 141, "stonegol"));
+ push_back(MonsterStruct("Higher Mummy", 100000, 400, 20, 60, 1, CLASS_CLERIC,
+ 10, 40, DT_PHYSICAL, SA_CURSEITEM, 100, 0,
+ MONSTER_UNDEAD, 0, 50, 50, 100, 50, 20, 75, 0, 0, 0, 0,
+ false, 39, 0, 0, 141, "mummy"));
+ push_back(MonsterStruct("Orc Guard", 5000, 60, 10, 20, 1, CLASS_12, 3, 10,
+ DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_HUMANOID, 0, 0,
+ 0, 0, 0, 0, 0, 0, 50, 0, 2, false, 40, 0, 0, 125, "orc"));
+ push_back(MonsterStruct("Octopod", 250000, 2500, 40, 80, 1, CLASS_15, 2, 100,
+ DT_POISON, SA_POISON, 1, 0, MONSTER_ANIMAL, 0, 0, 50,
+ 100, 0, 0, 0, 0, 0, 0, 0, true, 41, 0, 0, 101, "photon"));
+ push_back(MonsterStruct("Ogre", 10000, 100, 15, 30, 1, CLASS_16, 4, 10,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 100, 0, 0, false, 42, 0, 0, 136, "ogre"));
+ push_back(MonsterStruct("Orc Shaman", 10000, 50, 15, 30, 1, CLASS_15, 5, 5,
+ DT_COLD, SA_SLEEP, 1, 1, MONSTER_HUMANOID, 0, 0, 0, 0,
+ 0, 10, 0, 0, 75, 10, 2, false, 43, 0, 0, 125, "fx7"));
+ push_back(MonsterStruct("Sabertooth", 10000, 100, 20, 60, 3, CLASS_16, 5, 10,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_ANIMAL, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, false, 44, 1, 0, 101, "saber"));
+ push_back(MonsterStruct("Sand Flower", 10000, 100, 10, 50, 5, CLASS_16, 5, 5,
+ DT_PHYSICAL, SA_INLOVE, 50, 0, MONSTER_0, 0, 0, 0, 0,
+ 0, 50, 50, 0, 0, 0, 0, false, 45, 0, 0, 106, "sand"));
+ push_back(MonsterStruct("Killer Cobra", 25000, 1000, 25, 100, 1, CLASS_16, 2,
+ 100, DT_PHYSICAL, SA_AGING, 30, 0, MONSTER_ANIMAL, 0,
+ 0, 0, 100, 0, 50, 0, 0, 0, 0, 0, false, 46, 0, 0, 100,
+ "hiss"));
+ push_back(MonsterStruct("Sewer Rat", 2000, 40, 5, 35, 1, CLASS_16, 3, 10,
+ DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_ANIMAL, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, false, 47, 0, 0, 136, "rat"));
+ push_back(MonsterStruct("Sewer Slug", 1000, 25, 2, 25, 1, CLASS_16, 2, 10,
+ DT_PHYSICAL, SA_NONE, 5, 0, MONSTER_INSECT, 0, 0, 0,
+ 100, 0, 0, 0, 0, 0, 0, 0, false, 48, 0, 0, 111, "zombie"));
+ push_back(MonsterStruct("Skeletal Lich", 500000, 2000, 30, 200, 1,
+ CLASS_SORCERER, 1000, 1, DT_ENERGY, SA_ERADICATE, 1, 1,
+ MONSTER_UNDEAD, 80, 70, 80, 100, 100, 50, 50, 0, 0, 0,
+ 0, false, 49, 0, 0, 140, "elecbolt"));
+ push_back(MonsterStruct("Enchantress", 40000, 100, 25, 60, 1, CLASS_CLERIC, 3,
+ 150, DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_HUMANOID,
+ 10, 100, 10, 10, 10, 20, 0, 0, 0, 20, 0, false, 50, 0, 0,
+ 163, "disint"));
+ push_back(MonsterStruct("Sorceress", 80000, 200, 30, 80, 1, CLASS_15, 2, 50,
+ DT_MAGICAL, SA_NONE, 1, 1, MONSTER_HUMANOID, 10, 20, 10, 10,
+ 10, 80, 0, 0, 0, 50, 5, false, 50, 0, 0, 163, "disint"));
+ push_back(MonsterStruct("Arachnoid", 4000, 50, 10, 40, 1, CLASS_16, 3, 5,
+ DT_POISON, SA_POISON, 1, 0, MONSTER_INSECT, 0, 0, 0,
+ 100, 0, 0, 0, 0, 0, 0, 0, false, 52, 0, 0, 104, "web"));
+ push_back(MonsterStruct("Medusa Sprite", 5000, 30, 5, 30, 1, CLASS_RANGER, 3,
+ 3, DT_PHYSICAL, SA_STONE, 10, 0, MONSTER_0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, true, 53, 0, 0, 42, "hiss"));
+ push_back(MonsterStruct("Rogue", 5000, 50, 10, 30, 1, CLASS_ROBBER, 1, 60,
+ DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_HUMANOID, 0, 0,
+ 0, 0, 0, 0, 0, 0, 70, 0, 0, false, 54, 0, 0, 100, "thief"));
+ push_back(MonsterStruct("Thief", 10000, 100, 15, 40, 1, CLASS_ROBBER, 1, 100,
+ DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_HUMANOID, 0, 0,
+ 0, 0, 0, 0, 0, 0, 200, 0, 0, false, 54, 0, 0, 100,
+ "thief"));
+ push_back(MonsterStruct("Troll Grunt", 10000, 100, 5, 50, 1, CLASS_16, 2, 25,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 50, 50, 50,
+ 50, 0, 0, 0, 0, 0, 0, 0, false, 56, 0, 0, 136, "troll"));
+ push_back(MonsterStruct("Vampire", 200000, 400, 30, 80, 1, CLASS_CLERIC, 10,
+ 10, DT_PHYSICAL, SA_WEAKEN, 100, 0, MONSTER_UNDEAD, 50,
+ 50, 50, 50, 50, 50, 50, 0, 0, 0, 0, false, 57, 0, 0, 42,
+ "vamp"));
+ push_back(MonsterStruct("Vampire Lord", 300000, 500, 35, 100, 1, CLASS_CLERIC,
+ 10, 30, DT_PHYSICAL, SA_SLEEP, 120, 0, MONSTER_UNDEAD,
+ 50, 50, 50, 50, 50, 50, 70, 0, 0, 0, 0, false, 58, 0, 0,
+ 42, "vamp"));
+ push_back(MonsterStruct("Vulture Roc", 200000, 2500, 50, 150, 1, CLASS_16, 5,
+ 60, DT_PHYSICAL, SA_NONE, 100, 0, MONSTER_ANIMAL, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, true, 59, 0, 0, 120, "vulture"));
+ push_back(MonsterStruct("Sewer Hag", 50000, 75, 10, 40, 1, CLASS_PALADIN, 10,
+ 25, DT_ELECTRICAL, SA_INSANE, 1, 1, MONSTER_HUMANOID,
+ 0, 100, 0, 100, 0, 20, 0, 0, 0, 10, 0, false, 62, 0, 0,
+ 108, "elecspel"));
+ push_back(MonsterStruct("Tidal Terror", 500000, 1000, 10, 200, 1, CLASS_15, 5,
+ 100, DT_COLD, SA_NONE, 1, 0, MONSTER_0, 100, 50, 50,
+ 100, 50, 0, 100, 0, 0, 0, 0, true, 61, 0, 0, 101,
+ "splash3"));
+ push_back(MonsterStruct("Witch", 80000, 150, 15, 70, 1, CLASS_15, 10, 10,
+ DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_HUMANOID, 0, 100,
+ 0, 20, 0, 20, 0, 0, 0, 10, 0, false, 63, 0, 0, 114,
+ "elecspel"));
+ push_back(MonsterStruct("Coven Leader", 120000, 250, 20, 100, 1, CLASS_15, 10,
+ 15, DT_ENERGY, SA_DRAINSP, 1, 1, MONSTER_HUMANOID, 10,
+ 100, 0, 50, 100, 50, 0, 0, 0, 20, 6, false, 63, 0, 10, 114,
+ "elecspel"));
+ push_back(MonsterStruct("Master Wizard", 120000, 500, 25, 150, 2, CLASS_KNIGHT,
+ 10, 40, DT_FIRE, SA_NONE, 1, 1, MONSTER_HUMANOID, 100,
+ 50, 50, 50, 50, 50, 0, 0, 0, 50, 0, false, 64, 0, 0, 163,
+ "boltelec"));
+ push_back(MonsterStruct("Wizard", 60000, 250, 20, 125, 1, CLASS_PALADIN, 10,
+ 25, DT_MAGICAL, SA_NONE, 1, 1, MONSTER_HUMANOID, 50, 30, 30,
+ 30, 30, 30, 0, 0, 0, 20, 0, false, 65, 0, 0, 163, "wizard"));
+ push_back(MonsterStruct("Dark Wolf", 10000, 70, 10, 70, 3, CLASS_16, 3, 8,
+ DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_ANIMAL, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, false, 66, 1, 0, 100, "wolf"));
+ push_back(MonsterStruct("Screamer", 500000, 3000, 50, 200, 1, CLASS_15, 10, 20,
+ DT_POISON, SA_POISON, 1, 0, MONSTER_0, 0, 0, 0, 100, 0,
+ 0, 60, 0, 0, 0, 0, false, 67, 0, 0, 110, "dragon"));
+ push_back(MonsterStruct("Cult Leader", 100000, 100, 20, 60, 1, CLASS_15, 10,
+ 10, DT_ENERGY, SA_NONE, 1, 1, MONSTER_HUMANOID, 50, 50,
+ 50, 50, 100, 50, 0, 0, 0, 100, 6, false, 8, 0, 0, 100,
+ "cleric"));
+ push_back(MonsterStruct("Mega Dragon", 100000000, 64000, 100, 200, 1, CLASS_15,
+ 10, 200, DT_ENERGY, SA_ERADICATE, 1, 1, MONSTER_DRAGON,
+ 100, 100, 100, 100, 100, 100, 90, 0, 0, 232, 0, false, 11,
+ 0, 7, 100, "tiger1"));
+ push_back(MonsterStruct("Gettlewaithe", 5000, 100, 15, 35, 2, CLASS_16, 5, 5,
+ DT_PHYSICAL, SA_NONE, 10, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 2000, 0, 5, false, 25, 0, 0, 100, "gremlin"));
+ push_back(MonsterStruct("Doom Knight", 500000, 1000, 50, 100, 4, CLASS_PALADIN,
+ 2, 250, DT_PHYSICAL, SA_DEATH, 150, 0,
+ MONSTER_HUMANOID, 80, 80, 80, 80, 80, 20, 0, 0, 200,
+ 0, 7, false, 30, 0, 10, 100, "knight"));
+ push_back(MonsterStruct("Sandro", 200000, 1000, 20, 75, 1, CLASS_15, 10, 10,
+ DT_MAGICAL, SA_DEATH, 1, 1, MONSTER_UNDEAD, 0, 0, 0, 0, 0,
+ 90, 80, 0, 0, 100, 7, true, 34, 0, 10, 100, "lich"));
+ push_back(MonsterStruct("Mega Mage", 500000, 500, 35, 100, 1, CLASS_15, 10, 40,
+ DT_ELECTRICAL, SA_NONE, 1, 1, MONSTER_0, 80, 100, 80,
+ 80, 80, 80, 0, 0, 0, 100, 6, true, 35, 0, 11, 100,
+ "monsterb"));
+ push_back(MonsterStruct("Orc Elite", 15000, 200, 15, 40, 2, CLASS_12, 5, 10,
+ DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_HUMANOID, 0, 0,
+ 0, 0, 0, 0, 0, 0, 100, 0, 3, false, 40, 0, 0, 100, "orc"));
+ push_back(MonsterStruct("Shaalth", 20000, 300, 15, 50, 1, CLASS_15, 5, 10,
+ DT_COLD, SA_SLEEP, 1, 0, MONSTER_HUMANOID, 0, 0, 0, 0,
+ 0, 20, 0, 0, 1000, 50, 5, false, 43, 0, 10, 100, "fx7"));
+ push_back(MonsterStruct("Rooka", 5000, 60, 5, 40, 1, CLASS_16, 3, 10,
+ DT_PHYSICAL, SA_DISEASE, 15, 0, MONSTER_ANIMAL, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 10, 4, false, 47, 0, 0, 100, "rat"));
+ push_back(MonsterStruct("Morgana", 200000, 300, 35, 100, 1, CLASS_15, 2, 60,
+ DT_ENERGY, SA_PARALYZE, 1, 1, MONSTER_HUMANOID, 50, 50,
+ 50, 50, 100, 80, 0, 0, 0, 100, 6, false, 50, 0, 10, 100,
+ "disint"));
+ push_back(MonsterStruct("Master Thief", 20000, 100, 20, 50, 1, CLASS_ROBBER, 1,
+ 250, DT_PHYSICAL, SA_NONE, 40, 0, MONSTER_HUMANOID, 0,
+ 0, 0, 0, 0, 0, 0, 0, 250, 20, 4, false, 54, 0, 14, 100,
+ "thief"));
+ push_back(MonsterStruct("Royal Vampire", 400000, 750, 40, 125, 1, CLASS_CLERIC,
+ 10, 50, DT_PHYSICAL, SA_CURSEITEM, 120, 0,
+ MONSTER_UNDEAD, 50, 50, 50, 50, 50, 50, 65, 0, 0, 0, 0,
+ false, 57, 0, 0, 100, "vamp"));
+ push_back(MonsterStruct("Ct. Blackfang", 2000000, 1500, 50, 150, 1,
+ CLASS_CLERIC, 10, 100, DT_PHYSICAL, SA_DEATH, 120, 0,
+ MONSTER_UNDEAD, 75, 75, 75, 75, 75, 75, 75, 0, 0, 0, 0,
+ false, 58, 0, 10, 100, "vamp"));
+ push_back(MonsterStruct("Troll Guard", 15000, 200, 10, 60, 1, CLASS_16, 2, 35,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 50, 50, 50,
+ 50, 0, 0, 0, 0, 0, 0, 0, false, 56, 0, 0, 100, "troll"));
+ push_back(MonsterStruct("Troll Chief", 20000, 300, 15, 65, 1, CLASS_16, 2, 50,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 50, 50, 50,
+ 50, 0, 0, 0, 0, 0, 0, 0, false, 56, 0, 0, 100, "troll"));
+ push_back(MonsterStruct("Hobstadt", 25000, 400, 20, 70, 1, CLASS_16, 2, 50,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 50, 50, 50,
+ 50, 0, 0, 0, 0, 1000, 0, 4, false, 56, 0, 0, 100, "troll"));
+ push_back(MonsterStruct("Graalg", 20000, 200, 15, 50, 1, CLASS_16, 5, 10,
+ DT_PHYSICAL, SA_NONE, 30, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1000, 0, 5, false, 42, 0, 0, 100, "ogre"));
+ push_back(MonsterStruct("Vampire King", 3000000, 10000, 60, 200, 1,
+ CLASS_CLERIC, 10, 250, DT_PHYSICAL, SA_ERADICATE, 150,
+ 0, MONSTER_UNDEAD, 80, 80, 80, 80, 80, 80, 90, 0, 0, 0,
+ 0, false, 58, 0, 0, 100, "vamp"));
+ push_back(MonsterStruct("Valio", 60000, 150, 15, 60, 1, CLASS_PALADIN, 10, 25,
+ DT_MAGICAL, SA_NONE, 1, 0, MONSTER_HUMANOID, 50, 30, 30, 30,
+ 40, 30, 0, 0, 0, 0, 0, false, 65, 0, 0, 100, "wizard"));
+ push_back(MonsterStruct("Sky Golem", 200000, 1000, 50, 100, 1, CLASS_15, 2,
+ 100, DT_COLD, SA_NONE, 1, 1, MONSTER_GOLEM, 50, 50,
+ 100, 50, 50, 50, 50, 0, 0, 0, 0, true, 24, 0, 0, 100,
+ "golem"));
+ push_back(MonsterStruct("Gurodel", 100000, 750, 30, 60, 2, CLASS_16, 100, 6,
+ DT_PHYSICAL, SA_UNCONSCIOUS, 110, 0, MONSTER_0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 5000, 0, 6, false, 22, 0, 0, 100,
+ "giant"));
+ push_back(MonsterStruct("Yog", 25000, 100, 5, 60, 1, CLASS_SORCERER, 1, 30,
+ DT_PHYSICAL, SA_NONE, 25, 0, MONSTER_HUMANOID, 0, 0,
+ 0, 0, 0, 0, 0, 0, 200, 0, 4, false, 6, 0, 10, 100,
+ "barbarch"));
+ push_back(MonsterStruct("Sharla", 10000, 50, 5, 50, 1, CLASS_RANGER, 3, 4,
+ DT_PHYSICAL, SA_NONE, 20, 0, MONSTER_0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, true, 53, 0, 0, 100, "hiss"));
+ push_back(MonsterStruct("Ghost Mummy", 500000, 500, 35, 175, 1, CLASS_CLERIC,
+ 200, 5, DT_PHYSICAL, SA_AGING, 150, 0, MONSTER_UNDEAD,
+ 0, 60, 80, 80, 80, 50, 80, 0, 0, 0, 0, false, 40, 0, 6,
+ 100, "orc"));
+ push_back(MonsterStruct("Phase Mummy", 500000, 500, 35, 175, 1, CLASS_CLERIC,
+ 200, 6, DT_PHYSICAL, SA_DRAINSP, 150, 0,
+ MONSTER_UNDEAD, 0, 70, 80, 80, 80, 60, 85, 0, 0, 0, 0,
+ false, 39, 0, 7, 100, "mummy"));
+ push_back(MonsterStruct("Xenoc", 250000, 700, 35, 175, 1, CLASS_15, 10, 50,
+ DT_ENERGY, SA_NONE, 1, 0, MONSTER_HUMANOID, 50, 50, 50,
+ 50, 100, 50, 0, 0, 0, 100, 6, false, 64, 0, 0, 100,
+ "boltelec"));
+ push_back(MonsterStruct("Barkman", 4000000, 40000, 25, 100, 3, CLASS_16, 250,
+ 1, DT_FIRE, SA_NONE, 1, 0, MONSTER_0, 100, 50, 0, 100,
+ 0, 0, 0, 0, 0, 0, 6, false, 19, 0, 11, 100, "fire"));
+}
+
+void MonsterData::load(const Common::String &name) {
+ File f(name);
+ synchronize(f);
+}
+
+void MonsterData::synchronize(Common::SeekableReadStream &s) {
+ clear();
+
+ MonsterStruct spr;
+ while (!s.eos()) {
+ spr.synchronize(s);
+ push_back(spr);
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+SurroundingMazes::SurroundingMazes() {
+ clear();
+}
+
+void SurroundingMazes::clear() {
+ _north = 0;
+ _east = 0;
+ _south = 0;
+ _west = 0;
+}
+
+void SurroundingMazes::synchronize(Common::SeekableReadStream &s) {
+ _north = s.readUint16LE();
+ _east = s.readUint16LE();
+ _south = s.readUint16LE();
+ _west = s.readUint16LE();
+}
+
+int &SurroundingMazes::operator[](int idx) {
+ switch (idx) {
+ case DIR_NORTH:
+ return _north;
+ case DIR_EAST:
+ return _east;
+ case DIR_SOUTH:
+ return _south;
+ default:
+ return _west;
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+MazeDifficulties::MazeDifficulties() {
+ _unlockDoor = 0;
+ _unlockBox = 0;
+ _bashDoor = 0;
+ _bashGrate = 0;
+ _bashWall = 0;
+}
+
+void MazeDifficulties::synchronize(Common::SeekableReadStream &s) {
+ _wallNoPass = s.readByte();
+ _surfaceNoPass = s.readByte();
+ _unlockDoor = s.readByte();
+ _unlockBox = s.readByte();
+ _bashDoor = s.readByte();
+ _bashGrate = s.readByte();
+ _bashWall = s.readByte();
+ _chance2Run = s.readByte();
+}
+
+/*------------------------------------------------------------------------*/
+
+MazeData::MazeData() {
+ clear();
+}
+
+void MazeData::clear() {
+ for (int y = 0; y < MAP_HEIGHT; ++y) {
+ for (int x = 0; x < MAP_WIDTH; ++x)
+ _wallData[y][x]._data = 0;
+ Common::fill(&_seenTiles[y][0], &_seenTiles[y][MAP_WIDTH], false);
+ Common::fill(&_steppedOnTiles[y][0], &_steppedOnTiles[y][MAP_WIDTH], false);
+ _wallTypes[y] = 0;
+ _surfaceTypes[y] = 0;
+ }
+ _mazeNumber = 0;
+ _surroundingMazes.clear();
+ _mazeFlags = _mazeFlags2 = 0;
+ _floorType = 0;
+ _trapDamage = 0;
+ _wallKind = 0;
+ _tavernTips = 0;
+}
+
+void MazeData::synchronize(Common::SeekableReadStream &s) {
+ for (int y = 0; y < MAP_HEIGHT; ++y) {
+ for (int x = 0; x < MAP_WIDTH; ++x)
+ _wallData[y][x]._data = s.readUint16LE();
+ }
+ for (int y = 0; y < MAP_HEIGHT; ++y) {
+ for (int x = 0; x < MAP_WIDTH; ++x) {
+ byte b = s.readByte();
+ _cells[y][x]._surfaceId = b & 7;
+ _cells[y][x]._flags = b & 0xF8;
+ }
+ }
+
+ _mazeNumber = s.readUint16LE();
+ _surroundingMazes.synchronize(s);
+ _mazeFlags = s.readUint16LE();
+ _mazeFlags2 = s.readUint16LE();
+
+ for (int i = 0; i < 16; ++i)
+ _wallTypes[i] = s.readByte();
+ for (int i = 0; i < 16; ++i)
+ _surfaceTypes[i] = s.readByte();
+
+ _floorType = s.readByte();
+ _runPosition.x = s.readByte();
+ _difficulties.synchronize(s);
+ _runPosition.y = s.readByte();
+ _trapDamage = s.readByte();
+ _wallKind = s.readByte();
+ _tavernTips = s.readByte();
+
+ Common::Serializer ser(&s, nullptr);
+ for (int y = 0; y < MAP_HEIGHT; ++y)
+ SavesManager::syncBitFlags(ser, &_seenTiles[y][0], &_seenTiles[y][MAP_WIDTH]);
+ for (int y = 0; y < MAP_HEIGHT; ++y)
+ SavesManager::syncBitFlags(ser, &_steppedOnTiles[y][0], &_steppedOnTiles[y][MAP_WIDTH]);
+}
+
+void MazeData::setAllTilesStepped() {
+ for (int y = 0; y < MAP_HEIGHT; ++y)
+ Common::fill(&_steppedOnTiles[y][0], &_steppedOnTiles[y][MAP_WIDTH], true);
+}
+
+void MazeData::clearCellSurfaces() {
+ for (int y = 0; y < MAP_HEIGHT; ++y) {
+ for (int x = 0; x < MAP_WIDTH; ++x)
+ _cells[y][x]._surfaceId = 0;
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+MobStruct::MobStruct() {
+ _id = 0;
+ _direction = DIR_NORTH;
+}
+
+bool MobStruct::synchronize(XeenSerializer &s) {
+ s.syncAsSint8(_pos.x);
+ s.syncAsSint8(_pos.y);
+ s.syncAsByte(_id);
+ s.syncAsByte(_direction);
+
+ return _id != 0xff || _pos.x != -1 || _pos.y != -1;
+}
+
+/*------------------------------------------------------------------------*/
+
+MazeObject::MazeObject() {
+ _id = 0;
+ _frame = 0;
+ _spriteId = 0;
+ _direction = DIR_NORTH;
+ _flipped = false;
+ _sprites = nullptr;
+}
+
+/*------------------------------------------------------------------------*/
+
+MazeMonster::MazeMonster() {
+ _frame = 0;
+ _id = 0;
+ _spriteId = 0;
+ _isAttacking = false;
+ _damageType = DT_PHYSICAL;
+ _field9 = 0;
+ _fieldA = 0;
+ _hp = 0;
+ _effect1 = _effect2 = 0;
+ _effect3 = 0;
+ _sprites = nullptr;
+ _attackSprites = nullptr;
+ _monsterData = nullptr;
+}
+
+int MazeMonster::getTextColor() const {
+ if (_hp == _monsterData->_hp)
+ return 15;
+ else if (_hp >= (_monsterData->_hp / 2))
+ return 9;
+ else
+ return 32;
+}
+
+/*------------------------------------------------------------------------*/
+
+MazeWallItem::MazeWallItem() {
+ _id = 0;
+ _frame = 0;
+ _spriteId = 0;
+ _direction = DIR_NORTH;
+ _sprites = nullptr;
+}
+
+/*------------------------------------------------------------------------*/
+
+MonsterObjectData::MonsterObjectData(XeenEngine *vm): _vm(vm) {
+}
+
+void MonsterObjectData::synchronize(XeenSerializer &s, MonsterData &monsterData) {
+ Common::Array<MobStruct> mobStructs;
+ MobStruct mobStruct;
+ byte b;
+
+ if (s.isLoading()) {
+ _objectSprites.clear();
+ _monsterSprites.clear();
+ _monsterAttackSprites.clear();
+ _wallItemSprites.clear();
+ _objects.clear();
+ _monsters.clear();
+ _wallItems.clear();
+ }
+
+ for (uint i = 0; i < 16; ++i) {
+ b = (i >= _objectSprites.size()) ? 0xff : _objectSprites[i]._spriteId;
+ s.syncAsByte(b);
+ if (b != 0xff)
+ _objectSprites.push_back(SpriteResourceEntry(b));
+ }
+ for (uint i = 0; i < 16; ++i) {
+ b = (i >= _monsterSprites.size()) ? 0xff : _monsterSprites[i]._spriteId;
+ s.syncAsByte(b);
+ if (b != 0xff)
+ _monsterSprites.push_back(SpriteResourceEntry(b));
+ }
+ for (uint i = 0; i < 16; ++i) {
+ b = (i >= _wallItemSprites.size()) ? 0xff : _wallItemSprites[i]._spriteId;
+ s.syncAsByte(b);
+ if (b != 0xff)
+ _wallItemSprites.push_back(SpriteResourceEntry(b));
+ }
+
+ if (s.isSaving()) {
+ // Save objects
+ for (uint i = 0; i < _objects.size(); ++i) {
+ mobStruct._pos = _objects[i]._position;
+ mobStruct._id = _objects[i]._id;
+ mobStruct._direction = _objects[i]._direction;
+ mobStruct.synchronize(s);
+ }
+ mobStruct._pos.x = mobStruct._pos.y = -1;
+ mobStruct._id = 0xff;
+ mobStruct.synchronize(s);
+
+ // Save monsters
+ for (uint i = 0; i < _monsters.size(); ++i) {
+ mobStruct._pos = _monsters[i]._position;
+ mobStruct._id = _monsters[i]._id;
+ mobStruct._direction = DIR_NORTH;
+ mobStruct.synchronize(s);
+ }
+ mobStruct._pos.x = mobStruct._pos.y = -1;
+ mobStruct._id = 0xff;
+ mobStruct.synchronize(s);
+
+ // Save wall items
+ if (_wallItems.size() == 0) {
+ MobStruct nullStruct;
+ nullStruct.synchronize(s);
+ } else {
+ for (uint i = 0; i < _wallItems.size(); ++i) {
+ mobStruct._pos = _wallItems[i]._position;
+ mobStruct._id = _wallItems[i]._id;
+ mobStruct._direction = _wallItems[i]._direction;
+ mobStruct.synchronize(s);
+ }
+ }
+ mobStruct._pos.x = mobStruct._pos.y = -1;
+ mobStruct._id = 0xff;
+ mobStruct.synchronize(s);
+
+ } else {
+ // Load monster/obbject data and merge together with sprite Ids
+ // Merge together object data
+ mobStruct.synchronize(s);
+ do {
+ MazeObject obj;
+ obj._position = mobStruct._pos;
+ obj._id = mobStruct._id;
+ obj._direction = mobStruct._direction;
+ obj._frame = 100;
+ obj._spriteId = _objectSprites[obj._id]._spriteId;
+ obj._sprites = &_objectSprites[obj._id]._sprites;
+
+ _objects.push_back(obj);
+ mobStruct.synchronize(s);
+ } while (mobStruct._id != 255 || mobStruct._pos.x != -1);
+
+ // Merge together monster data
+ mobStruct.synchronize(s);
+ do {
+ MazeMonster mon;
+ mon._position = mobStruct._pos;
+ mon._id = mobStruct._id;
+ mon._spriteId = _monsterSprites[mon._id]._spriteId;
+ mon._sprites = &_monsterSprites[mon._id]._sprites;
+ mon._attackSprites = &_monsterSprites[mon._id]._attackSprites;
+ mon._monsterData = &monsterData[mon._spriteId];
+ mon._frame = _vm->getRandomNumber(7);
+
+ MonsterStruct &md = *mon._monsterData;
+ mon._hp = md._hp;
+ mon._effect1 = mon._effect2 = md._animationEffect;
+ if (md._animationEffect)
+ mon._effect3 = _vm->getRandomNumber(7);
+
+ _monsters.push_back(mon);
+ mobStruct.synchronize(s);
+ } while (mobStruct._id != 255 || mobStruct._pos.x != -1);
+
+ // Merge together wall item data
+ mobStruct.synchronize(s);
+ do {
+ if (mobStruct._id < (int)_wallItemSprites.size()) {
+ MazeWallItem wi;
+ wi._position = mobStruct._pos;
+ wi._id = mobStruct._id;
+ wi._direction = mobStruct._direction;
+ wi._spriteId = _wallItemSprites[wi._id]._spriteId;
+ wi._sprites = &_wallItemSprites[wi._id]._sprites;
+
+ _wallItems.push_back(wi);
+ }
+
+ mobStruct.synchronize(s);
+ } while (mobStruct._id != 255 || mobStruct._pos.x != -1);
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+HeadData::HeadData() {
+ for (int y = 0; y < MAP_HEIGHT; ++y) {
+ for (int x = 0; x < MAP_WIDTH; ++x) {
+ _data[y][x]._left = _data[y][x]._right = 0;
+ }
+ }
+}
+
+void HeadData::synchronize(Common::SeekableReadStream &s) {
+ for (int y = 0; y < MAP_HEIGHT; ++y) {
+ for (int x = 0; x < MAP_WIDTH; ++x) {
+ _data[y][x]._left = s.readByte();
+ _data[y][x]._right = s.readByte();
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+void AnimationEntry::synchronize(Common::SeekableReadStream &s) {
+ for (int i = 0; i < 4; ++i)
+ _frame1._frames[i] = s.readByte();
+ for (int i = 0; i < 4; ++i)
+ _flipped._flags[i] = s.readByte() != 0;
+ for (int i = 0; i < 4; ++i)
+ _frame2._frames[i] = s.readByte();
+}
+
+void AnimationInfo::synchronize(Common::SeekableReadStream &s) {
+ AnimationEntry entry;
+
+ clear();
+ while (s.pos() < s.size()) {
+ entry.synchronize(s);
+ push_back(entry);
+ }
+}
+
+void AnimationInfo::load(const Common::String &name) {
+ File f(name);
+ synchronize(f);
+ f.close();
+}
+
+/*------------------------------------------------------------------------*/
+
+Map::Map(XeenEngine *vm) : _vm(vm), _mobData(vm) {
+ _loadDarkSide = false;
+ _sideTownPortal = 0;
+ _sideObjects = 0;
+ _sideMonsters = 0;
+ _sidePictures = 0;
+ _isOutdoors = false;
+ _mazeDataIndex = 0;
+ _currentSteppedOn = false;
+ _currentSurfaceId = 0;
+ _currentWall = 0;
+ _currentTile = 0;
+ _currentGrateUnlocked = false;
+ _currentCantRest = false;
+ _currentIsDrain = false;
+ _currentIsEvent = false;
+ _currentSky = 0;
+ _currentMonsterFlags = 0;
+}
+
+void Map::load(int mapId) {
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ IndoorDrawList &indoorList = _vm->_interface->_indoorList;
+ OutdoorDrawList &outdoorList = _vm->_interface->_outdoorList;
+
+ if (intf._falling) {
+ Window &w = screen._windows[9];
+ w.open();
+ w.writeString(OOPS);
+ } else {
+ PleaseWait::show(_vm);
+ }
+
+ _vm->_party->_stepped = true;
+ _vm->_party->_mazeId = mapId;
+ _vm->_events->clearEvents();
+
+ _sideObjects = 1;
+ _sideMonsters = 1;
+ _sidePictures = 1;
+ if (mapId >= 113 && mapId <= 127) {
+ _sideTownPortal = 0;
+ } else {
+ _sideTownPortal = _loadDarkSide ? 1 : 0;
+ }
+
+ if (_vm->getGameID() == GType_WorldOfXeen) {
+ if (!_loadDarkSide) {
+ _animationInfo.load("clouds.dat");
+ _monsterData.load("xeen.mon");
+ _wallPicSprites.load("xeenpic.dat");
+ _sidePictures = 0;
+ _sideMonsters = 0;
+ _sideObjects = 0;
+ } else {
+ switch (mapId) {
+ case 113:
+ case 114:
+ case 115:
+ case 116:
+ case 128:
+ _animationInfo.load("clouds.dat");
+ _monsterData.load("dark.mon");
+ _wallPicSprites.load("darkpic.dat");
+ _sideObjects = 0;
+ break;
+ case 117:
+ case 118:
+ case 119:
+ case 120:
+ case 124:
+ _animationInfo.load("clouds.dat");
+ _monsterData.load("xeen.mon");
+ _wallPicSprites.load("darkpic.dat");
+ _sideObjects = 0;
+ _sideMonsters = 0;
+ break;
+ case 125:
+ case 126:
+ case 127:
+ _animationInfo.load("clouds.dat");
+ _monsterData.load("dark.mon");
+ _wallPicSprites.load("xeenpic.dat");
+ _sideObjects = 0;
+ _sidePictures = 0;
+ break;
+ default:
+ _animationInfo.load("dark.dat");
+ _monsterData.load("ddark.mon");
+ _wallPicSprites.load("darkpic.dat");
+ break;
+ }
+ }
+ }
+
+ // Load any events for the new map
+ loadEvents(mapId);
+
+ // Iterate through loading the given maze as well as the two successive
+ // mazes in each of the four cardinal directions
+ bool isDarkCc = _vm->getGameID() == GType_DarkSide;
+ MazeData *mazeDataP = &_mazeData[0];
+ bool textLoaded = false;
+
+ for (int idx = 0; idx < 9; ++idx, ++mazeDataP) {
+ mazeDataP->_mazeId = mapId;
+
+ if (mapId == 0) {
+ mazeDataP->clear();
+ } else {
+ // Load in the maze's data file
+ Common::String datName = Common::String::format("maze%c%03d.dat",
+ (mapId >= 100) ? 'x' : '0', mapId);
+ File datFile(datName);
+ mazeDataP->synchronize(datFile);
+ datFile.close();
+
+ if (isDarkCc && mapId == 50)
+ mazeDataP->setAllTilesStepped();
+ if (!isDarkCc && _vm->_party->_gameFlags[25] &&
+ (mapId == 42 || mapId == 43 || mapId == 4)) {
+ mazeDataP->clearCellSurfaces();
+ }
+
+ _isOutdoors = (mazeDataP->_mazeFlags2 & FLAG_IS_OUTDOORS) != 0;
+
+ // Handle loading text data
+ if (!textLoaded) {
+ textLoaded = true;
+ Common::String txtName = Common::String::format("%s%c%03d.txt",
+ isDarkCc ? "dark" : "xeen", mapId >= 100 ? 'x' : '0', mapId);
+ File fText(txtName);
+ char mazeName[33];
+ fText.read(mazeName, 33);
+ mazeName[32] = '\0';
+
+ _mazeName = Common::String(mazeName);
+ fText.close();
+
+ // Load the monster/object data
+ Common::String mobName = Common::String::format("maze%c%03d.mob",
+ (mapId >= 100) ? 'x' : '0', mapId);
+ File mobFile(mobName);
+ XeenSerializer sMob(&mobFile, nullptr);
+ _mobData.synchronize(sMob, _monsterData);
+ mobFile.close();
+
+ Common::String headName = Common::String::format("aaze%c%03d.hed",
+ (mapId >= 100) ? 'x' : '0', mapId);
+ File headFile(headName);
+ _headData.synchronize(headFile);
+ headFile.close();
+
+ if (!isDarkCc && _vm->_party->_mazeId)
+ _mobData._monsters.clear();
+
+ if (!isDarkCc && mapId == 15) {
+ if ((_mobData._monsters[0]._position.x > 31 || _mobData._monsters[0]._position.y > 31) &&
+ (_mobData._monsters[1]._position.x > 31 || _mobData._monsters[1]._position.y > 31) &&
+ (_mobData._monsters[2]._position.x > 31 || _mobData._monsters[2]._position.y > 31)) {
+ _vm->_party->_gameFlags[56] = true;
+ }
+ }
+ }
+ }
+
+ // Move to next surrounding maze
+ MazeData *baseMaze = &_mazeData[MAP_GRID_PRIOR_INDEX[idx]];
+ mapId = baseMaze->_surroundingMazes[MAP_GRID_PRIOR_DIRECTION[idx]];
+ if (!mapId) {
+ baseMaze = &_mazeData[MAP_GRID_PRIOR_INDEX2[idx]];
+ mapId = baseMaze->_surroundingMazes[MAP_GRID_PRIOR_DIRECTION2[idx]];
+ }
+ }
+
+ // TODO: Switch setting flags that don't seem to ever be used
+
+ // Reload the monster data for the main maze that we're loading
+ mapId = _vm->_party->_mazeId;
+ Common::String filename = Common::String::format("maze%c%03d.mob",
+ (mapId >= 100) ? 'x' : '0', mapId);
+ File mobFile(filename, *_vm->_saves);
+ XeenSerializer sMob(&mobFile, nullptr);
+ _mobData.synchronize(sMob, _monsterData);
+ mobFile.close();
+
+ // Load sprites for the objects
+ for (uint i = 0; i < _mobData._objectSprites.size(); ++i) {
+ if (_vm->_party->_cloudsEnd && _mobData._objectSprites[i]._spriteId == 85 &&
+ mapId == 27 && isDarkCc) {
+ _mobData._objects[29]._spriteId = 0;
+ _mobData._objects[29]._id = 8;
+ _mobData._objectSprites[i]._sprites.clear();
+ } else if (mapId == 12 && _vm->_party->_gameFlags[43] &&
+ _mobData._objectSprites[i]._spriteId == 118 && !isDarkCc) {
+ filename = "085.obj";
+ _mobData._objectSprites[0]._spriteId = 85;
+ } else {
+ filename = Common::String::format("%03d.%cbj",
+ _mobData._objectSprites[i]._spriteId,
+ _mobData._objectSprites[i]._spriteId >= 100 ? '0' : 'o');
+ }
+
+ // Read in the object sprites
+ _mobData._objectSprites[i]._sprites.load(filename,
+ *_vm->_files->_sideArchives[_sideObjects]);
+ }
+
+ // Load sprites for the monsters
+ for (uint i = 0; i < _mobData._monsterSprites.size(); ++i) {
+ CCArchive *archive = _vm->_files->_sideArchives[
+ _mobData._monsterSprites[i]._spriteId == 91 && _vm->getGameID() == GType_WorldOfXeen ?
+ 0 : _sideMonsters];
+
+ filename = Common::String::format("%03d.mon", _mobData._monsterSprites[i]._spriteId);
+ _mobData._monsterSprites[i]._sprites.load(filename, *archive);
+
+ filename = Common::String::format("%03d.att", _mobData._monsterSprites[i]._spriteId);
+ _mobData._monsterSprites[i]._attackSprites.load(filename, *archive);
+ }
+
+ // Load wall picture sprite resources
+ for (uint i = 0; i < _mobData._wallItemSprites.size(); ++i) {
+ filename = Common::String::format("%03d.pic", _mobData._wallItems[i]._spriteId);
+ _mobData._wallItemSprites[i]._sprites.load(filename,
+ *_vm->_files->_sideArchives[_sidePictures]);
+ }
+
+ // Handle loading miscellaneous sprites for the map
+ if (_isOutdoors) {
+ warning("TODO"); // Sound loading
+
+ _groundSprites.load("water.out");
+ _tileSprites.load("outdoor.til");
+ outdoorList._sky1._sprites = &_skySprites[0];
+ outdoorList._sky2._sprites = &_skySprites[0];
+ outdoorList._groundSprite._sprites = &_groundSprites;
+
+ for (int i = 0; i < TOTAL_SURFACES; ++i) {
+ _wallSprites._surfaces[i].clear();
+
+ if (_mazeData[0]._wallTypes[i] != 0) {
+ _wallSprites._surfaces[i].load(Common::String::format("%s.wal",
+ SURFACE_TYPE_NAMES[_mazeData[0]._wallTypes[i]]));
+ }
+
+ _surfaceSprites[i].clear();
+ if (i != 0 && _mazeData[0]._surfaceTypes[i] != 0)
+ _surfaceSprites[i].load(SURFACE_NAMES[_mazeData[0]._surfaceTypes[i]]);
+ }
+ } else {
+ warning("TODO"); // Sound loading
+
+ _skySprites[1].load(Common::String::format("%s.sky",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+ _groundSprites.load(Common::String::format("%s.gnd",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+ _tileSprites.load(Common::String::format("%s.til",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+
+ for (int i = 0; i < TOTAL_SURFACES; ++i) {
+ _surfaceSprites[i].clear();
+
+ if (_mazeData[0]._surfaceTypes[i] != 0 || i == 4)
+ _surfaceSprites[i].load(SURFACE_NAMES[_mazeData[0]._surfaceTypes[i]]);
+ }
+
+ for (int i = 0; i < TOTAL_SURFACES; ++i)
+ _wallSprites._surfaces[i].clear();
+
+ _wallSprites._fwl1.load(Common::String::format("f%s1.fwl",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+ _wallSprites._fwl2.load(Common::String::format("f%s2.fwl",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+ _wallSprites._fwl3.load(Common::String::format("f%s3.fwl",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+ _wallSprites._fwl4.load(Common::String::format("f%s4.fwl",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+ _wallSprites._swl.load(Common::String::format("s%s.swl",
+ TERRAIN_TYPES[_mazeData[0]._wallKind]));
+
+ // Set entries in the indoor draw list to the correct sprites
+ // for drawing various parts of the background
+ indoorList._swl_0F1R._sprites = &_wallSprites._swl;
+ indoorList._swl_0F1L._sprites = &_wallSprites._swl;
+ indoorList._swl_1F1R._sprites = &_wallSprites._swl;
+ indoorList._swl_1F1L._sprites = &_wallSprites._swl;
+ indoorList._swl_2F2R._sprites = &_wallSprites._swl;
+ indoorList._swl_2F1R._sprites = &_wallSprites._swl;
+ indoorList._swl_2F1L._sprites = &_wallSprites._swl;
+ indoorList._swl_2F2L._sprites = &_wallSprites._swl;
+
+ indoorList._swl_3F1R._sprites = &_wallSprites._swl;
+ indoorList._swl_3F2R._sprites = &_wallSprites._swl;
+ indoorList._swl_3F3R._sprites = &_wallSprites._swl;
+ indoorList._swl_3F4R._sprites = &_wallSprites._swl;
+ indoorList._swl_3F1L._sprites = &_wallSprites._swl;
+ indoorList._swl_3F2L._sprites = &_wallSprites._swl;
+ indoorList._swl_3F3L._sprites = &_wallSprites._swl;
+ indoorList._swl_3F4L._sprites = &_wallSprites._swl;
+
+ indoorList._swl_4F4R._sprites = &_wallSprites._swl;
+ indoorList._swl_4F3R._sprites = &_wallSprites._swl;
+ indoorList._swl_4F2R._sprites = &_wallSprites._swl;
+ indoorList._swl_4F1R._sprites = &_wallSprites._swl;
+ indoorList._swl_4F1L._sprites = &_wallSprites._swl;
+ indoorList._swl_4F2L._sprites = &_wallSprites._swl;
+ indoorList._swl_4F3L._sprites = &_wallSprites._swl;
+ indoorList._swl_4F4L._sprites = &_wallSprites._swl;
+
+ indoorList._fwl_4F4R._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F3R._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F2R._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F1R._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F1L._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F2L._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F3L._sprites = &_wallSprites._fwl4;
+ indoorList._fwl_4F4L._sprites = &_wallSprites._fwl4;
+
+ indoorList._fwl_2F1R._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_2F._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_2F1L._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_3F2R._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_3F1R._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_3F._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_3F1L._sprites = &_wallSprites._fwl3;
+ indoorList._fwl_3F2L._sprites = &_wallSprites._fwl3;
+
+ indoorList._fwl_1F._sprites = &_wallSprites._fwl1;
+ indoorList._fwl_1F1R._sprites = &_wallSprites._fwl1;
+ indoorList._fwl_1F1L._sprites = &_wallSprites._fwl1;
+ indoorList._horizon._sprites = &_wallSprites._fwl1;
+
+ indoorList._ground._sprites = &_groundSprites;
+
+ // Don't show horizon for certain maps
+ if (_vm->_files->_isDarkCc) {
+ if ((mapId >= 89 && mapId <= 112) || mapId == 128 || mapId == 129)
+ indoorList._horizon._sprites = nullptr;
+ } else {
+ if (mapId >= 25 && mapId <= 27)
+ indoorList._horizon._sprites = nullptr;
+ }
+ }
+
+ loadSky();
+}
+
+int Map::mazeLookup(const Common::Point &pt, int layerShift, int wallMask) {
+ Common::Point pos = pt;
+ int mapId = _vm->_party->_mazeId;
+
+ if (pt.x < -16 || pt.y < -16 || pt.x >= 32 || pt.y >= 32)
+ error("Invalid coordinate");
+
+ // Find the correct maze data out of the set to use
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != _vm->_party->_mazeId)
+ ++_mazeDataIndex;
+
+ // Handle map changing to the north or south as necessary
+ if (pos.y & 16) {
+ if (pos.y >= 0) {
+ pos.y -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
+ } else {
+ pos.y += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
+ }
+
+ if (mapId) {
+ // Move to the correct map to north/south
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+ } else {
+ // No map, so reached outside indoor area or outer space outdoors
+ _currentSteppedOn = true;
+ return _isOutdoors ? SURFTYPE_SPACE : INVALID_CELL;
+ }
+ }
+
+ // Handle map changing to the east or west as necessary
+ if (pos.x & 16) {
+ if (pos.x >= 0) {
+ pos.x -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
+ } else {
+ pos.x += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
+ }
+
+ if (mapId) {
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+ }
+ }
+
+ if (mapId) {
+ if (_isOutdoors) {
+ _currentSurfaceId = _mazeData[_mazeDataIndex]._wallData[pos.y][pos.x]._outdoors._surfaceId;
+ } else {
+ _currentSurfaceId = _mazeData[_mazeDataIndex]._cells[pos.y][pos.x]._surfaceId;
+ }
+
+ if (_currentSurfaceId == SURFTYPE_SPACE || _currentSurfaceId == SURFTYPE_SKY) {
+ _currentSteppedOn = true;
+ } else {
+ _currentSteppedOn = _mazeData[_mazeDataIndex]._steppedOnTiles[pos.y][pos.x];
+ }
+
+ return (_mazeData[_mazeDataIndex]._wallData[pos.y][pos.x]._data >> layerShift) & wallMask;
+
+ } else {
+ _currentSteppedOn = _isOutdoors;
+ return _isOutdoors ? SURFTYPE_SPACE : INVALID_CELL;
+ }
+}
+
+void Map::loadEvents(int mapId) {
+ // Load events
+ Common::String filename = Common::String::format("maze%c%03d.evt",
+ (mapId >= 100) ? 'x' : '0', mapId);
+ File fEvents(filename, *_vm->_saves);
+ XeenSerializer sEvents(&fEvents, nullptr);
+ _events.synchronize(sEvents);
+ fEvents.close();
+
+ // Load text data
+ filename = Common::String::format("aaze%c%03d.txt",
+ (mapId >= 100) ? 'x' : '0', mapId);
+ File fText(filename);
+ _events._text.clear();
+ while (fText.pos() < fText.size())
+ _events._text.push_back(fText.readString());
+ fText.close();
+}
+
+void Map::saveMaze() {
+ int mazeNum = _mazeData[0]._mazeNumber;
+ if (!mazeNum || (mazeNum == 85 && !_vm->_files->_isDarkCc))
+ return;
+
+ // Save the event data
+ Common::String filename = Common::String::format("maze%c%03d.evt",
+ (mazeNum >= 100) ? 'x' : '0', mazeNum);
+ OutFile fEvents(_vm, filename);
+ XeenSerializer sEvents(nullptr, &fEvents);
+ _events.synchronize(sEvents);
+ fEvents.finalize();
+
+ // Save the maze MOB file
+ filename = Common::String::format("maze%c%03d.mob",
+ (mazeNum >= 100) ? 'x' : '0', mazeNum);
+ OutFile fMob(_vm, filename);
+ XeenSerializer sMob(nullptr, &fEvents);
+ _mobData.synchronize(sMob, _monsterData);
+ fEvents.finalize();
+}
+
+void Map::cellFlagLookup(const Common::Point &pt) {
+ Common::Point pos = pt;
+ int mapId = _vm->_party->_mazeId;
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+
+ // Handle map changing to the north or south as necessary
+ if (pos.y & 16) {
+ if (pos.y >= 0) {
+ pos.y -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
+ } else {
+ pos.y += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
+ }
+
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+ }
+
+ // Handle map changing to the east or west as necessary
+ if (pos.x & 16) {
+ if (pos.x >= 0) {
+ pos.x -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
+ } else {
+ pos.x += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
+ }
+
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+ }
+
+ // Get the cell flags
+ const MazeCell &cell = _mazeData[_mazeDataIndex]._cells[pos.y][pos.x];
+ _currentGrateUnlocked = cell._flags & OUTFLAG_GRATE;
+ _currentCantRest = cell._flags & RESTRICTION_REST;
+ _currentIsDrain = cell._flags & OUTFLAG_DRAIN;
+ _currentIsEvent = cell._flags & FLAG_AUTOEXECUTE_EVENT;
+ _currentSky = (cell._flags & OUTFLAG_OBJECT_EXISTS) ? 1 : 0;
+ _currentMonsterFlags = cell._flags & 7;
+}
+
+void Map::setCellSurfaceFlags(const Common::Point &pt, int bits) {
+ mazeLookup(pt, 0);
+
+ Common::Point mapPos(pt.x & 15, pt.y & 15);
+ MazeCell &cell = _mazeData[_mazeDataIndex]._cells[mapPos.y][mapPos.x];
+ cell._flags |= bits & 0xF8;
+}
+
+void Map::setWall(const Common::Point &pt, Direction dir, int v) {
+ const int XOR_MASKS[4] = { 0xFFF, 0xF0FF, 0xFF0F, 0xFFF0 };
+ mazeLookup(pt, 0, 0);
+
+ Common::Point mapPos(pt.x & 15, pt.y & 15);
+ MazeWallLayers &wallLayer = _mazeData[_mazeDataIndex]._wallData[mapPos.y][mapPos.x];
+ wallLayer._data &= XOR_MASKS[dir];
+ wallLayer._data |= v << WALL_SHIFTS[dir][2];
+}
+
+int Map::getCell(int idx) {
+ int mapId = _vm->_party->_mazeId;
+ Direction dir = _vm->_party->_mazeDirection;
+ Common::Point pt(
+ _vm->_party->_mazePosition.x + SCREEN_POSITIONING_X[_vm->_party->_mazeDirection][idx],
+ _vm->_party->_mazePosition.y + SCREEN_POSITIONING_Y[_vm->_party->_mazeDirection][idx]
+ );
+
+ if (pt.x > 31 || pt.y > 31) {
+ if (_vm->_files->_isDarkCc) {
+ if ((mapId >= 53 && mapId <= 88 && mapId != 73) || (mapId >= 74 && mapId <= 120) ||
+ mapId == 125 || mapId == 126 || mapId == 128 || mapId == 129) {
+ _currentSurfaceId = SURFTYPE_DESERT;
+ } else {
+ _currentSurfaceId = 0;
+ }
+ } else {
+ _currentSurfaceId = (mapId >= 25 && mapId <= 27) ? 7 : 0;
+ }
+ _currentWall = INVALID_CELL;
+ return INVALID_CELL;
+ }
+
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+
+ if (pt.y & 16) {
+ if (pt.y >= 0) {
+ pt.y -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
+ } else {
+ pt.y += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
+ }
+
+ if (!mapId) {
+ if (_isOutdoors) {
+ _currentSurfaceId = SURFTYPE_SPACE;
+ _currentWall = 0;
+ return 0;
+ } else {
+ if (_vm->_files->_isDarkCc) {
+ if ((mapId >= 53 && mapId <= 88 && mapId != 73) || (mapId >= 74 && mapId <= 120) ||
+ mapId == 125 || mapId == 126 || mapId == 128 || mapId == 129) {
+ _currentSurfaceId = 6;
+ } else {
+ _currentSurfaceId = 0;
+ }
+ } else {
+ _currentSurfaceId = (mapId >= 25 && mapId <= 27) ? SURFTYPE_ROAD : SURFTYPE_DEFAULT;
+ }
+
+ _currentWall = INVALID_CELL;
+ return INVALID_CELL;
+ }
+
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+ }
+ }
+
+ if (pt.x & 16) {
+ if (pt.x >= 0) {
+ pt.x -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
+ } else {
+ pt.x += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
+ }
+
+ if (!mapId) {
+ if (_isOutdoors) {
+ _currentSurfaceId = SURFTYPE_SPACE;
+ _currentWall = 0;
+ return 0;
+ } else {
+ if (_vm->_files->_isDarkCc) {
+ if ((mapId >= 53 && mapId <= 88 && mapId != 73) || (mapId >= 74 && mapId <= 120) ||
+ mapId == 125 || mapId == 126 || mapId == 128 || mapId == 129) {
+ _currentSurfaceId = 6;
+ } else {
+ _currentSurfaceId = 0;
+ }
+ } else {
+ _currentSurfaceId = (mapId >= 25 && mapId <= 27) ? SURFTYPE_ROAD : SURFTYPE_DEFAULT;
+ }
+
+ _currentWall = INVALID_CELL;
+ return INVALID_CELL;
+ }
+ }
+
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId != mapId)
+ ++_mazeDataIndex;
+ }
+
+ int wallData = _mazeData[_mazeDataIndex]._wallData[pt.y][pt.x]._data;
+ if (_isOutdoors) {
+ if (mapId) {
+ // TODO: tile is set to word of (wallLayers >> 8) && 0xff? Makes no sense
+ _currentTile = (wallData >> 8) & 0xFF;
+ _currentWall = (wallData >> 4) & 0xF;
+ _currentSurfaceId = wallData & 0xF;
+ } else {
+ _currentSurfaceId = SURFTYPE_DEFAULT;
+ _currentWall = 0;
+ _currentTile = 0;
+ }
+ } else {
+ if (!mapId)
+ return 0;
+
+ if (pt.x > 31 || pt.y > 31)
+ _currentSurfaceId = 7;
+ else
+ _currentSurfaceId = _mazeData[_mazeDataIndex]._cells[pt.y][pt.x]._surfaceId;
+
+ _currentWall = wallData;
+ return (_currentWall >> WALL_SHIFTS[dir][idx]) & 0xF;
+ }
+
+ return _currentWall;
+}
+
+void Map::loadSky() {
+ Party &party = *_vm->_party;
+
+ party._isNight = party._minutes < (5 * 60) || party._minutes >= (21 * 60);
+ _skySprites[0].load(((party._mazeId >= 89 && party._mazeId <= 112) ||
+ party._mazeId == 128 || party._mazeId == 129) || !party._isNight
+ ? "sky.sky" : "night.sky");
+}
+
+void Map::getNewMaze() {
+ Party &party = *_vm->_party;
+ Common::Point pt = party._mazePosition;
+ int mapId = party._mazeId;
+
+ // Get the correct map to use from the cached list
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId == mapId)
+ ++_mazeDataIndex;
+
+ // Adjust Y and X to be in the 0-15 range, and on the correct surrounding
+ // map if either value is < 0 or >= 16
+ if (pt.y & 16) {
+ if (pt.y >= 0) {
+ pt.y -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._north;
+ } else {
+ pt.y += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._south;
+ }
+
+ if (mapId) {
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId == mapId)
+ ++_mazeDataIndex;
+ }
+ }
+
+ if (pt.x & 16) {
+ if (pt.x >= 0) {
+ pt.x -= 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._east;
+ } else {
+ pt.x += 16;
+ mapId = _mazeData[_mazeDataIndex]._surroundingMazes._west;
+ }
+
+ if (mapId) {
+ _mazeDataIndex = 0;
+ while (_mazeData[_mazeDataIndex]._mazeId == mapId)
+ ++_mazeDataIndex;
+ }
+ }
+
+ // Save the adjusted (0,0)-(15,15) position and load the given map.
+ // This will make it the new center, with it's own surrounding mazees loaded
+ party._mazePosition = pt;
+ if (mapId)
+ load(mapId);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/map.h b/engines/xeen/map.h
new file mode 100644
index 0000000000..2c02e6ed2f
--- /dev/null
+++ b/engines/xeen/map.h
@@ -0,0 +1,437 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_MAP_H
+#define XEEN_MAP_H
+
+#include "common/stream.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "xeen/combat.h"
+#include "xeen/party.h"
+#include "xeen/scripts.h"
+#include "xeen/sprites.h"
+
+namespace Xeen {
+
+#define MAP_WIDTH 16
+#define MAP_HEIGHT 16
+#define TOTAL_SURFACES 16
+#define INVALID_CELL 0x8888
+
+class XeenEngine;
+
+enum MonsterType {
+ MONSTER_0 = 0, MONSTER_ANIMAL = 1, MONSTER_INSECT = 2,
+ MONSTER_HUMANOID = 3, MONSTER_UNDEAD = 4, MONSTER_GOLEM = 5,
+ MONSTER_DRAGON = 6
+};
+
+class MonsterStruct {
+public:
+ Common::String _name;
+ int _experience;
+ int _hp;
+ int _accuracy;
+ int _speed;
+ int _numberOfAttacks;
+ int _hatesClass;
+ int _strikes;
+ int _dmgPerStrike;
+ DamageType _attackType;
+ SpecialAttack _specialAttack;
+ int _hitChance;
+ int _rangeAttack;
+ MonsterType _monsterType;
+ int _fireResistence;
+ int _electricityResistence;
+ int _coldResistence;
+ int _poisonResistence;
+ int _energyResistence;
+ int _magicResistence;
+ int _phsyicalResistence;
+ int _field29;
+ int _gold;
+ int _gems;
+ int _itemDrop;
+ bool _flying;
+ int _imageNumber;
+ int _loopAnimation;
+ int _animationEffect;
+ int _fx;
+ Common::String _attackVoc;
+public:
+ MonsterStruct();
+ MonsterStruct(Common::String name, int experience, int hp, int accuracy,
+ int speed, int numberOfAttacks, CharacterClass hatesClass, int strikes,
+ int dmgPerStrike, DamageType attackType, SpecialAttack specialAttack,
+ int hitChance, int rangeAttack, MonsterType monsterType,
+ int fireResistence, int electricityResistence, int coldResistence,
+ int poisonResistence, int energyResistence, int magicResistence,
+ int phsyicalResistence, int field29, int gold, int gems, int itemDrop,
+ bool flying, int imageNumber, int loopAnimation, int animationEffect,
+ int field32, Common::String attackVoc);
+
+ void synchronize(Common::SeekableReadStream &s);
+};
+
+class MonsterData : public Common::Array<MonsterStruct> {
+private:
+ void synchronize(Common::SeekableReadStream &s);
+public:
+ MonsterData();
+
+ void load(const Common::String &name);
+};
+
+class SurroundingMazes {
+public:
+ int _north;
+ int _east;
+ int _south;
+ int _west;
+public:
+ SurroundingMazes();
+
+ void clear();
+
+ void synchronize(Common::SeekableReadStream &s);
+
+ int &operator[](int idx);
+};
+
+class MazeDifficulties {
+public:
+ int _wallNoPass;
+ int _surfaceNoPass;
+ int _unlockDoor;
+ int _unlockBox;
+ int _bashDoor;
+ int _bashGrate;
+ int _bashWall;
+ int _chance2Run;
+public:
+ MazeDifficulties();
+
+ void synchronize(Common::SeekableReadStream &s);
+};
+
+enum MazeFlags {
+ OUTFLAG_GRATE = 0x80, OUTFLAG_DRAIN = 0x20, OUTFLAG_OBJECT_EXISTS = 0x08,
+ INFLAG_INSIDE = 0x08, FLAG_AUTOEXECUTE_EVENT = 0x10,
+ RESTRICTION_ETHERIALIZE = 0x40, RESTRICTION_80 = 0x80,
+ RESTRICTION_TOWN_PORTAL = 0x100, RESTRICTION_SUPER_SHELTER = 0x200,
+ RESTRICTION_TIME_DISTORTION = 0x400, RESTRICTION_LLOYDS_BEACON = 0x800,
+ RESTRICTION_TELPORT = 0x1000, RESTRICTION_2000 = 0x2000,
+ RESTRICTION_REST = 0x4000, RESTRICTION_SAVE = 0x8000,
+
+ FLAG_GROUND_BITS = 7
+};
+
+enum MazeFlags2 { FLAG_IS_OUTDOORS = 0x8000, FLAG_IS_DARK = 0x4000 };
+
+enum SurfaceType {
+ SURFTYPE_DEFAULT = 0,
+ SURFTYPE_WATER = 0, SURFTYPE_DIRT = 1, SURFTYPE_GRASS = 2,
+ SURFTYPE_SNOW = 3, SURFTYPE_SWAMP = 4, SURFTYPE_LAVA = 5,
+ SURFTYPE_DESERT = 6, SURFTYPE_ROAD = 7, SURFTYPE_DWATER = 8,
+ SURFTYPE_TFLR = 9, SURFTYPE_SKY = 10, SURFTYPE_CROAD = 11,
+ SURFTYPE_SEWER = 12, SURFTYPE_CLOUD = 13, SURFTYPE_SCORCH = 14,
+ SURFTYPE_SPACE = 15
+};
+
+union MazeWallLayers {
+ struct MazeWallIndoors {
+ int _wallNorth : 4;
+ int _wallEast : 4;
+ int _wallSouth : 4;
+ int _wallWest : 4;
+ } _indoors;
+ struct MazeWallOutdoors {
+ SurfaceType _surfaceId : 4;
+ int _iMiddle : 4;
+ int _iTop : 4;
+ int _iOverlay : 4;
+ } _outdoors;
+ uint16 _data;
+};
+
+struct MazeCell {
+ int _flags;
+ int _surfaceId;
+ MazeCell() : _flags(0), _surfaceId(0) {}
+};
+
+class MazeData {
+public:
+ // Resource fields
+ MazeWallLayers _wallData[MAP_HEIGHT][MAP_WIDTH];
+ MazeCell _cells[MAP_HEIGHT][MAP_WIDTH];
+ int _mazeNumber;
+ SurroundingMazes _surroundingMazes;
+ int _mazeFlags;
+ int _mazeFlags2;
+ int _wallTypes[16];
+ int _surfaceTypes[16];
+ int _floorType;
+ Common::Point _runPosition;
+ MazeDifficulties _difficulties;
+ int _trapDamage;
+ int _wallKind;
+ int _tavernTips;
+ bool _seenTiles[MAP_HEIGHT][MAP_WIDTH];
+ bool _steppedOnTiles[MAP_HEIGHT][MAP_WIDTH];
+
+ // Misc fields
+ int _mazeId;
+public:
+ MazeData();
+
+ void clear();
+
+ void synchronize(Common::SeekableReadStream &s);
+
+ /**
+ * Flags all tiles for the map as having been stepped on
+ */
+ void setAllTilesStepped();
+
+ void clearCellSurfaces();
+};
+
+class MobStruct {
+public:
+ Common::Point _pos;
+ int _id;
+ Direction _direction;
+public:
+ MobStruct();
+
+ bool synchronize(XeenSerializer &s);
+};
+
+struct MazeObject {
+public:
+ Common::Point _position;
+ int _id;
+ int _frame;
+ int _spriteId;
+ Direction _direction;
+ bool _flipped;
+ SpriteResource *_sprites;
+
+ MazeObject();
+};
+
+struct MazeMonster {
+ Common::Point _position;
+ int _frame;
+ int _id;
+ int _spriteId;
+ bool _isAttacking;
+ int _damageType;
+ int _field9;
+ int _fieldA;
+ int _hp;
+ int _effect1, _effect2;
+ int _effect3;
+ SpriteResource *_sprites;
+ SpriteResource *_attackSprites;
+ MonsterStruct *_monsterData;
+
+ MazeMonster();
+
+ /**
+ * Return the text color to use when displaying the monster's name in combat
+ * to indicate how damaged they are
+ */
+ int getTextColor() const;
+};
+
+class MazeWallItem {
+public:
+ Common::Point _position;
+ int _id;
+ int _frame;
+ int _spriteId;
+ Direction _direction;
+ SpriteResource *_sprites;
+public:
+ MazeWallItem();
+};
+
+struct WallSprites {
+ SpriteResource _surfaces[TOTAL_SURFACES];
+ SpriteResource _fwl1;
+ SpriteResource _fwl2;
+ SpriteResource _fwl3;
+ SpriteResource _fwl4;
+ SpriteResource _swl;
+};
+
+class Map;
+
+class MonsterObjectData {
+ friend class Map;
+public:
+ struct SpriteResourceEntry {
+ int _spriteId;
+ SpriteResource _sprites;
+ SpriteResource _attackSprites;
+
+ SpriteResourceEntry() { _spriteId = -1; }
+ SpriteResourceEntry(int spriteId): _spriteId(spriteId) { }
+ };
+private:
+ XeenEngine *_vm;
+ Common::Array<SpriteResourceEntry> _objectSprites;
+ Common::Array<SpriteResourceEntry> _monsterSprites;
+ Common::Array<SpriteResourceEntry> _monsterAttackSprites;
+ Common::Array<SpriteResourceEntry> _wallItemSprites;
+public:
+ Common::Array<MazeObject> _objects;
+ Common::Array<MazeMonster> _monsters;
+ Common::Array<MazeWallItem> _wallItems;
+public:
+ MonsterObjectData(XeenEngine *vm);
+
+ void synchronize(XeenSerializer &s, MonsterData &monsterData);
+};
+
+class HeadData {
+public:
+ struct HeadEntry {
+ int _left;
+ int _right;
+ };
+ HeadEntry _data[MAP_HEIGHT][MAP_WIDTH];
+public:
+ HeadData();
+
+ void synchronize(Common::SeekableReadStream &s);
+
+ HeadEntry *operator[](int y) { return &_data[y][0]; }
+};
+
+struct AnimationFrame { int _front, _left, _back, _right; };
+struct AnimationFlipped { bool _front, _left, _back, _right; };
+struct AnimationEntry {
+ union {
+ AnimationFrame _positions;
+ int _frames[4];
+ } _frame1;
+ union {
+ AnimationFlipped _positions;
+ bool _flags[4];
+ } _flipped;
+ union {
+ AnimationFrame _positions;
+ int _frames[4];
+ } _frame2;
+
+ /**
+ * Synchronize data for an animation entry
+ */
+ void synchronize(Common::SeekableReadStream &s);
+};
+
+class AnimationInfo : public Common::Array<AnimationEntry> {
+public:
+ /**
+ * Synchronize data for object animations within the game
+ */
+ void synchronize(Common::SeekableReadStream &s);
+
+ /**
+ * Load the animation info objects in the game
+ */
+ void load(const Common::String &name);
+};
+
+class Map {
+private:
+ XeenEngine *_vm;
+ MazeData _mazeData[9];
+ SpriteResource _wallPicSprites;
+ int _sidePictures;
+ int _sideObjects;
+ int _sideMonsters;
+ int _mazeDataIndex;
+
+ /**
+ * Load the events for a new map
+ */
+ void loadEvents(int mapId);
+public:
+ Common::String _mazeName;
+ bool _isOutdoors;
+ MonsterObjectData _mobData;
+ MonsterData _monsterData;
+ MazeEvents _events;
+ HeadData _headData;
+ AnimationInfo _animationInfo;
+ SpriteResource _skySprites[2];
+ SpriteResource _groundSprites;
+ SpriteResource _tileSprites;
+ SpriteResource _surfaceSprites[TOTAL_SURFACES];
+ WallSprites _wallSprites;
+ bool _currentGrateUnlocked;
+ bool _currentCantRest;
+ bool _currentIsDrain;
+ bool _currentIsEvent;
+ int _currentSky;
+ int _currentMonsterFlags;
+ int _currentWall;
+ int _currentTile;
+ int _currentSurfaceId;
+ bool _currentSteppedOn;
+ bool _loadDarkSide;
+ int _sideTownPortal;
+public:
+ Map(XeenEngine *vm);
+
+ void load(int mapId);
+
+ int mazeLookup(const Common::Point &pt, int layerShift, int wallMask = 0xf);
+
+ void cellFlagLookup(const Common::Point &pt);
+
+ void setCellSurfaceFlags(const Common::Point &pt, int bits);
+
+ void setWall(const Common::Point &pt, Direction dir, int v);
+
+ void saveMaze();
+
+ int getCell(int idx);
+
+ MazeData &mazeData() { return _mazeData[0]; }
+
+ MazeData &mazeDataCurrent() { return _mazeData[_mazeDataIndex]; }
+
+ void loadSky();
+
+ void getNewMaze();
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_MAP_H */
diff --git a/engines/xeen/module.mk b/engines/xeen/module.mk
new file mode 100644
index 0000000000..b18f44fa6d
--- /dev/null
+++ b/engines/xeen/module.mk
@@ -0,0 +1,53 @@
+MODULE := engines/xeen
+
+MODULE_OBJS := \
+ worldofxeen/clouds_cutscenes.o \
+ worldofxeen/darkside_cutscenes.o \
+ worldofxeen/worldofxeen.o \
+ character.o \
+ combat.o \
+ debugger.o \
+ detection.o \
+ dialogs.o \
+ dialogs_automap.o \
+ dialogs_char_info.o \
+ dialogs_control_panel.o \
+ dialogs_dismiss.o \
+ dialogs_error.o \
+ dialogs_exchange.o \
+ dialogs_fight_options.o \
+ dialogs_options.o \
+ dialogs_info.o \
+ dialogs_input.o \
+ dialogs_items.o \
+ dialogs_party.o \
+ dialogs_query.o \
+ dialogs_quests.o \
+ dialogs_quick_ref.o \
+ dialogs_spells.o \
+ dialogs_whowill.o \
+ events.o \
+ files.o \
+ font.o \
+ interface.o \
+ interface_map.o \
+ map.o \
+ party.o \
+ resources.o \
+ saves.o \
+ screen.o \
+ scripts.o \
+ sound.o \
+ spells.o \
+ sprites.o \
+ town.o \
+ xeen.o \
+ xsurface.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_XEEN), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/engines/xeen/party.cpp b/engines/xeen/party.cpp
new file mode 100644
index 0000000000..35a8f00fbe
--- /dev/null
+++ b/engines/xeen/party.cpp
@@ -0,0 +1,733 @@
+/* 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 "common/scummsys.h"
+#include "common/algorithm.h"
+#include "xeen/party.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/files.h"
+#include "xeen/resources.h"
+#include "xeen/saves.h"
+#include "xeen/spells.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+Roster::Roster() {
+ resize(TOTAL_CHARACTERS);
+
+ for (int idx = 0; idx < TOTAL_CHARACTERS; ++idx) {
+ // Set the index of the character in the roster list
+ operator[](idx)._rosterId = idx;
+
+ if (idx < XEEN_TOTAL_CHARACTERS) {
+ // Load new character resource
+ Common::String name = Common::String::format("char%02d.fac", idx + 1);
+ _charFaces[idx].load(name);
+ operator[](idx)._faceSprites = &_charFaces[idx];
+ } else {
+ operator[](idx)._faceSprites = nullptr;
+ }
+ }
+}
+
+void Roster::synchronize(Common::Serializer &s) {
+ for (uint i = 0; i < TOTAL_CHARACTERS; ++i)
+ (*this)[i].synchronize(s);
+}
+
+/*------------------------------------------------------------------------*/
+
+Treasure::Treasure() {
+ _hasItems = false;
+ _gold = _gems = 0;
+
+ _categories[0] = &_weapons[0];
+ _categories[1] = &_armor[0];
+ _categories[2] = &_accessories[0];
+ _categories[3] = &_misc[0];
+}
+
+/*------------------------------------------------------------------------*/
+
+XeenEngine *Party::_vm;
+
+Party::Party(XeenEngine *vm) {
+ _vm = vm;
+ _mazeDirection = DIR_NORTH;
+ _mazeId = _priorMazeId = 0;
+ _levitateActive = false;
+ _automapOn = false;
+ _wizardEyeActive = false;
+ _clairvoyanceActive = false;
+ _walkOnWaterActive = false;
+ _blessed = 0;
+ _powerShield = 0;
+ _holyBonus = 0;
+ _heroism = 0;
+ _difficulty = ADVENTURER;
+ _cloudsEnd = false;
+ _darkSideEnd = false;
+ _worldEnd = false;
+ _ctr24 = 0;
+ _day = 0;
+ _year = 0;
+ _minutes = 0;
+ _food = 0;
+ _lightCount = 0;
+ _torchCount = 0;
+ _fireResistence = 0;
+ _electricityResistence = 0;
+ _coldResistence = 0;
+ _poisonResistence = 0;
+ _deathCount = 0;
+ _winCount = 0;
+ _lossCount = 0;
+ _gold = 0;
+ _gems = 0;
+ _bankGold = 0;
+ _bankGems = 0;
+ _totalTime = 0;
+ _rested = false;
+
+ Common::fill(&_gameFlags[0], &_gameFlags[512], false);
+ Common::fill(&_worldFlags[0], &_worldFlags[128], false);
+ Common::fill(&_quests[0], &_quests[64], false);
+ Common::fill(&_questItems[0], &_questItems[85], 0);
+
+ for (int i = 0; i < TOTAL_CHARACTERS; ++i)
+ Common::fill(&_characterFlags[i][0], &_characterFlags[i][24], false);
+
+ _partyDead = false;
+ _newDay = false;
+ _isNight = false;
+ _stepped = false;
+ _damageType = DT_PHYSICAL;
+ _fallMaze = 0;
+ _fallDamage = 0;
+ _dead = false;
+}
+
+void Party::synchronize(Common::Serializer &s) {
+ byte dummy[30];
+ Common::fill(&dummy[0], &dummy[30], 0);
+ int partyCount = _activeParty.size();
+
+ int8 partyMembers[MAX_PARTY_COUNT];
+ if (s.isSaving()) {
+ Common::fill(&partyMembers[0], &partyMembers[8], -1);
+ for (uint idx = 0; idx < _activeParty.size(); ++idx)
+ partyMembers[idx] = _activeParty[idx]._rosterId;
+ } else {
+ _activeParty.clear();
+ }
+
+ s.syncAsByte(partyCount); // Party count
+ s.syncAsByte(partyCount); // Real party count
+ for (int idx = 0; idx < MAX_PARTY_COUNT; ++idx) {
+ s.syncAsByte(partyMembers[idx]);
+ if (s.isLoading() && idx < partyCount && partyMembers[idx] != -1)
+ _activeParty.push_back(_roster[partyMembers[idx]]);
+ }
+
+ s.syncAsByte(_mazeDirection);
+ s.syncAsByte(_mazePosition.x);
+ s.syncAsByte(_mazePosition.y);
+ s.syncAsByte(_mazeId);
+
+ // Game configuration flags not used in this implementation
+ s.syncBytes(dummy, 3);
+
+ s.syncAsByte(_priorMazeId);
+ s.syncAsByte(_levitateActive);
+ s.syncAsByte(_automapOn);
+ s.syncAsByte(_wizardEyeActive);
+ s.syncAsByte(_clairvoyanceActive);
+ s.syncAsByte(_walkOnWaterActive);
+ s.syncAsByte(_blessed);
+ s.syncAsByte(_powerShield);
+ s.syncAsByte(_holyBonus);
+ s.syncAsByte(_heroism);
+ s.syncAsByte(_difficulty);
+
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithWeapons[0][i].synchronize(s);
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithArmor[0][i].synchronize(s);
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithAccessories[0][i].synchronize(s);
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithMisc[0][i].synchronize(s);
+
+ s.syncAsUint16LE(_cloudsEnd);
+ s.syncAsUint16LE(_darkSideEnd);
+ s.syncAsUint16LE(_worldEnd);
+ s.syncAsUint16LE(_ctr24);
+ s.syncAsUint16LE(_day);
+ s.syncAsUint16LE(_year);
+ s.syncAsUint16LE(_minutes);
+ s.syncAsUint16LE(_food);
+ s.syncAsUint16LE(_lightCount);
+ s.syncAsUint16LE(_torchCount);
+ s.syncAsUint16LE(_fireResistence);
+ s.syncAsUint16LE(_electricityResistence);
+ s.syncAsUint16LE(_coldResistence);
+ s.syncAsUint16LE(_poisonResistence);
+ s.syncAsUint16LE(_deathCount);
+ s.syncAsUint16LE(_winCount);
+ s.syncAsUint16LE(_lossCount);
+ s.syncAsUint32LE(_gold);
+ s.syncAsUint32LE(_gems);
+ s.syncAsUint32LE(_bankGold);
+ s.syncAsUint32LE(_bankGems);
+ s.syncAsUint32LE(_totalTime);
+ s.syncAsByte(_rested);
+ SavesManager::syncBitFlags(s, &_gameFlags[0], &_gameFlags[512]);
+ SavesManager::syncBitFlags(s, &_worldFlags[0], &_worldFlags[128]);
+ SavesManager::syncBitFlags(s, &_quests[0], &_quests[64]);
+
+ for (int i = 0; i < 85; ++i)
+ s.syncAsByte(_questItems[i]);
+
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithWeapons[1][i].synchronize(s);
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithArmor[1][i].synchronize(s);
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithAccessories[1][i].synchronize(s);
+ for (int i = 0; i < ITEMS_COUNT; ++i)
+ _blacksmithMisc[1][i].synchronize(s);
+
+ for (int i = 0; i < TOTAL_CHARACTERS; ++i)
+ SavesManager::syncBitFlags(s, &_characterFlags[i][0], &_characterFlags[i][24]);
+ s.syncBytes(&dummy[0], 30);
+}
+
+void Party::loadActiveParty() {
+ // No implementation needed
+}
+
+bool Party::checkSkill(Skill skillId) {
+ uint total = 0;
+ for (uint i = 0; i < _activeParty.size(); ++i) {
+ if (_activeParty[i]._skills[skillId]) {
+ ++total;
+
+ switch (skillId) {
+ case MOUNTAINEER:
+ case PATHFINDER:
+ // At least two characters need skill for check to return true
+ if (total == 2)
+ return true;
+ break;
+ case CRUSADER:
+ case SWIMMING:
+ // Entire party must have skill for check to return true
+ if (total == _activeParty.size())
+ return true;
+ break;
+ default:
+ // All other skills only need to have a single player having it
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool Party::isInParty(int charId) {
+ for (uint i = 0; i < _activeParty.size(); ++i) {
+ if (_activeParty[i]._rosterId == charId)
+ return true;
+ }
+
+ return false;
+}
+
+void Party::copyPartyToRoster() {
+ for (uint i = 0; i < _activeParty.size(); ++i) {
+ _roster[_activeParty[i]._rosterId] = _activeParty[i];
+ }
+}
+
+void Party::changeTime(int numMinutes) {
+ bool killed = false;
+
+ if (((_minutes + numMinutes) / 480) != (_minutes / 480)) {
+ for (int idx = 0; idx < (int)_activeParty.size(); ++idx) {
+ Character &player = _activeParty[idx];
+
+ if (!player._conditions[DEAD] && !player._conditions[STONED] &&
+ !player._conditions[ERADICATED]) {
+ for (int statNum = 0; statNum < TOTAL_STATS; ++statNum) {
+ int statVal = player.getStat((Attribute)statNum);
+ if (statVal < 1)
+ player._conditions[DEAD] = 1;
+ }
+ }
+
+ // Handle heart broken condition becoming depression
+ if (player._conditions[HEART_BROKEN]) {
+ if (++player._conditions[HEART_BROKEN] > 10) {
+ player._conditions[HEART_BROKEN] = 0;
+ player._conditions[DEPRESSED] = 1;
+ }
+ }
+
+ // Handle poisoning
+ if (!player._conditions[POISONED]) {
+ if (_vm->getRandomNumber(1, 10) != 1 || !player.charSavingThrow(DT_ELECTRICAL))
+ player._conditions[POISONED] *= 2;
+ else
+ // Poison wears off
+ player._conditions[POISONED] = 0;
+ }
+
+ // Handle disease
+ if (!player._conditions[DISEASED]) {
+ if (_vm->getRandomNumber(9) != 1 || !player.charSavingThrow(DT_COLD))
+ player._conditions[DISEASED] *= 2;
+ else
+ // Disease wears off
+ player._conditions[DISEASED] = 0;
+ }
+
+ // Handle insane status
+ if (player._conditions[INSANE])
+ player._conditions[INSANE]++;
+
+ if (player._conditions[DEAD]) {
+ if (++player._conditions[DEAD] == 0)
+ player._conditions[DEAD] = -1;
+ }
+
+ if (player._conditions[STONED]) {
+ if (++player._conditions[STONED] == 0)
+ player._conditions[STONED] = -1;
+ }
+
+ if (player._conditions[ERADICATED]) {
+ if (++player._conditions[ERADICATED] == 0)
+ player._conditions[ERADICATED] = -1;
+ }
+
+ if (player._conditions[IN_LOVE]) {
+ if (++player._conditions[IN_LOVE] > 10) {
+ player._conditions[IN_LOVE] = 0;
+ player._conditions[HEART_BROKEN] = 1;
+ }
+ }
+
+ player._conditions[WEAK] = player._conditions[DRUNK];
+ player._conditions[DRUNK] = 0;
+
+ if (player._conditions[DEPRESSED]) {
+ player._conditions[DEPRESSED] = (player._conditions[DEPRESSED] + 1) % 4;
+ }
+ }
+ }
+
+ // Increment the time
+ addTime(numMinutes);
+
+ for (int idx = 0; idx < (int)_activeParty.size(); ++idx) {
+ Character &player = _activeParty[idx];
+
+ if (player._conditions[CONFUSED] && _vm->getRandomNumber(2) == 1) {
+ if (player.charSavingThrow(DT_PHYSICAL)) {
+ player._conditions[CONFUSED] = 0;
+ } else {
+ player._conditions[CONFUSED]--;
+ }
+ }
+
+ if (player._conditions[PARALYZED] && _vm->getRandomNumber(4) == 1)
+ player._conditions[PARALYZED]--;
+ }
+
+ if (killed)
+ _vm->_interface->drawParty(true);
+
+ if (_isNight != (_minutes < (5 * 60) || _minutes >= (21 * 60)))
+ _vm->_map->loadSky();
+}
+
+void Party::addTime(int numMinutes) {
+ int day = _day;
+ _minutes += numMinutes;
+
+ // If the total minutes has exceeded a day, move to next one
+ while (_minutes >= (24 * 60)) {
+ _minutes -= 24 * 60;
+ if (++_day >= 100) {
+ _day -= 100;
+ ++_year;
+ }
+ }
+
+ if ((_day % 10) == 1 || numMinutes > (24 * 60)) {
+ if (_day != day) {
+ warning("TODO: resetBlacksmith? and giveInterest?");
+ }
+ }
+
+ if (_day != day)
+ _newDay = true;
+
+ if (_newDay && _minutes >= 300) {
+ if (_vm->_mode != MODE_9 && _vm->_mode != MODE_17) {
+ resetTemps();
+ if (_rested || _vm->_mode == MODE_SLEEPING) {
+ _rested = false;
+ } else {
+ for (int idx = 0; idx < (int)_activeParty.size(); ++idx) {
+ if (_activeParty[idx]._conditions[WEAK] >= 0)
+ _activeParty[idx]._conditions[WEAK]++;
+ }
+
+ ErrorScroll::show(_vm, THE_PARTY_NEEDS_REST, WT_NONFREEZED_WAIT);
+ }
+
+ _vm->_interface->drawParty(true);
+ }
+
+ _newDay = false;
+ }
+}
+
+void Party::resetTemps() {
+ for (int idx = 0; idx < (int)_activeParty.size(); ++idx) {
+ Character &player = _activeParty[idx];
+
+ player._magicResistence._temporary = 0;
+ player._energyResistence._temporary = 0;
+ player._poisonResistence._temporary = 0;
+ player._electricityResistence._temporary = 0;
+ player._coldResistence._temporary = 0;
+ player._fireResistence._temporary = 0;
+ player._ACTemp = 0;
+ player._level._temporary = 0;
+ player._luck._temporary = 0;
+ player._accuracy._temporary = 0;
+ player._speed._temporary = 0;
+ player._endurance._temporary = 0;
+ player._personality._temporary = 0;
+ player._intellect._temporary = 0;
+ player._might._temporary = 0;
+ }
+
+ _poisonResistence = 0;
+ _coldResistence = 0;
+ _electricityResistence = 0;
+ _fireResistence = 0;
+ _lightCount = 0;
+ _levitateActive = false;
+ _walkOnWaterActive = false;
+ _wizardEyeActive = false;
+ _clairvoyanceActive = false;
+ _heroism = 0;
+ _holyBonus = 0;
+ _powerShield = 0;
+ _blessed = 0;
+}
+
+void Party::handleLight() {
+ Map &map = *_vm->_map;
+
+ if (_stepped) {
+ map.cellFlagLookup(_mazePosition);
+ if (map._currentIsDrain && _lightCount)
+ --_lightCount;
+
+ if (checkSkill(CARTOGRAPHER)) {
+ map.mazeDataCurrent()._steppedOnTiles[_mazePosition.y & 15][_mazePosition.x & 15] = true;
+ }
+ }
+
+ _vm->_interface->_intrIndex1 = _lightCount ||
+ (map.mazeData()._mazeFlags2 & FLAG_IS_DARK) == 0 ? 4 : 0;
+}
+
+int Party::subtract(int mode, uint amount, int whereId, ErrorWaitType wait) {
+ switch (mode) {
+ case 0:
+ // Gold
+ if (whereId) {
+ if (amount <= _bankGold) {
+ _bankGold -= amount;
+ } else {
+ notEnough(0, whereId, false, wait);
+ return false;
+ }
+ }
+ else {
+ if (amount <= _gold) {
+ _gold -= amount;
+ } else {
+ notEnough(0, whereId, false, wait);
+ return false;
+ }
+ }
+ break;
+
+ case 1:
+ // Gems
+ if (whereId) {
+ if (amount <= _bankGems) {
+ _bankGems -= amount;
+ } else {
+ notEnough(0, whereId, false, wait);
+ return false;
+ }
+ }
+ else {
+ if (amount <= _gems) {
+ _gems -= amount;
+ } else {
+ notEnough(0, whereId, false, wait);
+ return false;
+ }
+ }
+ break;
+
+ case 2:
+ // Food
+ if (amount > _food) {
+ _food -= amount;
+ } else {
+ notEnough(5, 0, 0, wait);
+ return false;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+void Party::notEnough(int consumableId, int whereId, bool mode, ErrorWaitType wait) {
+ Common::String msg = Common::String::format(
+ mode ? NO_X_IN_THE_Y : NOT_ENOUGH_X_IN_THE_Y,
+ CONSUMABLE_NAMES[consumableId], WHERE_NAMES[whereId]);
+ ErrorScroll::show(_vm, msg, wait);
+}
+
+void Party::checkPartyDead() {
+ Combat &combat = *_vm->_combat;
+ bool inCombat = _vm->_mode == MODE_COMBAT;
+
+ for (uint charIdx = 0; charIdx < (inCombat ? combat._combatParty.size() : _activeParty.size()); ++charIdx) {
+ Character &c = inCombat ? *combat._combatParty[charIdx] : _activeParty[charIdx];
+ Condition cond = c.worstCondition();
+ if (cond <= CONFUSED || cond == NO_CONDITION) {
+ _dead = false;
+ return;
+ }
+ }
+
+ _dead = true;
+}
+
+void Party::moveToRunLocation() {
+ _mazePosition = _vm->_map->mazeData()._runPosition;
+}
+
+void Party::giveTreasure() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ Scripts &scripts = *_vm->_scripts;
+ SoundManager &sound = *_vm->_sound;
+ Window &w = screen._windows[10];
+
+ if (!_treasure._gold && !_treasure._gems)
+ return;
+
+ bool monstersPresent = false;
+ for (int idx = 0; idx < 26 && !monstersPresent; ++idx)
+ monstersPresent = combat._attackMonsters[idx] != -1;
+
+ if (_vm->_mode != MODE_9 && monstersPresent)
+ return;
+
+ Common::fill(&combat._shooting[0], &combat._shooting[MAX_PARTY_COUNT], 0);
+ intf._charsShooting = false;
+ intf.draw3d(true);
+
+ if (_treasure._gold || _treasure._gems)
+ sound.playFX(54);
+
+ events.clearEvents();
+ w.close();
+ w.open();
+ w.writeString(Common::String::format(PARTY_FOUND, _treasure._gold, _treasure._gems));
+ w.update();
+
+ if (_vm->_mode != MODE_COMBAT)
+ _vm->_mode = MODE_7;
+
+ if (arePacksFull())
+ ErrorScroll::show(_vm, BACKPACKS_FULL_PRESS_KEY, WT_NONFREEZED_WAIT);
+
+ for (int categoryNum = 0; categoryNum < NUM_ITEM_CATEGORIES; ++categoryNum) {
+ for (int itemNum = 0; itemNum < MAX_TREASURE_ITEMS; ++itemNum) {
+ if (arePacksFull()) {
+ if (_treasure._weapons[itemNum]._id == 34) {
+ // Important item, so clear a slot for it
+ _activeParty[0]._weapons[INV_ITEMS_TOTAL - 1].clear();
+ } else {
+ // Otherwise, clear all the remaining treasure items,
+ // since all the party's packs are full
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ _treasure._weapons[idx].clear();
+ _treasure._armor[idx].clear();
+ _treasure._accessories[idx].clear();
+ _treasure._armor[idx].clear();
+ }
+ }
+ }
+
+ // If there's no treasure item to be distributed, skip to next slot
+ if (!_treasure._categories[categoryNum][itemNum]._id)
+ continue;
+
+ int charIndex = scripts._whoWill - 1;
+ if (charIndex >= 0 && charIndex < (int)_activeParty.size()) {
+ // Check the designated character first
+ Character &c = _activeParty[charIndex];
+ if (!c._items[(ItemCategory)categoryNum].isFull() && !c.isDisabledOrDead()) {
+ giveTreasureToCharacter(c, (ItemCategory)categoryNum, itemNum);
+ continue;
+ }
+
+ // Fall back on checking the entire conscious party
+ for (charIndex = 0; charIndex < (int)_activeParty.size(); ++charIndex) {
+ Character &ch = _activeParty[charIndex];
+ if (!ch._items[(ItemCategory)categoryNum].isFull() && !ch.isDisabledOrDead()) {
+ giveTreasureToCharacter(ch, (ItemCategory)categoryNum, itemNum);
+ break;
+ }
+ }
+ if (charIndex != (int)_activeParty.size())
+ continue;
+ }
+
+ // At this point, find an empty pack for any character, irrespective
+ // of whether the character is conscious or not
+ for (charIndex = 0; charIndex < (int)_activeParty.size(); ++charIndex) {
+ Character &c = _activeParty[charIndex];
+ if (!c._items[(ItemCategory)categoryNum].isFull() && !c.isDisabledOrDead()) {
+ giveTreasureToCharacter(c, (ItemCategory)categoryNum, itemNum);
+ continue;
+ }
+ }
+ }
+ }
+
+ w.writeString(HIT_A_KEY);
+ w.update();
+
+ do {
+ events.updateGameCounter();
+ intf.draw3d(true);
+
+ while (!events.isKeyMousePressed() && events.timeElapsed() < 1)
+ events.pollEventsAndWait();
+ } while (!_vm->shouldQuit() && events.timeElapsed() == 1);
+
+ if (_vm->_mode != MODE_COMBAT)
+ _vm->_mode = MODE_1;
+
+ w.close();
+ _gold += _treasure._gold;
+ _gems += _treasure._gems;
+ _treasure._gold = 0;
+ _treasure._gems = 0;
+
+ _treasure._hasItems = false;
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ _treasure._weapons[idx].clear();
+ _treasure._armor[idx].clear();
+ _treasure._accessories[idx].clear();
+ _treasure._armor[idx].clear();
+ }
+
+ scripts._v2 = 1;
+}
+
+bool Party::arePacksFull() const {
+ uint total = 0;
+ for (uint idx = 0; idx < _activeParty.size(); ++idx) {
+ const Character &c = _activeParty[idx];
+ total += (c._weapons[INV_ITEMS_TOTAL - 1]._id != 0 ? 1 : 0)
+ + (c._armor[INV_ITEMS_TOTAL - 1]._id != 0 ? 1 : 0)
+ + (c._accessories[INV_ITEMS_TOTAL - 1]._id != 0 ? 1 : 0)
+ + (c._misc[INV_ITEMS_TOTAL - 1]._id != 0 ? 1 : 0);
+ }
+
+ return total == (_activeParty.size() * NUM_ITEM_CATEGORIES);
+}
+
+void Party::giveTreasureToCharacter(Character &c, ItemCategory category, int itemIndex) {
+ EventsManager &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Window &w = screen._windows[10];
+ XeenItem &treasureItem = _treasure._categories[category][itemIndex];
+ sound.playFX(20);
+
+ if (treasureItem._id < 82) {
+ // Copy item into the character's inventory
+ c._items[category][INV_ITEMS_TOTAL - 1] = treasureItem;
+ c._items[category].sort();
+ }
+
+ w.writeString(GIVE_TREASURE_FORMATTING);
+ w.update();
+ events.ipause(5);
+
+ w.writeString(Common::String::format(X_FOUND_Y, c._name.c_str(),
+ ITEM_NAMES[category][treasureItem._id]));
+ w.update();
+
+ events.ipause(5);
+}
+
+bool Party::canShoot() const {
+ for (uint idx = 0; idx < _activeParty.size(); ++idx) {
+ if (_activeParty[idx].hasMissileWeapon())
+ return true;
+ }
+
+ return false;
+}
+
+bool Party::giveTake(int mode1, uint32 mask1, int mode2, int mask2, int charIdx) {
+ error("TODO");
+}
+
+
+} // End of namespace Xeen
diff --git a/engines/xeen/party.h b/engines/xeen/party.h
new file mode 100644
index 0000000000..d753b75801
--- /dev/null
+++ b/engines/xeen/party.h
@@ -0,0 +1,204 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_PARTY_H
+#define XEEN_PARTY_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/serializer.h"
+#include "xeen/character.h"
+#include "xeen/combat.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/items.h"
+
+namespace Xeen {
+
+enum Direction {
+ DIR_NORTH = 0, DIR_EAST = 1, DIR_SOUTH = 2, DIR_WEST = 3, DIR_ALL = 4
+};
+
+enum Difficulty { ADVENTURER = 0, WARRIOR = 1 };
+
+#define ITEMS_COUNT 36
+#define TOTAL_CHARACTERS 30
+#define XEEN_TOTAL_CHARACTERS 24
+#define MAX_ACTIVE_PARTY 6
+#define MAX_PARTY_COUNT 8
+#define TOTAL_STATS 7
+#define TOTAL_QUEST_ITEMS 85
+#define TOTAL_QUEST_FLAGS 56
+#define MAX_TREASURE_ITEMS 10
+
+class Roster: public Common::Array<Character> {
+public:
+ SpriteResource _charFaces[TOTAL_CHARACTERS];
+public:
+ Roster();
+
+ void synchronize(Common::Serializer &s);
+};
+
+class Treasure {
+public:
+ XeenItem _misc[MAX_TREASURE_ITEMS];
+ XeenItem _accessories[MAX_TREASURE_ITEMS];
+ XeenItem _armor[MAX_TREASURE_ITEMS];
+ XeenItem _weapons[MAX_TREASURE_ITEMS];
+ XeenItem *_categories[4];
+ bool _hasItems;
+ int _gems, _gold;
+public:
+ Treasure();
+};
+
+class Party {
+ friend class Character;
+ friend class InventoryItems;
+private:
+ static XeenEngine *_vm;
+
+ /**
+ * Give a treasure item to the given character's inventory
+ */
+ void giveTreasureToCharacter(Character &c, ItemCategory category, int itemIndex);
+public:
+ // Dynamic data that's saved
+ Direction _mazeDirection;
+ Common::Point _mazePosition;
+ int _mazeId;
+ int _priorMazeId;
+ bool _levitateActive;
+ bool _automapOn;
+ bool _wizardEyeActive;
+ bool _clairvoyanceActive;
+ bool _walkOnWaterActive;
+ int _blessed;
+ int _powerShield;
+ int _holyBonus;
+ int _heroism;
+ Difficulty _difficulty;
+ XeenItem _blacksmithWeapons[2][ITEMS_COUNT];
+ XeenItem _blacksmithArmor[2][ITEMS_COUNT];
+ XeenItem _blacksmithAccessories[2][ITEMS_COUNT];
+ XeenItem _blacksmithMisc[2][ITEMS_COUNT];
+ bool _cloudsEnd;
+ bool _darkSideEnd;
+ bool _worldEnd;
+ int _ctr24; // TODO: Figure out proper name
+ int _day;
+ uint _year;
+ int _minutes;
+ uint _food;
+ int _lightCount;
+ int _torchCount;
+ int _fireResistence;
+ int _electricityResistence;
+ int _coldResistence;
+ int _poisonResistence;
+ int _deathCount;
+ int _winCount;
+ int _lossCount;
+ uint _gold;
+ uint _gems;
+ uint _bankGold;
+ uint _bankGems;
+ int _totalTime;
+ bool _rested;
+ bool _gameFlags[512];
+ bool _worldFlags[128];
+ bool _quests[64];
+ int _questItems[TOTAL_QUEST_ITEMS];
+ bool _characterFlags[30][24];
+public:
+ // Other party related runtime data
+ Roster _roster;
+ Common::Array<Character> _activeParty;
+ bool _partyDead;
+ bool _newDay;
+ bool _isNight;
+ bool _stepped;
+ Common::Point _fallPosition;
+ int _fallMaze;
+ int _fallDamage;
+ DamageType _damageType;
+ bool _dead;
+ Treasure _treasure;
+ Treasure _savedTreasure;
+public:
+ Party(XeenEngine *vm);
+
+ void synchronize(Common::Serializer &s);
+
+ void loadActiveParty();
+
+ bool checkSkill(Skill skillId);
+
+ bool isInParty(int charId);
+
+ /**
+ * Copy the currently active party characters' data back to the roster
+ */
+ void copyPartyToRoster();
+
+ /**
+ * Adds time to the party's playtime, taking into account the effect of any
+ * stat modifier changes
+ */
+ void changeTime(int numMinutes);
+
+ void addTime(int numMinutes);
+
+ void resetTemps();
+
+ void handleLight();
+
+ int subtract(int mode, uint amount, int whereId, ErrorWaitType wait = WT_FREEZE_WAIT);
+
+ void notEnough(int consumableId, int whereId, bool mode, ErrorWaitType wait);
+
+ void checkPartyDead();
+
+ /**
+ * Move party position to the run destination on the current map
+ */
+ void moveToRunLocation();
+
+ /**
+ * Give treasure to the party
+ */
+ void giveTreasure();
+
+ /**
+ * Returns true if all the packs for all the characters are full
+ */
+ bool arePacksFull() const;
+
+ bool canShoot() const;
+
+ bool giveTake(int mode1, uint32 mask1, int mode2, int mask2, int charIdx);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_PARTY_H */
diff --git a/engines/xeen/resources.cpp b/engines/xeen/resources.cpp
new file mode 100644
index 0000000000..c171a1717b
--- /dev/null
+++ b/engines/xeen/resources.cpp
@@ -0,0 +1,1590 @@
+/* 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 "common/scummsys.h"
+#include "xeen/resources.h"
+#include "xeen/files.h"
+
+namespace Xeen {
+
+Resources::Resources() {
+ _globalSprites.load("global.icn");
+
+ File f("mae.xen");
+ while (f.pos() < f.size())
+ _maeNames.push_back(f.readString());
+ f.close();
+}
+
+/*------------------------------------------------------------------------*/
+
+const char *const CREDITS =
+ "\013""012\010""000\003""c\014""35Designed and Directed By:\n"
+ "\014""17Jon Van Caneghem\003""l\n"
+ "\n"
+ "\t025\014""35Programming:\n"
+ "\t035\014""17Mark Caldwell\n"
+ "\t035Dave Hathaway\n"
+ "\n"
+ "\t025\014""35Sound System & FX:\n"
+ "\t035\014""17Mike Heilemann\n"
+ "\n"
+ "\t025\014""35Music & Speech:\n"
+ "\t035\014""17Tim Tully\n"
+ "\n"
+ "\t025\014""35Writing:\n"
+ "\t035\014""17Paul Rattner\n"
+ "\t035Debbie Van Caneghem\n"
+ "\t035Jon Van Caneghem\013""012\n"
+ "\n"
+ "\n"
+ "\t180\014""35Graphics:\n"
+ "\t190\014""17Jonathan P. Gwyn\n"
+ "\t190Bonita Long-Hemsath\n"
+ "\t190Julia Ulano\n"
+ "\t190Ricardo Barrera\n"
+ "\n"
+ "\t180\014""35Testing:\n"
+ "\t190\014""17Benjamin Bent\n"
+ "\t190Christian Dailey\n"
+ "\t190Mario Escamilla\n"
+ "\t190Marco Hunter\n"
+ "\t190Robert J. Lupo\n"
+ "\t190Clayton Retzer\n"
+ "\t190David Vela\003""c";
+
+const char *const OPTIONS_TITLE =
+ "\x0D\x01\003""c\014""dMight and Magic Options\n"
+ "World of Xeen\x02\n"
+ "\v117Copyright (c) 1993 NWC, Inc.\n"
+ "All Rights Reserved\x01";
+
+const char *const THE_PARTY_NEEDS_REST = "\x0B""012The Party needs rest!";
+
+const char *const WHO_WILL = "\x03""c\x0B""000\x09""000%s\x0A\x0A"
+ "Who will\x0A%s?\x0A\x0B""055F1 - F%d";
+
+const char *const WHATS_THE_PASSWORD = "What's the Password?";
+
+const char *const IN_NO_CONDITION = "\x0B""007%s is not in any condition to perform actions!";
+
+const char *const NOTHING_HERE = "\x03""c\x0B""010Nothing here.";
+
+const char *const TERRAIN_TYPES[6] = {
+ "town", "cave", "towr", "cstl", "dung", "scfi"
+};
+
+const char *const SURFACE_TYPE_NAMES[15] = {
+ nullptr, "mount", "ltree", "dtree", "grass", "snotree", "snomnt",
+ "dedltree", "mount", "lavamnt", "palm", "dmount", "dedltree",
+ "dedltree", "dedltree"
+};
+
+const char *const SURFACE_NAMES[16] = {
+ "water.srf", "dirt.srf", "grass.srf", "snow.srf", "swamp.srf",
+ "lava.srf", "desert.srf", "road.srf", "dwater.srf", "tflr.srf",
+ "sky.srf", "croad.srf", "sewer.srf", "cloud.srf", "scortch.srf",
+ "space.srf"
+};
+
+const char *const WHO_ACTIONS[32] = {
+ "search", "open", "drink", "mine", "touch", "read", "learn", "take",
+ "bang", "steal", "bribe", "pay", "sit", "try", "turn", "bathe",
+ "destroy", "pull", "descend", "toss a coin", "pray", "join", "act",
+ "play", "push", "rub", "pick", "eat", "sign", "close", "look", "try"
+};
+
+const char *const WHO_WILL_ACTIONS[4] = {
+ "Open Grate", "Open Door", "Open Scroll", "Select Char"
+};
+
+const byte SYMBOLS[20][64] = {
+ { // 0
+ 0x00, 0x00, 0xA8, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0x00, 0xA8, 0x9E, 0x9C, 0x9C, 0x9E, 0x9E, 0x9E,
+ 0xAC, 0x9C, 0xA4, 0xAC, 0xAC, 0x9A, 0x9A, 0x9A, 0xAC, 0x9E, 0xAC, 0xA8, 0xA8, 0xA6, 0x97, 0x98,
+ 0xAC, 0xA0, 0xAC, 0xAC, 0xA4, 0xA6, 0x98, 0x99, 0x00, 0xAC, 0xA0, 0xA0, 0xA8, 0xAC, 0x9A, 0x9A,
+ 0x00, 0x00, 0xAC, 0xAC, 0xAC, 0xA4, 0x9B, 0x9A, 0x00, 0x00, 0x00, 0x00, 0xAC, 0xA0, 0x9B, 0x9B,
+ },
+ { // 1
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E,
+ 0x99, 0x9A, 0x9A, 0x99, 0x99, 0x99, 0x9A, 0x99, 0x98, 0x98, 0x98, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x99, 0x98, 0x98, 0x99, 0x98, 0x98, 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A,
+ 0x9A, 0x9B, 0x9B, 0x9C, 0x9B, 0x9A, 0x9C, 0x9A, 0x9B, 0x9A, 0x99, 0x99, 0x99, 0x9A, 0x9A, 0x9B,
+ },
+ { // 2
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E,
+ 0x99, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x99, 0x98, 0x98, 0x99, 0x98, 0x98, 0x97, 0x98, 0x98,
+ 0x99, 0x98, 0x98, 0x98, 0x99, 0x99, 0x98, 0x99, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A,
+ 0x9B, 0x9B, 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0x9B, 0x99, 0x9A, 0x9B, 0x9B, 0x9A, 0x9A, 0x99, 0x9A,
+ },
+ { // 3
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E,
+ 0x99, 0x9A, 0x9A, 0x9A, 0x99, 0x99, 0x99, 0x9A, 0x98, 0x98, 0x97, 0x97, 0x98, 0x98, 0x98, 0x98,
+ 0x99, 0x99, 0x98, 0x99, 0x98, 0x98, 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A,
+ 0x9B, 0x9C, 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0x9C, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x99, 0x99, 0x9A,
+ },
+ { // 4
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E,
+ 0x9A, 0x9A, 0x9A, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x97, 0x97, 0x97, 0x97, 0x97, 0x98, 0x98, 0x98,
+ 0x99, 0x99, 0x98, 0x99, 0x99, 0x98, 0x98, 0x98, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A,
+ 0x9A, 0x9C, 0x9B, 0x9B, 0x9C, 0x9B, 0x9B, 0x9B, 0x9A, 0x99, 0x9B, 0x9B, 0x9A, 0x99, 0x9A, 0x9A,
+ },
+ { // 5
+ 0xA4, 0xA4, 0xA8, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x9E, 0x9E, 0x9E, 0xA0, 0xA8, 0xAC, 0x00, 0x00,
+ 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9E, 0xAC, 0x00, 0x97, 0x97, 0x97, 0x98, 0x9C, 0x9C, 0xA0, 0xAC,
+ 0x99, 0x98, 0x99, 0x99, 0x99, 0x9B, 0xA0, 0xAC, 0x9A, 0x9A, 0x9A, 0x9A, 0x9A, 0x9B, 0xA0, 0xAC,
+ 0x9C, 0x9B, 0x9C, 0x9C, 0x9C, 0xA0, 0xAC, 0x00, 0x99, 0x9A, 0x9A, 0x9B, 0x9B, 0xA4, 0xAC, 0x00,
+ },
+ { // 6
+ 0x00, 0x00, 0x00, 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x00, 0x00, 0x00, 0xAC, 0xA0, 0x9C, 0x9B, 0x99,
+ 0x00, 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x98, 0x99, 0x99,
+ 0x00, 0xAC, 0xA0, 0x9C, 0x9C, 0xA0, 0x9C, 0x9A, 0x00, 0x00, 0xAC, 0xA4, 0xA0, 0x99, 0x99, 0x99,
+ 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x00, 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x99, 0x99,
+ },
+ { // 7
+ 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99, 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x00, 0x00, 0xAC, 0xA4, 0x9C, 0x9C, 0x99, 0x99,
+ 0x00, 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x00, 0x00, 0x00, 0xAC, 0xA4, 0x9C, 0x99, 0x99,
+ 0x00, 0x00, 0xAC, 0xA0, 0x9B, 0xA0, 0x9E, 0x9C, 0x00, 0xAC, 0xA4, 0x9C, 0x99, 0x9C, 0x99, 0x99,
+ },
+ { // 8
+ 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x9B, 0x99, 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99, 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x00, 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x99, 0x99, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x9C, 0x99, 0x00, 0xAC, 0xA4, 0x9C, 0x99, 0x9E, 0x9C, 0x99,
+ },
+ { // 9
+ 0x00, 0x00, 0xAC, 0xA4, 0xA0, 0x9C, 0x99, 0x99, 0x00, 0xAC, 0xA0, 0x9C, 0x9C, 0xA0, 0x9C, 0x9A,
+ 0xAC, 0xA4, 0x9C, 0x9A, 0x99, 0x99, 0x99, 0x99, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0xAC, 0xA4, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x99, 0x00, 0xAC, 0xA0, 0x9C, 0x99, 0x99, 0x99, 0x99,
+ 0x00, 0xAC, 0xA4, 0x9C, 0x9A, 0x9C, 0x99, 0x99, 0x00, 0x00, 0xAC, 0xA0, 0x9C, 0x9A, 0x99, 0x99,
+ },
+ { // 10
+ 0x99, 0x99, 0x99, 0x9A, 0xA0, 0xAC, 0x00, 0x00, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x00,
+ 0x99, 0x99, 0x9C, 0x9E, 0xA4, 0xAC, 0x00, 0x00, 0x99, 0x99, 0x9C, 0x99, 0x9C, 0xA4, 0xAC, 0x00,
+ 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x00,
+ 0x99, 0x99, 0x99, 0xA0, 0xA4, 0xAC, 0x00, 0x00, 0x9A, 0x9B, 0x9E, 0x9C, 0x9C, 0xA4, 0xAC, 0x00,
+ },
+ { // 11
+ 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0x9E, 0xAC,
+ 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA4, 0xAC, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC,
+ 0x9C, 0x99, 0x99, 0x99, 0x9C, 0x9C, 0xA4, 0xAC, 0x99, 0x9E, 0x9E, 0x9C, 0x9C, 0xA0, 0xAC, 0x00,
+ },
+ { // 12
+ 0x99, 0x99, 0x9C, 0xA0, 0xA4, 0xAC, 0x00, 0x00, 0x9B, 0x9C, 0x9E, 0x9C, 0x9C, 0xA4, 0xAC, 0x00,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0xA0, 0xAC, 0x00, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC,
+ 0x99, 0x99, 0x99, 0x99, 0x9C, 0x9C, 0xA4, 0xAC, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xA4, 0xAC, 0x00,
+ 0x99, 0x99, 0x9C, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC,
+ },
+ { // 13
+ 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC, 0x00, 0x00,
+ 0x99, 0x9B, 0x9C, 0xA0, 0xA4, 0xAC, 0x00, 0x00, 0x99, 0x99, 0x9A, 0x99, 0x9C, 0xA0, 0xAC, 0x00,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA4, 0xAC, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9C, 0xA0, 0xAC,
+ 0x99, 0x99, 0x99, 0x99, 0x9A, 0x9C, 0xA4, 0xAC, 0x99, 0x99, 0x99, 0x9A, 0x9C, 0xA4, 0xAC, 0x00,
+ },
+ { // 14
+ 0x00, 0x00, 0xAC, 0x9E, 0x9C, 0x9C, 0x9C, 0x9B, 0x00, 0xAC, 0x9C, 0xA0, 0x9E, 0xA4, 0xA4, 0xA4,
+ 0xAC, 0x9C, 0xA4, 0xAC, 0xAC, 0xAC, 0x9C, 0x9E, 0xAC, 0xA0, 0xAC, 0xA8, 0x9E, 0xA8, 0xAC, 0x99,
+ 0xAC, 0x9E, 0xAC, 0xA8, 0xAC, 0x9E, 0xA4, 0xAC, 0xAC, 0xA4, 0xA0, 0xAC, 0xAC, 0xA0, 0xA4, 0xAC,
+ 0x00, 0xAC, 0xA4, 0xA0, 0xA0, 0xA4, 0xAC, 0xA4, 0x00, 0x00, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC,
+ },
+ { // 15
+ 0x9C, 0x9C, 0x9C, 0x9B, 0x9C, 0x9C, 0x9C, 0x9B, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4,
+ 0x9E, 0x9E, 0x9E, 0x9C, 0x9E, 0x9E, 0x9E, 0x9E, 0x99, 0x99, 0x99, 0x99, 0x99, 0x98, 0x99, 0x98,
+ 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x9E, 0x9E, 0xA0,
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC,
+ },
+ { // 16
+ 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x9B, 0x9C, 0x9C, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4,
+ 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9E, 0x98, 0x98, 0x98, 0x98, 0x99, 0x99, 0x99, 0x99,
+ 0x9C, 0x9B, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0xA0, 0xA0, 0xA0, 0x9E, 0xA0, 0x9E, 0x9E, 0xA0,
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC,
+ },
+ { // 17
+ 0x9C, 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0x9C, 0x9B, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4,
+ 0x9E, 0x9E, 0x9E, 0x9C, 0x9C, 0x9C, 0x9E, 0x9E, 0x98, 0x98, 0x98, 0x99, 0x9A, 0x9A, 0x99, 0x98,
+ 0x9C, 0x9B, 0x9C, 0x9C, 0x9C, 0x9B, 0x9B, 0x9C, 0xA0, 0x9E, 0x9E, 0xA0, 0xA0, 0xA0, 0xA0, 0x9E,
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC,
+ },
+ { // 18
+ 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4,
+ 0x9E, 0x9E, 0x9E, 0x9E, 0x9C, 0x9C, 0x9C, 0x9E, 0x98, 0x98, 0x98, 0x98, 0x9A, 0x9A, 0x98, 0x99,
+ 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9B, 0x9C, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0xA0, 0xA0, 0xA0,
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC,
+ },
+ { // 19
+ 0x9C, 0x9B, 0x9C, 0x9C, 0xA0, 0xA4, 0xAC, 0x00, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xAC, 0x00, 0x00,
+ 0x9E, 0x9E, 0x9C, 0x9C, 0x9E, 0xA0, 0xAC, 0x00, 0x99, 0x98, 0x98, 0x99, 0x9A, 0x9A, 0xA0, 0xAC,
+ 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0xA0, 0xAC, 0xA0, 0xA0, 0x9E, 0xA0, 0xA0, 0xA0, 0xA0, 0xAC,
+ 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xA4, 0xAC, 0x00, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0x00, 0x00,
+ }
+};
+
+const byte TEXT_COLORS[40][4] = {
+ { 0x00, 0x19, 0x19, 0x19 },
+ { 0x00, 0x08, 0x08, 0x08 },
+ { 0x00, 0x0F, 0x0F, 0x0F },
+ { 0x00, 0x15, 0x15, 0x15 },
+ { 0x00, 0x01, 0x01, 0x01 },
+ { 0x00, 0x21, 0x21, 0x21 },
+ { 0x00, 0x26, 0x26, 0x26 },
+ { 0x00, 0x2B, 0x2B, 0x2B },
+ { 0x00, 0x31, 0x31, 0x31 },
+ { 0x00, 0x36, 0x36, 0x36 },
+ { 0x00, 0x3D, 0x3D, 0x3D },
+ { 0x00, 0x41, 0x41, 0x41 },
+ { 0x00, 0x46, 0x46, 0x46 },
+ { 0x00, 0x4C, 0x4C, 0x4C },
+ { 0x00, 0x50, 0x50, 0x50 },
+ { 0x00, 0x55, 0x55, 0x55 },
+ { 0x00, 0x5D, 0x5D, 0x5D },
+ { 0x00, 0x60, 0x60, 0x60 },
+ { 0x00, 0x65, 0x65, 0x65 },
+ { 0x00, 0x6C, 0x6C, 0x6C },
+ { 0x00, 0x70, 0x70, 0x70 },
+ { 0x00, 0x75, 0x75, 0x75 },
+ { 0x00, 0x7B, 0x7B, 0x7B },
+ { 0x00, 0x80, 0x80, 0x80 },
+ { 0x00, 0x85, 0x85, 0x85 },
+ { 0x00, 0x8D, 0x8D, 0x8D },
+ { 0x00, 0x90, 0x90, 0x90 },
+ { 0x00, 0x97, 0x97, 0x97 },
+ { 0x00, 0x9D, 0x9D, 0x9D },
+ { 0x00, 0xA4, 0xA4, 0xA4 },
+ { 0x00, 0xAB, 0xAB, 0xAB },
+ { 0x00, 0xB0, 0xB0, 0xB0 },
+ { 0x00, 0xB6, 0xB6, 0xB6 },
+ { 0x00, 0xBD, 0xBD, 0xBD },
+ { 0x00, 0xC0, 0xC0, 0xC0 },
+ { 0x00, 0xC6, 0xC6, 0xC6 },
+ { 0x00, 0xCD, 0xCD, 0xCD },
+ { 0x00, 0xD0, 0xD0, 0xD0 },
+ { 0x00, 0xD6, 0xD6, 0xD6 },
+ { 0x00, 0xDB, 0xDB, 0xDB },
+};
+
+const char *const DIRECTION_TEXT_UPPER[4] = { "NORTH", "EAST", "SOUTH", "WEST" };
+
+const char *const DIRECTION_TEXT[4] = { "North", "East", "South", "West" };
+
+const char *const RACE_NAMES[5] = { "Human", "Elf", "Dwarf", "Gnome", "H-Orc" };
+
+const int RACE_HP_BONUSES[5] = { 0, -2, 1, -1, 2 };
+
+const int RACE_SP_BONUSES[5][2] = {
+ { 0, 0 }, { 2, 0 }, { -1, -1 }, { 1, 1 }, { -2, -2 }
+};
+
+const char *const ALIGNMENT_NAMES[3] = { "Good", "Neutral", "Evil" };
+
+const char *const SEX_NAMES[2] = { "Male", "Female" };
+
+const char *const SKILL_NAMES[18] = {
+ "Thievery", "Arms Master", "Astrologer", "Body Builder", "Cartographer",
+ "Crusader", "Direction Sense", "Linguist", "Merchant", "Mountaineer",
+ "Navigator", "Path Finder", "Prayer Master", "Prestidigitator",
+ "Swimmer", "Tracker", "Spot Secret Door", "Danger Sense"
+};
+
+const char *const CLASS_NAMES[11] = {
+ "Knight", "Paladin", "Archer", "Cleric", "Sorcerer", "Robber",
+ "Ninja", "Barbarian", "Druid", "Ranger", nullptr
+};
+
+const uint CLASS_EXP_LEVELS[10] = {
+ 1500, 2000, 2000, 1500, 2000, 1000, 1500, 1500, 1500, 2000
+};
+
+const char *const CONDITION_NAMES[17] = {
+ "Cursed", "Heart Broken", "Weak", "Poisoned", "Diseased",
+ "Insane", "In Love", "Drunk", "Asleep", "Depressed", "Confused",
+ "Paralyzed", "Unconscious", "Dead", "Stone", "Eradicated", "Good"
+};
+
+const int CONDITION_COLORS[17] = {
+ 9, 9, 9, 9, 9, 9, 9, 9, 32, 32, 32, 32, 6, 6, 6, 6, 15
+};
+
+const char *const GOOD = "Good";
+
+const char *const BLESSED = "\n\t020Blessed\t095%+d";
+
+const char *const POWER_SHIELD = "\n\t020Power Shield\t095%+d";
+
+const char *const HOLY_BONUS = "\n\t020Holy Bonus\t095%+d";
+
+const char *const HEROISM = "\n\t020Heroism\t095%+d";
+
+const char *const IN_PARTY = "\014""15In Party\014""d";
+
+const char *const PARTY_DETAILS = "\015\003l\002\014""00"
+ "\013""001""\011""035%s"
+ "\013""009""\011""035%s"
+ "\013""017""\011""035%s"
+ "\013""025""\011""035%s"
+ "\013""001""\011""136%s"
+ "\013""009""\011""136%s"
+ "\013""017""\011""136%s"
+ "\013""025""\011""136%s"
+ "\013""044""\011""035%s"
+ "\013""052""\011""035%s"
+ "\013""060""\011""035%s"
+ "\013""068""\011""035%s"
+ "\013""044""\011""136%s"
+ "\013""052""\011""136%s"
+ "\013""060""\011""136%s"
+ "\013""068""\011""136%s";
+const char *const PARTY_DIALOG_TEXT =
+ "%s\x2\x3""c\v106\t013Up\t048Down\t083\f37D\fdel\t118\f37R\fdem"
+ "\t153\f37C\fdreate\t188E\f37x\fdit\x1";
+
+const int FACE_CONDITION_FRAMES[17] = {
+ 2, 2, 2, 1, 1, 4, 4, 4, 3, 2, 4, 3, 3, 5, 6, 7, 0
+};
+
+const int CHAR_FACES_X[6] = { 10, 45, 81, 117, 153, 189 };
+
+const int HP_BARS_X[6] = { 13, 50, 86, 122, 158, 194 };
+
+const char *const NO_ONE_TO_ADVENTURE_WITH = "You have no one to adventure with";
+
+const char *const YOUR_ROSTER_IS_FULL = "Your Roster is full!";
+
+const byte BACKGROUND_XLAT[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF7, 0xFF, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF9, 0xFF, 0x07, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF7, 0xFF, 0x09, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF5, 0xFF, 0x0B, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xF3, 0xFF, 0x0D, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+};
+
+const char *const PLEASE_WAIT = "\014""d\003""c\011""000"
+ "\013""002Please Wait...";
+
+const char *const OOPS = "\003""c\011""000\013""002Oops...";
+
+const int8 SCREEN_POSITIONING_X[4][48] = {
+ {
+ -1, 0, 0, 0, 1, -1, 0, 0, 0, 1, -2, -1,
+ -1, 0, 0, 0, 1, 1, 2, -4, -3, -3, -2, -2,
+ -1, -1, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4,
+ -3, -2, -1, 0, 0, 1, 2, 3, -4, 4, 0, 0
+ }, {
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 1
+ }, {
+ 1, 0, 0, 0, -1, 1, 0, 0, 0, -1, 2, 1,
+ 1, 0, 0, 0, -1, -1, -2, 4, 3, 3, 2, 2,
+ 1, 1, 0, 0, 0, -1, -1, -2, -2, -3, -3, -4,
+ 3, 2, 1, 0, 0, -1, -2, -3, 4, -4, 0, 0
+ }, {
+ 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -2, -2,
+ -2, -2, -2, -2, -2, -2, -2, -3, -3, -3, -3, -3,
+ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,
+ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, 0, -1
+ }
+};
+
+const int8 SCREEN_POSITIONING_Y[4][48] = {
+ {
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 1
+ }, {
+ 1, 0, 0, 0, -1, 1, 0, 0, 0, -1, 2, 1,
+ 1, 0, 0, 0, -1, -1, -2, 4, 3, 3, 2, 2,
+ 1, 1, 0, 0, 0, -1, -1, -2, -2, -3, -3, -4,
+ 3, 2, 1, 0, 0, -1, -2, -3, 4, -4, 0, 0
+ }, {
+ 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -2, -2,
+ -2, -2, -2, -2, -2, -2, -2, -3, -3, -3, -3, -3,
+ -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,
+ -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, 0, -1
+ }, {
+ -1, 0, 0, 0, 1, -1, 0, 0, 0, 1, -2, -1,
+ -1, 0, 0, 0, 1, 1, 2, -4, -3, -3, -2, -2,
+ -1, -1, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4,
+ -3, -2, -1, 0, 0, 1, 2, 3, -4, 4, 0, 0
+ }
+};
+
+const int MONSTER_GRID_BITMASK[12] = {
+ 0xC, 8, 4, 0, 0xF, 0xF000, 0xF00, 0xF0, 0xF00, 0xF0, 0x0F, 0xF000
+};
+
+const int INDOOR_OBJECT_X[2][12] = {
+ { 5, -7, -112, 98, -8, -65, 49, -9, -34, 16, -58, 40 },
+ { -35, -35, -142, 68, -35, -95, 19, -35, -62, -14, -98, 16 }
+};
+
+const int MAP_OBJECT_Y[2][12] = {
+ { 2, 25, 25, 25, 50, 50, 50, 58, 58, 58, 58, 58 },
+ { -65, -6, -6, -6, 36, 36, 36, 54, 54, 54, 54, 54 }
+};
+
+const int INDOOR_MONSTERS_Y[4] = { 2, 34, 53, 59 };
+
+const int OUTDOOR_OBJECT_X[2][12] = {
+ { -5, -7, -112, 98, -8, -77, 61, -9, -43, 25, -74, 56 },
+ { -35, -35, -142, 68, -35, -95, 19, -35, -62, -24, -98, 16 }
+};
+
+const int OUTDOOR_MONSTER_INDEXES[26] = {
+ 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 69, 70,
+ 71, 72, 73, 74, 75, 90, 91, 92, 93, 94, 112, 115, 118
+};
+
+const int OUTDOOR_MONSTERS_Y[26] = {
+ 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 53, 53,
+ 53, 53, 53, 53, 53, 34, 34, 34, 34, 34, 2, 2, 2
+};
+
+const int DIRECTION_ANIM_POSITIONS[4][4] = {
+ { 0, 1, 2, 3 }, { 3, 0, 1, 2 }, { 2, 3, 0, 1 }, { 1, 2, 3, 0 }
+};
+
+const byte WALL_SHIFTS[4][48] = {
+ {
+ 12, 0, 12, 8, 12, 12, 0, 12, 8, 12, 12, 0,
+ 12, 0, 12, 8, 12, 8, 12, 12, 0, 12, 0, 12,
+ 0, 12, 0, 12, 8, 12, 8, 12, 8, 12, 8, 12,
+ 0, 0, 0, 0, 8, 8, 8, 8, 0, 0, 4, 4
+ }, {
+ 8, 12, 8, 4, 8, 8, 12, 8, 4, 8, 8, 12,
+ 8, 12, 8, 4, 8, 4, 8, 8, 12, 8, 12, 8,
+ 12, 8, 12, 8, 4, 8, 4, 8, 4, 8, 4, 8,
+ 12, 12, 12, 12, 4, 4, 4, 4, 0, 0, 0, 0
+ }, {
+ 4, 8, 4, 0, 4, 4, 8, 4, 0, 4, 4, 8,
+ 4, 8, 4, 0, 4, 0, 4, 4, 8, 4, 8, 4,
+ 8, 4, 8, 4, 0, 4, 0, 4, 0, 4, 0, 4,
+ 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 12, 12
+ }, {
+ 0, 4, 0, 12, 0, 0, 4, 0, 12, 0, 0, 4,
+ 0, 4, 0, 12, 0, 12, 0, 0, 4, 0, 4, 0,
+ 4, 0, 4, 0, 12, 0, 12, 0, 12, 0, 12, 0,
+ 4, 4, 4, 4, 12, 12, 12, 12, 0, 0, 8, 8
+ }
+};
+
+const int DRAW_NUMBERS[25] = {
+ 36, 37, 38, 43, 42, 41,
+ 39, 20, 22, 24, 33, 31,
+ 29, 26, 10, 11, 18, 16,
+ 13, 5, 9, 6, 0, 4, 1
+};
+
+const int DRAW_FRAMES[25][2] = {
+ { 18, 24 }, { 19, 23 }, { 20, 22 }, { 24, 18 }, { 23, 19 }, { 22, 20 },
+ { 21, 21 }, { 11, 17 }, { 12, 16 }, { 13, 15 }, { 17, 11 }, { 16, 12 },
+ { 15, 13 }, { 14, 14 }, { 6, 10 }, { 7, 9 }, { 10, 6 }, { 9, 7 },
+ { 8, 8 }, { 3, 5 }, { 5, 3 }, { 4, 4 }, { 0, 2 }, { 2, 0 },
+ { 1, 1 }
+};
+
+const int COMBAT_FLOAT_X[8] = { -2, -1, 0, 1, 2, 1, 0, -1 };
+
+const int COMBAT_FLOAT_Y[8] = { -2, 0, 2, 0, -1, 0, 2, 0 };
+
+const int MONSTER_EFFECT_FLAGS[15][8] = {
+ { 0x104, 0x105, 0x106, 0x107, 0x108, 0x109, 0x10A, 0x10B },
+ { 0x10C, 0x10D, 0x10E, 0x10F, 0x0, 0x0, 0x0, 0x0 },
+ { 0x110, 0x111, 0x112, 0x113, 0x0, 0x0, 0x0, 0x0 },
+ { 0x114, 0x115, 0x116, 0x117, 0x0, 0x0, 0x0, 0x0 },
+ { 0x200, 0x201, 0x202, 0x203, 0x0, 0x0, 0x0, 0x0 },
+ { 0x300, 0x301, 0x302, 0x303, 0x400, 0x401, 0x402, 0x403 },
+ { 0x500, 0x501, 0x502, 0x503, 0x0, 0x0, 0x0, 0x0 },
+ { 0x600, 0x601, 0x602, 0x603, 0x0, 0x0, 0x0, 0x0 },
+ { 0x604, 0x605, 0x606, 0x607, 0x608, 0x609, 0x60A, 0x60B },
+ { 0x60C, 0x60D, 0x60E, 0x60F, 0x0, 0x0, 0x0, 0x0 },
+ { 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100, 0x100 },
+ { 0x101, 0x101, 0x101, 0x101, 0x101, 0x101, 0x101, 0x101 },
+ { 0x102, 0x102, 0x102, 0x102, 0x102, 0x102, 0x102, 0x102 },
+ { 0x103, 0x103, 0x103, 0x103, 0x103, 0x103, 0x103, 0x103 },
+ { 0x108, 0x108, 0x108, 0x108, 0x108, 0x108, 0x108, 0x108 }
+};
+
+const uint SPELLS_ALLOWED[3][40] = {
+ {
+ 0, 1, 2, 3, 5, 6, 7, 8, 9, 10,
+ 12, 14, 16, 23, 26, 27, 28, 30, 31, 32,
+ 33, 42, 46, 48, 49, 50, 52, 55, 56, 58,
+ 59, 62, 64, 65, 67, 68, 71, 73, 74, 76
+ }, {
+ 1, 4, 11, 13, 15, 17, 18, 19, 20, 21,
+ 22, 24, 25, 29, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 44, 45, 47, 51, 53, 54,
+ 57, 60, 61, 63, 66, 69, 70, 72, 75, 76
+ }, {
+ 0, 1, 2, 3, 4, 5, 7, 9, 10, 20,
+ 25, 26, 27, 28, 30, 31, 34, 38, 40, 41,
+ 42, 43, 44, 45, 49, 50, 52, 53, 55, 59,
+ 60, 61, 62, 67, 68, 72, 73, 74, 75, 76
+ }
+};
+
+const int BASE_HP_BY_CLASS[10] = { 10, 8, 7, 5, 4, 8, 7, 12, 6, 9 };
+
+const int AGE_RANGES[10] = { 1, 6, 11, 18, 36, 51, 76, 101, 201, 0xffff };
+
+const int AGE_RANGES_ADJUST[2][10] = {
+ { -250, -50, -20, -10, 0, -2, -5, -10, -20, -50 },
+ { -250, -50, -20, -10, 0, 2, 5, 10, 20, 50 }
+};
+
+const uint STAT_VALUES[24] = {
+ 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 25, 30, 35, 40,
+ 50, 75, 100, 125, 150, 175, 200, 225, 250,
+};
+
+const int STAT_BONUSES[24] = {
+ -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20
+};
+
+const int ELEMENTAL_CATEGORIES[6] = { 8, 15, 20, 25, 33, 36 };
+
+const int ATTRIBUTE_CATEGORIES[10] = {
+ 9, 17, 25, 33, 39, 45, 50, 56, 61, 72 };
+
+const int ATTRIBUTE_BONUSES[72] = {
+ 2, 3, 5, 8, 12, 17, 23, 30, 38, 47, // Might bonus
+ 2, 3, 5, 8, 12, 17, 23, 30, // INT bonus
+ 2, 3, 5, 8, 12, 17, 23, 30, // PER bonus
+ 2, 3, 5, 8, 12, 17, 23, 30, // SPD bonus
+ 3, 5, 10, 15, 20, 30, // ACC bonus
+ 5, 10, 15, 20, 25, 30, // LUC bonus
+ 4, 6, 10, 20, 50, // HP bonus
+ 4, 8, 12, 16, 20, 25, // SP bonus
+ 2, 4, 6, 10, 16, // AC bonus
+ 4, 6, 8, 10, 12, 14, 16, 18, 20, 25 // Thievery bonus
+};
+
+const int ELEMENTAL_RESISTENCES[37] = {
+ 0, 5, 7, 9, 12, 15, 20, 25, 30, 5, 7, 9, 12, 15, 20, 25,
+ 5, 10, 15, 20, 25, 10, 15, 20, 25, 40, 5, 7, 9, 11, 13, 15, 20, 25,
+ 5, 10, 20
+};
+
+const int ELEMENTAL_DAMAGE[37] = {
+ 0, 2, 3, 4, 5, 10, 15, 20, 30, 2, 3, 4, 5, 10, 15, 20, 2, 4, 5, 10, 20,
+ 2, 4, 8, 16, 32, 2, 3, 4, 5, 10, 15, 20, 30, 5, 10, 25
+};
+
+const int WEAPON_DAMAGE_BASE[35] = {
+ 0, 3, 2, 3, 2, 2, 4, 1, 2, 4, 2, 3,
+ 2, 2, 1, 1, 1, 1, 4, 4, 3, 2, 4, 2,
+ 2, 2, 5, 3, 3, 3, 3, 5, 4, 2, 6
+};
+
+const int WEAPON_DAMAGE_MULTIPLIER[35] = {
+ 0, 3, 3, 4, 5, 4, 2, 3, 3, 3, 3, 3,
+ 2, 4, 10, 6, 8, 9, 4, 3, 6, 8, 5, 6,
+ 4, 5, 3, 5, 6, 7, 2, 2, 2, 2, 4
+};
+
+const int METAL_DAMAGE[22] = {
+ -3, -6, -4, -2, 2, 4, 6, 8, 10, 0, 1,
+ 1, 2, 2, 3, 4, 5, 12, 15, 20, 30, 50
+};
+
+const int METAL_DAMAGE_PERCENT[22] = {
+ 253, 252, 3, 2, 1, 2, 3, 4, 6, 0, 1,
+ 1, 2, 2, 3, 4, 5, 6, 7, 8, 9, 10
+};
+
+const int METAL_LAC[9] = { -3, 0, -2, -1, 1, 2, 4, 6, 8 };
+
+const int ARMOR_STRENGTHS[14] = { 0, 2, 4, 5, 6, 7, 8, 10, 4, 2, 1, 1, 1, 1 };
+
+const int MAKE_ITEM_ARR1[6] = { 0, 8, 15, 20, 25, 33 };
+
+const int MAKE_ITEM_ARR2[6][7][2] = {
+ { { 0, 0 }, { 1, 3 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 5, 8 }, { 8, 8 } },
+ { { 0, 0 }, { 1, 3 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 6, 7 }, { 7, 7 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 5 }, { 4, 5 }, { 5, 5 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 4 }, { 4, 5 }, { 5, 5 } },
+ { { 0, 0 }, { 1, 3 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 5, 8 }, { 8, 8 } },
+ { { 0, 0 }, { 1, 1 }, { 1, 1 }, { 1, 2 }, { 2, 2 }, { 2, 3 }, { 3, 3 } }
+};
+
+const int MAKE_ITEM_ARR3[10][7][2] = {
+ { { 0, 0 }, { 1, 4 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 6, 10 }, { 10, 10 } },
+ { { 0, 0 }, { 1, 3 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 5, 8 }, { 8, 8 } },
+ { { 0, 0 }, { 1, 3 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 5, 8 }, { 8, 8 } },
+ { { 0, 0 }, { 1, 3 }, { 2, 5 }, { 3, 6 }, { 4, 7 }, { 5, 8 }, { 8, 8 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 5 }, { 4, 6 }, { 6, 6 } },
+ { { 0, 0 }, { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 5, 6 }, { 6, 6 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 4 }, { 4, 5 }, { 5, 5 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 5 }, { 4, 6 }, { 6, 6 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 4 }, { 4, 5 }, { 5, 5 } },
+ { { 0, 0 }, { 1, 2 }, { 1, 4 }, { 3, 6 }, { 5, 8 }, { 7, 10 }, { 10, 10 } }
+};
+
+const int MAKE_ITEM_ARR4[2][7][2] = {
+ { { 0, 0 }, { 1, 4 }, { 3, 7 }, { 4, 8 }, { 5, 9 }, { 8, 9 }, { 9, 9 } },
+ { { 0, 0 }, { 1, 4 }, { 2, 6 }, { 4, 7 }, { 6, 10 }, { 9, 13 }, { 13, 13 } }
+};
+
+
+const int MAKE_ITEM_ARR5[8][2] = {
+ { 0, 0 }, { 1, 15 }, { 16, 30 }, { 31, 40 }, { 41, 50 },
+ { 51, 60 }, { 61, 73 }, { 61, 73 }
+};
+
+const int OUTDOOR_DRAWSTRCT_INDEXES[44] = {
+ 37, 38, 39, 40, 41, 44, 42, 43, 47, 45, 46,
+ 48, 49, 52, 50, 51, 66, 67, 68, 69, 70, 71,
+ 72, 75, 73, 74, 87, 88, 89, 90, 91, 94, 92,
+ 93, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120
+};
+
+const int TOWN_MAXES[2][11] = {
+ { 23, 13, 32, 16, 26, 16, 16, 16, 16, 16, 16 },
+ { 26, 19, 48, 27, 26, 37, 16, 16, 16, 16, 16 }
+};
+
+const char *const TOWN_ACTION_MUSIC[14] = {
+ "bank.m", "smith.m", "guild.m", "tavern.m", "temple.m",
+ "grounds.m", "endgame.m", "bank.m", "sf09.m", "guild.m",
+ "tavern.m", "temple.m", "smith.m", "endgame.m"
+};
+
+const char *const TOWN_ACTION_SHAPES[4] = {
+ "bankr", "blck", "gild", "tvrn"
+};
+
+const int TOWN_ACTION_FILES[2][7] = {
+ { 3, 2, 4, 2, 4, 2, 1 }, { 5, 3, 7, 5, 4, 6, 1 }
+};
+
+const char *const BANK_TEXT = "\x0D\x02\x03""c\x0B""122\x09""013"
+ "\x0C""37D\x0C""dep\x09""040\x0C""37W\x0C""dith\x09""067ESC"
+ "\x01\x09""000\x0B""000Bank of Xeen\x0B""015\n"
+ "Bank\x03l\n"
+ "Gold\x03r\x09""000%s\x03l\n"
+ "Gems\x03r\x09""000%s\x03""c\n"
+ "\n"
+ "Party\x03l\n"
+ "Gold\x03r\x09""000%s\x03l\n"
+ "Gems\x03r\x09""000%s";
+
+const char *const BLACKSMITH_TEXT = "\x01\x0D\x03""c\x0B""000\x09""000"
+ "Store Options for\x09""039\x0B""027%s\x03""l\x0B""046\n"
+ "\x09""011\x0C""37B\x0C""drowse\n"
+ "\x09""000\x0B""090Gold\x03r\x09""000%s"
+ "\x02\x03""c\x0B""122\x09""040ESC\x01";
+
+const char *const GUILD_NOT_MEMBER_TEXT =
+ "\n\nYou have to be a member to shop here.";
+
+const char *const GUILD_TEXT = "\x03""c\x0B""027\x09""039%s"
+ "\x03l\x0B""046\n"
+ "\x09""012\x0C""37B\x0C""duy Spells\n"
+ "\x09""012\x0C""37S\x0C""dpell Info";
+
+const char *const TAVERN_TEXT =
+ "\x0D\x03""c\x0B""000\x09""000Tavern Options for\x09""039"
+ "\x0B""027%s%s\x03l\x09""000"
+ "\x0B""090Gold\x03r\x09""000%s\x02\x03""c\x0B""122"
+ "\x09""021\x0C""37S\x0C""dign in\x09""060ESC\x01";
+
+const char *const FOOD_AND_DRINK =
+ "\x03l\x09""017\x0B""046\x0C""37D\x0C""drink\n"
+ "\x09""017\x0C""37F\x0C""dood\n"
+ "\x09""017\x0C""37T\x0C""dip\n"
+ "\x09""017\x0C""37R\x0C""dumors";
+
+const char *const GOOD_STUFF = "\n"
+ "\n"
+ "Good Stuff\n"
+ "\n"
+ "Hit a key!";
+
+const char *const HAVE_A_DRINK = "\n\nHave a Drink\n\nHit a key!";
+
+const char *const YOURE_DRUNK = "\n\nYou're Drunk\n\nHit a key!";
+
+const int TAVERN_EXIT_LIST[2][6][5][2] = {
+ {
+ { { 21, 17 }, { 0, 0 }, { 20, 3 }, { 0, 0 }, { 0, 0 } },
+ { { 13, 4 }, { 0, 0 }, { 19, 9 }, { 0, 0 }, { 0, 0 } },
+ { { 20, 10 }, { 12, 8 }, { 5, 26 }, { 3, 4 }, { 7, 5 } },
+ { { 18, 4 }, { 0, 0 }, { 19, 16 }, { 0, 0 }, { 11, 12 } },
+ { { 15, 21 }, { 0, 0 }, { 13, 21 }, { 0, 0 }, { 0, 0 } },
+ { { 10, 8 }, { 0, 0 }, { 15, 12 }, { 0, 0 }, { 0, 0 } },
+ }, {
+ { { 21, 17 }, { 0, 0 }, { 20, 3 }, { 0, 0 }, { 0, 0 } },
+ { { 13, 4 }, { 0, 0 }, { 19, 9 }, { 0, 0 }, { 0, 0 } },
+ { { 20, 10 }, { 12, 8 }, { 5, 26 }, { 3, 4 }, { 7, 5 } },
+ { { 17, 24 }, { 14, 13 }, { 0, 0 }, { 0, 0 }, { 9, 4 } },
+ { { 15, 21 }, { 0, 0 }, { 13, 21 }, { 0, 0 }, { 0, 0 } },
+ { { 10, 8 }, { 0, 0 }, { 15, 12 }, { 0, 0 }, { 0, 0 } }
+ }
+};
+
+const char *const TEMPLE_TEXT =
+ "\x0D\x03""c\x0B""000\x09""000Temple Options for"
+ "\x09""039\x0B""027%s\x03l\x09""000\x0B""046"
+ "\x0C""37H\x0C""deal\x03r\x09""000%lu\x03l\n"
+ "\x0C""37D\x0C""donation\x03r\x09""000%lu\x03l\n"
+ "\x0C""37U\x0C""dnCurse\x03r\x09""000%s"
+ "\x03l\x09""000\x0B""090Gold\x03r\x09""000%s"
+ "\x02\x03""c\x0B""122\x09""040ESC\x01";
+
+const char *const EXPERIENCE_FOR_LEVEL =
+ "%s needs %lu experience for level %u.";
+
+const char *const LEARNED_ALL = "%s has learned all we can teach!";
+
+const char *const ELIGIBLE_FOR_LEVEL = "%s is eligible for level %d.";
+
+const char *const TRAINING_TEXT =
+ "\x0D\x03""cTraining Options\n"
+ "\n"
+ "%s\x03l\x0B""090\x09""000Gold\x03r\x09"
+ "000%s\x02\x03""c\x0B""122\x09""021"
+ "\x0C""37T\x0C""drain\x09""060ESC\x01";
+
+const char *const GOLD_GEMS =
+ "\x03""c\x0B""000\x09""000%s\x03l\n"
+ "\n"
+ "Gold\x03r\x09""000%s\x03l\n"
+ "Gems\x03r\x09""000%s\x02\x03""c\x0B""096\x09""013G"
+ "\x0C""37o\x0C""dld\x09""040G\x0C\x03""7e"
+ "\x0C""dms\x09""067ESC\x01";
+
+const char *const GOLD_GEMS_2 =
+ "\x09""000\x0B""000\x03""c%s\x03l\n"
+ "\n"
+ "\x04""077Gold\x03r\x09""000%s\x03l\n"
+ "\x04""077Gems\x03r\x09""000%s\x03l\x09""000\x0B""051\x04""077\n"
+ "\x04""077";
+
+const char *const DEPOSIT_WITHDRAWL[2] = { "Deposit", "Withdrawl" };
+
+const char *const NOT_ENOUGH_X_IN_THE_Y =
+ "\x03""c\x0B""012Not enough %s in the %s!\x03l";
+
+const char *const NO_X_IN_THE_Y = "\x03""c\x0B""012No %s in the %s!\x03l";
+
+const char *const STAT_NAMES[16] = {
+ "Might", "Intellect", "Personality", "Endurance", "Speed",
+ "Accuracy", "Luck", "Age", "Level", "Armor Class", "Hit Points",
+ "Spell Points", "Resistances", "Skills", "Awards", "Experience"
+};
+
+const char *const CONSUMABLE_NAMES[4] = { "Gold", "Gems", "Food", "Condition" };
+
+const char *const WHERE_NAMES[2] = { "Party", "Bank" };
+
+const char *const AMOUNT = "\x03""c\x09""000\x0B""051Amount\x03l\n";
+
+const char *const FOOD_PACKS_FULL = "\v007Your food packs are already full!";
+
+const char *const BUY_SPELLS =
+ "\x03""c\x0B""027\x09""039%s\x03l\x0B""046\n"
+ "\x09""012\x0C""37B\x0C""duy Spells\n"
+ "\x09""012\x0C""37S\x0C""dpell Info";
+
+const char *const GUILD_OPTIONS =
+ "\x0D\x0C""00\x03""c\x0B""000\x09""000Guild Options for%s"
+ "\x03l\x09""000\x0B""090Gold"
+ "\x03r\x09""000%s\x02\x03""c\x0B""122\x09""040ESC\x01";
+
+const int MISC_SPELL_INDEX[74] = {
+ NO_SPELL, MS_Light, MS_Awaken, MS_MagicArrow,
+ MS_FirstAid, MS_FlyingFist, MS_EnergyBlast, MS_Sleep,
+ MS_Revitalize, MS_CureWounds, MS_Sparks, MS_Shrapmetal,
+ MS_InsectSpray, MS_ToxicCloud, MS_ProtFromElements, MS_Pain,
+ MS_Jump, MS_BeastMaster, MS_Clairvoyance, MS_TurnUndead,
+ MS_Levitate, MS_WizardEye, MS_Bless, MS_IdentifyMonster,
+ MS_LightningBolt, MS_HolyBonus, MS_PowerCure, MS_NaturesCure,
+ MS_LloydsBeacon, MS_PowerShield, MS_Heroism, MS_Hynotize,
+ MS_WalkOnWater, MS_FrostBite, MS_DetectMonster, MS_Fireball,
+ MS_ColdRay, MS_CurePoison, MS_AcidSpray, MS_TimeDistortion,
+ MS_DragonSleep, MS_CureDisease, MS_Teleport, MS_FingerOfDeath,
+ MS_CureParalysis, MS_GolemStopper, MS_PoisonVolley, MS_DeadlySwarm,
+ MS_SuperShelter, MS_DayOfProtection, MS_DayOfSorcery, MS_CreateFood,
+ MS_FieryFlail, MS_RechargeItem, MS_FantasticFreeze, MS_TownPortal,
+ MS_StoneToFlesh, MS_RaiseDead, MS_Etheralize, MS_DancingSword,
+ MS_MoonRay, MS_MassDistortion, MS_PrismaticLight, MS_EnchantItem,
+ MS_Incinerate, MS_HolyWord, MS_Resurrection, MS_ElementalStorm,
+ MS_MegaVolts, MS_Inferno, MS_SunRay, MS_Implosion,
+ MS_StarBurst, MS_DivineIntervention
+};
+
+const int SPELL_COSTS[77] = {
+ 8, 1, 5, -2, 5, -2, 20, 10, 12, 8, 3,
+ - 3, 75, 40, 12, 6, 200, 10, 100, 30, -1, 30,
+ 15, 25, 10, -2, 1, 2, 7, 20, -2, -2, 100,
+ 15, 5, 100, 35, 75, 5, 20, 4, 5, 1, -2,
+ 6, 2, 75, 40, 60, 6, 4, 25, -2, -2, 60,
+ - 1, 50, 15, 125, 2, -1, 3, -1, 200, 35, 150,
+ 15, 5, 4, 10, 8, 30, 4, 5, 7, 5, 0
+};
+
+const int DARK_SPELL_RANGES[12][2] = {
+ { 0, 20 }, { 16, 35 }, { 27, 37 }, { 29, 39 },
+ { 0, 17 }, { 14, 34 }, { 26, 37 }, { 29, 39 },
+ { 0, 20 }, { 16, 35 }, { 27, 37 }, { 29, 39 }
+};
+
+const int CLOUDS_SPELL_OFFSETS[5][20] = {
+ {
+ 1, 10, 20, 26, 27, 38, 40, 42, 45, 50,
+ 55, 59, 60, 61, 62, 68, 72, 75, 77, 77
+ }, {
+ 3, 4, 5, 14, 15, 25, 30, 31, 34, 41,
+ 49, 51, 53, 67, 73, 75, -1, -1, -1, -1
+ }, {
+ 4, 8, 9, 12, 13, 22, 23, 24, 28, 34,
+ 41, 44, 52, 70, 73, 74, -1, -1, -1, -1
+ }, {
+ 6, 7, 9, 11, 12, 13, 17, 21, 22, 24,
+ 29, 36, 56, 58, 64, 71, -1, -1, -1, -1
+ }, {
+ 6, 7, 9, 11, 12, 13, 18, 21, 29, 32,
+ 36, 37, 46, 51, 56, 58, 69, -1, -1, -1
+ }
+};
+
+const uint DARK_SPELL_OFFSETS[3][39] = {
+ {
+ 42, 1, 26, 59, 27, 10, 50, 68, 55, 62, 67, 73, 2,
+ 5, 3, 31, 30, 52, 49, 28, 74, 0, 9, 7, 14, 8,
+ 33, 6, 23, 71, 64, 56, 48, 46, 12, 32, 58, 65, 16
+ }, {
+ 42, 1, 45, 61, 72, 40, 20, 60, 38, 41, 75, 34, 4,
+ 43, 25, 53, 44, 15, 70, 17, 24, 69, 22, 66, 57, 11,
+ 29, 39, 51, 21, 19, 36, 47, 13, 54, 37, 18, 35, 63
+ }, {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+ 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38
+ }
+};
+
+const int SPELL_GEM_COST[77] = {
+ 0, 0, 2, 1, 2, 4, 5, 0, 0, 0, 0, 10, 10, 10, 0, 0, 20, 4, 10, 20, 1, 10,
+ 5, 5, 4, 2, 0, 0, 0, 10, 3, 1, 20, 4, 0, 20, 10, 10, 1, 10, 0, 0, 0, 2,
+ 2, 0, 10, 10, 10, 0, 0, 10, 3, 2, 10, 1, 10, 10, 20, 0, 0, 1, 1, 20, 5, 20,
+ 5, 0, 0, 0, 0, 5, 1, 2, 0, 2, 0
+};
+
+const char *const NOT_A_SPELL_CASTER = "Not a spell caster...";
+
+const char *const SPELLS_FOR = "\xD\xC""d%s\x2\x3""c\x9""000\xB""002Spells for %s";
+
+const char *const SPELL_LINES_0_TO_9 =
+ "\x2\x3l\xB""015\x9""0011\n2\n3\n4\n5\n6\n7\n8\n9\n0";
+
+const char *const SPELLS_DIALOG_SPELLS = "\x3l\xB""015"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l\n"
+ "\x9""010\xC""%2u%s\xC""d\x3l"
+ "\x9""004\xB""110%s - %lu\x1";
+
+const char *const SPELL_PTS = "Spell Pts";
+
+const char *const GOLD = "Gold";
+
+const char *const SPELLS_PRESS_A_KEY =
+ "\x3""c\xC""09%s\xC""d\x3l\n"
+ "\n"
+ "%s\x3""c\x9""000\xB""100Press a Key!";
+
+const char *const SPELLS_PURCHASE =
+ "\x3l\xB""000\x9""000\xC""d%s Do you wish to purchase "
+ "\xC""09%s\xC""d for %u?";
+
+const char *const MAP_TEXT =
+ "\x3""c\xB""000\x9""000%s\x3l\xB""139"
+ "\x9""000X = %d\x3r\x9""000Y = %d\x3""c\x9""000%s";
+
+const char *const LIGHT_COUNT_TEXT = "\x3l\n\n\t024Light\x3r\t124%d";
+
+const char *const FIRE_RESISTENCE_TEXT = "%c%sFire%s%u";
+
+const char *const ELECRICITY_RESISTENCE_TEXT = "%c%sElectricity%s%u";
+
+const char *const COLD_RESISTENCE_TEXT = "c%sCold%s%u";
+
+const char *const POISON_RESISTENCE_TEXT = "%c%sPoison/Acid%s%u";
+
+const char *const CLAIRVOYANCE_TEXT = "%c%sClairvoyance%s";
+
+const char *const LEVITATE_TEXT = "%c%sLevitate%s";
+
+const char *const WALK_ON_WATER_TEXT = "%c%sWalk on Water";
+
+const char *const GAME_INFORMATION =
+ "\xD\x3""c\x9""000\xB""001\xC""37%s of Xeen\xC""d\n"
+ "Game Information\n"
+ "\n"
+ "Today is \xC""37%ssday\xC""d\n"
+ "\n"
+ "\x9""032Time\x9""072Day\x9""112Year\n"
+ "\x9""032\xC""37%d:%02d%c\x9""072%u\x9""112%u\xC""d%s";
+
+const char *const WORLD_GAME_TEXT = "World";
+const char *const DARKSIDE_GAME_TEXT = "Darkside";
+const char *const CLOUDS_GAME_TEXT = "Clouds";
+const char *const SWORDS_GAME_TEXT = "Swords";
+
+const char *const WEEK_DAY_STRINGS[10] = {
+ "Ten", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"
+};
+
+const char *const CHARACTER_DETAILS =
+ "\x3l\xB""041\x9""196%s\x9""000\xB""002%s : %s %s %s"
+ "\x3r\x9""053\xB""028\xC%02u%u\xC""d\x9""103\xC""%02u%u\xC""d"
+ "\x3l\x9""131\xC""%02u%d\xC""d\x9""196\xC""15%lu\xC""d\x3r"
+ "\x9""053\xB""051\xC""%02u%u\xC""d\x9""103\xC""%02u%u\xC""d"
+ "\x3l\x9""131\xC""%02u%u\xC""d\x9""196\xC""15%lu\xC""d"
+ "\x3r\x9""053\xB""074\xC""%02u%u\xC""d\x9""103\xC""%02u%u\xC""d"
+ "\x3l\x9""131\xC""15%u\xC""d\x9""196\xC""15%lu\xC""d"
+ "\x3r\x9""053\xB""097\xC""%02u%u\xC""d\x9""103\xC""%02u%u\xC""d"
+ "\x3l\x9""131\xC""15%u\xC""d\x9""196\xC""15%u day%c\xC""d"
+ "\x3r\x9""053\xB""120\xC""%02u%u\xC""d\x9""103\xC""%02u%u\xC""d"
+ "\x3l\x9""131\xC""15%u\xC""d\x9""196\xC""%02u%s\xC""d"
+ "\x9""230%s%s%s%s\xC""d";
+
+const char *const PARTY_GOLD = "Party Gold";
+
+const char *const PLUS_14 = "14+";
+
+const char *const CHARACTER_TEMPLATE =
+ "\x1\xC""00\xD\x3l\x9""029\xB""018Mgt\x9""080Acy\x9""131H.P.\x9""196Experience"
+ "\x9""029\xB""041Int\x9""080Lck\x9""131S.P.\x9""029\xB""064Per\x9""080Age"
+ "\x9""131Resis\x9""196Party Gems\x9""029\xB""087End\x9""080Lvl\x9""131Skills"
+ "\x9""196Party Food\x9""029\xB""110Spd\x9""080AC\x9""131Awrds\x9""196Condition\x3""c"
+ "\x9""290\xB""025\xC""37I\xC""dtem\x9""290\xB""057\xC""37Q"
+ "\xC""duick\x9""290\xB""089\xC""37E\xC""dxch\x9""290\xB""121Exit\x3l%s";
+
+const char *const EXCHANGING_IN_COMBAT = "\x3""c\xB""007\x9""000Exchanging in combat is not allowed!";
+
+const char *const CURRENT_MAXIMUM_RATING_TEXT = "\x2\x3""c%s\n"
+ "Current / Maximum\n"
+ "\x3r\x9""054%lu\x3l\x9""058/ %lu\n"
+ "\x3""cRating: %s";
+
+const char *const CURRENT_MAXIMUM_TEXT = "\x2\x3""c%s\n"
+ "Current / Maximum\n"
+ "\x3r\x9""054%u\x3l\x9""058/ %u";
+
+const char *const RATING_TEXT[24] = {
+ "Nonexistant", "Very Poor", "Poor", "Very Low", "Low", "Averarage", "Good",
+ "Very Good", "High", "Very High", "Great", "Super", "Amazing", "Incredible",
+ "Gigantic", "Fantastic", "Astoundig", "Astonishing", "Monumental", "Tremendous",
+ "Collosal", "Awesome", "AweInspiring", "aUltimate"
+};
+
+const char *const AGE_TEXT = "\x2\x3""c%s\n"
+ "Current / Natural\n"
+ "\x3r\x9""057%u\x3l\x9""061/ %u\n"
+ "\x3""cBorn: %u / %u\x1";
+
+const char *const LEVEL_TEXT =
+ "\x2\x3""c%s\n"
+ "Current / Maximum\n"
+ "\x3r\x9""054%u\x3l\x9""058/ %u\n"
+ "\x3""c%u Attack%s/Round\x1";
+
+const char *const RESISTENCES_TEXT =
+ "\x2\x3""c%s\x3l\n"
+ "\x9""020Fire\x9""100%u\n"
+ "\x9""020Cold\x9""100%u\n"
+ "\x9""020Electricity\x9""100%u\n"
+ "\x9""020Poison\x9""100%u\n"
+ "\x9""020Energy\x9""100%u\n"
+ "\x9""020Magic\x9""100%u";
+
+const char *const NONE = "\n\x9""020";
+
+const char *const EXPERIENCE_TEXT = "\x2\x3""c%s\x3l\n"
+ "\x9""010Current:\x9""070%lu\n"
+ "\x9""010Next Level:\x9""070%s\x1";
+
+const char *const ELIGIBLE = "\xC""12Eligible\xC""d";
+
+const char *const IN_PARTY_IN_BANK =
+ "\x2\x3""cParty %s\n"
+ "%lu on hand\n"
+ "%lu in bank\x1\x3l";
+
+const char *const FOOD_TEXT =
+ "\x2\x3""cParty %s\n"
+ "%u on hand\n"
+ "Enough for %u day%s\x3l";
+
+const char *const EXCHANGE_WITH_WHOM = "\t010\v005Exchange with whom?";
+
+const char *const QUICK_REF_LINE =
+ "\xB%3d\x9""007%u)\x9""027%s\x9""110%c%c%c\x3r\x9""160\xC%02u%u\xC""d"
+ "\x3l\x9""170\xC%02u%d\xC""d\x9""208\xC%02u%u\xC""d\x9""247\xC"
+ "%02u%u\xC""d\x9""270\xC%02u%c%c%c%c\xC""d";
+
+const char *const QUICK_REFERENCE =
+ "\xD\x3""cQuick Reference Chart\xB""012\x3l"
+ "\x9""007#\x9""027Name\x9""110Cls\x9""140Lvl\x9""176H.P."
+ "\x9""212S.P.\x9""241A.C.\x9""270Cond"
+ "%s%s%s%s%s%s%s%s"
+ "\xB""110\x9""064\x3""cGold\x9""144Gems\x9""224Food\xB""119"
+ "\x9""064\xC""15%lu\x9""144%lu\x9""224%u day%s\xC""d";
+
+const uint BLACKSMITH_MAP_IDS[2][4] = { { 28, 30, 73, 49 }, { 29, 31, 37, 43 } };
+
+const char *const ITEMS_DIALOG_TEXT1 =
+ "\r\x2\x3""c\v021\t017\f37W\fdeap\t051\f37A\fdrmor\t085A"
+ "\f37c\fdces\t119\f37M\fdisc\t153%s\t187%s\t221%s"
+ "\t255%s\t289Exit";
+const char *const ITEMS_DIALOG_TEXT2 =
+ "\r\x2\x3""c\v021\t017\f37W\fdeap\t051\f37A\fdrmor\t085A"
+ "\f37c\fdces\t119\f37M\fdisc\t153\f37%s\t289Exit";
+const char *const ITEMS_DIALOG_LINE1 = "\x3r\f%02u\f023%2d)\x3l\t028%s\n";
+const char *const ITEMS_DIALOG_LINE2 = "\x3r\f%02u\t023%2d)\x3l\t028%s\x3r\t000%lu\n";
+
+const char *const BTN_BUY = "\f37B\fduy";
+const char *const BTN_SELL = "\f37S\fdell";
+const char *const BTN_IDENTIFY = "\f37I\fddentify";
+const char *const BTN_FIX = "\f37F\fdix";
+const char *const BTN_USE = "\f37U\fdse";
+const char *const BTN_EQUIP = "\f37E\fdquip";
+const char *const BTN_REMOVE = "\f37R\fdem";
+const char *const BTN_DISCARD = "\f37D\fdisc";
+const char *const BTN_QUEST = "\f37Q\fduest";
+const char *const BTN_ENCHANT = "E\fdnchant";
+const char *const BTN_RECHARGE = "R\fdechrg";
+const char *const BTN_GOLD = "G\fdold";
+
+const char *const ITEM_BROKEN = "\f32broken ";
+const char *const ITEM_CURSED = "\f09cursed ";
+const char *const BONUS_NAMES[7] = {
+ "", "Dragon Slayer", "Undead Eater", "Golem Smasher",
+ "Bug Zapper", "Monster Masher", "Beast Bopper"
+};
+
+const char *const WEAPON_NAMES[35] = {
+ nullptr, "long sword ", "short sword ", "broad sword ", "scimitar ",
+ "cutlass ", "sabre ", "club ", "hand axe ", "katana ", "nunchakas ",
+ "wakazashi ", "dagger ", "mace ", "flail ", "cudgel ", "maul ", "spear ",
+ "bardiche ", "glaive ", "halberd ", "pike ", "flamberge ", "trident ",
+ "staff ", "hammer ", "naginata ", "battle axe ", "grand axe ", "great axe ",
+ "short bow ", "long bow ", "crossbow ", "sling ", "Xeen Slayer Sword"
+};
+
+const char *const ARMOR_NAMES[14] = {
+ nullptr, "Robes ", "Scale rmor ", "ring mail ", "chain mail ",
+ "splint mail ", "plate mail ", "plate armor ", "shield ",
+ "helm ", "boots ", "cloak ", "cape ", "gauntlets "
+};
+
+const char *const ACCESSORY_NAMES[11] = {
+ nullptr, "ring ", "belt ", "broach ", "medal ", "charm ", "cameo ",
+ "scarab ", "pendant ", "necklace ", "amulet "
+};
+
+const char *const MISC_NAMES[22] = {
+ nullptr, "rod ", "jewel ", "gem ", "box ", "orb ", "horn ", "coin ",
+ "wand ", "whistle ", "potion ", "scroll ", "RogueVM",
+ "bogusg", "bogus", "bogus", "bogus", "bogus",
+ "bogus", "bogus", "bogus", "bogus"
+};
+
+const char *const *ITEM_NAMES[4] = {
+ &WEAPON_NAMES[0], &ARMOR_NAMES[0], &ACCESSORY_NAMES[0], &MISC_NAMES[0]
+};
+
+const char *const ELEMENTAL_NAMES[6] = {
+ "Fire", "Elec", "Cold", "Acid/Poison", "Energy", "Magic"
+};
+
+const char *const ATTRIBUTE_NAMES[10] = {
+ "might", "Intellect", "Personality", "Speed", "accuracy", "Luck",
+ "Hit Points", "Spell Points", "Armor Class", "Thievery"
+};
+
+const char *const EFFECTIVENESS_NAMES[7] = {
+ nullptr, "Dragons", "Undead", "Golems", "Bugs", "Monsters", "Beasts"
+};
+
+const char *const QUEST_ITEM_NAMES[85] = {
+ "Deed to New Castle",
+ "Crystal Key to Witch Tower",
+ "Skeleton Key to Darzog's Tower",
+ "Enchanted Key to Tower of High Magic",
+ "Jeweled Amulet of the Northern Sphinx",
+ "Stone of a Thousand Terrors",
+ "Golem Stone of Admittance",
+ "Yak Stone of Opening",
+ "Xeen's Scepter of Temporal Distortion",
+ "Alacorn of Falista",
+ "Elixir of Restoration",
+ "Wand of Faery Magic",
+ "Princess Roxanne's Tiara",
+ "Holy Book of Elvenkind",
+ "Scarab of Imaging",
+ "Crystals of Piezoelectricity",
+ "Scroll of Insight",
+ "Phirna Root",
+ "Orothin's Bone Whistle",
+ "Barok's Magic Pendant",
+ "Ligono's Missing Skull",
+ "Last Flower of Summer",
+ "Last Raindrop of Spring",
+ "Last Snowflake of Winter",
+ "Last Leaf of Autumn",
+ "Ever Hot Lava Rock",
+ "King's Mega Credit",
+ "Excavation Permit",
+ "Cupie Doll",
+ "Might Doll",
+ "Speed Doll",
+ "Endurance Doll",
+ "Accuracy Doll",
+ "Luck Doll",
+ "Widget",
+ "Pass to Castleview",
+ "Pass to Sandcaster",
+ "Pass to Lakeside",
+ "Pass to Necropolis",
+ "Pass to Olympus",
+ "Key to Great Western Tower",
+ "Key to Great Southern Tower",
+ "Key to Great Eastern Tower",
+ "Key to Great Northern Tower",
+ "Key to Ellinger's Tower",
+ "Key to Dragon Tower",
+ "Key to Darkstone Tower",
+ "Key to Temple of Bark",
+ "Key to Dungeon of Lost Souls",
+ "Key to Ancient Pyramid",
+ "Key to Dungeon of Death",
+ "Amulet of the Southern Sphinx",
+ "Dragon Pharoah's Orb",
+ "Cube of Power",
+ "Chime of Opening",
+ "Gold ID Card",
+ "Silver ID Card",
+ "Vulture Repellant",
+ "Bridle",
+ "Enchanted Bridle",
+ "Treasure Map (Goto E1 x1, y11)",
+ "",
+ "Fake Map",
+ "Onyx Necklace",
+ "Dragon Egg",
+ "Tribble",
+ "Golden Pegasus Statuette",
+ "Golden Dragon Statuette",
+ "Golden Griffin Statuette",
+ "Chalice of Protection",
+ "Jewel of Ages",
+ "Songbird of Serenity",
+ "Sandro's Heart",
+ "Ector's Ring",
+ "Vespar's Emerald Handle",
+ "Queen Kalindra's Crown",
+ "Caleb's Magnifying Glass",
+ "Soul Box",
+ "Soul Box with Corak inside",
+ "Ruby Rock",
+ "Emerald Rock",
+ "Sapphire Rock",
+ "Diamond Rock",
+ "Monga Melon",
+ "Energy Disk"
+};
+
+const int WEAPON_BASE_COSTS[35] = {
+ 0, 50, 15, 100, 80, 40, 60, 1, 10, 150, 30, 60, 8, 50,
+ 100, 15, 30, 15, 200, 80, 250, 150, 400, 100, 40, 120,
+ 300, 100, 200, 300, 25, 100, 50, 15, 0
+};
+const int ARMOR_BASE_COSTS[25] = {
+ 0, 20, 100, 200, 400, 600, 1000, 2000, 100, 60, 40, 250, 200, 100
+};
+const int ACCESSORY_BASE_COSTS[11] = {
+ 0, 100, 100, 250, 100, 50, 300, 200, 500, 1000, 2000
+};
+const int MISC_MATERIAL_COSTS[22] = {
+ 0, 50, 1000, 500, 10, 100, 20, 10, 50, 10, 10, 100,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+};
+const int MISC_BASE_COSTS[76] = {
+ 0, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
+ 100, 100, 100, 100, 200, 200, 200, 200, 200, 200, 200, 200,
+ 200, 200, 200, 200, 200, 200, 200, 300, 300, 300, 300, 300,
+ 300, 300, 300, 300, 300, 400, 400, 400, 400, 400, 400, 400,
+ 400, 400, 400, 500, 500, 500, 500, 500, 500, 500, 500, 500,
+ 500, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
+ 600, 600, 600, 600
+};
+const int METAL_BASE_MULTIPLIERS[22] = {
+ 10, 25, 5, 75, 2, 5, 10, 20, 50, 2, 3, 5, 10, 20, 30, 40,
+ 50, 60, 70, 80, 90, 100
+};
+const int ITEM_SKILL_DIVISORS[4] = { 1, 2, 100, 10 };
+
+const int RESTRICTION_OFFSETS[4] = { 0, 35, 49, 60 };
+
+const int ITEM_RESTRICTIONS[86] = {
+ 0, 86, 86, 86, 86, 86, 86, 0, 6, 239, 239, 239, 2, 4, 4, 4, 4,
+ 6, 70, 70, 70, 70, 94, 70, 0, 4, 239, 86, 86, 86, 70, 70, 70, 70,
+ 0, 0, 0, 68, 100, 116, 125, 255, 255, 85, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+const char *const NOT_PROFICIENT =
+ "\t000\v007\x3""c%ss are not proficient with a %s!";
+
+const char *const NO_ITEMS_AVAILABLE = "\x3""c\n"
+ "\t000No items available.";
+
+const char *const CATEGORY_NAMES[4] = { "Weapons", "Armor", "Accessories", "Miscellaneous" };
+
+const char *const X_FOR_THE_Y =
+ "\x1\fd\r%s\v000\t000%s for %s the %s%s\v011\x2%s%s%s%s%s%s%s%s%s\x1\fd";
+
+const char *const X_FOR_Y =
+ "\x1\xC""d\r\x3l\v000\t000%s for %s\x3r\t000%s\x3l\v011\x2%s%s%s%s%s%s%s%s%s\x1\xC""d";
+
+const char *const X_FOR_Y_GOLD =
+ "\x1\fd\r\x3l\v000\t000%s for %s\t150Gold - %lu%s\x3l\v011"
+ "\x2%s%s%s%s%s%s%s%s%s\x1\fd";
+
+const char *const FMT_CHARGES = "\x3rr\t000Charges\x3l";
+
+const char *const AVAILABLE_GOLD_COST =
+ "\x1\fd\r\x3l\v000\t000Available %s%s\t150Gold - %lu\x3r\t000Cost"
+ "\x3l\v011\x2%s%s%s%s%s%s%s%s%s\x1\xC""d";
+
+const char *const CHARGES = "Charges";
+
+const char *const COST = "Cost";
+
+const char *const ITEM_ACTIONS[7] = {
+ "Equip", "Remove", "Use", "Discard", "Enchant", "Recharge", "Gold"
+};
+const char *const WHICH_ITEM = "\t010\v005%s which item?";
+
+const char *const WHATS_YOUR_HURRY = "\v007What's your hurry?\n"
+ "Wait till you get out of here!";
+
+const char *const USE_ITEM_IN_COMBAT =
+ "\v007To use an item in Combat, invoke the Use command on your turn!";
+
+const char *const NO_SPECIAL_ABILITIES = "\v005\x3""c%s\fdhas no special abilities!";
+
+const char *const CANT_CAST_WHILE_ENGAGED = "\x3""c\v007Can't cast %s while engaged!";
+
+const char *const EQUIPPED_ALL_YOU_CAN = "\x3""c\v007You have equipped all the %ss you can!";
+const char *const REMOVE_X_TO_EQUIP_Y = "\x3""c\v007You must remove %sto equip %s\x8!";
+const char *const RING = "ring";
+const char *const MEDAL = "medal";
+
+const char *const CANNOT_REMOVE_CURSED_ITEM = "\x3""You cannot remove a cursed item!";
+
+const char *const CANNOT_DISCARD_CURSED_ITEM = "\3x""cYou cannot discard a cursed item!";
+
+const char *const PERMANENTLY_DISCARD = "\v000\t000\x03lPermanently discard %s\fd?";
+
+const char *const BACKPACK_IS_FULL = "\v005\x3""c\fd%s's backpack is full.";
+
+const char *const CATEGORY_BACKPACK_IS_FULL[4] = {
+ "\v010\t000\x3""c%s's weapons backpack is full.",
+ "\v010\t000\x3""c%s's armor backpack is full.",
+ "\v010\t000\x3""c%s's accessories backpack is full.",
+ "\v010\t000\x3""c%s's miscellaneous backpack is full."
+};
+
+const char *const BUY_X_FOR_Y_GOLD = "\x3l\v000\t000\fdBuy %s\fd for %lu gold?";
+
+const char *const SELL_X_FOR_Y_GOLD = "\x3l\v000\t000\fdSell %s\fd for %lu gold?";
+
+const char *const NO_NEED_OF_THIS = "\v005\x3""c\fdWe have no need of this %s\f!";
+
+const char *const NOT_RECHARGABLE = "\v012\x3""c\fdNot Rechargeable. %s";
+
+const char *const NOT_ENCHANTABLE = "\v012\t000\x3""cNot Enchantable. %s";
+
+const char *const SPELL_FAILED = "Spell Failed!";
+
+const char *const ITEM_NOT_BROKEN = "\fdThat item is not broken!";
+
+const char *const FIX_IDENTIFY[2] = { "Fix", "Identify" };
+
+const char *const FIX_IDENTIFY_GOLD = "\x3l\v000\t000%s %s\fd for %lu gold?";
+
+const char *const IDENTIFY_ITEM_MSG = "\fd\v000\t000\x3""cIdentify Item\x3l\n"
+ "\n"
+ "\v012%s\fd\n"
+ "\n"
+ "%s";
+
+const char *const ITEM_DETAILS =
+ "Proficient Classes\t132:\t140%s\n"
+ "to Hit Modifier\t132:\t140%s\n"
+ "Physical Damage\t132:\t140%s\n"
+ "Elemental Damage\t132:\t140%s\n"
+ "Elemental Resistance\t132:\t140%s\n"
+ "Armor Class Bonus\t132:\t140%s\n"
+ "Attribute Bonus\t132:\t140%s\n"
+ "Special Power\t132:\t140%s";
+
+const char *const ALL = "All";
+const char *const FIELD_NONE = "None";
+const char *const DAMAGE_X_TO_Y = "%d to %d";
+const char *const ELEMENTAL_XY_DAMAGE = "%+d %s Damage";
+const char *const ATTR_XY_BONUS = "%+d %s";
+const char *const EFFECTIVE_AGAINST = "x3 vs %s";
+
+const char *const QUESTS_DIALOG_TEXT =
+ "\r\x2\x3""c\v021\t017\f37I\fdtems\t085\f37Q\fduests\t153"
+ "\f37A\fduto Notes 221\f37U\fdp\t255\f37D\fdown"
+ "\t289Exit";
+const char *const CLOUDS_OF_XEEN_LINE = "\b \b*-- \f04Clouds of Xeen\fd --";
+const char *const DARKSIDE_OF_XEEN_LINE = "\b \b*-- \f04Darkside of Xeen\fd --";
+
+const char *const NO_QUEST_ITEMS =
+ "\r\x3""c\v000 000Quest Items\x3l\x2\n"
+ "\n"
+ "\x3""cNo Quest Items";
+const char *const NO_CURRENT_QUESTS =
+ "\x3""c\v000\t000\n"
+ "\n"
+ "No Current Quests";
+const char *const NO_AUTO_NOTES = "\x3""cNo Auto Notes";
+
+const char *const QUEST_ITEMS_DATA =
+ "\r\x1\fd\x3""c\v000\t000Quest Items\x3l\x2\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s\n"
+ "\f04 * \fd%s";
+const char *const CURRENT_QUESTS_DATA =
+ "\r\x1\fd\x3""c\t000\v000Current Quests\x3l\x2\n"
+ "%s\n"
+ "\n"
+ "%s\n"
+ "\n"
+ "%s";
+const char *const AUTO_NOTES_DATA =
+ "\r\x1\fd\x3""c\t000\v000Auto Notes\x3l\x2\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l\n"
+ "%s\x3l";
+
+const char *const REST_COMPLETE =
+ "\v000\t0008 hours pass. Rest complete.\n"
+ "%s\n"
+ "%d food consumed.";
+const char *const PARTY_IS_STARVING = "\f07The Party is Starving!\fd";
+const char *const HIT_SPELL_POINTS_RESTORED = "Hit Pts and Spell Pts restored.";
+const char *const TOO_DANGEROUS_TO_REST = "Too dangerous to rest here!";
+const char *const SOME_CHARS_MAY_DIE = "Some Chars may die. Rest anyway?";
+
+const char *const CANT_DISMISS_LAST_CHAR = "You cannot dismiss your last character!";
+
+const char *const REMOVE_DELETE[2] = { "Remove", "Delete" };
+
+const char *const REMOVE_OR_DELETE_WHICH = "\x3l\t010\v005%s which character?";
+
+const char *const YOUR_PARTY_IS_FULL = "\v007Your party is full!";
+
+const char *const HAS_SLAYER_SWORD =
+ "\v000\t000This character has the Xeen Slayer Sword and cannot be deleted!";
+const char *const SURE_TO_DELETE_CHAR =
+ "Are you sure you want to delete %s the %s?";
+
+const char *const CREATE_CHAR_DETAILS =
+ "\f04\x3""c\x2\t144\v119\f37R\f04oll\t144\v149\f37C\f04reate"
+ "\t144\v179\f37ESC\f04\x3l\x1\t195\v021\f37M\f04gt"
+ "\t195\v045\f37I\f04nt\t195\v069\f37P\f04er\t195\v093\f37E\f04nd"
+ "\t195\v116\f37S\f04pd\t195\v140\f37A\f04cy\t195\v164\f37L\f04ck%s";
+
+const char *const NEW_CHAR_STATS =
+ "\f04\x3l\t022\v148Race\t055: %s\n"
+ "\t022Sex\t055: %s\n"
+ "\t022Class\t055:\n"
+ "\x3r\t215\v031%d\t215\v055%d\t215\v079%d\t215\v103%d\t215\v127%d"
+ "\t215\v151%d\t215\v175%d\x3l\t242\v020\f%2dKnight\t242\v031\f%2d"
+ "Paladin\t242\v042\f%2dArcher\t242\v053\f%2dCleric\t242\v064\f%2d"
+ "Sorcerer\t242\v075\f%2dRobber\t242\v086\f%2dNinja\t242\v097\f%2d"
+ "Barbarian\t242\v108\f%2dDruid\t242\v119\f%2dRanger\f04\x3""c"
+ "\t265\v142Skills\x3l\t223\v155%s\t223\v170%s%s";
+
+const char *const NAME_FOR_NEW_CHARACTER =
+ "\x3""cEnter a Name for this Character";
+const char *const SELECT_CLASS_BEFORE_SAVING =
+ "\v006\x3""cSelect a Class before saving.\x3l";
+const char *const EXCHANGE_ATTR_WITH = "Exchange %s with...";
+
+const int NEW_CHAR_SKILLS[10] = { 1, 5, -1, -1, 4, 0, 0, -1, 6, 11 };
+const int NEW_CHAR_SKILLS_LEN[10] = { 11, 8, 0, 0, 12, 8, 8, 0, 9, 11 };
+const int NEW_CHAR_RACE_SKILLS[10] = { 14, -1, 17, 16, -1, 0, 0, 0, 0, 0 };
+
+const int RACE_MAGIC_RESISTENCES[5] = { 7, 5, 20, 0, 0 };
+const int RACE_FIRE_RESISTENCES[5] = { 7, 0, 2, 5, 10 };
+const int RACE_ELECTRIC_RESISTENCES[5] = { 7, 0, 2, 5, 10 };
+const int RACE_COLD_RESISTENCES[5] = { 7, 0, 2, 5, 10 };
+const int RACE_ENERGY_RESISTENCES[5] = { 7, 5, 2, 5, 0 };
+const int RACE_POISON_RESISTENCES[5] = { 7, 0, 2, 20, 0 };
+const int NEW_CHARACTER_SPELLS[10][4] = {
+ { -1, -1, -1, -1 },
+ { 21, -1, -1, -1 },
+ { 22, -1, -1, -1 },
+ { 21, 1, 14, -1 },
+ { 22, 0, 25, -1 },
+ { -1, -1, -1, -1 },
+ { -1, -1, -1, -1 },
+ { -1, -1, -1, -1 },
+ { 20, 1, 11, 23 },
+ { 20, 1, -1, -1 }
+};
+
+const char *const COMBAT_DETAILS = "\r\f00\x3""c\v000\t000\x2""Combat%s%s%s\x1";
+
+const char *NOT_ENOUGH_TO_CAST = "\x3""c\v010Not enough %s to Cast %s";
+const char *SPELL_CAST_COMPONENTS[2] = { "Spell Points", "Gems" };
+
+const char *const CAST_SPELL_DETAILS =
+ "\r\x2\x3""c\v122\t013\f37C\fdast\t040\f37N\fdew"
+ "\t067ESC\x1\t000\v000\x3""cCast Spell\n"
+ "\n"
+ "%s\x3l\n"
+ "\n"
+ "Spell Ready:\x3""c\n"
+ "\n"
+ "\f09%s\fd\x2\x3l\n"
+ "\v082Cost\x3r\t000%u/%u\x3l\n"
+ "Cur SP\x3r\t000%u\x1";
+
+const char *const PARTY_FOUND =
+ "\x3""cThe Party Found:\n"
+ "\n"
+ "\x3r\t000%lu Gold\n"
+ "%lu Gems";
+
+const char *const BACKPACKS_FULL_PRESS_KEY =
+ "\v007\f12Warning! BackPacks Full!\fd\n"
+ "Press a Key";
+
+const char *const HIT_A_KEY = "\x3l\v120\t000\x4""077\x3""c\f37Hit a key\f'd";
+
+const char *const GIVE_TREASURE_FORMATTING =
+ "\x3l\v060\t000\x4""077\n"
+ "\x4""077\n"
+ "\x4""077\n"
+ "\x4""077\n"
+ "\x4""077\n"
+ "\x4""077";
+
+const char *const X_FOUND_Y = "\v060\t000\x3""c%s found: %s";
+
+const char *const ON_WHO = "\x3""c\v009On Who?";
+
+const char *const WHICH_ELEMENT1 =
+ "\r\x3""c\x1Which Element?\x2\v034\t014\f15F\fdire\t044"
+ "\f15E\fdlec\t074\f15C\fdold\t104\f15A\fdcid\x1";
+
+const char *const WHICH_ELEMENT2 =
+ "\r\x3""cWhich Element?', 2, 0Bh, '034\t014\f15F\fdire\t044"
+ "\f15E\fdlec\t074\f15C\fdold\t104\f15A\fdcid\x1";
+
+const char *const DETECT_MONSTERS = "\x3""cDetect Monsters";
+
+const char *const LLOYDS_BEACON =
+ "\r\x3""c\v000\t000\x1Lloyd's Beacon\n"
+ "\n"
+ "Last Location\n"
+ "\n"
+ "%s\x3l\n"
+ "x = %d\x3r\t000y = %d\x3""c\x2\v122\t021\f15S\fdet\t060\f15R\fdeturn\x1";
+
+const char *const HOW_MANY_SQUARES = "\x3""cTeleport\nHow many squares %s (1-9)";
+
+const char *const TOWN_PORTAL =
+ "\x3""cTown Portal\x3l\n"
+ "\n"
+ "\t0101. %s\n"
+ "\t0102. %s\n"
+ "\t0103. %s\n"
+ "\t0104. %s\n"
+ "\t0105. %s\x3""c\n"
+ "\n"
+ "To which Town (1-5)\n"
+ "\n";
+
+const int TOWN_MAP_NUMBERS[2][5] = {
+ { 28, 29, 30, 31, 32 }, { 29, 31, 33, 35, 37 }
+};
+
+const char *const MONSTER_DETAILS =
+ "\x3l\n"
+ "%s\x3""c\t100%s\t140%u\t180%u\x3r\t000%s";
+
+const char *const MONSTER_SPECIAL_ATTACKS[23] = {
+ "None", "Magic", "Fire", "Elec", "Cold", "Poison", "Energy", "Disease",
+ "Insane", "Asleep", "CurseItm", "InLove", "DrnSPts", "Curse", "Paralys",
+ "Uncons", "Confuse", "BrkWpn", "Weak", "Erad", "Age+5", "Dead", "Stone"
+};
+
+const char *const IDENTIFY_MONSTERS =
+ "Name\x3""c\t100HP\t140AC\t177#Atks\x3r\t000Special%s%s%s";
+
+const char *const EVENT_SAMPLES[6] = {
+ "ahh.voc", "whereto.voc", "gulp.voc", "null.voc", "scream.voc", "laff1.voc"
+};
+
+} // End of namespace Xeen
diff --git a/engines/xeen/resources.h b/engines/xeen/resources.h
new file mode 100644
index 0000000000..6e9ce6ce39
--- /dev/null
+++ b/engines/xeen/resources.h
@@ -0,0 +1,569 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_RESOURCES_H
+#define XEEN_RESOURCES_H
+
+#include "common/scummsys.h"
+#include "common/str-array.h"
+#include "gui/debugger.h"
+#include "xeen/party.h"
+#include "xeen/spells.h"
+
+namespace Xeen {
+
+class Resources {
+public:
+ SpriteResource _globalSprites;
+ Common::StringArray _maeNames; // Magic and equipment names
+public:
+ Resources();
+};
+
+#define Res (*_vm->_resources)
+
+extern const char *const CREDITS;
+
+extern const char *const OPTIONS_TITLE;
+
+extern const char *const THE_PARTY_NEEDS_REST;
+
+extern const char *const WHO_WILL;
+
+extern const char *const WHATS_THE_PASSWORD;
+
+extern const char *const IN_NO_CONDITION;
+
+extern const char *const NOTHING_HERE;
+
+extern const char *const TERRAIN_TYPES[6];
+
+extern const char *const SURFACE_TYPE_NAMES[15];
+
+extern const char *const SURFACE_NAMES[16];
+
+extern const char *const WHO_ACTIONS[32];
+
+extern const char *const WHO_WILL_ACTIONS[4];
+
+extern const byte SYMBOLS[20][64];
+
+extern const byte TEXT_COLORS[40][4];
+
+extern const char *const DIRECTION_TEXT_UPPER[4];
+
+extern const char *const DIRECTION_TEXT[4];
+
+extern const char *const RACE_NAMES[5];
+
+extern const int RACE_HP_BONUSES[5];
+
+extern const int RACE_SP_BONUSES[5][2];
+
+extern const char *const CLASS_NAMES[11];
+
+extern const uint CLASS_EXP_LEVELS[10];
+
+extern const char *const ALIGNMENT_NAMES[3];
+
+extern const char *const SEX_NAMES[2];
+
+extern const char *const SKILL_NAMES[18];
+
+extern const char *const CONDITION_NAMES[17];
+
+extern const int CONDITION_COLORS[17];
+
+extern const char *const GOOD;
+
+extern const char *const BLESSED;
+
+extern const char *const POWER_SHIELD;
+
+extern const char *const HOLY_BONUS;
+
+extern const char *const HEROISM;
+extern const char *const IN_PARTY;
+
+extern const char *const PARTY_DETAILS;
+extern const char *const PARTY_DIALOG_TEXT;
+
+extern const int FACE_CONDITION_FRAMES[17];
+
+extern const int CHAR_FACES_X[6];
+
+extern const int HP_BARS_X[6];
+
+extern const char *const NO_ONE_TO_ADVENTURE_WITH;
+
+extern const byte BACKGROUND_XLAT[];
+
+extern const char *const YOUR_ROSTER_IS_FULL;
+
+extern const char *const PLEASE_WAIT;
+
+extern const char *const OOPS;
+
+extern const int8 SCREEN_POSITIONING_X[4][48];
+
+extern const int8 SCREEN_POSITIONING_Y[4][48];
+
+extern const int MONSTER_GRID_BITMASK[12];
+
+extern const int INDOOR_OBJECT_X[2][12];
+
+extern const int MAP_OBJECT_Y[2][12];
+
+extern const int INDOOR_MONSTERS_Y[4];
+
+extern const int OUTDOOR_OBJECT_X[2][12];
+
+extern const int OUTDOOR_MONSTER_INDEXES[26];
+
+extern const int OUTDOOR_MONSTERS_Y[26];
+
+extern const int DIRECTION_ANIM_POSITIONS[4][4];
+
+extern const byte WALL_SHIFTS[4][48];
+
+extern const int DRAW_NUMBERS[25];
+
+extern const int DRAW_FRAMES[25][2];
+
+extern const int COMBAT_FLOAT_X[8];
+
+extern const int COMBAT_FLOAT_Y[8];
+
+extern const int MONSTER_EFFECT_FLAGS[15][8];
+
+extern const uint SPELLS_ALLOWED[3][40];
+
+extern const int BASE_HP_BY_CLASS[10];
+
+extern const int AGE_RANGES[10];
+
+extern const int AGE_RANGES_ADJUST[2][10];
+
+extern const uint STAT_VALUES[24];
+
+extern const int STAT_BONUSES[24];
+
+extern const int ELEMENTAL_CATEGORIES[6];
+
+extern const int ATTRIBUTE_CATEGORIES[10];
+
+extern const int ATTRIBUTE_BONUSES[72];
+
+extern const int ELEMENTAL_RESISTENCES[37];
+
+extern const int ELEMENTAL_DAMAGE[37];
+
+extern const int WEAPON_DAMAGE_BASE[35];
+extern const int WEAPON_DAMAGE_MULTIPLIER[35];
+extern const int METAL_DAMAGE[22];
+extern const int METAL_DAMAGE_PERCENT[22];
+
+extern const int METAL_LAC[9];
+
+extern const int ARMOR_STRENGTHS[14];
+
+extern const int MAKE_ITEM_ARR1[6];
+
+extern const int MAKE_ITEM_ARR2[6][7][2];
+
+extern const int MAKE_ITEM_ARR3[10][7][2];
+
+extern const int MAKE_ITEM_ARR4[2][7][2];
+
+extern const int MAKE_ITEM_ARR5[8][2];
+
+extern const int OUTDOOR_DRAWSTRCT_INDEXES[44];
+
+extern const int TOWN_MAXES[2][11];
+
+extern const char *const TOWN_ACTION_MUSIC[14];
+
+extern const char *const TOWN_ACTION_SHAPES[4];
+
+extern const int TOWN_ACTION_FILES[2][7];
+
+extern const char *const BANK_TEXT;
+
+extern const char *const BLACKSMITH_TEXT;
+
+extern const char *const GUILD_NOT_MEMBER_TEXT;
+
+extern const char *const GUILD_TEXT;
+
+extern const char *const TAVERN_TEXT;
+
+extern const char *const GOOD_STUFF;
+
+extern const char *const HAVE_A_DRINK;
+
+extern const char *const YOURE_DRUNK;
+
+extern const int TAVERN_EXIT_LIST[2][6][5][2];
+
+extern const char *const FOOD_AND_DRINK;
+
+extern const char *const TEMPLE_TEXT;
+
+extern const char *const EXPERIENCE_FOR_LEVEL;
+
+extern const char *const LEARNED_ALL;
+
+extern const char *const ELIGIBLE_FOR_LEVEL;
+
+extern const char *const TRAINING_TEXT;
+
+extern const char *const GOLD_GEMS;
+
+extern const char *const GOLD_GEMS_2;
+
+extern const char *const DEPOSIT_WITHDRAWL[2];
+
+extern const char *const NOT_ENOUGH_X_IN_THE_Y;
+
+extern const char *const NO_X_IN_THE_Y;
+
+extern const char *const STAT_NAMES[16];
+
+extern const char *const CONSUMABLE_NAMES[4];
+
+extern const char *const WHERE_NAMES[2];
+
+extern const char *const AMOUNT;
+
+extern const char *const FOOD_PACKS_FULL;
+
+extern const char *const BUY_SPELLS;
+
+extern const char *const GUILD_OPTIONS;
+
+extern const int MISC_SPELL_INDEX[74];
+
+extern const int SPELL_COSTS[77];
+
+extern const int CLOUDS_SPELL_OFFSETS[5][20];
+
+extern const uint DARK_SPELL_OFFSETS[3][39];
+
+extern const int DARK_SPELL_RANGES[12][2];
+
+extern const int SPELL_LEVEL_OFFSETS[3][39];
+
+extern const int SPELL_GEM_COST[77];
+
+extern const char *const NOT_A_SPELL_CASTER;
+
+extern const char *const SPELLS_FOR;
+
+extern const char *const SPELL_LINES_0_TO_9;
+
+extern const char *const SPELLS_DIALOG_SPELLS;
+
+extern const char *const SPELL_PTS;
+
+extern const char *const GOLD;
+
+extern const char *const SPELLS_PRESS_A_KEY;
+
+extern const char *const SPELLS_PURCHASE;
+
+extern const char *const MAP_TEXT;
+
+extern const char *const LIGHT_COUNT_TEXT;
+
+extern const char *const FIRE_RESISTENCE_TEXT;
+
+extern const char *const ELECRICITY_RESISTENCE_TEXT;
+
+extern const char *const COLD_RESISTENCE_TEXT;
+
+extern const char *const POISON_RESISTENCE_TEXT;
+
+extern const char *const CLAIRVOYANCE_TEXT;
+
+extern const char *const LEVITATE_TEXT;
+
+extern const char *const WALK_ON_WATER_TEXT;
+
+extern const char *const GAME_INFORMATION;
+
+extern const char *const WORLD_GAME_TEXT;
+
+extern const char *const DARKSIDE_GAME_TEXT;
+
+extern const char *const CLOUDS_GAME_TEXT;
+
+extern const char *const SWORDS_GAME_TEXT;
+
+extern const char *const WEEK_DAY_STRINGS[10];
+
+extern const char *const CHARACTER_DETAILS;
+
+extern const char *const PARTY_GOLD;
+
+extern const char *const PLUS_14;
+
+extern const char *const CHARACTER_TEMPLATE;
+
+extern const char *const EXCHANGING_IN_COMBAT;
+
+extern const char *const CURRENT_MAXIMUM_RATING_TEXT;
+
+extern const char *const CURRENT_MAXIMUM_TEXT;
+
+extern const char *const RATING_TEXT[24];
+
+extern const char *const AGE_TEXT;
+
+extern const char *const LEVEL_TEXT;
+
+extern const char *const RESISTENCES_TEXT;
+
+extern const char *const NONE;
+
+extern const char *const EXPERIENCE_TEXT;
+
+extern const char *const ELIGIBLE;
+
+extern const char *const IN_PARTY_IN_BANK;
+
+extern const char *const FOOD_TEXT;
+
+extern const char *const EXCHANGE_WITH_WHOM;
+
+extern const char *const QUICK_REF_LINE;
+
+extern const char *const QUICK_REFERENCE;
+
+extern const uint BLACKSMITH_MAP_IDS[2][4];
+
+extern const char *const ITEMS_DIALOG_TEXT1;
+extern const char *const ITEMS_DIALOG_TEXT2;
+extern const char *const ITEMS_DIALOG_LINE1;
+extern const char *const ITEMS_DIALOG_LINE2;
+
+extern const char *const BTN_BUY;
+extern const char *const BTN_SELL;
+extern const char *const BTN_IDENTIFY;
+extern const char *const BTN_FIX;
+extern const char *const BTN_USE;
+extern const char *const BTN_EQUIP;
+extern const char *const BTN_REMOVE;
+extern const char *const BTN_DISCARD;
+extern const char *const BTN_EQUIP;
+extern const char *const BTN_QUEST;
+extern const char *const BTN_ENCHANT;
+extern const char *const BTN_RECHARGE;
+extern const char *const BTN_GOLD;
+
+extern const char *const ITEM_BROKEN;
+extern const char *const ITEM_CURSED;
+extern const char *const BONUS_NAMES[7];
+extern const char *const WEAPON_NAMES[35];
+extern const char *const ARMOR_NAMES[14];
+extern const char *const ACCESSORY_NAMES[11];
+extern const char *const MISC_NAMES[22];
+extern const char *const *ITEM_NAMES[4];
+
+extern const char *const ELEMENTAL_NAMES[6];
+extern const char *const ATTRIBUTE_NAMES[10];
+extern const char *const EFFECTIVENESS_NAMES[7];
+extern const char *const QUEST_ITEM_NAMES[85];
+
+extern const int WEAPON_BASE_COSTS[35];
+extern const int ARMOR_BASE_COSTS[25];
+extern const int ACCESSORY_BASE_COSTS[11];
+extern const int MISC_MATERIAL_COSTS[22];
+extern const int MISC_BASE_COSTS[76];
+extern const int METAL_BASE_MULTIPLIERS[22];
+extern const int ITEM_SKILL_DIVISORS[4];
+
+extern const int RESTRICTION_OFFSETS[4];
+extern const int ITEM_RESTRICTIONS[86];
+
+extern const char *const NOT_PROFICIENT;
+
+extern const char *const NO_ITEMS_AVAILABLE;
+
+extern const char *const CATEGORY_NAMES[4];
+
+extern const char *const X_FOR_THE_Y;
+
+extern const char *const X_FOR_Y;
+
+extern const char *const X_FOR_Y_GOLD;
+
+extern const char *const FMT_CHARGES;
+
+extern const char *const AVAILABLE_GOLD_COST;
+
+extern const char *const CHARGES;
+
+extern const char *const COST;
+
+extern const char *const ITEM_ACTIONS[7];
+extern const char *const WHICH_ITEM;
+
+extern const char *const WHATS_YOUR_HURRY;
+
+extern const char *const USE_ITEM_IN_COMBAT;
+
+extern const char *const NO_SPECIAL_ABILITIES;
+
+extern const char *const CANT_CAST_WHILE_ENGAGED;
+
+extern const char *const EQUIPPED_ALL_YOU_CAN;
+extern const char *const REMOVE_X_TO_EQUIP_Y;
+extern const char *const RING;
+extern const char *const MEDAL;
+
+extern const char *const CANNOT_REMOVE_CURSED_ITEM;
+
+extern const char *const CANNOT_DISCARD_CURSED_ITEM;
+
+extern const char *const PERMANENTLY_DISCARD;
+
+extern const char *const BACKPACK_IS_FULL;
+
+extern const char *const CATEGORY_BACKPACK_IS_FULL[4];
+
+extern const char *const BUY_X_FOR_Y_GOLD;
+
+extern const char *const SELL_X_FOR_Y_GOLD;
+
+extern const char *const NO_NEED_OF_THIS;
+
+extern const char *const NOT_RECHARGABLE;
+
+extern const char *const SPELL_FAILED;
+
+extern const char *const NOT_ENCHANTABLE;
+
+extern const char *const ITEM_NOT_BROKEN;
+
+extern const char *const FIX_IDENTIFY[2];
+
+extern const char *const FIX_IDENTIFY_GOLD;
+
+extern const char *const IDENTIFY_ITEM_MSG;
+
+extern const char *const ITEM_DETAILS;
+
+extern const char *const ALL;
+extern const char *const FIELD_NONE;
+extern const char *const DAMAGE_X_TO_Y;
+extern const char *const ELEMENTAL_XY_DAMAGE;
+extern const char *const ATTR_XY_BONUS;
+extern const char *const EFFECTIVE_AGAINST;
+
+extern const char *const QUESTS_DIALOG_TEXT;
+extern const char *const CLOUDS_OF_XEEN_LINE;
+extern const char *const DARKSIDE_OF_XEEN_LINE;
+
+extern const char *const NO_QUEST_ITEMS;
+extern const char *const NO_CURRENT_QUESTS;
+extern const char *const NO_AUTO_NOTES;
+extern const char *const QUEST_ITEMS_DATA;
+extern const char *const CURRENT_QUESTS_DATA;
+extern const char *const AUTO_NOTES_DATA;
+
+extern const char *const REST_COMPLETE;
+extern const char *const PARTY_IS_STARVING;
+extern const char *const HIT_SPELL_POINTS_RESTORED;
+extern const char *const TOO_DANGEROUS_TO_REST;
+extern const char *const SOME_CHARS_MAY_DIE;
+
+extern const char *const CANT_DISMISS_LAST_CHAR;
+
+extern const char *const REMOVE_DELETE[2];
+extern const char *const REMOVE_OR_DELETE_WHICH;
+
+extern const char *const YOUR_PARTY_IS_FULL;
+
+extern const char *const HAS_SLAYER_SWORD;
+extern const char *const SURE_TO_DELETE_CHAR;
+
+extern const char *const CREATE_CHAR_DETAILS;
+
+extern const char *const NEW_CHAR_STATS;
+extern const char *const NAME_FOR_NEW_CHARACTER;
+extern const char *const SELECT_CLASS_BEFORE_SAVING;
+extern const char *const EXCHANGE_ATTR_WITH;
+extern const int NEW_CHAR_SKILLS[10];
+extern const int NEW_CHAR_SKILLS_LEN[10];
+extern const int NEW_CHAR_RACE_SKILLS[10];
+
+extern const int RACE_MAGIC_RESISTENCES[5];
+extern const int RACE_FIRE_RESISTENCES[5];
+extern const int RACE_ELECTRIC_RESISTENCES[5];
+extern const int RACE_COLD_RESISTENCES[5];
+extern const int RACE_ENERGY_RESISTENCES[5];
+extern const int RACE_POISON_RESISTENCES[5];
+extern const int NEW_CHARACTER_SPELLS[10][4];
+
+extern const char *const COMBAT_DETAILS;
+
+extern const char *NOT_ENOUGH_TO_CAST;
+extern const char *SPELL_CAST_COMPONENTS[2];
+
+extern const char *const CAST_SPELL_DETAILS;
+
+extern const char *const PARTY_FOUND;
+
+extern const char *const BACKPACKS_FULL_PRESS_KEY;
+
+extern const char *const HIT_A_KEY;
+
+extern const char *const GIVE_TREASURE_FORMATTING;
+
+extern const char *const X_FOUND_Y;
+
+extern const char *const ON_WHO;
+
+extern const char *const WHICH_ELEMENT1;
+extern const char *const WHICH_ELEMENT2;
+
+extern const char *const DETECT_MONSTERS;
+
+extern const char *const LLOYDS_BEACON;
+
+extern const char *const HOW_MANY_SQUARES;
+
+extern const char *const TOWN_PORTAL;
+
+extern const int TOWN_MAP_NUMBERS[2][5];
+
+extern const char *const MONSTER_DETAILS;
+
+extern const char *const MONSTER_SPECIAL_ATTACKS[23];
+
+extern const char *const IDENTIFY_MONSTERS;
+
+extern const char *const EVENT_SAMPLES[6];
+
+} // End of namespace Xeen
+
+#endif /* XEEN_RESOURCES_H */
diff --git a/engines/xeen/saves.cpp b/engines/xeen/saves.cpp
new file mode 100644
index 0000000000..9fd2d19a9b
--- /dev/null
+++ b/engines/xeen/saves.cpp
@@ -0,0 +1,171 @@
+/* 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 "common/scummsys.h"
+#include "common/algorithm.h"
+#include "common/memstream.h"
+#include "xeen/saves.h"
+#include "xeen/files.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+OutFile::OutFile(XeenEngine *vm, const Common::String filename) :
+ _vm(vm), _filename(filename) {
+}
+
+void OutFile::finalize() {
+ uint16 id = BaseCCArchive::convertNameToId(_filename);
+
+ if (!_vm->_saves->_newData.contains(id))
+ _vm->_saves->_newData[id] = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
+
+ Common::MemoryWriteStreamDynamic &out = _vm->_saves->_newData[id];
+ out.write(getData(), size());
+}
+
+/*------------------------------------------------------------------------*/
+
+SavesManager::SavesManager(XeenEngine *vm, Party &party) :
+ BaseCCArchive(), _vm(vm), _party(party) {
+ SearchMan.add("saves", this, 0, false);
+ _data = nullptr;
+ _wonWorld = false;
+ _wonDarkSide = false;
+}
+
+SavesManager::~SavesManager() {
+ delete[] _data;
+}
+
+void SavesManager::syncBitFlags(Common::Serializer &s, bool *startP, bool *endP) {
+ byte data = 0;
+
+ int bitCounter = 0;
+ for (bool *p = startP; p <= endP; ++p, bitCounter = (bitCounter + 1) % 8) {
+ if (p == endP || bitCounter == 0) {
+ if (p != endP || s.isSaving())
+ s.syncAsByte(data);
+ if (p == endP)
+ break;
+
+ if (s.isSaving())
+ data = 0;
+ }
+
+ if (s.isLoading())
+ *p = (data >> bitCounter) != 0;
+ else if (*p)
+ data |= 1 << bitCounter;
+ }
+}
+
+Common::SeekableReadStream *SavesManager::createReadStreamForMember(const Common::String &name) const {
+ CCEntry ccEntry;
+
+ // If the given resource has already been perviously "written" to the
+ // save manager, then return that new resource
+ uint16 id = BaseCCArchive::convertNameToId(name);
+ if (_newData.contains(id)) {
+ Common::MemoryWriteStreamDynamic stream = _newData[id];
+ return new Common::MemoryReadStream(stream.getData(), stream.size());
+ }
+
+ // Retrieve the resource from the loaded savefile
+ if (getHeaderEntry(name, ccEntry)) {
+ // Open the correct CC entry
+ return new Common::MemoryReadStream(_data + ccEntry._offset, ccEntry._size);
+ }
+
+ return nullptr;
+}
+
+void SavesManager::load(Common::SeekableReadStream *stream) {
+ loadIndex(stream);
+
+ delete[] _data;
+ _data = new byte[stream->size()];
+ stream->seek(0);
+ stream->read(_data, stream->size());
+
+ // Load in the character stats and active party
+ Common::SeekableReadStream *chr = createReadStreamForMember("maze.chr");
+ Common::Serializer sChr(chr, nullptr);
+ _party._roster.synchronize(sChr);
+ delete chr;
+
+ Common::SeekableReadStream *pty = createReadStreamForMember("maze.pty");
+ Common::Serializer sPty(pty, nullptr);
+ _party.synchronize(sPty);
+ delete pty;
+}
+
+void SavesManager::reset() {
+ Common::String prefix = _vm->getGameID() != GType_DarkSide ? "xeen|" : "dark|";
+ Common::MemoryWriteStreamDynamic saveFile(DisposeAfterUse::YES);
+ Common::File fIn;
+
+ const int RESOURCES[6] = { 0x2A0C, 0x2A1C, 0x2A2C, 0x2A3C, 0x284C, 0x2A5C };
+ for (int i = 0; i < 6; ++i) {
+ Common::String filename = prefix + Common::String::format("%.4x", RESOURCES[i]);
+ if (fIn.exists(filename)) {
+ // Read in the next resource
+ fIn.open(filename);
+ byte *data = new byte[fIn.size()];
+ fIn.read(data, fIn.size());
+
+ // Copy it to the combined savefile resource
+ saveFile.write(data, fIn.size());
+ delete[] data;
+ fIn.close();
+ }
+ }
+
+ Common::MemoryReadStream f(saveFile.getData(), saveFile.size());
+ load(&f);
+
+ // Set up the party and characters from dark.cur
+ CCArchive gameCur("xeen.cur", false);
+ File fParty("maze.pty", gameCur);
+ Common::Serializer sParty(&fParty, nullptr);
+ _party.synchronize(sParty);
+ fParty.close();
+
+ File fChar("maze.chr", gameCur);
+ Common::Serializer sChar(&fChar, nullptr);
+ _party._roster.synchronize(sChar);
+ fChar.close();
+}
+
+void SavesManager::readCharFile() {
+ warning("TODO: readCharFile");
+}
+
+void SavesManager::writeCharFile() {
+ warning("TODO: writeCharFile");
+}
+
+void SavesManager::saveChars() {
+ warning("TODO: saveChars");
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/saves.h b/engines/xeen/saves.h
new file mode 100644
index 0000000000..2571c46600
--- /dev/null
+++ b/engines/xeen/saves.h
@@ -0,0 +1,96 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_SAVES_H
+#define XEEN_SAVES_H
+
+#include "common/scummsys.h"
+#include "common/memstream.h"
+#include "common/savefile.h"
+#include "graphics/surface.h"
+#include "xeen/party.h"
+#include "xeen/files.h"
+
+namespace Xeen {
+
+struct XeenSavegameHeader {
+ uint8 _version;
+ Common::String _saveName;
+ Graphics::Surface *_thumbnail;
+ int _year, _month, _day;
+ int _hour, _minute;
+ int _totalFrames;
+};
+
+class XeenEngine;
+class SavesManager;
+
+class OutFile : public Common::MemoryWriteStreamDynamic {
+private:
+ XeenEngine *_vm;
+ Common::String _filename;
+public:
+ OutFile(XeenEngine *vm, const Common::String filename);
+
+ void finalize();
+};
+
+class SavesManager: public BaseCCArchive {
+ friend class OutFile;
+private:
+ XeenEngine *_vm;
+ Party &_party;
+ byte *_data;
+ Common::HashMap<uint16, Common::MemoryWriteStreamDynamic > _newData;
+
+ void load(Common::SeekableReadStream *stream);
+public:
+ /**
+ * Synchronizes a boolean array as a bitfield set
+ */
+ static void syncBitFlags(Common::Serializer &s, bool *startP, bool *endP);
+public:
+ bool _wonWorld;
+ bool _wonDarkSide;
+public:
+ SavesManager(XeenEngine *vm, Party &party);
+
+ ~SavesManager();
+
+ /**
+ * Sets up the dynamic data for the game for a new game
+ */
+ void reset();
+
+ void readCharFile();
+
+ void writeCharFile();
+
+ void saveChars();
+
+ // Archive implementation
+ virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_SAVES_H */
diff --git a/engines/xeen/screen.cpp b/engines/xeen/screen.cpp
new file mode 100644
index 0000000000..03b751a6b9
--- /dev/null
+++ b/engines/xeen/screen.cpp
@@ -0,0 +1,479 @@
+/* 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 "common/system.h"
+#include "graphics/palette.h"
+#include "graphics/surface.h"
+#include "xeen/screen.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+Window::Window() : XSurface(), _vm(nullptr), _enabled(false),
+ _a(0), _border(0), _xLo(0), _xHi(0), _ycL(0), _ycH(0) {
+}
+
+Window::Window(const Window &src) : XSurface(), _vm(src._vm), _enabled(src._enabled),
+ _a(src._a), _border(src._border), _xLo(src._xLo), _ycL(src._ycL),
+ _xHi(src._xHi), _ycH(src._ycH) {
+ if (src._vm) {
+ setBounds(src._bounds);
+ create(*_vm->_screen, Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+ }
+}
+
+Window::Window(XeenEngine *vm, const Common::Rect &bounds, int a, int border,
+ int xLo, int ycL, int xHi, int ycH): XSurface(),
+ _vm(vm), _enabled(false), _a(a), _border(border),
+ _xLo(xLo), _ycL(ycL), _xHi(xHi), _ycH(ycH) {
+ setBounds(bounds);
+ create(*_vm->_screen, Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+}
+
+void Window::setBounds(const Common::Rect &r) {
+ _bounds = r;
+ _innerBounds = r;
+ _innerBounds.grow(-_border);
+}
+
+void Window::open() {
+ if (!_enabled) {
+ _enabled = true;
+ _vm->_screen->_windowStack.push_back(this);
+ open2();
+ }
+
+ if (_vm->_mode == MODE_9) {
+ warning("TODO: copyFileToMemory");
+ }
+}
+
+void Window::open2() {
+ Screen &screen = *_vm->_screen;
+
+ // Save a copy of the area under the window
+ _savedArea.create(_bounds.width(), _bounds.height());
+ _savedArea.copyRectToSurface(screen, 0, 0, _bounds);
+
+ // Mark the area as dirty and fill it with a default background
+ addDirtyRect(_bounds);
+ frame();
+ fill();
+
+ screen._writePos.x = _bounds.right - 8;
+ screen.writeSymbol(19);
+
+ screen._writePos.x = _innerBounds.left;
+ screen._writePos.y = _innerBounds.top;
+ screen._fontJustify = JUSTIFY_NONE;
+ screen._fontReduced = false;
+}
+
+void Window::frame() {
+ Screen &screen = *_vm->_screen;
+ int xCount = (_bounds.width() - 9) / FONT_WIDTH;
+ int yCount = (_bounds.height() - 9) / FONT_HEIGHT;
+
+ // Write the top line
+ screen._writePos = Common::Point(_bounds.left, _bounds.top);
+ screen.writeSymbol(0);
+
+ if (xCount > 0) {
+ int symbolId = 1;
+ for (int i = 0; i < xCount; ++i) {
+ screen.writeSymbol(symbolId);
+ if (++symbolId == 5)
+ symbolId = 1;
+ }
+ }
+
+ screen._writePos.x = _bounds.right - FONT_WIDTH;
+ screen.writeSymbol(5);
+
+ // Write the vertical edges
+ if (yCount > 0) {
+ int symbolId = 6;
+ for (int i = 0; i < yCount; ++i) {
+ screen._writePos.y += 8;
+
+ screen._writePos.x = _bounds.left;
+ screen.writeSymbol(symbolId);
+
+ screen._writePos.x = _bounds.right - FONT_WIDTH;
+ screen.writeSymbol(symbolId + 4);
+
+ if (++symbolId == 10)
+ symbolId = 6;
+ }
+ }
+
+ // Write the bottom line
+ screen._writePos = Common::Point(_bounds.left, _bounds.bottom - FONT_HEIGHT);
+ screen.writeSymbol(14);
+
+ if (xCount > 0) {
+ int symbolId = 15;
+ for (int i = 0; i < xCount; ++i) {
+ screen.writeSymbol(symbolId);
+ if (++symbolId == 19)
+ symbolId = 15;
+ }
+ }
+
+ screen._writePos.x = _bounds.right - FONT_WIDTH;
+ screen.writeSymbol(19);
+}
+
+void Window::close() {
+ Screen &screen = *_vm->_screen;
+
+ if (_enabled) {
+ // Update the window
+ update();
+
+ // Restore the saved original content
+ screen.copyRectToSurface(_savedArea, _bounds.left, _bounds.top,
+ Common::Rect(0, 0, _bounds.width(), _bounds.height()));
+ addDirtyRect(_bounds);
+
+ // Remove the window from the stack and flag it as now disabled
+ for (uint i = 0; i < _vm->_screen->_windowStack.size(); ++i) {
+ if (_vm->_screen->_windowStack[i] == this)
+ _vm->_screen->_windowStack.remove_at(i);
+ }
+
+ _enabled = false;
+ }
+
+ if (_vm->_mode == MODE_9) {
+ warning("TODO: copyFileToMemory");
+ }
+}
+
+void Window::update() {
+ // Since all window drawing is done on the screen surface anyway,
+ // there's nothing that needs to be updated here
+}
+
+void Window::addDirtyRect(const Common::Rect &r) {
+ _vm->_screen->addDirtyRect(r);
+}
+
+void Window::fill() {
+ fillRect(_innerBounds, _vm->_screen->_bgColor);
+}
+
+const char *Window::writeString(const Common::String &s) {
+ return _vm->_screen->writeString(s, _innerBounds);
+}
+
+void Window::drawList(DrawStruct *items, int count) {
+ for (int i = 0; i < count; ++i, ++items) {
+ if (items->_frame == -1 || items->_scale == -1 || items->_sprites == nullptr)
+ continue;
+
+ Common::Point pt(items->_x, items->_y);
+ pt.x += _innerBounds.left;
+ pt.y += _innerBounds.top;
+
+ items->_sprites->draw(*this, items->_frame, pt, items->_flags, items->_scale);
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+Screen::Screen(XeenEngine *vm) : _vm(vm) {
+ _fadeIn = false;
+ create(SCREEN_WIDTH, SCREEN_HEIGHT);
+ Common::fill(&_tempPalette[0], &_tempPalette[PALETTE_SIZE], 0);
+ Common::fill(&_mainPalette[0], &_mainPalette[PALETTE_SIZE], 0);
+
+ // Load font data for the screen
+ File f("fnt");
+ byte *data = new byte[f.size()];
+ f.read(data, f.size());
+ _fontData = data;
+}
+
+Screen::~Screen() {
+ delete[] _fontData;
+}
+
+void Screen::setupWindows() {
+ Window windows[40] = {
+ Window(_vm, Common::Rect(0, 0, 320, 200), 0, 0, 0, 0, 320, 200),
+ Window(_vm, Common::Rect(237, 9, 317, 74), 0, 0, 237, 12, 307, 68),
+ Window(_vm, Common::Rect(225, 1, 319, 73), 1, 8, 225, 1, 319, 73),
+ Window(_vm, Common::Rect(0, 0, 230, 149), 0, 0, 9, 8, 216, 140),
+ Window(_vm, Common::Rect(235, 148, 309, 189), 2, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(70, 20, 250, 183), 3, 8, 80, 38, 240, 166),
+ Window(_vm, Common::Rect(52, 149, 268, 197), 4, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(108, 0, 200, 200), 5, 0, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(232, 9, 312, 74), 0, 0, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(103, 156, 217, 186), 6, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(226, 0, 319, 146), 7, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(8, 8, 224, 140), 8, 8, 8, 8, 224, 200),
+ Window(_vm, Common::Rect(0, 143, 320, 199), 9, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(50, 103, 266, 139), 10, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(0, 7, 320, 138), 11, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(50, 71, 182, 129), 12, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(228, 106, 319, 146), 13, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(20, 142, 290, 199), 14, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(0, 20, 320, 180), 15, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(231, 48, 317, 141), 16, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(72, 37, 248, 163), 17, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(99, 59, 237, 141), 18, 8, 99, 59, 237, 0),
+ Window(_vm, Common::Rect(65, 23, 250, 163), 19, 8, 75, 36, 245, 141),
+ Window(_vm, Common::Rect(80, 28, 256, 148), 20, 8, 80, 28, 256, 172),
+ Window(_vm, Common::Rect(0, 0, 320, 146), 21, 8, 0, 0, 320, 148),
+ Window(_vm, Common::Rect(27, 6, 207, 142), 22, 8, 0, 0, 0, 146),
+ Window(_vm, Common::Rect(15, 15, 161, 91), 23, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(90, 45, 220, 157), 24, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(0, 0, 320, 200), 25, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(0, 101, 320, 146), 26, 8, 0, 101, 320, 0),
+ Window(_vm, Common::Rect(0, 0, 320, 108), 27, 8, 0, 0, 0, 45),
+ Window(_vm, Common::Rect(50, 112, 266, 148), 28, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(12, 11, 164, 94), 0, 0, 0, 0, 52, 0),
+ Window(_vm, Common::Rect(8, 147, 224, 192), 0, 8, 0, 0, 0, 94),
+ Window(_vm, Common::Rect(232, 74, 312, 138), 29, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(226, 26, 319, 146), 30, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(225, 74, 319, 154), 31, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(27, 6, 195, 142), 0, 8, 0, 0, 0, 0),
+ Window(_vm, Common::Rect(225, 140, 319, 199), 0, 8, 0, 0, 0, 0)
+ };
+
+ _windows = Common::Array<Window>(windows, 40);
+}
+
+void Screen::closeWindows() {
+ for (int i = (int)_windowStack.size() - 1; i >= 0; --i)
+ _windowStack[i]->close();
+ assert(_windowStack.size() == 0);
+}
+
+void Screen::update() {
+ // Merge the dirty rects
+ mergeDirtyRects();
+
+ // Loop through copying dirty areas to the physical screen
+ Common::List<Common::Rect>::iterator i;
+ for (i = _dirtyRects.begin(); i != _dirtyRects.end(); ++i) {
+ const Common::Rect &r = *i;
+ const byte *srcP = (const byte *)getBasePtr(r.left, r.top);
+ g_system->copyRectToScreen(srcP, this->pitch, r.left, r.top,
+ r.width(), r.height());
+ }
+
+ // Signal the physical screen to update
+ g_system->updateScreen();
+ _dirtyRects.clear();
+}
+
+void Screen::addDirtyRect(const Common::Rect &r) {
+ assert(r.isValidRect() && r.width() > 0 && r.height() > 0
+ && r.left >= 0 && r.top >= 0
+ && r.right <= SCREEN_WIDTH && r.bottom <= SCREEN_HEIGHT);
+ _dirtyRects.push_back(r);
+}
+
+void Screen::mergeDirtyRects() {
+ Common::List<Common::Rect>::iterator rOuter, rInner;
+
+ // Ensure dirty rect list has at least two entries
+ rOuter = _dirtyRects.begin();
+ for (int i = 0; i < 2; ++i, ++rOuter) {
+ if (rOuter == _dirtyRects.end())
+ return;
+ }
+
+ // Process the dirty rect list to find any rects to merge
+ for (rOuter = _dirtyRects.begin(); rOuter != _dirtyRects.end(); ++rOuter) {
+ rInner = rOuter;
+ while (++rInner != _dirtyRects.end()) {
+
+ if ((*rOuter).intersects(*rInner)) {
+ // these two rectangles overlap or
+ // are next to each other - merge them
+
+ unionRectangle(*rOuter, *rOuter, *rInner);
+
+ // remove the inner rect from the list
+ _dirtyRects.erase(rInner);
+
+ // move back to beginning of list
+ rInner = rOuter;
+ }
+ }
+ }
+}
+
+bool Screen::unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2) {
+ destRect = src1;
+ destRect.extend(src2);
+
+ return !destRect.isEmpty();
+}
+
+void Screen::loadPalette(const Common::String &name) {
+ File f(name);
+ for (int i = 0; i < PALETTE_SIZE; ++i)
+ _tempPalette[i] = f.readByte() << 2;
+}
+
+void Screen::loadBackground(const Common::String &name) {
+ File f(name);
+
+ assert(f.size() == (SCREEN_WIDTH * SCREEN_HEIGHT));
+ f.read((byte *)getPixels(), SCREEN_WIDTH * SCREEN_HEIGHT);
+ addDirtyRect(Common::Rect(0, 0, this->w, this->h));
+}
+
+void Screen::loadPage(int pageNum) {
+ assert(pageNum == 0 || pageNum == 1);
+ if (_pages[0].empty()) {
+ _pages[0].create(SCREEN_WIDTH, SCREEN_HEIGHT);
+ _pages[1].create(SCREEN_WIDTH, SCREEN_HEIGHT);
+ }
+
+ _pages[pageNum].blitFrom(*this);
+}
+
+void Screen::freePages() {
+ _pages[0].free();
+ _pages[1].free();
+}
+
+void Screen::horizMerge(int xp) {
+ if (_pages[0].empty())
+ return;
+
+ for (int y = 0; y < SCREEN_HEIGHT; ++y) {
+ byte *destP = (byte *)getBasePtr(0, y);
+ const byte *srcP = (const byte *)_pages[0].getBasePtr(0, y);
+ Common::copy(srcP, srcP + SCREEN_WIDTH - xp, destP);
+
+ if (xp != 0) {
+ srcP = (const byte *)_pages[1].getBasePtr(0, y);
+ Common::copy(srcP + SCREEN_WIDTH - xp, srcP + SCREEN_WIDTH, destP + SCREEN_WIDTH - xp);
+ }
+ }
+}
+
+void Screen::vertMerge(int yp) {
+ if (_pages[0].empty())
+ return;
+
+ for (int y = 0; y < SCREEN_HEIGHT - yp; ++y) {
+ const byte *srcP = (const byte *)_pages[0].getBasePtr(0, y);
+ byte *destP = (byte *)getBasePtr(0, y);
+ Common::copy(srcP, srcP + SCREEN_WIDTH, destP);
+ }
+
+ for (int y = SCREEN_HEIGHT - yp; y < SCREEN_HEIGHT; ++y) {
+ const byte *srcP = (const byte *)_pages[1].getBasePtr(0, y);
+ byte *destP = (byte *)getBasePtr(0, y);
+ Common::copy(srcP, srcP + SCREEN_WIDTH, destP);
+ }
+}
+
+void Screen::draw(void *data) {
+ // TODO: Figure out data structure that can be passed to method
+ assert(!data);
+ drawScreen();
+}
+
+void Screen::drawScreen() {
+ addDirtyRect(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+}
+
+void Screen::fadeIn(int step) {
+ _fadeIn = true;
+ fadeInner(step);
+}
+
+void Screen::fadeOut(int step) {
+ _fadeIn = false;
+ fadeInner(step);
+}
+
+void Screen::fadeInner(int step) {
+ for (int idx = 128; idx >= 0 && !_vm->shouldQuit(); idx -= step) {
+ int val = MAX(idx, 0);
+ bool flag = !_fadeIn;
+ if (!flag) {
+ val = -(val - 128);
+ flag = step != 0x81;
+ }
+
+ if (!flag) {
+ step = 0x80;
+ } else {
+ // Create a scaled palette from the temporary one
+ for (int i = 0; i < PALETTE_SIZE; ++i) {
+ _mainPalette[i] = (_tempPalette[i] * val * 2) >> 8;
+ }
+
+ updatePalette();
+ }
+
+ _vm->_events->pollEventsAndWait();
+ }
+}
+
+void Screen::updatePalette() {
+ updatePalette(_mainPalette, 0, 16);
+}
+
+void Screen::updatePalette(const byte *pal, int start, int count16) {
+ g_system->getPaletteManager()->setPalette(pal, start, count16 * 16);
+}
+
+void Screen::saveBackground(int slot) {
+ assert(slot > 0 && slot < 10);
+ _savedScreens[slot - 1].copyFrom(*this);
+}
+
+void Screen::restoreBackground(int slot) {
+ assert(slot > 0 && slot < 10);
+
+ blitFrom(_savedScreens[slot - 1]);
+}
+
+void Screen::frameWindow(uint bgType) {
+ if (bgType >= 4)
+ return;
+
+ if (bgType == 0) {
+ // Totally black background
+ _vm->_screen->fillRect(Common::Rect(8, 8, 224, 140), 0);
+ } else {
+ const byte *lookup = BACKGROUND_XLAT + bgType;
+ for (int yp = 8; yp < 140; ++yp) {
+ byte *destP = (byte *)_vm->_screen->getBasePtr(8, yp);
+ for (int xp = 8; xp < 224; ++xp, ++destP)
+ *destP = lookup[*destP];
+ }
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/screen.h b/engines/xeen/screen.h
new file mode 100644
index 0000000000..854a05a7cf
--- /dev/null
+++ b/engines/xeen/screen.h
@@ -0,0 +1,194 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_SCREEN_H
+#define XEEN_SCREEN_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/array.h"
+#include "common/keyboard.h"
+#include "common/rect.h"
+#include "xeen/font.h"
+#include "xeen/sprites.h"
+#include "xeen/xsurface.h"
+
+namespace Xeen {
+
+#define SCREEN_WIDTH 320
+#define SCREEN_HEIGHT 200
+#define PALETTE_COUNT 256
+#define PALETTE_SIZE (256 * 3)
+#define GAME_WINDOW 28
+
+class XeenEngine;
+class Screen;
+
+struct DrawStruct {
+ SpriteResource *_sprites;
+ int _frame;
+ int _x;
+ int _y;
+ int _scale;
+ int _flags;
+
+ DrawStruct(int frame, int x, int y, int scale = 0, int flags = 0) :
+ _sprites(nullptr), _frame(frame), _x(x), _y(y), _scale(scale), _flags(flags) {}
+ DrawStruct(): _sprites(nullptr), _frame(0), _x(0), _y(0), _scale(0), _flags(0) {}
+};
+
+class Window: public XSurface {
+private:
+ XeenEngine *_vm;
+ Common::Rect _bounds;
+ Common::Rect _innerBounds;
+ XSurface _savedArea;
+ int _a;
+ int _border;
+ int _xLo, _xHi;
+ int _ycL, _ycH;
+
+ void open2();
+public:
+ bool _enabled;
+public:
+ virtual void addDirtyRect(const Common::Rect &r);
+public:
+ Window();
+ Window(const Window &src);
+ Window(XeenEngine *vm, const Common::Rect &bounds, int a, int border,
+ int xLo, int ycL, int xHi, int ycH);
+
+ void setBounds(const Common::Rect &r);
+
+ const Common::Rect &getBounds() { return _bounds; }
+
+ void open();
+
+ void close();
+
+ /**
+ * Update the window
+ */
+ void update();
+
+ void frame();
+
+ /**
+ * Fill the content area of a window with the current background color
+ */
+ void fill();
+
+ const char *writeString(const Common::String &s);
+
+ void drawList(DrawStruct *items, int count);
+
+ int getString(Common::String &line, uint maxLen, int maxWidth);
+};
+
+class Screen: public FontSurface {
+private:
+ XeenEngine *_vm;
+ Common::List<Common::Rect> _dirtyRects;
+ byte _mainPalette[PALETTE_SIZE];
+ byte _tempPalette[PALETTE_SIZE];
+ XSurface _pages[2];
+ XSurface _savedScreens[10];
+ bool _fadeIn;
+
+ void mergeDirtyRects();
+
+ bool unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2);
+
+ /**
+ * Mark the entire screen for drawing
+ */
+ void drawScreen();
+
+ void fadeInner(int step);
+
+ void updatePalette();
+
+ void updatePalette(const byte *pal, int start, int count16);
+public:
+ /**
+ * Adds an area that requires redrawing on the next frame update
+ */
+ virtual void addDirtyRect(const Common::Rect &r);
+public:
+ Common::Array<Window> _windows;
+
+ Common::Array<Window *> _windowStack;
+public:
+ Screen(XeenEngine *vm);
+
+ virtual ~Screen();
+
+ void setupWindows();
+
+ void closeWindows();
+
+ void update();
+
+ /**
+ * Load a palette resource into the temporary palette
+ */
+ void loadPalette(const Common::String &name);
+
+ /**
+ * Load a background resource into memory
+ */
+ void loadBackground(const Common::String &name);
+
+ /**
+ * Copy a loaded background into a display page
+ */
+ void loadPage(int pageNum);
+
+ void freePages();
+
+ /**
+ * Merge the two pages along a horizontal split point
+ */
+ void horizMerge(int xp);
+
+ /**
+ * Merge the two pages along a vertical split point
+ */
+ void vertMerge(int yp);
+
+ void draw(void *data = nullptr);
+
+ void fadeIn(int step);
+
+ void fadeOut(int step);
+
+ void saveBackground(int slot = 1);
+
+ void restoreBackground(int slot = 1);
+
+ void frameWindow(uint bgType);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_SCREEN_H */
diff --git a/engines/xeen/scripts.cpp b/engines/xeen/scripts.cpp
new file mode 100644
index 0000000000..0e752529d5
--- /dev/null
+++ b/engines/xeen/scripts.cpp
@@ -0,0 +1,1770 @@
+/* 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 "common/config-manager.h"
+#include "xeen/scripts.h"
+#include "xeen/dialogs_input.h"
+#include "xeen/dialogs_whowill.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/party.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+MazeEvent::MazeEvent() : _direction(DIR_ALL), _line(-1), _opcode(OP_None) {
+}
+
+void MazeEvent::synchronize(Common::Serializer &s) {
+ int len = 5 + _parameters.size();
+ s.syncAsByte(len);
+
+ s.syncAsByte(_position.x);
+ s.syncAsByte(_position.y);
+ s.syncAsByte(_direction);
+ s.syncAsByte(_line);
+ s.syncAsByte(_opcode);
+
+ len -= 5;
+ if (s.isLoading())
+ _parameters.resize(len);
+ for (int i = 0; i < len; ++i)
+ s.syncAsByte(_parameters[i]);
+}
+
+/*------------------------------------------------------------------------*/
+
+void MazeEvents::synchronize(XeenSerializer &s) {
+ MazeEvent e;
+
+ if (s.isLoading()) {
+ clear();
+ while (!s.finished()) {
+ e.synchronize(s);
+ push_back(e);
+ }
+ } else {
+ for (uint i = 0; i < size(); ++i)
+ (*this).operator[](i).synchronize(s);
+ }
+}
+
+/*------------------------------------------------------------------------*/
+
+bool MirrorEntry::synchronize(Common::SeekableReadStream &s) {
+ if (s.pos() >= s.size())
+ return false;
+
+ char buffer[28];
+ s.read(buffer, 28);
+ buffer[27] = '\0';
+
+ _name = Common::String(buffer);
+ _mapId = s.readByte();
+ _position.x = s.readSByte();
+ _position.y = s.readSByte();
+ _direction = s.readSByte();
+ return true;
+}
+
+/*------------------------------------------------------------------------*/
+
+Scripts::Scripts(XeenEngine *vm) : _vm(vm) {
+ _whoWill = 0;
+ _itemType = 0;
+ _treasureItems = 0;
+ _lineNum = 0;
+ _charIndex = 0;
+ _v2 = 0;
+ _nEdamageType = 0;
+ _animCounter = 0;
+ _eventSkipped = false;
+ _mirrorId = -1;
+ _refreshIcons = false;
+ _scriptResult = false;
+ _scriptExecuted = false;
+ _var50 = false;
+ _redrawDone = false;
+ _windowIndex = -1;
+}
+
+int Scripts::checkEvents() {
+ Combat &combat = *_vm->_combat;
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Town &town = *_vm->_town;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ _refreshIcons = false;
+ _itemType = 0;
+ _scriptExecuted = false;
+ _var50 = false;
+ _whoWill = 0;
+ Mode oldMode = _vm->_mode;
+ Common::fill(&intf._charFX[0], &intf._charFX[MAX_ACTIVE_PARTY], 0);
+ //int items = _treasureItems;
+
+ if (party._treasure._gold & party._treasure._gems) {
+ // Backup any current treasure data
+ party._savedTreasure = party._treasure;
+ party._treasure._hasItems = false;
+ party._treasure._gold = 0;
+ party._treasure._gems = 0;
+ } else {
+ party._savedTreasure._hasItems = false;
+ party._savedTreasure._gold = 0;
+ party._savedTreasure._gems = 0;
+ }
+
+ do {
+ _lineNum = 0;
+ _scriptResult = false;
+ _animCounter = 0;
+ _redrawDone = false;
+ _currentPos = party._mazePosition;
+ _charIndex = 1;
+ _v2 = 1;
+ _nEdamageType = 0;
+// int var40 = -1;
+
+ while (!_vm->shouldQuit() && _lineNum >= 0) {
+ // Break out of the events if there's an attacking monster
+ if (combat._attackMonsters[0] != -1) {
+ _eventSkipped = true;
+ break;
+ }
+
+ _eventSkipped = false;
+ uint eventIndex;
+ for (eventIndex = 0; eventIndex < map._events.size(); ++eventIndex) {
+ MazeEvent &event = map._events[eventIndex];
+
+ if (event._position == _currentPos && party._mazeDirection !=
+ (_currentPos.x | _currentPos.y) && event._line == _lineNum) {
+ if (event._direction == party._mazeDirection || event._direction == DIR_ALL) {
+ _vm->_mode = MODE_9;
+ _scriptExecuted = true;
+ doOpcode(event);
+ break;
+ } else {
+ _var50 = true;
+ }
+ }
+ }
+ if (eventIndex == map._events.size())
+ _lineNum = -1;
+ }
+ } while (!_vm->shouldQuit() && !_eventSkipped && _lineNum != -1);
+
+ intf._face1State = intf._face2State = 2;
+ if (_refreshIcons) {
+ screen.closeWindows();
+ intf.drawParty(true);
+ }
+
+ party.checkPartyDead();
+ if (party._treasure._hasItems || party._treasure._gold || party._treasure._gems)
+ party.giveTreasure();
+
+ if (_animCounter > 0 && intf._objNumber) {
+ MazeObject &selectedObj = map._mobData._objects[intf._objNumber - 1];
+
+ if (selectedObj._spriteId == (isDarkCc ? 15 : 16)) {
+ for (uint idx = 0; idx < 16; ++idx) {
+ MazeObject &obj = map._mobData._objects[idx];
+ if (obj._spriteId == (isDarkCc ? 62 : 57)) {
+ selectedObj._id = idx;
+ selectedObj._spriteId = isDarkCc ? 62 : 57;
+ break;
+ }
+ }
+ } else if (selectedObj._spriteId == 73) {
+ for (uint idx = 0; idx < 16; ++idx) {
+ MazeObject &obj = map._mobData._objects[idx];
+ if (obj._spriteId == 119) {
+ selectedObj._id = idx;
+ selectedObj._spriteId = 119;
+ break;
+ }
+ }
+ }
+ }
+
+ _animCounter = 0;
+ _vm->_mode = oldMode;
+ screen.closeWindows();
+
+ if (_scriptExecuted || !intf._objNumber || _var50) {
+ if (_var50 && !_scriptExecuted && intf._objNumber && !map._currentIsEvent) {
+ sound.playFX(21);
+ }
+ } else {
+ Window &w = screen._windows[38];
+ w.open();
+ w.writeString(NOTHING_HERE);
+ w.update();
+
+ do {
+ intf.draw3d(true);
+ events.updateGameCounter();
+ events.wait(1, true);
+ } while (!events.isKeyMousePressed());
+ events.clearEvents();
+
+ w.close();
+ }
+
+ // Restore saved treasure
+ if (party._savedTreasure._hasItems || party._savedTreasure._gold ||
+ party._savedTreasure._gems) {
+ party._treasure = party._savedTreasure;
+ }
+
+ // Clear any town loaded sprites
+ town.clearSprites();
+
+ _v2 = 1;
+ Common::fill(&intf._charFX[0], &intf._charFX[6], 0);
+
+ return _scriptResult;
+}
+
+void Scripts::openGrate(int wallVal, int action) {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ if ((wallVal != 13 || map._currentGrateUnlocked) && (!isDarkCc || wallVal != 9 ||
+ map.mazeData()._wallKind != 2)) {
+ if (wallVal != 9 && !map._currentGrateUnlocked) {
+ int charIndex = WhoWill::show(_vm, 13, action, false) - 1;
+ if (charIndex < 0) {
+ intf.draw3d(true);
+ return;
+ }
+
+ // There is a 1 in 4 chance the character will receive damage
+ if (_vm->getRandomNumber(1, 4) == 1) {
+ combat.giveCharDamage(map.mazeData()._trapDamage,
+ (DamageType)_vm->getRandomNumber(0, 6), charIndex);
+ }
+
+ // Check whether character can unlock the door
+ Character &c = party._activeParty[charIndex];
+ if ((c.getThievery() + _vm->getRandomNumber(1, 20)) <
+ map.mazeData()._difficulties._unlockDoor)
+ return;
+
+ c._experience += map.mazeData()._difficulties._unlockDoor * c.getCurrentLevel();
+ }
+
+ // Flag the grate as unlocked, and the wall the grate is on
+ map.setCellSurfaceFlags(party._mazePosition, 0x80);
+ map.setWall(party._mazePosition, party._mazeDirection, wallVal);
+
+ // Set the grate as opened and the wall on the other side of the grate
+ Common::Point pt = party._mazePosition;
+ Direction dir = (Direction)((int)party._mazeDirection ^ 2);
+ switch (party._mazeDirection) {
+ case DIR_NORTH:
+ pt.y++;
+ break;
+ case DIR_EAST:
+ pt.x++;
+ break;
+ case DIR_SOUTH:
+ pt.y--;
+ break;
+ case DIR_WEST:
+ pt.x--;
+ break;
+ default:
+ break;
+ }
+
+ map.setCellSurfaceFlags(pt, 0x80);
+ map.setWall(pt, dir, wallVal);
+
+ sound.playFX(10);
+ intf.draw3d(true);
+ }
+}
+
+typedef void(Scripts::*ScriptMethodPtr)(Common::Array<byte> &);
+
+void Scripts::doOpcode(MazeEvent &event) {
+ static const ScriptMethodPtr COMMAND_LIST[] = {
+ nullptr, &Scripts::cmdDisplay1, &Scripts::cmdDoorTextSml,
+ &Scripts::cmdDoorTextLrg, &Scripts::cmdSignText,
+ &Scripts::cmdNPC, &Scripts::cmdPlayFX, &Scripts::cmdTeleport,
+ &Scripts::cmdIf, &Scripts::cmdIf, &Scripts::cmdIf,
+ &Scripts::cmdMoveObj, &Scripts::cmdTakeOrGive, &Scripts::cmdNoAction,
+ &Scripts::cmdRemove, &Scripts::cmdSetChar, &Scripts::cmdSpawn,
+ &Scripts::cmdDoTownEvent, &Scripts::cmdExit, &Scripts::cmdAlterMap,
+ &Scripts::cmdGiveExtended, &Scripts::cmdConfirmWord, &Scripts::cmdDamage,
+ &Scripts::cmdJumpRnd, &Scripts::cmdAlterEvent, &Scripts::cmdCallEvent,
+ &Scripts::cmdReturn, &Scripts::cmdSetVar, &Scripts::cmdTakeOrGive,
+ &Scripts::cmdTakeOrGive, &Scripts::cmdCutsceneEndClouds,
+ &Scripts::cmdTeleport, &Scripts::cmdWhoWill,
+ &Scripts::cmdRndDamage, &Scripts::cmdMoveWallObj, &Scripts::cmdAlterCellFlag,
+ &Scripts::cmdAlterHed, &Scripts::cmdDisplayStat, &Scripts::cmdTakeOrGive,
+ &Scripts::cmdSeatTextSml, &Scripts::cmdPlayEventVoc, &Scripts::cmdDisplayBottom,
+ &Scripts::cmdIfMapFlag, &Scripts::cmdSelRndChar, &Scripts::cmdGiveEnchanted,
+ &Scripts::cmdItemType, &Scripts::cmdMakeNothingHere, &Scripts::cmdCheckProtection,
+ &Scripts::cmdChooseNumeric, &Scripts::cmdDisplayBottomTwoLines,
+ &Scripts::cmdDisplayLarge, &Scripts::cmdExchObj, &Scripts::cmdFallToMap,
+ &Scripts::cmdDisplayMain, &Scripts::cmdGoto, &Scripts::cmdConfirmWord,
+ &Scripts::cmdGotoRandom, &Scripts::cmdCutsceneEndDarkside,
+ &Scripts::cmdCutsceneEdWorld, &Scripts::cmdFlipWorld, &Scripts::cmdPlayCD
+ };
+
+ _event = &event;
+ (this->*COMMAND_LIST[event._opcode])(event._parameters);
+}
+
+void Scripts::cmdDisplay1(Common::Array<byte> &params) {
+ Screen &screen = *_vm->_screen;
+ Common::String paramText = _vm->_map->_events._text[_event->_parameters[0]];
+ Common::String msg = Common::String::format("\r\x03""c%s", paramText.c_str());
+
+ screen._windows[12].close();
+ if (screen._windows[38]._enabled)
+ screen._windows[38].open();
+ screen._windows[38].writeString(msg);
+ screen._windows[38].update();
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDoorTextSml(Common::Array<byte> &params) {
+ Interface &intf = *_vm->_interface;
+
+ Common::String paramText = _vm->_map->_events._text[_event->_parameters[0]];
+ intf._screenText = Common::String::format("\x02\f""08\x03""c\t116\v025%s\x03""l\fd""\x01",
+ paramText.c_str());
+ intf._upDoorText = true;
+ intf.draw3d(true);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDoorTextLrg(Common::Array<byte> &params) {
+ Interface &intf = *_vm->_interface;
+
+ Common::String paramText = _vm->_map->_events._text[_event->_parameters[0]];
+ intf._screenText = Common::String::format("\f04\x03""c\t116\v030%s\x03""l\fd",
+ paramText.c_str());
+ intf._upDoorText = true;
+ intf.draw3d(true);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdSignText(Common::Array<byte> &params) {
+ Interface &intf = *_vm->_interface;
+
+ Common::String paramText = _vm->_map->_events._text[_event->_parameters[0]];
+ intf._screenText = Common::String::format("\f08\x03""c\t120\v088%s\x03""l\fd",
+ paramText.c_str());
+ intf._upDoorText = true;
+ intf.draw3d(true);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdNPC(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+
+ if (TownMessage::show(_vm, params[2], _message, map._events._text[params[1]],
+ params[3]))
+ _lineNum = params[4] - 1;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdPlayFX(Common::Array<byte> &params) {
+ _vm->_sound->playFX(params[0]);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdTeleport(Common::Array<byte> &params) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+
+ screen.closeWindows();
+
+ int mapId;
+ Common::Point pt;
+
+ if (params[0]) {
+ mapId = params[0];
+ pt = Common::Point((int8)params[1], (int8)params[2]);
+ } else {
+ assert(_mirrorId > 0);
+ MirrorEntry &me = _mirror[_mirrorId - 1];
+ mapId = me._mapId;
+ pt = me._position;
+ if (me._direction != -1)
+ party._mazeDirection = (Direction)me._direction;
+
+ if (pt.x == 0 && pt.y == 0)
+ pt.x = 999;
+
+ sound.playFX(51);
+ }
+
+ party._stepped = true;
+ if (mapId != party._mazeId) {
+ int spriteId = (intf._objNumber == 0) ? -1 :
+ map._mobData._objects[intf._objNumber - 1]._spriteId;
+
+ switch (spriteId) {
+ case 47:
+ sound.playFX(45);
+ break;
+ case 48:
+ sound.playFX(44);
+ break;
+ default:
+ break;
+ }
+
+ // Load the new map
+ map.load(mapId);
+ }
+
+ if (pt.x == 999) {
+ party.moveToRunLocation();
+ } else {
+ party._mazePosition = pt;
+ }
+
+ events.clearEvents();
+
+ if (_event->_opcode == OP_TeleportAndContinue) {
+ intf.draw3d(true);
+ _lineNum = 0;
+ } else {
+ cmdExit(params);
+ }
+}
+
+void Scripts::cmdIf(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+ uint32 mask;
+ int newLineNum;
+
+ switch (params[0]) {
+ case 16:
+ case 34:
+ case 100:
+ mask = (params[4] << 24) | (params[3] << 16) | (params[2] << 8) | params[1];
+ newLineNum = params[5];
+ break;
+ case 25:
+ case 35:
+ case 101:
+ case 106:
+ mask = (params[2] << 8) | params[1];
+ newLineNum = params[3];
+ break;
+ default:
+ mask = params[1];
+ newLineNum = params[2];
+ break;
+ }
+
+ bool result;
+ if ((_charIndex != 0 && _charIndex != 8) || params[0] == 44) {
+ result = ifProc(params[0], mask, _event->_opcode - 8, _charIndex - 1);
+ } else {
+ result = false;
+ for (int idx = 0; idx < (int)party._activeParty.size() && !result; ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && (int)idx != _v2)) {
+ result = ifProc(params[0], mask, _event->_opcode - 8, idx);
+ }
+ }
+ }
+
+ if (result)
+ _lineNum = newLineNum - 1;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdMoveObj(Common::Array<byte> &params) {
+ MazeObject &mazeObj = _vm->_map->_mobData._objects[params[0]];
+
+ if (mazeObj._position.x == params[1] && mazeObj._position.y == params[2]) {
+ // Already in position, so simply flip it
+ mazeObj._flipped = !mazeObj._flipped;
+ } else {
+ mazeObj._position.x = params[1];
+ mazeObj._position.y = params[2];
+ }
+}
+
+void Scripts::cmdTakeOrGive(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ int mode1, mode2, mode3;
+ uint32 mask1, mask2, mask3;
+ byte *extraP;
+ // TODO: See if this needs to maintain values set in other opcodes
+ int param2 = 0;
+
+ mode1 = params[0];
+ switch (mode1) {
+ case 16:
+ case 34:
+ case 100:
+ mask1 = (params[4] << 24) | (params[3] << 16) | (params[2] << 8) | params[1];
+ extraP = &params[5];
+ break;
+ case 25:
+ case 35:
+ case 101:
+ case 106:
+ mask1 = (params[2] << 8) | params[1];
+ extraP = &params[3];
+ break;
+ default:
+ mask1 = params[1];
+ extraP = &params[2];
+ break;
+ }
+
+ mode2 = *extraP++;
+ switch (mode2) {
+ case 16:
+ case 34:
+ case 100:
+ mask2 = (extraP[3] << 24) | (extraP[2] << 16) | (extraP[1] << 8) | extraP[0];
+ extraP += 4;
+ break;
+ case 25:
+ case 35:
+ case 101:
+ case 106:
+ mask2 = (extraP[1] << 8) | extraP[0];
+ extraP += 2;
+ break;
+ default:
+ mask2 = extraP[0];
+ extraP++;
+ break;
+ }
+
+ mode3 = *extraP++;
+ switch (mode3) {
+ case 16:
+ case 34:
+ case 100:
+ mask3 = (extraP[3] << 24) | (extraP[2] << 16) | (extraP[1] << 8) | extraP[0];
+ break;
+ case 25:
+ case 35:
+ case 101:
+ case 106:
+ mask3 = (extraP[1] << 8) | extraP[0];
+ break;
+ default:
+ mask3 = extraP[0];
+ break;
+ }
+
+ if (mode2)
+ screen.closeWindows();
+
+ switch (_event->_opcode) {
+ case OP_TakeOrGive_2:
+ if (_charIndex == 0 || _charIndex == 8) {
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && (int)idx != _v2)) {
+ if (ifProc(params[0], mask1, _event->_opcode == OP_TakeOrGive_4 ? 2 : 1, idx)) {
+ party.giveTake(0, 0, mode2, mask2, idx);
+ if (mode2 == 82)
+ break;
+ }
+ }
+ }
+ } else if (ifProc(params[0], mask1, _event->_opcode == OP_TakeOrGive_4 ? 2 : 1, _charIndex - 1)) {
+ party.giveTake(0, 0, mode2, mask2, _charIndex - 1);
+ }
+ break;
+
+ case OP_TakeOrGive_3:
+ if (_charIndex == 0 || _charIndex == 8) {
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && (int)idx != _v2)) {
+ if (ifProc(params[0], mask1, 1, idx) && ifProc(mode2, mask2, 1, idx)) {
+ party.giveTake(0, 0, mode2, mask3, idx);
+ if (mode2 == 82)
+ break;
+ }
+ }
+ }
+ } else if (ifProc(params[0], mask1, 1, _charIndex - 1) &&
+ ifProc(mode2, mask2, 1, _charIndex - 1)) {
+ party.giveTake(0, 0, mode2, mask3, _charIndex - 1);
+ }
+ break;
+
+ case OP_TakeOrGive_4:
+ if (_charIndex == 0 || _charIndex == 8) {
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && (int)idx != _v2)) {
+ if (ifProc(params[0], mask1, _event->_opcode == OP_TakeOrGive_4 ? 2 : 1, idx)) {
+ party.giveTake(0, 0, mode2, mask2, idx);
+ if (mode2 == 82)
+ break;
+ }
+ }
+ }
+ } else if (ifProc(params[0], mask1, 1, _charIndex - 1)) {
+ party.giveTake(0, 0, mode2, mask2, _charIndex - 1);
+ }
+ break;
+
+ default:
+ if (_charIndex == 0 || _charIndex == 8) {
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && (int)idx != _v2)) {
+ party.giveTake(mode1, mask1, mode1, mask2, idx);
+
+ switch (mode1) {
+ case 8:
+ mode1 = 0;
+ // Deliberate fall-through
+ case 21:
+ case 66:
+ if (param2) {
+ switch (mode2) {
+ case 82:
+ mode1 = 0;
+ // Deliberate fall-through
+ case 21:
+ case 34:
+ case 35:
+ case 65:
+ case 66:
+ case 100:
+ case 101:
+ case 106:
+ if (param2)
+ continue;
+
+ // Break out of character loop
+ idx = party._activeParty.size();
+ break;
+ }
+ }
+ break;
+
+ case 34:
+ case 35:
+ case 65:
+ case 100:
+ case 101:
+ case 106:
+ if (param2) {
+ _lineNum = -1;
+ return;
+ }
+
+ // Break out of character loop
+ idx = party._activeParty.size();
+ break;
+
+ default:
+ switch (mode2) {
+ case 82:
+ mode1 = 0;
+ // Deliberate fall-through
+ case 21:
+ case 34:
+ case 35:
+ case 65:
+ case 66:
+ case 100:
+ case 101:
+ case 106:
+ if (param2)
+ continue;
+
+ // Break out of character loop
+ idx = party._activeParty.size();
+ break;
+ }
+ break;
+ }
+ }
+ }
+ } else {
+ if (!party.giveTake(mode1, mask1, mode2, mask2, _charIndex - 1)) {
+ if (mode2 == 79)
+ screen.closeWindows();
+ }
+ }
+ break;
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdNoAction(Common::Array<byte> &params) {
+ // Move to next line
+ _lineNum = _vm->_party->_partyDead ? -1 : _lineNum + 1;
+}
+
+void Scripts::cmdRemove(Common::Array<byte> &params) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+
+ if (intf._objNumber) {
+ // Give the active object a completely way out of bounds position
+ MazeObject &obj = map._mobData._objects[intf._objNumber - 1];
+ obj._position = Common::Point(128, 128);
+ }
+
+ cmdMakeNothingHere(params);
+}
+
+void Scripts::cmdSetChar(Common::Array<byte> &params) {
+ if (params[0] != 7) {
+ _charIndex = WhoWill::show(_vm, 22, 3, false);
+ if (_charIndex == 0) {
+ cmdExit(params);
+ return;
+ }
+ } else {
+ _charIndex = _vm->getRandomNumber(1, _vm->_party->_activeParty.size());
+ }
+
+ _v2 = 1;
+ cmdNoAction(params);
+}
+
+void Scripts::cmdSpawn(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ if (params[0] >= map._mobData._monsters.size())
+ map._mobData._monsters.resize(params[0] + 1);
+
+ MazeMonster &monster = _vm->_map->_mobData._monsters[params[0]];
+ MonsterStruct &monsterData = _vm->_map->_monsterData[monster._spriteId];
+ monster._monsterData = &monsterData;
+ monster._position.x = params[1];
+ monster._position.y = params[2];
+ monster._frame = _vm->getRandomNumber(7);
+ monster._damageType = 0;
+ monster._isAttacking = params[1] != 0;
+ monster._hp = monsterData._hp;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDoTownEvent(Common::Array<byte> &params) {
+ _scriptResult = _vm->_town->townAction(params[0]);
+ _vm->_party->_stepped = true;
+ _refreshIcons = true;
+
+ cmdExit(params);
+}
+
+void Scripts::cmdExit(Common::Array<byte> &params) {
+ _lineNum = -1;
+}
+
+void Scripts::cmdAlterMap(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+
+ if (params[2] == DIR_ALL) {
+ for (int dir = DIR_NORTH; dir <= DIR_WEST; ++dir)
+ map.setWall(Common::Point(params[0], params[1]), (Direction)dir, params[3]);
+ } else {
+ map.setWall(Common::Point(params[0], params[1]), (Direction)params[2], params[3]);
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdGiveExtended(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+ uint32 mask;
+ int newLineNum;
+ bool result;
+
+ switch (params[0]) {
+ case 16:
+ case 34:
+ case 100:
+ mask = (params[4] << 24) | params[3] | (params[2] << 8) | (params[1] << 16);
+ newLineNum = params[5];
+ break;
+ case 25:
+ case 35:
+ case 101:
+ case 106:
+ mask = (params[2] << 8) | params[1];
+ newLineNum = params[3];
+ break;
+ default:
+ mask = params[1];
+ newLineNum = params[2];
+ break;
+ }
+
+ if ((_charIndex != 0 && _charIndex != 8) || params[0] == 44) {
+ result = ifProc(params[0], mask, _event->_opcode - OP_If1, _charIndex - 1);
+ } else {
+ result = false;
+ for (int idx = 0; idx < (int)party._activeParty.size() && !result; ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && _v2 != idx)) {
+ result = ifProc(params[0], mask, _event->_opcode - OP_If1, idx);
+ }
+ }
+ }
+
+ if (result)
+ _lineNum = newLineNum - 1;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdConfirmWord(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Common::String msg1 = params[2] ? map._events._text[params[2]] :
+ _vm->_interface->_interfaceText;
+ Common::String msg2;
+
+ if (_event->_opcode == OP_ConfirmWord_2) {
+ msg2 = map._events._text[params[3]];
+ } else if (params[3]) {
+ msg2 = "";
+ } else {
+ msg2 = WHATS_THE_PASSWORD;
+ }
+
+ int result = StringInput::show(_vm, params[0], msg1, msg2,_event->_opcode);
+ if (result) {
+ if (result == 33 && _vm->_files->_isDarkCc) {
+ doEndGame2();
+ } else if (result == 34 && _vm->_files->_isDarkCc) {
+ doWorldEnd();
+ } else if (result == 35 && _vm->_files->_isDarkCc &&
+ _vm->getGameID() == GType_WorldOfXeen) {
+ doEndGame();
+ } else if (result == 40 && !_vm->_files->_isDarkCc) {
+ doEndGame();
+ } else if (result == 60 && !_vm->_files->_isDarkCc) {
+ doEndGame2();
+ }
+ else if (result == 61 && !_vm->_files->_isDarkCc) {
+ doWorldEnd();
+ } else {
+ if (result == 59 && !_vm->_files->_isDarkCc) {
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ XeenItem &item = party._treasure._weapons[idx];
+ if (!item._id) {
+ item._id = 34;
+ item._material = 0;
+ item._bonusFlags = 0;
+ party._treasure._hasItems = true;
+
+ cmdExit(params);
+ return;
+ }
+ }
+ }
+
+ _lineNum = result == -1 ? params[3] : params[1];
+
+ return;
+ }
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDamage(Common::Array<byte> &params) {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+
+ if (!_redrawDone) {
+ intf.draw3d(true);
+ _redrawDone = true;
+ }
+
+ int damage = (params[1] << 8) | params[0];
+ combat.giveCharDamage(damage, (DamageType)params[2], _charIndex);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdJumpRnd(Common::Array<byte> &params) {
+ int v = _vm->getRandomNumber(1, params[0]);
+ if (v == params[1])
+ _lineNum = params[2] - 1;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdAlterEvent(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+
+ for (uint idx = 0; idx < map._events.size(); ++idx) {
+ MazeEvent &evt = map._events[idx];
+ if (evt._position == party._mazePosition &&
+ (evt._direction == DIR_ALL || evt._direction == party._mazeDirection) &&
+ evt._line == params[0]) {
+ evt._opcode = (Opcode)params[1];
+ }
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdCallEvent(Common::Array<byte> &params) {
+ _stack.push(StackEntry(_currentPos, _lineNum));
+ _currentPos = Common::Point(params[0], params[1]);
+ _lineNum = params[2] - 1;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdReturn(Common::Array<byte> &params) {
+ StackEntry &se = _stack.top();
+ _currentPos = se;
+ _lineNum = se.line;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdSetVar(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+ uint val;
+ _refreshIcons = true;
+
+ switch (params[0]) {
+ case 25:
+ case 35:
+ case 101:
+ case 106:
+ val = (params[2] << 8) | params[1];
+ break;
+ case 16:
+ case 34:
+ case 100:
+ val = (params[4] << 24) | (params[3] << 16) | (params[2] << 8) | params[3];
+ break;
+ default:
+ val = params[1];
+ break;
+ }
+
+ if (_charIndex != 0 && _charIndex != 8) {
+ party._activeParty[_charIndex - 1].setValue(params[0], val);
+ } else {
+ // Set value for entire party
+ for (int idx = 0; idx < (int)party._activeParty.size(); ++idx) {
+ if (_charIndex == 0 || (_charIndex == 8 && _v2 != idx)) {
+ party._activeParty[idx].setValue(params[0], val);
+ }
+ }
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdCutsceneEndClouds(Common::Array<byte> &params) { error("TODO"); }
+
+void Scripts::cmdWhoWill(Common::Array<byte> &params) {
+ _charIndex = WhoWill::show(_vm, params[0], params[1], true);
+
+ if (_charIndex == 0)
+ cmdExit(params);
+ else
+ cmdNoAction(params);
+}
+
+void Scripts::cmdRndDamage(Common::Array<byte> &params) {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+
+ if (!_redrawDone) {
+ intf.draw3d(true);
+ _redrawDone = true;
+ }
+
+ combat.giveCharDamage(_vm->getRandomNumber(1, params[1]), (DamageType)params[0], _charIndex);
+ cmdNoAction(params);
+}
+
+void Scripts::cmdMoveWallObj(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+
+ map._mobData._wallItems[params[0]]._position = Common::Point(params[1], params[2]);
+ cmdNoAction(params);
+}
+
+void Scripts::cmdAlterCellFlag(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ Common::Point pt(params[0], params[1]);
+ map.cellFlagLookup(pt);
+
+ if (map._isOutdoors) {
+ MazeWallLayers &wallData = map.mazeDataCurrent()._wallData[pt.y][pt.x];
+ wallData._data = (wallData._data & 0xFFF0) | params[2];
+ } else {
+ pt.x &= 0xF;
+ pt.y &= 0xF;
+ MazeCell &cell = map.mazeDataCurrent()._cells[pt.y][pt.x];
+ cell._surfaceId = params[2];
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdAlterHed(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+
+ HeadData::HeadEntry &he = map._headData[party._mazePosition.y][party._mazePosition.x];
+ he._left = params[0];
+ he._right = params[1];
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDisplayStat(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+ Window &w = _vm->_screen->_windows[12];
+ Character &c = party._activeParty[_charIndex - 1];
+
+ if (!w._enabled)
+ w.open();
+ w.writeString(Common::String::format(_message.c_str(), c._name.c_str()));
+ w.update();
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdSeatTextSml(Common::Array<byte> &params) {
+ Interface &intf = *_vm->_interface;
+
+ intf._screenText = Common::String::format("\x2\f08\x3""c\t116\v090%s\x3l\fd\x1",
+ _message.c_str());
+ intf._upDoorText = true;
+ intf.draw3d(true);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdPlayEventVoc(Common::Array<byte> &params) {
+ SoundManager &sound = *_vm->_sound;
+ sound.playSample(nullptr, 0);
+ File f(EVENT_SAMPLES[params[0]]);
+ sound.playSample(&f, 1);
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDisplayBottom(Common::Array<byte> &params) {
+ _windowIndex = 12;
+
+ display(false, 0);
+ cmdNoAction(params);
+}
+
+void Scripts::cmdIfMapFlag(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ MazeMonster &monster = map._mobData._monsters[params[0]];
+
+ if (monster._position.x >= 32 || monster._position.y >= 32) {
+ _lineNum = params[1] - 1;
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdSelRndChar(Common::Array<byte> &params) {
+ _charIndex = _vm->getRandomNumber(1, _vm->_party->_activeParty.size());
+ cmdNoAction(params);
+}
+
+void Scripts::cmdGiveEnchanted(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+
+ if (params[0] >= 35) {
+ if (params[0] < 49) {
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ XeenItem &item = party._treasure._armor[idx];
+ if (!item.empty()) {
+ item._id = params[0] - 35;
+ item._material = params[1];
+ item._bonusFlags = params[2];
+ party._treasure._hasItems = true;
+ break;
+ }
+ }
+
+ cmdNoAction(params);
+ return;
+ } else if (params[0] < 60) {
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ XeenItem &item = party._treasure._accessories[idx];
+ if (!item.empty()) {
+ item._id = params[0] - 49;
+ item._material = params[1];
+ item._bonusFlags = params[2];
+ party._treasure._hasItems = true;
+ break;
+ }
+ }
+
+ cmdNoAction(params);
+ return;
+ } else if (params[0] < 82) {
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ XeenItem &item = party._treasure._misc[idx];
+ if (!item.empty()) {
+ item._id = params[0];
+ item._material = params[1];
+ item._bonusFlags = params[2];
+ party._treasure._hasItems = true;
+ break;
+ }
+ }
+
+ cmdNoAction(params);
+ return;
+ } else {
+ party._gameFlags[6 + params[0]] = true;
+ }
+ }
+
+ for (int idx = 0; idx < MAX_TREASURE_ITEMS; ++idx) {
+ XeenItem &item = party._treasure._weapons[idx];
+ if (!item.empty()) {
+ item._id = params[0];
+ item._material = params[1];
+ item._bonusFlags = params[2];
+ party._treasure._hasItems = true;
+ break;
+ }
+ }
+}
+
+void Scripts::cmdItemType(Common::Array<byte> &params) {
+ _itemType = params[0];
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdMakeNothingHere(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+
+ // Scan through the event list and mark the opcodes for all the lines of any scripts
+ // on the party's current cell as having no operation, effectively disabling them
+ for (uint idx = 0; idx < map._events.size(); ++idx) {
+ MazeEvent &evt = map._events[idx];
+ if (evt._position == party._mazePosition)
+ evt._opcode = OP_None;
+ }
+
+ cmdExit(params);
+}
+
+void Scripts::cmdCheckProtection(Common::Array<byte> &params) {
+ if (copyProtectionCheck())
+ cmdNoAction(params);
+ else
+ cmdExit(params);
+}
+
+void Scripts::cmdChooseNumeric(Common::Array<byte> &params) {
+ int choice = Choose123::show(_vm, params[0]);
+ if (choice) {
+ _lineNum = params[choice] - 1;
+ }
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdDisplayBottomTwoLines(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ Window &w = _vm->_screen->_windows[12];
+
+ warning("TODO: cmdDisplayBottomTwoLines");
+ Common::String msg = Common::String::format("\r\x03c\t000\v007%s\n\n%s",
+ "",
+ map._events._text[params[1]].c_str());
+ w.close();
+ w.open();
+ w.writeString(msg);
+ w.update();
+
+ YesNo::show(_vm, true);
+ _lineNum = -1;
+}
+
+void Scripts::cmdDisplayLarge(Common::Array<byte> &params) {
+ error("TODO: Implement event text loading");
+
+ display(true, 0);
+ cmdNoAction(params);
+}
+
+void Scripts::cmdExchObj(Common::Array<byte> &params) {
+ MazeObject &obj1 = _vm->_map->_mobData._objects[params[0]];
+ MazeObject &obj2 = _vm->_map->_mobData._objects[params[1]];
+
+ Common::Point pt = obj1._position;
+ obj1._position = obj2._position;
+ obj2._position = pt;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdFallToMap(Common::Array<byte> &params) {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ party._fallMaze = params[0];
+ party._fallPosition = Common::Point(params[1], params[2]);
+ party._fallDamage = params[3];
+ intf.startFalling(true);
+
+ _lineNum = -1;
+}
+
+void Scripts::cmdDisplayMain(Common::Array<byte> &params) {
+ display(false, 0);
+ cmdNoAction(params);
+}
+
+void Scripts::cmdGoto(Common::Array<byte> &params) {
+ Map &map = *_vm->_map;
+ map.getCell(1);
+ if (params[0] == map._currentSurfaceId)
+ _lineNum = params[1] - 1;
+
+ cmdNoAction(params);
+}
+
+void Scripts::cmdGotoRandom(Common::Array<byte> &params) {
+ _lineNum = params[_vm->getRandomNumber(1, params[0])] - 1;
+ cmdNoAction(params);
+}
+
+void Scripts::cmdCutsceneEndDarkside(Common::Array<byte> &params) {
+ Party &party = *_vm->_party;
+ _vm->_saves->_wonDarkSide = true;
+ party._questItems[53] = 1;
+ party._darkSideEnd = true;
+ party._mazeId = 29;
+ party._mazeDirection = DIR_NORTH;
+ party._mazePosition = Common::Point(25, 21);
+
+ doEndGame2();
+}
+
+void Scripts::cmdCutsceneEdWorld(Common::Array<byte> &params) {
+ _vm->_saves->_wonWorld = true;
+ _vm->_party->_worldEnd = true;
+ doWorldEnd();
+}
+
+void Scripts::cmdFlipWorld(Common::Array<byte> &params) {
+ _vm->_map->_loadDarkSide = params[0] != 0;
+}
+
+void Scripts::cmdPlayCD(Common::Array<byte> &params) { error("TODO"); }
+
+void Scripts::doEndGame() {
+ doEnding("ENDGAME", 0);
+}
+
+void Scripts::doEndGame2() {
+ Party &party = *_vm->_party;
+ int v2 = 0;
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ Character &player = party._activeParty[idx];
+ if (player.hasAward(77)) {
+ v2 = 2;
+ break;
+ }
+ else if (player.hasAward(76)) {
+ v2 = 1;
+ break;
+ }
+ }
+
+ doEnding("ENDGAME2", v2);
+}
+
+void Scripts::doWorldEnd() {
+
+}
+
+void Scripts::doEnding(const Common::String &endStr, int v2) {
+ _vm->_saves->saveChars();
+
+ warning("TODO: doEnding");
+}
+
+bool Scripts::ifProc(int action, uint32 mask, int mode, int charIndex) {
+ Party &party = *_vm->_party;
+ Character &ps = party._activeParty[charIndex];
+ uint v = 0;
+
+ switch (action) {
+ case 3:
+ // Player sex
+ v = (uint)ps._sex;
+ break;
+ case 4:
+ // Player race
+ v = (uint)ps._race;
+ break;
+ case 5:
+ // Player class
+ v = (uint)ps._class;
+ break;
+ case 8:
+ // Current health points
+ v = (uint)ps._currentHp;
+ break;
+ case 9:
+ // Current spell points
+ v = (uint)ps._currentSp;
+ break;
+ case 10:
+ // Get armor class
+ v = (uint)ps.getArmorClass(false);
+ break;
+ case 11:
+ // Level bonus (extra beyond base)
+ v = ps._level._temporary;
+ break;
+ case 12:
+ // Current age, including unnatural aging
+ v = ps.getAge(false);
+ break;
+ case 13:
+ assert(mask < 18);
+ if (ps._skills[mask])
+ v = mask;
+ break;
+ case 15:
+ // Award
+ assert(mask < 128);
+ if (ps.hasAward(mask))
+ v = mask;
+ break;
+ case 16:
+ // Experience
+ v = ps._experience;
+ break;
+ case 17:
+ // Party poison resistence
+ v = party._poisonResistence;
+ break;
+ case 18:
+ // Condition
+ assert(mask < 16);
+ if (!ps._conditions[mask] && !(mask & 0x10))
+ v = mask;
+ break;
+ case 19: {
+ // Can player cast a given spell
+
+ // Get the type of character
+ int category;
+ switch (ps._class) {
+ case CLASS_KNIGHT:
+ case CLASS_ARCHER:
+ category = 0;
+ break;
+ case CLASS_PALADIN:
+ case CLASS_CLERIC:
+ category = 1;
+ break;
+ case CLASS_BARBARIAN:
+ case CLASS_DRUID:
+ category = 2;
+ break;
+ default:
+ category = 0;
+ break;
+ }
+
+ // Check if the character class can cast the particular spell
+ for (int idx = 0; idx < 39; ++idx) {
+ if (SPELLS_ALLOWED[category][idx] == mask) {
+ // Can cast it. Check if the player has it in their spellbook
+ if (ps._spells[idx])
+ v = mask;
+ break;
+ }
+ }
+ break;
+ }
+ case 20:
+ if (_vm->_files->_isDarkCc)
+ mask += 0x100;
+ assert(mask < 0x200);
+ v = party._gameFlags[mask] ? mask : 0xffffffff;
+ break;
+ case 21:
+ // Scans inventories for given item number
+ v = 0xFFFFFFFF;
+ if (mask < 82) {
+ for (int idx = 0; idx < 9; ++idx) {
+ if (mask == 35) {
+ if (ps._weapons[idx]._id == mask) {
+ v = mask;
+ break;
+ }
+ } else if (mask < 49) {
+ if (ps._armor[idx]._id == (mask - 35)) {
+ v = mask;
+ break;
+ }
+ } else if (mask < 60) {
+ if (ps._accessories[idx]._id == (mask - 49)) {
+ v = mask;
+ break;
+ }
+ } else {
+ if (ps._misc[idx]._id == (mask - 60)) {
+ v = mask;
+ break;
+ }
+ }
+ }
+ } else {
+ int baseFlag = 8 * (6 + mask);
+ for (int idx = 0; idx < 8; ++idx) {
+ if (party._gameFlags[baseFlag + idx]) {
+ v = mask;
+ break;
+ }
+ }
+ }
+ break;
+ case 25:
+ // Returns number of minutes elapsed in the day (0-1440)
+ v = party._minutes;
+ break;
+ case 34:
+ // Current party gold
+ v = party._gold;
+ break;
+ case 35:
+ // Current party gems
+ v = party._gems;
+ break;
+ case 37:
+ // Might bonus (extra beond base)
+ v = ps._might._temporary;
+ break;
+ case 38:
+ // Intellect bonus (extra beyond base)
+ v = ps._intellect._temporary;
+ break;
+ case 39:
+ // Personality bonus (extra beyond base)
+ v = ps._personality._temporary;
+ break;
+ case 40:
+ // Endurance bonus (extra beyond base)
+ v = ps._endurance._temporary;
+ break;
+ case 41:
+ // Speed bonus (extra beyond base)
+ v = ps._speed._temporary;
+ break;
+ case 42:
+ // Accuracy bonus (extra beyond base)
+ v = ps._accuracy._temporary;
+ break;
+ case 43:
+ // Luck bonus (extra beyond base)
+ v = ps._luck._temporary;
+ break;
+ case 44:
+ v = YesNo::show(_vm, mask);
+ v = (!v && !mask) ? 2 : mask;
+ break;
+ case 45:
+ // Might base (before bonus)
+ v = ps._might._permanent;
+ break;
+ case 46:
+ // Intellect base (before bonus)
+ v = ps._intellect._permanent;
+ break;
+ case 47:
+ // Personality base (before bonus)
+ v = ps._personality._permanent;
+ break;
+ case 48:
+ // Endurance base (before bonus)
+ v = ps._endurance._permanent;
+ break;
+ case 49:
+ // Speed base (before bonus)
+ v = ps._speed._permanent;
+ break;
+ case 50:
+ // Accuracy base (before bonus)
+ v = ps._accuracy._permanent;
+ break;
+ case 51:
+ // Luck base (before bonus)
+ v = ps._luck._permanent;
+ break;
+ case 52:
+ // Fire resistence (before bonus)
+ v = ps._fireResistence._permanent;
+ break;
+ case 53:
+ // Elecricity resistence (before bonus)
+ v = ps._electricityResistence._permanent;
+ break;
+ case 54:
+ // Cold resistence (before bonus)
+ v = ps._coldResistence._permanent;
+ break;
+ case 55:
+ // Poison resistence (before bonus)
+ v = ps._poisonResistence._permanent;
+ break;
+ case 56:
+ // Energy reistence (before bonus)
+ v = ps._energyResistence._permanent;
+ break;
+ case 57:
+ // Energy resistence (before bonus)
+ v = ps._magicResistence._permanent;
+ break;
+ case 58:
+ // Fire resistence (extra beyond base)
+ v = ps._fireResistence._temporary;
+ break;
+ case 59:
+ // Electricity resistence (extra beyond base)
+ v = ps._electricityResistence._temporary;
+ break;
+ case 60:
+ // Cold resistence (extra beyond base)
+ v = ps._coldResistence._temporary;
+ break;
+ case 61:
+ // Poison resistence (extra beyod base)
+ v = ps._poisonResistence._temporary;
+ break;
+ case 62:
+ // Energy resistence (extra beyond base)
+ v = ps._energyResistence._temporary;
+ break;
+ case 63:
+ // Magic resistence (extra beyond base)
+ v = ps._magicResistence._temporary;
+ break;
+ case 64:
+ // Level (before bonus)
+ v = ps._level._permanent;
+ break;
+ case 65:
+ // Total party food
+ v = party._food;
+ break;
+ case 69:
+ // Test for Levitate being active
+ v = party._levitateActive ? 1 : 0;
+ break;
+ case 70:
+ // Amount of light
+ v = party._lightCount;
+ break;
+ case 71:
+ // Party magical fire resistence
+ v = party._fireResistence;
+ break;
+ case 72:
+ // Party magical electricity resistence
+ v = party._electricityResistence;
+ break;
+ case 73:
+ // Party magical cold resistence
+ v = party._coldResistence;
+ break;
+ case 76:
+ // Day of the year (100 per year)
+ v = party._day;
+ break;
+ case 77:
+ // Armor class (extra beyond base)
+ v = ps._ACTemp;
+ break;
+ case 78:
+ // Test whether current Hp is equal to or exceeds the max HP
+ v = ps._currentHp >= ps.getMaxHP() ? 1 : 0;
+ break;
+ case 79:
+ // Test for Wizard Eye being active
+ v = party._wizardEyeActive ? 1 : 0;
+ break;
+ case 81:
+ // Test whether current Sp is equal to or exceeds the max SP
+ v = ps._currentSp >= ps.getMaxSP() ? 1 : 0;
+ break;
+ case 84:
+ // Current facing direction
+ v = (uint)party._mazeDirection;
+ break;
+ case 85:
+ // Current game year since start
+ v = party._year;
+ break;
+ case 86:
+ case 87:
+ case 88:
+ case 89:
+ case 90:
+ case 91:
+ case 92:
+ // Get a player stat
+ v = ps.getStat((Attribute)(action - 86), 0);
+ break;
+ case 93:
+ // Current day of the week (10 days per week)
+ v = party._day / 10;
+ break;
+ case 94:
+ // Test whether Walk on Water is currently active
+ v = party._walkOnWaterActive ? 1 : 0;
+ break;
+ case 99:
+ // Party skills check
+ v = party.checkSkill((Skill)mask) ? mask : 0xffffffff;
+ break;
+ case 102:
+ // Thievery skill
+ v = ps.getThievery();
+ break;
+ case 103:
+ // Get value of world flag
+ v = party._worldFlags[mask] ? mask : 0xffffffff;
+ break;
+ case 104:
+ // Get value of quest flag
+ v = party._quests[mask + (_vm->_files->_isDarkCc ? 30 : 0)] ?
+ mask : 0xffffffff;
+ break;
+ case 105:
+ // Test number of Megacredits in party. Only used by King's Engineer in Castle Burlock
+ v = party._questItems[26];
+ break;
+ case 107:
+ // Get value of character flag
+ error("Unused");
+ break;
+ default:
+ break;
+ }
+
+ switch (mode) {
+ case 0:
+ return mask >= v;
+ case 1:
+ return mask == v;
+ case 2:
+ return mask <= v;
+ default:
+ return false;
+ }
+}
+
+bool Scripts::copyProtectionCheck() {
+ // Only bother doing the protection check if it's been explicitly turned on
+ if (!ConfMan.getBool("copy_protection"))
+ return true;
+
+ // Currently not implemented
+ return true;
+}
+
+void Scripts::display(bool justifyFlag, int var46) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ Window &w = screen._windows[_windowIndex];
+
+ if (!_redrawDone) {
+ intf.draw3d(true);
+ _redrawDone = true;
+ }
+ screen._windows[38].close();
+
+ if (!justifyFlag)
+ _displayMessage = Common::String::format("\r\x3""c%s", _message.c_str());
+
+ if (!w._enabled)
+ w.open();
+
+ while (!_vm->shouldQuit()) {
+ _displayMessage = w.writeString(_displayMessage);
+ w.update();
+ if (_displayMessage.empty())
+ break;
+ events.clearEvents();
+
+ do {
+ events.updateGameCounter();
+ intf.draw3d(true);
+
+ events.wait(1, true);
+ } while (!_vm->shouldQuit() && !events.isKeyMousePressed());
+
+ w.writeString(justifyFlag ? "\r" : "\r\x3""c");
+ }
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/scripts.h b/engines/xeen/scripts.h
new file mode 100644
index 0000000000..f41be1b7c6
--- /dev/null
+++ b/engines/xeen/scripts.h
@@ -0,0 +1,341 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_SCRIPTS_H
+#define XEEN_SCRIPTS_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/serializer.h"
+#include "common/stack.h"
+#include "common/str-array.h"
+#include "xeen/files.h"
+#include "xeen/party.h"
+
+namespace Xeen {
+
+enum Opcode {
+ OP_None = 0x00,
+ OP_Display0x01 = 0x01,
+ OP_DoorTextSml = 0x02,
+ OP_DoorTextLrg = 0x03,
+ OP_SignText = 0x04,
+ OP_NPC = 0x05,
+ OP_PlayFX = 0x06,
+ OP_TeleportAndExit = 0x07,
+ OP_If1 = 0x08,
+ OP_If2 = 0x09,
+ OP_If3 = 0x0A,
+ OP_MoveObj = 0x0B,
+ OP_TakeOrGive = 0x0C,
+ OP_NoAction = 0x0D,
+ OP_Remove = 0x0E,
+ OP_SetChar = 0x0F,
+ OP_Spawn = 0x10,
+ OP_DoTownEvent = 0x11,
+ OP_Exit = 0x12,
+ OP_AfterMap = 0x13,
+ OP_GiveExtended = 0x14,
+ OP_ConfirmWord = 0x15,
+ OP_Damage = 0x16,
+ OP_JumpRnd = 0x17,
+ OP_AfterEvent = 0x18,
+ OP_CallEvent = 0x19,
+ OP_Return = 0x1A,
+ OP_SetVar = 0x1B,
+ OP_TakeOrGive_2 = 0x1C,
+ OP_TakeOrGive_3 = 0x1D,
+ OP_CutsceneEndClouds = 0x1E,
+ OP_TeleportAndContinue = 0x1F,
+ OP_WhoWill = 0x20,
+ OP_RndDamage = 0x21,
+ OP_MoveWallObj = 0x22,
+ OP_AlterCellFlag= 0x23,
+ OP_AlterHed = 0x24,
+ OP_DisplayStat = 0x25,
+ OP_TakeOrGive_4 = 0x26,
+ OP_SeatTextSml = 0x27,
+ OP_PlayEventVoc = 0x28,
+ OP_DisplayBottom = 0x29,
+ OP_IfMapFlag = 0x2A,
+ OP_SelRndChar = 0x2B,
+ OP_GiveEnchanted= 0x2C,
+ OP_ItemType = 0x2D,
+ OP_MakeNothingHere = 0x2E,
+ OP_NoAction_2 = 0x2F,
+ OP_ChooseNumeric= 0x30,
+ OP_DisplayBottomTwoLines = 0x31,
+ OP_DisplayLarge = 0x32,
+ OP_ExchObj = 0x33,
+ OP_FallToMap = 0x34,
+ OP_DisplayMain = 0x35,
+ OP_Goto = 0x36,
+ OP_ConfirmWord_2= 0x37,
+ OP_GotoRandom = 0x38,
+ OP_CutsceneEndDarkside = 0x39,
+ OP_CutsceneEdWorld = 0x3A,
+ OP_FlipWorld = 0x3B,
+ OP_PlayCD = 0x3C
+};
+
+class XeenEngine;
+
+class MazeEvent {
+public:
+ Common::Point _position;
+ int _direction;
+ int _line;
+ Opcode _opcode;
+ Common::Array<byte> _parameters;
+public:
+ MazeEvent();
+
+ void synchronize(Common::Serializer &s);
+};
+
+class MazeEvents : public Common::Array<MazeEvent> {
+public:
+ Common::StringArray _text;
+public:
+ void synchronize(XeenSerializer &s);
+};
+
+struct StackEntry : public Common::Point {
+ int line;
+
+ StackEntry(const Common::Point &pt, int l) : Common::Point(pt), line(l) {}
+};
+
+struct MirrorEntry {
+ Common::String _name;
+ int _mapId;
+ Common::Point _position;
+ int _direction;
+
+ MirrorEntry() : _mapId(0), _direction(DIR_ALL) {}
+
+ bool synchronize(Common::SeekableReadStream &s);
+};
+
+class Scripts {
+private:
+ XeenEngine *_vm;
+ int _treasureItems;
+ int _lineNum;
+ int _charIndex;
+ int _mirrorId;
+ int _refreshIcons;
+ int _scriptResult;
+ bool _scriptExecuted;
+ bool _var50;
+ int _windowIndex;
+ bool _redrawDone;
+ MazeEvent *_event;
+ Common::Point _currentPos;
+ Common::Stack<StackEntry> _stack;
+ Common::String _message;
+ Common::String _displayMessage;
+
+ /**
+ * Handles executing a given script command
+ */
+ void doOpcode(MazeEvent &event);
+
+ /**
+ * Display a msesage on-screen
+ */
+ void cmdDisplay1(Common::Array<byte> &params);
+
+ /**
+ * Displays a door text message using the small font
+ */
+ void cmdDoorTextSml(Common::Array<byte> &params);
+
+ /**
+ * Displays a door text message using the large font
+ */
+ void cmdDoorTextLrg(Common::Array<byte> &params);
+
+ /**
+ * Show a sign text on-screen
+ */
+ void cmdSignText(Common::Array<byte> &params);
+ void cmdNPC(Common::Array<byte> &params);
+
+ /**
+ * Play a sound FX
+ */
+ void cmdPlayFX(Common::Array<byte> &params);
+ void cmdTeleport(Common::Array<byte> &params);
+
+ /**
+ * Do a conditional check
+ */
+ void cmdIf(Common::Array<byte> &params);
+
+ /**
+ * Moves the position of an object
+ */
+ void cmdMoveObj(Common::Array<byte> &params);
+ void cmdTakeOrGive(Common::Array<byte> &params);
+
+ /**
+ * Move to the next line of the script
+ */
+ void cmdNoAction(Common::Array<byte> &params);
+ void cmdRemove(Common::Array<byte> &params);
+
+ /**
+ * Set the currently active character for other script operations
+ */
+ void cmdSetChar(Common::Array<byte> &params);
+
+ /**
+ * Spawn a monster
+ */
+ void cmdSpawn(Common::Array<byte> &params);
+ void cmdDoTownEvent(Common::Array<byte> &params);
+
+ /**
+ * Stop executing the script
+ */
+ void cmdExit(Common::Array<byte> &params);
+
+ /**
+ * Changes the value for the wall on a given cell
+ */
+ void cmdAlterMap(Common::Array<byte> &params);
+ void cmdGiveExtended(Common::Array<byte> &params);
+ void cmdConfirmWord(Common::Array<byte> &params);
+ void cmdDamage(Common::Array<byte> &params);
+
+ /**
+ * Jump if a random number matches a given value
+ */
+ void cmdJumpRnd(Common::Array<byte> &params);
+
+ /**
+ * Alter an existing event
+ */
+ void cmdAlterEvent(Common::Array<byte> &params);
+
+ /**
+ * Stores the current location and line for later resuming, and set up to execute
+ * a script at a given location
+ */
+ void cmdCallEvent(Common::Array<byte> &params);
+
+ /**
+ * Return from executing a script to the script location that previously
+ * called the script
+ */
+ void cmdReturn(Common::Array<byte> &params);
+ void cmdSetVar(Common::Array<byte> &params);
+ void cmdCutsceneEndClouds(Common::Array<byte> &params);
+ void cmdWhoWill(Common::Array<byte> &params);
+ void cmdRndDamage(Common::Array<byte> &params);
+ void cmdMoveWallObj(Common::Array<byte> &params);
+ void cmdAlterCellFlag(Common::Array<byte> &params);
+ void cmdAlterHed(Common::Array<byte> &params);
+ void cmdDisplayStat(Common::Array<byte> &params);
+ void cmdSeatTextSml(Common::Array<byte> &params);
+ void cmdPlayEventVoc(Common::Array<byte> &params);
+ void cmdDisplayBottom(Common::Array<byte> &params);
+ void cmdIfMapFlag(Common::Array<byte> &params);
+ void cmdSelRndChar(Common::Array<byte> &params);
+ void cmdGiveEnchanted(Common::Array<byte> &params);
+ void cmdItemType(Common::Array<byte> &params);
+
+ /**
+ * Disable all the scripts at the party's current position
+ */
+ void cmdMakeNothingHere(Common::Array<byte> &params);
+ void cmdCheckProtection(Common::Array<byte> &params);
+
+ /**
+ * Given a number of options, and a list of line numbers associated with
+ * those options, jumps to whichever line for the option the user selects
+ */
+ void cmdChooseNumeric(Common::Array<byte> &params);
+ void cmdDisplayBottomTwoLines(Common::Array<byte> &params);
+ void cmdDisplayLarge(Common::Array<byte> &params);
+
+ /**
+ * Exchange the positions of two objects in the maze
+ */
+ void cmdExchObj(Common::Array<byte> &params);
+ void cmdFallToMap(Common::Array<byte> &params);
+ void cmdDisplayMain(Common::Array<byte> &params);
+
+ /**
+ * Jumps to a given line number if the surface at relative cell position 1 matches
+ * a specified surface.
+ * @remarks This opcode is apparently never actually used
+ */
+ void cmdGoto(Common::Array<byte> &params);
+
+ /**
+ * Pick a random value from the parameter list and jump to that line number
+ */
+ void cmdGotoRandom(Common::Array<byte> &params);
+ void cmdCutsceneEndDarkside(Common::Array<byte> &params);
+ void cmdCutsceneEdWorld(Common::Array<byte> &params);
+ void cmdFlipWorld(Common::Array<byte> &params);
+ void cmdPlayCD(Common::Array<byte> &params);
+
+ int whoWill(int v1, int v2, int v3);
+
+ void doEndGame();
+
+ void doEndGame2();
+
+ void doWorldEnd();
+
+ void doEnding(const Common::String &endStr, int v2);
+
+ /**
+ * This monstrosity handles doing the various types of If checks on various data
+ */
+ bool ifProc(int action, uint32 mask, int mode, int charIndex);
+
+ bool copyProtectionCheck();
+
+ void display(bool justifyFlag, int var46);
+public:
+ int _animCounter;
+ bool _eventSkipped;
+ int _whoWill;
+ int _nEdamageType;
+ int _itemType;
+ int _v2;
+ Common::Array<MirrorEntry> _mirror;
+public:
+ Scripts(XeenEngine *vm);
+
+ int checkEvents();
+
+ void openGrate(int wallVal, int action);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_SCRIPTS_H */
diff --git a/engines/xeen/sound.cpp b/engines/xeen/sound.cpp
new file mode 100644
index 0000000000..f5cc40249d
--- /dev/null
+++ b/engines/xeen/sound.cpp
@@ -0,0 +1,100 @@
+/* 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 "audio/decoders/voc.h"
+#include "xeen/sound.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+SoundManager *Voc::_sound;
+
+Voc::Voc(const Common::String &name) {
+ if (!open(name))
+ error("Could not open - %s", name.c_str());
+}
+
+void Voc::init(XeenEngine *vm) {
+ _sound = vm->_sound;
+}
+
+void Voc::play() {
+ _sound->playSound(this, _soundHandle);
+}
+
+void Voc::stop() {
+ _sound->stopSound(_soundHandle);
+}
+
+/*------------------------------------------------------------------------*/
+
+SoundManager *Music::_sound;
+
+Music::Music(const Common::String &name) {
+ if (!open(name))
+ error("Could not open - %s", name.c_str());
+}
+
+void Music::init(XeenEngine *vm) {
+ _sound = vm->_sound;
+}
+
+void Music::play() {
+ _sound->playMusic(this, _soundHandle);
+}
+
+void Music::stop() {
+ _sound->stopSound(_soundHandle);
+}
+
+/*------------------------------------------------------------------------*/
+
+SoundManager::SoundManager(XeenEngine *vm, Audio::Mixer *mixer): _mixer(mixer) {
+}
+
+void SoundManager::proc2(Common::SeekableReadStream &f) {
+ // TODO
+}
+
+void SoundManager::startMusic(int v1) {
+ // TODO
+}
+
+void SoundManager::stopMusic(int id) {
+ // TODO
+}
+
+void SoundManager::playSound(Common::SeekableReadStream *s, Audio::SoundHandle &soundHandle,
+ Audio::Mixer::SoundType soundType) {
+ Audio::SeekableAudioStream *stream = Audio::makeVOCStream(s, 0);
+ _mixer->playStream(soundType, &soundHandle, stream);
+}
+
+void SoundManager::playMusic(Common::SeekableReadStream *s, Audio::SoundHandle &soundHandle) {
+ // TODO
+}
+
+void SoundManager::stopSound(Audio::SoundHandle &soundHandle) {
+ _mixer->stopHandle(soundHandle);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/sound.h b/engines/xeen/sound.h
new file mode 100644
index 0000000000..954d324e8d
--- /dev/null
+++ b/engines/xeen/sound.h
@@ -0,0 +1,119 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_SOUND_H
+#define XEEN_SOUND_H
+
+#include "audio/mixer.h"
+#include "audio/audiostream.h"
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "xeen/files.h"
+
+namespace Xeen {
+
+class SoundManager;
+
+class Voc: public Common::File {
+private:
+ static SoundManager *_sound;
+ Audio::SoundHandle _soundHandle;
+public:
+ Voc() {}
+ Voc(const Common::String &name);
+ virtual ~Voc() { stop(); }
+ static void init(XeenEngine *vm);
+
+ /**
+ * Start playing the sound
+ */
+ void play();
+
+ /**
+ * Stop playing the sound
+ */
+ void stop();
+};
+
+class Music : public Common::File {
+private:
+ static SoundManager *_sound;
+ Audio::SoundHandle _soundHandle;
+public:
+ Music() {}
+ Music(const Common::String &name);
+ virtual ~Music() { stop(); }
+ static void init(XeenEngine *vm);
+
+ /**
+ * Start playing the sound
+ */
+ void play();
+
+ /**
+ * Stop playing the sound
+ */
+ void stop();
+};
+
+class SoundManager {
+private:
+ Audio::Mixer *_mixer;
+public:
+ SoundManager(XeenEngine *vm, Audio::Mixer *mixer);
+
+ void proc2(Common::SeekableReadStream &f);
+
+ void loadMusic(const Common::String &name, int v2) {}
+
+ void startMusic(int v1);
+
+ void stopMusic(int id);
+
+ void playSong(Common::SeekableReadStream &f) {}
+
+ /**
+ * Play a given sound
+ */
+ void playSound(Common::SeekableReadStream *s, Audio::SoundHandle &soundHandle,
+ Audio::Mixer::SoundType soundType = Audio::Mixer::kSFXSoundType);
+
+ /**
+ * Play a given music
+ */
+ void playMusic(Common::SeekableReadStream *s, Audio::SoundHandle &soundHandle);
+
+ /**
+ * Stop playing a sound
+ */
+ void stopSound(Audio::SoundHandle &soundHandle);
+
+ void playSample(const Common::SeekableReadStream *stream, int v2 = 1) {}
+
+ bool playSample(int v1, int v2) { return false; }
+
+ void playFX(int id) {}
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_SOUND_H */
diff --git a/engines/xeen/spells.cpp b/engines/xeen/spells.cpp
new file mode 100644
index 0000000000..ee3085d052
--- /dev/null
+++ b/engines/xeen/spells.cpp
@@ -0,0 +1,1329 @@
+/* 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 "xeen/spells.h"
+#include "xeen/dialogs_items.h"
+#include "xeen/dialogs_spells.h"
+#include "xeen/files.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+Spells::Spells(XeenEngine *vm) : _vm(vm) {
+ _lastCaster = 0;
+
+ load();
+}
+
+void Spells::load() {
+ File f1("spells.xen");
+ while (f1.pos() < f1.size())
+ _spellNames.push_back(f1.readString());
+ f1.close();
+}
+
+int Spells::calcSpellCost(int spellId, int expenseFactor) const {
+ int amount = SPELL_COSTS[spellId];
+ return (amount >= 0) ? (amount * 100) << expenseFactor :
+ (amount * -500) << expenseFactor;
+}
+
+int Spells::calcSpellPoints(int spellId, int expenseFactor) const {
+ int amount = SPELL_COSTS[spellId];
+ return (amount >= 0) ? amount : amount * -1 * expenseFactor;
+}
+
+typedef void(Spells::*SpellMethodPtr)();
+
+void Spells::executeSpell(MagicSpell spellId) {
+ static const SpellMethodPtr SPELL_LIST[76] = {
+ &Spells::acidSpray, &Spells::awaken, &Spells::beastMaster,
+ &Spells::bless, &Spells::clairvoyance, &Spells::coldRay,
+ &Spells::createFood, &Spells::cureDisease, &Spells::cureParalysis,
+ &Spells::curePoison, &Spells::cureWounds, &Spells::dancingSword,
+ &Spells::dayOfProtection, &Spells::dayOfSorcery, &Spells::deadlySwarm,
+ &Spells::detectMonster, &Spells::divineIntervention, &Spells::dragonSleep,
+ &Spells::elementalStorm, &Spells::enchantItem, &Spells::energyBlast,
+ &Spells::etherialize, &Spells::fantasticFreeze, &Spells::fieryFlail,
+ &Spells::fingerOfDeath, &Spells::fireball, &Spells::firstAid,
+ &Spells::flyingFist, &Spells::frostbite, &Spells::golemStopper,
+ &Spells::heroism, &Spells::holyBonus, &Spells::holyWord,
+ &Spells::hypnotize, &Spells::identifyMonster, &Spells::implosion,
+ &Spells::incinerate, &Spells::inferno, &Spells::insectSpray,
+ &Spells::itemToGold, &Spells::jump, &Spells::levitate,
+ &Spells::light, &Spells::lightningBolt, &Spells::lloydsBeacon,
+ &Spells::magicArrow, &Spells::massDistortion, &Spells::megaVolts,
+ &Spells::moonRay, &Spells::naturesCure, &Spells::pain,
+ &Spells::poisonVolley, &Spells::powerCure, &Spells::powerShield,
+ &Spells::prismaticLight, &Spells::protectionFromElements, &Spells::raiseDead,
+ &Spells::rechargeItem, &Spells::resurrection, &Spells::revitalize,
+ &Spells::shrapMetal, &Spells::sleep, &Spells::sparks,
+ &Spells::starBurst, &Spells::stoneToFlesh, &Spells::sunRay,
+ &Spells::superShelter, &Spells::suppressDisease, &Spells::suppressPoison,
+ &Spells::teleport, &Spells::timeDistortion, &Spells::townPortal,
+ &Spells::toxicCloud, &Spells::turnUndead, &Spells::walkOnWater,
+ &Spells::wizardEye
+ };
+
+ (this->*SPELL_LIST[spellId])();
+}
+
+void Spells::spellFailed() {
+ ErrorScroll::show(_vm, SPELL_FAILED, WT_NONFREEZED_WAIT);
+}
+
+void Spells::castItemSpell(int itemSpellId) {
+ switch (itemSpellId) {
+ case 15:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_Jump);
+ return;
+ }
+ break;
+ case 20:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_WizardEye);
+ return;
+ }
+ break;
+ case 27:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_LloydsBeacon);
+ return;
+ }
+ break;
+ case 32:
+ frostbite2();
+ break;
+ case 41:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_Teleport);
+ return;
+ }
+ break;
+ case 47:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_SuperShelter);
+ return;
+ }
+ break;
+ case 54:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_TownPortal);
+ return;
+ }
+ break;
+ case 57:
+ if (_vm->_mode == MODE_COMBAT) {
+ NotWhileEngaged::show(_vm, MS_Etheralize);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ static const MagicSpell spells[73] = {
+ MS_Light, MS_Awaken, MS_MagicArrow, MS_FirstAid, MS_FlyingFist,
+ MS_EnergyBlast, MS_Sleep, MS_Revitalize, MS_CureWounds, MS_Sparks,
+ MS_Shrapmetal, MS_InsectSpray, MS_ToxicCloud, MS_ProtFromElements, MS_Pain,
+ MS_Jump, MS_BeastMaster, MS_Clairvoyance, MS_TurnUndead, MS_Levitate,
+ MS_WizardEye, MS_Bless, MS_IdentifyMonster, MS_LightningBolt, MS_HolyBonus,
+ MS_PowerCure, MS_NaturesCure, MS_LloydsBeacon, MS_PowerShield, MS_Heroism,
+ MS_Hynotize, MS_WalkOnWater, NO_SPELL, MS_DetectMonster, MS_Fireball,
+ MS_ColdRay, MS_CurePoison, MS_AcidSpray, MS_TimeDistortion, MS_DragonSleep,
+ MS_CureDisease, MS_Teleport, MS_FingerOfDeath, MS_CureParalysis, MS_GolemStopper,
+ MS_PoisonVolley, MS_DeadlySwarm, MS_SuperShelter, MS_DayOfProtection, MS_DayOfProtection,
+ MS_CreateFood, MS_FieryFlail, MS_RechargeItem, MS_FantasticFreeze, MS_TownPortal,
+ MS_StoneToFlesh, MS_RaiseDead, MS_Etheralize, MS_DancingSword, MS_MoonRay,
+ MS_MassDistortion, MS_PrismaticLight, MS_EnchantItem, MS_Incinerate, MS_HolyWord,
+ MS_Resurrection, MS_ElementalStorm, MS_MegaVolts, MS_Inferno, MS_SunRay,
+ MS_Implosion, MS_StarBurst, MS_DivineIntervention
+ };
+
+ executeSpell(spells[itemSpellId]);
+}
+
+int Spells::castSpell(Character *c, MagicSpell spellId) {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+ int oldTillMove = intf._tillMove;
+ int result = 1;
+ combat._oldCharacter = c;
+
+ // Try and subtract the SP and gem requirements for the spell
+ int resultError = subSpellCost(*c, spellId);
+ if (resultError) {
+ CantCast::show(_vm, spellId, resultError);
+ result = -1;
+ } else {
+ // Some spells have special handling
+ switch (spellId) {
+ case MS_EnchantItem:
+ case MS_Etheralize:
+ case MS_Jump:
+ case MS_LloydsBeacon:
+ case MS_SuperShelter:
+ case MS_Teleport:
+ case MS_TownPortal:
+ case MS_WizardEye:
+ if (_vm->_mode != MODE_COMBAT) {
+ executeSpell(spellId);
+ } else {
+ // Return the spell costs and flag that another spell can be selected
+ addSpellCost(*c, spellId);
+ NotWhileEngaged::show(_vm, spellId);
+ result = -1;
+ }
+ break;
+
+ default:
+ executeSpell(spellId);
+ break;
+ }
+ }
+
+ combat._moveMonsters = 1;
+ intf._tillMove = oldTillMove;
+ return result;
+}
+
+int Spells::subSpellCost(Character &c, int spellId) {
+ Party &party = *_vm->_party;
+ int gemCost = SPELL_GEM_COST[spellId];
+ int spCost = SPELL_COSTS[spellId];
+
+ // Negative SP costs indicate it's dependent on the character's level
+ if (spCost <= 0) {
+ spCost = c.getCurrentLevel() * (spCost * -1);
+ }
+
+ if (spCost > c._currentSp)
+ // Not enough SP
+ return 1;
+ if (gemCost > (int)party._gems)
+ // Not enough gems
+ return 2;
+
+ c._currentSp -= spCost;
+ party._gems -= gemCost;
+ return 0;
+}
+
+void Spells::addSpellCost(Character &c, int spellId) {
+ Party &party = *_vm->_party;
+ int gemCost = SPELL_GEM_COST[spellId];
+ int spCost = SPELL_COSTS[spellId];
+
+ if (spCost < 1)
+ spCost *= -1 * c.getCurrentLevel();
+
+ c._currentSp += spCost;
+ party._gems += gemCost;
+}
+
+void Spells::acidSpray() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 15;
+ combat._damageType = DT_POISON;
+ combat._rangeType = RT_ALL;
+ sound.playFX(17);
+ combat.multiAttack(10);
+}
+
+void Spells::awaken() {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ Character &c = party._activeParty[idx];
+ c._conditions[ASLEEP] = 0;
+ if (c._currentHp > 0)
+ c._conditions[UNCONSCIOUS] = 0;
+ }
+
+ intf.drawParty(true);
+ sound.playFX(30);
+}
+
+void Spells::beastMaster() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_BEASTMASTER;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(7);
+}
+
+void Spells::bless() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ sound.playFX(30);
+ party._blessed = combat._oldCharacter->getCurrentLevel();
+}
+
+void Spells::clairvoyance() {
+ _vm->_party->_clairvoyanceActive = true;
+ _vm->_sound->playFX(20);
+}
+
+void Spells::coldRay() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = _vm->getRandomNumber(2, 4) * combat._oldCharacter->getCurrentLevel();
+ combat._damageType = DT_COLD;
+ combat._rangeType = RT_ALL;
+ sound.playFX(15);
+ combat.multiAttack(8);
+}
+
+void Spells::createFood() {
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ party._food += party._activeParty.size();
+ sound.playFX(20);
+}
+
+void Spells::cureDisease() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_CureDisease);
+ if (!c)
+ return;
+
+ sound.playFX(30);
+ c->addHitPoints(0);
+ c->_conditions[DISEASED] = 0;
+ intf.drawParty(true);
+}
+
+void Spells::cureParalysis() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_CureParalysis);
+ if (!c)
+ return;
+
+ sound.playFX(30);
+ c->addHitPoints(0);
+ c->_conditions[PARALYZED] = 0;
+ intf.drawParty(true);
+}
+
+void Spells::curePoison() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_CurePoison);
+ if (!c)
+ return;
+
+ sound.playFX(30);
+ c->addHitPoints(0);
+ c->_conditions[POISONED] = 0;
+ intf.drawParty(true);
+}
+
+void Spells::cureWounds() {
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_CureWounds);
+ if (!c)
+ return;
+
+ if (c->isDead()) {
+ spellFailed();
+ } else {
+ sound.playFX(30);
+ c->addHitPoints(15);
+ }
+}
+
+void Spells::dancingSword() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = _vm->getRandomNumber(6, 14) * combat._oldCharacter->getCurrentLevel();
+ combat._damageType = DT_PHYSICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(14);
+}
+
+void Spells::dayOfProtection() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ int lvl = combat._oldCharacter->getCurrentLevel();
+ party._walkOnWaterActive = true;
+ party._heroism = lvl;
+ party._holyBonus = lvl;
+ party._blessed = lvl;
+ party._poisonResistence = lvl;
+ party._coldResistence = lvl;
+ party._electricityResistence = lvl;
+ party._fireResistence = lvl;
+ party._lightCount = lvl;
+ sound.playFX(20);
+}
+
+void Spells::dayOfSorcery() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ int lvl = combat._oldCharacter->getCurrentLevel();
+ party._powerShield = lvl;
+ party._clairvoyanceActive = true;
+ party._wizardEyeActive = true;
+ party._levitateActive = true;
+ party._lightCount = lvl;
+ party._automapOn = false;
+ sound.playFX(20);
+}
+
+void Spells::deadlySwarm() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 40;
+ combat._damageType = DT_PHYSICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(13);
+ combat.multiAttack(15);
+}
+
+void Spells::detectMonster() {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ Window &w = screen._windows[19];
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ int grid[7][7];
+
+ SpriteResource sprites(isDarkCc ? "detectmn.icn" : "detctmon.icn");
+ Common::fill(&grid[0][0], &grid[7][7], 0);
+
+ w.open();
+ w.writeString(DETECT_MONSTERS);
+ sprites.draw(w, 0, Common::Point(243, 80));
+
+ for (int yDiff = 3; yDiff >= -3; --yDiff) {
+ for (int xDiff = -3; xDiff <= 3; ++xDiff) {
+ for (uint monIndex = 0; monIndex < map._mobData._monsters.size(); ++monIndex) {
+ MazeMonster &monster = map._mobData._monsters[monIndex];
+ Common::Point pt = party._mazePosition + Common::Point(xDiff, yDiff);
+ if (monster._position == pt) {
+ if (++grid[yDiff][xDiff] > 3)
+ grid[yDiff][xDiff] = 3;
+
+ sprites.draw(w, grid[yDiff][xDiff], Common::Point(xDiff * 9 + 244,
+ yDiff * 7 + 81));
+ }
+ }
+ }
+ }
+
+ sprites.draw(w, party._mazeDirection + 1, Common::Point(270, 101));
+ sound.playFX(20);
+ w.update();
+
+ do {
+ events.updateGameCounter();
+ intf.draw3d(true);
+
+ events.wait(1);
+ } while (!events.isKeyMousePressed());
+
+ w.close();
+}
+
+void Spells::divineIntervention() {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ Character &castChar = *combat._oldCharacter;
+
+ if ((castChar._tempAge + 5) > 250) {
+ castChar._tempAge = 250;
+ } else {
+ castChar._tempAge += 5;
+ }
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ Character &c = party._activeParty[idx];
+ Common::fill(&c._conditions[CURSED], &c._conditions[ERADICATED], 0);
+ if (!c._conditions[ERADICATED])
+ c._currentHp = c.getMaxHP();
+ }
+
+ sound.playFX(20);
+ intf.drawParty(true);
+}
+
+void Spells::dragonSleep() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_DRAGONSLEEP;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(18);
+ combat.multiAttack(7);
+}
+
+void Spells::elementalStorm() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+ static const int STORM_FX_LIST[4] = { 13, 14, 15, 17 };
+ static const int STORM_MA_LIST[4] = { 0, 5, 9, 10 };
+
+ combat._monsterDamage = 150;
+ combat._damageType = (DamageType)_vm->getRandomNumber(DT_FIRE, DT_POISON);
+ combat._rangeType = RT_ALL;
+ sound.playFX(STORM_FX_LIST[combat._damageType]);
+ combat.multiAttack(STORM_MA_LIST[combat._damageType]);
+}
+
+void Spells::enchantItem() {
+ Mode oldMode = _vm->_mode;
+
+ Character *c = SpellOnWho::show(_vm, MS_EnchantItem);
+ if (!c)
+ return;
+
+ ItemsDialog::show(_vm, c, ITEMMODE_ENCHANT);
+
+ _vm->_mode = oldMode;
+}
+
+void Spells::energyBlast() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = combat._oldCharacter->getCurrentLevel() * 2;
+ combat._damageType = DT_ENERGY;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(16);
+ combat.multiAttack(13);
+}
+
+void Spells::etherialize() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ Common::Point pt = party._mazePosition + Common::Point(
+ SCREEN_POSITIONING_X[party._mazeDirection][7],
+ SCREEN_POSITIONING_Y[party._mazeDirection][7]
+ );
+
+ if ((map.mazeData()._mazeFlags & RESTRICTION_ETHERIALIZE) ||
+ map.mazeLookup(pt, 0, 0xffff) == INVALID_CELL) {
+ spellFailed();
+ } else {
+ party._mazePosition = pt;
+ sound.playFX(51);
+ }
+}
+
+void Spells::fantasticFreeze() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 40;
+ combat._damageType = DT_COLD;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(15);
+ combat.multiAttack(8);
+}
+
+void Spells::fieryFlail() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 100;
+ combat._damageType = DT_FIRE;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(13);
+ combat.multiAttack(2);
+}
+
+void Spells::fingerOfDeath() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_FINGEROFDEATH;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(14);
+}
+
+void Spells::fireball() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = _vm->getRandomNumber(3, 7) * combat._oldCharacter->getCurrentLevel();
+ combat._damageType = DT_FIRE;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(13);
+ combat.multiAttack(0);
+}
+
+void Spells::firstAid() {
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_FirstAid);
+ if (!c)
+ return;
+
+ if (c->isDead()) {
+ spellFailed();
+ } else {
+ sound.playFX(30);
+ c->addHitPoints(6);
+ }
+}
+
+void Spells::flyingFist() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 6;
+ combat._damageType = DT_PHYSICAL;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(18);
+ combat.multiAttack(14);
+}
+
+void Spells::frostbite() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 35;
+ combat._damageType = DT_COLD;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(8);
+ combat.multiAttack(8);
+}
+
+void Spells::golemStopper() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_GOLEMSTOPPER;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(16);
+ combat.multiAttack(6);
+}
+
+void Spells::heroism() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ sound.playFX(30);
+ party._heroism = combat._oldCharacter->getCurrentLevel();
+}
+
+void Spells::holyBonus() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ sound.playFX(30);
+ party._holyBonus = combat._oldCharacter->getCurrentLevel();
+}
+
+void Spells::holyWord() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_HOLYWORD;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(13);
+}
+
+void Spells::hypnotize() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_HYPNOTIZE;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(7);
+}
+
+void Spells::identifyMonster() {
+ Combat &combat = *_vm->_combat;
+
+ if (combat._attackMonsters[0] == -1 && combat._attackMonsters[1] == -1
+ && combat._attackMonsters[2] == -1) {
+ spellFailed();
+ } else {
+ IdentifyMonster::show(_vm);
+ }
+}
+
+void Spells::implosion() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 1000;
+ combat._damageType = DT_ENERGY;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(18);
+ combat.multiAttack(6);
+}
+
+void Spells::incinerate() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 250;
+ combat._damageType = DT_FIRE;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(22);
+ combat.multiAttack(1);
+}
+
+void Spells::inferno() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 250;
+ combat._damageType = DT_FIRE;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(13);
+ combat.multiAttack(1);
+}
+
+void Spells::insectSpray() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_INSECT_SPRAY;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(17);
+ combat.multiAttack(10);
+}
+
+void Spells::itemToGold() {
+ Character *c = SpellOnWho::show(_vm, MS_ItemToGold);
+ if (!c)
+ return;
+
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_FF;
+
+ _vm->_screen->_windows[11].close();
+ ItemsDialog::show(_vm, c, ITEMMODE_TO_GOLD);
+
+ _vm->_mode = oldMode;
+}
+
+void Spells::jump() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ if (map._isOutdoors) {
+ map.getCell(7);
+ if (map._currentWall != 1) {
+ map.getCell(14);
+ if (map._currentSurfaceId != 0 && map._currentWall != 1) {
+ party._mazePosition += Common::Point(
+ SCREEN_POSITIONING_X[party._mazeDirection][14],
+ SCREEN_POSITIONING_Y[party._mazeDirection][14]
+ );
+ sound.playFX(51);
+ party._stepped = true;
+ return;
+ }
+ }
+ } else {
+ Common::Point pt = party._mazePosition + Common::Point(
+ SCREEN_POSITIONING_X[party._mazeDirection][7],
+ SCREEN_POSITIONING_Y[party._mazeDirection][7]);
+ if (!map.mazeLookup(party._mazePosition, MONSTER_GRID_BITMASK[party._mazeDirection]) &&
+ !map.mazeLookup(pt, MONSTER_GRID_BITMASK[party._mazeDirection])) {
+ party._mazePosition += Common::Point(
+ SCREEN_POSITIONING_X[party._mazeDirection][14],
+ SCREEN_POSITIONING_Y[party._mazeDirection][14]
+ );
+ sound.playFX(51);
+ party._stepped = true;
+ return;
+ }
+ }
+
+ spellFailed();
+}
+
+void Spells::levitate() {
+ _vm->_party->_levitateActive = true;
+ _vm->_sound->playFX(20);
+}
+
+void Spells::light() {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ ++party._lightCount;
+ if (intf._intrIndex1)
+ party._stepped = true;
+ sound.playFX(39);
+}
+
+void Spells::lightningBolt() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = _vm->getRandomNumber(4, 6) * combat._oldCharacter->getCurrentLevel();
+ combat._damageType = DT_ELECTRICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(14);
+ combat.multiAttack(3);
+}
+
+void Spells::lloydsBeacon() {
+ if (_vm->_map->mazeData()._mazeFlags & RESTRICTION_LLOYDS_BEACON) {
+ spellFailed();
+ } else {
+ if (!LloydsBeacon::show(_vm))
+ spellFailed();
+ }
+}
+
+void Spells::magicArrow() {
+ Combat &combat = *_vm->_combat;
+ combat._monsterDamage = 0;
+ combat._damageType = DT_MAGIC_ARROW;
+ combat._rangeType = RT_SINGLE;
+ combat.multiAttack(11);
+}
+
+void Spells::massDistortion() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_MASS_DISTORTION;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(6);
+}
+
+void Spells::megaVolts() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 150;
+ combat._damageType = DT_ELECTRICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(14);
+ combat.multiAttack(4);
+}
+
+void Spells::moonRay() {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 30;
+ combat._damageType = DT_ENERGY;
+ combat._rangeType = RT_ALL;
+ sound.playFX(16);
+ combat.multiAttack(13);
+
+ for (uint idx = 0; idx < party._activeParty.size(); ++idx) {
+ sound.playFX(30);
+ party._activeParty[idx].addHitPoints(_vm->getRandomNumber(1, 30));
+ }
+
+ intf.drawParty(true);
+}
+
+void Spells::naturesCure() {
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_NaturesCure);
+ if (!c)
+ return;
+
+ if (c->isDead()) {
+ spellFailed();
+ } else {
+ sound.playFX(30);
+ c->addHitPoints(25);
+ }
+}
+
+void Spells::pain() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_PHYSICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(14);
+}
+
+void Spells::poisonVolley() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 10;
+ combat._damageType = DT_POISON_VOLLEY;
+ combat._rangeType = RT_ALL;
+ sound.playFX(49);
+ combat.multiAttack(11);
+}
+
+void Spells::powerCure() {
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_PowerCure);
+ if (!c)
+ return;
+
+ if (c->isDead()) {
+ spellFailed();
+ } else {
+ sound.playFX(30);
+ c->addHitPoints(_vm->getRandomNumber(2, 12) * _vm->_combat->_oldCharacter->getCurrentLevel());
+ }
+}
+
+void Spells::powerShield() {
+ Combat &combat = *_vm->_combat;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ sound.playFX(20);
+ party._powerShield = combat._oldCharacter->getCurrentLevel();
+}
+
+void Spells::prismaticLight() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 80;
+ combat._damageType = (DamageType)_vm->getRandomNumber(DT_PHYSICAL, DT_ENERGY);
+ combat._rangeType = RT_ALL;
+ sound.playFX(18);
+ combat.multiAttack(14);
+}
+
+void Spells::protectionFromElements() {
+ Combat &combat = *_vm->_combat;
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ Character &c = *combat._oldCharacter;
+ int resist = MIN(c.getCurrentLevel() * 2 + 5, (uint)200);
+
+ int elementType = SelectElement::show(_vm, MS_ProtFromElements);
+ if (elementType != -1) {
+ switch (elementType) {
+ case DT_FIRE:
+ party._fireResistence = resist;
+ break;
+ case DT_ELECTRICAL:
+ party._fireResistence = resist;
+ break;
+ case DT_COLD:
+ party._coldResistence = resist;
+ break;
+ case DT_POISON:
+ party._poisonResistence = resist;
+ break;
+ default:
+ break;
+ }
+
+ sound.playFX(20);
+ intf.drawParty(true);
+ }
+}
+
+void Spells::raiseDead() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_RaiseDead);
+ if (!c)
+ return;
+
+ if (!c->_conditions[DEAD]) {
+ spellFailed();
+ } else {
+ c->_conditions[DEAD] = 0;
+ c->_conditions[UNCONSCIOUS] = 0;
+ c->_currentHp = 0;
+ sound.playFX(30);
+ c->addHitPoints(1);
+ if (--c->_endurance._permanent < 1)
+ c->_endurance._permanent = 1;
+
+ intf.drawParty(true);
+ }
+}
+
+void Spells::rechargeItem() {
+ Mode oldMode = _vm->_mode;
+
+ Character *c = SpellOnWho::show(_vm, MS_RechargeItem);
+ if (!c)
+ return;
+
+ ItemsDialog::show(_vm, c, ITEMMODE_RECHARGE);
+ _vm->_mode = oldMode;
+}
+
+void Spells::resurrection() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_RaiseDead);
+ if (!c)
+ return;
+
+ if (!c->_conditions[ERADICATED]) {
+ spellFailed();
+ sound.playFX(30);
+ } else {
+ sound.playFX(30);
+ c->addHitPoints(0);
+ c->_conditions[ERADICATED] = 0;
+
+ if (--c->_endurance._permanent < 1)
+ c->_endurance._permanent = 1;
+ if ((c->_tempAge + 5) >= 250)
+ c->_tempAge = 250;
+ else
+ c->_tempAge += 5;
+
+ intf.drawParty(true);
+ }
+}
+
+void Spells::revitalize() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_Revitalize);
+ if (!c)
+ return;
+
+ sound.playFX(30);
+ c->addHitPoints(0);
+ c->_conditions[WEAK] = 0;
+ intf.drawParty(true);
+}
+
+void Spells::shrapMetal() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = combat._oldCharacter->getCurrentLevel() * 2;
+ combat._damageType = DT_PHYSICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(16);
+ combat.multiAttack(15);
+}
+
+void Spells::sleep() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_SLEEP;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(7);
+}
+
+void Spells::sparks() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = combat._oldCharacter->getCurrentLevel() * 2;
+ combat._damageType = DT_ELECTRICAL;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(14);
+ combat.multiAttack(5);
+}
+
+void Spells::starBurst() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 500;
+ combat._damageType = DT_FIRE;
+ combat._rangeType = RT_ALL;
+ sound.playFX(13);
+ combat.multiAttack(15);
+}
+
+void Spells::stoneToFlesh() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_StoneToFlesh);
+ if (!c)
+ return;
+
+ sound.playFX(30);
+ c->addHitPoints(0);
+ c->_conditions[STONED] = 0;
+ intf.drawParty(true);
+}
+
+void Spells::sunRay() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 200;
+ combat._damageType = DT_ENERGY;
+ combat._rangeType = RT_ALL;
+ sound.playFX(16);
+ combat.multiAttack(13);
+}
+
+void Spells::superShelter() {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ SoundManager &sound = *_vm->_sound;
+
+ if (map.mazeData()._mazeFlags & RESTRICTION_SUPER_SHELTER) {
+ spellFailed();
+ } else {
+ Mode oldMode = _vm->_mode;
+ _vm->_mode = MODE_12;
+ sound.playFX(30);
+ intf.rest();
+ _vm->_mode = oldMode;
+ }
+}
+
+void Spells::suppressDisease() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_SuppressDisease);
+ if (!c)
+ return;
+
+ if (c->_conditions[DISEASED]) {
+ if (c->_conditions[DISEASED] >= 4)
+ c->_conditions[DISEASED] -= 3;
+ else
+ c->_conditions[DISEASED] = 1;
+
+ sound.playFX(20);
+ c->addHitPoints(0);
+ intf.drawParty(true);
+ }
+}
+
+void Spells::suppressPoison() {
+ Interface &intf = *_vm->_interface;
+ SoundManager &sound = *_vm->_sound;
+
+ Character *c = SpellOnWho::show(_vm, MS_FirstAid);
+ if (!c)
+ return;
+
+ if (c->_conditions[POISONED]) {
+ if (c->_conditions[POISONED] >= 4) {
+ c->_conditions[POISONED] -= 2;
+ } else {
+ c->_conditions[POISONED] = 1;
+ }
+ }
+
+ sound.playFX(20);
+ c->addHitPoints(0);
+ intf.drawParty(1);
+}
+
+void Spells::teleport() {
+ Map &map = *_vm->_map;
+ SoundManager &sound = *_vm->_sound;
+
+ if (map.mazeData()._mazeFlags & RESTRICTION_TELPORT) {
+ spellFailed();
+ } else {
+ switch (Teleport::show(_vm)) {
+ case 0:
+ spellFailed();
+ break;
+ case 1:
+ sound.playFX(51);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void Spells::timeDistortion() {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ if (map.mazeData()._mazeFlags & RESTRICTION_TIME_DISTORTION) {
+ spellFailed();
+ } else {
+ party.moveToRunLocation();
+ sound.playFX(51);
+ intf.draw3d(true);
+ }
+}
+
+void Spells::townPortal() {
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ if (map.mazeData()._mazeFlags & RESTRICTION_TOWN_PORTAL) {
+ spellFailed();
+ return;
+ }
+
+ int townNumber = TownPortal::show(_vm);
+ if (!townNumber)
+ return;
+
+ sound.playFX(51);
+ map._loadDarkSide = map._sideTownPortal;
+ _vm->_files->_isDarkCc = map._sideTownPortal > 0;
+ map.load(TOWN_MAP_NUMBERS[map._sideTownPortal][townNumber - 1]);
+
+ if (!_vm->_files->_isDarkCc) {
+ party.moveToRunLocation();
+ } else {
+ switch (townNumber) {
+ case 1:
+ party._mazePosition = Common::Point(14, 11);
+ party._mazeDirection = DIR_SOUTH;
+ break;
+ case 2:
+ party._mazePosition = Common::Point(5, 12);
+ party._mazeDirection = DIR_WEST;
+ break;
+ case 3:
+ party._mazePosition = Common::Point(2, 15);
+ party._mazeDirection = DIR_EAST;
+ break;
+ case 4:
+ party._mazePosition = Common::Point(8, 11);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ case 5:
+ party._mazePosition = Common::Point(2, 6);
+ party._mazeDirection = DIR_NORTH;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void Spells::toxicCloud() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 10;
+ combat._damageType = DT_POISON;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(17);
+ combat.multiAttack(10);
+}
+
+void Spells::turnUndead() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 0;
+ combat._damageType = DT_UNDEAD;
+ combat._rangeType = RT_GROUP;
+ sound.playFX(18);
+ combat.multiAttack(13);
+}
+
+void Spells::walkOnWater() {
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ party._walkOnWaterActive = true;
+ sound.playFX(20);
+}
+
+void Spells::wizardEye() {
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ party._wizardEyeActive = true;
+ party._automapOn = false;
+ sound.playFX(20);
+}
+
+void Spells::frostbite2() {
+ Combat &combat = *_vm->_combat;
+ SoundManager &sound = *_vm->_sound;
+
+ combat._monsterDamage = 35;
+ combat._damageType = DT_COLD;
+ combat._rangeType = RT_SINGLE;
+ sound.playFX(15);
+ combat.multiAttack(9);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/spells.h b/engines/xeen/spells.h
new file mode 100644
index 0000000000..a05e81c56d
--- /dev/null
+++ b/engines/xeen/spells.h
@@ -0,0 +1,191 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_SPELLS_H
+#define XEEN_SPELLS_H
+
+#include "common/scummsys.h"
+#include "common/str-array.h"
+
+namespace Xeen {
+
+class XeenEngine;
+class Character;
+
+#define MAX_SPELLS_PER_CLASS 40
+
+enum MagicSpell {
+ MS_AcidSpray = 0, MS_Awaken = 1, MS_BeastMaster = 2, MS_Bless = 3,
+ MS_Clairvoyance = 4, MS_ColdRay = 5, MS_CreateFood = 6,
+ MS_CureDisease = 7, MS_CureParalysis = 8, MS_CurePoison = 9,
+ MS_CureWounds = 10, MS_DancingSword = 11, MS_DayOfProtection = 12,
+ MS_DayOfSorcery = 13, MS_DeadlySwarm = 14, MS_DetectMonster = 15,
+ MS_DivineIntervention = 16, MS_DragonSleep = 17, MS_ElementalStorm = 18,
+ MS_EnchantItem = 19, MS_EnergyBlast = 20, MS_Etheralize = 21,
+ MS_FantasticFreeze = 22, MS_FieryFlail = 23, MS_FingerOfDeath = 24,
+ MS_Fireball = 25, MS_FirstAid = 26, MS_FlyingFist = 27,
+ MS_FrostBite = 28, MS_GolemStopper = 29, MS_Heroism = 30,
+ MS_HolyBonus = 31, MS_HolyWord = 32, MS_Hynotize = 33,
+ MS_IdentifyMonster = 34, MS_Implosion = 35, MS_Incinerate = 36,
+ MS_Inferno = 37, MS_InsectSpray = 38, MS_ItemToGold = 39,
+ MS_Jump = 40, MS_Levitate = 41, MS_Light = 42, MS_LightningBolt = 43,
+ MS_LloydsBeacon = 44, MS_MagicArrow = 45, MS_MassDistortion = 46,
+ MS_MegaVolts = 47, MS_MoonRay = 48, MS_NaturesCure = 49, MS_Pain = 50,
+ MS_PoisonVolley = 51, MS_PowerCure = 52, MS_PowerShield = 53,
+ MS_PrismaticLight = 54, MS_ProtFromElements = 55, MS_RaiseDead = 56,
+ MS_RechargeItem = 57, MS_Resurrection = 58, MS_Revitalize = 59,
+ MS_Shrapmetal = 60, MS_Sleep = 61, MS_Sparks = 62, MS_StarBurst = 63,
+ MS_StoneToFlesh = 64, MS_SunRay = 65, MS_SuperShelter = 66,
+ MS_SuppressDisease = 67, MS_SuppressPoison = 68, MS_Teleport = 69,
+ MS_TimeDistortion = 70, MS_TownPortal = 71, MS_ToxicCloud = 72,
+ MS_TurnUndead = 73, MS_WalkOnWater = 74, MS_WizardEye = 75,
+ NO_SPELL = 76
+};
+
+class Spells {
+private:
+ XeenEngine *_vm;
+
+ void load();
+
+ void executeSpell(MagicSpell spellId);
+
+ /**
+ * Spell being cast failed
+ */
+ void spellFailed();
+
+ // Spell list
+ void acidSpray();
+ void awaken();
+ void beastMaster();
+ void bless();
+ void clairvoyance();
+ void coldRay();
+ void createFood();
+ void cureDisease();
+ void cureParalysis();
+ void curePoison();
+ void cureWounds();
+ void dancingSword();
+ void dayOfProtection();
+ void dayOfSorcery();
+ void deadlySwarm();
+ void detectMonster();
+ void divineIntervention();
+ void dragonSleep();
+ void elementalStorm();
+ void enchantItem();
+ void energyBlast();
+ void etherialize();
+ void fantasticFreeze();
+ void fieryFlail();
+ void fingerOfDeath();
+ void fireball();
+ void firstAid();
+ void flyingFist();
+ void frostbite();
+ void golemStopper();
+ void heroism();
+ void holyBonus();
+ void holyWord();
+ void hypnotize();
+ void identifyMonster();
+ void implosion();
+ void incinerate();
+ void inferno();
+ void insectSpray();
+ void itemToGold();
+ void jump();
+ void levitate();
+ void light();
+ void lightningBolt();
+ void lloydsBeacon();
+ void magicArrow();
+ void massDistortion();
+ void megaVolts();
+ void moonRay();
+ void naturesCure();
+ void pain();
+ void poisonVolley();
+ void powerCure();
+ void powerShield();
+ void prismaticLight();
+ void protectionFromElements();
+ void raiseDead();
+ void rechargeItem();
+ void resurrection();
+ void revitalize();
+ void shrapMetal();
+ void sleep();
+ void sparks();
+ void starBurst();
+ void stoneToFlesh();
+ void sunRay();
+ void superShelter();
+ void suppressDisease();
+ void suppressPoison();
+ void teleport();
+ void timeDistortion();
+ void townPortal();
+ void toxicCloud();
+ void turnUndead();
+ void walkOnWater();
+ void wizardEye();
+
+ void frostbite2();
+public:
+ Common::StringArray _spellNames;
+ int _lastCaster;
+public:
+ Spells(XeenEngine *vm);
+
+ int calcSpellCost(int spellId, int expenseFactor) const;
+
+ int calcSpellPoints(int spellId, int expenseFactor) const;
+
+ /**
+ * Cast a spell associated with an item
+ */
+ void castItemSpell(int itemSpellId);
+
+ /**
+ * Cast a given spell
+ */
+ int castSpell(Character *c, MagicSpell spellId);
+
+ /**
+ * Subtract the requirements for a given spell if available, returning
+ * true if there was sufficient
+ */
+ int subSpellCost(Character &c, int spellId);
+
+ /**
+ * Add the SP and gem requirements for a given spell to the given
+ * character and party
+ */
+ void addSpellCost(Character &c, int spellId);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_SPELLS_H */
diff --git a/engines/xeen/sprites.cpp b/engines/xeen/sprites.cpp
new file mode 100644
index 0000000000..4804f0aa66
--- /dev/null
+++ b/engines/xeen/sprites.cpp
@@ -0,0 +1,334 @@
+/* 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 "common/scummsys.h"
+#include "common/archive.h"
+#include "common/memstream.h"
+#include "common/textconsole.h"
+#include "xeen/xeen.h"
+#include "xeen/screen.h"
+#include "xeen/sprites.h"
+
+namespace Xeen {
+
+#define SCENE_CLIP_LEFT 8
+#define SCENE_CLIP_RIGHT 223
+
+SpriteResource::SpriteResource() {
+ _filesize = 0;
+ _data = nullptr;
+ _scaledWidth = _scaledHeight = 0;
+ Common::fill(&_lineDist[0], &_lineDist[SCREEN_WIDTH], false);
+}
+
+SpriteResource::SpriteResource(const Common::String &filename) {
+ _data = nullptr;
+ _scaledWidth = _scaledHeight = 0;
+ Common::fill(&_lineDist[0], &_lineDist[SCREEN_WIDTH], false);
+ load(filename);
+}
+
+SpriteResource::~SpriteResource() {
+ clear();
+}
+
+SpriteResource &SpriteResource::operator=(const SpriteResource &src) {
+ delete[] _data;
+ _index.clear();
+
+ _filesize = src._filesize;
+ _data = new byte[_filesize];
+ Common::copy(src._data, src._data + _filesize, _data);
+
+ _index.resize(src._index.size());
+ for (uint i = 0; i < src._index.size(); ++i)
+ _index[i] = src._index[i];
+
+ return *this;
+}
+
+void SpriteResource::load(const Common::String &filename) {
+ File f(filename);
+ load(f);
+}
+
+void SpriteResource::load(const Common::String &filename, Common::Archive &archive) {
+ File f(filename, archive);
+ load(f);
+}
+
+void SpriteResource::load(Common::SeekableReadStream &f) {
+ // Read in a copy of the file
+ _filesize = f.size();
+ delete[] _data;
+ _data = new byte[_filesize];
+ f.read(_data, _filesize);
+
+ // Read in the index
+ f.seek(0);
+ int count = f.readUint16LE();
+ _index.resize(count);
+
+ for (int i = 0; i < count; ++i) {
+ _index[i]._offset1 = f.readUint16LE();
+ _index[i]._offset2 = f.readUint16LE();
+ }
+}
+
+void SpriteResource::clear() {
+ delete[] _data;
+ _data = nullptr;
+ _filesize = 0;
+}
+
+void SpriteResource::drawOffset(XSurface &dest, uint16 offset, const Common::Point &pt,
+ const Common::Rect &bounds, int flags, int scale) {
+ static const uint SCALE_TABLE[] = {
+ 0xFFFF, 0xFFEF, 0xEFEF, 0xEFEE, 0xEEEE, 0xEEAE, 0xAEAE, 0xAEAA,
+ 0xAAAA, 0xAA8A, 0x8A8A, 0x8A88, 0x8888, 0x8880, 0x8080, 0x8000
+ };
+ static const int PATTERN_STEPS[] = { 0, 1, 1, 1, 2, 2, 3, 3, 0, -1, -1, -1, -2, -2, -3, -3 };
+
+ uint16 scaleMask = SCALE_TABLE[scale & 0x7fff];
+ uint16 scaleMaskX = scaleMask, scaleMaskY = scaleMask;
+ bool flipped = (flags & SPRFLAG_HORIZ_FLIPPED) != 0;
+ int xInc = flipped ? -1 : 1;
+ bool enlarge = (scale & 0x8000) != 0;
+
+ // Get cell header
+ Common::MemoryReadStream f(_data, _filesize);
+ f.seek(offset);
+ int xOffset = f.readUint16LE();
+ int width = f.readUint16LE();
+ int yOffset = f.readUint16LE();
+ int height = f.readUint16LE();
+
+ // Figure out drawing x, y
+ Common::Point destPos;
+ destPos.x = pt.x + getScaledVal(xOffset, scaleMaskX);
+ destPos.x += (width - getScaledVal(width, scaleMaskX)) / 2;
+
+ destPos.y = pt.y + getScaledVal(yOffset, scaleMaskY);
+
+ // If the flags allow the dest surface to be resized, ensure dest surface is big enough
+ if (flags & SPRFLAG_RESIZE) {
+ if (dest.w < (xOffset + width) || dest.h < (yOffset + height))
+ dest.create(xOffset + width, yOffset + height);
+ }
+
+ uint16 scaleMaskXCopy = scaleMaskX;
+ Common::Rect drawBounds;
+ drawBounds.left = SCREEN_WIDTH;
+ drawBounds.top = SCREEN_HEIGHT;
+ drawBounds.right = drawBounds.bottom = 0;
+
+ // Main loop
+ for (int yCtr = height; yCtr > 0; --yCtr) {
+ // The number of bytes in this scan line
+ int lineLength = f.readByte();
+
+ if (lineLength == 0) {
+ // Skip the specified number of scan lines
+ int numLines = f.readByte();
+ destPos.y += getScaledVal(numLines, scaleMaskY);
+ yCtr -= numLines;
+ continue;
+ }
+
+ // Roll the scale mask
+ uint bit = (scaleMaskY >> 15) & 1;
+ scaleMaskY = ((scaleMaskY & 0x7fff) << 1) + bit;
+
+ if (!bit) {
+ // Not a line to be drawn due to scaling down
+ f.skip(lineLength);
+ } else if (destPos.y < bounds.top || destPos.y >= bounds.bottom) {
+ // Skip over the bytes of the line
+ f.skip(lineLength);
+ destPos.y++;
+ } else {
+ scaleMaskX = scaleMaskXCopy;
+ xOffset = f.readByte();
+
+ // Initialize the array to hold the temporary data for the line. We do this to make it simpler
+ // to handle both deciding which pixels to draw in a scaled image, as well as when images
+ // have been horizontally flipped
+ int tempLine[SCREEN_WIDTH];
+ Common::fill(&tempLine[0], &tempLine[SCREEN_WIDTH], -1);
+ int *lineP = flipped ? &tempLine[width - 1 - xOffset] : &tempLine[xOffset];
+
+ // Build up the line
+ int byteCount, opr1, opr2;
+ int32 pos;
+ for (byteCount = 1; byteCount < lineLength; ) {
+ // The next byte is an opcode that determines what operators are to follow and how to interpret them.
+ int opcode = f.readByte(); ++byteCount;
+
+ // Decode the opcode
+ int len = opcode & 0x1F;
+ int cmd = (opcode & 0xE0) >> 5;
+
+ switch (cmd) {
+ case 0: // The following len + 1 bytes are stored as indexes into the color table.
+ case 1: // The following len + 33 bytes are stored as indexes into the color table.
+ for (int i = 0; i < opcode + 1; ++i, ++byteCount) {
+ byte b = f.readByte();
+ *lineP = b;
+ lineP += xInc;
+ }
+ break;
+
+ case 2: // The following byte is an index into the color table, draw it len + 3 times.
+ opr1 = f.readByte(); ++byteCount;
+ for (int i = 0; i < len + 3; ++i) {
+ *lineP = opr1;
+ lineP += xInc;
+ }
+ break;
+
+ case 3: // Stream copy command.
+ opr1 = f.readUint16LE(); byteCount += 2;
+ pos = f.pos();
+ f.seek(-opr1, SEEK_CUR);
+
+ for (int i = 0; i < len + 4; ++i) {
+ *lineP = f.readByte();
+ lineP += xInc;
+ }
+
+ f.seek(pos, SEEK_SET);
+ break;
+
+ case 4: // The following two bytes are indexes into the color table, draw the pair len + 2 times.
+ opr1 = f.readByte(); ++byteCount;
+ opr2 = f.readByte(); ++byteCount;
+ for (int i = 0; i < len + 2; ++i) {
+ *lineP = opr1;
+ lineP += xInc;
+ *lineP = opr2;
+ lineP += xInc;
+ }
+ break;
+
+ case 5: // Skip len + 1 pixels
+ lineP += (len + 1) * xInc;
+ break;
+
+ case 6: // Pattern command.
+ case 7:
+ // The pattern command has a different opcode format
+ len = opcode & 0x07;
+ cmd = (opcode >> 2) & 0x0E;
+
+ opr1 = f.readByte(); ++byteCount;
+ for (int i = 0; i < len + 3; ++i) {
+ *lineP = opr1;
+ lineP += xInc;
+ opr1 += PATTERN_STEPS[cmd + (i % 2)];
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ assert(byteCount == lineLength);
+
+ drawBounds.top = MIN(drawBounds.top, destPos.y);
+ drawBounds.bottom = MAX(drawBounds.bottom, destPos.y);
+
+ // Handle drawing out the line
+ byte *destP = (byte *)dest.getBasePtr(destPos.x, destPos.y);
+ int16 xp = destPos.x;
+ lineP = &tempLine[0];
+
+ for (int xCtr = 0; xCtr < width; ++xCtr, ++lineP) {
+ bit = (scaleMaskX >> 15) & 1;
+ scaleMaskX = ((scaleMaskX & 0x7fff) << 1) + bit;
+
+ if (bit) {
+ // Check whether there's a pixel to write, and we're within the allowable bounds. Note that for
+ // the SPRFLAG_SCENE_CLIPPED or when scale == 0x8000, we also have an extra horizontal bounds check
+ if (*lineP != -1 && xp >= bounds.left && xp < bounds.right &&
+ ((!(flags & SPRFLAG_SCENE_CLIPPED) && !enlarge) || (xp >= SCENE_CLIP_LEFT && xp < SCENE_CLIP_RIGHT))) {
+ drawBounds.left = MIN(drawBounds.left, xp);
+ drawBounds.right = MAX(drawBounds.right, xp);
+ *destP = (byte)*lineP;
+ if (enlarge)
+ *(destP + SCREEN_WIDTH) = (byte)*lineP;
+ }
+
+ ++destP;
+ ++xp;
+ }
+ }
+
+ ++destPos.y;
+ if (enlarge)
+ ++destPos.y;
+ }
+ }
+
+ if (drawBounds.isValidRect()) {
+ drawBounds.clip(Common::Rect(0, 0, dest.w, dest.h));
+ if (!drawBounds.isEmpty())
+ dest.addDirtyRect(drawBounds);
+ }
+}
+
+void SpriteResource::draw(XSurface &dest, int frame, const Common::Point &destPos,
+ int flags, int scale) {
+ draw(dest, frame, destPos, Common::Rect(0, 0, dest.w, dest.h), flags, scale);
+}
+
+void SpriteResource::draw(Window &dest, int frame, const Common::Point &destPos,
+ int flags, int scale) {
+ draw(dest, frame, destPos, dest.getBounds(), flags, scale);
+}
+
+void SpriteResource::draw(XSurface &dest, int frame, const Common::Point &destPos,
+ const Common::Rect &bounds, int flags, int scale) {
+
+ drawOffset(dest, _index[frame]._offset1, destPos, bounds, flags, scale);
+ if (_index[frame]._offset2)
+ drawOffset(dest, _index[frame]._offset2, destPos, bounds, flags, scale);
+}
+
+void SpriteResource::draw(XSurface &dest, int frame) {
+ draw(dest, frame, Common::Point());
+}
+
+uint SpriteResource::getScaledVal(int xy, uint16 &scaleMask) {
+ if (!xy)
+ return 0;
+
+ uint result = 0;
+ for (int idx = 0; idx < xy; ++idx) {
+ uint bit = (scaleMask >> 15) & 1;
+ scaleMask = ((scaleMask & 0x7fff) << 1) + bit;
+ result += bit;
+ }
+
+ return result;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/sprites.h b/engines/xeen/sprites.h
new file mode 100644
index 0000000000..9a241c2100
--- /dev/null
+++ b/engines/xeen/sprites.h
@@ -0,0 +1,116 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_SPRITES_H
+#define XEEN_SPRITES_H
+
+#include "common/scummsys.h"
+#include "common/array.h"
+#include "common/file.h"
+#include "graphics/surface.h"
+#include "xeen/xsurface.h"
+
+namespace Xeen {
+
+class XeenEngine;
+class Window;
+
+enum SpriteFlags { SPRFLAG_SCENE_CLIPPED = 0x2000, SPRFLAG_4000 = 0x4000,
+ SPRFLAG_HORIZ_FLIPPED = 0x8000, SPRFLAG_RESIZE = 0x10000 };
+
+class SpriteResource {
+private:
+ struct IndexEntry {
+ uint16 _offset1, _offset2;
+ };
+ Common::Array<IndexEntry> _index;
+ int32 _filesize;
+ byte *_data;
+ bool _lineDist[320];
+ int _scaledWidth, _scaledHeight;
+
+ /**
+ * Load a sprite resource from a stream
+ */
+ void load(Common::SeekableReadStream &f);
+
+ /**
+ * Draw the sprite onto the given surface
+ */
+ void draw(XSurface &dest, int frame, const Common::Point &destPos,
+ const Common::Rect &bounds, int flags = 0, int scale = 0);
+
+ /**
+ * Draw a sprite frame based on a passed offset into the data stream
+ */
+ void drawOffset(XSurface &dest, uint16 offset, const Common::Point &pt,
+ const Common::Rect &bounds, int flags, int scale);
+
+ /**
+ * Scale a co-ordinate value based on the passed scaling mask
+ */
+ static uint getScaledVal(int xy, uint16 &scaleMask);
+public:
+ SpriteResource();
+ SpriteResource(const Common::String &filename);
+
+ virtual ~SpriteResource();
+
+ /**
+ * Copy operator for duplicating a sprite resource
+ */
+ SpriteResource &operator=(const SpriteResource &src);
+
+ /**
+ * Load a sprite resource from a given file
+ */
+ void load(const Common::String &filename);
+
+ /**
+ * Load a sprite resource from a given file and archive
+ */
+ void load(const Common::String &filename, Common::Archive &archive);
+
+ /**
+ * Clears the sprite resource
+ */
+ void clear();
+
+ void draw(XSurface &dest, int frame, const Common::Point &destPos,
+ int flags = 0, int scale = 0);
+
+ void draw(Window &dest, int frame, const Common::Point &destPos,
+ int flags = 0, int scale = 0);
+
+ /**
+ * Draw the sprite onto the given surface
+ */
+ void draw(XSurface &dest, int frame);
+
+ int size() const { return _index.size(); }
+
+ bool empty() const { return _index.size() == 0; }
+};
+
+} // End of namespace Xeen
+
+#endif /* MADS_SPRITES_H */
diff --git a/engines/xeen/town.cpp b/engines/xeen/town.cpp
new file mode 100644
index 0000000000..cc83563de2
--- /dev/null
+++ b/engines/xeen/town.cpp
@@ -0,0 +1,1310 @@
+/* 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 "xeen/town.h"
+#include "xeen/dialogs_input.h"
+#include "xeen/dialogs_items.h"
+#include "xeen/dialogs_query.h"
+#include "xeen/dialogs_spells.h"
+#include "xeen/resources.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+Town::Town(XeenEngine *vm) : _vm(vm) {
+ Common::fill(&_arr1[0], &_arr1[6], 0);
+ _townMaxId = 0;
+ _townActionId = 0;
+ _drawFrameIndex = 0;
+ _currentCharLevel = 0;
+ _v1 = 0;
+ _v2 = 0;
+ _donation = 0;
+ _healCost = 0;
+ _v5 = _v6 = 0;
+ _v10 = _v11 = 0;
+ _v12 = _v13 = 0;
+ _v14 = 0;
+ _v20 = 0;
+ _v21 = 0;
+ _v22 = 0;
+ _v23 = 0;
+ _v24 = 0;
+ _dayOfWeek = 0;
+ _uncurseCost = 0;
+ _flag1 = false;
+ _experienceToNextLevel = 0;
+ _drawCtr1 = _drawCtr2 = 0;
+}
+
+void Town::loadStrings(const Common::String &name) {
+ File f(name);
+ _textStrings.clear();
+ while (f.pos() < f.size())
+ _textStrings.push_back(f.readString());
+ f.close();
+}
+
+int Town::townAction(int actionId) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ if (actionId == 12) {
+ pyramidEvent();
+ return 0;
+ }
+
+ _townMaxId = TOWN_MAXES[_vm->_files->_isDarkCc][actionId];
+ _townActionId = actionId;
+ _drawFrameIndex = 0;
+ _v1 = 0;
+ _townPos = Common::Point(8, 8);
+ intf._overallFrame = 0;
+
+ // This area sets up the GUI buttos and startup sample to play for the
+ // given town action
+ Common::String vocName = "hello1.voc";
+ clearButtons();
+ _icons1.clear();
+ _icons2.clear();
+
+ switch (actionId) {
+ case 0:
+ // Bank
+ _icons1.load("bank.icn");
+ _icons2.load("bank2.icn");
+ addButton(Common::Rect(234, 108, 259, 128), Common::KEYCODE_d, &_icons1);
+ addButton(Common::Rect(261, 108, 285, 128), Common::KEYCODE_w, &_icons1);
+ addButton(Common::Rect(288, 108, 312, 128), Common::KEYCODE_ESCAPE, &_icons1);
+ intf._overallFrame = 1;
+
+ sound.playSample(nullptr, 0);
+ vocName = isDarkCc ? "bank1.voc" : "banker.voc";
+ break;
+
+ case 1:
+ // Blacksmith
+ _icons1.load("esc.icn");
+ addButton(Common::Rect(261, 100, 285, 120), Common::KEYCODE_ESCAPE, &_icons1);
+ addButton(Common::Rect(234, 54, 308, 62), 0);
+ addButton(Common::Rect(234, 64, 308, 72), Common::KEYCODE_b);
+ addButton(Common::Rect(234, 74, 308, 82), 0);
+ addButton(Common::Rect(234, 84, 308, 92), 0);
+
+ sound.playSample(nullptr, 0);
+ vocName = isDarkCc ? "see2.voc" : "whaddayo.voc";
+ break;
+
+ case 2:
+ // Guild
+ loadStrings("spldesc.bin");
+ _icons1.load("esc.icn");
+ addButton(Common::Rect(261, 100, 285, 120), Common::KEYCODE_ESCAPE, &_icons1);
+ addButton(Common::Rect(234, 54, 308, 62), 0);
+ addButton(Common::Rect(234, 64, 308, 72), Common::KEYCODE_b);
+ addButton(Common::Rect(234, 74, 308, 82), Common::KEYCODE_s);
+ addButton(Common::Rect(234, 84, 308, 92), 0);
+ _vm->_mode = MODE_17;
+
+ sound.playSample(nullptr, 0);
+ vocName = isDarkCc ? "parrot1.voc" : "guild10.voc";
+ break;
+
+ case 3:
+ // Tavern
+ loadStrings("tavern.bin");
+ _icons1.load("tavern.icn");
+ addButton(Common::Rect(281, 108, 305, 128), Common::KEYCODE_ESCAPE, &_icons1);
+ addButton(Common::Rect(242, 108, 266, 128), Common::KEYCODE_s, &_icons1);
+ addButton(Common::Rect(234, 54, 308, 62), Common::KEYCODE_d);
+ addButton(Common::Rect(234, 64, 308, 72), Common::KEYCODE_f);
+ addButton(Common::Rect(234, 74, 308, 82), Common::KEYCODE_t);
+ addButton(Common::Rect(234, 84, 308, 92), Common::KEYCODE_r);
+ _vm->_mode = MODE_17;
+
+ sound.playSample(nullptr, 0);
+ vocName = isDarkCc ? "hello1.voc" : "hello.voc";
+ break;
+
+ case 4:
+ // Temple
+ _icons1.load("esc.icn");
+ addButton(Common::Rect(261, 100, 285, 120), Common::KEYCODE_ESCAPE, &_icons1);
+ addButton(Common::Rect(234, 54, 308, 62), Common::KEYCODE_h);
+ addButton(Common::Rect(234, 64, 308, 72), Common::KEYCODE_d);
+ addButton(Common::Rect(234, 74, 308, 82), Common::KEYCODE_u);
+ addButton(Common::Rect(234, 84, 308, 92), 0);
+
+ sound.playSample(nullptr, 0);
+ vocName = isDarkCc ? "help2.voc" : "maywe2.voc";
+ break;
+
+ case 5:
+ // Training
+ Common::fill(&_arr1[0], &_arr1[6], 0);
+ _v2 = 0;
+
+ _icons1.load("train.icn");
+ addButton(Common::Rect(281, 108, 305, 128), Common::KEYCODE_ESCAPE, &_icons1);
+ addButton(Common::Rect(242, 108, 266, 128), Common::KEYCODE_t);
+
+ sound.playSample(nullptr, 0);
+ vocName = isDarkCc ? "training.voc" : "youtrn1.voc";
+ break;
+
+ case 6:
+ // Arena event
+ arenaEvent();
+ return false;
+
+ case 8:
+ // Reaper event
+ reaperEvent();
+ return false;
+
+ case 9:
+ // Golem event
+ golemEvent();
+ return false;
+
+ case 10:
+ case 13:
+ dwarfEvent();
+ return false;
+
+ case 11:
+ sphinxEvent();
+ return false;
+
+ default:
+ break;
+ }
+
+ sound.loadMusic(TOWN_ACTION_MUSIC[actionId], 223);
+
+ _townSprites.resize(TOWN_ACTION_FILES[isDarkCc][actionId]);
+ for (uint idx = 0; idx < _townSprites.size(); ++idx) {
+ Common::String shapesName = Common::String::format("%s%d.twn",
+ TOWN_ACTION_SHAPES[actionId], idx + 1);
+ _townSprites[idx].load(shapesName);
+ }
+
+ Character *charP = &party._activeParty[0];
+ Common::String title = createTownText(*charP);
+ intf._face1UIFrame = intf._face2UIFrame = 0;
+ intf._dangerSenseUIFrame = 0;
+ intf._spotDoorsUIFrame = 0;
+ intf._levitateUIFrame = 0;
+
+ _townSprites[_drawFrameIndex / 8].draw(screen, _drawFrameIndex % 8, _townPos);
+ if (actionId == 0 && isDarkCc) {
+ _townSprites[4].draw(screen, _vm->getRandomNumber(13, 18),
+ Common::Point(8, 30));
+ }
+
+ intf.assembleBorder();
+
+ // Open up the window and write the string
+ screen._windows[10].open();
+ screen._windows[10].writeString(title);
+ drawButtons(&screen);
+
+ screen._windows[0].update();
+ intf.highlightChar(0);
+ drawTownAnim(1);
+
+ if (actionId == 0)
+ intf._overallFrame = 2;
+
+ File voc(vocName);
+ sound.playSample(&voc, 1);
+
+ do {
+ townWait();
+ charP = doTownOptions(charP);
+ screen._windows[10].writeString(title);
+ drawButtons(&screen);
+ } while (!_vm->shouldQuit() && _buttonValue != Common::KEYCODE_ESCAPE);
+
+ switch (actionId) {
+ case 1:
+ // Leave blacksmith
+ if (isDarkCc) {
+ sound.playSample(nullptr, 0);
+ File f("come1.voc");
+ sound.playSample(&f, 1);
+ }
+ break;
+
+ case 3: {
+ // Leave Tavern
+ sound.playSample(nullptr, 0);
+ File f(isDarkCc ? "gdluck1.voc" : "goodbye.voc");
+ sound.playSample(&f, 1);
+
+ map.mazeData()._mazeNumber = party._mazeId;
+ break;
+ }
+ default:
+ break;
+ }
+
+ int result;
+ if (party._mazeId != 0) {
+ map.load(party._mazeId);
+ _v1 += 1440;
+ party.addTime(_v1);
+ result = 0;
+ } else {
+ _vm->_saves->saveChars();
+ result = 2;
+ }
+
+ for (uint idx = 0; idx < _townSprites.size(); ++idx)
+ _townSprites[idx].clear();
+ intf.mainIconsPrint();
+ _buttonValue = 0;
+
+ return result;
+}
+
+int Town::townWait() {
+ EventsManager &events = *_vm->_events;
+
+ _buttonValue = 0;
+ while (!_vm->shouldQuit() && !_buttonValue) {
+ events.updateGameCounter();
+ while (!_vm->shouldQuit() && !_buttonValue && events.timeElapsed() < 3) {
+ events.pollEventsAndWait();
+ checkEvents(_vm);
+ }
+ if (!_buttonValue)
+ drawTownAnim(!_vm->_screen->_windows[11]._enabled);
+ }
+
+ return _buttonValue;
+}
+
+void Town::pyramidEvent() {
+ error("TODO: pyramidEvent");
+}
+
+void Town::arenaEvent() {
+ error("TODO: arenaEvent");
+}
+
+void Town::reaperEvent() {
+ error("TODO: repearEvent");
+}
+
+void Town::golemEvent() {
+ error("TODO: golemEvent");
+}
+
+void Town::sphinxEvent() {
+ error("TODO: sphinxEvent");
+}
+
+void Town::dwarfEvent() {
+ error("TODO: dwarfEvent");
+}
+
+Common::String Town::createTownText(Character &ch) {
+ Party &party = *_vm->_party;
+ Common::String msg;
+
+ switch (_townActionId) {
+ case 0:
+ // Bank
+ return Common::String::format(BANK_TEXT,
+ XeenEngine::printMil(party._bankGold).c_str(),
+ XeenEngine::printMil(party._bankGems).c_str(),
+ XeenEngine::printMil(party._gold).c_str(),
+ XeenEngine::printMil(party._gems).c_str());
+ case 1:
+ // Blacksmith
+ return Common::String::format(BLACKSMITH_TEXT,
+ XeenEngine::printMil(party._gold).c_str());
+
+ case 2:
+ // Guild
+ return !ch.guildMember() ? GUILD_NOT_MEMBER_TEXT :
+ Common::String::format(GUILD_TEXT, ch._name.c_str());
+
+ case 3:
+ // Tavern
+ return Common::String::format(TAVERN_TEXT, ch._name.c_str(),
+ FOOD_AND_DRINK, XeenEngine::printMil(party._gold).c_str());
+
+ case 4:
+ // Temple
+ _donation = 0;
+ _uncurseCost = 0;
+ _v5 = 0;
+ _v6 = 0;
+ _healCost = 0;
+
+ if (party._mazeId == (_vm->_files->_isDarkCc ? 29 : 28)) {
+ _v10 = _v11 = _v12 = _v13 = 0;
+ _v14 = 10;
+ } else if (party._mazeId == (_vm->_files->_isDarkCc ? 31 : 30)) {
+ _v13 = 10;
+ _v12 = 50;
+ _v11 = 500;
+ _v10 = 100;
+ _v14 = 25;
+ } else if (party._mazeId == (_vm->_files->_isDarkCc ? 37 : 73)) {
+ _v13 = 20;
+ _v12 = 100;
+ _v11 = 1000;
+ _v10 = 200;
+ _v14 = 50;
+ } else if (_vm->_files->_isDarkCc || party._mazeId == 49) {
+ _v13 = 100;
+ _v12 = 500;
+ _v11 = 5000;
+ _v10 = 300;
+ _v14 = 100;
+ }
+
+ _currentCharLevel = ch.getCurrentLevel();
+ if (ch._currentHp < ch.getMaxHP()) {
+ _healCost = _currentCharLevel * 10 + _v13;
+ }
+
+ for (int attrib = HEART_BROKEN; attrib <= UNCONSCIOUS; ++attrib) {
+ if (ch._conditions[attrib])
+ _healCost += _currentCharLevel * 10;
+ }
+
+ _v6 = 0;
+ if (ch._conditions[DEAD]) {
+ _v6 += (_currentCharLevel * 100) + (ch._conditions[DEAD] * 50) + _v12;
+ }
+ if (ch._conditions[STONED]) {
+ _v6 += (_currentCharLevel * 100) + (ch._conditions[STONED] * 50) + _v12;
+ }
+ if (ch._conditions[ERADICATED]) {
+ _v5 = (_currentCharLevel * 1000) + (ch._conditions[ERADICATED] * 500) + _v11;
+ }
+
+ for (int idx = 0; idx < 9; ++idx) {
+ _uncurseCost |= ch._weapons[idx]._bonusFlags & 0x40;
+ _uncurseCost |= ch._armor[idx]._bonusFlags & 0x40;
+ _uncurseCost |= ch._accessories[idx]._bonusFlags & 0x40;
+ _uncurseCost |= ch._misc[idx]._bonusFlags & 0x40;
+ }
+
+ if (_uncurseCost || ch._conditions[CURSED])
+ _v5 = (_currentCharLevel * 20) + _v10;
+
+ _donation = _flag1 ? 0 : _v14;
+ _healCost += _v6 + _v5;
+
+ return Common::String::format(TEMPLE_TEXT, ch._name.c_str(),
+ _healCost, _donation, XeenEngine::printK(_uncurseCost).c_str(),
+ XeenEngine::printMil(party._gold).c_str());
+
+ case 5:
+ // Training
+ if (_vm->_files->_isDarkCc) {
+ switch (party._mazeId) {
+ case 29:
+ _v20 = 30;
+ break;
+ case 31:
+ _v20 = 50;
+ break;
+ case 37:
+ _v20 = 200;
+ break;
+ default:
+ _v20 = 100;
+ break;
+ }
+ } else {
+ switch (party._mazeId) {
+ case 28:
+ _v20 = 10;
+ break;
+ case 30:
+ _v20 = 15;
+ break;
+ default:
+ _v20 = 20;
+ break;
+ }
+ }
+
+ _experienceToNextLevel = ch.experienceToNextLevel();
+
+ if (_experienceToNextLevel >= 0x10000 && ch._level._permanent < _v20) {
+ int nextLevel = ch._level._permanent + 1;
+ return Common::String::format(EXPERIENCE_FOR_LEVEL,
+ ch._name.c_str(), _experienceToNextLevel, nextLevel);
+ } else if (ch._level._permanent >= 20) {
+ _experienceToNextLevel = 1;
+ msg = Common::String::format(LEARNED_ALL, ch._name.c_str());
+ } else {
+ msg = Common::String::format(ELIGIBLE_FOR_LEVEL,
+ ch._name.c_str(), ch._level._permanent + 1);
+ }
+
+ return Common::String::format(TRAINING_TEXT,
+ XeenEngine::printMil(party._gold).c_str());
+
+ default:
+ return "";
+ }
+}
+
+Character *Town::doTownOptions(Character *c) {
+ switch (_townActionId) {
+ case 0:
+ // Bank
+ c = doBankOptions(c);
+ break;
+ case 1:
+ // Blacksmith
+ c = doBlacksmithOptions(c);
+ break;
+ case 2:
+ // Guild
+ c = doGuildOptions(c);
+ break;
+ case 3:
+ // Tavern
+ c = doTavernOptions(c);
+ break;
+ case 4:
+ // Temple
+ c = doTempleOptions(c);
+ case 5:
+ // Training
+ c = doTrainingOptions(c);
+ break;
+ default:
+ break;
+ }
+
+ return c;
+}
+
+Character *Town::doBankOptions(Character *c) {
+ if (_buttonValue == Common::KEYCODE_d)
+ _buttonValue = 0;
+ else if (_buttonValue == Common::KEYCODE_w)
+ _buttonValue = 1;
+ else
+ return c;
+
+ depositWithdrawl(_buttonValue);
+ return c;
+}
+
+Character *Town::doBlacksmithOptions(Character *c) {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+
+ if (_buttonValue >= Common::KEYCODE_F1 && _buttonValue <= Common::KEYCODE_F6) {
+ // Switch character
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ c = &party._activeParty[_buttonValue];
+ intf.highlightChar(_buttonValue);
+ }
+ }
+ else if (_buttonValue == Common::KEYCODE_b) {
+ c = ItemsDialog::show(_vm, c, ITEMMODE_BLACKSMITH);
+ _buttonValue = 0;
+ }
+
+ return c;
+}
+
+Character *Town::doGuildOptions(Character *c) {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ if (_buttonValue >= Common::KEYCODE_F1 && _buttonValue <= Common::KEYCODE_F6) {
+ // Switch character
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ c = &party._activeParty[_buttonValue];
+ intf.highlightChar(_buttonValue);
+
+ if (!c->guildMember()) {
+ sound.playSample(nullptr, 0);
+ intf._overallFrame = 5;
+ File f(isDarkCc ? "skull1.voc" : "guild11.voc");
+ sound.playSample(&f, 1);
+ }
+ }
+ } else if (_buttonValue == Common::KEYCODE_s) {
+ if (c->guildMember())
+ c = SpellsDialog::show(_vm, nullptr, c, 0x80);
+ _buttonValue = 0;
+ } else if (_buttonValue == Common::KEYCODE_c) {
+ if (!c->noActions()) {
+ if (c->guildMember())
+ c = SpellsDialog::show(_vm, nullptr, c, 0);
+ _buttonValue = 0;
+ }
+ }
+
+ return c;
+}
+
+Character *Town::doTavernOptions(Character *c) {
+ Interface &intf = *_vm->_interface;
+ Map &map = *_vm->_map;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ Screen &screen = *_vm->_screen;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+ int idx = 0;
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ // Switch character
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ c = &party._activeParty[_buttonValue];
+ intf.highlightChar(_buttonValue);
+ _v21 = 0;
+ }
+ break;
+ case Common::KEYCODE_d:
+ // Drink
+ if (!c->noActions()) {
+ if (party.subtract(0, 1, 0, WT_2)) {
+ sound.playSample(nullptr, 0);
+ File f("gulp.voc");
+ sound.playSample(&f, 0);
+ _v21 = 1;
+
+ screen._windows[10].writeString(Common::String::format(TAVERN_TEXT,
+ c->_name.c_str(), GOOD_STUFF,
+ XeenEngine::printMil(party._gold).c_str()));
+ drawButtons(&screen._windows[0]);
+ screen._windows[10].update();
+
+ if (_vm->getRandomNumber(100) < 26) {
+ ++c->_conditions[DRUNK];
+ intf.drawParty(true);
+ sound.playFX(28);
+ }
+
+ townWait();
+ }
+ }
+ break;
+ case Common::KEYCODE_f: {
+ // Food
+ if (party._mazeId == (isDarkCc ? 29 : 28)) {
+ _v22 = party._activeParty.size() * 15;
+ _v23 = 10;
+ idx = 0;
+ } else if (isDarkCc && party._mazeId == 31) {
+ _v22 = party._activeParty.size() * 60;
+ _v23 = 100;
+ idx = 1;
+ } else if (!isDarkCc && party._mazeId == 30) {
+ _v22 = party._activeParty.size() * 50;
+ _v23 = 50;
+ idx = 1;
+ } else if (isDarkCc) {
+ _v22 = party._activeParty.size() * 120;
+ _v23 = 250;
+ idx = 2;
+ } else if (party._mazeId == 49) {
+ _v22 = party._activeParty.size() * 120;
+ _v23 = 100;
+ idx = 2;
+ } else {
+ _v22 = party._activeParty.size() * 15;
+ _v23 = 10;
+ idx = 0;
+ }
+
+ Common::String msg = _textStrings[(isDarkCc ? 60 : 75) + idx];
+ screen._windows[10].close();
+ screen._windows[12].open();
+ screen._windows[12].writeString(msg);
+ screen._windows[12].update();
+
+ if (YesNo::show(_vm, false, true)) {
+ if (party._food >= _v22) {
+ ErrorScroll::show(_vm, FOOD_PACKS_FULL, WT_2);
+ } else if (party.subtract(0, _v23, 0, WT_2)) {
+ party._food = _v22;
+ sound.playSample(nullptr, 0);
+ File f(isDarkCc ? "thanks2.voc" : "thankyou.voc");
+ sound.playSample(&f, 1);
+ }
+ }
+
+ screen._windows[12].close();
+ screen._windows[10].open();
+ _buttonValue = 0;
+ break;
+ }
+
+ case Common::KEYCODE_r: {
+ // Rumors
+ if (party._mazeId == (isDarkCc ? 29 : 28)) {
+ idx = 0;
+ } else if (party._mazeId == (isDarkCc ? 31 : 30)) {
+ idx = 10;
+ } else if (isDarkCc || party._mazeId == 49) {
+ idx = 20;
+ }
+
+ Common::String msg = Common::String::format("\x03""c\x0B""012%s",
+ _textStrings[(party._day % 10) + idx].c_str());
+ Window &w = screen._windows[12];
+ w.open();
+ w.writeString(msg);
+ w.update();
+
+ townWait();
+ w.close();
+ break;
+ }
+
+ case Common::KEYCODE_s: {
+ // Sign In
+ idx = isDarkCc ? (party._mazeId - 29) >> 1 : party._mazeId - 28;
+ assert(idx >= 0);
+ party._mazePosition.x = TAVERN_EXIT_LIST[isDarkCc ? 1 : 0][_townActionId][idx][0];
+ party._mazePosition.y = TAVERN_EXIT_LIST[isDarkCc ? 1 : 0][_townActionId][idx][1];
+
+ if (!isDarkCc || party._mazeId == 29)
+ party._mazeDirection = DIR_WEST;
+ else if (party._mazeId == 31)
+ party._mazeDirection = DIR_EAST;
+ else
+ party._mazeDirection = DIR_SOUTH;
+
+ party._priorMazeId = party._mazeId;
+ for (idx = 0; idx < (int)party._activeParty.size(); ++idx) {
+ party._activeParty[idx]._savedMazeId = party._mazeId;
+ party._activeParty[idx]._xeenSide = map._loadDarkSide;
+ }
+
+ party.addTime(1440);
+ party._mazeId = 0;
+ _vm->_quitMode = 2;
+ break;
+ }
+
+ case Common::KEYCODE_t:
+ if (!c->noActions()) {
+ if (!_v21) {
+ screen._windows[10].writeString(Common::String::format(TAVERN_TEXT,
+ c->_name.c_str(), HAVE_A_DRINK,
+ XeenEngine::printMil(party._gold).c_str()));
+ drawButtons(&screen);
+ screen._windows[10].update();
+ townWait();
+ } else {
+ _v21 = 0;
+ if (c->_conditions[DRUNK]) {
+ screen._windows[10].writeString(Common::String::format(TAVERN_TEXT,
+ c->_name.c_str(), YOURE_DRUNK,
+ XeenEngine::printMil(party._gold).c_str()));
+ drawButtons(&screen);
+ screen._windows[10].update();
+ townWait();
+ } else if (party.subtract(0, 1, 0, WT_2)) {
+ sound.playSample(nullptr, 0);
+ File f(isDarkCc ? "thanks2.voc" : "thankyou.voc");
+ sound.playSample(&f, 1);
+
+ if (party._mazeId == (isDarkCc ? 29 : 28)) {
+ _v24 = 30;
+ } else if (isDarkCc && party._mazeId == 31) {
+ _v24 = 40;
+ } else if (!isDarkCc && party._mazeId == 45) {
+ _v24 = 45;
+ } else if (!isDarkCc && party._mazeId == 49) {
+ _v24 = 60;
+ } else if (isDarkCc) {
+ _v24 = 50;
+ }
+
+ Common::String msg = _textStrings[map.mazeData()._tavernTips + _v24];
+ map.mazeData()._tavernTips = (map.mazeData()._tavernTips + 1) /
+ (isDarkCc ? 10 : 15);
+
+ Window &w = screen._windows[12];
+ w.open();
+ w.writeString(Common::String::format("\x03""c\x0B""012%s", msg.c_str()));
+ w.update();
+ townWait();
+ w.close();
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return c;
+}
+
+Character *Town::doTempleOptions(Character *c) {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ // Switch character
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ c = &party._activeParty[_buttonValue];
+ intf.highlightChar(_buttonValue);
+ _dayOfWeek = 0;
+ }
+ break;
+
+ case Common::KEYCODE_d:
+ if (_donation && party.subtract(0, _donation, 0, WT_2)) {
+ sound.playSample(nullptr, 0);
+ File f("coina.voc");
+ sound.playSample(&f, 1);
+ _dayOfWeek = (_dayOfWeek + 1) / 10;
+
+ if (_dayOfWeek == (party._day / 10)) {
+ party._clairvoyanceActive = true;
+ party._lightCount = 1;
+
+ int amt = _dayOfWeek ? _dayOfWeek : 10;
+ party._heroism = amt;
+ party._holyBonus = amt;
+ party._powerShield = amt;
+ party._blessed = amt;
+
+ intf.drawParty(true);
+ sound.playSample(nullptr, 0);
+ Voc voc("ahh.voc");
+ voc.play();
+ _flag1 = true;
+ _donation = 0;
+ }
+ }
+ break;
+
+ case Common::KEYCODE_h:
+ if (_healCost && party.subtract(0, _healCost, 0, WT_2)) {
+ c->_magicResistence._temporary = 0;
+ c->_energyResistence._temporary = 0;
+ c->_poisonResistence._temporary = 0;
+ c->_electricityResistence._temporary = 0;
+ c->_coldResistence._temporary = 0;
+ c->_fireResistence._temporary = 0;
+ c->_ACTemp = 0;
+ c->_level._temporary = 0;
+ c->_luck._temporary = 0;
+ c->_accuracy._temporary = 0;
+ c->_speed._temporary = 0;
+ c->_endurance._temporary = 0;
+ c->_personality._temporary = 0;
+ c->_intellect._temporary = 0;
+ c->_might._temporary = 0;
+ c->_currentHp = c->getMaxHP();
+ Common::fill(&c->_conditions[HEART_BROKEN], &c->_conditions[NO_CONDITION], 0);
+
+ _v1 = 1440;
+ intf.drawParty(true);
+ sound.playSample(nullptr, 0);
+ File f("ahh.voc");
+ sound.playSample(&f, 1);
+ }
+ break;
+
+ case Common::KEYCODE_u:
+ if (_uncurseCost && party.subtract(0, _uncurseCost, 0, WT_2)) {
+ for (int idx = 0; idx < 9; ++idx) {
+ c->_weapons[idx]._bonusFlags &= ~ITEMFLAG_CURSED;
+ c->_armor[idx]._bonusFlags &= ~ITEMFLAG_CURSED;
+ c->_accessories[idx]._bonusFlags &= ~ITEMFLAG_CURSED;
+ c->_misc[idx]._bonusFlags &= ~ITEMFLAG_CURSED;
+ }
+
+ _v1 = 1440;
+ intf.drawParty(true);
+ sound.playSample(nullptr, 0);
+ File f("ahh.voc");
+ sound.playSample(&f, 1);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return c;
+}
+
+Character *Town::doTrainingOptions(Character *c) {
+ Interface &intf = *_vm->_interface;
+ Party &party = *_vm->_party;
+ SoundManager &sound = *_vm->_sound;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ switch (_buttonValue) {
+ case Common::KEYCODE_F1:
+ case Common::KEYCODE_F2:
+ case Common::KEYCODE_F3:
+ case Common::KEYCODE_F4:
+ case Common::KEYCODE_F5:
+ case Common::KEYCODE_F6:
+ // Switch character
+ _buttonValue -= Common::KEYCODE_F1;
+ if (_buttonValue < (int)party._activeParty.size()) {
+ _v2 = _buttonValue;
+ c = &party._activeParty[_buttonValue];
+ intf.highlightChar(_buttonValue);
+ }
+ break;
+
+ case Common::KEYCODE_t:
+ if (_experienceToNextLevel) {
+ sound.playSample(nullptr, 0);
+ _drawFrameIndex = 0;
+
+ Common::String name;
+ if (c->_level._permanent >= _v20) {
+ name = isDarkCc ? "gtlost.voc" : "trainin1.voc";
+ } else {
+ name = isDarkCc ? "gtlost.voc" : "trainin0.voc";
+ }
+
+ File f(name);
+ sound.playSample(&f);
+
+ } else if (!c->noActions()) {
+ if (party.subtract(0, (c->_level._permanent * c->_level._permanent) * 10, 0, WT_2)) {
+ _drawFrameIndex = 0;
+ sound.playSample(nullptr, 0);
+ File f(isDarkCc ? "prtygd.voc" : "trainin2.voc");
+ sound.playSample(&f, 1);
+
+ c->_experience -= c->nextExperienceLevel() -
+ (c->getCurrentExperience() - c->_experience);
+ c->_level._permanent++;
+
+ if (!_arr1[_v2]) {
+ party.addTime(1440);
+ _arr1[_v2] = 1;
+ }
+
+ party.resetTemps();
+ c->_currentHp = c->getMaxHP();
+ c->_currentSp = c->getMaxSP();
+ intf.drawParty(true);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return c;
+}
+
+void Town::depositWithdrawl(int choice) {
+ Party &party = *_vm->_party;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ int gold, gems;
+
+ if (choice) {
+ gold = party._bankGold;
+ gems = party._bankGems;
+ } else {
+ gold = party._gold;
+ gems = party._gems;
+ }
+
+ for (uint idx = 0; idx < _buttons.size(); ++idx)
+ _buttons[idx]._sprites = &_icons2;
+ _buttons[0]._value = Common::KEYCODE_o;
+ _buttons[1]._value = Common::KEYCODE_e;
+ _buttons[2]._value = Common::KEYCODE_ESCAPE;
+
+ Common::String msg = Common::String::format(GOLD_GEMS,
+ DEPOSIT_WITHDRAWL[choice],
+ XeenEngine::printMil(gold).c_str(),
+ XeenEngine::printMil(gems).c_str());
+
+ screen._windows[35].open();
+ screen._windows[35].writeString(msg);
+ drawButtons(&screen._windows[35]);
+ screen._windows[35].update();
+
+ sound.playSample(nullptr, 0);
+ File voc("coina.voc");
+ bool flag = false;
+
+ do {
+ switch (townWait()) {
+ case Common::KEYCODE_o:
+ flag = false;
+ break;
+ case Common::KEYCODE_e:
+ flag = true;
+ break;
+ case Common::KEYCODE_ESCAPE:
+ break;
+ default:
+ continue;
+ }
+
+ if ((choice && !party._bankGems && flag) ||
+ (choice && !party._bankGold && !flag) ||
+ (!choice && !party._gems && flag) ||
+ (!choice && !party._gold && !flag)) {
+ party.notEnough(flag, choice, 1, WT_2);
+ } else {
+ screen._windows[35].writeString(AMOUNT);
+ int amount = NumericInput::show(_vm, 35, 10, 77);
+
+ if (amount) {
+ if (flag) {
+ if (party.subtract(true, amount, choice, WT_2)) {
+ if (choice) {
+ party._gems += amount;
+ } else {
+ party._bankGems += amount;
+ }
+ }
+ } else {
+ if (party.subtract(false, amount, choice, WT_2)) {
+ if (choice) {
+ party._gold += amount;
+ } else {
+ party._bankGold += amount;
+ }
+ }
+ }
+ }
+
+ if (choice) {
+ gold = party._bankGold;
+ gems = party._bankGems;
+ } else {
+ gold = party._gold;
+ gems = party._gems;
+ }
+
+ sound.playSample(&voc, 0);
+ msg = Common::String::format(GOLD_GEMS_2, DEPOSIT_WITHDRAWL[choice],
+ XeenEngine::printMil(gold).c_str(), XeenEngine::printMil(gems).c_str());
+ screen._windows[35].writeString(msg);
+ screen._windows[35].update();
+ }
+ // TODO
+ } while (!_vm->shouldQuit() && _buttonValue != Common::KEYCODE_ESCAPE);
+
+ for (uint idx = 0; idx < _buttons.size(); ++idx)
+ _buttons[idx]._sprites = &_icons1;
+ _buttons[0]._value = Common::KEYCODE_d;
+ _buttons[1]._value = Common::KEYCODE_w;
+ _buttons[2]._value = Common::KEYCODE_ESCAPE;
+}
+
+void Town::drawTownAnim(bool flag) {
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ bool isDarkCc = _vm->_files->_isDarkCc;
+
+ if (_townActionId == 1) {
+ if (sound.playSample(1, 0)) {
+ if (isDarkCc) {
+ _townSprites[_drawFrameIndex / 8].draw(screen, _drawFrameIndex % 8, _townPos);
+ _townSprites[2].draw(screen, _vm->getRandomNumber(11) == 1 ? 9 : 10,
+ Common::Point(34, 33));
+ _townSprites[2].draw(screen, _vm->getRandomNumber(5) + 3,
+ Common::Point(34, 54));
+ }
+ } else {
+ _townSprites[_drawFrameIndex / 8].draw(screen, _drawFrameIndex % 8, _townPos);
+ if (isDarkCc) {
+ _townSprites[2].draw(screen, _vm->getRandomNumber(11) == 1 ? 9 : 10,
+ Common::Point(34, 33));
+ }
+ }
+ } else {
+ if (!isDarkCc || _townActionId != 5) {
+ if (!_townSprites[_drawFrameIndex / 8].empty())
+ _townSprites[_drawFrameIndex / 8].draw(screen, _drawFrameIndex % 8, _townPos);
+ }
+ }
+
+ switch (_townActionId) {
+ case 0:
+ if (sound.playSample(1, 0) || (isDarkCc && intf._overallFrame)) {
+ if (isDarkCc) {
+ if (sound.playSample(1, 0) || intf._overallFrame == 1) {
+ _townSprites[4].draw(screen, _vm->getRandomNumber(13, 18),
+ Common::Point(8, 30));
+ } else if (intf._overallFrame > 1) {
+ _townSprites[4].draw(screen, 13 - intf._overallFrame++,
+ Common::Point(8, 30));
+ if (intf._overallFrame > 14)
+ intf._overallFrame = 0;
+ }
+ } else {
+ _townSprites[2].draw(screen, _vm->getRandomNumber(7, 11), Common::Point(8, 8));
+ }
+ }
+ break;
+
+ case 2:
+ if (sound.playSample(1, 0)) {
+ if (isDarkCc) {
+ if (intf._overallFrame) {
+ intf._overallFrame ^= 1;
+ _townSprites[6].draw(screen, intf._overallFrame, Common::Point(8, 106));
+ } else {
+ _townSprites[6].draw(screen, _vm->getRandomNumber(3), Common::Point(16, 48));
+ }
+ }
+ }
+ break;
+
+ case 3:
+ if (sound.playSample(1, 0) && isDarkCc) {
+ _townSprites[4].draw(screen, _vm->getRandomNumber(7), Common::Point(153, 49));
+ }
+ break;
+ case 4:
+ if (sound.playSample(1, 0)) {
+ _townSprites[3].draw(screen, _vm->getRandomNumber(2, 4), Common::Point(8, 8));
+
+ }
+ break;
+
+ case 5:
+ if (sound.playSample(1, 0)) {
+ if (isDarkCc) {
+ _townSprites[_drawFrameIndex / 8].draw(screen, _drawFrameIndex % 8, _townPos);
+ }
+ } else {
+ if (isDarkCc) {
+ _townSprites[0].draw(screen, ++intf._overallFrame % 8, Common::Point(8, 8));
+ _townSprites[5].draw(screen, _vm->getRandomNumber(5), Common::Point(61, 74));
+ } else {
+ _townSprites[1].draw(screen, _vm->getRandomNumber(8, 12), Common::Point(8, 8));
+ }
+ }
+ }
+
+ if (flag) {
+ intf._face1UIFrame = 0;
+ intf._face2UIFrame = 0;
+ intf._dangerSenseUIFrame = 0;
+ intf._spotDoorsUIFrame = 0;
+ intf._levitateUIFrame = 0;
+
+ intf.assembleBorder();
+ }
+
+ if (screen._windows[11]._enabled) {
+ _drawCtr1 = (_drawCtr1 + 1) % 2;
+ if (!_drawCtr1 || !_drawCtr2) {
+ _drawFrameIndex = 0;
+ _drawCtr2 = 0;
+ } else {
+ _drawFrameIndex = _vm->getRandomNumber(3);
+ }
+ } else {
+ _drawFrameIndex = (_drawFrameIndex + 1) % _townMaxId;
+ }
+
+ if (isDarkCc) {
+ if (_townActionId == 1 && (_drawFrameIndex == 4 || _drawFrameIndex == 13))
+ sound.playFX(45);
+
+ if (_townActionId == 5 && _drawFrameIndex == 23) {
+ File f("spit1.voc");
+ sound.playSample(&f, 0);
+ }
+ } else {
+ if (_townMaxId == 32 && _drawFrameIndex == 0)
+ _drawFrameIndex = 17;
+ if (_townMaxId == 26 && _drawFrameIndex == 0)
+ _drawFrameIndex = 20;
+ if (_townActionId == 1 && (_drawFrameIndex == 3 || _drawFrameIndex == 9))
+ sound.playFX(45);
+ }
+
+ screen._windows[3].update();
+}
+
+bool Town::isActive() const {
+ return _townSprites.size() > 0 && !_townSprites[0].empty();
+}
+
+void Town::clearSprites() {
+ _townSprites.clear();
+}
+
+/*------------------------------------------------------------------------*/
+
+bool TownMessage::show(XeenEngine *vm, int portrait, const Common::String &name,
+ const Common::String &text, int confirm) {
+ TownMessage *dlg = new TownMessage(vm);
+ bool result = dlg->execute(portrait, name, text, confirm);
+ delete dlg;
+
+ return result;
+}
+
+bool TownMessage::execute(int portrait, const Common::String &name, const Common::String &text,
+ int confirm) {
+ EventsManager &events = *_vm->_events;
+ Interface &intf = *_vm->_interface;
+ Screen &screen = *_vm->_screen;
+ Town &town = *_vm->_town;
+ Window &w = screen._windows[11];
+
+ town._townMaxId = 4;
+ town._townActionId = 7;
+ town._drawFrameIndex = 0;
+ town._townPos = Common::Point(23, 22);
+
+ if (!confirm)
+ loadButtons();
+
+ if (town._townSprites[0].empty()) {
+ town._townSprites[0].load(Common::String::format("face%02d.fac", portrait));
+ town._townSprites[1].load("frame.fac");
+ }
+
+ if (!w._enabled)
+ w.open();
+
+ int result = -1;
+ Common::String msgText = text;
+ for (;;) {
+ Common::String msg = Common::String::format("\r\v014\x03c\t125%s\t000\v054%s",
+ name.c_str(), msgText.c_str());
+ const char *msgEnd = w.writeString(msg.c_str());
+ int wordCount = 0;
+
+ for (const char *msgP = msg.c_str(); msgP < msgEnd; ++msgP) {
+ if (*msgP == ' ')
+ ++wordCount;
+ }
+
+ town._drawCtr2 = wordCount * 2;
+ town._townSprites[1].draw(screen, 0, Common::Point(16, 16));
+ town._townSprites[0].draw(screen, town._drawFrameIndex, Common::Point(23, 22));
+ w.update();
+
+ if (!msgEnd) {
+ // Doesn't look like the code here in original can ever be reached
+ assert(0);
+ }
+
+ if (confirm == 2) {
+ intf._face1State = intf._face2State = 2;
+ return false;
+ }
+
+ do {
+ events.clearEvents();
+ events.updateGameCounter();
+ if (msgEnd)
+ clearButtons();
+
+ do {
+ events.wait(3, true);
+ checkEvents(_vm);
+ if (_vm->shouldQuit())
+ return false;
+
+ town.drawTownAnim(false);
+ events.updateGameCounter();
+ } while (!_buttonValue);
+
+ if (msgEnd)
+ break;
+
+ if (!msgEnd) {
+ if (confirm || _buttonValue == Common::KEYCODE_ESCAPE ||
+ _buttonValue == Common::KEYCODE_n)
+ result = 0;
+ else if (_buttonValue == Common::KEYCODE_y)
+ result = 1;
+ }
+ } while (result == -1);
+
+ if (msgEnd) {
+ msgText = Common::String(msgEnd);
+ town._drawCtr2 = wordCount;
+ continue;
+ }
+ } while (result == -1);
+
+ intf._face1State = intf._face2State = 2;
+ if (!confirm)
+ intf.mainIconsPrint();
+
+ town._townSprites[0].clear();
+ town._townSprites[1].clear();
+ return result == 1;
+}
+
+void TownMessage::loadButtons() {
+ _iconSprites.load("confirm.icn");
+
+ addButton(Common::Rect(235, 75, 259, 95), Common::KEYCODE_y, &_iconSprites);
+ addButton(Common::Rect(260, 75, 284, 95), Common::KEYCODE_n, &_iconSprites);
+ addButton(Common::Rect(), Common::KEYCODE_ESCAPE);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/town.h b/engines/xeen/town.h
new file mode 100644
index 0000000000..f56f24ca73
--- /dev/null
+++ b/engines/xeen/town.h
@@ -0,0 +1,133 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_TOWN_H
+#define XEEN_TOWN_H
+
+#include "common/scummsys.h"
+#include "common/str-array.h"
+#include "xeen/dialogs.h"
+#include "xeen/dialogs_error.h"
+#include "xeen/party.h"
+
+namespace Xeen {
+
+class XeenEngine;
+class TownMessage;
+
+class Town: public ButtonContainer {
+ friend class TownMessage;
+private:
+ XeenEngine *_vm;
+ SpriteResource _icons1, _icons2;
+ Common::StringArray _textStrings;
+ Common::Array<SpriteResource> _townSprites;
+ int _townMaxId;
+ int _townActionId;
+ int _v1, _v2;
+ int _donation;
+ int _healCost;
+ int _v5, _v6;
+ int _v10, _v11, _v12;
+ int _v13, _v14;
+ uint _v20;
+ int _v21;
+ uint _v22;
+ int _v23;
+ int _v24;
+ int _dayOfWeek;
+ int _uncurseCost;
+ Common::Point _townPos;
+ int _arr1[6];
+ int _currentCharLevel;
+ bool _flag1;
+ uint _experienceToNextLevel;
+ int _drawFrameIndex;
+ int _drawCtr1, _drawCtr2;
+
+ void loadStrings(const Common::String &name);
+
+ void pyramidEvent();
+
+ void arenaEvent();
+
+ void reaperEvent();
+
+ void golemEvent();
+
+ void sphinxEvent();
+
+ void dwarfEvent();
+
+ Common::String createTownText(Character &ch);
+
+ int townWait();
+
+ Character *doTownOptions(Character *c);
+
+ Character *doBankOptions(Character *c);
+
+ Character *doBlacksmithOptions(Character *c);
+
+ Character *doGuildOptions(Character *c);
+
+ Character *doTavernOptions(Character *c);
+
+ Character *doTempleOptions(Character *c);
+
+ Character *doTrainingOptions(Character *c);
+
+ void depositWithdrawl(int choice);
+public:
+ Town(XeenEngine *vm);
+
+ int townAction(int actionId);
+
+ void drawTownAnim(bool flag);
+
+ /**
+ * Returns true if a town location (bank, blacksmith, etc.) is currently active
+ */
+ bool isActive() const;
+
+ void clearSprites();
+};
+
+class TownMessage : public ButtonContainer {
+private:
+ XeenEngine *_vm;
+ SpriteResource _iconSprites;
+
+ TownMessage(XeenEngine *vm) : ButtonContainer(), _vm(vm) {}
+
+ bool execute(int portrait, const Common::String &name,
+ const Common::String &text, int confirm);
+
+ void loadButtons();
+public:
+ static bool show(XeenEngine *vm, int portrait, const Common::String &name,
+ const Common::String &text, int confirm);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_SPELLS_H */
diff --git a/engines/xeen/worldofxeen/clouds_cutscenes.cpp b/engines/xeen/worldofxeen/clouds_cutscenes.cpp
new file mode 100644
index 0000000000..1d578bdbea
--- /dev/null
+++ b/engines/xeen/worldofxeen/clouds_cutscenes.cpp
@@ -0,0 +1,43 @@
+/* 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 "xeen/worldofxeen/clouds_cutscenes.h"
+#include "xeen/sound.h"
+
+namespace Xeen {
+
+bool CloudsCutscenes::showCloudsTitle() {
+ // TODO
+ return true;
+}
+
+bool CloudsCutscenes::showCloudsIntro() {
+ // TODO
+ return true;
+}
+
+bool CloudsCutscenes::showCloudsEnding() {
+ // TODO
+ return true;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/worldofxeen/clouds_cutscenes.h b/engines/xeen/worldofxeen/clouds_cutscenes.h
new file mode 100644
index 0000000000..2c2ed602ea
--- /dev/null
+++ b/engines/xeen/worldofxeen/clouds_cutscenes.h
@@ -0,0 +1,55 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_WORLDOFXEEN_CLOUDS_CUTSCENES_H
+#define XEEN_WORLDOFXEEN_CLOUDS_CUTSCENES_H
+
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+class XeenEngine;
+
+class CloudsCutscenes {
+private:
+ XeenEngine *_vm;
+public:
+ CloudsCutscenes(XeenEngine *vm) : _vm(vm) {}
+
+ /**
+ * Shows the Clouds of Xeen title screen
+ */
+ bool showCloudsTitle();
+
+ /**
+ * Shows the Clouds of Xeen intro sequence
+ */
+ bool showCloudsIntro();
+
+ /**
+ * Shows the Clouds of Xeen ending sequence
+ */
+ bool showCloudsEnding();
+};
+} // End of namespace Xeen
+
+#endif /* XEEN_WORLDOFXEEN_CLOUDS_CUTSCENES_H */
diff --git a/engines/xeen/worldofxeen/darkside_cutscenes.cpp b/engines/xeen/worldofxeen/darkside_cutscenes.cpp
new file mode 100644
index 0000000000..a5de6559c4
--- /dev/null
+++ b/engines/xeen/worldofxeen/darkside_cutscenes.cpp
@@ -0,0 +1,258 @@
+/* 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 "xeen/worldofxeen/darkside_cutscenes.h"
+#include "xeen/worldofxeen/worldofxeen.h"
+#include "xeen/sound.h"
+#include "xeen/xeen.h"
+
+namespace Xeen {
+
+bool DarkSideCutscenes::showDarkSideTitle() {
+ EventsManager &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+
+ // TODO: Starting method, and sound
+ //sub_28F40
+ screen.loadPalette("dark.pal");
+ SpriteResource nwc[4] = {
+ SpriteResource("nwc1.int"), SpriteResource("nwc2.int"),
+ SpriteResource("nwc3.int"), SpriteResource("nwc4.int")
+ };
+ Voc voc[3];
+ voc[0].open("dragon1.voc");
+ voc[1].open("dragon2.voc");
+ voc[2].open("dragon3.voc");
+
+ // Load backgrounds
+ screen.loadBackground("nwc1.raw");
+ screen.loadPage(0);
+ screen.loadBackground("nwc2.raw");
+ screen.loadPage(1);
+
+ // Draw the screen and fade it in
+ screen.horizMerge(0);
+ screen.draw();
+ screen.fadeIn(4);
+
+ // Initial loop for dragon roaring
+ int nwcIndex = 0, nwcFrame = 0;
+ for (int idx = 0; idx < 55 && !_vm->shouldQuit(); ++idx) {
+ // Render the next frame
+ events.updateGameCounter();
+ screen.vertMerge(0);
+ nwc[nwcIndex].draw(screen, nwcFrame);
+ screen.draw();
+
+ switch (idx) {
+ case 17:
+ voc[0].play();
+ break;
+ case 34:
+ case 44:
+ ++nwcIndex;
+ nwcFrame = 0;
+ break;
+ case 35:
+ voc[1].play();
+ break;
+ default:
+ ++nwcFrame;
+ }
+
+ if (events.wait(2, true))
+ return false;
+ }
+
+ // Loop for dragon using flyspray
+ for (int idx = 0; idx < 42 && !_vm->shouldQuit(); ++idx) {
+ events.updateGameCounter();
+ screen.vertMerge(SCREEN_HEIGHT);
+ nwc[3].draw(screen, idx);
+ screen.draw();
+
+ switch (idx) {
+ case 3:
+ sound.startMusic(40);
+ break;
+ case 11:
+ sound.startMusic(0);
+ case 27:
+ case 30:
+ sound.startMusic(3);
+ break;
+ case 31:
+ sound.proc2(voc[2]);
+ break;
+ case 33:
+ sound.startMusic(2);
+ break;
+ default:
+ break;
+ }
+
+ if (events.wait(2, true))
+ return false;
+ }
+
+ // Pause for a bit
+ if (events.wait(10, true))
+ return false;
+
+ voc[0].stop();
+ voc[1].stop();
+ voc[2].stop();
+ sound.stopMusic(95);
+
+ screen.loadBackground("jvc.raw");
+ screen.fadeOut(8);
+ screen.draw();
+ screen.fadeIn(4);
+
+ events.updateGameCounter();
+ events.wait(60, true);
+ return true;
+}
+
+bool DarkSideCutscenes::showDarkSideIntro() {
+ EventsManager &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+ const int XLIST1[] = {
+ 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 58, 60, 62
+ };
+ const int YLIST1[] = {
+ 0, 5, 10, 15, 20, 25, 30, 35, 40, 40, 39, 37, 35, 33, 31
+ };
+ const int XLIST2[] = {
+ 160, 155, 150, 145, 140, 135, 130, 125, 120, 115, 110, 105, 98, 90, 82
+ };
+
+ screen.fadeOut(8);
+ screen.loadBackground("pyramid2.raw");
+ screen.loadPage(0);
+ screen.loadPage(1);
+ screen.loadBackground("pyramid3.raw");
+ screen.saveBackground(1);
+
+ SpriteResource sprites[3] = {
+ SpriteResource("title.int"), SpriteResource("pyratop.int"), SpriteResource("pyramid.int")
+ };
+ Voc voc[2];
+ voc[0].open("pharoh1a.voc");
+ voc[1].open("pharoh1b.voc");
+
+ screen.vertMerge(SCREEN_HEIGHT);
+ screen.loadPage(0);
+ screen.loadPage(1);
+
+ // Show Might and Magic Darkside of Xeen title, and gradualy scroll
+ // the background vertically down to show the Pharoah's base
+ int yp = 0;
+ int frameNum = 0;
+ int idx1 = 0;
+ bool skipElapsed = false;
+ uint32 timeExpired = 0;
+ bool fadeFlag = true;
+
+ for (int yCtr = SCREEN_HEIGHT; yCtr > 0; ) {
+ events.updateGameCounter();
+ screen.vertMerge(yp);
+
+ sprites[0].draw(screen, 0);
+ if (frameNum)
+ sprites[0].draw(screen, frameNum);
+
+ idx1 = (idx1 + 1) % 4;
+ if (!idx1)
+ frameNum = (frameNum + 1) % 10;
+
+ screen.draw();
+ if (!skipElapsed) {
+ timeExpired = MAX((int)events.timeElapsed() - 1, 1);
+ skipElapsed = true;
+ }
+
+ yCtr -= timeExpired;
+ yp = MIN(yp + timeExpired, (uint)200);
+
+ if (events.wait(1, true))
+ return false;
+
+ if (fadeFlag) {
+ screen.fadeIn(4);
+ fadeFlag = false;
+ }
+ }
+
+ screen.vertMerge(SCREEN_HEIGHT);
+ screen.saveBackground(1);
+ screen.draw();
+ screen.freePages();
+
+ events.updateGameCounter();
+ events.wait(30, true);
+
+ // Zoom into the Pharoah's base closeup view
+ for (int idx = 14; idx >= 0; --idx) {
+ events.updateGameCounter();
+ sprites[1].draw(screen, 0, Common::Point(XLIST1[idx], YLIST1[idx]));
+ sprites[1].draw(screen, 1, Common::Point(XLIST2[idx], YLIST1[idx]));
+ screen.draw();
+
+ if (idx == 2)
+ sound.stopMusic(48);
+ if (events.wait(2, true))
+ return false;
+ }
+
+ // TODO: More
+ sound.playSong(voc[0]);
+ sound.playSong(voc[1]);
+
+ return true;
+}
+
+bool DarkSideCutscenes::showDarkSideEnding() {
+ EventsManager &events = *_vm->_events;
+ Screen &screen = *_vm->_screen;
+ SoundManager &sound = *_vm->_sound;
+
+ Voc voc("ido2.voc");
+ Music newBright("newbrigh.m");
+ SpriteResource box("box.vga");
+
+ newBright.play();
+ screen.loadBackground("scene1.raw");
+ screen.loadPalette("endgame.pal");
+ screen.update();
+
+ screen.fadeIn(4);
+ events.updateGameCounter();
+
+ // TODO
+ events.wait(5000);
+ return true;
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/worldofxeen/darkside_cutscenes.h b/engines/xeen/worldofxeen/darkside_cutscenes.h
new file mode 100644
index 0000000000..1b78e3ea2d
--- /dev/null
+++ b/engines/xeen/worldofxeen/darkside_cutscenes.h
@@ -0,0 +1,54 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_WORLDOFXEEN_DARKSIDE_CUTSCENES_H
+#define XEEN_WORLDOFXEEN_DARKSIDE_CUTSCENES_H
+
+namespace Xeen {
+
+class XeenEngine;
+
+class DarkSideCutscenes {
+private:
+ XeenEngine *_vm;
+public:
+ DarkSideCutscenes(XeenEngine *vm) : _vm(vm) {}
+
+ /**
+ * Shows the Dark Side of Xeen title screen
+ */
+ bool showDarkSideTitle();
+
+ /**
+ * Shows the Dark Side of Xeen intro sequence
+ */
+ bool showDarkSideIntro();
+
+ /**
+ * Shows the Dark Side of Xeen ending sequence
+ */
+ bool showDarkSideEnding();
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_WORLDOFXEEN_DARKSIDE_CUTSCENES_H */
diff --git a/engines/xeen/worldofxeen/worldofxeen.cpp b/engines/xeen/worldofxeen/worldofxeen.cpp
new file mode 100644
index 0000000000..86a6ee3da7
--- /dev/null
+++ b/engines/xeen/worldofxeen/worldofxeen.cpp
@@ -0,0 +1,46 @@
+/* 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 "xeen/worldofxeen/worldofxeen.h"
+#include "xeen/worldofxeen/darkside_cutscenes.h"
+#include "xeen/worldofxeen/clouds_cutscenes.h"
+#include "xeen/sound.h"
+
+namespace Xeen {
+
+WorldOfXeenEngine::WorldOfXeenEngine(OSystem *syst, const XeenGameDescription *gameDesc)
+ : XeenEngine(syst, gameDesc), CloudsCutscenes(this),
+ DarkSideCutscenes(this) {
+ _seenDarkSideIntro = false;
+}
+
+void WorldOfXeenEngine::showIntro() {
+ // **DEBUG**
+ if (gDebugLevel == 0)
+ return;
+
+ bool completed = showDarkSideTitle();
+ if (!_seenDarkSideIntro && completed)
+ showDarkSideIntro();
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/worldofxeen/worldofxeen.h b/engines/xeen/worldofxeen/worldofxeen.h
new file mode 100644
index 0000000000..68a83bb89d
--- /dev/null
+++ b/engines/xeen/worldofxeen/worldofxeen.h
@@ -0,0 +1,50 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_WORLDOFXEEN_WORLDOFXEEN_H
+#define XEEN_WORLDOFXEEN_WORLDOFXEEN_H
+
+#include "xeen/xeen.h"
+#include "xeen/worldofxeen/clouds_cutscenes.h"
+#include "xeen/worldofxeen/darkside_cutscenes.h"
+
+namespace Xeen {
+
+/**
+ * Implements a descendant of the base Xeen engine to handle
+ * Clouds of Xeen, Dark Side of Xeen, and Worlds of Xeen specific
+ * game code
+ */
+class WorldOfXeenEngine: public XeenEngine, public CloudsCutscenes,
+ public DarkSideCutscenes {
+protected:
+ virtual void showIntro();
+public:
+ bool _seenDarkSideIntro;
+public:
+ WorldOfXeenEngine(OSystem *syst, const XeenGameDescription *gameDesc);
+ virtual ~WorldOfXeenEngine() {}
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_WORLDOFXEEN_WORLDOFXEEN_H */
diff --git a/engines/xeen/xeen.cpp b/engines/xeen/xeen.cpp
new file mode 100644
index 0000000000..e549c8e845
--- /dev/null
+++ b/engines/xeen/xeen.cpp
@@ -0,0 +1,353 @@
+/* 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 "common/scummsys.h"
+#include "common/config-manager.h"
+#include "common/debug-channels.h"
+#include "common/events.h"
+#include "engines/util.h"
+#include "graphics/scaler.h"
+#include "graphics/thumbnail.h"
+#include "xeen/xeen.h"
+#include "xeen/dialogs_options.h"
+#include "xeen/files.h"
+#include "xeen/resources.h"
+
+namespace Xeen {
+
+XeenEngine::XeenEngine(OSystem *syst, const XeenGameDescription *gameDesc)
+ : Engine(syst), _gameDescription(gameDesc), _randomSource("Xeen") {
+ _combat = nullptr;
+ _debugger = nullptr;
+ _events = nullptr;
+ _files = nullptr;
+ _interface = nullptr;
+ _map = nullptr;
+ _party = nullptr;
+ _resources = nullptr;
+ _saves = nullptr;
+ _screen = nullptr;
+ _scripts = nullptr;
+ _sound = nullptr;
+ _spells = nullptr;
+ _town = nullptr;
+ _eventData = nullptr;
+ _quitMode = 0;
+ _noDirectionSense = false;
+ _mode = MODE_0;
+ _startupWindowActive = false;
+}
+
+XeenEngine::~XeenEngine() {
+ delete _combat;
+ delete _debugger;
+ delete _events;
+ delete _interface;
+ delete _map;
+ delete _party;
+ delete _saves;
+ delete _screen;
+ delete _scripts;
+ delete _sound;
+ delete _spells;
+ delete _town;
+ delete _eventData;
+ delete _resources;
+ delete _files;
+}
+
+void XeenEngine::initialize() {
+ // Set up debug channels
+ DebugMan.addDebugChannel(kDebugPath, "Path", "Pathfinding debug level");
+ DebugMan.addDebugChannel(kDebugScripts, "scripts", "Game scripts");
+ DebugMan.addDebugChannel(kDebugGraphics, "graphics", "Graphics handling");
+ DebugMan.addDebugChannel(kDebugSound, "sound", "Sound and Music handling");
+
+ // Create sub-objects of the engine
+ _files = new FileManager(this);
+ _resources = new Resources();
+ _combat = new Combat(this);
+ _debugger = new Debugger(this);
+ _events = new EventsManager(this);
+ _interface = new Interface(this);
+ _map = new Map(this);
+ _party = new Party(this);
+ _saves = new SavesManager(this, *_party);
+ _screen = new Screen(this);
+ _scripts = new Scripts(this);
+ _screen->setupWindows();
+ _sound = new SoundManager(this, _mixer);
+ _spells = new Spells(this);
+ _town = new Town(this);
+ Voc::init(this);
+ Music::init(this);
+
+ File f("029.obj");
+ _eventData = f.readStream(f.size());
+
+ // Set graphics mode
+ initGraphics(320, 200, false);
+
+ // If requested, load a savegame instead of showing the intro
+ if (ConfMan.hasKey("save_slot")) {
+ int saveSlot = ConfMan.getInt("save_slot");
+ if (saveSlot >= 0 && saveSlot <= 999)
+ _loadSaveSlot = saveSlot;
+ }
+}
+
+Common::Error XeenEngine::run() {
+ initialize();
+
+ showIntro();
+ if (shouldQuit())
+ return Common::kNoError;
+
+ showMainMenu();
+ if (shouldQuit())
+ return Common::kNoError;
+
+ playGame();
+
+ return Common::kNoError;
+}
+
+int XeenEngine::getRandomNumber(int maxNumber) {
+ return _randomSource.getRandomNumber(maxNumber);
+}
+
+int XeenEngine::getRandomNumber(int minNumber, int maxNumber) {
+ return getRandomNumber(maxNumber - minNumber) + minNumber;
+}
+
+Common::Error XeenEngine::saveGameState(int slot, const Common::String &desc) {
+ Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving(
+ generateSaveName(slot));
+ if (!out)
+ return Common::kCreatingFileFailed;
+
+ XeenSavegameHeader header;
+ header._saveName = desc;
+ writeSavegameHeader(out, header);
+
+ Common::Serializer s(nullptr, out);
+ synchronize(s);
+
+ out->finalize();
+ delete out;
+
+ return Common::kNoError;
+}
+
+Common::Error XeenEngine::loadGameState(int slot) {
+ Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(
+ generateSaveName(slot));
+ if (!saveFile)
+ return Common::kReadingFailed;
+
+ Common::Serializer s(saveFile, nullptr);
+
+ // Load the savaegame header
+ XeenSavegameHeader header;
+ if (!readSavegameHeader(saveFile, header))
+ error("Invalid savegame");
+
+ if (header._thumbnail) {
+ header._thumbnail->free();
+ delete header._thumbnail;
+ }
+
+ // Load most of the savegame data
+ synchronize(s);
+ delete saveFile;
+
+ return Common::kNoError;
+}
+
+Common::String XeenEngine::generateSaveName(int slot) {
+ return Common::String::format("%s.%03d", _targetName.c_str(), slot);
+}
+
+bool XeenEngine::canLoadGameStateCurrently() {
+ return true;
+}
+
+bool XeenEngine::canSaveGameStateCurrently() {
+ return true;
+}
+
+void XeenEngine::synchronize(Common::Serializer &s) {
+ // TODO
+}
+
+const char *const SAVEGAME_STR = "XEEN";
+#define SAVEGAME_STR_SIZE 6
+
+bool XeenEngine::readSavegameHeader(Common::InSaveFile *in, XeenSavegameHeader &header) {
+ char saveIdentBuffer[SAVEGAME_STR_SIZE + 1];
+ header._thumbnail = nullptr;
+
+ // Validate the header Id
+ in->read(saveIdentBuffer, SAVEGAME_STR_SIZE + 1);
+ if (strncmp(saveIdentBuffer, SAVEGAME_STR, SAVEGAME_STR_SIZE))
+ return false;
+
+ header._version = in->readByte();
+ if (header._version > XEEN_SAVEGAME_VERSION)
+ return false;
+
+ // Read in the string
+ header._saveName.clear();
+ char ch;
+ while ((ch = (char)in->readByte()) != '\0')
+ header._saveName += ch;
+
+ // Get the thumbnail
+ header._thumbnail = Graphics::loadThumbnail(*in);
+ if (!header._thumbnail)
+ return false;
+
+ // Read in save date/time
+ header._year = in->readSint16LE();
+ header._month = in->readSint16LE();
+ header._day = in->readSint16LE();
+ header._hour = in->readSint16LE();
+ header._minute = in->readSint16LE();
+ header._totalFrames = in->readUint32LE();
+
+ return true;
+}
+
+void XeenEngine::writeSavegameHeader(Common::OutSaveFile *out, XeenSavegameHeader &header) {
+ // Write out a savegame header
+ out->write(SAVEGAME_STR, SAVEGAME_STR_SIZE + 1);
+
+ out->writeByte(XEEN_SAVEGAME_VERSION);
+
+ // Write savegame name
+ out->writeString(header._saveName);
+ out->writeByte('\0');
+
+ // Write a thumbnail of the screen
+/*
+ uint8 thumbPalette[768];
+ _screen->getPalette(thumbPalette);
+ Graphics::Surface saveThumb;
+ ::createThumbnail(&saveThumb, (const byte *)_screen->getPixels(),
+ _screen->w, _screen->h, thumbPalette);
+ Graphics::saveThumbnail(*out, saveThumb);
+ saveThumb.free();
+*/
+ // Write out the save date/time
+ TimeDate td;
+ g_system->getTimeAndDate(td);
+ out->writeSint16LE(td.tm_year + 1900);
+ out->writeSint16LE(td.tm_mon + 1);
+ out->writeSint16LE(td.tm_mday);
+ out->writeSint16LE(td.tm_hour);
+ out->writeSint16LE(td.tm_min);
+// out->writeUint32LE(_events->getFrameCounter());
+}
+
+void XeenEngine::showMainMenu() {
+ //OptionsMenu::show(this);
+}
+
+void XeenEngine::playGame() {
+ _saves->reset();
+ play();
+}
+
+/*
+ * Secondary method for handling the actual gameplay
+ */
+void XeenEngine::play() {
+ // TODO: Init variables
+ _quitMode = 0;
+
+ _interface->setup();
+ _screen->loadBackground("back.raw");
+ _screen->loadPalette("mm4.pal");
+
+ if (getGameID() != GType_WorldOfXeen && !_map->_loadDarkSide) {
+ _map->_loadDarkSide = true;
+ _party->_mazeId = 29;
+ _party->_mazeDirection = DIR_NORTH;
+ _party->_mazePosition.x = 25;
+ _party->_mazePosition.y = 21;
+ }
+
+ _map->load(_party->_mazeId);
+
+ _interface->startup();
+ if (_mode == MODE_0) {
+// _screen->fadeOut(4);
+ }
+
+ _screen->_windows[0].update();
+ _interface->mainIconsPrint();
+ _screen->_windows[0].update();
+ _events->setCursor(0);
+
+ _combat->_moveMonsters = true;
+ if (_mode == MODE_0) {
+ _mode = MODE_1;
+ _screen->fadeIn(4);
+ }
+
+ _combat->_moveMonsters = true;
+
+ gameLoop();
+}
+
+void XeenEngine::gameLoop() {
+ // Main game loop
+ while (!shouldQuit()) {
+ _map->cellFlagLookup(_party->_mazePosition);
+ if (_map->_currentIsEvent) {
+ _quitMode = _scripts->checkEvents();
+ if (shouldQuit() || _quitMode)
+ return;
+ }
+ _party->giveTreasure();
+
+ // Main user interface handler for waiting for and processing user input
+ _interface->perform();
+ }
+}
+
+Common::String XeenEngine::printMil(uint value) {
+ return (value >= 1000000) ? Common::String::format("%u mil", value / 1000000) :
+ Common::String::format("%u", value);
+}
+
+Common::String XeenEngine::printK(uint value) {
+ return (value > 9999) ? Common::String::format("%uk", value / 1000) :
+ Common::String::format("%u", value);
+}
+
+Common::String XeenEngine::printK2(uint value) {
+ return (value > 999) ? Common::String::format("%uk", value / 1000) :
+ Common::String::format("%u", value);
+}
+
+} // End of namespace Xeen
diff --git a/engines/xeen/xeen.h b/engines/xeen/xeen.h
new file mode 100644
index 0000000000..389d73dcc3
--- /dev/null
+++ b/engines/xeen/xeen.h
@@ -0,0 +1,213 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_XEEN_H
+#define XEEN_XEEN_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/error.h"
+#include "common/random.h"
+#include "common/savefile.h"
+#include "common/serializer.h"
+#include "common/util.h"
+#include "engines/engine.h"
+#include "xeen/combat.h"
+#include "xeen/debugger.h"
+#include "xeen/dialogs.h"
+#include "xeen/events.h"
+#include "xeen/files.h"
+#include "xeen/interface.h"
+#include "xeen/map.h"
+#include "xeen/party.h"
+#include "xeen/resources.h"
+#include "xeen/saves.h"
+#include "xeen/screen.h"
+#include "xeen/scripts.h"
+#include "xeen/sound.h"
+#include "xeen/spells.h"
+#include "xeen/town.h"
+
+/**
+ * This is the namespace of the Xeen engine.
+ *
+ * Status of this engine: In Development
+ *
+ * Games using this engine:
+ * - Might & Magic 4: Clouds of Xeen
+ * - Might & Magic 5: Dark Side of Xeen
+ * - Might & Magic: World of Xeen
+ * - Might & Magic: Swords of Xeen
+ */
+namespace Xeen {
+
+enum {
+ GType_Clouds = 1,
+ GType_DarkSide = 2,
+ GType_WorldOfXeen = 3,
+ GType_Swords = 4
+};
+
+enum XeenDebugChannels {
+ kDebugPath = 1 << 0,
+ kDebugScripts = 1 << 1,
+ kDebugGraphics = 1 << 2,
+ kDebugSound = 1 << 3
+};
+
+enum Mode {
+ MODE_FF = -1,
+ MODE_0 = 0,
+ MODE_1 = 1,
+ MODE_COMBAT = 2,
+ MODE_3 = 3,
+ MODE_4 = 4,
+ MODE_SLEEPING = 5,
+ MODE_6 = 6,
+ MODE_7 = 7,
+ MODE_8 = 8,
+ MODE_9 = 9,
+ MODE_CHARACTER_INFO = 10,
+ MODE_12 = 12,
+ MODE_DIALOG_123 = 13,
+ MODE_17 = 17,
+ MODE_86 = 86
+};
+
+struct XeenGameDescription;
+
+#define XEEN_SAVEGAME_VERSION 1
+#define GAME_FRAME_TIME 50
+
+class XeenEngine : public Engine {
+private:
+ const XeenGameDescription *_gameDescription;
+ Common::RandomSource _randomSource;
+ int _loadSaveSlot;
+
+ void showMainMenu();
+
+ void play();
+
+ void pleaseWait();
+
+ void gameLoop();
+protected:
+ virtual void showIntro() = 0;
+
+ /**
+ * Play the game
+ */
+ virtual void playGame();
+private:
+ void initialize();
+
+ /**
+ * Synchronize savegame data
+ */
+ void synchronize(Common::Serializer &s);
+
+ /**
+ * Support method that generates a savegame name
+ * @param slot Slot number
+ */
+ Common::String generateSaveName(int slot);
+
+ // Engine APIs
+ virtual Common::Error run();
+ virtual bool hasFeature(EngineFeature f) const;
+public:
+ Combat *_combat;
+ Debugger *_debugger;
+ EventsManager *_events;
+ FileManager *_files;
+ Interface *_interface;
+ Map *_map;
+ Party *_party;
+ Resources *_resources;
+ SavesManager *_saves;
+ Screen *_screen;
+ Scripts *_scripts;
+ SoundManager *_sound;
+ Spells *_spells;
+ Town *_town;
+ Mode _mode;
+ GameEvent _gameEvent;
+ Common::SeekableReadStream *_eventData;
+ int _quitMode;
+ bool _noDirectionSense;
+ bool _startupWindowActive;
+public:
+ XeenEngine(OSystem *syst, const XeenGameDescription *gameDesc);
+ virtual ~XeenEngine();
+
+ uint32 getFeatures() const;
+ Common::Language getLanguage() const;
+ Common::Platform getPlatform() const;
+ uint16 getVersion() const;
+ uint32 getGameID() const;
+ uint32 getGameFeatures() const;
+
+ int getRandomNumber(int maxNumber);
+
+ int getRandomNumber(int minNumber, int maxNumber);
+
+ /**
+ * Load a savegame
+ */
+ virtual Common::Error loadGameState(int slot);
+
+ /**
+ * Save the game
+ */
+ virtual Common::Error saveGameState(int slot, const Common::String &desc);
+
+ /**
+ * Returns true if a savegame can currently be loaded
+ */
+ bool canLoadGameStateCurrently();
+
+ /**
+ * Returns true if the game can currently be saved
+ */
+ bool canSaveGameStateCurrently();
+
+ /**
+ * Read in a savegame header
+ */
+ static bool readSavegameHeader(Common::InSaveFile *in, XeenSavegameHeader &header);
+
+ /**
+ * Write out a savegame header
+ */
+ void writeSavegameHeader(Common::OutSaveFile *out, XeenSavegameHeader &header);
+
+ static Common::String printMil(uint value);
+
+ static Common::String printK(uint value);
+
+ static Common::String printK2(uint value);
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_XEEN_H */
diff --git a/engines/xeen/xsurface.cpp b/engines/xeen/xsurface.cpp
new file mode 100644
index 0000000000..5f10a83dc6
--- /dev/null
+++ b/engines/xeen/xsurface.cpp
@@ -0,0 +1,31 @@
+/* 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 "common/algorithm.h"
+#include "common/util.h"
+#include "xeen/xsurface.h"
+#include "xeen/resources.h"
+#include "xeen/screen.h"
+
+namespace Xeen {
+
+} // End of namespace Xeen
diff --git a/engines/xeen/xsurface.h b/engines/xeen/xsurface.h
new file mode 100644
index 0000000000..40d4611419
--- /dev/null
+++ b/engines/xeen/xsurface.h
@@ -0,0 +1,53 @@
+/* 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.
+ *
+ */
+
+#ifndef XEEN_XSURFACE_H
+#define XEEN_XSURFACE_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/rect.h"
+#include "graphics/managed_surface.h"
+
+namespace Xeen {
+
+class BaseSurface: public Graphics::ManagedSurface {
+public:
+ virtual void addDirtyRect(const Common::Rect &r) {
+ Graphics::ManagedSurface::addDirtyRect(r);
+ }
+public:
+ BaseSurface() : Graphics::ManagedSurface() {}
+ BaseSurface(int width, int height) : Graphics::ManagedSurface(width, height) {}
+ virtual ~BaseSurface() {}
+};
+
+class XSurface : public BaseSurface {
+public:
+ XSurface() : BaseSurface() {}
+ XSurface(int width, int height) : BaseSurface(width, height) {}
+ virtual ~XSurface() {}
+};
+
+} // End of namespace Xeen
+
+#endif /* XEEN_XSURFACE_H */