1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-28 15:09:43 +00:00

Merge branch 'clickbait' into 'master'

Mostly dehardcode skills

See merge request OpenMW/openmw!3112
This commit is contained in:
psi29a 2023-06-15 07:03:13 +00:00
commit 807893eb45
69 changed files with 543 additions and 646 deletions

View file

@ -43,20 +43,13 @@ namespace MWClass
return; return;
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
switch (shield->getClass().getEquipmentSkill(*shield)) const ESM::RefId skill = shield->getClass().getEquipmentSkill(*shield);
{ if (skill == ESM::Skill::LightArmor)
case ESM::Skill::LightArmor: sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f);
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f); else if (skill == ESM::Skill::MediumArmor)
break; sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f);
case ESM::Skill::MediumArmor: else if (skill == ESM::Skill::HeavyArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f); sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
break;
case ESM::Skill::HeavyArmor:
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
break;
default:
return;
}
} }
osg::Vec3f Actor::getRotationVector(const MWWorld::Ptr& ptr) const osg::Vec3f Actor::getRotationVector(const MWWorld::Ptr& ptr) const

View file

@ -112,7 +112,7 @@ namespace MWClass
return std::make_pair(slots_, false); return std::make_pair(slots_, false);
} }
int Armor::getEquipmentSkill(const MWWorld::ConstPtr& ptr) const ESM::RefId Armor::getEquipmentSkill(const MWWorld::ConstPtr& ptr) const
{ {
const MWWorld::LiveCellRef<ESM::Armor>* ref = ptr.get<ESM::Armor>(); const MWWorld::LiveCellRef<ESM::Armor>* ref = ptr.get<ESM::Armor>();
@ -150,7 +150,7 @@ namespace MWClass
} }
if (typeGmst.empty()) if (typeGmst.empty())
return -1; return {};
const MWWorld::Store<ESM::GameSetting>& gmst const MWWorld::Store<ESM::GameSetting>& gmst
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>(); = MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
@ -178,7 +178,7 @@ namespace MWClass
const ESM::RefId& Armor::getUpSoundId(const MWWorld::ConstPtr& ptr) const const ESM::RefId& Armor::getUpSoundId(const MWWorld::ConstPtr& ptr) const
{ {
int es = getEquipmentSkill(ptr); const ESM::RefId es = getEquipmentSkill(ptr);
static const ESM::RefId lightUp = ESM::RefId::stringRefId("Item Armor Light Up"); static const ESM::RefId lightUp = ESM::RefId::stringRefId("Item Armor Light Up");
static const ESM::RefId mediumUp = ESM::RefId::stringRefId("Item Armor Medium Up"); static const ESM::RefId mediumUp = ESM::RefId::stringRefId("Item Armor Medium Up");
static const ESM::RefId heavyUp = ESM::RefId::stringRefId("Item Armor Heavy Up"); static const ESM::RefId heavyUp = ESM::RefId::stringRefId("Item Armor Heavy Up");
@ -193,7 +193,7 @@ namespace MWClass
const ESM::RefId& Armor::getDownSoundId(const MWWorld::ConstPtr& ptr) const const ESM::RefId& Armor::getDownSoundId(const MWWorld::ConstPtr& ptr) const
{ {
int es = getEquipmentSkill(ptr); const ESM::RefId es = getEquipmentSkill(ptr);
static const ESM::RefId lightDown = ESM::RefId::stringRefId("Item Armor Light Down"); static const ESM::RefId lightDown = ESM::RefId::stringRefId("Item Armor Light Down");
static const ESM::RefId mediumDown = ESM::RefId::stringRefId("Item Armor Medium Down"); static const ESM::RefId mediumDown = ESM::RefId::stringRefId("Item Armor Medium Down");
static const ESM::RefId heavyDown = ESM::RefId::stringRefId("Item Armor Heavy Down"); static const ESM::RefId heavyDown = ESM::RefId::stringRefId("Item Armor Heavy Down");
@ -232,7 +232,7 @@ namespace MWClass
} }
else else
{ {
int armorType = getEquipmentSkill(ptr); const ESM::RefId armorType = getEquipmentSkill(ptr);
if (armorType == ESM::Skill::LightArmor) if (armorType == ESM::Skill::LightArmor)
typeText = "#{sLight}"; typeText = "#{sLight}";
else if (armorType == ESM::Skill::MediumArmor) else if (armorType == ESM::Skill::MediumArmor)
@ -297,7 +297,7 @@ namespace MWClass
{ {
const MWWorld::LiveCellRef<ESM::Armor>* ref = ptr.get<ESM::Armor>(); const MWWorld::LiveCellRef<ESM::Armor>* ref = ptr.get<ESM::Armor>();
int armorSkillType = getEquipmentSkill(ptr); const ESM::RefId armorSkillType = getEquipmentSkill(ptr);
float armorSkill = actor.getClass().getSkill(actor, armorSkillType); float armorSkill = actor.getClass().getSkill(actor, armorSkillType);
int iBaseArmorSkill = MWBase::Environment::get() int iBaseArmorSkill = MWBase::Environment::get()

View file

@ -41,9 +41,7 @@ namespace MWClass
///< \return first: Return IDs of the slot this object can be equipped in; second: can object ///< \return first: Return IDs of the slot this object can be equipped in; second: can object
/// stay stacked when equipped? /// stay stacked when equipped?
int getEquipmentSkill(const MWWorld::ConstPtr& ptr) const override; ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr) const override;
/// Return the index of the skill this item corresponds to when equipped or -1, if there is
/// no such skill.
MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override;
///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.

View file

@ -101,14 +101,14 @@ namespace MWClass
return std::make_pair(slots_, false); return std::make_pair(slots_, false);
} }
int Clothing::getEquipmentSkill(const MWWorld::ConstPtr& ptr) const ESM::RefId Clothing::getEquipmentSkill(const MWWorld::ConstPtr& ptr) const
{ {
const MWWorld::LiveCellRef<ESM::Clothing>* ref = ptr.get<ESM::Clothing>(); const MWWorld::LiveCellRef<ESM::Clothing>* ref = ptr.get<ESM::Clothing>();
if (ref->mBase->mData.mType == ESM::Clothing::Shoes) if (ref->mBase->mData.mType == ESM::Clothing::Shoes)
return ESM::Skill::Unarmored; return ESM::Skill::Unarmored;
return -1; return {};
} }
int Clothing::getValue(const MWWorld::ConstPtr& ptr) const int Clothing::getValue(const MWWorld::ConstPtr& ptr) const

View file

@ -33,9 +33,7 @@ namespace MWClass
///< \return first: Return IDs of the slot this object can be equipped in; second: can object ///< \return first: Return IDs of the slot this object can be equipped in; second: can object
/// stay stacked when equipped? /// stay stacked when equipped?
int getEquipmentSkill(const MWWorld::ConstPtr& ptr) const override; ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr) const override;
/// Return the index of the skill this item corresponds to when equipped or -1, if there is
/// no such skill.
MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override; MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override;
///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip. ///< @return the content of the tool tip to be displayed. raises exception if the object has no tooltip.

View file

@ -762,11 +762,11 @@ namespace MWClass
throw std::runtime_error("Unexpected soundgen type: " + std::string(name)); throw std::runtime_error("Unexpected soundgen type: " + std::string(name));
} }
float Creature::getSkill(const MWWorld::Ptr& ptr, int skill) const float Creature::getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const
{ {
MWWorld::LiveCellRef<ESM::Creature>* ref = ptr.get<ESM::Creature>(); MWWorld::LiveCellRef<ESM::Creature>* ref = ptr.get<ESM::Creature>();
const ESM::Skill* skillRecord = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(skill); const ESM::Skill* skillRecord = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(id);
switch (skillRecord->mData.mSpecialization) switch (skillRecord->mData.mSpecialization)
{ {

View file

@ -115,7 +115,7 @@ namespace MWClass
bool canSwim(const MWWorld::ConstPtr& ptr) const override; bool canSwim(const MWWorld::ConstPtr& ptr) const override;
bool canWalk(const MWWorld::ConstPtr& ptr) const override; bool canWalk(const MWWorld::ConstPtr& ptr) const override;
float getSkill(const MWWorld::Ptr& ptr, int skill) const override; float getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const override;
/// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
int getBloodTexture(const MWWorld::ConstPtr& ptr) const override; int getBloodTexture(const MWWorld::ConstPtr& ptr) const override;

View file

@ -111,20 +111,18 @@ namespace
{ {
float modifierSum = 0; float modifierSum = 0;
for (int j = 0; j < ESM::Skill::Length; ++j) for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
{ {
const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(j); if (skill.mData.mAttribute != attribute)
if (skill->mData.mAttribute != attribute)
continue; continue;
// is this a minor or major skill? // is this a minor or major skill?
float add = 0.2f; float add = 0.2f;
for (const auto& skills : class_->mData.mSkills) for (const auto& skills : class_->mData.mSkills)
{ {
if (skills[0] == j) if (skills[0] == skill.mIndex)
add = 0.5; add = 0.5;
if (skills[1] == j) if (skills[1] == skill.mIndex)
add = 1.0; add = 1.0;
} }
modifierSum += add; modifierSum += add;
@ -181,15 +179,15 @@ namespace
for (const auto& skills : class_->mData.mSkills) for (const auto& skills : class_->mData.mSkills)
{ {
int index = skills[i]; ESM::RefId id = ESM::Skill::indexToRefId(skills[i]);
if (index >= 0 && index < ESM::Skill::Length) if (!id.empty())
{ {
npcStats.getSkill(index).setBase(npcStats.getSkill(index).getBase() + bonus); npcStats.getSkill(id).setBase(npcStats.getSkill(id).getBase() + bonus);
} }
} }
} }
for (int skillIndex = 0; skillIndex < ESM::Skill::Length; ++skillIndex) for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
{ {
float majorMultiplier = 0.1f; float majorMultiplier = 0.1f;
float specMultiplier = 0.0f; float specMultiplier = 0.0f;
@ -198,14 +196,14 @@ namespace
int specBonus = 0; int specBonus = 0;
auto bonusIt = std::find_if(race->mData.mBonus.begin(), race->mData.mBonus.end(), auto bonusIt = std::find_if(race->mData.mBonus.begin(), race->mData.mBonus.end(),
[skillIndex](const auto& bonus) { return bonus.mSkill == skillIndex; }); [&](const auto& bonus) { return bonus.mSkill == skill.mIndex; });
if (bonusIt != race->mData.mBonus.end()) if (bonusIt != race->mData.mBonus.end())
raceBonus = bonusIt->mBonus; raceBonus = bonusIt->mBonus;
for (const auto& skills : class_->mData.mSkills) for (const auto& skills : class_->mData.mSkills)
{ {
// is this a minor or major skill? // is this a minor or major skill?
if (std::find(skills.begin(), skills.end(), skillIndex) != skills.end()) if (std::find(skills.begin(), skills.end(), skill.mIndex) != skills.end())
{ {
majorMultiplier = 1.0f; majorMultiplier = 1.0f;
break; break;
@ -213,30 +211,25 @@ namespace
} }
// is this skill in the same Specialization as the class? // is this skill in the same Specialization as the class?
const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(skillIndex); if (skill.mData.mSpecialization == class_->mData.mSpecialization)
if (skill->mData.mSpecialization == class_->mData.mSpecialization)
{ {
specMultiplier = 0.5f; specMultiplier = 0.5f;
specBonus = 5; specBonus = 5;
} }
npcStats.getSkill(skillIndex) npcStats.getSkill(skill.mId).setBase(
.setBase(std::min(round_ieee_754(npcStats.getSkill(skillIndex).getBase() + 5 + raceBonus + specBonus std::min(round_ieee_754(npcStats.getSkill(skill.mId).getBase() + 5 + raceBonus + specBonus
+ (int(level) - 1) * (majorMultiplier + specMultiplier)), + (int(level) - 1) * (majorMultiplier + specMultiplier)),
100)); // Must gracefully handle level 0 100)); // Must gracefully handle level 0
} }
int skills[ESM::Skill::Length];
for (int i = 0; i < ESM::Skill::Length; ++i)
skills[i] = npcStats.getSkill(i).getBase();
int attributes[ESM::Attribute::Length]; int attributes[ESM::Attribute::Length];
for (int i = 0; i < ESM::Attribute::Length; ++i) for (int i = 0; i < ESM::Attribute::Length; ++i)
attributes[i] = npcStats.getAttribute(i).getBase(); attributes[i] = npcStats.getAttribute(i).getBase();
if (!spellsInitialised) if (!spellsInitialised)
{ {
std::vector<ESM::RefId> spells = MWMechanics::autoCalcNpcSpells(skills, attributes, race); std::vector<ESM::RefId> spells = MWMechanics::autoCalcNpcSpells(npcStats.getSkills(), attributes, race);
npcStats.getSpells().addAllToInstance(spells); npcStats.getSpells().addAllToInstance(spells);
} }
} }
@ -315,7 +308,7 @@ namespace MWClass
gold = ref->mBase->mNpdt.mGold; gold = ref->mBase->mNpdt.mGold;
for (size_t i = 0; i < ref->mBase->mNpdt.mSkills.size(); ++i) for (size_t i = 0; i < ref->mBase->mNpdt.mSkills.size(); ++i)
data->mNpcStats.getSkill(i).setBase(ref->mBase->mNpdt.mSkills[i]); data->mNpcStats.getSkill(ESM::Skill::indexToRefId(i)).setBase(ref->mBase->mNpdt.mSkills[i]);
data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt.mStrength); data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt.mStrength);
data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt.mIntelligence); data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt.mIntelligence);
@ -589,7 +582,7 @@ namespace MWClass
victim = result.first; victim = result.first;
hitPosition = result.second; hitPosition = result.second;
int weapskill = ESM::Skill::HandToHand; ESM::RefId weapskill = ESM::Skill::HandToHand;
if (!weapon.isEmpty()) if (!weapon.isEmpty())
weapskill = weapon.getClass().getEquipmentSkill(weapon); weapskill = weapon.getClass().getEquipmentSkill(weapon);
@ -658,7 +651,7 @@ namespace MWClass
if (ptr == MWMechanics::getPlayer()) if (ptr == MWMechanics::getPlayer())
{ {
int weapskill = ESM::Skill::HandToHand; ESM::RefId weapskill = ESM::Skill::HandToHand;
if (!weapon.isEmpty()) if (!weapon.isEmpty())
weapskill = weapon.getClass().getEquipmentSkill(weapon); weapskill = weapon.getClass().getEquipmentSkill(weapon);
skillUsageSucceeded(ptr, weapskill, 0); skillUsageSucceeded(ptr, weapskill, 0);
@ -849,21 +842,16 @@ namespace MWClass
armor = *inv.unequipItem(armor); armor = *inv.unequipItem(armor);
} }
ESM::RefId skill = armor.getClass().getEquipmentSkill(armor);
if (ptr == MWMechanics::getPlayer()) if (ptr == MWMechanics::getPlayer())
skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0); skillUsageSucceeded(ptr, skill, 0);
switch (armor.getClass().getEquipmentSkill(armor)) if (skill == ESM::Skill::LightArmor)
{ sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f);
case ESM::Skill::LightArmor: else if (skill == ESM::Skill::MediumArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f); sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f);
break; else if (skill == ESM::Skill::HeavyArmor)
case ESM::Skill::MediumArmor: sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f);
break;
case ESM::Skill::HeavyArmor:
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
break;
}
} }
else if (ptr == MWMechanics::getPlayer()) else if (ptr == MWMechanics::getPlayer())
skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0); skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0);
@ -1161,7 +1149,7 @@ namespace MWClass
return cast.cast(recordId); return cast.cast(recordId);
} }
void Npc::skillUsageSucceeded(const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor) const void Npc::skillUsageSucceeded(const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor) const
{ {
MWMechanics::NpcStats& stats = getNpcStats(ptr); MWMechanics::NpcStats& stats = getNpcStats(ptr);
@ -1311,18 +1299,13 @@ namespace MWClass
if (boots == inv.end() || boots->getType() != ESM::Armor::sRecordId) if (boots == inv.end() || boots->getType() != ESM::Armor::sRecordId)
return (name == "left") ? footBareLeft : footBareRight; return (name == "left") ? footBareLeft : footBareRight;
switch (boots->getClass().getEquipmentSkill(*boots)) ESM::RefId skill = boots->getClass().getEquipmentSkill(*boots);
{ if (skill == ESM::Skill::LightArmor)
case ESM::Skill::LightArmor: return (name == "left") ? footLightLeft : footLightRight;
return (name == "left") ? footLightLeft : footLightRight; else if (skill == ESM::Skill::MediumArmor)
break; return (name == "left") ? footMediumLeft : footMediumRight;
case ESM::Skill::MediumArmor: else if (skill == ESM::Skill::HeavyArmor)
return (name == "left") ? footMediumLeft : footMediumRight; return (name == "left") ? footHeavyLeft : footHeavyRight;
break;
case ESM::Skill::HeavyArmor:
return (name == "left") ? footHeavyLeft : footHeavyRight;
break;
}
} }
return ESM::RefId(); return ESM::RefId();
} }
@ -1355,9 +1338,9 @@ namespace MWClass
return MWWorld::Ptr(cell.insert(ref), &cell); return MWWorld::Ptr(cell.insert(ref), &cell);
} }
float Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const float Npc::getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const
{ {
return getNpcStats(ptr).getSkill(skill).getModified(); return getNpcStats(ptr).getSkill(id).getModified();
} }
int Npc::getBloodTexture(const MWWorld::ConstPtr& ptr) const int Npc::getBloodTexture(const MWWorld::ConstPtr& ptr) const

View file

@ -120,7 +120,7 @@ namespace MWClass
/// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh /// @param rendering Indicates if the scale to adjust is for the rendering mesh, or for the collision mesh
void skillUsageSucceeded( void skillUsageSucceeded(
const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor = 1.f) const override; const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor = 1.f) const override;
///< Inform actor \a ptr that a skill use has succeeded. ///< Inform actor \a ptr that a skill use has succeeded.
bool isEssential(const MWWorld::ConstPtr& ptr) const override; bool isEssential(const MWWorld::ConstPtr& ptr) const override;
@ -134,7 +134,7 @@ namespace MWClass
std::string getModel(const MWWorld::ConstPtr& ptr) const override; std::string getModel(const MWWorld::ConstPtr& ptr) const override;
float getSkill(const MWWorld::Ptr& ptr, int skill) const override; float getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const override;
/// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
int getBloodTexture(const MWWorld::ConstPtr& ptr) const override; int getBloodTexture(const MWWorld::ConstPtr& ptr) const override;

View file

