You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw/components/esm3/loadmgef.cpp

655 lines
21 KiB
C++

#include "loadmgef.hpp"
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include "loadskil.hpp"
#include <components/misc/strings/algorithm.hpp>
namespace ESM
{
namespace
{
const int NumberOfHardcodedFlags = 143;
const int HardcodedFlags[NumberOfHardcodedFlags] = { 0x11c8, 0x11c0, 0x11c8, 0x11e0, 0x11e0, 0x11e0, 0x11e0,
0x11d0, 0x11c0, 0x11c0, 0x11e0, 0x11c0, 0x11184, 0x11184, 0x1f0, 0x1f0, 0x1f0, 0x11d2, 0x11f0, 0x11d0,
0x11d0, 0x11d1, 0x1d2, 0x1f0, 0x1d0, 0x1d0, 0x1d1, 0x1f0, 0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x11d0,
0x11d0, 0x11d0, 0x11d0, 0x1d0, 0x1d0, 0x11c8, 0x31c0, 0x11c0, 0x11c0, 0x11c0, 0x1180, 0x11d8, 0x11d8,
0x11d0, 0x11d0, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11c4, 0x111b8,
0x1040, 0x104c, 0x104c, 0x104c, 0x104c, 0x1040, 0x1040, 0x1040, 0x11c0, 0x11c0, 0x1cc, 0x1cc, 0x1cc, 0x1cc,
0x1cc, 0x1c2, 0x1c0, 0x1c0, 0x1c0, 0x1c1, 0x11c2, 0x11c0, 0x11c0, 0x11c0, 0x11c1, 0x11c0, 0x21192, 0x20190,
0x20190, 0x20190, 0x21191, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0,
0x1c0, 0x11190, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048,
0x9048, 0x9048, 0x9048, 0x9048, 0x11c0, 0x1180, 0x1180, 0x5048, 0x5048, 0x5048, 0x5048, 0x5048, 0x5048,
0x1188, 0x5048, 0x5048, 0x5048, 0x5048, 0x5048, 0x1048, 0x104c, 0x1048, 0x40, 0x11c8, 0x1048, 0x1048,
0x1048, 0x1048, 0x1048, 0x1048 };
}
void MagicEffect::load(ESMReader& esm, bool& isDeleted)
{
isDeleted = false; // MagicEffect record can't be deleted now (may be changed in the future)
mRecordFlags = esm.getRecordFlags();
esm.getHNT(mIndex, "INDX");
mId = indexToRefId(mIndex);
esm.getSubNameIs("MEDT");
esm.getSubHeader();
int32_t school;
esm.getT(school);
mData.mSchool = MagicSchool::indexToSkillRefId(school);
esm.getT(mData.mBaseCost);
esm.getT(mData.mFlags);
esm.getT(mData.mRed);
esm.getT(mData.mGreen);
esm.getT(mData.mBlue);
esm.getT(mData.mUnknown1);
esm.getT(mData.mSpeed);
esm.getT(mData.mUnknown2);
if (esm.getFormatVersion() == DefaultFormatVersion)
{
// don't allow mods to change fixed flags in the legacy format
mData.mFlags &= (AllowSpellmaking | AllowEnchanting | NegativeLight);
if (mIndex >= 0 && mIndex < NumberOfHardcodedFlags)
mData.mFlags |= HardcodedFlags[mIndex];
}
// vanilla MW accepts the _SND subrecords before or after DESC... I hope
// this isn't true for other records, or we have to do a mass-refactor
while (esm.hasMoreSubs())
{
esm.getSubName();
switch (esm.retSubName().toInt())
{
case fourCC("ITEX"):
mIcon = esm.getHString();
break;
case fourCC("PTEX"):
mParticle = esm.getHString();
break;
case fourCC("BSND"):
mBoltSound = esm.getRefId();
break;
case fourCC("CSND"):
mCastSound = esm.getRefId();
break;
case fourCC("HSND"):
mHitSound = esm.getRefId();
break;
case fourCC("ASND"):
mAreaSound = esm.getRefId();
break;
case fourCC("CVFX"):
mCasting = esm.getRefId();
break;
case fourCC("BVFX"):
mBolt = esm.getRefId();
break;
case fourCC("HVFX"):
mHit = esm.getRefId();
break;
case fourCC("AVFX"):
mArea = esm.getRefId();
break;
case fourCC("DESC"):
mDescription = esm.getHString();
break;
default:
esm.fail("Unknown subrecord");
}
}
}
void MagicEffect::save(ESMWriter& esm, bool /*isDeleted*/) const
{
esm.writeHNT("INDX", mIndex);
esm.startSubRecord("MEDT");
esm.writeT(MagicSchool::skillRefIdToIndex(mData.mSchool));
esm.writeT(mData.mBaseCost);
esm.writeT(mData.mFlags);
esm.writeT(mData.mRed);
esm.writeT(mData.mGreen);
esm.writeT(mData.mBlue);
esm.writeT(mData.mUnknown1);
esm.writeT(mData.mSpeed);
esm.writeT(mData.mUnknown2);
esm.endRecord("MEDT");
esm.writeHNOCString("ITEX", mIcon);
esm.writeHNOCString("PTEX", mParticle);
esm.writeHNOCRefId("BSND", mBoltSound);
esm.writeHNOCRefId("CSND", mCastSound);
esm.writeHNOCRefId("HSND", mHitSound);
esm.writeHNOCRefId("ASND", mAreaSound);
esm.writeHNOCRefId("CVFX", mCasting);
esm.writeHNOCRefId("BVFX", mBolt);
esm.writeHNOCRefId("HVFX", mHit);
esm.writeHNOCRefId("AVFX", mArea);
esm.writeHNOString("DESC", mDescription);
}
short MagicEffect::getResistanceEffect(short effect)
{
// Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute
// <Effect, Effect providing resistance against first effect>
static std::map<short, short> effects;
if (effects.empty())
{
effects[DisintegrateArmor] = Sanctuary;
effects[DisintegrateWeapon] = Sanctuary;
for (int i = DrainAttribute; i <= DamageSkill; ++i)
effects[i] = ResistMagicka;
for (int i = AbsorbAttribute; i <= AbsorbSkill; ++i)
effects[i] = ResistMagicka;
for (int i = WeaknessToFire; i <= WeaknessToNormalWeapons; ++i)
effects[i] = ResistMagicka;
effects[Burden] = ResistMagicka;
effects[Charm] = ResistMagicka;
effects[Silence] = ResistMagicka;
effects[Blind] = ResistMagicka;
effects[Sound] = ResistMagicka;
for (int i = 0; i < 2; ++i)
{
effects[CalmHumanoid + i] = ResistMagicka;
effects[FrenzyHumanoid + i] = ResistMagicka;
effects[DemoralizeHumanoid + i] = ResistMagicka;
effects[RallyHumanoid + i] = ResistMagicka;
}
effects[TurnUndead] = ResistMagicka;
effects[FireDamage] = ResistFire;
effects[FrostDamage] = ResistFrost;
effects[ShockDamage] = ResistShock;
effects[Vampirism] = ResistCommonDisease;
effects[Corprus] = ResistCorprusDisease;
effects[Poison] = ResistPoison;
effects[Paralyze] = ResistParalysis;
}
if (effects.find(effect) != effects.end())
return effects[effect];
else
return -1;
}
short MagicEffect::getWeaknessEffect(short effect)
{
static std::map<short, short> effects;
if (effects.empty())
{
for (int i = DrainAttribute; i <= DamageSkill; ++i)
effects[i] = WeaknessToMagicka;
for (int i = AbsorbAttribute; i <= AbsorbSkill; ++i)
effects[i] = WeaknessToMagicka;
for (int i = WeaknessToFire; i <= WeaknessToNormalWeapons; ++i)
effects[i] = WeaknessToMagicka;
effects[Burden] = WeaknessToMagicka;
effects[Charm] = WeaknessToMagicka;
effects[Silence] = WeaknessToMagicka;
effects[Blind] = WeaknessToMagicka;
effects[Sound] = WeaknessToMagicka;
for (int i = 0; i < 2; ++i)
{
effects[CalmHumanoid + i] = WeaknessToMagicka;
effects[FrenzyHumanoid + i] = WeaknessToMagicka;
effects[DemoralizeHumanoid + i] = WeaknessToMagicka;
effects[RallyHumanoid + i] = WeaknessToMagicka;
}
effects[TurnUndead] = WeaknessToMagicka;
effects[FireDamage] = WeaknessToFire;
effects[FrostDamage] = WeaknessToFrost;
effects[ShockDamage] = WeaknessToShock;
effects[Vampirism] = WeaknessToCommonDisease;
effects[Corprus] = WeaknessToCorprusDisease;
effects[Poison] = WeaknessToPoison;
effects[Paralyze] = -1;
}
if (effects.find(effect) != effects.end())
return effects[effect];
else
return -1;
}
// Map effect ID to GMST name
const std::array<std::string, MagicEffect::Length> MagicEffect::sGmstEffectIds = {
"sEffectWaterBreathing",
"sEffectSwiftSwim",
"sEffectWaterWalking",
"sEffectShield",
"sEffectFireShield",
"sEffectLightningShield",
"sEffectFrostShield",
"sEffectBurden",
"sEffectFeather",
"sEffectJump",
"sEffectLevitate",
"sEffectSlowFall",
"sEffectLock",
"sEffectOpen",
"sEffectFireDamage",
"sEffectShockDamage",
"sEffectFrostDamage",
"sEffectDrainAttribute",
"sEffectDrainHealth",
"sEffectDrainSpellpoints",
"sEffectDrainFatigue",
"sEffectDrainSkill",
"sEffectDamageAttribute",
"sEffectDamageHealth",
"sEffectDamageMagicka",
"sEffectDamageFatigue",
"sEffectDamageSkill",
"sEffectPoison",
"sEffectWeaknesstoFire",
"sEffectWeaknesstoFrost",
"sEffectWeaknesstoShock",
"sEffectWeaknesstoMagicka",
"sEffectWeaknesstoCommonDisease",
"sEffectWeaknesstoBlightDisease",
"sEffectWeaknesstoCorprusDisease",
"sEffectWeaknesstoPoison",
"sEffectWeaknesstoNormalWeapons",
"sEffectDisintegrateWeapon",
"sEffectDisintegrateArmor",
"sEffectInvisibility",
"sEffectChameleon",
"sEffectLight",
"sEffectSanctuary",
"sEffectNightEye",
"sEffectCharm",
"sEffectParalyze",
"sEffectSilence",
"sEffectBlind",
"sEffectSound",
"sEffectCalmHumanoid",
"sEffectCalmCreature",
"sEffectFrenzyHumanoid",
"sEffectFrenzyCreature",
"sEffectDemoralizeHumanoid",
"sEffectDemoralizeCreature",
"sEffectRallyHumanoid",
"sEffectRallyCreature",
"sEffectDispel",
"sEffectSoultrap",
"sEffectTelekinesis",
"sEffectMark",
"sEffectRecall",
"sEffectDivineIntervention",
"sEffectAlmsiviIntervention",
"sEffectDetectAnimal",
"sEffectDetectEnchantment",
"sEffectDetectKey",
"sEffectSpellAbsorption",
"sEffectReflect",
"sEffectCureCommonDisease",
"sEffectCureBlightDisease",
"sEffectCureCorprusDisease",
"sEffectCurePoison",
"sEffectCureParalyzation",
"sEffectRestoreAttribute",
"sEffectRestoreHealth",
"sEffectRestoreSpellPoints",
"sEffectRestoreFatigue",
"sEffectRestoreSkill",
"sEffectFortifyAttribute",
"sEffectFortifyHealth",
"sEffectFortifySpellpoints",
"sEffectFortifyFatigue",
"sEffectFortifySkill",
"sEffectFortifyMagickaMultiplier",
"sEffectAbsorbAttribute",
"sEffectAbsorbHealth",
"sEffectAbsorbSpellPoints",
"sEffectAbsorbFatigue",
"sEffectAbsorbSkill",
"sEffectResistFire",
"sEffectResistFrost",
"sEffectResistShock",
"sEffectResistMagicka",
"sEffectResistCommonDisease",
"sEffectResistBlightDisease",
"sEffectResistCorprusDisease",
"sEffectResistPoison",
"sEffectResistNormalWeapons",
"sEffectResistParalysis",
"sEffectRemoveCurse",
"sEffectTurnUndead",
"sEffectSummonScamp",
"sEffectSummonClannfear",
"sEffectSummonDaedroth",
"sEffectSummonDremora",
"sEffectSummonAncestralGhost",
"sEffectSummonSkeletalMinion",
"sEffectSummonLeastBonewalker",
"sEffectSummonGreaterBonewalker",
"sEffectSummonBonelord",
"sEffectSummonWingedTwilight",
"sEffectSummonHunger",
"sEffectSummonGoldenSaint",
"sEffectSummonFlameAtronach",
"sEffectSummonFrostAtronach",
"sEffectSummonStormAtronach",
"sEffectFortifyAttackBonus",
"sEffectCommandCreatures",
"sEffectCommandHumanoids",
"sEffectBoundDagger",
"sEffectBoundLongsword",
"sEffectBoundMace",
"sEffectBoundBattleAxe",
"sEffectBoundSpear",
"sEffectBoundLongbow",
"sEffectExtraSpell",
"sEffectBoundCuirass",
"sEffectBoundHelm",
"sEffectBoundBoots",
"sEffectBoundShield",
"sEffectBoundGloves",
"sEffectCorpus", // NB this typo. (bethesda made it)
"sEffectVampirism",
"sEffectSummonCenturionSphere",
"sEffectSunDamage",
"sEffectStuntedMagicka",
// tribunal
"sEffectSummonFabricant",
// bloodmoon
"sEffectSummonCreature01",
"sEffectSummonCreature02",
"sEffectSummonCreature03",
"sEffectSummonCreature04",
"sEffectSummonCreature05",
};
// Map effect ID to identifying name
const std::array<std::string_view, MagicEffect::Length> MagicEffect::sIndexNames = {
"WaterBreathing",
"SwiftSwim",
"WaterWalking",
"Shield",
"FireShield",
"LightningShield",
"FrostShield",
"Burden",
"Feather",
"Jump",
"Levitate",
"SlowFall",
"Lock",
"Open",
"FireDamage",
"ShockDamage",
"FrostDamage",
"DrainAttribute",
"DrainHealth",
"DrainMagicka",
"DrainFatigue",
"DrainSkill",
"DamageAttribute",
"DamageHealth",
"DamageMagicka",
"DamageFatigue",
"DamageSkill",
"Poison",
"WeaknessToFire",
"WeaknessToFrost",
"WeaknessToShock",
"WeaknessToMagicka",
"WeaknessToCommonDisease",
"WeaknessToBlightDisease",
"WeaknessToCorprusDisease",
"WeaknessToPoison",
"WeaknessToNormalWeapons",
"DisintegrateWeapon",
"DisintegrateArmor",
"Invisibility",
"Chameleon",
"Light",
"Sanctuary",
"NightEye",
"Charm",
"Paralyze",
"Silence",
"Blind",
"Sound",
"CalmHumanoid",
"CalmCreature",
"FrenzyHumanoid",
"FrenzyCreature",
"DemoralizeHumanoid",
"DemoralizeCreature",
"RallyHumanoid",
"RallyCreature",
"Dispel",
"Soultrap",
"Telekinesis",
"Mark",
"Recall",
"DivineIntervention",
"AlmsiviIntervention",
"DetectAnimal",
"DetectEnchantment",
"DetectKey",
"SpellAbsorption",
"Reflect",
"CureCommonDisease",
"CureBlightDisease",
"CureCorprusDisease",
"CurePoison",
"CureParalyzation",
"RestoreAttribute",
"RestoreHealth",
"RestoreMagicka",
"RestoreFatigue",
"RestoreSkill",
"FortifyAttribute",
"FortifyHealth",
"FortifyMagicka",
"FortifyFatigue",
"FortifySkill",
"FortifyMaximumMagicka",
"AbsorbAttribute",
"AbsorbHealth",
"AbsorbMagicka",
"AbsorbFatigue",
"AbsorbSkill",
"ResistFire",
"ResistFrost",
"ResistShock",
"ResistMagicka",
"ResistCommonDisease",
"ResistBlightDisease",
"ResistCorprusDisease",
"ResistPoison",
"ResistNormalWeapons",
"ResistParalysis",
"RemoveCurse",
"TurnUndead",
"SummonScamp",
"SummonClannfear",
"SummonDaedroth",
"SummonDremora",
"SummonAncestralGhost",
"SummonSkeletalMinion",
"SummonBonewalker",
"SummonGreaterBonewalker",
"SummonBonelord",
"SummonWingedTwilight",
"SummonHunger",
"SummonGoldenSaint",
"SummonFlameAtronach",
"SummonFrostAtronach",
"SummonStormAtronach",
"FortifyAttack",
"CommandCreature",
"CommandHumanoid",
"BoundDagger",
"BoundLongsword",
"BoundMace",
"BoundBattleAxe",
"BoundSpear",
"BoundLongbow",
"ExtraSpell",
"BoundCuirass",
"BoundHelm",
"BoundBoots",
"BoundShield",
"BoundGloves",
"Corprus",
"Vampirism",
"SummonCenturionSphere",
"SunDamage",
"StuntedMagicka",
// tribunal
"SummonFabricant",
// bloodmoon
"SummonWolf",
"SummonBear",
"SummonBonewolf",
"SummonCreature04",
"SummonCreature05",
};
template <typename Collection>
static std::map<std::string_view, int, Misc::StringUtils::CiComp> initStringToIntMap(const Collection& strings)
{
std::map<std::string_view, int, Misc::StringUtils::CiComp> map;
for (size_t i = 0; i < strings.size(); i++)
map[strings[i]] = i;
return map;
}
const std::map<std::string_view, int, Misc::StringUtils::CiComp> MagicEffect::sGmstEffectIdToIndexMap
= initStringToIntMap(MagicEffect::sGmstEffectIds);
const std::map<std::string_view, int, Misc::StringUtils::CiComp> MagicEffect::sIndexNameToIndexMap
= initStringToIntMap(MagicEffect::sIndexNames);
class FindSecond
{
std::string_view mName;
public:
FindSecond(std::string_view name)
: mName(name)
{
}
bool operator()(const std::pair<short, std::string>& item) const
{
if (Misc::StringUtils::ciEqual(item.second, mName))
return true;
return false;
}
};
MagicEffect::MagnitudeDisplayType MagicEffect::getMagnitudeDisplayType() const
{
if (mData.mFlags & NoMagnitude)
return MDT_None;
if (mIndex == 84)
return MDT_TimesInt;
if (mIndex == 59 || (mIndex >= 64 && mIndex <= 66))
return MDT_Feet;
if (mIndex == 118 || mIndex == 119)
return MDT_Level;
if ((mIndex >= 28 && mIndex <= 36) || (mIndex >= 90 && mIndex <= 99) || mIndex == 40 || mIndex == 47
|| mIndex == 57 || mIndex == 68)
return MDT_Percentage;
return MDT_Points;
}
void MagicEffect::blank()
{
mRecordFlags = 0;
mData.mSchool = ESM::Skill::Alteration;
mData.mBaseCost = 0;
mData.mFlags = 0;
mData.mRed = 0;
mData.mGreen = 0;
mData.mBlue = 0;
mData.mSpeed = 1;
mIcon.clear();
mParticle.clear();
mCasting = ESM::RefId();
mHit = ESM::RefId();
mArea = ESM::RefId();
mBolt = ESM::RefId();
mCastSound = ESM::RefId();
mBoltSound = ESM::RefId();
mHitSound = ESM::RefId();
mAreaSound = ESM::RefId();
mDescription.clear();
}
osg::Vec4f MagicEffect::getColor() const
{
osg::Vec4f color{ mData.mRed / 255.f, mData.mGreen / 255.f, mData.mBlue / 255.f, 1.f };
if (mData.mFlags & NegativeLight)
return osg::Vec4f(1.f, 1.f, 1.f, 2.f) - color;
return color;
}
const std::string& MagicEffect::indexToGmstString(int effectID)
{
if (effectID < 0 || static_cast<std::size_t>(effectID) >= sGmstEffectIds.size())
throw std::runtime_error(std::string("Unimplemented effect ID ") + std::to_string(effectID));
return sGmstEffectIds[effectID];
}
std::string_view MagicEffect::indexToName(int effectID)
{
if (effectID < 0 || static_cast<std::size_t>(effectID) >= sIndexNames.size())
throw std::runtime_error(std::string("Unimplemented effect ID ") + std::to_string(effectID));
return sIndexNames[effectID];
}
int MagicEffect::indexNameToIndex(std::string_view effect)
{
auto name = sIndexNameToIndexMap.find(effect);
if (name == sIndexNameToIndexMap.end())
return -1;
return name->second;
}
int MagicEffect::effectGmstIdToIndex(std::string_view gmstId)
{
auto name = sGmstEffectIdToIndexMap.find(gmstId);
if (name == sGmstEffectIdToIndexMap.end())
throw std::runtime_error("Unimplemented effect " + std::string(gmstId));
return name->second;
}
RefId MagicEffect::indexToRefId(int index)
{
if (index == -1)
return RefId();
return RefId::index(sRecordId, static_cast<std::uint32_t>(index));
}
}