1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-28 16: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;
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
switch (shield->getClass().getEquipmentSkill(*shield))
{
case ESM::Skill::LightArmor:
const ESM::RefId skill = shield->getClass().getEquipmentSkill(*shield);
if (skill == ESM::Skill::LightArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f);
break;
case ESM::Skill::MediumArmor:
else if (skill == ESM::Skill::MediumArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f);
break;
case ESM::Skill::HeavyArmor:
else if (skill == 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

View file

@ -112,7 +112,7 @@ namespace MWClass
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>();
@ -150,7 +150,7 @@ namespace MWClass
}
if (typeGmst.empty())
return -1;
return {};
const MWWorld::Store<ESM::GameSetting>& gmst
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
@ -178,7 +178,7 @@ namespace MWClass
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 mediumUp = ESM::RefId::stringRefId("Item Armor Medium 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
{
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 mediumDown = ESM::RefId::stringRefId("Item Armor Medium Down");
static const ESM::RefId heavyDown = ESM::RefId::stringRefId("Item Armor Heavy Down");
@ -232,7 +232,7 @@ namespace MWClass
}
else
{
int armorType = getEquipmentSkill(ptr);
const ESM::RefId armorType = getEquipmentSkill(ptr);
if (armorType == ESM::Skill::LightArmor)
typeText = "#{sLight}";
else if (armorType == ESM::Skill::MediumArmor)
@ -297,7 +297,7 @@ namespace MWClass
{
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);
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
/// stay stacked when equipped?
int 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.
ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr) 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.

View file

@ -101,14 +101,14 @@ namespace MWClass
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>();
if (ref->mBase->mData.mType == ESM::Clothing::Shoes)
return ESM::Skill::Unarmored;
return -1;
return {};
}
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
/// stay stacked when equipped?
int 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.
ESM::RefId getEquipmentSkill(const MWWorld::ConstPtr& ptr) 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.

View file

@ -762,11 +762,11 @@ namespace MWClass
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>();
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)
{

View file

@ -115,7 +115,7 @@ namespace MWClass
bool canSwim(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)
int getBloodTexture(const MWWorld::ConstPtr& ptr) const override;

View file

@ -111,20 +111,18 @@ namespace
{
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;
// is this a minor or major skill?
float add = 0.2f;
for (const auto& skills : class_->mData.mSkills)
{
if (skills[0] == j)
if (skills[0] == skill.mIndex)
add = 0.5;
if (skills[1] == j)
if (skills[1] == skill.mIndex)
add = 1.0;
}
modifierSum += add;
@ -181,15 +179,15 @@ namespace
for (const auto& skills : class_->mData.mSkills)
{
int index = skills[i];
if (index >= 0 && index < ESM::Skill::Length)
ESM::RefId id = ESM::Skill::indexToRefId(skills[i]);
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 specMultiplier = 0.0f;
@ -198,14 +196,14 @@ namespace
int specBonus = 0;
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())
raceBonus = bonusIt->mBonus;
for (const auto& skills : class_->mData.mSkills)
{
// 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;
break;
@ -213,30 +211,25 @@ namespace
}
// 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;
specBonus = 5;
}
npcStats.getSkill(skillIndex)
.setBase(std::min(round_ieee_754(npcStats.getSkill(skillIndex).getBase() + 5 + raceBonus + specBonus
npcStats.getSkill(skill.mId).setBase(
std::min(round_ieee_754(npcStats.getSkill(skill.mId).getBase() + 5 + raceBonus + specBonus
+ (int(level) - 1) * (majorMultiplier + specMultiplier)),
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];
for (int i = 0; i < ESM::Attribute::Length; ++i)
attributes[i] = npcStats.getAttribute(i).getBase();
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);
}
}
@ -315,7 +308,7 @@ namespace MWClass
gold = ref->mBase->mNpdt.mGold;
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::Intelligence, ref->mBase->mNpdt.mIntelligence);
@ -589,7 +582,7 @@ namespace MWClass
victim = result.first;
hitPosition = result.second;
int weapskill = ESM::Skill::HandToHand;
ESM::RefId weapskill = ESM::Skill::HandToHand;
if (!weapon.isEmpty())
weapskill = weapon.getClass().getEquipmentSkill(weapon);
@ -658,7 +651,7 @@ namespace MWClass
if (ptr == MWMechanics::getPlayer())
{
int weapskill = ESM::Skill::HandToHand;
ESM::RefId weapskill = ESM::Skill::HandToHand;
if (!weapon.isEmpty())
weapskill = weapon.getClass().getEquipmentSkill(weapon);
skillUsageSucceeded(ptr, weapskill, 0);
@ -849,21 +842,16 @@ namespace MWClass
armor = *inv.unequipItem(armor);
}
ESM::RefId skill = armor.getClass().getEquipmentSkill(armor);
if (ptr == MWMechanics::getPlayer())
skillUsageSucceeded(ptr, armor.getClass().getEquipmentSkill(armor), 0);
skillUsageSucceeded(ptr, skill, 0);
switch (armor.getClass().getEquipmentSkill(armor))
{
case ESM::Skill::LightArmor:
if (skill == ESM::Skill::LightArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f);
break;
case ESM::Skill::MediumArmor:
else if (skill == ESM::Skill::MediumArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Medium Armor Hit"), 1.0f, 1.0f);
break;
case ESM::Skill::HeavyArmor:
else if (skill == ESM::Skill::HeavyArmor)
sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f);
break;
}
}
else if (ptr == MWMechanics::getPlayer())
skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0);
@ -1161,7 +1149,7 @@ namespace MWClass
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);
@ -1311,18 +1299,13 @@ namespace MWClass
if (boots == inv.end() || boots->getType() != ESM::Armor::sRecordId)
return (name == "left") ? footBareLeft : footBareRight;
switch (boots->getClass().getEquipmentSkill(*boots))
{
case ESM::Skill::LightArmor:
ESM::RefId skill = boots->getClass().getEquipmentSkill(*boots);
if (skill == ESM::Skill::LightArmor)
return (name == "left") ? footLightLeft : footLightRight;
break;
case ESM::Skill::MediumArmor:
else if (skill == ESM::Skill::MediumArmor)
return (name == "left") ? footMediumLeft : footMediumRight;
break;
case ESM::Skill::HeavyArmor:
else if (skill == ESM::Skill::HeavyArmor)
return (name == "left") ? footHeavyLeft : footHeavyRight;
break;
}
}
return ESM::RefId();
}
@ -1355,9 +1338,9 @@ namespace MWClass
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

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
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.
bool isEssential(const MWWorld::ConstPtr& ptr) const override;
@ -134,7 +134,7 @@ namespace MWClass
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)
int getBloodTexture(const MWWorld::ConstPtr& ptr) const override;