@ -108,7 +108,7 @@ namespace MWClass
return std::make_pair(slots_, stack); return std::make_pair(slots_, stack);
} }
int Weapon::getEquipmentSkill(const MWWorld::ConstPtr& ptr) const ESM::RefId Weapon::getEquipmentSkill(const MWWorld::ConstPtr& ptr) const
{ {
const MWWorld::LiveCellRef<ESM::Weapon>* ref = ptr.get<ESM::Weapon>(); const MWWorld::LiveCellRef<ESM::Weapon>* ref = ptr.get<ESM::Weapon>();
int type = ref->mBase->mData.mType; int type = ref->mBase->mData.mType;

View file

@ -42,9 +42,7 @@ namespace MWClass
///< \return first: Return IDs of the slot this object can be equipped in; second: can object ///< \return first: Return IDs of the slot this object can be equipped in; second: can object
/// stay stacked when equipped? /// stay stacked when equipped?
int getEquipmentSkill(const MWWorld::ConstPtr& ptr) const override; ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr) const override;
/// Return the index of the skill this item corresponds to when equipped or -1, if there is
/// no such skill.
int getValue(const MWWorld::ConstPtr& ptr) const override; int getValue(const MWWorld::ConstPtr& ptr) const override;
///< Return trade value of the object. Throws an exception, if the object can't be traded. ///< Return trade value of the object. Throws an exception, if the object can't be traded.

View file

@ -5,6 +5,7 @@
#include <components/esm3/loadcrea.hpp> #include <components/esm3/loadcrea.hpp>
#include <components/esm3/loadfact.hpp> #include <components/esm3/loadfact.hpp>
#include <components/esm3/loadmgef.hpp> #include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadskil.hpp>
#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -385,9 +386,10 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons
return player.getClass().getCreatureStats(player).getAttribute(select.getArgument()).getModified(); return player.getClass().getCreatureStats(player).getAttribute(select.getArgument()).getModified();
case SelectWrapper::Function_PcSkill: case SelectWrapper::Function_PcSkill:
{
return static_cast<int>(player.getClass().getNpcStats(player).getSkill(select.getArgument()).getModified()); ESM::RefId skill = ESM::Skill::indexToRefId(select.getArgument());
return static_cast<int>(player.getClass().getNpcStats(player).getSkill(skill).getModified());
}
case SelectWrapper::Function_FriendlyHit: case SelectWrapper::Function_FriendlyHit:
{ {
int hits = mActor.getClass().getCreatureStats(mActor).getFriendlyHits(); int hits = mActor.getClass().getCreatureStats(mActor).getFriendlyHits();

View file

@ -99,7 +99,7 @@ namespace MWGui
mPlayerAttributes.emplace(attribute.mId, MWMechanics::AttributeValue()); mPlayerAttributes.emplace(attribute.mId, MWMechanics::AttributeValue());
for (const auto& skill : store.get<ESM::Skill>()) for (const auto& skill : store.get<ESM::Skill>())
mPlayerSkillValues.emplace(skill.second.mIndex, MWMechanics::SkillValue()); mPlayerSkillValues.emplace(skill.mId, MWMechanics::SkillValue());
} }
void CharacterCreation::setValue(std::string_view id, const MWMechanics::AttributeValue& value) void CharacterCreation::setValue(std::string_view id, const MWMechanics::AttributeValue& value)
@ -138,14 +138,14 @@ namespace MWGui
} }
} }
void CharacterCreation::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) void CharacterCreation::setValue(ESM::RefId id, const MWMechanics::SkillValue& value)
{ {
mPlayerSkillValues[parSkill] = value; mPlayerSkillValues[id] = value;
if (mReviewDialog) if (mReviewDialog)
mReviewDialog->setSkillValue(parSkill, value); mReviewDialog->setSkillValue(id, value);
} }
void CharacterCreation::configureSkills(const SkillList& major, const SkillList& minor) void CharacterCreation::configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor)
{ {
if (mReviewDialog) if (mReviewDialog)
mReviewDialog->configureSkills(major, minor); mReviewDialog->configureSkills(major, minor);
@ -275,10 +275,9 @@ namespace MWGui
mReviewDialog->setAttribute( mReviewDialog->setAttribute(
static_cast<ESM::Attribute::AttributeID>(attributePair.first), attributePair.second); static_cast<ESM::Attribute::AttributeID>(attributePair.first), attributePair.second);
} }
for (auto& skillPair : mPlayerSkillValues) for (const auto& [skill, value] : mPlayerSkillValues)
{ {
mReviewDialog->setSkillValue( mReviewDialog->setSkillValue(skill, value);
static_cast<ESM::Skill::SkillEnum>(skillPair.first), skillPair.second);
} }
mReviewDialog->configureSkills(mPlayerMajorSkills, mPlayerMinorSkills); mReviewDialog->configureSkills(mPlayerMajorSkills, mPlayerMinorSkills);
@ -476,14 +475,14 @@ namespace MWGui
assert(attributes.size() == klass.mData.mAttribute.size()); assert(attributes.size() == klass.mData.mAttribute.size());
std::copy(attributes.begin(), attributes.end(), klass.mData.mAttribute.begin()); std::copy(attributes.begin(), attributes.end(), klass.mData.mAttribute.begin());
std::vector<ESM::Skill::SkillEnum> majorSkills = mCreateClassDialog->getMajorSkills(); std::vector<ESM::RefId> majorSkills = mCreateClassDialog->getMajorSkills();
std::vector<ESM::Skill::SkillEnum> minorSkills = mCreateClassDialog->getMinorSkills(); std::vector<ESM::RefId> minorSkills = mCreateClassDialog->getMinorSkills();
assert(majorSkills.size() >= klass.mData.mSkills.size()); assert(majorSkills.size() >= klass.mData.mSkills.size());
assert(minorSkills.size() >= klass.mData.mSkills.size()); assert(minorSkills.size() >= klass.mData.mSkills.size());
for (size_t i = 0; i < klass.mData.mSkills.size(); ++i) for (size_t i = 0; i < klass.mData.mSkills.size(); ++i)
{ {
klass.mData.mSkills[i][1] = majorSkills[i]; klass.mData.mSkills[i][1] = majorSkills[i].getIf<ESM::IndexRefId>()->getValue();
klass.mData.mSkills[i][0] = minorSkills[i]; klass.mData.mSkills[i][0] = minorSkills[i].getIf<ESM::IndexRefId>()->getValue();
} }
MWBase::Environment::get().getMechanicsManager()->setPlayerClass(klass); MWBase::Environment::get().getMechanicsManager()->setPlayerClass(klass);

View file

@ -38,8 +38,6 @@ namespace MWGui
class CharacterCreation : public StatsListener class CharacterCreation : public StatsListener
{ {
public: public:
typedef std::vector<int> SkillList;
CharacterCreation(osg::Group* parent, Resource::ResourceSystem* resourceSystem); CharacterCreation(osg::Group* parent, Resource::ResourceSystem* resourceSystem);
virtual ~CharacterCreation(); virtual ~CharacterCreation();
@ -48,8 +46,8 @@ namespace MWGui
void setValue(std::string_view id, const MWMechanics::AttributeValue& value) override; void setValue(std::string_view id, const MWMechanics::AttributeValue& value) override;
void setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value) override; void setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value) override;
void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) override; void setValue(ESM::RefId id, const MWMechanics::SkillValue& value) override;
void configureSkills(const SkillList& major, const SkillList& minor) override; void configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor) override;
void onFrame(float duration); void onFrame(float duration);
@ -57,9 +55,9 @@ namespace MWGui
osg::Group* mParent; osg::Group* mParent;
Resource::ResourceSystem* mResourceSystem; Resource::ResourceSystem* mResourceSystem;
SkillList mPlayerMajorSkills, mPlayerMinorSkills; std::vector<ESM::RefId> mPlayerMajorSkills, mPlayerMinorSkills;
std::map<int, MWMechanics::AttributeValue> mPlayerAttributes; std::map<int, MWMechanics::AttributeValue> mPlayerAttributes;
std::map<int, MWMechanics::SkillValue> mPlayerSkillValues; std::map<ESM::RefId, MWMechanics::SkillValue> mPlayerSkillValues;
// Dialogs // Dialogs
std::unique_ptr<TextInputDialog> mNameDialog; std::unique_ptr<TextInputDialog> mNameDialog;

View file

@ -3,6 +3,7 @@
#include <MyGUI_Gui.h> #include <MyGUI_Gui.h>
#include <MyGUI_ImageBox.h> #include <MyGUI_ImageBox.h>
#include <MyGUI_ListBox.h> #include <MyGUI_ListBox.h>
#include <MyGUI_ScrollView.h>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -266,10 +267,12 @@ namespace MWGui
for (size_t i = 0; i < klass->mData.mSkills.size(); ++i) for (size_t i = 0; i < klass->mData.mSkills.size(); ++i)
{ {
mMinorSkill[i]->setSkillNumber(klass->mData.mSkills[i][0]); ESM::RefId minor = ESM::Skill::indexToRefId(klass->mData.mSkills[i][0]);
mMajorSkill[i]->setSkillNumber(klass->mData.mSkills[i][1]); ESM::RefId major = ESM::Skill::indexToRefId(klass->mData.mSkills[i][1]);
ToolTips::createSkillToolTip(mMinorSkill[i], klass->mData.mSkills[i][0]); mMinorSkill[i]->setSkillId(minor);
ToolTips::createSkillToolTip(mMajorSkill[i], klass->mData.mSkills[i][1]); mMajorSkill[i]->setSkillId(major);
ToolTips::createSkillToolTip(mMinorSkill[i], minor);
ToolTips::createSkillToolTip(mMajorSkill[i], major);
} }
setClassImage(mClassImage, mCurrentClassId); setClassImage(mClassImage, mCurrentClassId);
@ -514,24 +517,24 @@ namespace MWGui
return v; return v;
} }
std::vector<ESM::Skill::SkillEnum> CreateClassDialog::getMajorSkills() const std::vector<ESM::RefId> CreateClassDialog::getMajorSkills() const
{ {
std::vector<ESM::Skill::SkillEnum> v; std::vector<ESM::RefId> v;
v.reserve(5); v.reserve(mMajorSkill.size());
for (int i = 0; i < 5; i++) for (const auto& widget : mMajorSkill)
{ {
v.push_back(mMajorSkill[i]->getSkillId()); v.push_back(widget->getSkillId());
} }
return v; return v;
} }
std::vector<ESM::Skill::SkillEnum> CreateClassDialog::getMinorSkills() const std::vector<ESM::RefId> CreateClassDialog::getMinorSkills() const
{ {
std::vector<ESM::Skill::SkillEnum> v; std::vector<ESM::RefId> v;
v.reserve(5); v.reserve(mMinorSkill.size());
for (int i = 0; i < 5; i++) for (const auto& widget : mMinorSkill)
{ {
v.push_back(mMinorSkill[i]->getSkillId()); v.push_back(widget->getSkillId());
} }
return v; return v;
} }
@ -624,7 +627,7 @@ namespace MWGui
void CreateClassDialog::onSkillSelected() void CreateClassDialog::onSkillSelected()
{ {
ESM::Skill::SkillEnum id = mSkillDialog->getSkillId(); ESM::RefId id = mSkillDialog->getSkillId();
// Avoid duplicate skills by swapping any skill field that matches the selected one // Avoid duplicate skills by swapping any skill field that matches the selected one
for (Widgets::MWSkillPtr& skill : mSkills) for (Widgets::MWSkillPtr& skill : mSkills)
@ -793,43 +796,33 @@ namespace MWGui
// Centre dialog // Centre dialog
center(); center();
for (int i = 0; i < 9; i++) std::array<std::pair<MyGUI::ScrollView*, MyGUI::IntCoord>, 3> specializations;
getWidget(specializations[ESM::Class::Combat].first, "CombatSkills");
getWidget(specializations[ESM::Class::Magic].first, "MagicSkills");
getWidget(specializations[ESM::Class::Stealth].first, "StealthSkills");
for (auto& [widget, coord] : specializations)
{ {
char theIndex = '0' + i; coord.width = widget->getCoord().width;
getWidget(mCombatSkill[i], std::string("CombatSkill").append(1, theIndex)); coord.height = 18;
getWidget(mMagicSkill[i], std::string("MagicSkill").append(1, theIndex)); while (widget->getChildCount() > 0)
getWidget(mStealthSkill[i], std::string("StealthSkill").append(1, theIndex)); MyGUI::Gui::getInstance().destroyWidget(widget->getChildAt(0));
} }
for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
struct
{ {
Widgets::MWSkillPtr widget; auto& [widget, coord] = specializations[skill.mData.mSpecialization];
ESM::Skill::SkillEnum skillId; auto* skillWidget
} mSkills[3][9] = widget->createWidget<Widgets::MWSkill>("MW_StatNameButton", coord, MyGUI::Align::Default);
= { { { mCombatSkill[0], ESM::Skill::Block }, { mCombatSkill[1], ESM::Skill::Armorer }, coord.top += coord.height;
{ mCombatSkill[2], ESM::Skill::MediumArmor }, { mCombatSkill[3], ESM::Skill::HeavyArmor }, skillWidget->setSkillId(skill.mId);
{ mCombatSkill[4], ESM::Skill::BluntWeapon }, { mCombatSkill[5], ESM::Skill::LongBlade }, skillWidget->eventClicked += MyGUI::newDelegate(this, &SelectSkillDialog::onSkillClicked);
{ mCombatSkill[6], ESM::Skill::Axe }, { mCombatSkill[7], ESM::Skill::Spear }, ToolTips::createSkillToolTip(skillWidget, skill.mId);
{ mCombatSkill[8], ESM::Skill::Athletics } }, }
{ { mMagicSkill[0], ESM::Skill::Enchant }, { mMagicSkill[1], ESM::Skill::Destruction }, for (const auto& [widget, coord] : specializations)
{ mMagicSkill[2], ESM::Skill::Alteration }, { mMagicSkill[3], ESM::Skill::Illusion },
{ mMagicSkill[4], ESM::Skill::Conjuration }, { mMagicSkill[5], ESM::Skill::Mysticism },
{ mMagicSkill[6], ESM::Skill::Restoration }, { mMagicSkill[7], ESM::Skill::Alchemy },
{ mMagicSkill[8], ESM::Skill::Unarmored } },
{ { mStealthSkill[0], ESM::Skill::Security }, { mStealthSkill[1], ESM::Skill::Sneak },
{ mStealthSkill[2], ESM::Skill::Acrobatics }, { mStealthSkill[3], ESM::Skill::LightArmor },
{ mStealthSkill[4], ESM::Skill::ShortBlade }, { mStealthSkill[5], ESM::Skill::Marksman },
{ mStealthSkill[6], ESM::Skill::Mercantile }, { mStealthSkill[7], ESM::Skill::Speechcraft },
{ mStealthSkill[8], ESM::Skill::HandToHand } } };
for (int spec = 0; spec < 3; ++spec)
{ {
for (int i = 0; i < 9; ++i) widget->setVisibleVScroll(false);
{ widget->setCanvasSize(MyGUI::IntSize(widget->getWidth(), std::max(widget->getHeight(), coord.top)));
mSkills[spec][i].widget->setSkillId(mSkills[spec][i].skillId); widget->setVisibleVScroll(true);
mSkills[spec][i].widget->eventClicked += MyGUI::newDelegate(this, &SelectSkillDialog::onSkillClicked); widget->setViewOffset(MyGUI::IntPoint());
ToolTips::createSkillToolTip(mSkills[spec][i].widget, mSkills[spec][i].widget->getSkillId());
}
} }
MyGUI::Button* cancelButton; MyGUI::Button* cancelButton;

View file

@ -1,6 +1,7 @@
#ifndef MWGUI_CLASS_H #ifndef MWGUI_CLASS_H
#define MWGUI_CLASS_H #define MWGUI_CLASS_H
#include <array>
#include <memory> #include <memory>
#include <MyGUI_EditBox.h> #include <MyGUI_EditBox.h>
@ -218,7 +219,7 @@ namespace MWGui
bool exit() override; bool exit() override;
ESM::Skill::SkillEnum getSkillId() const { return mSkillId; } ESM::RefId getSkillId() const { return mSkillId; }
// Events // Events
typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void;
@ -238,11 +239,7 @@ namespace MWGui
void onCancelClicked(MyGUI::Widget* _sender); void onCancelClicked(MyGUI::Widget* _sender);
private: private:
Widgets::MWSkillPtr mCombatSkill[9]; ESM::RefId mSkillId;
Widgets::MWSkillPtr mMagicSkill[9];
Widgets::MWSkillPtr mStealthSkill[9];
ESM::Skill::SkillEnum mSkillId;
}; };
class DescriptionDialog : public WindowModal class DescriptionDialog : public WindowModal
@ -278,8 +275,8 @@ namespace MWGui
std::string getDescription() const; std::string getDescription() const;
ESM::Class::Specialization getSpecializationId() const; ESM::Class::Specialization getSpecializationId() const;
std::vector<int> getFavoriteAttributes() const; std::vector<int> getFavoriteAttributes() const;
std::vector<ESM::Skill::SkillEnum> getMajorSkills() const; std::vector<ESM::RefId> getMajorSkills() const;
std::vector<ESM::Skill::SkillEnum> getMinorSkills() const; std::vector<ESM::RefId> getMinorSkills() const;
void setNextButtonShow(bool shown); void setNextButtonShow(bool shown);
@ -318,8 +315,8 @@ namespace MWGui
MyGUI::EditBox* mEditName; MyGUI::EditBox* mEditName;
MyGUI::TextBox* mSpecializationName; MyGUI::TextBox* mSpecializationName;
Widgets::MWAttributePtr mFavoriteAttribute0, mFavoriteAttribute1; Widgets::MWAttributePtr mFavoriteAttribute0, mFavoriteAttribute1;
Widgets::MWSkillPtr mMajorSkill[5]; std::array<Widgets::MWSkillPtr, 5> mMajorSkill;
Widgets::MWSkillPtr mMinorSkill[5]; std::array<Widgets::MWSkillPtr, 5> mMinorSkill;
std::vector<Widgets::MWSkillPtr> mSkills; std::vector<Widgets::MWSkillPtr> mSkills;
std::string mDescription; std::string mDescription;

View file

