#include "loadmgef.hpp" #include #include "components/esm/defs.hpp" #include "esmreader.hpp" #include "esmwriter.hpp" #include namespace ESM { namespace { static const char* sIds[MagicEffect::Length] = { "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 only "SummonFabricant", // Bloodmoon only "SummonWolf", "SummonBear", "SummonBonewolf", "SummonCreature04", "SummonCreature05", }; 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 }; } } namespace ESM { 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 = indexToId(mIndex); esm.getHNTSized<36>(mData, "MEDT"); if (esm.getFormat() == 0) { // 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.getHString(); break; case fourCC("CSND"): mCastSound = esm.getHString(); break; case fourCC("HSND"): mHitSound = esm.getHString(); break; case fourCC("ASND"): mAreaSound = esm.getHString(); break; case fourCC("CVFX"): mCasting = esm.getHString(); break; case fourCC("BVFX"): mBolt = esm.getHString(); break; case fourCC("HVFX"): mHit = esm.getHString(); break; case fourCC("AVFX"): mArea = esm.getHString(); 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.writeHNT("MEDT", mData, 36); esm.writeHNOCString("ITEX", mIcon); esm.writeHNOCString("PTEX", mParticle); esm.writeHNOCString("BSND", mBoltSound); esm.writeHNOCString("CSND", mCastSound); esm.writeHNOCString("HSND", mHitSound); esm.writeHNOCString("ASND", mAreaSound); esm.writeHNOCString("CVFX", mCasting); esm.writeHNOCString("BVFX", mBolt); esm.writeHNOCString("HVFX", mHit); esm.writeHNOCString("AVFX", mArea); esm.writeHNOString("DESC", mDescription); } short MagicEffect::getResistanceEffect(short effect) { // Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute // static std::map 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 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; } static std::map genNameMap() { // Map effect ID to GMST name // http://www.uesp.net/morrow/hints/mweffects.shtml std::map names; names[85] = "sEffectAbsorbAttribute"; names[88] = "sEffectAbsorbFatigue"; names[86] = "sEffectAbsorbHealth"; names[87] = "sEffectAbsorbSpellPoints"; names[89] = "sEffectAbsorbSkill"; names[63] = "sEffectAlmsiviIntervention"; names[47] = "sEffectBlind"; names[123] = "sEffectBoundBattleAxe"; names[129] = "sEffectBoundBoots"; names[127] = "sEffectBoundCuirass"; names[120] = "sEffectBoundDagger"; names[131] = "sEffectBoundGloves"; names[128] = "sEffectBoundHelm"; names[125] = "sEffectBoundLongbow"; names[126] = "sEffectExtraSpell"; names[121] = "sEffectBoundLongsword"; names[122] = "sEffectBoundMace"; names[130] = "sEffectBoundShield"; names[124] = "sEffectBoundSpear"; names[7] = "sEffectBurden"; names[50] = "sEffectCalmCreature"; names[49] = "sEffectCalmHumanoid"; names[40] = "sEffectChameleon"; names[44] = "sEffectCharm"; names[118] = "sEffectCommandCreatures"; names[119] = "sEffectCommandHumanoids"; names[132] = "sEffectCorpus"; // NB this typo. (bethesda made it) names[70] = "sEffectCureBlightDisease"; names[69] = "sEffectCureCommonDisease"; names[71] = "sEffectCureCorprusDisease"; names[73] = "sEffectCureParalyzation"; names[72] = "sEffectCurePoison"; names[22] = "sEffectDamageAttribute"; names[25] = "sEffectDamageFatigue"; names[23] = "sEffectDamageHealth"; names[24] = "sEffectDamageMagicka"; names[26] = "sEffectDamageSkill"; names[54] = "sEffectDemoralizeCreature"; names[53] = "sEffectDemoralizeHumanoid"; names[64] = "sEffectDetectAnimal"; names[65] = "sEffectDetectEnchantment"; names[66] = "sEffectDetectKey"; names[38] = "sEffectDisintegrateArmor"; names[37] = "sEffectDisintegrateWeapon"; names[57] = "sEffectDispel"; names[62] = "sEffectDivineIntervention"; names[17] = "sEffectDrainAttribute"; names[20] = "sEffectDrainFatigue"; names[18] = "sEffectDrainHealth"; names[19] = "sEffectDrainSpellpoints"; names[21] = "sEffectDrainSkill"; names[8] = "sEffectFeather"; names[14] = "sEffectFireDamage"; names[4] = "sEffectFireShield"; names[117] = "sEffectFortifyAttackBonus"; names[79] = "sEffectFortifyAttribute"; names[82] = "sEffectFortifyFatigue"; names[80] = "sEffectFortifyHealth"; names[81] = "sEffectFortifySpellpoints"; names[84] = "sEffectFortifyMagickaMultiplier"; names[83] = "sEffectFortifySkill"; names[52] = "sEffectFrenzyCreature"; names[51] = "sEffectFrenzyHumanoid"; names[16] = "sEffectFrostDamage"; names[6] = "sEffectFrostShield"; names[39] = "sEffectInvisibility"; names[9] = "sEffectJump"; names[10] = "sEffectLevitate"; names[41] = "sEffectLight"; names[5] = "sEffectLightningShield"; names[12] = "sEffectLock"; names[60] = "sEffectMark"; names[43] = "sEffectNightEye"; names[13] = "sEffectOpen"; names[45] = "sEffectParalyze"; names[27] = "sEffectPoison"; names[56] = "sEffectRallyCreature"; names[55] = "sEffectRallyHumanoid"; names[61] = "sEffectRecall"; names[68] = "sEffectReflect"; names[100] = "sEffectRemoveCurse"; names[95] = "sEffectResistBlightDisease"; names[94] = "sEffectResistCommonDisease"; names[96] = "sEffectResistCorprusDisease"; names[90] = "sEffectResistFire"; names[91] = "sEffectResistFrost"; names[93] = "sEffectResistMagicka"; names[98] = "sEffectResistNormalWeapons"; names[99] = "sEffectResistParalysis"; names[97] = "sEffectResistPoison"; names[92] = "sEffectResistShock"; names[74] = "sEffectRestoreAttribute"; names[77] = "sEffectRestoreFatigue"; names[75] = "sEffectRestoreHealth"; names[76] = "sEffectRestoreSpellPoints"; names[78] = "sEffectRestoreSkill"; names[42] = "sEffectSanctuary"; names[3] = "sEffectShield"; names[15] = "sEffectShockDamage"; names[46] = "sEffectSilence"; names[11] = "sEffectSlowFall"; names[58] = "sEffectSoultrap"; names[48] = "sEffectSound"; names[67] = "sEffectSpellAbsorption"; names[136] = "sEffectStuntedMagicka"; names[106] = "sEffectSummonAncestralGhost"; names[110] = "sEffectSummonBonelord"; names[108] = "sEffectSummonLeastBonewalker"; names[134] = "sEffectSummonCenturionSphere"; names[103] = "sEffectSummonClannfear"; names[104] = "sEffectSummonDaedroth"; names[105] = "sEffectSummonDremora"; names[114] = "sEffectSummonFlameAtronach"; names[115] = "sEffectSummonFrostAtronach"; names[113] = "sEffectSummonGoldenSaint"; names[109] = "sEffectSummonGreaterBonewalker"; names[112] = "sEffectSummonHunger"; names[102] = "sEffectSummonScamp"; names[107] = "sEffectSummonSkeletalMinion"; names[116] = "sEffectSummonStormAtronach"; names[111] = "sEffectSummonWingedTwilight"; names[135] = "sEffectSunDamage"; names[1] = "sEffectSwiftSwim"; names[59] = "sEffectTelekinesis"; names[101] = "sEffectTurnUndead"; names[133] = "sEffectVampirism"; names[0] = "sEffectWaterBreathing"; names[2] = "sEffectWaterWalking"; names[33] = "sEffectWeaknesstoBlightDisease"; names[32] = "sEffectWeaknesstoCommonDisease"; names[34] = "sEffectWeaknesstoCorprusDisease"; names[28] = "sEffectWeaknesstoFire"; names[29] = "sEffectWeaknesstoFrost"; names[31] = "sEffectWeaknesstoMagicka"; names[36] = "sEffectWeaknesstoNormalWeapons"; names[35] = "sEffectWeaknesstoPoison"; names[30] = "sEffectWeaknesstoShock"; // bloodmoon names[138] = "sEffectSummonCreature01"; names[139] = "sEffectSummonCreature02"; names[140] = "sEffectSummonCreature03"; names[141] = "sEffectSummonCreature04"; names[142] = "sEffectSummonCreature05"; // tribunal names[137] = "sEffectSummonFabricant"; return names; } const std::map MagicEffect::sNames = genNameMap(); const std::string& MagicEffect::effectIdToString(short effectID) { std::map::const_iterator name = sNames.find(effectID); if (name == sNames.end()) throw std::runtime_error(std::string("Unimplemented effect ID ") + std::to_string(effectID)); return name->second; } class FindSecond { std::string_view mName; public: FindSecond(std::string_view name) : mName(name) { } bool operator()(const std::pair& item) const { if (Misc::StringUtils::ciEqual(item.second, mName)) return true; return false; } }; short MagicEffect::effectStringToId(std::string_view effect) { std::map::const_iterator name; name = std::find_if(sNames.begin(), sNames.end(), FindSecond(effect)); if (name == sNames.end()) throw std::runtime_error("Unimplemented effect " + std::string(effect)); return name->first; } 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 = 0; mData.mBaseCost = 0; mData.mFlags = 0; mData.mRed = 0; mData.mGreen = 0; mData.mBlue = 0; mData.mSpeed = 0; mIcon.clear(); mParticle.clear(); mCasting.clear(); mHit.clear(); mArea.clear(); mBolt.clear(); mCastSound.clear(); mBoltSound.clear(); mHitSound.clear(); mAreaSound.clear(); mDescription.clear(); } std::string MagicEffect::indexToId(int index) { std::ostringstream stream; if (index != -1) { stream << "#"; if (index < 100) { stream << "0"; if (index < 10) stream << "0"; } stream << index; if (index >= 0 && index < Length) stream << sIds[index]; } return stream.str(); } }