View file

@ -108,7 +108,7 @@ namespace MWClass
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>();
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
/// stay stacked when equipped?
int 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.
ESM::RefId getEquipmentSkill(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.

View file

@ -5,6 +5,7 @@
#include <components/esm3/loadcrea.hpp>
#include <components/esm3/loadfact.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadskil.hpp>
#include "../mwbase/dialoguemanager.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();
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:
{
int hits = mActor.getClass().getCreatureStats(mActor).getFriendlyHits();

View file

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

View file

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

View file

@ -3,6 +3,7 @@
#include <MyGUI_Gui.h>
#include <MyGUI_ImageBox.h>
#include <MyGUI_ListBox.h>
#include <MyGUI_ScrollView.h>
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
@ -266,10 +267,12 @@ namespace MWGui
for (size_t i = 0; i < klass->mData.mSkills.size(); ++i)
{
mMinorSkill[i]->setSkillNumber(klass->mData.mSkills[i][0]);
mMajorSkill[i]->setSkillNumber(klass->mData.mSkills[i][1]);
ToolTips::createSkillToolTip(mMinorSkill[i], klass->mData.mSkills[i][0]);
ToolTips::createSkillToolTip(mMajorSkill[i], klass->mData.mSkills[i][1]);
ESM::RefId minor = ESM::Skill::indexToRefId(klass->mData.mSkills[i][0]);
ESM::RefId major = ESM::Skill::indexToRefId(klass->mData.mSkills[i][1]);
mMinorSkill[i]->setSkillId(minor);
mMajorSkill[i]->setSkillId(major);
ToolTips::createSkillToolTip(mMinorSkill[i], minor);
ToolTips::createSkillToolTip(mMajorSkill[i], major);
}
setClassImage(mClassImage, mCurrentClassId);
@ -514,24 +517,24 @@ namespace MWGui
return v;
}
std::vector<ESM::Skill::SkillEnum> CreateClassDialog::getMajorSkills() const
std::vector<ESM::RefId> CreateClassDialog::getMajorSkills() const
{
std::vector<ESM::Skill::SkillEnum> v;
v.reserve(5);
for (int i = 0; i < 5; i++)
std::vector<ESM::RefId> v;
v.reserve(mMajorSkill.size());
for (const auto& widget : mMajorSkill)
{
v.push_back(mMajorSkill[i]->getSkillId());
v.push_back(widget->getSkillId());
}
return v;
}
std::vector<ESM::Skill::SkillEnum> CreateClassDialog::getMinorSkills() const
std::vector<ESM::RefId> CreateClassDialog::getMinorSkills() const
{
std::vector<ESM::Skill::SkillEnum> v;
v.reserve(5);
for (int i = 0; i < 5; i++)
std::vector<ESM::RefId> v;
v.reserve(mMinorSkill.size());
for (const auto& widget : mMinorSkill)
{
v.push_back(mMinorSkill[i]->getSkillId());
v.push_back(widget->getSkillId());
}
return v;
}
@ -624,7 +627,7 @@ namespace MWGui
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
for (Widgets::MWSkillPtr& skill : mSkills)
@ -793,43 +796,33 @@ namespace MWGui
// Centre dialog
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;
getWidget(mCombatSkill[i], std::string("CombatSkill").append(1, theIndex));
getWidget(mMagicSkill[i], std::string("MagicSkill").append(1, theIndex));
getWidget(mStealthSkill[i], std::string("StealthSkill").append(1, theIndex));
coord.width = widget->getCoord().width;
coord.height = 18;
while (widget->getChildCount() > 0)
MyGUI::Gui::getInstance().destroyWidget(widget->getChildAt(0));
}
struct
for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
{
Widgets::MWSkillPtr widget;
ESM::Skill::SkillEnum skillId;
} mSkills[3][9]
= { { { mCombatSkill[0], ESM::Skill::Block }, { mCombatSkill[1], ESM::Skill::Armorer },
{ mCombatSkill[2], ESM::Skill::MediumArmor }, { mCombatSkill[3], ESM::Skill::HeavyArmor },
{ mCombatSkill[4], ESM::Skill::BluntWeapon }, { mCombatSkill[5], ESM::Skill::LongBlade },
{ mCombatSkill[6], ESM::Skill::Axe }, { mCombatSkill[7], ESM::Skill::Spear },
{ mCombatSkill[8], ESM::Skill::Athletics } },
{ { mMagicSkill[0], ESM::Skill::Enchant }, { mMagicSkill[1], ESM::Skill::Destruction },
{ 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)
{
mSkills[spec][i].widget->setSkillId(mSkills[spec][i].skillId);
mSkills[spec][i].widget->eventClicked += MyGUI::newDelegate(this, &SelectSkillDialog::onSkillClicked);
ToolTips::createSkillToolTip(mSkills[spec][i].widget, mSkills[spec][i].widget->getSkillId());
auto& [widget, coord] = specializations[skill.mData.mSpecialization];
auto* skillWidget
= widget->createWidget<Widgets::MWSkill>("MW_StatNameButton", coord, MyGUI::Align::Default);
coord.top += coord.height;
skillWidget->setSkillId(skill.mId);
skillWidget->eventClicked += MyGUI::newDelegate(this, &SelectSkillDialog::onSkillClicked);
ToolTips::createSkillToolTip(skillWidget, skill.mId);
}
for (const auto& [widget, coord] : specializations)
{
widget->setVisibleVScroll(false);
widget->setCanvasSize(MyGUI::IntSize(widget->getWidth(), std::max(widget->getHeight(), coord.top)));
widget->setVisibleVScroll(true);
widget->setViewOffset(MyGUI::IntPoint());
}
MyGUI::Button* cancelButton;

View file

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

View file

@ -93,11 +93,11 @@ namespace MWGui
for (int day = 0; day < mDays; ++day)
{
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);
MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill->mIndex);
if (skill->mIndex == ESM::Skill::Security || skill->mIndex == ESM::Skill::Sneak)
MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill->mId);
if (skill->mId == ESM::Skill::Security || skill->mId == ESM::Skill::Sneak)
value.setBase(std::min(100.f, value.getBase() + 1));
else
value.setBase(std::max(0.f, value.getBase() - 1));
@ -116,9 +116,9 @@ namespace MWGui
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();
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 = 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);
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;
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));
ToolTips::createSkillToolTip(skillWidget, bonus.mSkill);
ToolTips::createSkillToolTip(skillWidget, skill);
mSkillItems.push_back(skillWidget);

View file