@ -93,11 +93,11 @@ namespace MWGui
for (int day = 0; day < mDays; ++day) for (int day = 0; day < mDays; ++day)
{ {
auto& prng = MWBase::Environment::get().getWorld()->getPrng(); auto& prng = MWBase::Environment::get().getWorld()->getPrng();
const ESM::Skill* skill = skillStore.find(Misc::Rng::rollDice(ESM::Skill::Length, prng)); const ESM::Skill* skill = skillStore.searchRandom({}, prng);
skills.insert(skill); skills.insert(skill);
MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill->mIndex); MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill->mId);
if (skill->mIndex == ESM::Skill::Security || skill->mIndex == ESM::Skill::Sneak) if (skill->mId == ESM::Skill::Security || skill->mId == ESM::Skill::Sneak)
value.setBase(std::min(100.f, value.getBase() + 1)); value.setBase(std::min(100.f, value.getBase() + 1));
else else
value.setBase(std::max(0.f, value.getBase() - 1)); value.setBase(std::max(0.f, value.getBase() - 1));
@ -116,9 +116,9 @@ namespace MWGui
for (const ESM::Skill* skill : skills) for (const ESM::Skill* skill : skills)
{ {
int skillValue = player.getClass().getNpcStats(player).getSkill(skill->mIndex).getBase(); int skillValue = player.getClass().getNpcStats(player).getSkill(skill->mId).getBase();
std::string skillMsg = gmst.find("sNotifyMessage44")->mValue.getString(); std::string skillMsg = gmst.find("sNotifyMessage44")->mValue.getString();
if (skill->mIndex == ESM::Skill::Sneak || skill->mIndex == ESM::Skill::Security) if (skill->mId == ESM::Skill::Sneak || skill->mId == ESM::Skill::Security)
skillMsg = gmst.find("sNotifyMessage39")->mValue.getString(); skillMsg = gmst.find("sNotifyMessage39")->mValue.getString();
skillMsg = Misc::StringUtils::format(skillMsg, skill->mName, skillValue); skillMsg = Misc::StringUtils::format(skillMsg, skill->mName, skillValue);

View file

@ -414,13 +414,14 @@ namespace MWGui
const ESM::Race* race = store.get<ESM::Race>().find(mCurrentRaceId); const ESM::Race* race = store.get<ESM::Race>().find(mCurrentRaceId);
for (const auto& bonus : race->mData.mBonus) for (const auto& bonus : race->mData.mBonus)
{ {
if (bonus.mSkill < 0 || bonus.mSkill >= ESM::Skill::Length) // Skip unknown skill indexes ESM::RefId skill = ESM::Skill::indexToRefId(bonus.mSkill);
if (skill.empty()) // Skip unknown skill indexes
continue; continue;
skillWidget = mSkillList->createWidget<Widgets::MWSkill>("MW_StatNameValue", coord1, MyGUI::Align::Default); skillWidget = mSkillList->createWidget<Widgets::MWSkill>("MW_StatNameValue", coord1, MyGUI::Align::Default);
skillWidget->setSkillId(ESM::Skill::SkillEnum(bonus.mSkill)); skillWidget->setSkillId(skill);
skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast<float>(bonus.mBonus), 0.f)); skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(static_cast<float>(bonus.mBonus), 0.f));
ToolTips::createSkillToolTip(skillWidget, bonus.mSkill); ToolTips::createSkillToolTip(skillWidget, skill);
mSkillItems.push_back(skillWidget); mSkillItems.push_back(skillWidget);

View file

@ -90,10 +90,10 @@ namespace MWGui
getWidget(mSkillView, "SkillView"); getWidget(mSkillView, "SkillView");
mSkillView->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel); mSkillView->eventMouseWheel += MyGUI::newDelegate(this, &ReviewDialog::onMouseWheel);
for (int i = 0; i < ESM::Skill::Length; ++i) for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
{ {
mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue())); mSkillValues.emplace(skill.mId, MWMechanics::SkillValue());
mSkillWidgetMap.insert(std::make_pair(i, static_cast<MyGUI::TextBox*>(nullptr))); mSkillWidgetMap.emplace(skill.mId, static_cast<MyGUI::TextBox*>(nullptr));
} }
MyGUI::Button* backButton; MyGUI::Button* backButton;
@ -204,13 +204,14 @@ namespace MWGui
} }
} }
void ReviewDialog::setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value) void ReviewDialog::setSkillValue(ESM::RefId id, const MWMechanics::SkillValue& value)
{ {
mSkillValues[skillId] = value; mSkillValues[id] = value;
MyGUI::TextBox* widget = mSkillWidgetMap[skillId]; MyGUI::TextBox* widget = mSkillWidgetMap[id];
if (widget) if (widget)
{ {
float modified = static_cast<float>(value.getModified()), base = static_cast<float>(value.getBase()); float modified = value.getModified();
float base = value.getBase();
std::string text = MyGUI::utility::toString(std::floor(modified)); std::string text = MyGUI::utility::toString(std::floor(modified));
std::string state = "normal"; std::string state = "normal";
if (modified > base) if (modified > base)
@ -225,21 +226,21 @@ namespace MWGui
mUpdateSkillArea = true; mUpdateSkillArea = true;
} }
void ReviewDialog::configureSkills(const std::vector<int>& major, const std::vector<int>& minor) void ReviewDialog::configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor)
{ {
mMajorSkills = major; mMajorSkills = major;
mMinorSkills = minor; mMinorSkills = minor;
// Update misc skills with the remaining skills not in major or minor // Update misc skills with the remaining skills not in major or minor
std::set<int> skillSet; std::set<ESM::RefId> skillSet;
std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin())); std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin()));
std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin())); std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin()));
mMiscSkills.clear(); mMiscSkills.clear();
const auto& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>(); const auto& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>();
for (const auto& skill : store) for (const ESM::Skill& skill : store)
{ {
if (!skillSet.contains(skill.second.mIndex)) if (!skillSet.contains(skill.mId))
mMiscSkills.push_back(skill.second.mIndex); mMiscSkills.push_back(skill.mId);
} }
mUpdateSkillArea = true; mUpdateSkillArea = true;
@ -327,8 +328,8 @@ namespace MWGui
coord2.top += lineHeight; coord2.top += lineHeight;
} }
void ReviewDialog::addSkills(const SkillList& skills, const std::string& titleId, const std::string& titleDefault, void ReviewDialog::addSkills(const std::vector<ESM::RefId>& skills, const std::string& titleId,
MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2)
{ {
// Add a line separator if there are items above // Add a line separator if there are items above
if (!mSkillWidgets.empty()) if (!mSkillWidgets.empty())
@ -339,12 +340,12 @@ namespace MWGui
addGroup( addGroup(
MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2); MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2);
for (const int& skillId : skills) for (const ESM::RefId& skillId : skills)
{ {
const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().search(skillId); const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().search(skillId);
if (!skill) // Skip unknown skill indexes if (!skill) // Skip unknown skills
continue; continue;
const MWMechanics::SkillValue& stat = mSkillValues.find(skillId)->second; const MWMechanics::SkillValue& stat = mSkillValues.find(skill->mId)->second;
int base = stat.getBase(); int base = stat.getBase();
int modified = stat.getModified(); int modified = stat.getModified();
@ -358,10 +359,10 @@ namespace MWGui
for (int i = 0; i < 2; ++i) for (int i = 0; i < 2; ++i)
{ {
ToolTips::createSkillToolTip(mSkillWidgets[mSkillWidgets.size() - 1 - i], skillId); ToolTips::createSkillToolTip(mSkillWidgets[mSkillWidgets.size() - 1 - i], skill->mId);
} }
mSkillWidgetMap[skillId] = widget; mSkillWidgetMap[skill->mId] = widget;
} }
} }
@ -393,15 +394,11 @@ namespace MWGui
if (!mRaceId.empty()) if (!mRaceId.empty())
race = MWBase::Environment::get().getESMStore()->get<ESM::Race>().find(mRaceId); race = MWBase::Environment::get().getESMStore()->get<ESM::Race>().find(mRaceId);
int skills[ESM::Skill::Length];
for (int i = 0; i < ESM::Skill::Length; ++i)
skills[i] = mSkillValues.find(i)->second.getBase();
int attributes[ESM::Attribute::Length]; int attributes[ESM::Attribute::Length];
for (int i = 0; i < ESM::Attribute::Length; ++i) for (int i = 0; i < ESM::Attribute::Length; ++i)
attributes[i] = mAttributeWidgets[i]->getAttributeValue().getBase(); attributes[i] = mAttributeWidgets[i]->getAttributeValue().getBase();
std::vector<ESM::RefId> selectedSpells = MWMechanics::autoCalcPlayerSpells(skills, attributes, race); std::vector<ESM::RefId> selectedSpells = MWMechanics::autoCalcPlayerSpells(mSkillValues, attributes, race);
for (ESM::RefId& spellId : selectedSpells) for (ESM::RefId& spellId : selectedSpells)
{ {
if (std::find(spells.begin(), spells.end(), spellId) == spells.end()) if (std::find(spells.begin(), spells.end(), spellId) == spells.end())

View file

@ -24,7 +24,6 @@ namespace MWGui
CLASS_DIALOG, CLASS_DIALOG,
BIRTHSIGN_DIALOG BIRTHSIGN_DIALOG
}; };
typedef std::vector<int> SkillList;
ReviewDialog(); ReviewDialog();
@ -41,8 +40,8 @@ namespace MWGui
void setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value); void setAttribute(ESM::Attribute::AttributeID attributeId, const MWMechanics::AttributeValue& value);
void configureSkills(const SkillList& major, const SkillList& minor); void configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor);
void setSkillValue(ESM::Skill::SkillEnum skillId, const MWMechanics::SkillValue& value); void setSkillValue(ESM::RefId id, const MWMechanics::SkillValue& value);
void onOpen() override; void onOpen() override;
@ -76,8 +75,8 @@ namespace MWGui
void onMouseWheel(MyGUI::Widget* _sender, int _rel); void onMouseWheel(MyGUI::Widget* _sender, int _rel);
private: private:
void addSkills(const SkillList& skills, const std::string& titleId, const std::string& titleDefault, void addSkills(const std::vector<ESM::RefId>& skills, const std::string& titleId,
MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2);
void addSeparator(MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); void addSeparator(MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2);
void addGroup(std::string_view label, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); void addGroup(std::string_view label, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2);
MyGUI::TextBox* addValueItem(std::string_view text, const std::string& value, const std::string& state, MyGUI::TextBox* addValueItem(std::string_view text, const std::string& value, const std::string& state,
@ -93,9 +92,9 @@ namespace MWGui
std::map<int, Widgets::MWAttributePtr> mAttributeWidgets; std::map<int, Widgets::MWAttributePtr> mAttributeWidgets;
SkillList mMajorSkills, mMinorSkills, mMiscSkills; std::vector<ESM::RefId> mMajorSkills, mMinorSkills, mMiscSkills;
std::map<int, MWMechanics::SkillValue> mSkillValues; std::map<ESM::RefId, MWMechanics::SkillValue> mSkillValues;
std::map<int, MyGUI::TextBox*> mSkillWidgetMap; std::map<ESM::RefId, MyGUI::TextBox*> mSkillWidgetMap;
ESM::RefId mRaceId, mBirthSignId; ESM::RefId mRaceId, mBirthSignId;
std::string mName; std::string mName;
ESM::Class mKlass; ESM::Class mKlass;

View file

@ -286,9 +286,9 @@ namespace MWGui
exit(); exit();
} }
void EditEffectDialog::setSkill(int skill) void EditEffectDialog::setSkill(ESM::RefId skill)
{ {
mEffect.mSkill = skill; mEffect.mSkill = skill.getIf<ESM::IndexRefId>()->getValue();
eventEffectModified(mEffect); eventEffectModified(mEffect);
} }

View file

@ -30,7 +30,7 @@ namespace MWGui
void setConstantEffect(bool constant); void setConstantEffect(bool constant);
void setSkill(int skill); void setSkill(ESM::RefId skill);
void setAttribute(int attribute); void setAttribute(int attribute);
void newEffect(const ESM::MagicEffect* effect); void newEffect(const ESM::MagicEffect* effect);

View file

@ -83,7 +83,8 @@ namespace MWGui
if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill) if (effect->mData.mFlags & ESM::MagicEffect::TargetSkill)
{ {
const ESM::Skill* skill = store->get<ESM::Skill>().find(effectInfo.mKey.mArg); const ESM::Skill* skill
= store->get<ESM::Skill>().find(ESM::Skill::indexToRefId(effectInfo.mKey.mArg));
sourcesDescription += " (" + skill->mName + ')'; sourcesDescription += " (" + skill->mName + ')';
} }
if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute) if (effect->mData.mFlags & ESM::MagicEffect::TargetAttribute)

View file

@ -55,7 +55,7 @@ namespace MWGui
{ {
const ESM::MagicEffect* magicEffect = store.get<ESM::MagicEffect>().find(effectId); const ESM::MagicEffect* magicEffect = store.get<ESM::MagicEffect>().find(effectId);
const ESM::Attribute* attribute = store.get<ESM::Attribute>().search(effect.mAttribute); const ESM::Attribute* attribute = store.get<ESM::Attribute>().search(effect.mAttribute);
const ESM::Skill* skill = store.get<ESM::Skill>().search(effect.mSkill); const ESM::Skill* skill = store.get<ESM::Skill>().search(ESM::Skill::indexToRefId(effect.mSkill));
std::string fullEffectName = MWMechanics::getMagicEffectString(*magicEffect, attribute, skill); std::string fullEffectName = MWMechanics::getMagicEffectString(*magicEffect, attribute, skill);
std::string convert = Utf8Stream::lowerCaseUtf8(fullEffectName); std::string convert = Utf8Stream::lowerCaseUtf8(fullEffectName);

View file

@ -83,13 +83,13 @@ namespace MWGui
} }
} }
// Loop over ESM::Skill::SkillEnum for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
for (int i = 0; i < ESM::Skill::Length; ++i)
{ {
if (stats.getSkill(i) != mWatchedSkills[i] || mWatchedStatsEmpty) const auto& value = stats.getSkill(skill.mId);
if (value != mWatchedSkills[skill.mId] || mWatchedStatsEmpty)
{ {
mWatchedSkills[i] = stats.getSkill(i); mWatchedSkills[skill.mId] = value;
setValue((ESM::Skill::SkillEnum)i, stats.getSkill(i)); setValue(skill.mId, value);
} }
} }
@ -125,13 +125,13 @@ namespace MWGui
setValue("class", cls->mName); setValue("class", cls->mName);
size_t size = cls->mData.mSkills.size(); size_t size = cls->mData.mSkills.size();
MWBase::WindowManager::SkillList majorSkills(size); std::vector<ESM::RefId> majorSkills(size);
MWBase::WindowManager::SkillList minorSkills(size); std::vector<ESM::RefId> minorSkills(size);
for (size_t i = 0; i < size; ++i) for (size_t i = 0; i < size; ++i)
{ {
minorSkills[i] = cls->mData.mSkills[i][0]; minorSkills[i] = ESM::Skill::indexToRefId(cls->mData.mSkills[i][0]);
majorSkills[i] = cls->mData.mSkills[i][1]; majorSkills[i] = ESM::Skill::indexToRefId(cls->mData.mSkills[i][1]);
} }
configureSkills(majorSkills, minorSkills); configureSkills(majorSkills, minorSkills);
@ -157,12 +157,10 @@ namespace MWGui
listener->setValue(id, value); listener->setValue(id, value);
} }
void StatsWatcher::setValue(ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) void StatsWatcher::setValue(ESM::RefId id, const MWMechanics::SkillValue& value)
{ {
/// \todo Don't use the skill enum as a parameter type (we will have to drop it anyway, once we
/// allow custom skills.
for (StatsListener* listener : mListeners) for (StatsListener* listener : mListeners)
listener->setValue(parSkill, value); listener->setValue(id, value);
} }
void StatsWatcher::setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value) void StatsWatcher::setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value)
@ -183,7 +181,7 @@ namespace MWGui
listener->setValue(id, value); listener->setValue(id, value);
} }
void StatsWatcher::configureSkills(const std::vector<int>& major, const std::vector<int>& minor) void StatsWatcher::configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor)
{ {
for (StatsListener* listener : mListeners) for (StatsListener* listener : mListeners)
listener->configureSkills(major, minor); listener->configureSkills(major, minor);

View file

@ -1,6 +1,7 @@
#ifndef MWGUI_STATSWATCHER_H #ifndef MWGUI_STATSWATCHER_H
#define MWGUI_STATSWATCHER_H #define MWGUI_STATSWATCHER_H
#include <map>
#include <set> #include <set>
#include <components/esm/attr.hpp> #include <components/esm/attr.hpp>
@ -22,8 +23,8 @@ namespace MWGui
virtual void setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value) {} virtual void setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value) {}
virtual void setValue(std::string_view, const std::string& value) {} virtual void setValue(std::string_view, const std::string& value) {}
virtual void setValue(std::string_view, int value) {} virtual void setValue(std::string_view, int value) {}
virtual void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) {} virtual void setValue(ESM::RefId id, const MWMechanics::SkillValue& value) {}
virtual void configureSkills(const std::vector<int>& major, const std::vector<int>& minor) {} virtual void configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor) {}
}; };
class StatsWatcher class StatsWatcher
@ -31,7 +32,7 @@ namespace MWGui
MWWorld::Ptr mWatched; MWWorld::Ptr mWatched;
MWMechanics::AttributeValue mWatchedAttributes[ESM::Attribute::Length]; MWMechanics::AttributeValue mWatchedAttributes[ESM::Attribute::Length];
MWMechanics::SkillValue mWatchedSkills[ESM::Skill::Length]; std::map<ESM::RefId, MWMechanics::SkillValue> mWatchedSkills;
MWMechanics::DynamicStat<float> mWatchedHealth; MWMechanics::DynamicStat<float> mWatchedHealth;
MWMechanics::DynamicStat<float> mWatchedMagicka; MWMechanics::DynamicStat<float> mWatchedMagicka;
@ -53,8 +54,8 @@ namespace MWGui
void setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value); void setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value);
void setValue(std::string_view id, const std::string& value); void setValue(std::string_view id, const std::string& value);
void setValue(std::string_view id, int value); void setValue(std::string_view id, int value);
void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value); void setValue(ESM::RefId id, const MWMechanics::SkillValue& value);
void configureSkills(const std::vector<int>& major, const std::vector<int>& minor); void configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor);
public: public:
StatsWatcher(); StatsWatcher();

View file

