diff options
92 files changed, 33476 insertions, 0 deletions
diff --git a/engines/xeen/character.cpp b/engines/xeen/character.cpp new file mode 100644 index 0000000000..ca224ab4f1 --- /dev/null +++ b/engines/xeen/character.cpp @@ -0,0 +1,1911 @@ +/* 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(); +} + +/** +* Return whether a given item passes class-based usage restrictions +*/ +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; +} + +/** + * Return the bare name of a given inventory item + */ +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); +} + +/** + * Discard an item from the inventory + */ +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; +} + +/** + * Sorts the items list, removing any empty item slots to the end of the array + */ +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; + } + } + } + } +} + +/** + * Un-equips the given item + */ +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::vm() { + 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)); +} + +/** + * Return if the given inventory items list is full + */ +bool InventoryItems::isFull() const { + return operator[](size() - 1)._id != 0; +} + + +/*------------------------------------------------------------------------*/ + +/** + * Equip a given weapon + */ +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; + } + } +} + +/** + * Assembles a full lines description for a specified item for use in + * the Items dialog + */ +Common::String WeaponItems::getFullDescription(int itemIndex, int displayNum) { + XeenItem &i = operator[](itemIndex); + Resources &res = *vm()->_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 : "", + 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 = *vm()->_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() + ); +} + +/*------------------------------------------------------------------------*/ + +/** + * Equip a given piece of armor + */ +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; + } +} + +/** + * Assembles a full lines description for a specified item for use in + * the Items dialog + */ +Common::String ArmorItems::getFullDescription(int itemIndex, int displayNum) { + XeenItem &i = operator[](itemIndex); + Resources &res = *vm()->_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 : "", + ARMOR_NAMES[i._id], + (i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED)) || + !i._bonusFlags ? "\b " : "" + ); +} + +void ArmorItems::enchantItem(int itemIndex, int amount) { + SoundManager &sound = *vm()->_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); +} + +/*------------------------------------------------------------------------*/ + +/** + * Equip a given accessory + */ +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; + } +} + +/** + * Assembles a full lines description for a specified item for use in + * the Items dialog + */ +Common::String AccessoryItems::getFullDescription(int itemIndex, int displayNum) { + XeenItem &i = operator[](itemIndex); + Resources &res = *vm()->_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 : "", + 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); +} + +/*------------------------------------------------------------------------*/ + +/** + * Assembles a full lines description for a specified item for use in + * the Items dialog + */ +Common::String MiscItems::getFullDescription(int itemIndex, int displayNum) { + XeenItem &i = operator[](itemIndex); + Resources &res = *vm()->_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 : "", + 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 = *vm()->_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]; +} + +/** + * Breaks all the items in a given character's inventory + */ +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'); + strncpy(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); +} + +/** + * Returns the worst condition the character is suffering from + */ +Condition Character::worstCondition() const { + for (int cond = ERADICATED; cond >= CURSED; --cond) { + if (_conditions[cond]) + return (Condition)cond; + } + + return NO_CONDITION; +} + +/** + * Returns whether the given character has a disabling condition, but still alive + */ +bool Character::isDisabled() const { + Condition condition = worstCondition(); + + return condition == ASLEEP || condition == PARALYZED || condition == UNCONSCIOUS + || condition == STONED || condition == ERADICATED; +} + +/** +* Returns whether the given character has a disabling condition, or is dead +*/ +bool Character::isDisabledOrDead() const { + Condition condition = worstCondition(); + + return condition == ASLEEP || (condition >= PARALYZED && condition <= ERADICATED); +} + +/** + * Returns whether the given character has a dead condition + */ +bool Character::isDead() const { + Condition condition = worstCondition(); + + return condition >= DEAD && condition <= ERADICATED; +} + +/** + * Get the character's age + */ +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; +} + +/** + * Get the effective value of a given stat for the character + */ +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); +} + +/** + * Return the color number to use for a given stat value in the character + * info or quick reference dialogs + */ +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); +} + +/** + * Returns the thievery skill level, adjusted by class and race + */ +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; +} + +/** + * Modifies a passed attribute value based on player's condition + */ +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; + } + + 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; + } + + return category; +} + +/** + * Add hit points to a character + */ +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); +} + +/** + * Remove hit points fromo the character + */ +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..f1243f1568 --- /dev/null +++ b/engines/xeen/character.h @@ -0,0 +1,340 @@ +/* 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 *vm(); + 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); + + void clear(); + + bool passRestrictions(int itemId, bool showError) const; + + Common::String getName(int itemIndex); + + virtual Common::String getFullDescription(int itemIndex, int displayNum = 15) = 0; + + Common::String getIdentifiedDetails(int itemIndex); + + bool discardItem(int itemIndex); + + virtual void equipItem(int itemIndex) {} + + void removeItem(int itemIndex); + + void sort(); + + virtual void enchantItem(int itemIndex, int amount); + + 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 void equipItem(int itemIndex); + + 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 void equipItem(int itemIndex); + + 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) {} + + virtual void equipItem(int itemIndex); + + 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 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); + + void breakAllItems(); +}; + + +class AttributePair { +public: + uint _permanent; + uint _temporary; +public: + AttributePair(); + void synchronize(Common::Serializer &s); +}; + +class Character { +private: + 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); + + Condition worstCondition() const; + + bool isDisabled() const; + + bool isDisabledOrDead() const; + + bool isDead() const; + + int getAge(bool ignoreTemp = false) const; + + int getMaxHP() const; + + int getMaxSP() const; + + uint getStat(Attribute attrib, bool baseOnly = false) const; + + 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; + + 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); + + void addHitPoints(int amount); + + 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..1d03a5128d --- /dev/null +++ b/engines/xeen/combat.cpp @@ -0,0 +1,2090 @@ +/* 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; + } +} + +/** + * Do damage to a specific character + */ +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; + } + + 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])); + } + } + } + } + } + } + } + } + + 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]; + } + } + } + } + } + } +} + +/** + * Determines whether a given monster can move + */ +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; + } + } +} + +/** + * Moves a monster by a given delta amount if it's a valid move + */ +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; + + bool 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; + } + } +} + +/** + * Setup the combat party with a copy of the currently active party + */ +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; + } + } + } + } +} + +/** + * Returns true if all participants in the combat are disabled + */ +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; +} + +/** + * Returns true if all the characters of the party are disabled + */ +bool Combat::charsCantAct() const { + for (uint idx = 0; idx < _combatParty.size(); ++idx) { + if (!_combatParty[idx]->isDisabledOrDead()) + return false; + } + + return true; +} + +/** + * Return a description of the monsters being faced + */ +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 &c = party._activeParty[charIndex]; + + if (_shooting[charIndex] && !_missedShot[charIndex]) { + if (!hitMonster(c, 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; + } + + 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; + } + } +} + +/** + * Flag the currently active character as blocking/defending + */ +void Combat::block() { + _charsBlocked[_whosTurn] = true; +} + +/** + * Perform whatever the current combat character's quick action is + */ +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; + } +} + +/** + * Current selected character is trying to run away + */ +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; + } + + 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; +} + +/** + * Distribute experience between active party members + */ +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(); +} + +/** + * Fires off a ranged attack at all oncoming monsters + */ +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..33309b243b --- /dev/null +++ b/engines/xeen/combat.h @@ -0,0 +1,185 @@ +/* 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 11 + +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); + + 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); + + void doCharDamage(Character &c, int charNum, int monsterDataIndex); + + void moveMonsters(); + + void setupCombatParty(); + + void setSpeedTable(); + + bool allHaveGone() const; + + bool charsCantAct() const; + + Common::String getMonsterDescriptions(); + + void attack(Character &c, RangeType rangeType); + + void block(); + + void quickFight(); + + void run(); + + void monstersAttack(); + + void setupMonsterAttack(int monsterDataIndex, const Common::Point &pt); + + bool monsterCanMove(const Common::Point &pt, int wallShift, + int v1, int v2, int monsterId); + + 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); + + 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..64b28bf687 --- /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_game.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..0735daa507 --- /dev/null +++ b/engines/xeen/detection_tables.h @@ -0,0 +1,48 @@ +/* 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 + }, + + { 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..77cdd92169 --- /dev/null +++ b/engines/xeen/dialogs.cpp @@ -0,0 +1,270 @@ +/* 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 { + +/** + * Saves the current list of buttons + */ +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; +} + + +/** +* Draws the scroll in the background +*/ +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]; +} + +/** + * Draws the buttons onto the passed surface + */ +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..6e809ba2dc --- /dev/null +++ b/engines/xeen/dialogs.h @@ -0,0 +1,106 @@ +/* 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; + + void doScroll(XeenEngine *vm, bool drawFlag, bool doFade); + + bool checkEvents(XeenEngine *vm); +public: + ButtonContainer() : _buttonValue(0) {} + + 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); + + 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..c00916cd6f --- /dev/null +++ b/engines/xeen/dialogs_char_info.cpp @@ -0,0 +1,580 @@ +/* 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; +} + +/** + * Load the draw structure list with frame numbers and positions + */ +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; +} + +/** + * Set up the button list for the dialog + */ +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); +} + +/** + * Return a string containing the details of the character + */ +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 : "" + ); +} + +/** + * Cursor display handling + */ +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..5a20ff2248 --- /dev/null +++ b/engines/xeen/dialogs_char_info.h @@ -0,0 +1,58 @@ +/* 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); + + void loadDrawStructs(); + + void addButtons(); + + Common::String loadCharacterDetails(const Character &c); + + 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..7ccaa7fe71 --- /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..8b754ab6de --- /dev/null +++ b/engines/xeen/dialogs_input.cpp @@ -0,0 +1,279 @@ +/* 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; +} + +/** + * Allows the user to enter a string + */ +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\x0""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(); +} + +/** + * Draws the cursor and waits until the user presses a key + */ +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(); +} + +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..2f30b73973 --- /dev/null +++ b/engines/xeen/dialogs_input.h @@ -0,0 +1,83 @@ +/* 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: + Common::KeyCode doCursor(const Common::String &msg); +protected: + XeenEngine *_vm; + Window *_window; + + 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..bdcdffaa84 --- /dev/null +++ b/engines/xeen/dialogs_items.cpp @@ -0,0 +1,1083 @@ +/* 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; +} + +/** + * Load the buttons for the dialog + */ +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; + } +} + +/** + * 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 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]; + } +} + +/** +* Saves the inventory from the temporary _itemsCharacter character back into the +* blacksmith storage, so changes in blacksmith inventory remain persistent +*/ +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]; + } +} + +/** + * Sets the equipment icon to use for each item for display + */ +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; + } + } + } +} + +/** + * Calculate the cost of an item + */ +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 &items = _oldCharacter->_items[category]; + if (items[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 msg = Common::String::format(IDENTIFY_ITEM_MSG, + desc.c_str(), details.c_str()); + + Window &w = screen._windows[14]; + w.open(); + w.writeString(msg); + w.update(); + + saveButtons(); + clearButtons(); + + while (!_vm->shouldQuit() && !events.isKeyMousePressed()) + events.pollEventsAndWait(); + events.clearEvents(); + + restoreButtons(); + w.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..bc995c52f8 --- /dev/null +++ b/engines/xeen/dialogs_items.h @@ -0,0 +1,73 @@ +/* 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); + + void loadButtons(ItemsMode mode, Character *&c); + + void blackData2CharData(); + + void charData2BlackData(); + + void setEquipmentIcons(); + + 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..4b4974b9aa --- /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 % (_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 % 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..544c110c82 --- /dev/null +++ b/engines/xeen/dialogs_party.cpp @@ -0,0 +1,1071 @@ +/* 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 &c = party._roster[idx]; + if (!c._name.empty() && c._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(); +} + +/** + * Sets up the faces from the avaialble roster for display in the party dialog + */ +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 (int 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]], + (CharacterClass)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; +} + +/** + * 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 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); +} + +/** + * Set a list of flags for which classes the passed attribute set meet the + * minimum requirements of + */ +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; +} + +/** + * Return details of the generated character + */ +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; +} + +/** + * Print the selection arrow to indicate the selected class + */ +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(); +} + +/** + * Print the dice animation + */ +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); +} + +/** + * Exchanging two attributes for the character being rolled + */ +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; +} + +/** + * Saves the rolled character into the roster + */ +bool PartyDialog::saveCharacter(Character &c, CharacterClass 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 = 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..db2a3dfb36 --- /dev/null +++ b/engines/xeen/dialogs_party.h @@ -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. + * + */ + +#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(); + + void setupFaces(int firstDisplayChar, bool updateFlag); + + void startingCharChanged(int firstDisplayChar); + + void createChar(); + + int selectCharacter(bool isDelete, int firstDisplayChar); + + void throwDice(uint attribs[TOTAL_ATTRIBUTES], bool allowedClasses[TOTAL_CLASSES]); + + void checkClass(const uint attribs[TOTAL_ATTRIBUTES], bool allowedClasses[TOTAL_CLASSES]); + + int newCharDetails(const uint attribs[TOTAL_ATTRIBUTES], + bool allowedClasses[TOTAL_CLASSES], Race race, Sex sex, int classId, + int selectedClass, Common::String &msg); + + void printSelectionArrow(SpriteResource &icons, int selectedClass); + + void drawDice(SpriteResource &dice); + + int exchangeAttribute(int srcAttr); + + bool saveCharacter(Character &c, CharacterClass 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..c982b8acd2 --- /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..7cde5f37ef --- /dev/null +++ b/engines/xeen/dialogs_spells.cpp @@ -0,0 +1,1027 @@ +/* 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 spellId = _spells[newSelection]._spellId; + int spellIndex = _spells[newSelection]._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, msg.c_str()) : + Common::String::format(SPELLS_PURCHASE, msg.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; + } + + 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..92dc8b487f --- /dev/null +++ b/engines/xeen/events.cpp @@ -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. + * + */ + +#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 { + +/** + * Constructor + */ +EventsManager::EventsManager(XeenEngine *vm) : _vm(vm), + _frameCounter(0), _priorFrameCounterTime(0), _gameCounter(0), + _priorGameCounterTime(0), _keyCode(Common::KEYCODE_INVALID), + _leftButton(false), _rightButton(false), + _sprites("mouse.icn") { + Common::fill(&_gameCounters[0], &_gameCounters[6], 0); +} + +/** + * Destructor + */ +EventsManager::~EventsManager() { +} + +/* + * Set the cursor + */ +void EventsManager::setCursor(int cursorId) { + XSurface cursor; + _sprites.draw(cursor, cursorId); + + CursorMan.replaceCursor(cursor.getPixels(), cursor.w, cursor.h, 0, 0, 0); + showCursor(); +} + +/** + * Show the mouse cursor + */ +void EventsManager::showCursor() { + CursorMan.showMouse(true); +} + +/** + * Hide the mouse cursor + */ +void EventsManager::hideCursor() { + CursorMan.showMouse(false); +} + +/** + * Returns if the mouse cursor is visible + */ +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; +} + +/** + * Returns true if a key or mouse press is pending + */ +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); +} + +/** + * Handles moving to the next game frame + */ +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..cce3155a4b --- /dev/null +++ b/engines/xeen/events.h @@ -0,0 +1,104 @@ +/* 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]; + uint32 _priorGameCounterTime; + Common::KeyCode _keyCode; + SpriteResource _sprites; + + void nextFrame(); +public: + bool _leftButton, _rightButton; + Common::Point _mousePos; +public: + EventsManager(XeenEngine *vm); + + ~EventsManager(); + + void setCursor(int cursorId); + + void showCursor(); + + void hideCursor(); + + bool isCursorVisible(); + + void pollEvents(); + + void pollEventsAndWait(); + + void clearEvents(); + + void debounceMouse(); + + bool getKey(Common::KeyState &key); + + bool isKeyPending() const; + + 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..50949b7696 --- /dev/null +++ b/engines/xeen/files.cpp @@ -0,0 +1,246 @@ +/* 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 { + +/** +* Hash a given filename to produce the Id that represents it +*/ +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; +} + +/** +* Load the index of a given CC file +*/ +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); +} + +/** +* Given a resource name, returns whether an entry exists, and returns +* the header index data for that entry +*/ +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; +} + +/*------------------------------------------------------------------------*/ + +/** + * Instantiates the resource manager + */ +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)); +} + +/*------------------------------------------------------------------------*/ + +/** + * Opens the given file, throwing an error if it can't be opened + */ +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..f0c92d1050 --- /dev/null +++ b/engines/xeen/files.h @@ -0,0 +1,150 @@ +/* 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: + 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() {} + + void openFile(const Common::String &filename); + 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; + + void loadIndex(Common::SeekableReadStream *stream); + + virtual bool getHeaderEntry(const Common::String &resourceName, CCEntry &ccEntry) const; +public: + 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..bfdc4bde6b --- /dev/null +++ b/engines/xeen/font.cpp @@ -0,0 +1,377 @@ +/* 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); +} + +/** + * Draws a symbol to the surface. + * @param symbolId Symbol number from 0 to 19 + */ +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; +} + +/** + * 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 *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; +} + +/** + * Return the next pending character to display + */ +char FontSurface::getNextChar() { + return *_displayString++ & 0x7f; +} + +/** +* Return the width of a given character +*/ +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; + } +} + +/** + * Handles moving to the next line of the given bounded area + */ +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); +} + +/** + * Extract a number of a given maximum length from the string + */ +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; +} + +/** + * Set the text colors based on the specified index in the master text colors list + */ +void FontSurface::setTextColor(int idx) { + const byte *colP = &TEXT_COLORS[idx][0]; + Common::copy(colP, colP + 4, &_textColors[0]); +} + +/** + * Wrie a character to the surface + */ +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..caaa03c5ba --- /dev/null +++ b/engines/xeen/font.h @@ -0,0 +1,71 @@ +/* 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; + + char getNextChar(); + + bool getNextCharWidth(int &total); + + bool newLine(const Common::Rect &bounds); + + int fontAtoi(int len = 3); + + void setTextColor(int idx); + + 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() {} + + void writeSymbol(int symbolId); + + 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..d1a1478d95 --- /dev/null +++ b/engines/xeen/interface.cpp @@ -0,0 +1,2315 @@ +/* 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; + } +} + +/** + * Waits for a keypress or click, whilst still allowing the game scene to + * be animated. + */ +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; + } + 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; + } +} + +/** + * Handles incrementing game time + */ +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); + } + } +} + +/** + * Start the party falling + */ +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; +} + +/** + * Check movement in the given direction + */ +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 { + int 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; + } + } + } + } + + 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); +} + +/** + * Handle doing the falling + */ +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) { + +} + +/** + * Shake the screen + */ +void Interface::shake(int time) { + +} + +/** + * Draw the minimap + */ +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; +} + +/** + * Draw the display borders + */ +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; +} + +/** + * Select next character or monster to be attacking + */ +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..24edf9d23d --- /dev/null +++ b/engines/xeen/interface.h @@ -0,0 +1,155 @@ +/* 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(); + + void stepTime(); + + void doStepCode(); + + bool checkMoveDirection(int key); + + void handleFalling(); + + void saveFall(); + + void fall(int v); + + void shake(int time); + + void drawMiniMap(); + + 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(); + + void startFalling(bool v); + + void perform(); + + void rest(); + + void bash(const Common::Point &pt, Direction direction); + + void draw3d(bool updateFlag, bool skipDelay = false); + + 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..a08ce808d8 --- /dev/null +++ b/engines/xeen/interface_map.cpp @@ -0,0 +1,4474 @@ +/* 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 { + +static bool debugFlag = false; + +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; + } + + 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(); +} + +/** + * Handles animation of monsters, wall items, and combat within the 3d + * view by cycling the appropriate frame numbers + */ +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[3033]; + 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; + } +} + +/** + * Set up draw structures for displaying on-screen monsters + */ +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; + } +} + +/** + * Helper method for setIndoorsMonsters to set a draw structure + * with the deatils for a given monster + */ +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]; +} + +/** + * Set up draw structures for displaying on-screen objects + */ +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; + } + } + } +} + +/** + * Set up draw structures for displaying on-screen wall items + */ +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; + } + } + } + } +} + +/** + * Set up the draw structures for displaying monsters on outdoor maps + */ +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 +} + +/** + * Set up the draw structures for displaying objects on outdoor maps + */ +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; + } + } +} + +/** + * Draw the contents of the current 3d view of an indoor map + */ +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; +} + +/** + * Draw the contents of the current 3d view of an outdoor map + */ +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..a37bf349ec --- /dev/null +++ b/engines/xeen/interface_map.h @@ -0,0 +1,146 @@ +/* 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(); + + 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(); + + 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() {} + + void setIndoorsMonsters(); + + void setIndoorsObjects(); + + void setIndoorsWallPics(); + + void drawIndoors(); + + void setOutdoorsMonsters(); + + void setOutdoorsObjects(); + + 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..ff7938746e --- /dev/null +++ b/engines/xeen/map.cpp @@ -0,0 +1,1600 @@ +/* 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]); +} + +/** + * Flags all tiles for the map as having been stepped on + */ +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; +} + +/** + * Return the text color to use when displaying the monster's name in combat + * to indicate how damaged they are + */ +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(); + } + } +} + +/*------------------------------------------------------------------------*/ + +/** + * Synchronize data for an animation entry + */ +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(); +} + +/** + * Synchronize data for object animations within the game + */ +void AnimationInfo::synchronize(Common::SeekableReadStream &s) { + AnimationEntry entry; + + clear(); + while (s.pos() < s.size()) { + entry.synchronize(s); + push_back(entry); + } +} + +/** + * Load the animation info objects in the game + */ +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; + } +} + +/** + * Load the events for a new map + */ +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..a7e88c1726 --- /dev/null +++ b/engines/xeen/map.h @@ -0,0 +1,418 @@ +/* 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); + + 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(); + + 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; + + void synchronize(Common::SeekableReadStream &s); +}; + +class AnimationInfo : public Common::Array<AnimationEntry> { +public: + void synchronize(Common::SeekableReadStream &s); + + void load(const Common::String &name); +}; + +class Map { +private: + XeenEngine *_vm; + MazeData _mazeData[9]; + SpriteResource _wallPicSprites; + int _sidePictures; + int _sideObjects; + int _sideMonsters; + int _mazeDataIndex; + + 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..58a2f1a29a --- /dev/null +++ b/engines/xeen/module.mk @@ -0,0 +1,56 @@ +MODULE := engines/xeen + +MODULE_OBJS := \ + worldofxeen/clouds_ending.o \ + worldofxeen/clouds_intro.o \ + worldofxeen/darkside_ending.o \ + worldofxeen/darkside_intro.o \ + worldofxeen/worldofxeen_game.o \ + character.o \ + combat.o \ + debugger.o \ + detection.o \ + dialogs.o \ + automap.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..9f56b98c4c --- /dev/null +++ b/engines/xeen/party.cpp @@ -0,0 +1,752 @@ +/* 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; +} + +/** + * Copy the currently active party characters' data back to the roster + */ +void Party::copyPartyToRoster() { + for (uint i = 0; i < _activeParty.size(); ++i) { + _roster[_activeParty[i]._rosterId] = _activeParty[i]; + } +} + +/** + * Adds time to the party's playtime, taking into account the effect of any + * stat modifier changes + */ +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; +} + +/** + * Move party position to the run destination on the current map + */ +void Party::moveToRunLocation() { + _mazePosition = _vm->_map->mazeData()._runPosition; +} + +/** + * Give treasure to the party + */ +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 &c = _activeParty[charIndex]; + if (!c._items[(ItemCategory)categoryNum].isFull() && !c.isDisabledOrDead()) { + giveTreasureToCharacter(c, (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; +} + +/** + * Returns true if all the packs for all the characters are full + */ +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); +} + +/** + * Give a treasure item to the given character's inventory + */ +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..df6864da33 --- /dev/null +++ b/engines/xeen/party.h @@ -0,0 +1,185 @@ +/* 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; + + 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); + + void copyPartyToRoster(); + + 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(); + + void moveToRunLocation(); + + void giveTreasure(); + + 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..0b30ce3e8b --- /dev/null +++ b/engines/xeen/resources.cpp @@ -0,0 +1,1592 @@ +/* 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_ELEMENT = ""; + +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..61022a31cb --- /dev/null +++ b/engines/xeen/saves.cpp @@ -0,0 +1,177 @@ +/* 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; +} + +/** + * Synchronizes a boolean array as a bitfield set + */ +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; +} + +/** + * Sets up the dynamic data for the game for a new game + */ +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..8f112f689e --- /dev/null +++ b/engines/xeen/saves.h @@ -0,0 +1,90 @@ +/* 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: + static void syncBitFlags(Common::Serializer &s, bool *startP, bool *endP); +public: + bool _wonWorld; + bool _wonDarkSide; +public: + SavesManager(XeenEngine *vm, Party &party); + + ~SavesManager(); + + 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..80f0149d7c --- /dev/null +++ b/engines/xeen/screen.cpp @@ -0,0 +1,500 @@ +/* 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() : _vm(nullptr), _enabled(false), _a(0), _border(0), + _xLo(0), _xHi(0), _ycL(0), _ycH(0) { +} + +Window::Window(XeenEngine *vm, const Common::Rect &bounds, int a, int border, + int xLo, int ycL, int xHi, int ycH): + _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"); + } +} + +/** + * Update the window + */ +void Window::update() { + // Since all window drawing is done on the screen surface anyway, + // there's nothing that needs to be updated here +} + +/** + * Adds an area that requires redrawing on the next frame update + */ +void Window::addDirtyRect(const Common::Rect &r) { + _vm->_screen->addDirtyRect(r); +} + +/** + * Fill the content area of a window with the current background color + */ +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); + } +} + +/*------------------------------------------------------------------------*/ + +/** + * Constructor + */ +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(); +} + +/** + * Load a palette resource into the temporary palette + */ +void Screen::loadPalette(const Common::String &name) { + File f(name); + for (int i = 0; i < PALETTE_SIZE; ++i) + _tempPalette[i] = f.readByte() << 2; +} + +/** + * Load a background resource into memory + */ +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)); +} + +/** + * Copy a loaded background into a display page + */ +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); + } + + blitTo(_pages[pageNum]); +} + +void Screen::freePages() { + _pages[0].free(); + _pages[1].free(); +} + +/** + * Merge the two pages along a horizontal split point + */ +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); + } + } +} + +/** + * Merge the two pages along a vertical split point + */ +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(); +} + +/** + * Mark the entire screen for drawing + */ +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); + + _savedScreens[slot - 1].blitTo(*this); +} + +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..21b7e8992e --- /dev/null +++ b/engines/xeen/screen.h @@ -0,0 +1,167 @@ +/* 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(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(); + + void update(); + + void frame(); + + 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); + + void drawScreen(); + + void fadeInner(int step); + + void updatePalette(); + + void updatePalette(const byte *pal, int start, int count16); +public: + 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(); + + void loadPalette(const Common::String &name); + + void loadBackground(const Common::String &name); + + void loadPage(int pageNum); + + void freePages(); + + void horizMerge(int xp); + + 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..175292fa1d --- /dev/null +++ b/engines/xeen/scripts.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 "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; + } + + map.setCellSurfaceFlags(pt, 0x80); + map.setWall(pt, dir, wallVal); + + sound.playFX(10); + intf.draw3d(true); + } +} + +typedef void(Scripts::*ScriptMethodPtr)(Common::Array<byte> &); + +/** + * Handles executing a given script command + */ +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); +} + +/** + * Display a msesage on-screen + */ +void Scripts::cmdDisplay1(Common::Array<byte> ¶ms) { + 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); +} + +/** + * Displays a door text message using the small font + */ +void Scripts::cmdDoorTextSml(Common::Array<byte> ¶ms) { + 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); +} + +/** + * Displays a door text message using the large font + */ +void Scripts::cmdDoorTextLrg(Common::Array<byte> ¶ms) { + 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); +} + +/** + * Show a sign text on-screen + */ +void Scripts::cmdSignText(Common::Array<byte> ¶ms) { + 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> ¶ms) { + Map &map = *_vm->_map; + + if (TownMessage::show(_vm, params[2], _message, map._events._text[params[1]], + params[3])) + _lineNum = params[4] - 1; + + cmdNoAction(params); +} + +/** + * Play a sound FX + */ +void Scripts::cmdPlayFX(Common::Array<byte> ¶ms) { + _vm->_sound->playFX(params[0]); + + cmdNoAction(params); +} + +void Scripts::cmdTeleport(Common::Array<byte> ¶ms) { + 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); + } +} + +/** + * Do a conditional check + */ +void Scripts::cmdIf(Common::Array<byte> ¶ms) { + 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); +} + +/** + * Moves the position of an object + */ +void Scripts::cmdMoveObj(Common::Array<byte> ¶ms) { + 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> ¶ms) { + 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 = ¶ms[5]; + break; + case 25: + case 35: + case 101: + case 106: + mask1 = (params[2] << 8) | params[1]; + extraP = ¶ms[3]; + break; + default: + mask1 = params[1]; + extraP = ¶ms[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); +} + +/** + * Move to the next line of the script + */ +void Scripts::cmdNoAction(Common::Array<byte> ¶ms) { + // Move to next line + _lineNum = _vm->_party->_partyDead ? -1 : _lineNum + 1; +} + +void Scripts::cmdRemove(Common::Array<byte> ¶ms) { + 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); +} + +/** + * Set the currently active character for other script operations + */ +void Scripts::cmdSetChar(Common::Array<byte> ¶ms) { + 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); +} + +/** + * Spawn a monster + */ +void Scripts::cmdSpawn(Common::Array<byte> ¶ms) { + 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> ¶ms) { + _scriptResult = _vm->_town->townAction(params[0]); + _vm->_party->_stepped = true; + _refreshIcons = true; + + cmdExit(params); +} + +/** + * Stop executing the script + */ +void Scripts::cmdExit(Common::Array<byte> ¶ms) { + _lineNum = -1; +} + +/** + * Changes the value for the wall on a given cell + */ +void Scripts::cmdAlterMap(Common::Array<byte> ¶ms) { + 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> ¶ms) { + 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> ¶ms) { + 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> ¶ms) { + 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); +} + +/** + * Jump if a random number matches a given value + */ +void Scripts::cmdJumpRnd(Common::Array<byte> ¶ms) { + int v = _vm->getRandomNumber(1, params[0]); + if (v == params[1]) + _lineNum = params[2] - 1; + + cmdNoAction(params); +} + +/** + * Alter an existing event + */ +void Scripts::cmdAlterEvent(Common::Array<byte> ¶ms) { + 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); +} + +/** + * Stores the current location and line for later resuming, and set up to execute + * a script at a given location + */ +void Scripts::cmdCallEvent(Common::Array<byte> ¶ms) { + _stack.push(StackEntry(_currentPos, _lineNum)); + _currentPos = Common::Point(params[0], params[1]); + _lineNum = params[2] - 1; + + cmdNoAction(params); +} + +/** + * Return from executing a script to the script location that previously + * called the script + */ +void Scripts::cmdReturn(Common::Array<byte> ¶ms) { + StackEntry &se = _stack.top(); + _currentPos = se; + _lineNum = se.line; + + cmdNoAction(params); +} + +void Scripts::cmdSetVar(Common::Array<byte> ¶ms) { + 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> ¶ms) { error("TODO"); } + +void Scripts::cmdWhoWill(Common::Array<byte> ¶ms) { + _charIndex = WhoWill::show(_vm, params[0], params[1], true); + + if (_charIndex == 0) + cmdExit(params); + else + cmdNoAction(params); +} + +void Scripts::cmdRndDamage(Common::Array<byte> ¶ms) { + 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> ¶ms) { + Map &map = *_vm->_map; + + map._mobData._wallItems[params[0]]._position = Common::Point(params[1], params[2]); + cmdNoAction(params); +} + +void Scripts::cmdAlterCellFlag(Common::Array<byte> ¶ms) { + 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> ¶ms) { + 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> ¶ms) { + 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> ¶ms) { + Interface &intf = *_vm->_interface; + + intf._screenText = Common::String::format("\x2\f08\x3""c\t116\v090%s\x3l\fd\x1", + _message); + intf._upDoorText = true; + intf.draw3d(true); + + cmdNoAction(params); +} + +void Scripts::cmdPlayEventVoc(Common::Array<byte> ¶ms) { + 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> ¶ms) { + _windowIndex = 12; + + display(false, 0); + cmdNoAction(params); +} + +void Scripts::cmdIfMapFlag(Common::Array<byte> ¶ms) { + 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> ¶ms) { + _charIndex = _vm->getRandomNumber(1, _vm->_party->_activeParty.size()); + cmdNoAction(params); +} + +void Scripts::cmdGiveEnchanted(Common::Array<byte> ¶ms) { + 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]]++; + } + } + + 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> ¶ms) { + _itemType = params[0]; + + cmdNoAction(params); +} + +/** + * Disable all the scripts at the party's current position + */ +void Scripts::cmdMakeNothingHere(Common::Array<byte> ¶ms) { + 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> ¶ms) { + if (copyProtectionCheck()) + cmdNoAction(params); + else + cmdExit(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 Scripts::cmdChooseNumeric(Common::Array<byte> ¶ms) { + int choice = Choose123::show(_vm, params[0]); + if (choice) { + _lineNum = params[choice] - 1; + } + + cmdNoAction(params); +} + +void Scripts::cmdDisplayBottomTwoLines(Common::Array<byte> ¶ms) { + Map &map = *_vm->_map; + Window &w = _vm->_screen->_windows[12]; + + 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> ¶ms) { + error("TODO: Implement event text loading"); + + display(true, 0); + cmdNoAction(params); +} + +/** + * Exchange the positions of two objects in the maze + */ +void Scripts::cmdExchObj(Common::Array<byte> ¶ms) { + 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> ¶ms) { + 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> ¶ms) { + display(false, 0); + cmdNoAction(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 Scripts::cmdGoto(Common::Array<byte> ¶ms) { + Map &map = *_vm->_map; + map.getCell(1); + if (params[0] == map._currentSurfaceId) + _lineNum = params[1] - 1; + + cmdNoAction(params); +} + +/** + * Pick a random value from the parameter list and jump to that line number + */ +void Scripts::cmdGotoRandom(Common::Array<byte> ¶ms) { + _lineNum = params[_vm->getRandomNumber(1, params[0])] - 1; + cmdNoAction(params); +} + +void Scripts::cmdCutsceneEndDarkside(Common::Array<byte> ¶ms) { + 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> ¶ms) { + _vm->_saves->_wonWorld = true; + _vm->_party->_worldEnd = true; + doWorldEnd(); +} + +void Scripts::cmdFlipWorld(Common::Array<byte> ¶ms) { + _vm->_map->_loadDarkSide = params[0] != 0; +} + +void Scripts::cmdPlayCD(Common::Array<byte> ¶ms) { 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"); +} + +/** + * This monstrosity handles doing the various types of If checks on various data + */ +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[mode][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..15550dd9c0 --- /dev/null +++ b/engines/xeen/scripts.h @@ -0,0 +1,246 @@ +/* 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; + + void doOpcode(MazeEvent &event); + void cmdDisplay1(Common::Array<byte> ¶ms); + void cmdDoorTextSml(Common::Array<byte> ¶ms); + void cmdDoorTextLrg(Common::Array<byte> ¶ms); + void cmdSignText(Common::Array<byte> ¶ms); + void cmdNPC(Common::Array<byte> ¶ms); + void cmdPlayFX(Common::Array<byte> ¶ms); + void cmdTeleport(Common::Array<byte> ¶ms); + void cmdIf(Common::Array<byte> ¶ms); + void cmdMoveObj(Common::Array<byte> ¶ms); + void cmdTakeOrGive(Common::Array<byte> ¶ms); + void cmdNoAction(Common::Array<byte> ¶ms); + void cmdRemove(Common::Array<byte> ¶ms); + void cmdSetChar(Common::Array<byte> ¶ms); + void cmdSpawn(Common::Array<byte> ¶ms); + void cmdDoTownEvent(Common::Array<byte> ¶ms); + void cmdExit(Common::Array<byte> ¶ms); + void cmdAlterMap(Common::Array<byte> ¶ms); + void cmdGiveExtended(Common::Array<byte> ¶ms); + void cmdConfirmWord(Common::Array<byte> ¶ms); + void cmdDamage(Common::Array<byte> ¶ms); + void cmdJumpRnd(Common::Array<byte> ¶ms); + void cmdAlterEvent(Common::Array<byte> ¶ms); + void cmdCallEvent(Common::Array<byte> ¶ms); + void cmdReturn(Common::Array<byte> ¶ms); + void cmdSetVar(Common::Array<byte> ¶ms); + void cmdCutsceneEndClouds(Common::Array<byte> ¶ms); + void cmdWhoWill(Common::Array<byte> ¶ms); + void cmdRndDamage(Common::Array<byte> ¶ms); + void cmdMoveWallObj(Common::Array<byte> ¶ms); + void cmdAlterCellFlag(Common::Array<byte> ¶ms); + void cmdAlterHed(Common::Array<byte> ¶ms); + void cmdDisplayStat(Common::Array<byte> ¶ms); + void cmdSeatTextSml(Common::Array<byte> ¶ms); + void cmdPlayEventVoc(Common::Array<byte> ¶ms); + void cmdDisplayBottom(Common::Array<byte> ¶ms); + void cmdIfMapFlag(Common::Array<byte> ¶ms); + void cmdSelRndChar(Common::Array<byte> ¶ms); + void cmdGiveEnchanted(Common::Array<byte> ¶ms); + void cmdItemType(Common::Array<byte> ¶ms); + void cmdMakeNothingHere(Common::Array<byte> ¶ms); + void cmdCheckProtection(Common::Array<byte> ¶ms); + void cmdChooseNumeric(Common::Array<byte> ¶ms); + void cmdDisplayBottomTwoLines(Common::Array<byte> ¶ms); + void cmdDisplayLarge(Common::Array<byte> ¶ms); + void cmdExchObj(Common::Array<byte> ¶ms); + void cmdFallToMap(Common::Array<byte> ¶ms); + void cmdDisplayMain(Common::Array<byte> ¶ms); + void cmdGoto(Common::Array<byte> ¶ms); + void cmdGotoRandom(Common::Array<byte> ¶ms); + void cmdCutsceneEndDarkside(Common::Array<byte> ¶ms); + void cmdCutsceneEdWorld(Common::Array<byte> ¶ms); + void cmdFlipWorld(Common::Array<byte> ¶ms); + void cmdPlayCD(Common::Array<byte> ¶ms); + + int whoWill(int v1, int v2, int v3); + + void doEndGame(); + + void doEndGame2(); + + void doWorldEnd(); + + void doEnding(const Common::String &endStr, int v2); + + 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..00b92472cc --- /dev/null +++ b/engines/xeen/sound.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/sound.h" + +namespace Xeen { + +void VOC::stop() { + warning("TODO: VOC::stop"); +} + +SoundManager::SoundManager(XeenEngine *vm): _vm(vm) { +} + +void SoundManager::proc2(Common::SeekableReadStream &f) { + +} + +void SoundManager::startMusic(int v1) { + +} + +void SoundManager::stopMusic(int id) { +} + + +} // End of namespace Xeen diff --git a/engines/xeen/sound.h b/engines/xeen/sound.h new file mode 100644 index 0000000000..5c123d7d89 --- /dev/null +++ b/engines/xeen/sound.h @@ -0,0 +1,75 @@ +/* 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 "common/scummsys.h" +#include "common/system.h" +#include "xeen/files.h" + +namespace Xeen { + +class SoundManager; + +class VOC: public Common::File { + friend class SoundManager; +private: + SoundManager *_sound; +public: + VOC() : _sound(nullptr) {} + virtual ~VOC() { stop(); } + + /** + * Stop playing the sound + */ + void stop(); +}; + +class SoundManager { +private: + XeenEngine *_vm; +public: + SoundManager(XeenEngine *vm); + + 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) {} + + void playSound(VOC &voc) {} + + 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..ba4e78bfb9 --- /dev/null +++ b/engines/xeen/spells.cpp @@ -0,0 +1,1346 @@ +/* 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])(); +} + +/** + * Spell being cast failed + */ +void Spells::spellFailed() { + ErrorScroll::show(_vm, SPELL_FAILED, WT_NONFREEZED_WAIT); +} + +/** + * Cast a spell associated with an item + */ +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]); +} + +/** + * Cast a given spell + */ +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; +} + +/** + * Subtract the requirements for a given spell if available, returning + * true if there was sufficient + */ +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; +} + +/** + * Add the SP and gem requirements for a given spell to the given + * character and party + */ +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..1af4fb222f --- /dev/null +++ b/engines/xeen/spells.h @@ -0,0 +1,174 @@ +/* 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); + + 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; + + void castItemSpell(int itemSpellId); + + int castSpell(Character *c, MagicSpell spellId); + + int subSpellCost(Character &c, int spellId); + + 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..3491f6fc7b --- /dev/null +++ b/engines/xeen/sprites.cpp @@ -0,0 +1,352 @@ +/* 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(); +} + +/** + * Copy operator for duplicating a sprite resource + */ +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; +} + +/** + * Load a sprite resource from a given file + */ +void SpriteResource::load(const Common::String &filename) { + File f(filename); + load(f); +} + +/** + * Load a sprite resource from a given file and archive + */ +void SpriteResource::load(const Common::String &filename, Common::Archive &archive) { + File f(filename, archive); + load(f); +} + +/** + * Load a sprite resource from a stream + */ +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(); + } +} + +/** + * Clears the sprite resource + */ +void SpriteResource::clear() { + delete[] _data; + _data = nullptr; + _filesize = 0; +} + +/** + * Draws a frame using data at a specific offset in the sprite resource + */ +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; + int 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..2a00ecaf76 --- /dev/null +++ b/engines/xeen/sprites.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_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; + + 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(); + + SpriteResource &operator=(const SpriteResource &src); + + void load(const Common::String &filename); + + void load(const Common::String &filename, Common::Archive &archive); + + 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..9b4bfa3ab1 --- /dev/null +++ b/engines/xeen/town.cpp @@ -0,0 +1,1314 @@ +/* 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 + int 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 (uint idx = 0; idx < 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); + File f("ahh.voc"); + sound.playSample(&f, 1); + _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; + } + } + } + } + + uint gold, gems; + 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(); +} + +/** + * Returns true if a town location (bank, blacksmith, etc.) is currently active + */ +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..c64ef891f1 --- /dev/null +++ b/engines/xeen/town.h @@ -0,0 +1,130 @@ +/* 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); + + 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_ending.cpp b/engines/xeen/worldofxeen/clouds_ending.cpp new file mode 100644 index 0000000000..75c8755bb7 --- /dev/null +++ b/engines/xeen/worldofxeen/clouds_ending.cpp @@ -0,0 +1,36 @@ +/* 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_ending.h" +#include "xeen/sound.h" + +namespace Xeen { + +bool showCloudsEnding(XeenEngine &vm) { + EventsManager &events = *vm._events; + Screen &screen = *vm._screen; + SoundManager &sound = *vm._sound; + + return true; +} + +} // End of namespace Xeen diff --git a/engines/xeen/worldofxeen/clouds_ending.h b/engines/xeen/worldofxeen/clouds_ending.h new file mode 100644 index 0000000000..fc7945f2a1 --- /dev/null +++ b/engines/xeen/worldofxeen/clouds_ending.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_CLOUDS_ENDING_H +#define XEEN_CLOUDS_ENDING_H + +#include "xeen/xeen.h" + +namespace Xeen { + +bool showCloudsEnding(XeenEngine &vm); + +} // End of namespace Xeen + +#endif /* XEEN_CLOUDS_ENDING_H */ diff --git a/engines/xeen/worldofxeen/clouds_intro.cpp b/engines/xeen/worldofxeen/clouds_intro.cpp new file mode 100644 index 0000000000..1ea6765761 --- /dev/null +++ b/engines/xeen/worldofxeen/clouds_intro.cpp @@ -0,0 +1,36 @@ +/* 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_intro.h" +#include "xeen/sound.h" + +namespace Xeen { + +bool showCloudsTitle(XeenEngine &vm) { + return true; +} + +bool showCloudsIntro(XeenEngine &vm) { + return true; +} + +} // End of namespace Xeen diff --git a/engines/xeen/worldofxeen/clouds_intro.h b/engines/xeen/worldofxeen/clouds_intro.h new file mode 100644 index 0000000000..0bd5633315 --- /dev/null +++ b/engines/xeen/worldofxeen/clouds_intro.h @@ -0,0 +1,36 @@ +/* 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_CLOUDS_INTRO_H +#define XEEN_CLOUDS_INTRO_H + +#include "xeen/xeen.h" + +namespace Xeen { + +bool showCloudsTitle(XeenEngine &vm); + +bool showCloudsIntro(XeenEngine &vm); + +} // End of namespace Xeen + +#endif /* XEEN_CLOUDS_INTRO_H */ diff --git a/engines/xeen/worldofxeen/darkside_ending.cpp b/engines/xeen/worldofxeen/darkside_ending.cpp new file mode 100644 index 0000000000..0a8211f55c --- /dev/null +++ b/engines/xeen/worldofxeen/darkside_ending.cpp @@ -0,0 +1,32 @@ +/* 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_ending.h" +#include "xeen/sound.h" + +namespace Xeen { + +bool showDarkSideEnding(XeenEngine &vm) { + return true; +} + +} // End of namespace Xeen diff --git a/engines/xeen/worldofxeen/darkside_ending.h b/engines/xeen/worldofxeen/darkside_ending.h new file mode 100644 index 0000000000..49dcf2d40b --- /dev/null +++ b/engines/xeen/worldofxeen/darkside_ending.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_DARKSIDE_ENDING_H +#define XEEN_DARKSIDE_ENDING_H + +#include "xeen/xeen.h" + +namespace Xeen { + +bool showDarkSideEnding(XeenEngine &vm); + +} // End of namespace Xeen + +#endif /* XEEN_DARKSIDE_ENDING_H */ diff --git a/engines/xeen/worldofxeen/darkside_intro.cpp b/engines/xeen/worldofxeen/darkside_intro.cpp new file mode 100644 index 0000000000..7ea03286d3 --- /dev/null +++ b/engines/xeen/worldofxeen/darkside_intro.cpp @@ -0,0 +1,234 @@ +/* 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_intro.h" +#include "xeen/sound.h" + +namespace Xeen { + +bool showDarkSideTitle(XeenEngine &vm) { + 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: + sound.playSound(voc[0]); + break; + case 34: + case 44: + ++nwcIndex; + nwcFrame = 0; + break; + case 35: + sound.playSound(voc[1]); + 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 showDarkSideIntro(XeenEngine &vm) { + 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") + }; + Common::File 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; +} + +} // End of namespace Xeen diff --git a/engines/xeen/worldofxeen/darkside_intro.h b/engines/xeen/worldofxeen/darkside_intro.h new file mode 100644 index 0000000000..e37f095347 --- /dev/null +++ b/engines/xeen/worldofxeen/darkside_intro.h @@ -0,0 +1,36 @@ +/* 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_DARKSIDE_INTRO_H +#define XEEN_DARKSIDE_INTRO_H + +#include "xeen/xeen.h" + +namespace Xeen { + +bool showDarkSideTitle(XeenEngine &vm); + +bool showDarkSideIntro(XeenEngine &vm); + +} // End of namespace Xeen + +#endif /* XEEN_DARKSIDE_INTRO_H */ diff --git a/engines/xeen/worldofxeen/worldofxeen_game.cpp b/engines/xeen/worldofxeen/worldofxeen_game.cpp new file mode 100644 index 0000000000..f7c9336c64 --- /dev/null +++ b/engines/xeen/worldofxeen/worldofxeen_game.cpp @@ -0,0 +1,44 @@ +/* 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_game.h" +#include "xeen/worldofxeen/darkside_intro.h" +#include "xeen/sound.h" + +namespace Xeen { + +WorldOfXeenEngine::WorldOfXeenEngine(OSystem *syst, const XeenGameDescription *gameDesc) + : XeenEngine(syst, gameDesc) { + _seenDarkSideIntro = false; +} + +void WorldOfXeenEngine::showIntro() { + // **DEBUG** + if (gDebugLevel == 0) + return; + + bool completed = showDarkSideTitle(*this); + if (!_seenDarkSideIntro && completed) + showDarkSideIntro(*this); +} + +} // End of namespace Xeen diff --git a/engines/xeen/worldofxeen/worldofxeen_game.h b/engines/xeen/worldofxeen/worldofxeen_game.h new file mode 100644 index 0000000000..97a8754470 --- /dev/null +++ b/engines/xeen/worldofxeen/worldofxeen_game.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_WORLDOFXEEN_GAME_H +#define XEEN_WORLDOFXEEN_GAME_H + +#include "xeen/xeen.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 { +protected: + virtual void showIntro(); +public: + bool _seenDarkSideIntro; +public: + WorldOfXeenEngine(OSystem *syst, const XeenGameDescription *gameDesc); + virtual ~WorldOfXeenEngine() {} +}; + +} // End of namespace Xeen + +#endif /* XEEN_WORLDOFXEEN_GAME_H */ diff --git a/engines/xeen/xeen.cpp b/engines/xeen/xeen.cpp new file mode 100644 index 0000000000..90349858ee --- /dev/null +++ b/engines/xeen/xeen.cpp @@ -0,0 +1,354 @@ +/* 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); + _spells = new Spells(this); + _town = new Town(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); +} + +/** + * Main method for playing the game + */ +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("%lu mil", value / 1000000) : + Common::String::format("%lu", 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..cd1b98b1b8 --- /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..04a264a80c --- /dev/null +++ b/engines/xeen/xsurface.cpp @@ -0,0 +1,76 @@ +/* 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 { + +XSurface::XSurface() : Graphics::Surface(), _freeFlag(false) { +} + +XSurface::XSurface(int w, int h) : Graphics::Surface(), _freeFlag(false) { + create(w, h); +} + +XSurface::~XSurface() { + if (_freeFlag) + free(); +} + +void XSurface::create(uint16 w, uint16 h) { + Graphics::Surface::create(w, h, Graphics::PixelFormat::createFormatCLUT8()); + _freeFlag = true; +} + +void XSurface::create(XSurface *s, const Common::Rect &bounds) { + pixels = (byte *)s->getBasePtr(bounds.left, bounds.top); + format = Graphics::PixelFormat::createFormatCLUT8(); + pitch = s->pitch; + w = bounds.width(); + h = bounds.height(); + + _freeFlag = false; +} + +void XSurface::blitTo(XSurface &dest) const { + blitTo(dest, Common::Point()); +} + +void XSurface::blitTo(XSurface &dest, const Common::Point &destPos) const { + if (dest.getPixels() == nullptr) + dest.create(w, h); + + for (int yp = 0; yp < h; ++yp) { + const byte *srcP = (const byte *)getBasePtr(0, yp); + byte *destP = (byte *)dest.getBasePtr(destPos.x, destPos.y + yp); + + Common::copy(srcP, srcP + w, destP); + } + + dest.addDirtyRect(Common::Rect(destPos.x, destPos.y, destPos.x + w, destPos.y + h)); +} + +} // End of namespace Xeen diff --git a/engines/xeen/xsurface.h b/engines/xeen/xsurface.h new file mode 100644 index 0000000000..d8747d4372 --- /dev/null +++ b/engines/xeen/xsurface.h @@ -0,0 +1,56 @@ +/* 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/surface.h" + +namespace Xeen { + +class XSurface: public Graphics::Surface { +private: + bool _freeFlag; +public: + virtual void addDirtyRect(const Common::Rect &r) {} +public: + XSurface(); + XSurface(int w, int h); + virtual ~XSurface(); + + void create(uint16 w, uint16 h); + + void create(XSurface *s, const Common::Rect &bounds); + + void blitTo(XSurface &dest, const Common::Point &destPos) const; + + void blitTo(XSurface &dest) const; + + bool empty() const { return getPixels() == nullptr; } +}; + +} // End of namespace Xeen + +#endif /* XEEN_XSURFACE_H */ |