@ -90,10 +90,10 @@ namespace MWGui
getWidget(mSkillView, "SkillView");
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()));
mSkillWidgetMap.insert(std::make_pair(i, static_cast<MyGUI::TextBox*>(nullptr)));
mSkillValues.emplace(skill.mId, MWMechanics::SkillValue());
mSkillWidgetMap.emplace(skill.mId, static_cast<MyGUI::TextBox*>(nullptr));
}
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;
MyGUI::TextBox* widget = mSkillWidgetMap[skillId];
mSkillValues[id] = value;
MyGUI::TextBox* widget = mSkillWidgetMap[id];
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 state = "normal";
if (modified > base)
@ -225,21 +226,21 @@ namespace MWGui
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;
mMinorSkills = 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(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin()));
mMiscSkills.clear();
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))
mMiscSkills.push_back(skill.second.mIndex);
if (!skillSet.contains(skill.mId))
mMiscSkills.push_back(skill.mId);
}
mUpdateSkillArea = true;
@ -327,8 +328,8 @@ namespace MWGui
coord2.top += lineHeight;
}
void ReviewDialog::addSkills(const SkillList& skills, const std::string& titleId, const std::string& titleDefault,
MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2)
void ReviewDialog::addSkills(const std::vector<ESM::RefId>& skills, const std::string& titleId,
const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2)
{
// Add a line separator if there are items above
if (!mSkillWidgets.empty())
@ -339,12 +340,12 @@ namespace MWGui
addGroup(
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);
if (!skill) // Skip unknown skill indexes
if (!skill) // Skip unknown skills
continue;
const MWMechanics::SkillValue& stat = mSkillValues.find(skillId)->second;
const MWMechanics::SkillValue& stat = mSkillValues.find(skill->mId)->second;
int base = stat.getBase();
int modified = stat.getModified();
@ -358,10 +359,10 @@ namespace MWGui
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())
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];
for (int i = 0; i < ESM::Attribute::Length; ++i)
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)
{
if (std::find(spells.begin(), spells.end(), spellId) == spells.end())

View file

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

View file

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

View file

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

View file

@ -83,7 +83,8 @@ namespace MWGui
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 + ')';
}
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::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 convert = Utf8Stream::lowerCaseUtf8(fullEffectName);

View file

@ -83,13 +83,13 @@ namespace MWGui
}
}
// Loop over ESM::Skill::SkillEnum
for (int i = 0; i < ESM::Skill::Length; ++i)
for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
{
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);
setValue((ESM::Skill::SkillEnum)i, stats.getSkill(i));
mWatchedSkills[skill.mId] = value;
setValue(skill.mId, value);
}
}
@ -125,13 +125,13 @@ namespace MWGui
setValue("class", cls->mName);
size_t size = cls->mData.mSkills.size();
MWBase::WindowManager::SkillList majorSkills(size);
MWBase::WindowManager::SkillList minorSkills(size);
std::vector<ESM::RefId> majorSkills(size);
std::vector<ESM::RefId> minorSkills(size);
for (size_t i = 0; i < size; ++i)
{
minorSkills[i] = cls->mData.mSkills[i][0];
majorSkills[i] = cls->mData.mSkills[i][1];
minorSkills[i] = ESM::Skill::indexToRefId(cls->mData.mSkills[i][0]);
majorSkills[i] = ESM::Skill::indexToRefId(cls->mData.mSkills[i][1]);
}
configureSkills(majorSkills, minorSkills);
@ -157,12 +157,10 @@ namespace MWGui
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)
listener->setValue(parSkill, value);
listener->setValue(id, value);
}
void StatsWatcher::setValue(std::string_view id, const MWMechanics::DynamicStat<float>& value)
@ -183,7 +181,7 @@ namespace MWGui
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)
listener->configureSkills(major, minor);

View file

@ -1,6 +1,7 @@
#ifndef MWGUI_STATSWATCHER_H
#define MWGUI_STATSWATCHER_H
#include <map>
#include <set>
#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, const std::string& value) {}
virtual void setValue(std::string_view, int value) {}
virtual void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) {}
virtual void configureSkills(const std::vector<int>& major, const std::vector<int>& minor) {}
virtual void setValue(ESM::RefId id, const MWMechanics::SkillValue& value) {}
virtual void configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor) {}
};
class StatsWatcher
@ -31,7 +32,7 @@ namespace MWGui
MWWorld::Ptr mWatched;
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> 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 std::string& value);
void setValue(std::string_view id, int value);
void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value);
void configureSkills(const std::vector<int>& major, const std::vector<int>& minor);
void setValue(ESM::RefId id, const MWMechanics::SkillValue& value);
void configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor);
public:
StatsWatcher();

View file