@ -37,17 +37,8 @@ namespace MWGui
: WindowPinnableBase("openmw_stats_window.layout") : WindowPinnableBase("openmw_stats_window.layout")
, NoDrop(drag, mMainWidget) , NoDrop(drag, mMainWidget)
, mSkillView(nullptr) , mSkillView(nullptr)
, mMajorSkills()
, mMinorSkills()
, mMiscSkills()
, mSkillValues()
, mSkillWidgetMap()
, mFactionWidgetMap()
, mFactions()
, mBirthSignId()
, mReputation(0) , mReputation(0)
, mBounty(0) , mBounty(0)
, mSkillWidgets()
, mChanged(true) , mChanged(true)
, mMinFullWidth(mMainWidget->getSize().width) , mMinFullWidth(mMainWidget->getSize().width)
{ {
@ -67,11 +58,10 @@ namespace MWGui
getWidget(mLeftPane, "LeftPane"); getWidget(mLeftPane, "LeftPane");
getWidget(mRightPane, "RightPane"); getWidget(mRightPane, "RightPane");
for (int i = 0; i < ESM::Skill::Length; ++i) for (const ESM::Skill& skill : store.get<ESM::Skill>())
{ {
mSkillValues.insert(std::make_pair(i, MWMechanics::SkillValue())); mSkillValues.emplace(skill.mId, MWMechanics::SkillValue());
mSkillWidgetMap.insert( mSkillWidgetMap.emplace(skill.mId, std::make_pair<MyGUI::TextBox*, MyGUI::TextBox*>(nullptr, nullptr));
std::make_pair(i, std::make_pair((MyGUI::TextBox*)nullptr, (MyGUI::TextBox*)nullptr)));
} }
MyGUI::Window* t = mMainWidget->castType<MyGUI::Window>(); MyGUI::Window* t = mMainWidget->castType<MyGUI::Window>();
@ -237,7 +227,7 @@ namespace MWGui
} }
} }
void setSkillProgress(MyGUI::Widget* w, float progress, int skillId) void setSkillProgress(MyGUI::Widget* w, float progress, ESM::RefId skillId)
{ {
MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::Ptr player = MWMechanics::getPlayer();
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
@ -255,10 +245,10 @@ namespace MWGui
w->setUserString("RangePosition_SkillProgress", MyGUI::utility::toString(progressPercent)); w->setUserString("RangePosition_SkillProgress", MyGUI::utility::toString(progressPercent));
} }
void StatsWindow::setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) void StatsWindow::setValue(ESM::RefId id, const MWMechanics::SkillValue& value)
{ {
mSkillValues[parSkill] = value; mSkillValues[id] = value;
std::pair<MyGUI::TextBox*, MyGUI::TextBox*> widgets = mSkillWidgetMap[(int)parSkill]; std::pair<MyGUI::TextBox*, MyGUI::TextBox*> widgets = mSkillWidgetMap[id];
MyGUI::TextBox* valueWidget = widgets.second; MyGUI::TextBox* valueWidget = widgets.second;
MyGUI::TextBox* nameWidget = widgets.first; MyGUI::TextBox* nameWidget = widgets.first;
if (valueWidget && nameWidget) if (valueWidget && nameWidget)
@ -296,8 +286,8 @@ namespace MWGui
valueWidget->setUserString("Visible_SkillProgressVBox", "true"); valueWidget->setUserString("Visible_SkillProgressVBox", "true");
valueWidget->setUserString("UserData^Hidden_SkillProgressVBox", "false"); valueWidget->setUserString("UserData^Hidden_SkillProgressVBox", "false");
setSkillProgress(nameWidget, value.getProgress(), parSkill); setSkillProgress(nameWidget, value.getProgress(), id);
setSkillProgress(valueWidget, value.getProgress(), parSkill); setSkillProgress(valueWidget, value.getProgress(), id);
} }
else else
{ {
@ -314,21 +304,21 @@ namespace MWGui
} }
} }
void StatsWindow::configureSkills(const std::vector<int>& major, const std::vector<int>& minor) void StatsWindow::configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor)
{ {
mMajorSkills = major; mMajorSkills = major;
mMinorSkills = minor; mMinorSkills = minor;
// Update misc skills with the remaining skills not in major or minor // Update misc skills with the remaining skills not in major or minor
std::set<int> skillSet; std::set<ESM::RefId> skillSet;
std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin())); std::copy(major.begin(), major.end(), std::inserter(skillSet, skillSet.begin()));
std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin())); std::copy(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin()));
mMiscSkills.clear(); mMiscSkills.clear();
const auto& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>(); const auto& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>();
for (const auto& skill : store) for (const auto& skill : store)
{ {
if (!skillSet.contains(skill.second.mIndex)) if (!skillSet.contains(skill.mId))
mMiscSkills.push_back(skill.second.mIndex); mMiscSkills.push_back(skill.mId);
} }
updateSkillArea(); updateSkillArea();
@ -492,8 +482,8 @@ namespace MWGui
return skillNameWidget; return skillNameWidget;
} }
void StatsWindow::addSkills(const SkillList& skills, const std::string& titleId, const std::string& titleDefault, void StatsWindow::addSkills(const std::vector<ESM::RefId>& skills, const std::string& titleId,
MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2) const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2)
{ {
// Add a line separator if there are items above // Add a line separator if there are items above
if (!mSkillWidgets.empty()) if (!mSkillWidgets.empty())
@ -504,19 +494,18 @@ namespace MWGui
addGroup( addGroup(
MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2); MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2);
for (const int skillId : skills) const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
for (const ESM::RefId& skillId : skills)
{ {
if (skillId < 0 || skillId >= ESM::Skill::Length) // Skip unknown skill indexes const ESM::Skill* skill = esmStore.get<ESM::Skill>().search(skillId);
if (!skill) // Skip unknown skills
continue; continue;
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
const ESM::Skill* skill = esmStore.get<ESM::Skill>().find(skillId);
const ESM::Attribute* attr = esmStore.get<ESM::Attribute>().find(skill->mData.mAttribute); const ESM::Attribute* attr = esmStore.get<ESM::Attribute>().find(skill->mData.mAttribute);
std::pair<MyGUI::TextBox*, MyGUI::TextBox*> widgets std::pair<MyGUI::TextBox*, MyGUI::TextBox*> widgets
= addValueItem(skill->mName, {}, "normal", coord1, coord2); = addValueItem(skill->mName, {}, "normal", coord1, coord2);
mSkillWidgetMap[skillId] = widgets; mSkillWidgetMap[skill->mId] = widgets;
for (int i = 0; i < 2; ++i) for (int i = 0; i < 2; ++i)
{ {
@ -532,7 +521,7 @@ namespace MWGui
mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("Range_SkillProgress", "100"); mSkillWidgets[mSkillWidgets.size() - 1 - i]->setUserString("Range_SkillProgress", "100");
} }
setValue(static_cast<ESM::Skill::SkillEnum>(skillId), mSkillValues.find(skillId)->second); setValue(skill->mId, mSkillValues.find(skill->mId)->second);
} }
} }
@ -640,13 +629,13 @@ namespace MWGui
bool firstSkill = true; bool firstSkill = true;
for (int id : faction->mData.mSkills) for (int id : faction->mData.mSkills)
{ {
if (id != -1) const ESM::Skill* skill = store.get<ESM::Skill>().search(ESM::Skill::indexToRefId(id));
if (skill)
{ {
if (!firstSkill) if (!firstSkill)
text += ", "; text += ", ";
firstSkill = false; firstSkill = false;
const ESM::Skill* skill = store.get<ESM::Skill>().find(id);
text += MyGUI::TextIterator::toTagsString(skill->mName); text += MyGUI::TextIterator::toTagsString(skill->mName);
} }
} }

View file

@ -12,8 +12,6 @@ namespace MWGui
public: public:
typedef std::map<ESM::RefId, int> FactionList; typedef std::map<ESM::RefId, int> FactionList;
typedef std::vector<int> SkillList;
StatsWindow(DragAndDrop* drag); StatsWindow(DragAndDrop* drag);
/// automatically updates all the data in the stats window, but only if it has changed. /// automatically updates all the data in the stats window, but only if it has changed.
@ -27,8 +25,8 @@ namespace MWGui
void setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value) override; void setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value) override;
void setValue(std::string_view id, const std::string& value) override; void setValue(std::string_view id, const std::string& value) override;
void setValue(std::string_view id, int value) override; void setValue(std::string_view id, int value) override;
void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) override; void setValue(ESM::RefId id, const MWMechanics::SkillValue& value) override;
void configureSkills(const SkillList& major, const SkillList& minor) override; void configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor) override;
void setReputation(int reputation) void setReputation(int reputation)
{ {
@ -47,8 +45,8 @@ namespace MWGui
void onOpen() override { onWindowResize(mMainWidget->castType<MyGUI::Window>()); } void onOpen() override { onWindowResize(mMainWidget->castType<MyGUI::Window>()); }
private: private:
void addSkills(const SkillList& skills, const std::string& titleId, const std::string& titleDefault, void addSkills(const std::vector<ESM::RefId>& skills, const std::string& titleId,
MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2);
void addSeparator(MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); void addSeparator(MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2);
void addGroup(std::string_view label, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2); void addGroup(std::string_view label, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2);
std::pair<MyGUI::TextBox*, MyGUI::TextBox*> addValueItem(std::string_view text, const std::string& value, std::pair<MyGUI::TextBox*, MyGUI::TextBox*> addValueItem(std::string_view text, const std::string& value,
@ -67,9 +65,9 @@ namespace MWGui
MyGUI::ScrollView* mSkillView; MyGUI::ScrollView* mSkillView;
SkillList mMajorSkills, mMinorSkills, mMiscSkills; std::vector<ESM::RefId> mMajorSkills, mMinorSkills, mMiscSkills;
std::map<int, MWMechanics::SkillValue> mSkillValues; std::map<ESM::RefId, MWMechanics::SkillValue> mSkillValues;
std::map<int, std::pair<MyGUI::TextBox*, MyGUI::TextBox*>> mSkillWidgetMap; std::map<ESM::RefId, std::pair<MyGUI::TextBox*, MyGUI::TextBox*>> mSkillWidgetMap;
std::map<std::string, MyGUI::Widget*> mFactionWidgetMap; std::map<std::string, MyGUI::Widget*> mFactionWidgetMap;
FactionList mFactions; ///< Stores a list of factions and the current rank FactionList mFactions; ///< Stores a list of factions and the current rank
ESM::RefId mBirthSignId; ESM::RefId mBirthSignId;

View file

@ -805,9 +805,9 @@ namespace MWGui
mFocusToolTipY = min_y; mFocusToolTipY = min_y;
} }
void ToolTips::createSkillToolTip(MyGUI::Widget* widget, int skillId) void ToolTips::createSkillToolTip(MyGUI::Widget* widget, ESM::RefId skillId)
{ {
if (skillId == -1) if (skillId.empty())
return; return;
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
@ -846,7 +846,7 @@ namespace MWGui
const MWWorld::Store<ESM::Skill>& skills = MWBase::Environment::get().getESMStore()->get<ESM::Skill>(); const MWWorld::Store<ESM::Skill>& skills = MWBase::Environment::get().getESMStore()->get<ESM::Skill>();
bool isFirst = true; bool isFirst = true;
for (const auto& [_, skill] : skills) for (const auto& skill : skills)
{ {
if (skill.mData.mSpecialization == specId) if (skill.mData.mSpecialization == specId)
{ {

View file

@ -93,7 +93,7 @@ namespace MWGui
// these do not create an actual tooltip, but they fill in the data that is required so the tooltip // these do not create an actual tooltip, but they fill in the data that is required so the tooltip
// system knows what to show in case this widget is hovered // system knows what to show in case this widget is hovered
static void createSkillToolTip(MyGUI::Widget* widget, int skillId); static void createSkillToolTip(MyGUI::Widget* widget, ESM::RefId skillId);
static void createAttributeToolTip(MyGUI::Widget* widget, int attributeId); static void createAttributeToolTip(MyGUI::Widget* widget, int attributeId);
static void createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId); static void createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId);
static void createBirthsignToolTip(MyGUI::Widget* widget, const ESM::RefId& birthsignId); static void createBirthsignToolTip(MyGUI::Widget* widget, const ESM::RefId& birthsignId);

View file

@ -21,24 +21,6 @@
#include "tooltips.hpp" #include "tooltips.hpp"
namespace
{
// Sorts a container descending by skill value. If skill value is equal, sorts ascending by skill ID.
// pair <skill ID, skill value>
bool sortSkills(const std::pair<int, int>& left, const std::pair<int, int>& right)
{
if (left == right)
return false;
if (left.second > right.second)
return true;
else if (left.second < right.second)
return false;
return left.first < right.first;
}
}
namespace MWGui namespace MWGui
{ {
@ -80,34 +62,37 @@ namespace MWGui
mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold));
const auto& store = MWBase::Environment::get().getESMStore();
const MWWorld::Store<ESM::GameSetting>& gmst = store->get<ESM::GameSetting>();
const MWWorld::Store<ESM::Skill>& skillStore = store->get<ESM::Skill>();
// NPC can train you in his best 3 skills // NPC can train you in his best 3 skills
std::vector<std::pair<int, float>> skills; std::vector<std::pair<const ESM::Skill*, float>> skills;
MWMechanics::NpcStats const& actorStats(actor.getClass().getNpcStats(actor)); MWMechanics::NpcStats const& actorStats(actor.getClass().getNpcStats(actor));
for (int i = 0; i < ESM::Skill::Length; ++i) for (const ESM::Skill& skill : skillStore)
{ {
float value = getSkillForTraining(actorStats, i); float value = getSkillForTraining(actorStats, skill.mId);
skills.emplace_back(i, value); skills.emplace_back(&skill, value);
} }
std::sort(skills.begin(), skills.end(), sortSkills); std::sort(skills.begin(), skills.end(), [](const auto& left, const auto& right) {
return std::tie(right.second, left.first->mId) < std::tie(left.second, right.first->mId);
});
MyGUI::EnumeratorWidgetPtr widgets = mTrainingOptions->getEnumerator(); MyGUI::EnumeratorWidgetPtr widgets = mTrainingOptions->getEnumerator();
MyGUI::Gui::getInstance().destroyWidgets(widgets); MyGUI::Gui::getInstance().destroyWidgets(widgets);
MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player);
const auto& store = MWBase::Environment::get().getESMStore();
const MWWorld::Store<ESM::GameSetting>& gmst = store->get<ESM::GameSetting>();
const MWWorld::Store<ESM::Skill>& skillStore = store->get<ESM::Skill>();
const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2; const int lineHeight = MWBase::Environment::get().getWindowManager()->getFontHeight() + 2;
for (int i = 0; i < 3; ++i) for (int i = 0; i < 3; ++i)
{ {
const ESM::Skill* skill = skills[i].first;
int price = static_cast<int>( int price = static_cast<int>(
pcStats.getSkill(skills[i].first).getBase() * gmst.find("iTrainingMod")->mValue.getInteger()); pcStats.getSkill(skill->mId).getBase() * gmst.find("iTrainingMod")->mValue.getInteger());
price = std::max(1, price); price = std::max(1, price);
price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true);
@ -120,13 +105,12 @@ namespace MWGui
button->setUserData(skills[i].first); button->setUserData(skills[i].first);
button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected); button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected);
const ESM::Skill* skill = skillStore.find(skills[i].first);
button->setCaptionWithReplacing( button->setCaptionWithReplacing(
MyGUI::TextIterator::toTagsString(skill->mName) + " - " + MyGUI::utility::toString(price)); MyGUI::TextIterator::toTagsString(skill->mName) + " - " + MyGUI::utility::toString(price));
button->setSize(button->getTextSize().width + 12, button->getSize().height); button->setSize(button->getTextSize().width + 12, button->getSize().height);
ToolTips::createSkillToolTip(button, skills[i].first); ToolTips::createSkillToolTip(button, skill->mId);
} }
center(); center();
@ -144,29 +128,29 @@ namespace MWGui
void TrainingWindow::onTrainingSelected(MyGUI::Widget* sender) void TrainingWindow::onTrainingSelected(MyGUI::Widget* sender)
{ {
int skillId = *sender->getUserData<int>(); const ESM::Skill* skill = *sender->getUserData<const ESM::Skill*>();
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player);
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
int price = pcStats.getSkill(skillId).getBase() int price = pcStats.getSkill(skill->mId).getBase()
* store.get<ESM::GameSetting>().find("iTrainingMod")->mValue.getInteger(); * store.get<ESM::GameSetting>().find("iTrainingMod")->mValue.getInteger();
price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true); price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true);
if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId))
return; return;
if (getSkillForTraining(mPtr.getClass().getNpcStats(mPtr), skillId) <= pcStats.getSkill(skillId).getBase()) if (getSkillForTraining(mPtr.getClass().getNpcStats(mPtr), skill->mId)
<= pcStats.getSkill(skill->mId).getBase())
{ {
MWBase::Environment::get().getWindowManager()->messageBox("#{sServiceTrainingWords}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sServiceTrainingWords}");
return; return;
} }
// You can not train a skill above its governing attribute // You can not train a skill above its governing attribute
const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(skillId); if (pcStats.getSkill(skill->mId).getBase() >= pcStats.getAttribute(skill->mData.mAttribute).getBase())
if (pcStats.getSkill(skillId).getBase() >= pcStats.getAttribute(skill->mData.mAttribute).getBase())
{ {
MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage17}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage17}");
return; return;
@ -176,7 +160,7 @@ namespace MWGui
MWWorld::LiveCellRef<ESM::NPC>* playerRef = player.get<ESM::NPC>(); MWWorld::LiveCellRef<ESM::NPC>* playerRef = player.get<ESM::NPC>();
const ESM::Class* class_ = store.get<ESM::Class>().find(playerRef->mBase->mClass); const ESM::Class* class_ = store.get<ESM::Class>().find(playerRef->mBase->mClass);
pcStats.increaseSkill(skillId, *class_, true); pcStats.increaseSkill(skill->mId, *class_, true);
// remove gold // remove gold
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price);
@ -212,11 +196,11 @@ namespace MWGui
MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode(); MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode();
} }
float TrainingWindow::getSkillForTraining(const MWMechanics::NpcStats& stats, int skillId) const float TrainingWindow::getSkillForTraining(const MWMechanics::NpcStats& stats, ESM::RefId id) const
{ {
if (mTrainingSkillBasedOnBaseSkill) if (mTrainingSkillBasedOnBaseSkill)
return stats.getSkill(skillId).getBase(); return stats.getSkill(id).getBase();
return stats.getSkill(skillId).getModified(); return stats.getSkill(id).getModified();
} }
void TrainingWindow::onFrame(float dt) void TrainingWindow::onFrame(float dt)

View file

@ -42,7 +42,7 @@ namespace MWGui
// Retrieve the base skill value if the setting 'training skills based on base skill' is set; // Retrieve the base skill value if the setting 'training skills based on base skill' is set;
// otherwise returns the modified skill // otherwise returns the modified skill
float getSkillForTraining(const MWMechanics::NpcStats& stats, int skillId) const; float getSkillForTraining(const MWMechanics::NpcStats& stats, ESM::RefId id) const;
MyGUI::Widget* mTrainingOptions; MyGUI::Widget* mTrainingOptions;
MyGUI::Button* mCancelButton; MyGUI::Button* mCancelButton;

View file

