mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-21 01:08:25 +00:00
1862 lines
44 KiB
C++
1862 lines
44 KiB
C++
/* 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_message.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; Res.ELEMENTAL_CATEGORIES[idx] < _material; ++idx)
|
|
;
|
|
|
|
return (ElementalCategory)idx;
|
|
}
|
|
|
|
AttributeCategory XeenItem::getAttributeCategory() const {
|
|
int m = _material - 59;
|
|
int idx;
|
|
for (idx = 0; Res.ATTRIBUTE_CATEGORIES[idx] < m; ++idx)
|
|
;
|
|
|
|
return (AttributeCategory)idx;
|
|
}
|
|
|
|
const char *XeenItem::getItemName(ItemCategory category, uint id) {
|
|
if (id < 82)
|
|
return Res.ITEM_NAMES[category][id];
|
|
|
|
switch (category) {
|
|
case CATEGORY_WEAPON:
|
|
return Res.QUEST_ITEM_NAMES[id - 82];
|
|
|
|
case CATEGORY_ARMOR:
|
|
return Res.QUEST_ITEM_NAMES[id - 82 + 35];
|
|
|
|
case CATEGORY_ACCESSORY:
|
|
return Res.QUEST_ITEM_NAMES[id - 82 + 35 + 14];
|
|
|
|
default:
|
|
return Res.QUEST_ITEM_NAMES[id - 82 + 35 + 14 + 11];
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
InventoryItems::InventoryItems(Character *character, ItemCategory category):
|
|
_character(character), _category(category) {
|
|
resize(INV_ITEMS_TOTAL);
|
|
|
|
_names = Res.ITEM_NAMES[category];
|
|
}
|
|
|
|
void InventoryItems::clear() {
|
|
for (uint idx = 0; idx < size(); ++idx)
|
|
operator[](idx).clear();
|
|
}
|
|
|
|
bool InventoryItems::passRestrictions(int itemId, bool showError) const {
|
|
CharacterClass charClass = _character->_class;
|
|
|
|
switch (charClass) {
|
|
case CLASS_KNIGHT:
|
|
case CLASS_PALADIN:
|
|
return true;
|
|
|
|
case CLASS_ARCHER:
|
|
case CLASS_CLERIC:
|
|
case CLASS_SORCERER:
|
|
case CLASS_ROBBER:
|
|
case CLASS_NINJA:
|
|
case CLASS_BARBARIAN:
|
|
case CLASS_DRUID:
|
|
case CLASS_RANGER: {
|
|
if (!(Res.ITEM_RESTRICTIONS[itemId + Res.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(Res.NOT_PROFICIENT,
|
|
Res.CLASS_NAMES[charClass], name.c_str());
|
|
ErrorScroll::show(Party::_vm, msg, WT_FREEZE_WAIT);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Common::String InventoryItems::getName(int itemIndex) {
|
|
int id = operator[](itemIndex)._id;
|
|
return _names[id];
|
|
}
|
|
|
|
Common::String InventoryItems::getIdentifiedDetails(int itemIndex) {
|
|
XeenItem &item = operator[](itemIndex);
|
|
|
|
Common::String classes;
|
|
for (int charClass = CLASS_KNIGHT; charClass <= CLASS_RANGER; ++charClass) {
|
|
if (passRestrictions(charClass, true)) {
|
|
const char *const name = Res.CLASS_NAMES[charClass];
|
|
classes += name[0];
|
|
classes += name[1];
|
|
classes += " ";
|
|
}
|
|
}
|
|
if (classes.size() == 30)
|
|
classes = Res.ALL;
|
|
|
|
return getAttributes(item, classes);
|
|
}
|
|
|
|
bool InventoryItems::discardItem(int itemIndex) {
|
|
XeenItem &item = operator[](itemIndex);
|
|
XeenEngine *vm = Party::_vm;
|
|
|
|
if (item._bonusFlags & ITEMFLAG_CURSED) {
|
|
ErrorScroll::show(vm, Res.CANNOT_DISCARD_CURSED_ITEM);
|
|
} else {
|
|
Common::String itemDesc = getFullDescription(itemIndex, 4);
|
|
Common::String msg = Common::String::format(Res.PERMANENTLY_DISCARD, itemDesc.c_str());
|
|
|
|
if (Confirm::show(vm, msg)) {
|
|
operator[](itemIndex).clear();
|
|
sort();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void InventoryItems::sort() {
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
if (operator[](idx)._id == 0) {
|
|
// Found empty slot
|
|
operator[](idx).clear();
|
|
|
|
// Scan through the rest of the list to find any item
|
|
for (uint idx2 = idx + 1; idx2 < size(); ++idx2) {
|
|
if (operator[](idx2)._id) {
|
|
// Found an item, so move it into the blank slot
|
|
operator[](idx) = operator[](idx2);
|
|
operator[](idx2).clear();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void InventoryItems::removeItem(int itemIndex) {
|
|
XeenItem &item = operator[](itemIndex);
|
|
XeenEngine *vm = Party::_vm;
|
|
|
|
if (item._bonusFlags & ITEMFLAG_CURSED)
|
|
ErrorScroll::show(vm, Res.CANNOT_REMOVE_CURSED_ITEM);
|
|
else
|
|
item._frame = 0;
|
|
}
|
|
|
|
XeenEngine *InventoryItems::getVm() {
|
|
return Party::_vm;
|
|
}
|
|
|
|
void InventoryItems::equipError(int itemIndex1, ItemCategory category1, int itemIndex2,
|
|
ItemCategory category2) {
|
|
XeenEngine *vm = Party::_vm;
|
|
|
|
if (itemIndex1 >= 0) {
|
|
Common::String itemName1 = _character->_items[category1].getName(itemIndex1);
|
|
Common::String itemName2 = _character->_items[category2].getName(itemIndex2);
|
|
|
|
MessageDialog::show(vm, Common::String::format(Res.REMOVE_X_TO_EQUIP_Y,
|
|
itemName1.c_str(), itemName2.c_str()));
|
|
} else {
|
|
MessageDialog::show(vm, Common::String::format(Res.EQUIPPED_ALL_YOU_CAN,
|
|
(itemIndex1 == -1) ? Res.RING : Res.MEDAL));
|
|
}
|
|
}
|
|
|
|
void InventoryItems::enchantItem(int itemIndex, int amount) {
|
|
XeenEngine *vm = Party::_vm;
|
|
vm->_sound->playFX(21);
|
|
ErrorScroll::show(vm, Common::String::format(Res.NOT_ENCHANTABLE, Res.SPELL_FAILED));
|
|
}
|
|
|
|
bool InventoryItems::isFull() const {
|
|
return operator[](size() - 1)._id != 0;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
void WeaponItems::equipItem(int itemIndex) {
|
|
XeenItem &item = operator[](itemIndex);
|
|
|
|
if (item._id <= 17) {
|
|
if (passRestrictions(item._id, false)) {
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 13 || i._frame == 1) {
|
|
equipError(itemIndex, CATEGORY_WEAPON, idx, CATEGORY_WEAPON);
|
|
return;
|
|
}
|
|
}
|
|
|
|
item._frame = 1;
|
|
}
|
|
} else if (item._id >= 30 && item._id <= 33) {
|
|
if (passRestrictions(item._id, false)) {
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 4) {
|
|
equipError(itemIndex, CATEGORY_WEAPON, idx, CATEGORY_WEAPON);
|
|
return;
|
|
}
|
|
}
|
|
|
|
item._frame = 4;
|
|
}
|
|
} else {
|
|
if (passRestrictions(item._id, false)) {
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 13 || i._frame == 1) {
|
|
equipError(itemIndex, CATEGORY_WEAPON, idx, CATEGORY_WEAPON);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (uint idx = 0; idx < _character->_armor.size(); ++idx) {
|
|
XeenItem &i = _character->_armor[idx];
|
|
if (i._frame == 2) {
|
|
equipError(itemIndex, CATEGORY_WEAPON, idx, CATEGORY_ARMOR);
|
|
return;
|
|
}
|
|
}
|
|
|
|
item._frame = 13;
|
|
}
|
|
}
|
|
}
|
|
|
|
Common::String WeaponItems::getFullDescription(int itemIndex, int displayNum) {
|
|
XeenItem &i = operator[](itemIndex);
|
|
Resources &res = *getVm()->_resources;
|
|
|
|
return Common::String::format("\f%02u%s%s%s\f%02u%s%s%s", displayNum,
|
|
!i._bonusFlags ? res._maeNames[i._material].c_str() : "",
|
|
(i._bonusFlags & ITEMFLAG_BROKEN) ? Res.ITEM_BROKEN : "",
|
|
(i._bonusFlags & ITEMFLAG_CURSED) ? Res.ITEM_CURSED : "",
|
|
displayNum,
|
|
Res.WEAPON_NAMES[i._id],
|
|
!i._bonusFlags ? "" : Res.BONUS_NAMES[i._bonusFlags & ITEMFLAG_BONUS_MASK],
|
|
(i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED)) ||
|
|
!i._bonusFlags ? "\b " : ""
|
|
);
|
|
}
|
|
|
|
void WeaponItems::enchantItem(int itemIndex, int amount) {
|
|
Sound &sound = *getVm()->_sound;
|
|
XeenItem &item = operator[](itemIndex);
|
|
Character tempCharacter;
|
|
|
|
if (item._material == 0 && item._bonusFlags == 0 && item._id != 34) {
|
|
tempCharacter.makeItem(amount, 0, 1);
|
|
XeenItem &tempItem = tempCharacter._weapons[0];
|
|
|
|
item._material = tempItem._material;
|
|
item._bonusFlags = tempItem._bonusFlags;
|
|
sound.playFX(19);
|
|
} else {
|
|
InventoryItems::enchantItem(itemIndex, amount);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns a text string listing all the stats/attributes of a given item
|
|
*/
|
|
Common::String WeaponItems::getAttributes(XeenItem &item, const Common::String &classes) {
|
|
Common::String attrBonus, elemDamage, physDamage, toHit, specialPower;
|
|
attrBonus = elemDamage = physDamage = toHit = specialPower = Res.FIELD_NONE;
|
|
|
|
// First calculate physical damage
|
|
int minVal = Res.WEAPON_DAMAGE_BASE[item._id];
|
|
int maxVal = minVal * Res.WEAPON_DAMAGE_MULTIPLIER[item._id];
|
|
|
|
if (item._material >= 37 && item._material <= 58) {
|
|
minVal += Res.METAL_DAMAGE[item._material - 37];
|
|
maxVal += Res.METAL_DAMAGE[item._material - 37];
|
|
toHit = Common::String::format("%+d", Res.METAL_DAMAGE_PERCENT[item._material - 37]);
|
|
}
|
|
|
|
physDamage = Common::String::format(Res.DAMAGE_X_TO_Y, minVal, maxVal);
|
|
|
|
// Next handle elemental/attribute damage
|
|
if (item._material < 37) {
|
|
int damage = Res.ELEMENTAL_DAMAGE[item._material];
|
|
if (damage > 0) {
|
|
ElementalCategory elemCategory = item.getElementalCategory();
|
|
elemDamage = Common::String::format(Res.ELEMENTAL_XY_DAMAGE,
|
|
damage, Res.ELEMENTAL_NAMES[elemCategory]);
|
|
}
|
|
} else if (item._material >= 59) {
|
|
int bonus = Res.ATTRIBUTE_BONUSES[item._material - 59];
|
|
AttributeCategory attrCategory = item.getAttributeCategory();
|
|
attrBonus = Common::String::format(Res.ATTR_XY_BONUS, bonus,
|
|
Res.ATTRIBUTE_NAMES[attrCategory]);
|
|
}
|
|
|
|
// Handle weapon effective against
|
|
int effective = item._bonusFlags & ITEMFLAG_BONUS_MASK;
|
|
if (effective) {
|
|
specialPower = Common::String::format(Res.EFFECTIVE_AGAINST,
|
|
Res.EFFECTIVENESS_NAMES[effective]);
|
|
}
|
|
|
|
return Common::String::format(Res.ITEM_DETAILS, classes.c_str(),
|
|
toHit.c_str(), physDamage.c_str(), elemDamage.c_str(),
|
|
Res.FIELD_NONE, Res.FIELD_NONE, attrBonus.c_str(), specialPower.c_str()
|
|
);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
void ArmorItems::equipItem(int itemIndex) {
|
|
XeenItem &item = operator[](itemIndex);
|
|
|
|
if (item._id <= 7) {
|
|
if (passRestrictions(item._id, false)) {
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 9) {
|
|
equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
|
|
return;
|
|
}
|
|
}
|
|
|
|
item._frame = 3;
|
|
}
|
|
} else if (item._id == 8) {
|
|
if (passRestrictions(item._id, false)) {
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 2) {
|
|
equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (uint idx = 0; idx < _character->_weapons.size(); ++idx) {
|
|
XeenItem &i = _character->_weapons[idx];
|
|
if (i._frame == 13) {
|
|
equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_WEAPON);
|
|
return;
|
|
}
|
|
}
|
|
|
|
item._frame = 2;
|
|
}
|
|
} else if (item._id == 9) {
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 5) {
|
|
equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
|
|
return;
|
|
}
|
|
}
|
|
|
|
item._frame = 5;
|
|
} else if (item._id == 10) {
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 9) {
|
|
equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
|
|
return;
|
|
}
|
|
}
|
|
|
|
item._frame = 9;
|
|
} else if (item._id <= 12) {
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 10) {
|
|
equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
|
|
return;
|
|
}
|
|
}
|
|
|
|
item._frame = 10;
|
|
} else {
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 6) {
|
|
equipError(itemIndex, CATEGORY_ARMOR, idx, CATEGORY_ARMOR);
|
|
return;
|
|
}
|
|
}
|
|
|
|
item._frame = 6;
|
|
}
|
|
}
|
|
|
|
Common::String ArmorItems::getFullDescription(int itemIndex, int displayNum) {
|
|
XeenItem &i = operator[](itemIndex);
|
|
Resources &res = *getVm()->_resources;
|
|
|
|
return Common::String::format("\f%02u%s%s%s\f%02u%s%s", displayNum,
|
|
!i._bonusFlags ? "" : res._maeNames[i._material].c_str(),
|
|
(i._bonusFlags & ITEMFLAG_BROKEN) ? Res.ITEM_BROKEN : "",
|
|
(i._bonusFlags & ITEMFLAG_CURSED) ? Res.ITEM_CURSED : "",
|
|
displayNum,
|
|
Res.ARMOR_NAMES[i._id],
|
|
(i._bonusFlags & (ITEMFLAG_BROKEN | ITEMFLAG_CURSED)) ||
|
|
!i._bonusFlags ? "\b " : ""
|
|
);
|
|
}
|
|
|
|
void ArmorItems::enchantItem(int itemIndex, int amount) {
|
|
Sound &sound = *getVm()->_sound;
|
|
XeenItem &item = operator[](itemIndex);
|
|
Character tempCharacter;
|
|
|
|
if (item._material == 0 && item._bonusFlags == 0) {
|
|
tempCharacter.makeItem(amount, 0, 2);
|
|
XeenItem &tempItem = tempCharacter._armor[0];
|
|
|
|
item._material = tempItem._material;
|
|
item._bonusFlags = tempItem._bonusFlags;
|
|
sound.playFX(19);
|
|
} else {
|
|
InventoryItems::enchantItem(itemIndex, amount);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns a text string listing all the stats/attributes of a given item
|
|
*/
|
|
Common::String ArmorItems::getAttributes(XeenItem &item, const Common::String &classes) {
|
|
Common::String elemResist, attrBonus, acBonus;
|
|
elemResist = attrBonus = acBonus = Res.FIELD_NONE;
|
|
|
|
if (item._material < 36) {
|
|
int resistence = Res.ELEMENTAL_RESISTENCES[item._material];
|
|
if (resistence > 0) {
|
|
int eCategory = ELEM_FIRE;
|
|
while (eCategory < ELEM_MAGIC && Res.ELEMENTAL_CATEGORIES[eCategory] < item._material)
|
|
++eCategory;
|
|
|
|
elemResist = Common::String::format(Res.ATTR_XY_BONUS, resistence,
|
|
Res.ELEMENTAL_NAMES[eCategory]);
|
|
}
|
|
} else if (item._material >= 59) {
|
|
int bonus = Res.ATTRIBUTE_BONUSES[item._material - 59];
|
|
AttributeCategory aCategory = item.getAttributeCategory();
|
|
attrBonus = Common::String::format(Res.ATTR_XY_BONUS, bonus,
|
|
Res.ATTRIBUTE_NAMES[aCategory]);
|
|
}
|
|
|
|
int strength = Res.ARMOR_STRENGTHS[item._id];
|
|
if (item._material >= 37 && item._material <= 58) {
|
|
strength += Res.METAL_LAC[item._material - 37];
|
|
}
|
|
acBonus = Common::String::format("%+d", strength);
|
|
|
|
return Common::String::format(Res.ITEM_DETAILS, classes.c_str(),
|
|
Res.FIELD_NONE, Res.FIELD_NONE, Res.FIELD_NONE,
|
|
elemResist.c_str(), acBonus.c_str(), attrBonus.c_str(), Res.FIELD_NONE);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
void AccessoryItems::equipItem(int itemIndex) {
|
|
XeenItem &item = operator[](itemIndex);
|
|
|
|
if (item._id == 1) {
|
|
int count = 0;
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 8)
|
|
++count;
|
|
}
|
|
|
|
if (count <= 1)
|
|
item._frame = 8;
|
|
else
|
|
equipError(-1, CATEGORY_ACCESSORY, itemIndex, CATEGORY_ACCESSORY);
|
|
} else if (item._id == 2) {
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 12) {
|
|
equipError(itemIndex, CATEGORY_ACCESSORY, idx, CATEGORY_ACCESSORY);
|
|
return;
|
|
}
|
|
}
|
|
} else if (item._id <= 7) {
|
|
int count = 0;
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 7)
|
|
++count;
|
|
}
|
|
|
|
if (count <= 1)
|
|
item._frame = 7;
|
|
else
|
|
equipError(-2, CATEGORY_ACCESSORY, itemIndex, CATEGORY_ACCESSORY);
|
|
} else {
|
|
for (uint idx = 0; idx < size(); ++idx) {
|
|
XeenItem &i = operator[](idx);
|
|
if (i._frame == 11) {
|
|
equipError(itemIndex, CATEGORY_ACCESSORY, idx, CATEGORY_ACCESSORY);
|
|
return;
|
|
}
|
|
}
|
|
|
|
item._frame = 11;
|
|
}
|
|
}
|
|
|
|
Common::String AccessoryItems::getFullDescription(int itemIndex, int displayNum) {
|
|
XeenItem &i = operator[](itemIndex);
|
|
Resources &res = *getVm()->_resources;
|
|
|
|
return Common::String::format("\f%02u%s%s%s\f%02u%s%s", displayNum,
|
|
!i._bonusFlags ? "" : res._maeNames[i._material].c_str(),
|
|
(i._bonusFlags & ITEMFLAG_BROKEN) ? Res.ITEM_BROKEN : "",
|
|
(i._bonusFlags & ITEMFLAG_CURSED) ? Res.ITEM_CURSED : "",
|
|
displayNum,
|
|
Res.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 = Res.FIELD_NONE;
|
|
|
|
if (item._material < 36) {
|
|
int resistence = Res.ELEMENTAL_RESISTENCES[item._material];
|
|
if (resistence > 0) {
|
|
int eCategory = ELEM_FIRE;
|
|
while (eCategory < ELEM_MAGIC && Res.ELEMENTAL_CATEGORIES[eCategory] < item._material)
|
|
++eCategory;
|
|
|
|
elemResist = Common::String::format(Res.ATTR_XY_BONUS, resistence,
|
|
Res.ELEMENTAL_NAMES[eCategory]);
|
|
}
|
|
} else if (item._material >= 59) {
|
|
int bonus = Res.ATTRIBUTE_BONUSES[item._material - 59];
|
|
AttributeCategory aCategory = item.getAttributeCategory();
|
|
attrBonus = Common::String::format(Res.ATTR_XY_BONUS, bonus,
|
|
Res.ATTRIBUTE_NAMES[aCategory]);
|
|
}
|
|
|
|
return Common::String::format(Res.ITEM_DETAILS, classes.c_str(),
|
|
Res.FIELD_NONE, Res.FIELD_NONE, Res.FIELD_NONE,
|
|
elemResist.c_str(), Res.FIELD_NONE, attrBonus.c_str(), Res.FIELD_NONE);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
Common::String MiscItems::getFullDescription(int itemIndex, int displayNum) {
|
|
XeenItem &i = operator[](itemIndex);
|
|
Resources &res = *getVm()->_resources;
|
|
|
|
return Common::String::format("\f%02u%s%s%s\f%02u%s%s", displayNum,
|
|
!i._bonusFlags ? "" : res._maeNames[i._material].c_str(),
|
|
(i._bonusFlags & ITEMFLAG_BROKEN) ? Res.ITEM_BROKEN : "",
|
|
(i._bonusFlags & ITEMFLAG_CURSED) ? Res.ITEM_CURSED : "",
|
|
displayNum,
|
|
Res.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 = Res.FIELD_NONE;
|
|
Spells &spells = *getVm()->_spells;
|
|
|
|
if (item._id) {
|
|
specialPower = spells._spellNames[Res.MISC_SPELL_INDEX[item._id]];
|
|
}
|
|
|
|
return Common::String::format(Res.ITEM_DETAILS, classes.c_str(),
|
|
Res.FIELD_NONE, Res.FIELD_NONE, Res.FIELD_NONE, Res.FIELD_NONE, Res.FIELD_NONE,
|
|
Res.FIELD_NONE, specialPower.c_str());
|
|
}
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
InventoryItemsGroup::InventoryItemsGroup(InventoryItems &weapons, InventoryItems &armor,
|
|
InventoryItems &accessories, InventoryItems &misc) {
|
|
_itemSets[0] = &weapons;
|
|
_itemSets[1] = &armor;
|
|
_itemSets[2] = &accessories;
|
|
_itemSets[3] = &misc;
|
|
}
|
|
|
|
InventoryItems &InventoryItemsGroup::operator[](ItemCategory category) {
|
|
return *_itemSets[category];
|
|
}
|
|
|
|
void InventoryItemsGroup::breakAllItems() {
|
|
for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
|
|
if ((*_itemSets[0])[idx]._id != 34) {
|
|
(*_itemSets[0])[idx]._bonusFlags |= ITEMFLAG_BROKEN;
|
|
(*_itemSets[0])[idx]._frame = 0;
|
|
}
|
|
|
|
(*_itemSets[1])[idx]._bonusFlags |= ITEMFLAG_BROKEN;
|
|
(*_itemSets[2])[idx]._bonusFlags |= ITEMFLAG_BROKEN;
|
|
(*_itemSets[3])[idx]._bonusFlags |= ITEMFLAG_BROKEN;
|
|
(*_itemSets[1])[idx]._frame = 0;
|
|
(*_itemSets[2])[idx]._frame = 0;
|
|
}
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
|
|
void AttributePair::synchronize(Common::Serializer &s) {
|
|
s.syncAsByte(_permanent);
|
|
s.syncAsByte(_temporary);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
AttributePair::AttributePair() {
|
|
_temporary = _permanent = 0;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------*/
|
|
|
|
Character::Character():
|
|
_weapons(this), _armor(this), _accessories(this), _misc(this),
|
|
_items(_weapons, _armor, _accessories, _misc) {
|
|
clear();
|
|
_faceSprites = nullptr;
|
|
_rosterId = -1;
|
|
}
|
|
|
|
void Character::clear() {
|
|
_sex = MALE;
|
|
_race = HUMAN;
|
|
_xeenSide = 0;
|
|
_class = CLASS_KNIGHT;
|
|
_ACTemp = 0;
|
|
_birthDay = 0;
|
|
_tempAge = 0;
|
|
Common::fill(&_skills[0], &_skills[18], 0);
|
|
Common::fill(&_awards[0], &_awards[128], 0);
|
|
Common::fill(&_spells[0], &_spells[39], false);
|
|
_lloydMap = 0;
|
|
_hasSpells = false;
|
|
_currentSpell = 0;
|
|
_quickOption = QUICK_ATTACK;
|
|
_lloydSide = 0;
|
|
Common::fill(&_conditions[0], &_conditions[16], 0);
|
|
_townUnknown = 0;
|
|
_savedMazeId = 0;
|
|
_currentHp = 0;
|
|
_currentSp = 0;
|
|
_birthYear = 0;
|
|
_experience = 0;
|
|
_currentAdventuringSpell = 0;
|
|
_currentCombatSpell = 0;
|
|
|
|
_might._permanent = _might._temporary = 0;
|
|
_intellect._permanent = _intellect._temporary = 0;
|
|
_personality._permanent = _personality._temporary = 0;
|
|
_endurance._permanent = _endurance._temporary = 0;
|
|
_speed._permanent = _speed._temporary = 0;
|
|
_accuracy._permanent = _accuracy._temporary = 0;
|
|
_luck._permanent = _luck._temporary = 0;
|
|
_fireResistence._permanent = _fireResistence._temporary = 0;
|
|
_coldResistence._permanent = _coldResistence._temporary = 0;
|
|
_electricityResistence._permanent = _electricityResistence._temporary = 0;
|
|
_poisonResistence._permanent = _poisonResistence._temporary = 0;
|
|
_energyResistence._permanent = _energyResistence._temporary = 0;
|
|
_magicResistence._permanent = _magicResistence._temporary = 0;
|
|
_weapons.clear();
|
|
_armor.clear();
|
|
_accessories.clear();
|
|
_misc.clear();
|
|
}
|
|
|
|
void Character::synchronize(Common::Serializer &s) {
|
|
char name[16];
|
|
Common::fill(&name[0], &name[16], '\0');
|
|
Common::strlcpy(name, _name.c_str(), 16);
|
|
s.syncBytes((byte *)name, 16);
|
|
|
|
if (s.isLoading())
|
|
_name = Common::String(name);
|
|
|
|
s.syncAsByte(_sex);
|
|
s.syncAsByte(_race);
|
|
s.syncAsByte(_xeenSide);
|
|
s.syncAsByte(_class);
|
|
|
|
_might.synchronize(s);
|
|
_intellect.synchronize(s);
|
|
_personality.synchronize(s);
|
|
_endurance.synchronize(s);
|
|
_speed.synchronize(s);
|
|
_accuracy.synchronize(s);
|
|
_luck.synchronize(s);
|
|
s.syncAsByte(_ACTemp);
|
|
_level.synchronize(s);
|
|
s.syncAsByte(_birthDay);
|
|
s.syncAsByte(_tempAge);
|
|
|
|
// Synchronize the skill list
|
|
for (int idx = 0; idx < 18; ++idx)
|
|
s.syncAsByte(_skills[idx]);
|
|
|
|
// Synchronize character awards. The original packed awards 64..127 in the
|
|
// upper nibble of the first 64 bytes. Except for award 9, which was a full
|
|
// byte counter counting the number of times the warzone was awarded
|
|
for (int idx = 0; idx < 64; ++idx) {
|
|
byte b = (idx == WARZONE_AWARD) ? _awards[idx] :
|
|
(_awards[idx] ? 0x1 : 0) | (_awards[idx + 64] ? 0x10 : 0);
|
|
s.syncAsByte(b);
|
|
if (s.isLoading()) {
|
|
_awards[idx] = (idx == WARZONE_AWARD) ? b : b & 0xF;
|
|
_awards[idx + 64] = (idx == WARZONE_AWARD) ? 0 : (b >> 4) & 0xf;
|
|
}
|
|
}
|
|
|
|
// Synchronize spell list
|
|
for (int i = 0; i < MAX_SPELLS_PER_CLASS; ++i)
|
|
s.syncAsByte(_spells[i]);
|
|
s.syncAsByte(_lloydMap);
|
|
s.syncAsByte(_lloydPosition.x);
|
|
s.syncAsByte(_lloydPosition.y);
|
|
s.syncAsByte(_hasSpells);
|
|
s.syncAsByte(_currentSpell);
|
|
s.syncAsByte(_quickOption);
|
|
|
|
for (int i = 0; i < 9; ++i)
|
|
_weapons[i].synchronize(s);
|
|
for (int i = 0; i < 9; ++i)
|
|
_armor[i].synchronize(s);
|
|
for (int i = 0; i < 9; ++i)
|
|
_accessories[i].synchronize(s);
|
|
for (int i = 0; i < 9; ++i)
|
|
_misc[i].synchronize(s);
|
|
|
|
s.syncAsByte(_lloydSide);
|
|
_fireResistence.synchronize(s);
|
|
_coldResistence.synchronize(s);
|
|
_electricityResistence.synchronize(s);
|
|
_poisonResistence.synchronize(s);
|
|
_energyResistence.synchronize(s);
|
|
_magicResistence.synchronize(s);
|
|
|
|
for (int i = 0; i < 16; ++i)
|
|
s.syncAsByte(_conditions[i]);
|
|
|
|
s.syncAsUint16LE(_townUnknown);
|
|
s.syncAsByte(_savedMazeId);
|
|
s.syncAsUint16LE(_currentHp);
|
|
s.syncAsUint16LE(_currentSp);
|
|
s.syncAsUint16LE(_birthYear);
|
|
s.syncAsUint32LE(_experience);
|
|
s.syncAsByte(_currentAdventuringSpell);
|
|
s.syncAsByte(_currentCombatSpell);
|
|
}
|
|
|
|
Condition Character::worstCondition() const {
|
|
for (int cond = ERADICATED; cond >= CURSED; --cond) {
|
|
if (_conditions[cond])
|
|
return (Condition)cond;
|
|
}
|
|
|
|
return NO_CONDITION;
|
|
}
|
|
|
|
bool Character::isDisabled() const {
|
|
Condition condition = worstCondition();
|
|
|
|
return condition == ASLEEP || condition == PARALYZED || condition == UNCONSCIOUS
|
|
|| condition == STONED || condition == ERADICATED;
|
|
}
|
|
|
|
bool Character::isDisabledOrDead() const {
|
|
Condition condition = worstCondition();
|
|
|
|
return condition == ASLEEP || (condition >= PARALYZED && condition <= ERADICATED);
|
|
}
|
|
|
|
bool Character::isDead() const {
|
|
Condition condition = worstCondition();
|
|
|
|
return condition >= DEAD && condition <= ERADICATED;
|
|
}
|
|
|
|
int Character::getAge(bool ignoreTemp) const {
|
|
int year = MIN(Party::_vm->_party->_year - _birthYear, (uint)254);
|
|
|
|
return ignoreTemp ? year : year + _tempAge;
|
|
}
|
|
|
|
int Character::getMaxHP() const {
|
|
int hp = Res.BASE_HP_BY_CLASS[_class];
|
|
hp += statBonus(getStat(ENDURANCE));
|
|
hp += Res.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 += Res.RACE_SP_BONUSES[_race][attrib - 1];
|
|
|
|
if (_skills[skill])
|
|
result += 2;
|
|
if (result < 1)
|
|
result = 1;
|
|
|
|
// Multiply it by the character's level
|
|
result *= getCurrentLevel();
|
|
|
|
// Classes other than sorcerer, clerics, and druids only get half the SP
|
|
if (_class != CLASS_SORCERER && _class != CLASS_CLERIC && _class != CLASS_DRUID)
|
|
result /= 2;
|
|
|
|
if (flag || (_class != CLASS_DRUID && _class != CLASS_RANGER))
|
|
break;
|
|
|
|
// Druids and rangers get bonuses averaged on both personality and intellect
|
|
attrib = INTELLECT;
|
|
flag = true;
|
|
amount = result;
|
|
}
|
|
if (flag)
|
|
result = (amount + result) / 2;
|
|
|
|
result += itemScan(8);
|
|
if (result < 0)
|
|
result = 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
uint Character::getStat(Attribute attrib, bool baseOnly) const {
|
|
AttributePair attr;
|
|
int mode = 0;
|
|
|
|
switch (attrib) {
|
|
case MIGHT:
|
|
attr = _might;
|
|
break;
|
|
case INTELLECT:
|
|
attr = _intellect;
|
|
mode = 1;
|
|
break;
|
|
case PERSONALITY:
|
|
attr = _personality;
|
|
mode = 1;
|
|
break;
|
|
case ENDURANCE:
|
|
attr = _endurance;
|
|
break;
|
|
case SPEED:
|
|
attr = _speed;
|
|
break;
|
|
case ACCURACY:
|
|
attr = _accuracy;
|
|
break;
|
|
case LUCK:
|
|
attr = _luck;
|
|
mode = 2;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
// All the attributes except luck are affected by the character's age
|
|
if (mode < 2) {
|
|
int age = getAge(false);
|
|
int ageIndex = 0;
|
|
while (Res.AGE_RANGES[ageIndex] <= age)
|
|
++ageIndex;
|
|
|
|
attr._permanent += Res.AGE_RANGES_ADJUST[mode][ageIndex];
|
|
}
|
|
|
|
|
|
attr._permanent += itemScan((int)attrib);
|
|
|
|
if (!baseOnly) {
|
|
attr._permanent += conditionMod(attrib);
|
|
attr._permanent += attr._temporary;
|
|
}
|
|
|
|
return MAX(attr._permanent, (uint)0);
|
|
}
|
|
|
|
int Character::statColor(int amount, int threshold) {
|
|
if (amount < 1)
|
|
return 6;
|
|
else if (amount > threshold)
|
|
return 2;
|
|
else if (amount == threshold)
|
|
return 15;
|
|
else if (amount <= (threshold / 4))
|
|
return 9;
|
|
else
|
|
return 32;
|
|
}
|
|
|
|
int Character::statBonus(uint statValue) const {
|
|
int idx;
|
|
for (idx = 0; Res.STAT_VALUES[idx] <= statValue; ++idx)
|
|
;
|
|
|
|
return Res.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(Res.IN_NO_CONDITION, _name.c_str());
|
|
ErrorScroll::show(Party::_vm, msg,
|
|
Party::_vm->_mode == 17 ? WT_LOC_WAIT : 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 ? 1 : 0;
|
|
}
|
|
|
|
bool Character::hasAward(int awardId) const {
|
|
int v = awardId;
|
|
if (awardId == 73)
|
|
v = 126;
|
|
else if (awardId == 81)
|
|
v = 127;
|
|
|
|
return _awards[v];
|
|
}
|
|
|
|
int Character::getArmorClass(bool baseOnly) const {
|
|
Party &party = *Party::_vm->_party;
|
|
|
|
int result = statBonus(getStat(SPEED)) + itemScan(9);
|
|
if (!baseOnly)
|
|
result += party._blessed + _ACTemp;
|
|
|
|
return MAX(result, 0);
|
|
}
|
|
|
|
int Character::getThievery() const {
|
|
int result = getCurrentLevel() * 2;
|
|
|
|
if (_class == CLASS_NINJA)
|
|
result += 15;
|
|
else if (_class == CLASS_ROBBER)
|
|
result += 30;
|
|
|
|
switch (_race) {
|
|
case ELF:
|
|
case GNOME:
|
|
result += 10;
|
|
break;
|
|
case DWARF:
|
|
result += 5;
|
|
break;
|
|
case HALF_ORC:
|
|
result -= 10;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
result += itemScan(10);
|
|
|
|
// If the character doesn't have a thievery skill, then don't 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;
|
|
|
|
// Weapons
|
|
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 += Res.ATTRIBUTE_BONUSES[item._material - 59];
|
|
}
|
|
}
|
|
|
|
// Armor
|
|
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 += Res.ATTRIBUTE_BONUSES[item._material - 59];
|
|
}
|
|
|
|
if (itemId > 10 && item._material < 37) {
|
|
int mIndex = item.getElementalCategory() + 11;
|
|
|
|
if (mIndex == itemId) {
|
|
result += Res.ELEMENTAL_RESISTENCES[item._material];
|
|
}
|
|
}
|
|
|
|
if (itemId == 9) {
|
|
result += Res.ARMOR_STRENGTHS[item._id];
|
|
|
|
if (item._material >= 37 && item._material <= 58)
|
|
result += Res.METAL_LAC[item._material - 37];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Accessories
|
|
for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) {
|
|
const XeenItem &item = _accessories[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 += Res.ATTRIBUTE_BONUSES[item._material - 59];
|
|
}
|
|
}
|
|
|
|
if (itemId > 10 && item._material < 37) {
|
|
int mIndex = item.getElementalCategory() + 11;
|
|
|
|
if (mIndex == itemId)
|
|
result += Res.ELEMENTAL_RESISTENCES[item._material];
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int Character::conditionMod(Attribute attrib) const {
|
|
if (_conditions[DEAD] || _conditions[STONED] || _conditions[ERADICATED])
|
|
return 0;
|
|
|
|
int v[7];
|
|
Common::fill(&v[0], &v[7], 0);
|
|
if (_conditions[CURSED])
|
|
v[6] -= _conditions[CURSED];
|
|
|
|
if (_conditions[INSANE]) {
|
|
v[2] -= _conditions[INSANE];
|
|
v[1] -= _conditions[INSANE];
|
|
v[5] -= _conditions[INSANE];
|
|
v[0] -= _conditions[INSANE];
|
|
v[4] -= _conditions[INSANE];
|
|
}
|
|
|
|
if (_conditions[POISONED]) {
|
|
v[0] -= _conditions[POISONED];
|
|
v[4] -= _conditions[POISONED];
|
|
v[5] -= _conditions[POISONED];
|
|
}
|
|
|
|
if (_conditions[DISEASED]) {
|
|
v[3] -= _conditions[DISEASED];
|
|
v[2] -= _conditions[DISEASED];
|
|
v[1] -= _conditions[DISEASED];
|
|
}
|
|
|
|
for (int idx = 0; idx < 7; ++idx) {
|
|
v[idx] -= _conditions[HEART_BROKEN];
|
|
v[idx] -= _conditions[IN_LOVE];
|
|
v[idx] -= _conditions[WEAK];
|
|
v[idx] -= _conditions[DRUNK];
|
|
}
|
|
|
|
return v[attrib];
|
|
}
|
|
|
|
void Character::setValue(int id, uint value) {
|
|
Party &party = *Party::_vm->_party;
|
|
Scripts &scripts = *Party::_vm->_scripts;
|
|
|
|
switch (id) {
|
|
case 3:
|
|
// Set character sex
|
|
_sex = (Sex)value;
|
|
break;
|
|
case 4:
|
|
// Set race
|
|
_race = (Race)value;
|
|
break;
|
|
case 5:
|
|
// Set class
|
|
_class = (CharacterClass)value;
|
|
break;
|
|
case 8:
|
|
// Set the current Hp
|
|
_currentHp = value;
|
|
break;
|
|
case 9:
|
|
// Set the current Sp
|
|
_currentSp = value;
|
|
break;
|
|
case 10:
|
|
case 77:
|
|
// Set temporary armor class
|
|
_ACTemp = value;
|
|
break;
|
|
case 11:
|
|
// Set temporary level
|
|
_level._temporary = value;
|
|
break;
|
|
case 12:
|
|
// Set the character's temporary age
|
|
_tempAge = value;
|
|
break;
|
|
case 16:
|
|
// Set character experience
|
|
_experience = value;
|
|
break;
|
|
case 17:
|
|
// Set party poison resistence
|
|
party._poisonResistence = value;
|
|
break;
|
|
case 18:
|
|
// Set condition
|
|
if (value == 16) {
|
|
// Clear all the conditions
|
|
clearConditions();
|
|
} 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:
|
|
party._levitateCount = value;
|
|
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._levitateCount = value;
|
|
break;
|
|
case 76:
|
|
// Set day of the year (0-99)
|
|
party._day = value;
|
|
break;
|
|
case 79:
|
|
party._wizardEyeActive = true;
|
|
break;
|
|
case 83:
|
|
scripts._nEdamageType = (DamageType)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 {
|
|
FileManager &files = *g_vm->_files;
|
|
Party &party = *g_vm->_party;
|
|
|
|
if (party._mazeId == 49 && !files._isDarkCc) {
|
|
return hasAward(SHANGRILA_GUILD_MEMBER);
|
|
}
|
|
|
|
switch (party._mazeId) {
|
|
case 29:
|
|
return hasAward(CASTLEVIEW_GUILD_MEMBER);
|
|
case 31:
|
|
return hasAward(SANDCASTER_GUILD_MEMBER);
|
|
case 33:
|
|
return hasAward(LAKESIDE_GUILD_MEMBER);
|
|
case 35:
|
|
return hasAward(NECROPOLIS_GUILD_MEMBER);
|
|
default:
|
|
return hasAward(OLYMPUS_GUILD_MEMBER);
|
|
}
|
|
}
|
|
|
|
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;
|
|
assert(_level._permanent > 0);
|
|
shift = _level._permanent - 1;
|
|
}
|
|
|
|
return (base * 1024000) + (Res.CLASS_EXP_LEVELS[_class] << shift);
|
|
}
|
|
|
|
uint Character::getCurrentExperience() const {
|
|
int lev = _level._permanent - 1;
|
|
int shift, base;
|
|
|
|
if (lev == 0)
|
|
return _experience;
|
|
|
|
if (lev >= 12) {
|
|
base = lev - 12;
|
|
shift = 10;
|
|
} else {
|
|
base = 0;
|
|
shift = lev - 1;
|
|
}
|
|
|
|
return (base * 1024000) + (Res.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 < AWARDS_TOTAL; ++idx) {
|
|
if (hasAward(idx))
|
|
++total;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
int Character::makeItem(int p1, int itemIndex, int p3) {
|
|
XeenEngine *vm = Party::_vm;
|
|
Scripts &scripts = *vm->_scripts;
|
|
|
|
if (!p1)
|
|
return 0;
|
|
|
|
int itemId = 0;
|
|
int v4 = vm->getRandomNumber(100);
|
|
int v6 = vm->getRandomNumber(p1 < 6 ? 100 : 80);
|
|
ItemCategory category;
|
|
int v16 = 0, v14 = 0, miscBonus = 0, miscId = 0, v8 = 0, v12 = 0;
|
|
|
|
// Randomly pick a category and item Id
|
|
if (p3 == 12) {
|
|
if (scripts._itemType < 35) {
|
|
category = CATEGORY_WEAPON;
|
|
itemId = scripts._itemType;
|
|
} else if (scripts._itemType < 49) {
|
|
category = CATEGORY_ARMOR;
|
|
itemId = scripts._itemType - 35;
|
|
} else if (scripts._itemType < 60) {
|
|
category = CATEGORY_ACCESSORY;
|
|
itemId = scripts._itemType - 49;
|
|
} else {
|
|
category = CATEGORY_MISC;
|
|
itemId = scripts._itemType - 60;
|
|
}
|
|
} else {
|
|
switch (p3) {
|
|
case 1:
|
|
v4 = 35;
|
|
break;
|
|
case 2:
|
|
v4 = 60;
|
|
break;
|
|
case 3:
|
|
v4 = 100;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (p1 == 1) {
|
|
if (v4 <= 40) {
|
|
category = CATEGORY_WEAPON;
|
|
if (v6 <= 30) {
|
|
itemId = vm->getRandomNumber(1, 6);
|
|
} else if (v6 <= 60) {
|
|
itemId = vm->getRandomNumber(7, 17);
|
|
} else if (v6 <= 85) {
|
|
itemId = vm->getRandomNumber(18, 29);
|
|
} else {
|
|
itemId = vm->getRandomNumber(30, 33);
|
|
}
|
|
} else if (v4 <= 85) {
|
|
category = CATEGORY_ARMOR;
|
|
itemId = vm->getRandomNumber(1, 7);
|
|
} else {
|
|
category = CATEGORY_MISC;
|
|
itemId = vm->getRandomNumber(1, 9);
|
|
}
|
|
} else if (v4 <= 35) {
|
|
category = CATEGORY_WEAPON;
|
|
if (v6 <= 30) {
|
|
itemId = vm->getRandomNumber(1, 6);
|
|
} else if (v6 <= 60) {
|
|
itemId = vm->getRandomNumber(7, 17);
|
|
} else if (v6 <= 85) {
|
|
itemId = vm->getRandomNumber(18, 29);
|
|
} else {
|
|
itemId = vm->getRandomNumber(30, 33);
|
|
}
|
|
} else if (v4 <= 60) {
|
|
category = CATEGORY_ARMOR;
|
|
itemId = (v6 > 70) ? 8 : vm->getRandomNumber(1, 7);
|
|
} else if (v6 <= 10) {
|
|
category = CATEGORY_ARMOR;
|
|
itemId = 9;
|
|
} else if (v6 <= 20) {
|
|
category = CATEGORY_ARMOR;
|
|
itemId = 13;
|
|
} else if (v6 <= 35) {
|
|
category = CATEGORY_ACCESSORY;
|
|
itemId = 1;
|
|
} else if (v6 <= 45) {
|
|
category = CATEGORY_ARMOR;
|
|
itemId = 10;
|
|
} else if (v6 <= 55) {
|
|
category = CATEGORY_ARMOR;
|
|
itemId = vm->getRandomNumber(11, 12);
|
|
} else if (v6 <= 65) {
|
|
category = CATEGORY_ACCESSORY;
|
|
itemId = 2;
|
|
} else if (v6 <= 75) {
|
|
category = CATEGORY_ACCESSORY;
|
|
itemId = vm->getRandomNumber(3, 7);
|
|
} else if (v6 <= 80) {
|
|
category = CATEGORY_ACCESSORY;
|
|
itemId = vm->getRandomNumber(8, 10);
|
|
} else {
|
|
category = CATEGORY_MISC;
|
|
itemId = vm->getRandomNumber(1, 9);
|
|
}
|
|
}
|
|
|
|
XeenItem &newItem = _items[category][itemIndex];
|
|
newItem.clear();
|
|
newItem._id = itemId;
|
|
|
|
v4 = vm->getRandomNumber(1, 100);
|
|
switch (category) {
|
|
case CATEGORY_WEAPON:
|
|
case CATEGORY_ARMOR:
|
|
if (p1 != 1) {
|
|
if (v4 <= 70) {
|
|
v8 = 3;
|
|
} else if (v4 <= 98) {
|
|
v8 = 1;
|
|
} else {
|
|
v8 = 2;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CATEGORY_ACCESSORY:
|
|
if (v4 <= 20) {
|
|
v8 = 3;
|
|
} else if (v4 <= 60) {
|
|
v8 = 1;
|
|
} else {
|
|
v8 = 2;
|
|
}
|
|
break;
|
|
|
|
case CATEGORY_MISC:
|
|
v8 = 4;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (p1 != 1 || category == CATEGORY_MISC) {
|
|
int rval, mult;
|
|
switch (v8) {
|
|
case 1:
|
|
rval = vm->getRandomNumber(1, 100);
|
|
if (rval <= 25) {
|
|
mult = 0;
|
|
} else if (rval <= 45) {
|
|
mult = 1;
|
|
} else if (rval <= 60) {
|
|
mult = 2;
|
|
} else if (rval <= 75) {
|
|
mult = 3;
|
|
} else if (rval <= 95) {
|
|
mult = 4;
|
|
} else {
|
|
mult = 5;
|
|
}
|
|
|
|
v12 = Res.MAKE_ITEM_ARR1[vm->getRandomNumber(Res.MAKE_ITEM_ARR2[mult][p1][0],
|
|
Res.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;
|
|
}
|
|
|
|
v14 = Res.MAKE_ITEM_ARR1[vm->getRandomNumber(Res.MAKE_ITEM_ARR3[mult][p1][0],
|
|
Res.MAKE_ITEM_ARR3[mult][p1][1])];
|
|
break;
|
|
|
|
case 3:
|
|
mult = p1 == 7 || vm->getRandomNumber(1, 100) > 70 ? 1 : 0;
|
|
v16 = vm->getRandomNumber(Res.MAKE_ITEM_ARR4[mult][p1 - 1][0],
|
|
Res.MAKE_ITEM_ARR4[mult][p1 - 1][1]);
|
|
if (mult)
|
|
v16 += 9;
|
|
break;
|
|
|
|
case 4:
|
|
miscBonus = vm->getRandomNumber(Res.MAKE_ITEM_ARR5[p1][0], Res.MAKE_ITEM_ARR5[p1][1]);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (category) {
|
|
case CATEGORY_WEAPON:
|
|
if (p1 != 1) {
|
|
newItem._material = (v14 ? v14 + 58 : 0) + (v16 ? v16 + 36 : 0) + v12;
|
|
if (vm->getRandomNumber(20) == 10)
|
|
newItem._bonusFlags = vm->getRandomNumber(1, 6);
|
|
}
|
|
break;
|
|
|
|
case CATEGORY_ARMOR:
|
|
case CATEGORY_ACCESSORY:
|
|
if (p1 != 1) {
|
|
newItem._material = (v14 ? v14 + 58 : 0) + (v16 ? v16 + 36 : 0) + v12;
|
|
}
|
|
break;
|
|
|
|
case CATEGORY_MISC:
|
|
newItem._id = miscId;
|
|
newItem._bonusFlags = miscBonus;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return category;
|
|
}
|
|
|
|
void Character::addHitPoints(int amount) {
|
|
Interface &intf = *Party::_vm->_interface;
|
|
Common::fill(&intf._charFX[0], &intf._charFX[MAX_ACTIVE_PARTY], 0);
|
|
|
|
if (!isDead()) {
|
|
int maxHp = getMaxHP();
|
|
if (_currentHp <= maxHp) {
|
|
_currentHp = MIN(_currentHp + amount, maxHp);
|
|
intf.spellFX(this);
|
|
}
|
|
|
|
if (_currentHp > 0)
|
|
_conditions[UNCONSCIOUS] = 0;
|
|
|
|
intf.drawParty(true);
|
|
}
|
|
|
|
Common::fill(&intf._charFX[0], &intf._charFX[MAX_ACTIVE_PARTY], 0);
|
|
}
|
|
|
|
void Character::subtractHitPoints(int amount) {
|
|
Debugger &debugger = *g_vm->_debugger;
|
|
Sound &sound = *g_vm->_sound;
|
|
|
|
// If invincibility is turned on in the debugger, ignore all damage
|
|
if (debugger._invincible)
|
|
return;
|
|
|
|
// Subtract the given HP amount
|
|
_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::hasSlayerSword() 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;
|
|
}
|
|
}
|
|
|
|
void Character::clearConditions() {
|
|
Common::fill(&_conditions[CURSED], &_conditions[NO_CONDITION], false);
|
|
}
|
|
|
|
} // End of namespace Xeen
|