@ -37,17 +37,8 @@ namespace MWGui
: WindowPinnableBase("openmw_stats_window.layout")
, NoDrop(drag, mMainWidget)
, mSkillView(nullptr)
, mMajorSkills()
, mMinorSkills()
, mMiscSkills()
, mSkillValues()
, mSkillWidgetMap()
, mFactionWidgetMap()
, mFactions()
, mBirthSignId()
, mReputation(0)
, mBounty(0)
, mSkillWidgets()
, mChanged(true)
, mMinFullWidth(mMainWidget->getSize().width)
{
@ -67,11 +58,10 @@ namespace MWGui
getWidget(mLeftPane, "LeftPane");
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()));
mSkillWidgetMap.insert(
std::make_pair(i, std::make_pair((MyGUI::TextBox*)nullptr, (MyGUI::TextBox*)nullptr)));
mSkillValues.emplace(skill.mId, MWMechanics::SkillValue());
mSkillWidgetMap.emplace(skill.mId, std::make_pair<MyGUI::TextBox*, MyGUI::TextBox*>(nullptr, nullptr));
}
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();
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
@ -255,10 +245,10 @@ namespace MWGui
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;
std::pair<MyGUI::TextBox*, MyGUI::TextBox*> widgets = mSkillWidgetMap[(int)parSkill];
mSkillValues[id] = value;
std::pair<MyGUI::TextBox*, MyGUI::TextBox*> widgets = mSkillWidgetMap[id];
MyGUI::TextBox* valueWidget = widgets.second;
MyGUI::TextBox* nameWidget = widgets.first;
if (valueWidget && nameWidget)
@ -296,8 +286,8 @@ namespace MWGui
valueWidget->setUserString("Visible_SkillProgressVBox", "true");
valueWidget->setUserString("UserData^Hidden_SkillProgressVBox", "false");
setSkillProgress(nameWidget, value.getProgress(), parSkill);
setSkillProgress(valueWidget, value.getProgress(), parSkill);
setSkillProgress(nameWidget, value.getProgress(), id);
setSkillProgress(valueWidget, value.getProgress(), id);
}
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;
mMinorSkills = 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(minor.begin(), minor.end(), std::inserter(skillSet, skillSet.begin()));
mMiscSkills.clear();
const auto& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::Skill>();
for (const auto& skill : store)
{
if (!skillSet.contains(skill.second.mIndex))
mMiscSkills.push_back(skill.second.mIndex);
if (!skillSet.contains(skill.mId))
mMiscSkills.push_back(skill.mId);
}
updateSkillArea();
@ -492,8 +482,8 @@ namespace MWGui
return skillNameWidget;
}
void StatsWindow::addSkills(const SkillList& skills, const std::string& titleId, const std::string& titleDefault,
MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2)
void StatsWindow::addSkills(const std::vector<ESM::RefId>& skills, const std::string& titleId,
const std::string& titleDefault, MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2)
{
// Add a line separator if there are items above
if (!mSkillWidgets.empty())
@ -504,19 +494,18 @@ namespace MWGui
addGroup(
MWBase::Environment::get().getWindowManager()->getGameSettingString(titleId, titleDefault), coord1, coord2);
for (const int skillId : skills)
{
if (skillId < 0 || skillId >= ESM::Skill::Length) // Skip unknown skill indexes
continue;
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
const ESM::Skill* skill = esmStore.get<ESM::Skill>().find(skillId);
for (const ESM::RefId& skillId : skills)
{
const ESM::Skill* skill = esmStore.get<ESM::Skill>().search(skillId);
if (!skill) // Skip unknown skills
continue;
const ESM::Attribute* attr = esmStore.get<ESM::Attribute>().find(skill->mData.mAttribute);
std::pair<MyGUI::TextBox*, MyGUI::TextBox*> widgets
= addValueItem(skill->mName, {}, "normal", coord1, coord2);
mSkillWidgetMap[skillId] = widgets;
mSkillWidgetMap[skill->mId] = widgets;
for (int i = 0; i < 2; ++i)
{
@ -532,7 +521,7 @@ namespace MWGui
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;
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)
text += ", ";
firstSkill = false;
const ESM::Skill* skill = store.get<ESM::Skill>().find(id);
text += MyGUI::TextIterator::toTagsString(skill->mName);
}
}

View file

@ -12,8 +12,6 @@ namespace MWGui
public:
typedef std::map<ESM::RefId, int> FactionList;
typedef std::vector<int> SkillList;
StatsWindow(DragAndDrop* drag);
/// 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 std::string& value) override;
void setValue(std::string_view id, int value) override;
void setValue(const ESM::Skill::SkillEnum parSkill, const MWMechanics::SkillValue& value) override;
void configureSkills(const SkillList& major, const SkillList& minor) override;
void setValue(ESM::RefId id, const MWMechanics::SkillValue& value) override;
void configureSkills(const std::vector<ESM::RefId>& major, const std::vector<ESM::RefId>& minor) override;
void setReputation(int reputation)
{
@ -47,8 +45,8 @@ namespace MWGui
void onOpen() override { onWindowResize(mMainWidget->castType<MyGUI::Window>()); }
private:
void addSkills(const SkillList& skills, const std::string& titleId, const std::string& titleDefault,
MyGUI::IntCoord& coord1, MyGUI::IntCoord& coord2);
void addSkills(const std::vector<ESM::RefId>& skills, const std::string& titleId,
const std::string& titleDefault, 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);
std::pair<MyGUI::TextBox*, MyGUI::TextBox*> addValueItem(std::string_view text, const std::string& value,
@ -67,9 +65,9 @@ namespace MWGui
MyGUI::ScrollView* mSkillView;
SkillList mMajorSkills, mMinorSkills, mMiscSkills;
std::map<int, MWMechanics::SkillValue> mSkillValues;
std::map<int, std::pair<MyGUI::TextBox*, MyGUI::TextBox*>> mSkillWidgetMap;
std::vector<ESM::RefId> mMajorSkills, mMinorSkills, mMiscSkills;
std::map<ESM::RefId, MWMechanics::SkillValue> mSkillValues;
std::map<ESM::RefId, std::pair<MyGUI::TextBox*, MyGUI::TextBox*>> mSkillWidgetMap;
std::map<std::string, MyGUI::Widget*> mFactionWidgetMap;
FactionList mFactions; ///< Stores a list of factions and the current rank
ESM::RefId mBirthSignId;

View file

@ -805,9 +805,9 @@ namespace MWGui
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;
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>();
bool isFirst = true;
for (const auto& [_, skill] : skills)
for (const auto& skill : skills)
{
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
// 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 createSpecializationToolTip(MyGUI::Widget* widget, const std::string& name, int specId);
static void createBirthsignToolTip(MyGUI::Widget* widget, const ESM::RefId& birthsignId);

View file

@ -21,24 +21,6 @@
#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
{
@ -80,34 +62,37 @@ namespace MWGui
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
std::vector<std::pair<int, float>> skills;
std::vector<std::pair<const ESM::Skill*, float>> skills;
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::Gui::getInstance().destroyWidgets(widgets);
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;
for (int i = 0; i < 3; ++i)
{
const ESM::Skill* skill = skills[i].first;
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 = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true);
@ -120,13 +105,12 @@ namespace MWGui
button->setUserData(skills[i].first);
button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected);
const ESM::Skill* skill = skillStore.find(skills[i].first);
button->setCaptionWithReplacing(
MyGUI::TextIterator::toTagsString(skill->mName) + " - " + MyGUI::utility::toString(price));
button->setSize(button->getTextSize().width + 12, button->getSize().height);
ToolTips::createSkillToolTip(button, skills[i].first);
ToolTips::createSkillToolTip(button, skill->mId);
}
center();
@ -144,29 +128,29 @@ namespace MWGui
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();
MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player);
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();
price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr, price, true);
if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId))
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}");
return;
}
// 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(skillId).getBase() >= pcStats.getAttribute(skill->mData.mAttribute).getBase())
if (pcStats.getSkill(skill->mId).getBase() >= pcStats.getAttribute(skill->mData.mAttribute).getBase())
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage17}");
return;
@ -176,7 +160,7 @@ namespace MWGui
MWWorld::LiveCellRef<ESM::NPC>* playerRef = player.get<ESM::NPC>();
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
player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price);
@ -212,11 +196,11 @@ namespace MWGui
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)
return stats.getSkill(skillId).getBase();
return stats.getSkill(skillId).getModified();
return stats.getSkill(id).getBase();
return stats.getSkill(id).getModified();
}
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;
// 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::Button* mCancelButton;

View file