@ -28,28 +28,17 @@ namespace MWGui::Widgets
/* MWSkill */ /* MWSkill */
MWSkill::MWSkill() MWSkill::MWSkill()
: mSkillId(ESM::Skill::Length) : mSkillNameWidget(nullptr)
, mSkillNameWidget(nullptr)
, mSkillValueWidget(nullptr) , mSkillValueWidget(nullptr)
{ {
} }
void MWSkill::setSkillId(ESM::Skill::SkillEnum skill) void MWSkill::setSkillId(ESM::RefId skill)
{ {
mSkillId = skill; mSkillId = skill;
updateWidgets(); updateWidgets();
} }
void MWSkill::setSkillNumber(int skill)
{
if (skill < 0)
setSkillId(ESM::Skill::Length);
else if (skill < ESM::Skill::Length)
setSkillId(static_cast<ESM::Skill::SkillEnum>(skill));
else
throw std::runtime_error("Skill number out of range");
}
void MWSkill::setSkillValue(const SkillValue& value) void MWSkill::setSkillValue(const SkillValue& value)
{ {
mValue = value; mValue = value;
@ -374,7 +363,7 @@ namespace MWGui::Widgets
const ESM::MagicEffect* magicEffect = store.get<ESM::MagicEffect>().search(mEffectParams.mEffectID); const ESM::MagicEffect* magicEffect = store.get<ESM::MagicEffect>().search(mEffectParams.mEffectID);
const ESM::Attribute* attribute = store.get<ESM::Attribute>().search(mEffectParams.mAttribute); const ESM::Attribute* attribute = store.get<ESM::Attribute>().search(mEffectParams.mAttribute);
const ESM::Skill* skill = store.get<ESM::Skill>().search(mEffectParams.mSkill); const ESM::Skill* skill = store.get<ESM::Skill>().search(ESM::Skill::indexToRefId(mEffectParams.mSkill));
assert(magicEffect); assert(magicEffect);

View file

@ -100,11 +100,10 @@ namespace MWGui
typedef MWMechanics::Stat<float> SkillValue; typedef MWMechanics::Stat<float> SkillValue;
void setSkillId(ESM::Skill::SkillEnum skillId); void setSkillId(ESM::RefId skillId);
void setSkillNumber(int skillId);
void setSkillValue(const SkillValue& value); void setSkillValue(const SkillValue& value);
ESM::Skill::SkillEnum getSkillId() const { return mSkillId; } ESM::RefId getSkillId() const { return mSkillId; }
const SkillValue& getSkillValue() const { return mValue; } const SkillValue& getSkillValue() const { return mValue; }
// Events // Events
@ -125,7 +124,7 @@ namespace MWGui
private: private:
void updateWidgets(); void updateWidgets();
ESM::Skill::SkillEnum mSkillId; ESM::RefId mSkillId;
SkillValue mValue; SkillValue mValue;
MyGUI::TextBox* mSkillNameWidget; MyGUI::TextBox* mSkillNameWidget;
MyGUI::TextBox* mSkillValueWidget; MyGUI::TextBox* mSkillValueWidget;

View file

@ -4,6 +4,7 @@
#include <memory> #include <memory>
#include <set> #include <set>
#include <string> #include <string>
#include <utility>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/scriptscontainer.hpp> #include <components/lua/scriptscontainer.hpp>
@ -21,11 +22,12 @@ namespace MWLua
class CachedStat class CachedStat
{ {
public: public:
using Setter = void (*)(int, std::string_view, const MWWorld::Ptr&, const sol::object&); using Index = std::variant<int, ESM::RefId>;
using Setter = void (*)(const Index&, std::string_view, const MWWorld::Ptr&, const sol::object&);
CachedStat(Setter setter, int index, std::string_view prop) CachedStat(Setter setter, Index index, std::string_view prop)
: mSetter(setter) : mSetter(setter)
, mIndex(index) , mIndex(std::move(index))
, mProp(std::move(prop)) , mProp(std::move(prop))
{ {
} }
@ -42,7 +44,7 @@ namespace MWLua
private: private:
Setter mSetter; // Function that updates a stat's property Setter mSetter; // Function that updates a stat's property
int mIndex; // Optional index to disambiguate the stat Index mIndex; // Optional index to disambiguate the stat
std::string_view mProp; // Name of the stat's property std::string_view mProp; // Name of the stat's property
}; };

View file

@ -24,9 +24,10 @@ namespace
{ {
using SelfObject = MWLua::SelfObject; using SelfObject = MWLua::SelfObject;
using ObjectVariant = MWLua::ObjectVariant; using ObjectVariant = MWLua::ObjectVariant;
using Index = const SelfObject::CachedStat::Index&;
template <class T> template <class T>
auto addIndexedAccessor(int index) auto addIndexedAccessor(Index index)
{ {
return [index](const sol::object& o) { return T::create(ObjectVariant(o), index); }; return [index](const sol::object& o) { return T::create(ObjectVariant(o), index); };
} }
@ -40,7 +41,7 @@ namespace
template <class G> template <class G>
sol::object getValue(const MWLua::Context& context, const ObjectVariant& obj, SelfObject::CachedStat::Setter setter, sol::object getValue(const MWLua::Context& context, const ObjectVariant& obj, SelfObject::CachedStat::Setter setter,
int index, std::string_view prop, G getter) Index index, std::string_view prop, G getter)
{ {
if (obj.isSelfObject()) if (obj.isSelfObject())
{ {
@ -99,14 +100,14 @@ namespace MWLua
return sol::make_object(context.mLua->sol(), ptr.getClass().getNpcStats(ptr).getLevelProgress()); return sol::make_object(context.mLua->sol(), ptr.getClass().getNpcStats(ptr).getLevelProgress());
} }
static std::optional<LevelStat> create(ObjectVariant object, int index) static std::optional<LevelStat> create(ObjectVariant object, Index)
{ {
if (!object.ptr().getClass().isActor()) if (!object.ptr().getClass().isActor())
return {}; return {};
return LevelStat{ std::move(object) }; return LevelStat{ std::move(object) };
} }
static void setValue(int, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) static void setValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
{ {
auto& stats = ptr.getClass().getCreatureStats(ptr); auto& stats = ptr.getClass().getCreatureStats(ptr);
if (prop == "current") if (prop == "current")
@ -135,10 +136,11 @@ namespace MWLua
}); });
} }
static std::optional<DynamicStat> create(ObjectVariant object, int index) static std::optional<DynamicStat> create(ObjectVariant object, Index i)
{ {
if (!object.ptr().getClass().isActor()) if (!object.ptr().getClass().isActor())
return {}; return {};
int index = std::get<int>(i);
return DynamicStat{ std::move(object), index }; return DynamicStat{ std::move(object), index };
} }
@ -149,8 +151,9 @@ namespace MWLua
obj->mStatsCache[SelfObject::CachedStat{ &DynamicStat::setValue, mIndex, prop }] = value; obj->mStatsCache[SelfObject::CachedStat{ &DynamicStat::setValue, mIndex, prop }] = value;
} }
static void setValue(int index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
{ {
int index = std::get<int>(i);
auto& stats = ptr.getClass().getCreatureStats(ptr); auto& stats = ptr.getClass().getCreatureStats(ptr);
auto stat = stats.getDynamic(index); auto stat = stats.getDynamic(index);
float floatValue = LuaUtil::cast<float>(value); float floatValue = LuaUtil::cast<float>(value);
@ -193,10 +196,11 @@ namespace MWLua
return std::max(0.f, base - damage + modifier); // Should match AttributeValue::getModified return std::max(0.f, base - damage + modifier); // Should match AttributeValue::getModified
} }
static std::optional<AttributeStat> create(ObjectVariant object, int index) static std::optional<AttributeStat> create(ObjectVariant object, Index i)
{ {
if (!object.ptr().getClass().isActor()) if (!object.ptr().getClass().isActor())
return {}; return {};
int index = std::get<int>(i);
return AttributeStat{ std::move(object), index }; return AttributeStat{ std::move(object), index };
} }
@ -207,8 +211,9 @@ namespace MWLua
obj->mStatsCache[SelfObject::CachedStat{ &AttributeStat::setValue, mIndex, prop }] = value; obj->mStatsCache[SelfObject::CachedStat{ &AttributeStat::setValue, mIndex, prop }] = value;
} }
static void setValue(int index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
{ {
int index = std::get<int>(i);
auto& stats = ptr.getClass().getCreatureStats(ptr); auto& stats = ptr.getClass().getCreatureStats(ptr);
auto stat = stats.getAttribute(index); auto stat = stats.getAttribute(index);
float floatValue = LuaUtil::cast<float>(value); float floatValue = LuaUtil::cast<float>(value);
@ -228,37 +233,36 @@ namespace MWLua
class SkillStat class SkillStat
{ {
ObjectVariant mObject; ObjectVariant mObject;
int mIndex; ESM::RefId mId;
SkillStat(ObjectVariant object, int index) SkillStat(ObjectVariant object, ESM::RefId id)
: mObject(std::move(object)) : mObject(std::move(object))
, mIndex(index) , mId(id)
{ {
} }
static float getProgress(const MWWorld::Ptr& ptr, int index, const MWMechanics::SkillValue& stat) static float getProgress(const MWWorld::Ptr& ptr, ESM::RefId id, const MWMechanics::SkillValue& stat)
{ {
float progress = stat.getProgress(); float progress = stat.getProgress();
if (progress != 0.f) if (progress != 0.f)
progress /= getMaxProgress(ptr, index, stat); progress /= getMaxProgress(ptr, id, stat);
return progress; return progress;
} }
static float getMaxProgress(const MWWorld::Ptr& ptr, int index, const MWMechanics::SkillValue& stat) static float getMaxProgress(const MWWorld::Ptr& ptr, ESM::RefId id, const MWMechanics::SkillValue& stat)
{ {
const auto& store = *MWBase::Environment::get().getESMStore(); const auto& store = *MWBase::Environment::get().getESMStore();
const auto cl = store.get<ESM::Class>().find(ptr.get<ESM::NPC>()->mBase->mClass); const auto cl = store.get<ESM::Class>().find(ptr.get<ESM::NPC>()->mBase->mClass);
return ptr.getClass().getNpcStats(ptr).getSkillProgressRequirement(index, *cl); return ptr.getClass().getNpcStats(ptr).getSkillProgressRequirement(id, *cl);
} }
public: public:
template <class G> template <class G>
sol::object get(const Context& context, std::string_view prop, G getter) const sol::object get(const Context& context, std::string_view prop, G getter) const
{ {
return getValue( return getValue(context, mObject, &SkillStat::setValue, mId, prop, [this, getter](const MWWorld::Ptr& ptr) {
context, mObject, &SkillStat::setValue, mIndex, prop, [this, getter](const MWWorld::Ptr& ptr) { return (ptr.getClass().getNpcStats(ptr).getSkill(mId).*getter)();
return (ptr.getClass().getNpcStats(ptr).getSkill(mIndex).*getter)(); });
});
} }
float getModified(const Context& context) const float getModified(const Context& context) const
@ -271,30 +275,31 @@ namespace MWLua
sol::object getProgress(const Context& context) const sol::object getProgress(const Context& context) const
{ {
return getValue( return getValue(context, mObject, &SkillStat::setValue, mId, "progress", [this](const MWWorld::Ptr& ptr) {
context, mObject, &SkillStat::setValue, mIndex, "progress", [this](const MWWorld::Ptr& ptr) { return getProgress(ptr, mId, ptr.getClass().getNpcStats(ptr).getSkill(mId));
return getProgress(ptr, mIndex, ptr.getClass().getNpcStats(ptr).getSkill(mIndex)); });
});
} }
static std::optional<SkillStat> create(ObjectVariant object, int index) static std::optional<SkillStat> create(ObjectVariant object, Index index)
{ {
if (!object.ptr().getClass().isNpc()) if (!object.ptr().getClass().isNpc())
return {}; return {};
return SkillStat{ std::move(object), index }; ESM::RefId id = std::get<ESM::RefId>(index);
return SkillStat{ std::move(object), id };
} }
void cache(const Context& context, std::string_view prop, const sol::object& value) const void cache(const Context& context, std::string_view prop, const sol::object& value) const
{ {
SelfObject* obj = mObject.asSelfObject(); SelfObject* obj = mObject.asSelfObject();
addStatUpdateAction(context.mLuaManager, *obj); addStatUpdateAction(context.mLuaManager, *obj);
obj->mStatsCache[SelfObject::CachedStat{ &SkillStat::setValue, mIndex, prop }] = value; obj->mStatsCache[SelfObject::CachedStat{ &SkillStat::setValue, mId, prop }] = value;
} }
static void setValue(int index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) static void setValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value)
{ {
ESM::RefId id = std::get<ESM::RefId>(index);
auto& stats = ptr.getClass().getNpcStats(ptr); auto& stats = ptr.getClass().getNpcStats(ptr);
auto stat = stats.getSkill(index); auto stat = stats.getSkill(id);
float floatValue = LuaUtil::cast<float>(value); float floatValue = LuaUtil::cast<float>(value);
if (prop == "base") if (prop == "base")
stat.setBase(floatValue); stat.setBase(floatValue);
@ -306,8 +311,8 @@ namespace MWLua
else if (prop == "modifier") else if (prop == "modifier")
stat.setModifier(floatValue); stat.setModifier(floatValue);
else if (prop == "progress") else if (prop == "progress")
stat.setProgress(floatValue * getMaxProgress(ptr, index, stat)); stat.setProgress(floatValue * getMaxProgress(ptr, id, stat));
stats.setSkill(index, stat); stats.setSkill(id, stat);
} }
}; };
} }
@ -385,7 +390,8 @@ namespace MWLua
[context](const SkillStat& stat, const sol::object& value) { stat.cache(context, "progress", value); }); [context](const SkillStat& stat, const sol::object& value) { stat.cache(context, "progress", value); });
sol::table skills(context.mLua->sol(), sol::create); sol::table skills(context.mLua->sol(), sol::create);
npcStats["skills"] = LuaUtil::makeReadOnly(skills); npcStats["skills"] = LuaUtil::makeReadOnly(skills);
for (int id = ESM::Skill::Block; id < ESM::Skill::Length; ++id) for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
skills[Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[id])] = addIndexedAccessor<SkillStat>(id); skills[Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[skill.mIndex])]
= addIndexedAccessor<SkillStat>(skill.mId);
} }
} }

View file

@ -67,7 +67,7 @@ namespace MWLua
sol::table skill(context.mLua->sol(), sol::create); sol::table skill(context.mLua->sol(), sol::create);
book["SKILL"] = LuaUtil::makeStrictReadOnly(skill); book["SKILL"] = LuaUtil::makeStrictReadOnly(skill);
book["createRecordDraft"] = tableToBook; book["createRecordDraft"] = tableToBook;
for (int id = ESM::Skill::Block; id < ESM::Skill::Length; ++id) for (int id = 0; id < ESM::Skill::Length; ++id)
{ {
std::string skillName = Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[id]); std::string skillName = Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[id]);
skill[skillName] = skillName; skill[skillName] = skillName;

View file

@ -576,7 +576,7 @@ std::vector<std::string> MWMechanics::Alchemy::effectsDescription(const MWWorld:
if (effectID != -1) if (effectID != -1)
{ {
const ESM::Attribute* attribute = store->get<ESM::Attribute>().search(data.mAttributes[i]); const ESM::Attribute* attribute = store->get<ESM::Attribute>().search(data.mAttributes[i]);
const ESM::Skill* skill = store->get<ESM::Skill>().search(data.mAttributes[i]); const ESM::Skill* skill = store->get<ESM::Skill>().search(ESM::Skill::indexToRefId(data.mSkills[i]));
std::string effect = getMagicEffectString(*mgef.find(effectID), attribute, skill); std::string effect = getMagicEffectString(*mgef.find(effectID), attribute, skill);
effects.push_back(effect); effects.push_back(effect);

View file

@ -27,7 +27,8 @@ namespace MWMechanics
ESM::RefId mWeakestSpell; ESM::RefId mWeakestSpell;
}; };
std::vector<ESM::RefId> autoCalcNpcSpells(const int* actorSkills, const int* actorAttributes, const ESM::Race* race) std::vector<ESM::RefId> autoCalcNpcSpells(
const std::map<ESM::RefId, SkillValue>& actorSkills, const int* actorAttributes, const ESM::Race* race)
{ {
const MWWorld::Store<ESM::GameSetting>& gmst const MWWorld::Store<ESM::GameSetting>& gmst
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>(); = MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
@ -148,7 +149,7 @@ namespace MWMechanics
} }
std::vector<ESM::RefId> autoCalcPlayerSpells( std::vector<ESM::RefId> autoCalcPlayerSpells(
const int* actorSkills, const int* actorAttributes, const ESM::Race* race) const std::map<ESM::RefId, SkillValue>& actorSkills, const int* actorAttributes, const ESM::Race* race)
{ {
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
@ -226,7 +227,8 @@ namespace MWMechanics
return selectedSpells; return selectedSpells;
} }
bool attrSkillCheck(const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes) bool attrSkillCheck(
const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills, const int* actorAttributes)
{ {
for (const auto& spellEffect : spell->mEffects.mList) for (const auto& spellEffect : spell->mEffects.mList)
{ {
@ -240,8 +242,9 @@ namespace MWMechanics
if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)) if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill))
{ {
assert(spellEffect.mSkill >= 0 && spellEffect.mSkill < ESM::Skill::Length); ESM::RefId skill = ESM::Skill::indexToRefId(spellEffect.mSkill);
if (actorSkills[spellEffect.mSkill] < iAutoSpellAttSkillMin) auto found = actorSkills.find(skill);
if (found == actorSkills.end() || found->second.getBase() < iAutoSpellAttSkillMin)
return false; return false;
} }
@ -256,7 +259,8 @@ namespace MWMechanics
return true; return true;
} }
void calcWeakestSchool(const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm) void calcWeakestSchool(const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills,
int& effectiveSchool, float& skillTerm)
{ {
// Morrowind for some reason uses a formula slightly different from magicka cost calculation // Morrowind for some reason uses a formula slightly different from magicka cost calculation
float minChance = std::numeric_limits<float>::max(); float minChance = std::numeric_limits<float>::max();
@ -294,7 +298,11 @@ namespace MWMechanics
if (effect.mRange == ESM::RT_Target) if (effect.mRange == ESM::RT_Target)
x *= 1.5f; x *= 1.5f;
float s = 2.f * actorSkills[spellSchoolToSkill(magicEffect->mData.mSchool)]; float s = 0.f;
ESM::RefId skill = spellSchoolToSkill(magicEffect->mData.mSchool);
auto found = actorSkills.find(skill);
if (found != actorSkills.end())
s = 2.f * found->second.getBase();
if (s - x < minChance) if (s - x < minChance)
{ {
minChance = s - x; minChance = s - x;
@ -304,8 +312,8 @@ namespace MWMechanics
} }
} }
float calcAutoCastChance( float calcAutoCastChance(const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills,
const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, int effectiveSchool) const int* actorAttributes, int effectiveSchool)
{ {
if (spell->mData.mType != ESM::Spell::ST_Spell) if (spell->mData.mType != ESM::Spell::ST_Spell)
return 100.f; return 100.f;
@ -315,7 +323,12 @@ namespace MWMechanics
float skillTerm = 0; float skillTerm = 0;
if (effectiveSchool != -1) if (effectiveSchool != -1)
skillTerm = 2.f * actorSkills[spellSchoolToSkill(effectiveSchool)]; {
ESM::RefId skill = spellSchoolToSkill(effectiveSchool);
auto found = actorSkills.find(skill);
if (found != actorSkills.end())
skillTerm = 2.f * found->second.getBase();
}
else else
calcWeakestSchool( calcWeakestSchool(
spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this

View file

@ -1,9 +1,12 @@
#ifndef OPENMW_AUTOCALCSPELL_H #ifndef OPENMW_AUTOCALCSPELL_H
#define OPENMW_AUTOCALCSPELL_H #define OPENMW_AUTOCALCSPELL_H
#include "creaturestats.hpp"
#include <components/esm/refid.hpp> #include <components/esm/refid.hpp>
#include <map>
#include <string> #include <string>
#include <vector> #include <vector>
namespace ESM namespace ESM
{ {
struct Spell; struct Spell;
@ -17,19 +20,21 @@ namespace MWMechanics
/// @note We might want to move this code to a component later, so the editor can use it for preview purposes /// @note We might want to move this code to a component later, so the editor can use it for preview purposes
std::vector<ESM::RefId> autoCalcNpcSpells( std::vector<ESM::RefId> autoCalcNpcSpells(
const int* actorSkills, const int* actorAttributes, const ESM::Race* race); const std::map<ESM::RefId, SkillValue>& actorSkills, const int* actorAttributes, const ESM::Race* race);
std::vector<ESM::RefId> autoCalcPlayerSpells( std::vector<ESM::RefId> autoCalcPlayerSpells(
const int* actorSkills, const int* actorAttributes, const ESM::Race* race); const std::map<ESM::RefId, SkillValue>& actorSkills, const int* actorAttributes, const ESM::Race* race);
// Helpers // Helpers
bool attrSkillCheck(const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes); bool attrSkillCheck(
const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills, const int* actorAttributes);
void calcWeakestSchool(const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm); void calcWeakestSchool(const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills,
int& effectiveSchool, float& skillTerm);
float calcAutoCastChance( float calcAutoCastChance(const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills,
const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, int effectiveSchool); const int* actorAttributes, int effectiveSchool);
} }

View file

@ -217,7 +217,7 @@ namespace MWMechanics
bool validVictim = !victim.isEmpty() && victim.getClass().isActor(); bool validVictim = !victim.isEmpty() && victim.getClass().isActor();
int weaponSkill = ESM::Skill::Marksman; ESM::RefId weaponSkill = ESM::Skill::Marksman;
if (!weapon.isEmpty()) if (!weapon.isEmpty())
weaponSkill = weapon.getClass().getEquipmentSkill(weapon); weaponSkill = weapon.getClass().getEquipmentSkill(weapon);

View file

@ -53,8 +53,8 @@ namespace MWMechanics
{ {
const auto& store = MWBase::Environment::get().getESMStore(); const auto& store = MWBase::Environment::get().getESMStore();
const ESM::MagicEffect* magicEffect = store->get<ESM::MagicEffect>().search(mId); const ESM::MagicEffect* magicEffect = store->get<ESM::MagicEffect>().search(mId);
return getMagicEffectString( return getMagicEffectString(*magicEffect, store->get<ESM::Attribute>().search(mArg),
*magicEffect, store->get<ESM::Attribute>().search(mArg), store->get<ESM::Skill>().search(mArg)); store->get<ESM::Skill>().search(ESM::Skill::indexToRefId(mArg)));
} }
bool operator<(const EffectKey& left, const EffectKey& right) bool operator<(const EffectKey& left, const EffectKey& right)

View file

@ -129,7 +129,7 @@ namespace MWMechanics
creatureStats.getActiveSpells().clear(ptr); creatureStats.getActiveSpells().clear(ptr);
for (size_t i = 0; i < player->mNpdt.mSkills.size(); ++i) for (size_t i = 0; i < player->mNpdt.mSkills.size(); ++i)
npcStats.getSkill(i).setBase(player->mNpdt.mSkills[i]); npcStats.getSkill(ESM::Skill::indexToRefId(i)).setBase(player->mNpdt.mSkills[i]);
creatureStats.setAttribute(ESM::Attribute::Strength, player->mNpdt.mStrength); creatureStats.setAttribute(ESM::Attribute::Strength, player->mNpdt.mStrength);
creatureStats.setAttribute(ESM::Attribute::Intelligence, player->mNpdt.mIntelligence); creatureStats.setAttribute(ESM::Attribute::Intelligence, player->mNpdt.mIntelligence);
@ -155,16 +155,16 @@ namespace MWMechanics
creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale); creatureStats.setAttribute(i, male ? attribute.mMale : attribute.mFemale);
} }
for (int i = 0; i < 27; ++i) for (const ESM::Skill& skill : esmStore.get<ESM::Skill>())
{ {
int bonus = 0; int bonus = 0;
auto bonusIt = std::find_if(race->mData.mBonus.begin(), race->mData.mBonus.end(), auto bonusIt = std::find_if(race->mData.mBonus.begin(), race->mData.mBonus.end(),
[i](const auto& bonus) { return bonus.mSkill == i; }); [&](const auto& bonus) { return bonus.mSkill == skill.mIndex; });
if (bonusIt != race->mData.mBonus.end()) if (bonusIt != race->mData.mBonus.end())
bonus = bonusIt->mBonus; bonus = bonusIt->mBonus;
npcStats.getSkill(i).setBase(5 + bonus); npcStats.getSkill(skill.mId).setBase(5 + bonus);
} }
for (const ESM::RefId& power : race->mPowers.mList) for (const ESM::RefId& power : race->mPowers.mList)
@ -205,29 +205,16 @@ namespace MWMechanics
for (const auto& skills : class_->mData.mSkills) for (const auto& skills : class_->mData.mSkills)
{ {
int index = skills[i]; ESM::RefId id = ESM::Skill::indexToRefId(skills[i]);
if (!id.empty())
if (index >= 0 && index < ESM::Skill::Length) npcStats.getSkill(id).setBase(npcStats.getSkill(id).getBase() + bonus);
{
npcStats.getSkill(index).setBase(npcStats.getSkill(index).getBase() + bonus);
}
} }
} }
const MWWorld::Store<ESM::Skill>& skills = esmStore.get<ESM::Skill>(); for (const ESM::Skill& skill : esmStore.get<ESM::Skill>())
MWWorld::Store<ESM::Skill>::iterator iter = skills.begin();
for (; iter != skills.end(); ++iter)
{ {
if (iter->second.mData.mSpecialization == class_->mData.mSpecialization) if (skill.mData.mSpecialization == class_->mData.mSpecialization)
{ npcStats.getSkill(skill.mId).setBase(npcStats.getSkill(skill.mId).getBase() + 5);
int index = iter->first;
if (index >= 0 && index < 27)
{
npcStats.getSkill(index).setBase(npcStats.getSkill(index).getBase() + 5);
}
}
} }
} }
@ -236,16 +223,12 @@ namespace MWMechanics
if (mRaceSelected) if (mRaceSelected)
race = esmStore.get<ESM::Race>().find(player->mRace); race = esmStore.get<ESM::Race>().find(player->mRace);
int skills[ESM::Skill::Length];
for (int i = 0; i < ESM::Skill::Length; ++i)
skills[i] = npcStats.getSkill(i).getBase();
int attributes[ESM::Attribute::Length]; int attributes[ESM::Attribute::Length];
for (int i = 0; i < ESM::Attribute::Length; ++i) for (int i = 0; i < ESM::Attribute::Length; ++i)
attributes[i] = npcStats.getAttribute(i).getBase(); attributes[i] = npcStats.getAttribute(i).getBase();
npcStats.updateHealth(); npcStats.updateHealth();
std::vector<ESM::RefId> selectedSpells = autoCalcPlayerSpells(skills, attributes, race); std::vector<ESM::RefId> selectedSpells = autoCalcPlayerSpells(npcStats.getSkills(), attributes, race);
for (const ESM::RefId& spell : selectedSpells) for (const ESM::RefId& spell : selectedSpells)
creatureStats.getSpells().add(spell); creatureStats.getSpells().add(spell);
@ -1921,7 +1904,7 @@ namespace MWMechanics
const ESM::Skill* acrobatics const ESM::Skill* acrobatics
= MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(ESM::Skill::Acrobatics); = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(ESM::Skill::Acrobatics);
MWMechanics::NpcStats& stats = actor.getClass().getNpcStats(actor); MWMechanics::NpcStats& stats = actor.getClass().getNpcStats(actor);
auto& skill = stats.getSkill(acrobatics->mIndex); auto& skill = stats.getSkill(acrobatics->mId);
skill.setModifier(acrobatics->mWerewolfValue - skill.getModified()); skill.setModifier(acrobatics->mWerewolfValue - skill.getModified());
} }

View file

@ -30,6 +30,8 @@ MWMechanics::NpcStats::NpcStats()
{ {
mSkillIncreases.resize(ESM::Attribute::Length, 0); mSkillIncreases.resize(ESM::Attribute::Length, 0);
mSpecIncreases.resize(3, 0); mSpecIncreases.resize(3, 0);
for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
mSkills.emplace(skill.mId, SkillValue{});
} }
int MWMechanics::NpcStats::getBaseDisposition() const int MWMechanics::NpcStats::getBaseDisposition() const
@ -42,28 +44,28 @@ void MWMechanics::NpcStats::setBaseDisposition(int disposition)
mDisposition = disposition; mDisposition = disposition;
} }
const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill(int index) const const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill(ESM::RefId id) const
{ {
if (index < 0 || index >= ESM::Skill::Length) auto it = mSkills.find(id);
throw std::runtime_error("skill index out of range"); if (it == mSkills.end())
throw std::runtime_error("skill not found");
return mSkill[index]; return it->second;
} }
MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill(int index) MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill(ESM::RefId id)
{ {
if (index < 0 || index >= ESM::Skill::Length) auto it = mSkills.find(id);
throw std::runtime_error("skill index out of range"); if (it == mSkills.end())
throw std::runtime_error("skill not found");
return mSkill[index]; return it->second;
} }
void MWMechanics::NpcStats::setSkill(int index, const MWMechanics::SkillValue& value) void MWMechanics::NpcStats::setSkill(ESM::RefId id, const MWMechanics::SkillValue& value)
{ {
if (index < 0 || index >= ESM::Skill::Length) auto it = mSkills.find(id);
throw std::runtime_error("skill index out of range"); if (it == mSkills.end())
throw std::runtime_error("skill not found");
mSkill[index] = value; it->second = value;
} }
const std::map<ESM::RefId, int>& MWMechanics::NpcStats::getFactionRanks() const const std::map<ESM::RefId, int>& MWMechanics::NpcStats::getFactionRanks() const
@ -154,22 +156,23 @@ void MWMechanics::NpcStats::setFactionReputation(const ESM::RefId& faction, int
mFactionReputation[faction] = value; mFactionReputation[faction] = value;
} }
float MWMechanics::NpcStats::getSkillProgressRequirement(int skillIndex, const ESM::Class& class_) const float MWMechanics::NpcStats::getSkillProgressRequirement(ESM::RefId id, const ESM::Class& class_) const
{ {
float progressRequirement = static_cast<float>(1 + getSkill(skillIndex).getBase()); float progressRequirement = 1.f + getSkill(id).getBase();
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>(); const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(id);
float typeFactor = gmst.find("fMiscSkillBonus")->mValue.getFloat(); float typeFactor = gmst.find("fMiscSkillBonus")->mValue.getFloat();
for (const auto& skills : class_.mData.mSkills) for (const auto& skills : class_.mData.mSkills)
{ {
if (skills[0] == skillIndex) if (skills[0] == skill->mIndex)
{ {
typeFactor = gmst.find("fMinorSkillBonus")->mValue.getFloat(); typeFactor = gmst.find("fMinorSkillBonus")->mValue.getFloat();
break; break;
} }
else if (skills[1] == skillIndex) else if (skills[1] == skill->mIndex)
{ {
typeFactor = gmst.find("fMajorSkillBonus")->mValue.getFloat(); typeFactor = gmst.find("fMajorSkillBonus")->mValue.getFloat();
break; break;
@ -183,7 +186,6 @@ float MWMechanics::NpcStats::getSkillProgressRequirement(int skillIndex, const E
float specialisationFactor = 1; float specialisationFactor = 1;
const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(skillIndex);
if (skill->mData.mSpecialization == class_.mData.mSpecialization) if (skill->mData.mSpecialization == class_.mData.mSpecialization)
{ {
specialisationFactor = gmst.find("fSpecialSkillBonus")->mValue.getFloat(); specialisationFactor = gmst.find("fSpecialSkillBonus")->mValue.getFloat();
@ -196,9 +198,9 @@ float MWMechanics::NpcStats::getSkillProgressRequirement(int skillIndex, const E
return progressRequirement; return progressRequirement;
} }
void MWMechanics::NpcStats::useSkill(int skillIndex, const ESM::Class& class_, int usageType, float extraFactor) void MWMechanics::NpcStats::useSkill(ESM::RefId id, const ESM::Class& class_, int usageType, float extraFactor)
{ {
const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(skillIndex); const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(id);
float skillGain = 1; float skillGain = 1;
if (usageType >= 4) if (usageType >= 4)
throw std::runtime_error("skill usage type out of range"); throw std::runtime_error("skill usage type out of range");
@ -210,21 +212,21 @@ void MWMechanics::NpcStats::useSkill(int skillIndex, const ESM::Class& class_, i
} }
skillGain *= extraFactor; skillGain *= extraFactor;
MWMechanics::SkillValue& value = getSkill(skillIndex); MWMechanics::SkillValue& value = getSkill(skill->mId);
value.setProgress(value.getProgress() + skillGain); value.setProgress(value.getProgress() + skillGain);
if (int(value.getProgress()) >= int(getSkillProgressRequirement(skillIndex, class_))) if (int(value.getProgress()) >= int(getSkillProgressRequirement(skill->mId, class_)))
{ {
// skill levelled up // skill levelled up
increaseSkill(skillIndex, class_, false); increaseSkill(skill->mId, class_, false);
} }
} }
void MWMechanics::NpcStats::increaseSkill( void MWMechanics::NpcStats::increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook)
int skillIndex, const ESM::Class& class_, bool preserveProgress, bool readBook)
{ {
float base = getSkill(skillIndex).getBase(); const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(id);
float base = getSkill(skill->mId).getBase();
if (base >= 100.f) if (base >= 100.f)
return; return;
@ -237,13 +239,13 @@ void MWMechanics::NpcStats::increaseSkill(
int increase = gmst.find("iLevelupMiscMultAttriubte")->mValue.getInteger(); // Note: GMST has a typo int increase = gmst.find("iLevelupMiscMultAttriubte")->mValue.getInteger(); // Note: GMST has a typo
for (const auto& skills : class_.mData.mSkills) for (const auto& skills : class_.mData.mSkills)
{ {
if (skills[0] == skillIndex) if (skills[0] == skill->mIndex)
{ {
mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger(); mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger();
increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger(); increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger();
break; break;
} }
else if (skills[1] == skillIndex) else if (skills[1] == skill->mIndex)
{ {
mLevelProgress += gmst.find("iLevelUpMajorMult")->mValue.getInteger(); mLevelProgress += gmst.find("iLevelUpMajorMult")->mValue.getInteger();
increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger(); increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger();
@ -251,7 +253,6 @@ void MWMechanics::NpcStats::increaseSkill(
} }
} }
const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(skillIndex);
mSkillIncreases[skill->mData.mAttribute] += increase; mSkillIncreases[skill->mData.mAttribute] += increase;
mSpecIncreases[skill->mData.mSpecialization] += gmst.find("iLevelupSpecialization")->mValue.getInteger(); mSpecIncreases[skill->mData.mSpecialization] += gmst.find("iLevelupSpecialization")->mValue.getInteger();
@ -275,9 +276,9 @@ void MWMechanics::NpcStats::increaseSkill(
MWBase::Environment::get().getWindowManager()->messageBox("#{sLevelUpMsg}", MWGui::ShowInDialogueMode_Never); MWBase::Environment::get().getWindowManager()->messageBox("#{sLevelUpMsg}", MWGui::ShowInDialogueMode_Never);
} }
getSkill(skillIndex).setBase(base); getSkill(skill->mId).setBase(base);
if (!preserveProgress) if (!preserveProgress)
getSkill(skillIndex).setProgress(0); getSkill(skill->mId).setProgress(0);
} }
int MWMechanics::NpcStats::getLevelProgress() const int MWMechanics::NpcStats::getLevelProgress() const
@ -388,9 +389,10 @@ bool MWMechanics::NpcStats::hasSkillsForRank(const ESM::RefId& factionId, int ra
std::vector<int> skills; std::vector<int> skills;
for (int id : faction.mData.mSkills) for (int index : faction.mData.mSkills)
{ {
if (id != -1) ESM::RefId id = ESM::Skill::indexToRefId(index);
if (!id.empty())
skills.push_back(static_cast<int>(getSkill(id).getBase())); skills.push_back(static_cast<int>(getSkill(id).getBase()));
} }
@ -470,8 +472,12 @@ void MWMechanics::NpcStats::writeState(ESM::NpcStats& state) const
state.mDisposition = mDisposition; state.mDisposition = mDisposition;
for (size_t i = 0; i < state.mSkills.size(); ++i) for (const auto& [id, value] : mSkills)
mSkill[i].writeState(state.mSkills[i]); {
// TODO extend format
auto index = id.getIf<ESM::IndexRefId>()->getValue();
value.writeState(state.mSkills[index]);
}
state.mIsWerewolf = mIsWerewolf; state.mIsWerewolf = mIsWerewolf;
@ -524,7 +530,11 @@ void MWMechanics::NpcStats::readState(const ESM::NpcStats& state)
mDisposition = state.mDisposition; mDisposition = state.mDisposition;
for (size_t i = 0; i < state.mSkills.size(); ++i) for (size_t i = 0; i < state.mSkills.size(); ++i)
mSkill[i].readState(state.mSkills[i]); {
// TODO extend format
ESM::RefId id = ESM::Skill::indexToRefId(i);
mSkills[id].readState(state.mSkills[i]);
}
mIsWerewolf = state.mIsWerewolf; mIsWerewolf = state.mIsWerewolf;

View file

@ -3,6 +3,7 @@
#include "creaturestats.hpp" #include "creaturestats.hpp"
#include <components/esm/refid.hpp> #include <components/esm/refid.hpp>
#include <components/esm3/loadskil.hpp>
#include <map> #include <map>
#include <set> #include <set>
#include <string> #include <string>
@ -21,7 +22,7 @@ namespace MWMechanics
class NpcStats : public CreatureStats class NpcStats : public CreatureStats
{ {
int mDisposition; int mDisposition;
SkillValue mSkill[ESM::Skill::Length]; // SkillValue.mProgress used by the player only std::map<ESM::RefId, SkillValue> mSkills; // SkillValue.mProgress used by the player only
int mReputation; int mReputation;
int mCrimeId; int mCrimeId;
@ -58,9 +59,9 @@ namespace MWMechanics
int getCrimeId() const; int getCrimeId() const;
void setCrimeId(int id); void setCrimeId(int id);
const SkillValue& getSkill(int index) const; const SkillValue& getSkill(ESM::RefId id) const;
SkillValue& getSkill(int index); SkillValue& getSkill(ESM::RefId id);
void setSkill(int index, const SkillValue& value); void setSkill(ESM::RefId id, const SkillValue& value);
int getFactionRank(const ESM::RefId& faction) const; int getFactionRank(const ESM::RefId& faction) const;
const std::map<ESM::RefId, int>& getFactionRanks() const; const std::map<ESM::RefId, int>& getFactionRanks() const;
@ -79,12 +80,12 @@ namespace MWMechanics
bool isInFaction(const ESM::RefId& faction) const; bool isInFaction(const ESM::RefId& faction) const;
float getSkillProgressRequirement(int skillIndex, const ESM::Class& class_) const; float getSkillProgressRequirement(ESM::RefId id, const ESM::Class& class_) const;
void useSkill(int skillIndex, const ESM::Class& class_, int usageType = -1, float extraFactor = 1.f); void useSkill(ESM::RefId id, const ESM::Class& class_, int usageType = -1, float extraFactor = 1.f);
///< Increase skill by usage. ///< Increase skill by usage.
void increaseSkill(int skillIndex, const ESM::Class& class_, bool preserveProgress, bool readBook = false); void increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook = false);
int getLevelProgress() const; int getLevelProgress() const;
@ -133,6 +134,8 @@ namespace MWMechanics
void readState(const ESM::CreatureStats& state); void readState(const ESM::CreatureStats& state);
void readState(const ESM::NpcStats& state); void readState(const ESM::NpcStats& state);
const std::map<ESM::RefId, SkillValue>& getSkills() const { return mSkills; }
}; };
} }

View file

@ -105,7 +105,7 @@ namespace
void damageSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) void damageSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{ {
auto& npcStats = target.getClass().getNpcStats(target); auto& npcStats = target.getClass().getNpcStats(target);
auto& skill = npcStats.getSkill(effect.mArg); auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg));
if (effect.mEffectId == ESM::MagicEffect::DamageSkill) if (effect.mEffectId == ESM::MagicEffect::DamageSkill)
magnitude = std::min(skill.getModified(), magnitude); magnitude = std::min(skill.getModified(), magnitude);
skill.damage(magnitude); skill.damage(magnitude);
@ -114,14 +114,14 @@ namespace
void restoreSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) void restoreSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{ {
auto& npcStats = target.getClass().getNpcStats(target); auto& npcStats = target.getClass().getNpcStats(target);
auto& skill = npcStats.getSkill(effect.mArg); auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg));
skill.restore(magnitude); skill.restore(magnitude);
} }
void fortifySkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) void fortifySkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{ {
auto& npcStats = target.getClass().getNpcStats(target); auto& npcStats = target.getClass().getNpcStats(target);
auto& skill = npcStats.getSkill(effect.mArg); auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg));
skill.setModifier(skill.getModifier() + magnitude); skill.setModifier(skill.getModifier() + magnitude);
} }
@ -668,7 +668,7 @@ namespace MWMechanics
if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
{ {
auto& npcStats = target.getClass().getNpcStats(target); auto& npcStats = target.getClass().getNpcStats(target);
SkillValue& skill = npcStats.getSkill(effect.mArg); SkillValue& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg));
// Damage Skill abilities reduce base skill :todd: // Damage Skill abilities reduce base skill :todd:
skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f)); skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f));
} }
@ -760,7 +760,7 @@ namespace MWMechanics
{ {
// Abilities affect base stats, but not for drain // Abilities affect base stats, but not for drain
auto& npcStats = target.getClass().getNpcStats(target); auto& npcStats = target.getClass().getNpcStats(target);
auto& skill = npcStats.getSkill(effect.mArg); auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg));
skill.setBase(skill.getBase() + effect.mMagnitude); skill.setBase(skill.getBase() + effect.mMagnitude);
} }
else else
@ -1218,7 +1218,7 @@ namespace MWMechanics
if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
{ {
auto& npcStats = target.getClass().getNpcStats(target); auto& npcStats = target.getClass().getNpcStats(target);
auto& skill = npcStats.getSkill(effect.mArg); auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg));
skill.setBase(skill.getBase() - effect.mMagnitude); skill.setBase(skill.getBase() - effect.mMagnitude);
} }
else else