@ -28,28 +28,17 @@ namespace MWGui::Widgets
/* MWSkill */
MWSkill::MWSkill()
: mSkillId(ESM::Skill::Length)
, mSkillNameWidget(nullptr)
: mSkillNameWidget(nullptr)
, mSkillValueWidget(nullptr)
{
}
void MWSkill::setSkillId(ESM::Skill::SkillEnum skill)
void MWSkill::setSkillId(ESM::RefId skill)
{
mSkillId = skill;
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)
{
mValue = value;
@ -374,7 +363,7 @@ namespace MWGui::Widgets
const ESM::MagicEffect* magicEffect = store.get<ESM::MagicEffect>().search(mEffectParams.mEffectID);
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);

View file

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

View file

@ -4,6 +4,7 @@
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <components/lua/luastate.hpp>
#include <components/lua/scriptscontainer.hpp>
@ -21,11 +22,12 @@ namespace MWLua
class CachedStat
{
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)
, mIndex(index)
, mIndex(std::move(index))
, mProp(std::move(prop))
{
}
@ -42,7 +44,7 @@ namespace MWLua
private:
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
};

View file

@ -24,9 +24,10 @@ namespace
{
using SelfObject = MWLua::SelfObject;
using ObjectVariant = MWLua::ObjectVariant;
using Index = const SelfObject::CachedStat::Index&;
template <class T>
auto addIndexedAccessor(int index)
auto addIndexedAccessor(Index index)
{
return [index](const sol::object& o) { return T::create(ObjectVariant(o), index); };
}
@ -40,7 +41,7 @@ namespace
template <class G>
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())
{
@ -99,14 +100,14 @@ namespace MWLua
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())
return {};
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);
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())
return {};
int index = std::get<int>(i);
return DynamicStat{ std::move(object), index };
}
@ -149,8 +151,9 @@ namespace MWLua
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 stat = stats.getDynamic(index);
float floatValue = LuaUtil::cast<float>(value);
@ -193,10 +196,11 @@ namespace MWLua
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())
return {};
int index = std::get<int>(i);
return AttributeStat{ std::move(object), index };
}
@ -207,8 +211,9 @@ namespace MWLua
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 stat = stats.getAttribute(index);
float floatValue = LuaUtil::cast<float>(value);
@ -228,36 +233,35 @@ namespace MWLua
class SkillStat
{
ObjectVariant mObject;
int mIndex;
ESM::RefId mId;
SkillStat(ObjectVariant object, int index)
SkillStat(ObjectVariant object, ESM::RefId id)
: 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();
if (progress != 0.f)
progress /= getMaxProgress(ptr, index, stat);
progress /= getMaxProgress(ptr, id, stat);
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 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:
template <class G>
sol::object get(const Context& context, std::string_view prop, G getter) const
{
return getValue(
context, mObject, &SkillStat::setValue, mIndex, prop, [this, getter](const MWWorld::Ptr& ptr) {
return (ptr.getClass().getNpcStats(ptr).getSkill(mIndex).*getter)();
return getValue(context, mObject, &SkillStat::setValue, mId, prop, [this, getter](const MWWorld::Ptr& ptr) {
return (ptr.getClass().getNpcStats(ptr).getSkill(mId).*getter)();
});
}
@ -271,30 +275,31 @@ namespace MWLua
sol::object getProgress(const Context& context) const
{
return getValue(
context, mObject, &SkillStat::setValue, mIndex, "progress", [this](const MWWorld::Ptr& ptr) {
return getProgress(ptr, mIndex, ptr.getClass().getNpcStats(ptr).getSkill(mIndex));
return getValue(context, mObject, &SkillStat::setValue, mId, "progress", [this](const MWWorld::Ptr& ptr) {
return getProgress(ptr, mId, ptr.getClass().getNpcStats(ptr).getSkill(mId));
});
}
static std::optional<SkillStat> create(ObjectVariant object, int index)
static std::optional<SkillStat> create(ObjectVariant object, Index index)
{
if (!object.ptr().getClass().isNpc())
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
{
SelfObject* obj = mObject.asSelfObject();
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 stat = stats.getSkill(index);
auto stat = stats.getSkill(id);
float floatValue = LuaUtil::cast<float>(value);
if (prop == "base")
stat.setBase(floatValue);
@ -306,8 +311,8 @@ namespace MWLua
else if (prop == "modifier")
stat.setModifier(floatValue);
else if (prop == "progress")
stat.setProgress(floatValue * getMaxProgress(ptr, index, stat));
stats.setSkill(index, stat);
stat.setProgress(floatValue * getMaxProgress(ptr, id, stat));
stats.setSkill(id, stat);
}
};
}
@ -385,7 +390,8 @@ namespace MWLua
[context](const SkillStat& stat, const sol::object& value) { stat.cache(context, "progress", value); });
sol::table skills(context.mLua->sol(), sol::create);
npcStats["skills"] = LuaUtil::makeReadOnly(skills);
for (int id = ESM::Skill::Block; id < ESM::Skill::Length; ++id)
skills[Misc::StringUtils::lowerCase(ESM::Skill::sSkillNames[id])] = addIndexedAccessor<SkillStat>(id);
for (const ESM::Skill& skill : MWBase::Environment::get().getESMStore()->get<ESM::Skill>())
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);
book["SKILL"] = LuaUtil::makeStrictReadOnly(skill);
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]);
skill[skillName] = skillName;

View file

@ -576,7 +576,7 @@ std::vector<std::string> MWMechanics::Alchemy::effectsDescription(const MWWorld:
if (effectID != -1)
{
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);
effects.push_back(effect);

View file

@ -27,7 +27,8 @@ namespace MWMechanics
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
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
@ -148,7 +149,7 @@ namespace MWMechanics
}
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();
@ -226,7 +227,8 @@ namespace MWMechanics
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)
{
@ -240,8 +242,9 @@ namespace MWMechanics
if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill))
{
assert(spellEffect.mSkill >= 0 && spellEffect.mSkill < ESM::Skill::Length);
if (actorSkills[spellEffect.mSkill] < iAutoSpellAttSkillMin)
ESM::RefId skill = ESM::Skill::indexToRefId(spellEffect.mSkill);
auto found = actorSkills.find(skill);
if (found == actorSkills.end() || found->second.getBase() < iAutoSpellAttSkillMin)
return false;
}
@ -256,7 +259,8 @@ namespace MWMechanics
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
float minChance = std::numeric_limits<float>::max();
@ -294,7 +298,11 @@ namespace MWMechanics
if (effect.mRange == ESM::RT_Target)
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)
{
minChance = s - x;
@ -304,8 +312,8 @@ namespace MWMechanics
}
}
float calcAutoCastChance(
const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, int effectiveSchool)
float calcAutoCastChance(const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills,
const int* actorAttributes, int effectiveSchool)
{
if (spell->mData.mType != ESM::Spell::ST_Spell)
return 100.f;
@ -315,7 +323,12 @@ namespace MWMechanics
float skillTerm = 0;
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
calcWeakestSchool(
spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this

View file

@ -1,9 +1,12 @@
#ifndef OPENMW_AUTOCALCSPELL_H
#define OPENMW_AUTOCALCSPELL_H
#include "creaturestats.hpp"
#include <components/esm/refid.hpp>
#include <map>
#include <string>
#include <vector>
namespace ESM
{
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
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(
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
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(
const ESM::Spell* spell, const int* actorSkills, const int* actorAttributes, int effectiveSchool);
float calcAutoCastChance(const ESM::Spell* spell, const std::map<ESM::RefId, SkillValue>& actorSkills,
const int* actorAttributes, int effectiveSchool);
}

View file

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

View file

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

View file

@ -129,7 +129,7 @@ namespace MWMechanics
creatureStats.getActiveSpells().clear(ptr);
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::Intelligence, player->mNpdt.mIntelligence);
@ -155,16 +155,16 @@ namespace MWMechanics
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;
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())
bonus = bonusIt->mBonus;
npcStats.getSkill(i).setBase(5 + bonus);
npcStats.getSkill(skill.mId).setBase(5 + bonus);
}
for (const ESM::RefId& power : race->mPowers.mList)
@ -205,29 +205,16 @@ namespace MWMechanics
for (const auto& skills : class_->mData.mSkills)
{
int index = skills[i];
if (index >= 0 && index < ESM::Skill::Length)
{
npcStats.getSkill(index).setBase(npcStats.getSkill(index).getBase() + bonus);
}
ESM::RefId id = ESM::Skill::indexToRefId(skills[i]);
if (!id.empty())
npcStats.getSkill(id).setBase(npcStats.getSkill(id).getBase() + bonus);
}
}
const MWWorld::Store<ESM::Skill>& skills = esmStore.get<ESM::Skill>();
MWWorld::Store<ESM::Skill>::iterator iter = skills.begin();
for (; iter != skills.end(); ++iter)
for (const ESM::Skill& skill : esmStore.get<ESM::Skill>())
{
if (iter->second.mData.mSpecialization == class_->mData.mSpecialization)
{
int index = iter->first;
if (index >= 0 && index < 27)
{
npcStats.getSkill(index).setBase(npcStats.getSkill(index).getBase() + 5);
}
}
if (skill.mData.mSpecialization == class_->mData.mSpecialization)
npcStats.getSkill(skill.mId).setBase(npcStats.getSkill(skill.mId).getBase() + 5);
}
}
@ -236,16 +223,12 @@ namespace MWMechanics
if (mRaceSelected)
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];
for (int i = 0; i < ESM::Attribute::Length; ++i)
attributes[i] = npcStats.getAttribute(i).getBase();
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)
creatureStats.getSpells().add(spell);
@ -1921,7 +1904,7 @@ namespace MWMechanics
const ESM::Skill* acrobatics
= MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(ESM::Skill::Acrobatics);
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());
}

View file

@ -30,6 +30,8 @@ MWMechanics::NpcStats::NpcStats()
{
mSkillIncreases.resize(ESM::Attribute::Length, 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
@ -42,28 +44,28 @@ void MWMechanics::NpcStats::setBaseDisposition(int 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)
throw std::runtime_error("skill index out of range");
return mSkill[index];
auto it = mSkills.find(id);
if (it == mSkills.end())
throw std::runtime_error("skill not found");
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)
throw std::runtime_error("skill index out of range");
return mSkill[index];
auto it = mSkills.find(id);
if (it == mSkills.end())
throw std::runtime_error("skill not found");
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)
throw std::runtime_error("skill index out of range");
mSkill[index] = value;
auto it = mSkills.find(id);
if (it == mSkills.end())
throw std::runtime_error("skill not found");
it->second = value;
}
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;
}
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 ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(id);
float typeFactor = gmst.find("fMiscSkillBonus")->mValue.getFloat();
for (const auto& skills : class_.mData.mSkills)
{
if (skills[0] == skillIndex)
if (skills[0] == skill->mIndex)
{
typeFactor = gmst.find("fMinorSkillBonus")->mValue.getFloat();
break;
}
else if (skills[1] == skillIndex)
else if (skills[1] == skill->mIndex)
{
typeFactor = gmst.find("fMajorSkillBonus")->mValue.getFloat();
break;
@ -183,7 +186,6 @@ float MWMechanics::NpcStats::getSkillProgressRequirement(int skillIndex, const E
float specialisationFactor = 1;
const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get<ESM::Skill>().find(skillIndex);
if (skill->mData.mSpecialization == class_.mData.mSpecialization)
{
specialisationFactor = gmst.find("fSpecialSkillBonus")->mValue.getFloat();
@ -196,9 +198,9 @@ float MWMechanics::NpcStats::getSkillProgressRequirement(int skillIndex, const E
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;
if (usageType >= 4)
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;
MWMechanics::SkillValue& value = getSkill(skillIndex);
MWMechanics::SkillValue& value = getSkill(skill->mId);
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
increaseSkill(skillIndex, class_, false);
increaseSkill(skill->mId, class_, false);
}
}
void MWMechanics::NpcStats::increaseSkill(
int skillIndex, const ESM::Class& class_, bool preserveProgress, bool readBook)
void MWMechanics::NpcStats::increaseSkill(ESM::RefId id, 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)
return;
@ -237,13 +239,13 @@ void MWMechanics::NpcStats::increaseSkill(
int increase = gmst.find("iLevelupMiscMultAttriubte")->mValue.getInteger(); // Note: GMST has a typo
for (const auto& skills : class_.mData.mSkills)
{
if (skills[0] == skillIndex)
if (skills[0] == skill->mIndex)
{
mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger();
increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger();
break;
}
else if (skills[1] == skillIndex)
else if (skills[1] == skill->mIndex)
{
mLevelProgress += gmst.find("iLevelUpMajorMult")->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;
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);
}
getSkill(skillIndex).setBase(base);
getSkill(skill->mId).setBase(base);
if (!preserveProgress)
getSkill(skillIndex).setProgress(0);
getSkill(skill->mId).setProgress(0);
}
int MWMechanics::NpcStats::getLevelProgress() const
@ -388,9 +389,10 @@ bool MWMechanics::NpcStats::hasSkillsForRank(const ESM::RefId& factionId, int ra
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()));
}
@ -470,8 +472,12 @@ void MWMechanics::NpcStats::writeState(ESM::NpcStats& state) const
state.mDisposition = mDisposition;
for (size_t i = 0; i < state.mSkills.size(); ++i)
mSkill[i].writeState(state.mSkills[i]);
for (const auto& [id, value] : mSkills)
{
// TODO extend format
auto index = id.getIf<ESM::IndexRefId>()->getValue();
value.writeState(state.mSkills[index]);
}
state.mIsWerewolf = mIsWerewolf;
@ -524,7 +530,11 @@ void MWMechanics::NpcStats::readState(const ESM::NpcStats& state)
mDisposition = state.mDisposition;
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;

View file

@ -3,6 +3,7 @@
#include "creaturestats.hpp"
#include <components/esm/refid.hpp>
#include <components/esm3/loadskil.hpp>
#include <map>
#include <set>
#include <string>
@ -21,7 +22,7 @@ namespace MWMechanics
class NpcStats : public CreatureStats
{
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 mCrimeId;
@ -58,9 +59,9 @@ namespace MWMechanics
int getCrimeId() const;
void setCrimeId(int id);
const SkillValue& getSkill(int index) const;
SkillValue& getSkill(int index);
void setSkill(int index, const SkillValue& value);
const SkillValue& getSkill(ESM::RefId id) const;
SkillValue& getSkill(ESM::RefId id);
void setSkill(ESM::RefId id, const SkillValue& value);
int getFactionRank(const ESM::RefId& faction) const;
const std::map<ESM::RefId, int>& getFactionRanks() const;
@ -79,12 +80,12 @@ namespace MWMechanics
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.
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;
@ -133,6 +134,8 @@ namespace MWMechanics
void readState(const ESM::CreatureStats& 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)
{
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)
magnitude = std::min(skill.getModified(), magnitude);
skill.damage(magnitude);
@ -114,14 +114,14 @@ namespace
void restoreSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{
auto& npcStats = target.getClass().getNpcStats(target);
auto& skill = npcStats.getSkill(effect.mArg);
auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(effect.mArg));
skill.restore(magnitude);
}
void fortifySkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{
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);
}
@ -668,7 +668,7 @@ namespace MWMechanics
if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
{
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:
skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f));
}
@ -760,7 +760,7 @@ namespace MWMechanics
{
// Abilities affect base stats, but not for drain
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);
}
else
@ -1218,7 +1218,7 @@ namespace MWMechanics
if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
{
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);
}
else