View file

@ -565,7 +565,7 @@ namespace MWMechanics
case ESM::MagicEffect::DrainSkill: case ESM::MagicEffect::DrainSkill:
if (enemy.isEmpty() || !enemy.getClass().isNpc()) if (enemy.isEmpty() || !enemy.getClass().isNpc())
return 0.f; return 0.f;
if (enemy.getClass().getSkill(enemy, effect.mSkill) <= 0) if (enemy.getClass().getSkill(enemy, ESM::Skill::indexToRefId(effect.mSkill)) <= 0)
return 0.f; return 0.f;
break; break;
@ -600,7 +600,7 @@ namespace MWMechanics
&& (e != ESM::MagicEffect::BoundLongbow || effect.mEffectID == e && (e != ESM::MagicEffect::BoundLongbow || effect.mEffectID == e
|| rateAmmo(actor, enemy, getWeaponType(ESM::Weapon::MarksmanBow)->mAmmoType) <= 0.f)) || rateAmmo(actor, enemy, getWeaponType(ESM::Weapon::MarksmanBow)->mAmmoType) <= 0.f))
return 0.f; return 0.f;
ESM::Skill::SkillEnum skill = ESM::Skill::ShortBlade; ESM::RefId skill = ESM::Skill::ShortBlade;
if (effect.mEffectID == ESM::MagicEffect::BoundLongsword) if (effect.mEffectID == ESM::MagicEffect::BoundLongsword)
skill = ESM::Skill::LongBlade; skill = ESM::Skill::LongBlade;
else if (effect.mEffectID == ESM::MagicEffect::BoundMace) else if (effect.mEffectID == ESM::MagicEffect::BoundMace)

View file

@ -15,9 +15,9 @@
namespace MWMechanics namespace MWMechanics
{ {
ESM::Skill::SkillEnum spellSchoolToSkill(int school) ESM::RefId spellSchoolToSkill(int school)
{ {
static const std::array<ESM::Skill::SkillEnum, 6> schoolSkillArray{ static const std::array<ESM::RefId, 6> schoolSkillArray{
ESM::Skill::Alteration, ESM::Skill::Alteration,
ESM::Skill::Conjuration, ESM::Skill::Conjuration,
ESM::Skill::Destruction, ESM::Skill::Destruction,

View file

@ -17,7 +17,7 @@ namespace MWWorld
namespace MWMechanics namespace MWMechanics
{ {
ESM::Skill::SkillEnum spellSchoolToSkill(int school); ESM::RefId spellSchoolToSkill(int school);
enum class EffectCostMethod enum class EffectCostMethod
{ {

View file

@ -120,8 +120,8 @@ namespace MWMechanics
int value = 50.f; int value = 50.f;
if (actor.getClass().isNpc()) if (actor.getClass().isNpc())
{ {
int skill = item.getClass().getEquipmentSkill(item); ESM::RefId skill = item.getClass().getEquipmentSkill(item);
if (skill != -1) if (!skill.empty())
value = actor.getClass().getSkill(actor, skill); value = actor.getClass().getSkill(actor, skill);
} }
else else

View file

@ -392,7 +392,7 @@ namespace MWScript
return; return;
} }
int skill = it->getClass().getEquipmentSkill(*it); ESM::RefId skill = it->getClass().getEquipmentSkill(*it);
if (skill == ESM::Skill::HeavyArmor) if (skill == ESM::Skill::HeavyArmor)
runtime.push(2); runtime.push(2);
else if (skill == ESM::Skill::MediumArmor) else if (skill == ESM::Skill::MediumArmor)

View file

@ -345,11 +345,11 @@ namespace MWScript
template <class R> template <class R>
class OpGetSkill : public Interpreter::Opcode0 class OpGetSkill : public Interpreter::Opcode0
{ {
int mIndex; ESM::RefId mId;
public: public:
OpGetSkill(int index) OpGetSkill(ESM::RefId id)
: mIndex(index) : mId(id)
{ {
} }
@ -357,7 +357,7 @@ namespace MWScript
{ {
MWWorld::Ptr ptr = R()(runtime); MWWorld::Ptr ptr = R()(runtime);
Interpreter::Type_Float value = ptr.getClass().getSkill(ptr, mIndex); Interpreter::Type_Float value = ptr.getClass().getSkill(ptr, mId);
runtime.push(value); runtime.push(value);
} }
@ -366,11 +366,11 @@ namespace MWScript
template <class R> template <class R>
class OpSetSkill : public Interpreter::Opcode0 class OpSetSkill : public Interpreter::Opcode0
{ {
int mIndex; ESM::RefId mId;
public: public:
OpSetSkill(int index) OpSetSkill(ESM::RefId id)
: mIndex(index) : mId(id)
{ {
} }
@ -383,18 +383,18 @@ namespace MWScript
MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats(ptr); MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats(ptr);
stats.getSkill(mIndex).setBase(value, true); stats.getSkill(mId).setBase(value, true);
} }
}; };
template <class R> template <class R>
class OpModSkill : public Interpreter::Opcode0 class OpModSkill : public Interpreter::Opcode0
{ {
int mIndex; ESM::RefId mId;
public: public:
OpModSkill(int index) OpModSkill(ESM::RefId id)
: mIndex(index) : mId(id)
{ {
} }
@ -405,7 +405,7 @@ namespace MWScript
Interpreter::Type_Float value = runtime[0].mFloat; Interpreter::Type_Float value = runtime[0].mFloat;
runtime.pop(); runtime.pop();
MWMechanics::SkillValue& skill = ptr.getClass().getNpcStats(ptr).getSkill(mIndex); MWMechanics::SkillValue& skill = ptr.getClass().getNpcStats(ptr).getSkill(mId);
modStat(skill, value); modStat(skill, value);
} }
}; };
@ -1364,14 +1364,15 @@ namespace MWScript
for (int i = 0; i < Compiler::Stats::numberOfSkills; ++i) for (int i = 0; i < Compiler::Stats::numberOfSkills; ++i)
{ {
interpreter.installSegment5<OpGetSkill<ImplicitRef>>(Compiler::Stats::opcodeGetSkill + i, i); ESM::RefId id = ESM::Skill::indexToRefId(i);
interpreter.installSegment5<OpGetSkill<ExplicitRef>>(Compiler::Stats::opcodeGetSkillExplicit + i, i); interpreter.installSegment5<OpGetSkill<ImplicitRef>>(Compiler::Stats::opcodeGetSkill + i, id);
interpreter.installSegment5<OpGetSkill<ExplicitRef>>(Compiler::Stats::opcodeGetSkillExplicit + i, id);
interpreter.installSegment5<OpSetSkill<ImplicitRef>>(Compiler::Stats::opcodeSetSkill + i, i); interpreter.installSegment5<OpSetSkill<ImplicitRef>>(Compiler::Stats::opcodeSetSkill + i, id);
interpreter.installSegment5<OpSetSkill<ExplicitRef>>(Compiler::Stats::opcodeSetSkillExplicit + i, i); interpreter.installSegment5<OpSetSkill<ExplicitRef>>(Compiler::Stats::opcodeSetSkillExplicit + i, id);
interpreter.installSegment5<OpModSkill<ImplicitRef>>(Compiler::Stats::opcodeModSkill + i, i); interpreter.installSegment5<OpModSkill<ImplicitRef>>(Compiler::Stats::opcodeModSkill + i, id);
interpreter.installSegment5<OpModSkill<ExplicitRef>>(Compiler::Stats::opcodeModSkillExplicit + i, i); interpreter.installSegment5<OpModSkill<ExplicitRef>>(Compiler::Stats::opcodeModSkillExplicit + i, id);
} }
interpreter.installSegment5<OpGetPCCrimeLevel>(Compiler::Stats::opcodeGetPCCrimeLevel); interpreter.installSegment5<OpGetPCCrimeLevel>(Compiler::Stats::opcodeGetPCCrimeLevel);

View file

@ -16,19 +16,4 @@ namespace MWWorld
{ {
actor.getClass().consume(getTarget(), actor); actor.getClass().consume(getTarget(), actor);
} }
ActionApplyWithSkill::ActionApplyWithSkill(const Ptr& object, const ESM::RefId& id, int skillIndex, int usageType)
: Action(false, object)
, mId(id)
, mSkillIndex(skillIndex)
, mUsageType(usageType)
{
}
void ActionApplyWithSkill::executeImp(const Ptr& actor)
{
bool consumed = actor.getClass().consume(getTarget(), actor);
if (consumed && mUsageType != -1 && actor == MWMechanics::getPlayer())
actor.getClass().skillUsageSucceeded(actor, mSkillIndex, mUsageType);
}
} }

View file

@ -16,18 +16,6 @@ namespace MWWorld
public: public:
ActionApply(const Ptr& object, const ESM::RefId& id); ActionApply(const Ptr& object, const ESM::RefId& id);
}; };
class ActionApplyWithSkill : public Action
{
ESM::RefId mId;
int mSkillIndex;
int mUsageType;
void executeImp(const Ptr& actor) override;
public:
ActionApplyWithSkill(const Ptr& object, const ESM::RefId& id, int skillIndex, int usageType);
};
} }
#endif #endif

View file

@ -2,6 +2,7 @@
#include <components/esm3/loadbook.hpp> #include <components/esm3/loadbook.hpp>
#include <components/esm3/loadclas.hpp> #include <components/esm3/loadclas.hpp>
#include <components/esm3/loadskil.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -46,15 +47,15 @@ namespace MWWorld
MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor); MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor);
// Skill gain from books // Skill gain from books
if (ref->mBase->mData.mSkillId >= 0 && ref->mBase->mData.mSkillId < ESM::Skill::Length ESM::RefId skill = ESM::Skill::indexToRefId(ref->mBase->mData.mSkillId);
&& !npcStats.hasBeenUsed(ref->mBase->mId)) if (!skill.empty() && !npcStats.hasBeenUsed(ref->mBase->mId))
{ {
MWWorld::LiveCellRef<ESM::NPC>* playerRef = actor.get<ESM::NPC>(); MWWorld::LiveCellRef<ESM::NPC>* playerRef = actor.get<ESM::NPC>();
const ESM::Class* class_ const ESM::Class* class_
= MWBase::Environment::get().getESMStore()->get<ESM::Class>().find(playerRef->mBase->mClass); = MWBase::Environment::get().getESMStore()->get<ESM::Class>().find(playerRef->mBase->mClass);
npcStats.increaseSkill(ref->mBase->mData.mSkillId, *class_, true, true); npcStats.increaseSkill(skill, *class_, true, true);
npcStats.flagAsUsed(ref->mBase->mId); npcStats.flagAsUsed(ref->mBase->mId);
} }

View file

@ -52,7 +52,7 @@ namespace MWWorld
return false; return false;
} }
void Class::skillUsageSucceeded(const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor) const void Class::skillUsageSucceeded(const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor) const
{ {
throw std::runtime_error("class does not represent an actor"); throw std::runtime_error("class does not represent an actor");
} }
@ -209,9 +209,9 @@ namespace MWWorld
return std::make_pair(std::vector<int>(), false); return std::make_pair(std::vector<int>(), false);
} }
int Class::getEquipmentSkill(const ConstPtr& ptr) const ESM::RefId Class::getEquipmentSkill(const ConstPtr& ptr) const
{ {
return -1; return {};
} }
int Class::getValue(const ConstPtr& ptr) const int Class::getValue(const ConstPtr& ptr) const
@ -438,7 +438,7 @@ namespace MWWorld
return canSwim(ptr) || canWalk(ptr) || canFly(ptr); return canSwim(ptr) || canWalk(ptr) || canFly(ptr);
} }
float Class::getSkill(const MWWorld::Ptr& ptr, int skill) const float Class::getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const
{ {
throw std::runtime_error("class does not support skills"); throw std::runtime_error("class does not support skills");
} }

View file