View file

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

View file

@ -15,9 +15,9 @@
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::Conjuration,
ESM::Skill::Destruction,

View file

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

View file

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

View file

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

View file

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

View file

@ -16,19 +16,4 @@ namespace MWWorld
{
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:
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

View file

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

View file

@ -52,7 +52,7 @@ namespace MWWorld
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");
}
@ -209,9 +209,9 @@ namespace MWWorld
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
@ -438,7 +438,7 @@ namespace MWWorld
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");
}

View file

@ -14,6 +14,7 @@
#include "../mwmechanics/aisetting.hpp"
#include <components/esm/refid.hpp>
#include <components/esm3/loadskil.hpp>
namespace ESM
{
@ -208,10 +209,9 @@ namespace MWWorld
///
/// Default implementation: return (empty vector, false).
virtual int getEquipmentSkill(const ConstPtr& ptr) const;
/// Return the index of the skill this item corresponds to when equipped or -1, if there is
/// no such skill.
/// (default implementation: return -1)
virtual ESM::RefId getEquipmentSkill(const ConstPtr& ptr) const;
/// Return the index of the skill this item corresponds to when equipped.
/// (default implementation: return empty ref id)
virtual int getValue(const ConstPtr& ptr) const;
///< 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.
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.
///
/// (default implementations: throws an exception)
@ -340,7 +340,7 @@ namespace MWWorld
bool isPureLandCreature(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;
///< Read additional state from \a state into \a ptr.

View file

@ -236,7 +236,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_)
return;
}
static const ESM::Skill::SkillEnum weaponSkills[] = {
static const ESM::RefId weaponSkills[] = {
ESM::Skill::LongBlade,
ESM::Skill::Axe,
ESM::Skill::Spear,
@ -285,7 +285,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_)
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])
{
max = skillValue;

View file

@ -221,7 +221,7 @@ namespace MWWorld
creatureStats.mAiSettings[i].mMod = 0.f;
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;
}
}

View file

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

View file

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

View file

@ -114,7 +114,6 @@ namespace MWWorld
// Need to instantiate these before they're used
template class IndexedStore<ESM::MagicEffect>;
template class IndexedStore<ESM::Skill>;
template <class T, class Id>
TypedDynamicStore<T, Id>::TypedDynamicStore()
@ -169,12 +168,20 @@ namespace MWWorld
const T* TypedDynamicStore<T, Id>::searchRandom(const std::string_view prefix, Misc::Rng::Generator& prng) const
{
if constexpr (std::is_same_v<Id, ESM::RefId>)
{
if (prefix.empty())
{
if (!mShared.empty())
return mShared[Misc::Rng::rollDice(mShared.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;
}
else
@ -916,8 +923,6 @@ namespace MWWorld
// Skill
//=========================================================================
Store<ESM::Skill>::Store() {}
void Store<ESM::Skill>::setUp(const MWWorld::Store<ESM::GameSetting>& settings)
{
constexpr std::string_view skillValues[ESM::Skill::Length][3] = {
@ -950,15 +955,13 @@ namespace MWWorld
{ "sSkillSpeechcraft", "icons\\k\\stealth_speechcraft.dds", "fWerewolfSpeechcraft" },
{ "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 (found != mStatic.end())
if (skill->mIndex >= 0)
{
ESM::Skill& skill = found->second;
skill.mName = getGMSTString(settings, skillValues[i][0]);
skill.mIcon = skillValues[i][1];
skill.mWerewolfValue = getGMSTFloat(settings, skillValues[i][2]);
skill->mName = getGMSTString(settings, skillValues[skill->mIndex][0]);
skill->mIcon = skillValues[skill->mIndex][1];
skill->mWerewolfValue = getGMSTFloat(settings, skillValues[skill->mIndex][2]);
}
}
}
@ -1363,7 +1366,7 @@ template class MWWorld::TypedDynamicStore<ESM::Race>;
template class MWWorld::TypedDynamicStore<ESM::Region>;
template class MWWorld::TypedDynamicStore<ESM::Repair>;
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::SoundGenerator>;
template class MWWorld::TypedDynamicStore<ESM::Spell>;

View file

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

View file

@ -496,7 +496,7 @@ namespace
const RecordType* result = nullptr;
if constexpr (std::is_same_v<RecordType, ESM::LandTexture>)
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);
else
result = esmStore.get<RecordType>().search(refId);

View file

@ -37,11 +37,11 @@ namespace ESM
"Handtohand",
};
Skill::SkillEnum Skill::stringToSkillId(std::string_view skill)
int Skill::stringToSkillId(std::string_view skill)
{
for (int id = 0; id < Skill::Length; ++id)
if (Misc::StringUtils::ciEqual(sSkillNames[id], skill))
return Skill::SkillEnum(id);
return id;
throw std::logic_error("No such skill: " + std::string(skill));
}
@ -75,6 +75,8 @@ namespace ESM
}
if (!hasIndex)
esm.fail("Missing INDX");
else if (mIndex < 0 || mIndex >= Length)
esm.fail("Invalid INDX");
if (!hasData)
esm.fail("Missing SKDT");
@ -101,7 +103,7 @@ namespace ESM
RefId Skill::indexToRefId(int index)
{
if (index == -1)
if (index < 0 || index >= Length)
return RefId();
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,
// they only have a numerical index that matches one of the
// hard-coded skills in the game.
int mIndex;
int mIndex{ -1 };
std::string mDescription;
std::string mName;
std::string mIcon;
float mWerewolfValue{};
enum SkillEnum
{
Block = 0,
Armorer = 1,
MediumArmor = 2,
HeavyArmor = 3,
BluntWeapon = 4,
LongBlade = 5,
Axe = 6,
Spear = 7,
Athletics = 8,
Enchant = 9,
Destruction = 10,
Alteration = 11,
Illusion = 12,
Conjuration = 13,
Mysticism = 14,
Restoration = 15,
Alchemy = 16,
Unarmored = 17,
Security = 18,
Sneak = 19,
Acrobatics = 20,
LightArmor = 21,
ShortBlade = 22,
Marksman = 23,
Mercantile = 24,
Speechcraft = 25,
HandToHand = 26,
Length
};
static constexpr IndexRefId Block{ sRecordId, 0 };
static constexpr IndexRefId Armorer{ sRecordId, 1 };
static constexpr IndexRefId MediumArmor{ sRecordId, 2 };
static constexpr IndexRefId HeavyArmor{ sRecordId, 3 };
static constexpr IndexRefId BluntWeapon{ sRecordId, 4 };
static constexpr IndexRefId LongBlade{ sRecordId, 5 };
static constexpr IndexRefId Axe{ sRecordId, 6 };
static constexpr IndexRefId Spear{ sRecordId, 7 };
static constexpr IndexRefId Athletics{ sRecordId, 8 };
static constexpr IndexRefId Enchant{ sRecordId, 9 };
static constexpr IndexRefId Destruction{ sRecordId, 10 };
static constexpr IndexRefId Alteration{ sRecordId, 11 };
static constexpr IndexRefId Illusion{ sRecordId, 12 };
static constexpr IndexRefId Conjuration{ sRecordId, 13 };
static constexpr IndexRefId Mysticism{ sRecordId, 14 };
static constexpr IndexRefId Restoration{ sRecordId, 15 };
static constexpr IndexRefId Alchemy{ sRecordId, 16 };
static constexpr IndexRefId Unarmored{ sRecordId, 17 };
static constexpr IndexRefId Security{ sRecordId, 18 };
static constexpr IndexRefId Sneak{ sRecordId, 19 };
static constexpr IndexRefId Acrobatics{ sRecordId, 20 };
static constexpr IndexRefId LightArmor{ sRecordId, 21 };
static constexpr IndexRefId ShortBlade{ sRecordId, 22 };
static constexpr IndexRefId Marksman{ sRecordId, 23 };
static constexpr IndexRefId Mercantile{ sRecordId, 24 };
static constexpr IndexRefId Speechcraft{ sRecordId, 25 };
static constexpr IndexRefId HandToHand{ sRecordId, 26 };
static constexpr int Length = 27;
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 save(ESMWriter& esm, bool isDeleted = false) const;

View file

@ -109,13 +109,13 @@ namespace ESM
ESM::RefId mSoundIdUp;
std::string mAttachBone;
std::string mSheathingBone;
Skill::SkillEnum mSkill;
ESM::RefId mSkill;
Class mWeaponClass;
int mAmmoType;
int mFlags;
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))
, mLongGroup(std::move(longGroup))
, mSoundIdDown(ESM::RefId::stringRefId(soundId + " Down"))

View file

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

View file

@ -14,45 +14,27 @@
<Property key="Caption" value="#{sSpecializationCombat}"/>
<Property key="TextAlign" value="Left Top"/>
</Widget>
<Widget type="MWSkill" skin="MW_StatNameButton" position="0 50 154 18" name="CombatSkill0" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="0 68 154 18" name="CombatSkill1" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="0 86 154 18" name="CombatSkill2" align="Left Top"/>
<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"/>
<Widget type="ScrollView" skin="MW_ScrollView" position="0 50 154 168" name="CombatSkills" align="Left Top">
<Property key="CanvasAlign" value="Left"/>
</Widget>
<!-- Magic list -->
<Widget type="TextBox" skin="HeaderText" position="158 32 154 18" name="MagicLabelT" align="Left Top">
<Property key="Caption" value="#{sSpecializationMagic}"/>
<Property key="TextAlign" value="Left Top"/>
</Widget>
<Widget type="MWSkill" skin="MW_StatNameButton" position="158 50 154 18" name="MagicSkill0" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="158 68 154 18" name="MagicSkill1" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="158 86 154 18" name="MagicSkill2" align="Left Top"/>
<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"/>
<Widget type="ScrollView" skin="MW_ScrollView" position="158 50 154 168" name="MagicSkills" align="Left Top">
<Property key="CanvasAlign" value="Left"/>
</Widget>
<!-- Stealth list -->
<Widget type="TextBox" skin="HeaderText" position="316 32 131 18" name="StealthLabelT" align="Left Top">
<Property key="Caption" value="#{sSpecializationStealth}"/>
<Property key="TextAlign" value="Left Top"/>
</Widget>
<Widget type="MWSkill" skin="MW_StatNameButton" position="316 50 154 18" name="StealthSkill0" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="316 68 154 18" name="StealthSkill1" align="Left Top"/>
<Widget type="MWSkill" skin="MW_StatNameButton" position="316 86 154 18" name="StealthSkill2" align="Left Top"/>
<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"/>
<Widget type="ScrollView" skin="MW_ScrollView" position="316 50 154 168" name="StealthSkills" align="Left Top">
<Property key="CanvasAlign" value="Left"/>
</Widget>
<!-- Dialog buttons -->
<Widget type="HBox" position="0 218 457 28">