@ -14,6 +14,7 @@
#include "../mwmechanics/aisetting.hpp" #include "../mwmechanics/aisetting.hpp"
#include <components/esm/refid.hpp> #include <components/esm/refid.hpp>
#include <components/esm3/loadskil.hpp>
namespace ESM namespace ESM
{ {
@ -208,10 +209,9 @@ namespace MWWorld
/// ///
/// Default implementation: return (empty vector, false). /// Default implementation: return (empty vector, false).
virtual int getEquipmentSkill(const ConstPtr& ptr) const; virtual ESM::RefId getEquipmentSkill(const ConstPtr& ptr) const;
/// Return the index of the skill this item corresponds to when equipped or -1, if there is /// Return the index of the skill this item corresponds to when equipped.
/// no such skill. /// (default implementation: return empty ref id)
/// (default implementation: return -1)
virtual int getValue(const ConstPtr& ptr) const; virtual int getValue(const ConstPtr& ptr) const;
///< Return trade value of the object. Throws an exception, if the object can't be traded. ///< Return trade value of the object. Throws an exception, if the object can't be traded.
@ -234,7 +234,7 @@ namespace MWWorld
///< Consume an item, e. g. a potion. ///< Consume an item, e. g. a potion.
virtual void skillUsageSucceeded( virtual void skillUsageSucceeded(
const MWWorld::Ptr& ptr, int skill, int usageType, float extraFactor = 1.f) const; const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor = 1.f) const;
///< Inform actor \a ptr that a skill use has succeeded. ///< Inform actor \a ptr that a skill use has succeeded.
/// ///
/// (default implementations: throws an exception) /// (default implementations: throws an exception)
@ -340,7 +340,7 @@ namespace MWWorld
bool isPureLandCreature(const MWWorld::Ptr& ptr) const; bool isPureLandCreature(const MWWorld::Ptr& ptr) const;
bool isMobile(const MWWorld::Ptr& ptr) const; bool isMobile(const MWWorld::Ptr& ptr) const;
virtual float getSkill(const MWWorld::Ptr& ptr, int skill) const; virtual float getSkill(const MWWorld::Ptr& ptr, ESM::RefId id) const;
virtual void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const; virtual void readAdditionalState(const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const;
///< Read additional state from \a state into \a ptr. ///< Read additional state from \a state into \a ptr.

View file

@ -236,7 +236,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_)
return; return;
} }
static const ESM::Skill::SkillEnum weaponSkills[] = { static const ESM::RefId weaponSkills[] = {
ESM::Skill::LongBlade, ESM::Skill::LongBlade,
ESM::Skill::Axe, ESM::Skill::Axe,
ESM::Skill::Spear, ESM::Skill::Spear,
@ -285,7 +285,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_)
for (int j = 0; j < static_cast<int>(weaponSkillsLength); ++j) for (int j = 0; j < static_cast<int>(weaponSkillsLength); ++j)
{ {
float skillValue = mActor.getClass().getSkill(mActor, static_cast<int>(weaponSkills[j])); float skillValue = mActor.getClass().getSkill(mActor, weaponSkills[j]);
if (skillValue > max && !weaponSkillVisited[j]) if (skillValue > max && !weaponSkillVisited[j])
{ {
max = skillValue; max = skillValue;

View file

@ -221,7 +221,7 @@ namespace MWWorld
creatureStats.mAiSettings[i].mMod = 0.f; creatureStats.mAiSettings[i].mMod = 0.f;
if (npcStats) if (npcStats)
{ {
for (std::size_t i = 0; i < ESM::Skill::Length; ++i) for (std::size_t i = 0; i < npcStats->mSkills.size(); ++i)
npcStats->mSkills[i].mMod = 0.f; npcStats->mSkills[i].mMod = 0.f;
} }
} }

View file

@ -59,23 +59,23 @@ namespace MWWorld
{ {
MWMechanics::NpcStats& stats = getPlayer().getClass().getNpcStats(getPlayer()); MWMechanics::NpcStats& stats = getPlayer().getClass().getNpcStats(getPlayer());
for (int i = 0; i < ESM::Skill::Length; ++i) for (size_t i = 0; i < mSaveSkills.size(); ++i)
mSaveSkills[i] = stats.getSkill(i).getModified(); mSaveSkills[i] = stats.getSkill(ESM::Skill::indexToRefId(i)).getModified();
for (int i = 0; i < ESM::Attribute::Length; ++i) for (int i = 0; i < ESM::Attribute::Length; ++i)
mSaveAttributes[i] = stats.getAttribute(i).getModified(); mSaveAttributes[i] = stats.getAttribute(i).getModified();
} }
void Player::restoreStats() void Player::restoreStats()
{ {
const MWWorld::Store<ESM::GameSetting>& gmst const auto& store = MWBase::Environment::get().getESMStore();
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>(); const MWWorld::Store<ESM::GameSetting>& gmst = store->get<ESM::GameSetting>();
MWMechanics::CreatureStats& creatureStats = getPlayer().getClass().getCreatureStats(getPlayer()); MWMechanics::CreatureStats& creatureStats = getPlayer().getClass().getCreatureStats(getPlayer());
MWMechanics::NpcStats& npcStats = getPlayer().getClass().getNpcStats(getPlayer()); MWMechanics::NpcStats& npcStats = getPlayer().getClass().getNpcStats(getPlayer());
MWMechanics::DynamicStat<float> health = creatureStats.getDynamic(0); MWMechanics::DynamicStat<float> health = creatureStats.getDynamic(0);
creatureStats.setHealth(health.getBase() / gmst.find("fWereWolfHealth")->mValue.getFloat()); creatureStats.setHealth(health.getBase() / gmst.find("fWereWolfHealth")->mValue.getFloat());
for (int i = 0; i < ESM::Skill::Length; ++i) for (size_t i = 0; i < mSaveSkills.size(); ++i)
{ {
auto& skill = npcStats.getSkill(i); auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(i));
skill.restore(skill.getDamage()); skill.restore(skill.getDamage());
skill.setModifier(mSaveSkills[i] - skill.getBase()); skill.setModifier(mSaveSkills[i] - skill.getBase());
} }
@ -103,13 +103,13 @@ namespace MWWorld
npcStats.setAttribute(attribute.mId, value); npcStats.setAttribute(attribute.mId, value);
} }
for (const auto& [_, skill] : store->get<ESM::Skill>()) for (const auto& skill : store->get<ESM::Skill>())
{ {
// Acrobatics is set separately for some reason. // Acrobatics is set separately for some reason.
if (skill.mIndex == ESM::Skill::Acrobatics) if (skill.mId == ESM::Skill::Acrobatics)
continue; continue;
MWMechanics::SkillValue& value = npcStats.getSkill(skill.mIndex); MWMechanics::SkillValue& value = npcStats.getSkill(skill.mId);
value.setModifier(skill.mWerewolfValue - value.getModified()); value.setModifier(skill.mWerewolfValue - value.getModified());
} }
} }
@ -251,10 +251,7 @@ namespace MWWorld
mPreviousItems.clear(); mPreviousItems.clear();
mLastKnownExteriorPosition = osg::Vec3f(0, 0, 0); mLastKnownExteriorPosition = osg::Vec3f(0, 0, 0);
for (int i = 0; i < ESM::Skill::Length; ++i) mSaveSkills.fill(0.f);
{
mSaveSkills[i] = 0.f;
}
for (int i = 0; i < ESM::Attribute::Length; ++i) for (int i = 0; i < ESM::Attribute::Length; ++i)
{ {
@ -296,7 +293,7 @@ namespace MWWorld
for (int i = 0; i < ESM::Attribute::Length; ++i) for (int i = 0; i < ESM::Attribute::Length; ++i)
player.mSaveAttributes[i] = mSaveAttributes[i]; player.mSaveAttributes[i] = mSaveAttributes[i];
for (int i = 0; i < ESM::Skill::Length; ++i) for (size_t i = 0; i < mSaveSkills.size(); ++i)
player.mSaveSkills[i] = mSaveSkills[i]; player.mSaveSkills[i] = mSaveSkills[i];
player.mPreviousItems = mPreviousItems; player.mPreviousItems = mPreviousItems;
@ -334,7 +331,7 @@ namespace MWWorld
for (int i = 0; i < ESM::Attribute::Length; ++i) for (int i = 0; i < ESM::Attribute::Length; ++i)
mSaveAttributes[i] = player.mSaveAttributes[i]; mSaveAttributes[i] = player.mSaveAttributes[i];
for (int i = 0; i < ESM::Skill::Length; ++i) for (size_t i = 0; i < mSaveSkills.size(); ++i)
mSaveSkills[i] = player.mSaveSkills[i]; mSaveSkills[i] = player.mSaveSkills[i];
if (player.mObject.mNpcStats.mIsWerewolf) if (player.mObject.mNpcStats.mIsWerewolf)

View file

@ -1,6 +1,7 @@
#ifndef GAME_MWWORLD_PLAYER_H #ifndef GAME_MWWORLD_PLAYER_H
#define GAME_MWWORLD_PLAYER_H #define GAME_MWWORLD_PLAYER_H
#include <array>
#include <map> #include <map>
#include "../mwworld/livecellref.hpp" #include "../mwworld/livecellref.hpp"
@ -50,7 +51,7 @@ namespace MWWorld
PreviousItems mPreviousItems; PreviousItems mPreviousItems;
// Saved stats prior to becoming a werewolf // Saved stats prior to becoming a werewolf
float mSaveSkills[ESM::Skill::Length]; std::array<float, ESM::Skill::Length> mSaveSkills;
float mSaveAttributes[ESM::Attribute::Length]; float mSaveAttributes[ESM::Attribute::Length];
bool mJumping; bool mJumping;

View file

@ -114,7 +114,6 @@ namespace MWWorld
// Need to instantiate these before they're used // Need to instantiate these before they're used
template class IndexedStore<ESM::MagicEffect>; template class IndexedStore<ESM::MagicEffect>;
template class IndexedStore<ESM::Skill>;
template <class T, class Id> template <class T, class Id>
TypedDynamicStore<T, Id>::TypedDynamicStore() TypedDynamicStore<T, Id>::TypedDynamicStore()
@ -170,11 +169,19 @@ namespace MWWorld
{ {
if constexpr (std::is_same_v<Id, ESM::RefId>) if constexpr (std::is_same_v<Id, ESM::RefId>)
{ {
std::vector<const T*> results; if (prefix.empty())
std::copy_if(mShared.begin(), mShared.end(), std::back_inserter(results), {
[prefix](const T* item) { return item->mId.startsWith(prefix); }); if (!mShared.empty())
if (!results.empty()) return mShared[Misc::Rng::rollDice(mShared.size(), prng)];
return results[Misc::Rng::rollDice(results.size(), prng)]; }
else
{
std::vector<const T*> results;
std::copy_if(mShared.begin(), mShared.end(), std::back_inserter(results),
[prefix](const T* item) { return item->mId.startsWith(prefix); });
if (!results.empty())
return results[Misc::Rng::rollDice(results.size(), prng)];
}
return nullptr; return nullptr;
} }
else else
@ -916,8 +923,6 @@ namespace MWWorld
// Skill // Skill
//========================================================================= //=========================================================================
Store<ESM::Skill>::Store() {}
void Store<ESM::Skill>::setUp(const MWWorld::Store<ESM::GameSetting>& settings) void Store<ESM::Skill>::setUp(const MWWorld::Store<ESM::GameSetting>& settings)
{ {
constexpr std::string_view skillValues[ESM::Skill::Length][3] = { constexpr std::string_view skillValues[ESM::Skill::Length][3] = {
@ -950,15 +955,13 @@ namespace MWWorld
{ "sSkillSpeechcraft", "icons\\k\\stealth_speechcraft.dds", "fWerewolfSpeechcraft" }, { "sSkillSpeechcraft", "icons\\k\\stealth_speechcraft.dds", "fWerewolfSpeechcraft" },
{ "sSkillHandtohand", "icons\\k\\stealth_handtohand.dds", "fWerewolfHandtohand" }, { "sSkillHandtohand", "icons\\k\\stealth_handtohand.dds", "fWerewolfHandtohand" },
}; };
for (int i = 0; i < ESM::Skill::Length; ++i) for (ESM::Skill* skill : mShared)
{ {
auto found = mStatic.find(i); if (skill->mIndex >= 0)
if (found != mStatic.end())
{ {
ESM::Skill& skill = found->second; skill->mName = getGMSTString(settings, skillValues[skill->mIndex][0]);
skill.mName = getGMSTString(settings, skillValues[i][0]); skill->mIcon = skillValues[skill->mIndex][1];
skill.mIcon = skillValues[i][1]; skill->mWerewolfValue = getGMSTFloat(settings, skillValues[skill->mIndex][2]);
skill.mWerewolfValue = getGMSTFloat(settings, skillValues[i][2]);
} }
} }
} }
@ -1363,7 +1366,7 @@ template class MWWorld::TypedDynamicStore<ESM::Race>;
template class MWWorld::TypedDynamicStore<ESM::Region>; template class MWWorld::TypedDynamicStore<ESM::Region>;
template class MWWorld::TypedDynamicStore<ESM::Repair>; template class MWWorld::TypedDynamicStore<ESM::Repair>;
template class MWWorld::TypedDynamicStore<ESM::Script>; template class MWWorld::TypedDynamicStore<ESM::Script>;
// template class MWWorld::Store<ESM::Skill>; template class MWWorld::TypedDynamicStore<ESM::Skill>;
template class MWWorld::TypedDynamicStore<ESM::Sound>; template class MWWorld::TypedDynamicStore<ESM::Sound>;
template class MWWorld::TypedDynamicStore<ESM::SoundGenerator>; template class MWWorld::TypedDynamicStore<ESM::SoundGenerator>;
template class MWWorld::TypedDynamicStore<ESM::Spell>; template class MWWorld::TypedDynamicStore<ESM::Spell>;

View file

@ -17,6 +17,7 @@
#include <components/esm3/loadgmst.hpp> #include <components/esm3/loadgmst.hpp>
#include <components/esm3/loadland.hpp> #include <components/esm3/loadland.hpp>
#include <components/esm3/loadpgrd.hpp> #include <components/esm3/loadpgrd.hpp>
#include <components/esm3/loadskil.hpp>
#include <components/esm4/loadcell.hpp> #include <components/esm4/loadcell.hpp>
#include <components/esm4/loadland.hpp> #include <components/esm4/loadland.hpp>
#include <components/esm4/loadrefr.hpp> #include <components/esm4/loadrefr.hpp>
@ -30,7 +31,6 @@ namespace ESM
struct Attribute; struct Attribute;
struct LandTexture; struct LandTexture;
struct MagicEffect; struct MagicEffect;
struct Skill;
struct WeaponType; struct WeaponType;
class ESMReader; class ESMReader;
class ESMWriter; class ESMWriter;
@ -478,10 +478,12 @@ namespace MWWorld
}; };
template <> template <>
class Store<ESM::Skill> : public IndexedStore<ESM::Skill> class Store<ESM::Skill> : public TypedDynamicStore<ESM::Skill>
{ {
using TypedDynamicStore<ESM::Skill>::setUp;
public: public:
Store(); Store() = default;
void setUp(const MWWorld::Store<ESM::GameSetting>& settings); void setUp(const MWWorld::Store<ESM::GameSetting>& settings);
}; };

View file

@ -496,7 +496,7 @@ namespace
const RecordType* result = nullptr; const RecordType* result = nullptr;
if constexpr (std::is_same_v<RecordType, ESM::LandTexture>) if constexpr (std::is_same_v<RecordType, ESM::LandTexture>)
result = esmStore.get<RecordType>().search(index, 0); result = esmStore.get<RecordType>().search(index, 0);
else if constexpr (ESM::hasIndex<RecordType>) else if constexpr (ESM::hasIndex<RecordType> && !std::is_same_v<RecordType, ESM::Skill>)
result = esmStore.get<RecordType>().search(index); result = esmStore.get<RecordType>().search(index);
else else
result = esmStore.get<RecordType>().search(refId); result = esmStore.get<RecordType>().search(refId);

View file

@ -37,11 +37,11 @@ namespace ESM
"Handtohand", "Handtohand",
}; };
Skill::SkillEnum Skill::stringToSkillId(std::string_view skill) int Skill::stringToSkillId(std::string_view skill)
{ {
for (int id = 0; id < Skill::Length; ++id) for (int id = 0; id < Skill::Length; ++id)
if (Misc::StringUtils::ciEqual(sSkillNames[id], skill)) if (Misc::StringUtils::ciEqual(sSkillNames[id], skill))
return Skill::SkillEnum(id); return id;
throw std::logic_error("No such skill: " + std::string(skill)); throw std::logic_error("No such skill: " + std::string(skill));
} }
@ -75,6 +75,8 @@ namespace ESM
} }
if (!hasIndex) if (!hasIndex)
esm.fail("Missing INDX"); esm.fail("Missing INDX");
else if (mIndex < 0 || mIndex >= Length)
esm.fail("Invalid INDX");
if (!hasData) if (!hasData)
esm.fail("Missing SKDT"); esm.fail("Missing SKDT");
@ -101,7 +103,7 @@ namespace ESM
RefId Skill::indexToRefId(int index) RefId Skill::indexToRefId(int index)
{ {
if (index == -1) if (index < 0 || index >= Length)
return RefId(); return RefId();
return RefId::index(sRecordId, static_cast<std::uint32_t>(index)); return RefId::index(sRecordId, static_cast<std::uint32_t>(index));
} }

View file

@ -41,47 +41,44 @@ namespace ESM
// Skill index. Skils don't have an id ("NAME") like most records, // Skill index. Skils don't have an id ("NAME") like most records,
// they only have a numerical index that matches one of the // they only have a numerical index that matches one of the
// hard-coded skills in the game. // hard-coded skills in the game.
int mIndex; int mIndex{ -1 };
std::string mDescription; std::string mDescription;
std::string mName; std::string mName;
std::string mIcon; std::string mIcon;
float mWerewolfValue{}; float mWerewolfValue{};
enum SkillEnum static constexpr IndexRefId Block{ sRecordId, 0 };
{ static constexpr IndexRefId Armorer{ sRecordId, 1 };
Block = 0, static constexpr IndexRefId MediumArmor{ sRecordId, 2 };
Armorer = 1, static constexpr IndexRefId HeavyArmor{ sRecordId, 3 };
MediumArmor = 2, static constexpr IndexRefId BluntWeapon{ sRecordId, 4 };
HeavyArmor = 3, static constexpr IndexRefId LongBlade{ sRecordId, 5 };
BluntWeapon = 4, static constexpr IndexRefId Axe{ sRecordId, 6 };
LongBlade = 5, static constexpr IndexRefId Spear{ sRecordId, 7 };
Axe = 6, static constexpr IndexRefId Athletics{ sRecordId, 8 };
Spear = 7, static constexpr IndexRefId Enchant{ sRecordId, 9 };
Athletics = 8, static constexpr IndexRefId Destruction{ sRecordId, 10 };
Enchant = 9, static constexpr IndexRefId Alteration{ sRecordId, 11 };
Destruction = 10, static constexpr IndexRefId Illusion{ sRecordId, 12 };
Alteration = 11, static constexpr IndexRefId Conjuration{ sRecordId, 13 };
Illusion = 12, static constexpr IndexRefId Mysticism{ sRecordId, 14 };
Conjuration = 13, static constexpr IndexRefId Restoration{ sRecordId, 15 };
Mysticism = 14, static constexpr IndexRefId Alchemy{ sRecordId, 16 };
Restoration = 15, static constexpr IndexRefId Unarmored{ sRecordId, 17 };
Alchemy = 16, static constexpr IndexRefId Security{ sRecordId, 18 };
Unarmored = 17, static constexpr IndexRefId Sneak{ sRecordId, 19 };
Security = 18, static constexpr IndexRefId Acrobatics{ sRecordId, 20 };
Sneak = 19, static constexpr IndexRefId LightArmor{ sRecordId, 21 };
Acrobatics = 20, static constexpr IndexRefId ShortBlade{ sRecordId, 22 };
LightArmor = 21, static constexpr IndexRefId Marksman{ sRecordId, 23 };
ShortBlade = 22, static constexpr IndexRefId Mercantile{ sRecordId, 24 };
Marksman = 23, static constexpr IndexRefId Speechcraft{ sRecordId, 25 };
Mercantile = 24, static constexpr IndexRefId HandToHand{ sRecordId, 26 };
Speechcraft = 25, static constexpr int Length = 27;
HandToHand = 26,
Length
};
static const std::string sSkillNames[Length]; static const std::string sSkillNames[Length];
static SkillEnum stringToSkillId(std::string_view skill); static int stringToSkillId(std::string_view skill);
void load(ESMReader& esm, bool& isDeleted); void load(ESMReader& esm, bool& isDeleted);
void save(ESMWriter& esm, bool isDeleted = false) const; void save(ESMWriter& esm, bool isDeleted = false) const;

View file

@ -109,13 +109,13 @@ namespace ESM
ESM::RefId mSoundIdUp; ESM::RefId mSoundIdUp;
std::string mAttachBone; std::string mAttachBone;
std::string mSheathingBone; std::string mSheathingBone;
Skill::SkillEnum mSkill; ESM::RefId mSkill;
Class mWeaponClass; Class mWeaponClass;
int mAmmoType; int mAmmoType;
int mFlags; int mFlags;
WeaponType(std::string shortGroup, std::string longGroup, const std::string& soundId, std::string attachBone, WeaponType(std::string shortGroup, std::string longGroup, const std::string& soundId, std::string attachBone,
std::string sheathingBone, Skill::SkillEnum skill, Class weaponClass, int ammoType, int flags) std::string sheathingBone, ESM::RefId skill, Class weaponClass, int ammoType, int flags)
: mShortGroup(std::move(shortGroup)) : mShortGroup(std::move(shortGroup))
, mLongGroup(std::move(longGroup)) , mLongGroup(std::move(longGroup))
, mSoundIdDown(ESM::RefId::stringRefId(soundId + " Down")) , mSoundIdDown(ESM::RefId::stringRefId(soundId + " Down"))

View file

@ -73,7 +73,8 @@ namespace ESM
mSaveSkills[i] = skill.mBase + skill.mMod - skill.mDamage; mSaveSkills[i] = skill.mBase + skill.mMod - skill.mDamage;
if (mObject.mNpcStats.mIsWerewolf) if (mObject.mNpcStats.mIsWerewolf)
{ {
if (i == Skill::Acrobatics) constexpr int Acrobatics = 20;
if (i == Acrobatics)
mSetWerewolfAcrobatics = mObject.mNpcStats.mSkills[i].mBase != skill.mBase; mSetWerewolfAcrobatics = mObject.mNpcStats.mSkills[i].mBase != skill.mBase;
mObject.mNpcStats.mSkills[i] = skill; mObject.mNpcStats.mSkills[i] = skill;
} }

View file

@ -14,45 +14,27 @@
<Property key="Caption" value="#{sSpecializationCombat}"/> <Property key="Caption" value="#{sSpecializationCombat}"/>
<Property key="TextAlign" value="Left Top"/> <Property key="TextAlign" value="Left Top"/>
</Widget> </Widget>
<Widget type="MWSkill" skin="MW_StatNameButton" position="0 50 154 18" name="CombatSkill0" align="Left Top"/> <Widget type="ScrollView" skin="MW_ScrollView" position="0 50 154 168" name="CombatSkills" align="Left Top">
<Widget type="MWSkill" skin="MW_StatNameButton" position="0 68 154 18" name="CombatSkill1" align="Left Top"/> <Property key="CanvasAlign" value="Left"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="0 86 154 18" name="CombatSkill2" align="Left Top"/> </Widget>
<Widget type="MWSkill" skin="MW_StatNameButton" position="0 104 154 18" name="CombatSkill3" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="0 122 154 18" name="CombatSkill4" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="0 140 154 18" name="CombatSkill5" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="0 158 154 18" name="CombatSkill6" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="0 176 154 18" name="CombatSkill7" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="0 194 154 18" name="CombatSkill8" align="Left Top"/>
<!-- Magic list --> <!-- Magic list -->
<Widget type="TextBox" skin="HeaderText" position="158 32 154 18" name="MagicLabelT" align="Left Top"> <Widget type="TextBox" skin="HeaderText" position="158 32 154 18" name="MagicLabelT" align="Left Top">
<Property key="Caption" value="#{sSpecializationMagic}"/> <Property key="Caption" value="#{sSpecializationMagic}"/>
<Property key="TextAlign" value="Left Top"/> <Property key="TextAlign" value="Left Top"/>
</Widget> </Widget>
<Widget type="MWSkill" skin="MW_StatNameButton" position="158 50 154 18" name="MagicSkill0" align="Left Top"/> <Widget type="ScrollView" skin="MW_ScrollView" position="158 50 154 168" name="MagicSkills" align="Left Top">
<Widget type="MWSkill" skin="MW_StatNameButton" position="158 68 154 18" name="MagicSkill1" align="Left Top"/> <Property key="CanvasAlign" value="Left"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="158 86 154 18" name="MagicSkill2" align="Left Top"/> </Widget>
<Widget type="MWSkill" skin="MW_StatNameButton" position="158 104 154 18" name="MagicSkill3" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="158 122 154 18" name="MagicSkill4" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="158 140 154 18" name="MagicSkill5" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="158 158 154 18" name="MagicSkill6" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="158 176 154 18" name="MagicSkill7" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="158 194 154 18" name="MagicSkill8" align="Left Top"/>
<!-- Stealth list --> <!-- Stealth list -->
<Widget type="TextBox" skin="HeaderText" position="316 32 131 18" name="StealthLabelT" align="Left Top"> <Widget type="TextBox" skin="HeaderText" position="316 32 131 18" name="StealthLabelT" align="Left Top">
<Property key="Caption" value="#{sSpecializationStealth}"/> <Property key="Caption" value="#{sSpecializationStealth}"/>
<Property key="TextAlign" value="Left Top"/> <Property key="TextAlign" value="Left Top"/>
</Widget> </Widget>
<Widget type="MWSkill" skin="MW_StatNameButton" position="316 50 154 18" name="StealthSkill0" align="Left Top"/> <Widget type="ScrollView" skin="MW_ScrollView" position="316 50 154 168" name="StealthSkills" align="Left Top">
<Widget type="MWSkill" skin="MW_StatNameButton" position="316 68 154 18" name="StealthSkill1" align="Left Top"/> <Property key="CanvasAlign" value="Left"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="316 86 154 18" name="StealthSkill2" align="Left Top"/> </Widget>
<Widget type="MWSkill" skin="MW_StatNameButton" position="316 104 154 18" name="StealthSkill3" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="316 122 154 18" name="StealthSkill4" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="316 140 154 18" name="StealthSkill5" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="316 158 154 18" name="StealthSkill6" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="316 176 154 18" name="StealthSkill7" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="316 194 154 18" name="StealthSkill8" align="Left Top"/>
<!-- Dialog buttons --> <!-- Dialog buttons -->
<Widget type="HBox" position="0 218 457 28"> <Widget type="HBox" position="0 218 457 28">