#include "statsextensions.hpp" #include #include #include #include "../mwworld/esmstore.hpp" #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "ref.hpp" namespace { ESM::RefId getDialogueActorFaction(const MWWorld::ConstPtr& actor) { ESM::RefId factionId = actor.getClass().getPrimaryFaction(actor); if (factionId.empty()) throw std::runtime_error("failed to determine dialogue actors faction (because actor is factionless)"); return factionId; } void modStat(MWMechanics::AttributeValue& stat, float amount) { const float base = stat.getBase(); const float modifier = stat.getModifier() - stat.getDamage(); const float modified = base + modifier; // Clamp to 100 unless base < 100 and we have a fortification going if ((modifier <= 0.f || base >= 100.f) && amount > 0.f) amount = std::clamp(100.f - modified, 0.f, amount); // Clamp the modified value in a way that doesn't properly account for negative numbers float newModified = modified + amount; if (newModified < 0.f) { if (modified >= 0.f) newModified = 0.f; else if (newModified < modified) newModified = modified; } // Calculate damage/fortification based on the clamped base value stat.setBase(std::clamp(base + amount, 0.f, 100.f), true); stat.setModifier(newModified - stat.getBase()); } template void updateBaseRecord(MWWorld::Ptr& ptr) { const auto& store = *MWBase::Environment::get().getESMStore(); const T* base = store.get().find(ptr.getCellRef().getRefId()); ptr.get()->mBase = base; } } namespace MWScript { namespace Stats { template class OpGetLevel : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).getLevel(); runtime.push(value); } }; template class OpSetLevel : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); ptr.getClass().getCreatureStats(ptr).setLevel(value); } }; template class OpGetAttribute : public Interpreter::Opcode0 { int mIndex; public: OpGetAttribute(int index) : mIndex(index) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex).getModified(); runtime.push(value); } }; template class OpSetAttribute : public Interpreter::Opcode0 { int mIndex; public: OpSetAttribute(int index) : mIndex(index) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); attribute.setBase(value, true); ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); } }; template class OpModAttribute : public Interpreter::Opcode0 { int mIndex; public: OpModAttribute(int index) : mIndex(index) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); modStat(attribute, value); ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); } }; template class OpGetDynamic : public Interpreter::Opcode0 { int mIndex; public: OpGetDynamic(int index) : mIndex(index) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value; if (mIndex == 0 && ptr.getClass().hasItemHealth(ptr)) { // health is a special case value = static_cast(ptr.getClass().getItemMaxHealth(ptr)); } else { value = ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex).getCurrent(); // GetMagicka shouldn't return negative values if (mIndex == 1 && value < 0) value = 0; } runtime.push(value); } }; template class OpSetDynamic : public Interpreter::Opcode0 { int mIndex; public: OpSetDynamic(int index) : mIndex(index) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::DynamicStat stat(ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex)); stat.setBase(value); stat.setCurrent(stat.getModified(false), true, true); ptr.getClass().getCreatureStats(ptr).setDynamic(mIndex, stat); } }; template class OpModDynamic : public Interpreter::Opcode0 { int mIndex; public: OpModDynamic(int index) : mIndex(index) { } void execute(Interpreter::Runtime& runtime) override { int peek = R::implicit ? 0 : runtime[0].mInteger; MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float diff = runtime[0].mFloat; runtime.pop(); // workaround broken endgame scripts that kill dagoth ur if (!R::implicit && ptr.getCellRef().getRefId() == "dagoth_ur_1") { runtime.push(peek); if (R()(runtime, false, true).isEmpty()) { Log(Debug::Warning) << "Warning: Compensating for broken script in Morrowind.esm by " << "ignoring remote access to dagoth_ur_1"; return; } } MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); MWMechanics::DynamicStat stat = stats.getDynamic(mIndex); float current = stat.getCurrent(); float base = diff + stat.getBase(); if (mIndex != 2) base = std::max(base, 0.f); stat.setBase(base); stat.setCurrent(diff + current, true, true); stats.setDynamic(mIndex, stat); } }; template class OpModCurrentDynamic : public Interpreter::Opcode0 { int mIndex; public: OpModCurrentDynamic(int index) : mIndex(index) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float diff = runtime[0].mFloat; runtime.pop(); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent(); MWMechanics::DynamicStat stat(ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex)); bool allowDecreaseBelowZero = false; if (mIndex == 2) // Fatigue-specific logic { // For fatigue, a negative current value is allowed and means the actor will be knocked down allowDecreaseBelowZero = true; // Knock down the actor immediately if a non-positive new value is the case if (diff + current <= 0.f) ptr.getClass().getCreatureStats(ptr).setKnockedDown(true); } stat.setCurrent(diff + current, allowDecreaseBelowZero); ptr.getClass().getCreatureStats(ptr).setDynamic(mIndex, stat); } }; template class OpGetDynamicGetRatio : public Interpreter::Opcode0 { int mIndex; public: OpGetDynamicGetRatio(int index) : mIndex(index) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); runtime.push(stats.getDynamic(mIndex).getRatio()); } }; template class OpGetSkill : public Interpreter::Opcode0 { ESM::RefId mId; public: OpGetSkill(ESM::RefId id) : mId(id) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = ptr.getClass().getSkill(ptr, mId); runtime.push(value); } }; template class OpSetSkill : public Interpreter::Opcode0 { ESM::RefId mId; public: OpSetSkill(ESM::RefId id) : mId(id) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats(ptr); stats.getSkill(mId).setBase(value, true); } }; template class OpModSkill : public Interpreter::Opcode0 { ESM::RefId mId; public: OpModSkill(ESM::RefId id) : mId(id) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); MWMechanics::SkillValue& skill = ptr.getClass().getNpcStats(ptr).getSkill(mId); modStat(skill, value); } }; class OpGetPCCrimeLevel : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); runtime.push(static_cast(player.getClass().getNpcStats(player).getBounty())); } }; class OpSetPCCrimeLevel : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); int bounty = static_cast(runtime[0].mFloat); runtime.pop(); player.getClass().getNpcStats(player).setBounty(bounty); if (bounty == 0) MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; class OpModPCCrimeLevel : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); player.getClass().getNpcStats(player).setBounty( static_cast(runtime[0].mFloat) + player.getClass().getNpcStats(player).getBounty()); runtime.pop(); } }; template class OpAddSpell : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(id); MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); creatureStats.getSpells().add(spell); ESM::Spell::SpellType type = static_cast(spell->mData.mType); if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Power) { // Add spell effect to *this actor's* queue immediately creatureStats.getActiveSpells().addSpell(spell, ptr); // Apply looping particles immediately for constant effects MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } } }; template class OpRemoveSpell : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); creatureStats.getSpells().remove(id); MWBase::WindowManager* wm = MWBase::Environment::get().getWindowManager(); if (ptr == MWMechanics::getPlayer() && id == wm->getSelectedSpell()) { wm->unsetSelectedSpell(); } } }; template class OpRemoveSpellEffects : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId spellid = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffects(ptr, spellid); } }; template class OpRemoveEffects : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer effectId = runtime[0].mInteger; runtime.pop(); ptr.getClass().getCreatureStats(ptr).getActiveSpells().purgeEffect(ptr, effectId); } }; template class OpGetSpell : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); Interpreter::Type_Integer value = 0; if (ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getSpells().hasSpell(id)) value = 1; runtime.push(value); } }; template class OpPCJoinFaction : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr actor = R()(runtime, false); ESM::RefId factionID; if (arg0 == 0) { factionID = getDialogueActorFaction(actor); } else { factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); } // Make sure this faction exists MWBase::Environment::get().getESMStore()->get().find(factionID); if (!factionID.empty()) { MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).joinFaction(factionID); } } }; template class OpPCRaiseRank : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr actor = R()(runtime, false); ESM::RefId factionID; if (arg0 == 0) { factionID = getDialogueActorFaction(actor); } else { factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); } // Make sure this faction exists MWBase::Environment::get().getESMStore()->get().find(factionID); if (!factionID.empty()) { MWWorld::Ptr player = MWMechanics::getPlayer(); if (!player.getClass().getNpcStats(player).isInFaction(factionID)) { player.getClass().getNpcStats(player).joinFaction(factionID); } else { int currentRank = player.getClass().getNpcStats(player).getFactionRank(factionID); player.getClass().getNpcStats(player).setFactionRank(factionID, currentRank + 1); } } } }; template class OpPCLowerRank : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr actor = R()(runtime, false); ESM::RefId factionID; if (arg0 == 0) { factionID = getDialogueActorFaction(actor); } else { factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); } // Make sure this faction exists MWBase::Environment::get().getESMStore()->get().find(factionID); if (!factionID.empty()) { MWWorld::Ptr player = MWMechanics::getPlayer(); int currentRank = player.getClass().getNpcStats(player).getFactionRank(factionID); player.getClass().getNpcStats(player).setFactionRank(factionID, currentRank - 1); } } }; template class OpGetPCRank : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); ESM::RefId factionID; if (arg0 > 0) { factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); } else { factionID = ptr.getClass().getPrimaryFaction(ptr); } // Make sure this faction exists MWBase::Environment::get().getESMStore()->get().find(factionID); if (!factionID.empty()) { MWWorld::Ptr player = MWMechanics::getPlayer(); runtime.push(player.getClass().getNpcStats(player).getFactionRank(factionID)); } else { runtime.push(-1); } } }; template class OpModDisposition : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); if (ptr.getClass().isNpc()) ptr.getClass().getNpcStats(ptr).setBaseDisposition( ptr.getClass().getNpcStats(ptr).getBaseDisposition() + value); // else: must not throw exception (used by an Almalexia dialogue script) } }; template class OpSetDisposition : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); if (ptr.getClass().isNpc()) ptr.getClass().getNpcStats(ptr).setBaseDisposition(value); } }; template class OpGetDisposition : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); if (!ptr.getClass().isNpc()) runtime.push(0); else runtime.push(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr)); } }; class OpGetDeadCount : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime[0].mInteger = MWBase::Environment::get().getMechanicsManager()->countDeaths(id); } }; template class OpGetPCFacRep : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); ESM::RefId factionId; if (arg0 == 1) { factionId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); } else { factionId = getDialogueActorFaction(ptr); } if (factionId.empty()) throw std::runtime_error("failed to determine faction"); MWWorld::Ptr player = MWMechanics::getPlayer(); runtime.push(player.getClass().getNpcStats(player).getFactionReputation(factionId)); } }; template class OpSetPCFacRep : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); ESM::RefId factionId; if (arg0 == 1) { factionId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); } else { factionId = getDialogueActorFaction(ptr); } if (factionId.empty()) throw std::runtime_error("failed to determine faction"); MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).setFactionReputation(factionId, value); } }; template class OpModPCFacRep : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); ESM::RefId factionId; if (arg0 == 1) { factionId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); } else { factionId = getDialogueActorFaction(ptr); } if (factionId.empty()) throw std::runtime_error("failed to determine faction"); MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).setFactionReputation( factionId, player.getClass().getNpcStats(player).getFactionReputation(factionId) + value); } }; template class OpGetCommonDisease : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push(ptr.getClass().getCreatureStats(ptr).hasCommonDisease()); } }; template class OpGetBlightDisease : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push(ptr.getClass().getCreatureStats(ptr).hasBlightDisease()); } }; template class OpGetRace : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::ConstPtr ptr = R()(runtime); ESM::RefId race = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); const ESM::RefId& npcRace = ptr.get()->mBase->mRace; runtime.push(race == npcRace); } }; class OpGetWerewolfKills : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); runtime.push(ptr.getClass().getNpcStats(ptr).getWerewolfKills()); } }; template class OpPcExpelled : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); ESM::RefId factionID; if (arg0 > 0) { factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); } else { factionID = ptr.getClass().getPrimaryFaction(ptr); } MWWorld::Ptr player = MWMechanics::getPlayer(); if (!factionID.empty()) { runtime.push(player.getClass().getNpcStats(player).getExpelled(factionID)); } else { runtime.push(0); } } }; template class OpPcExpell : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); ESM::RefId factionID; if (arg0 > 0) { factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); } else { factionID = ptr.getClass().getPrimaryFaction(ptr); } MWWorld::Ptr player = MWMechanics::getPlayer(); if (!factionID.empty()) { player.getClass().getNpcStats(player).expell(factionID); } } }; template class OpPcClearExpelled : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::ConstPtr ptr = R()(runtime, false); ESM::RefId factionID; if (arg0 > 0) { factionID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); } else { factionID = ptr.getClass().getPrimaryFaction(ptr); } MWWorld::Ptr player = MWMechanics::getPlayer(); if (!factionID.empty()) player.getClass().getNpcStats(player).clearExpelled(factionID); } }; template class OpRaiseRank : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); const ESM::RefId& factionID = ptr.getClass().getPrimaryFaction(ptr); if (factionID.empty()) return; MWWorld::Ptr player = MWMechanics::getPlayer(); // no-op when executed on the player if (ptr == player) return; // If we already changed rank for this NPC, modify current rank in the NPC stats. // Otherwise take rank from base NPC record, increase it and put it to NPC data. int currentRank = ptr.getClass().getNpcStats(ptr).getFactionRank(factionID); if (currentRank >= 0) ptr.getClass().getNpcStats(ptr).setFactionRank(factionID, currentRank + 1); else { int rank = ptr.getClass().getPrimaryFactionRank(ptr); ptr.getClass().getNpcStats(ptr).joinFaction(factionID); ptr.getClass().getNpcStats(ptr).setFactionRank(factionID, rank + 1); } } }; template class OpLowerRank : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); const ESM::RefId& factionID = ptr.getClass().getPrimaryFaction(ptr); if (factionID.empty()) return; MWWorld::Ptr player = MWMechanics::getPlayer(); // no-op when executed on the player if (ptr == player) return; // If we already changed rank for this NPC, modify current rank in the NPC stats. // Otherwise take rank from base NPC record, decrease it and put it to NPC data. int currentRank = ptr.getClass().getNpcStats(ptr).getFactionRank(factionID); if (currentRank == 0) return; else if (currentRank > 0) ptr.getClass().getNpcStats(ptr).setFactionRank(factionID, currentRank - 1); else { int rank = ptr.getClass().getPrimaryFactionRank(ptr); ptr.getClass().getNpcStats(ptr).joinFaction(factionID); ptr.getClass().getNpcStats(ptr).setFactionRank(factionID, std::max(0, rank - 1)); } } }; template class OpOnDeath : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).hasDied(); if (value) ptr.getClass().getCreatureStats(ptr).clearHasDied(); runtime.push(value); } }; template class OpOnMurder : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).hasBeenMurdered(); if (value) ptr.getClass().getCreatureStats(ptr).clearHasBeenMurdered(); runtime.push(value); } }; template class OpOnKnockout : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).getKnockedDownOneFrame(); runtime.push(value); } }; template class OpIsWerewolf : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); runtime.push(ptr.getClass().getNpcStats(ptr).isWerewolf()); } }; template class OpSetWerewolf : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWBase::Environment::get().getMechanicsManager()->setWerewolf(ptr, set); } }; template class OpSetWerewolfAcrobatics : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWBase::Environment::get().getMechanicsManager()->applyWerewolfAcrobatics(ptr); } }; template class OpResurrect : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getMechanicsManager()->resurrect(ptr); if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Ended) MWBase::Environment::get().getStateManager()->resumeGame(); } else if (ptr.getClass().getCreatureStats(ptr).isDead()) { bool wasEnabled = ptr.getRefData().isEnabled(); MWBase::Environment::get().getWorld()->undeleteObject(ptr); auto windowManager = MWBase::Environment::get().getWindowManager(); bool wasOpen = windowManager->containsMode(MWGui::GM_Container); windowManager->onDeleteCustomData(ptr); // HACK: disable/enable object to re-add it to the scene properly (need a new Animation). MWBase::Environment::get().getWorld()->disable(ptr); // The actor's base record may have changed after this specific reference was created. // So we need to update to the current version if (ptr.getClass().isNpc()) updateBaseRecord(ptr); else updateBaseRecord(ptr); if (wasOpen && !windowManager->containsMode(MWGui::GM_Container)) { // Reopen the loot GUI if it was closed because we resurrected the actor we were looting MWBase::Environment::get().getMechanicsManager()->resurrect(ptr); windowManager->forceLootMode(ptr); } else { MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); // resets runtime state such as inventory, stats and AI. does not reset position in the world ptr.getRefData().setCustomData(nullptr); } if (wasEnabled) MWBase::Environment::get().getWorld()->enable(ptr); } } }; template class OpGetStat : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { // dummy runtime.pop(); runtime.push(0); } }; template class OpGetMagicEffect : public Interpreter::Opcode0 { int mPositiveEffect; int mNegativeEffect; public: OpGetMagicEffect(int positiveEffect, int negativeEffect) : mPositiveEffect(positiveEffect) , mNegativeEffect(negativeEffect) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); float currentValue = effects.getOrDefault(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) currentValue -= effects.getOrDefault(mNegativeEffect).getMagnitude(); // GetResist* should take in account elemental shields if (mPositiveEffect == ESM::MagicEffect::ResistFire) currentValue += effects.getOrDefault(ESM::MagicEffect::FireShield).getMagnitude(); if (mPositiveEffect == ESM::MagicEffect::ResistShock) currentValue += effects.getOrDefault(ESM::MagicEffect::LightningShield).getMagnitude(); if (mPositiveEffect == ESM::MagicEffect::ResistFrost) currentValue += effects.getOrDefault(ESM::MagicEffect::FrostShield).getMagnitude(); int ret = static_cast(currentValue); runtime.push(ret); } }; template class OpSetMagicEffect : public Interpreter::Opcode0 { int mPositiveEffect; int mNegativeEffect; public: OpSetMagicEffect(int positiveEffect, int negativeEffect) : mPositiveEffect(positiveEffect) , mNegativeEffect(negativeEffect) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); float currentValue = effects.getOrDefault(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) currentValue -= effects.getOrDefault(mNegativeEffect).getMagnitude(); // SetResist* should take in account elemental shields if (mPositiveEffect == ESM::MagicEffect::ResistFire) currentValue += effects.getOrDefault(ESM::MagicEffect::FireShield).getMagnitude(); if (mPositiveEffect == ESM::MagicEffect::ResistShock) currentValue += effects.getOrDefault(ESM::MagicEffect::LightningShield).getMagnitude(); if (mPositiveEffect == ESM::MagicEffect::ResistFrost) currentValue += effects.getOrDefault(ESM::MagicEffect::FrostShield).getMagnitude(); int arg = runtime[0].mInteger; runtime.pop(); effects.modifyBase(mPositiveEffect, (arg - static_cast(currentValue))); } }; template class OpModMagicEffect : public Interpreter::Opcode0 { int mPositiveEffect; int mNegativeEffect; public: OpModMagicEffect(int positiveEffect, int negativeEffect) : mPositiveEffect(positiveEffect) , mNegativeEffect(negativeEffect) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); int arg = runtime[0].mInteger; runtime.pop(); stats.getMagicEffects().modifyBase(mPositiveEffect, arg); } }; class OpGetPCVisionBonus : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr player = MWMechanics::getPlayer(); MWMechanics::EffectParam nightEye = player.getClass().getCreatureStats(player).getMagicEffects().getOrDefault( ESM::MagicEffect::NightEye); runtime.push(std::clamp(nightEye.getMagnitude() / 100.f, 0.f, 1.f)); } }; class OpSetPCVisionBonus : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { float arg = runtime[0].mFloat; runtime.pop(); MWWorld::Ptr player = MWMechanics::getPlayer(); auto& effects = player.getClass().getCreatureStats(player).getMagicEffects(); float delta = std::clamp(arg * 100.f, 0.f, 100.f) - effects.getOrDefault(ESM::MagicEffect::NightEye).getMagnitude(); effects.modifyBase(ESM::MagicEffect::NightEye, static_cast(delta)); } }; class OpModPCVisionBonus : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { float arg = runtime[0].mFloat; runtime.pop(); MWWorld::Ptr player = MWMechanics::getPlayer(); auto& effects = player.getClass().getCreatureStats(player).getMagicEffects(); const MWMechanics::EffectParam nightEye = effects.getOrDefault(ESM::MagicEffect::NightEye); float newBase = std::clamp(nightEye.getMagnitude() + arg * 100.f, 0.f, 100.f); newBase -= nightEye.getModifier(); float delta = std::clamp(newBase, 0.f, 100.f) - nightEye.getMagnitude(); effects.modifyBase(ESM::MagicEffect::NightEye, static_cast(delta)); } }; struct MagicEffect { int mPositiveEffect; int mNegativeEffect; }; void installOpcodes(Interpreter::Interpreter& interpreter) { for (int i = 0; i < Compiler::Stats::numberOfAttributes; ++i) { interpreter.installSegment5>(Compiler::Stats::opcodeGetAttribute + i, i); interpreter.installSegment5>( Compiler::Stats::opcodeGetAttributeExplicit + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeSetAttribute + i, i); interpreter.installSegment5>( Compiler::Stats::opcodeSetAttributeExplicit + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeModAttribute + i, i); interpreter.installSegment5>( Compiler::Stats::opcodeModAttributeExplicit + i, i); } for (int i = 0; i < Compiler::Stats::numberOfDynamics; ++i) { interpreter.installSegment5>(Compiler::Stats::opcodeGetDynamic + i, i); interpreter.installSegment5>( Compiler::Stats::opcodeGetDynamicExplicit + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeSetDynamic + i, i); interpreter.installSegment5>( Compiler::Stats::opcodeSetDynamicExplicit + i, i); interpreter.installSegment5>(Compiler::Stats::opcodeModDynamic + i, i); interpreter.installSegment5>( Compiler::Stats::opcodeModDynamicExplicit + i, i); interpreter.installSegment5>( Compiler::Stats::opcodeModCurrentDynamic + i, i); interpreter.installSegment5>( Compiler::Stats::opcodeModCurrentDynamicExplicit + i, i); interpreter.installSegment5>( Compiler::Stats::opcodeGetDynamicGetRatio + i, i); interpreter.installSegment5>( Compiler::Stats::opcodeGetDynamicGetRatioExplicit + i, i); } for (int i = 0; i < Compiler::Stats::numberOfSkills; ++i) { ESM::RefId id = ESM::Skill::indexToRefId(i); interpreter.installSegment5>(Compiler::Stats::opcodeGetSkill + i, id); interpreter.installSegment5>(Compiler::Stats::opcodeGetSkillExplicit + i, id); interpreter.installSegment5>(Compiler::Stats::opcodeSetSkill + i, id); interpreter.installSegment5>(Compiler::Stats::opcodeSetSkillExplicit + i, id); interpreter.installSegment5>(Compiler::Stats::opcodeModSkill + i, id); interpreter.installSegment5>(Compiler::Stats::opcodeModSkillExplicit + i, id); } interpreter.installSegment5(Compiler::Stats::opcodeGetPCCrimeLevel); interpreter.installSegment5(Compiler::Stats::opcodeSetPCCrimeLevel); interpreter.installSegment5(Compiler::Stats::opcodeModPCCrimeLevel); interpreter.installSegment5>(Compiler::Stats::opcodeAddSpell); interpreter.installSegment5>(Compiler::Stats::opcodeAddSpellExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpell); interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpellExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeRemoveSpellEffects); interpreter.installSegment5>( Compiler::Stats::opcodeRemoveSpellEffectsExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeResurrect); interpreter.installSegment5>(Compiler::Stats::opcodeResurrectExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeRemoveEffects); interpreter.installSegment5>(Compiler::Stats::opcodeRemoveEffectsExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetSpell); interpreter.installSegment5>(Compiler::Stats::opcodeGetSpellExplicit); interpreter.installSegment3>(Compiler::Stats::opcodePCRaiseRank); interpreter.installSegment3>(Compiler::Stats::opcodePCLowerRank); interpreter.installSegment3>(Compiler::Stats::opcodePCJoinFaction); interpreter.installSegment3>(Compiler::Stats::opcodePCRaiseRankExplicit); interpreter.installSegment3>(Compiler::Stats::opcodePCLowerRankExplicit); interpreter.installSegment3>(Compiler::Stats::opcodePCJoinFactionExplicit); interpreter.installSegment3>(Compiler::Stats::opcodeGetPCRank); interpreter.installSegment3>(Compiler::Stats::opcodeGetPCRankExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeModDisposition); interpreter.installSegment5>(Compiler::Stats::opcodeModDispositionExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeSetDisposition); interpreter.installSegment5>(Compiler::Stats::opcodeSetDispositionExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetDisposition); interpreter.installSegment5>(Compiler::Stats::opcodeGetDispositionExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetLevel); interpreter.installSegment5>(Compiler::Stats::opcodeGetLevelExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeSetLevel); interpreter.installSegment5>(Compiler::Stats::opcodeSetLevelExplicit); interpreter.installSegment5(Compiler::Stats::opcodeGetDeadCount); interpreter.installSegment3>(Compiler::Stats::opcodeGetPCFacRep); interpreter.installSegment3>(Compiler::Stats::opcodeGetPCFacRepExplicit); interpreter.installSegment3>(Compiler::Stats::opcodeSetPCFacRep); interpreter.installSegment3>(Compiler::Stats::opcodeSetPCFacRepExplicit); interpreter.installSegment3>(Compiler::Stats::opcodeModPCFacRep); interpreter.installSegment3>(Compiler::Stats::opcodeModPCFacRepExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetCommonDisease); interpreter.installSegment5>( Compiler::Stats::opcodeGetCommonDiseaseExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetBlightDisease); interpreter.installSegment5>( Compiler::Stats::opcodeGetBlightDiseaseExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetRace); interpreter.installSegment5>(Compiler::Stats::opcodeGetRaceExplicit); interpreter.installSegment5(Compiler::Stats::opcodeGetWerewolfKills); interpreter.installSegment3>(Compiler::Stats::opcodePcExpelled); interpreter.installSegment3>(Compiler::Stats::opcodePcExpelledExplicit); interpreter.installSegment3>(Compiler::Stats::opcodePcExpell); interpreter.installSegment3>(Compiler::Stats::opcodePcExpellExplicit); interpreter.installSegment3>(Compiler::Stats::opcodePcClearExpelled); interpreter.installSegment3>(Compiler::Stats::opcodePcClearExpelledExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeRaiseRank); interpreter.installSegment5>(Compiler::Stats::opcodeRaiseRankExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeLowerRank); interpreter.installSegment5>(Compiler::Stats::opcodeLowerRankExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeOnDeath); interpreter.installSegment5>(Compiler::Stats::opcodeOnDeathExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeOnMurder); interpreter.installSegment5>(Compiler::Stats::opcodeOnMurderExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeOnKnockout); interpreter.installSegment5>(Compiler::Stats::opcodeOnKnockoutExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeIsWerewolf); interpreter.installSegment5>(Compiler::Stats::opcodeIsWerewolfExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeBecomeWerewolf); interpreter.installSegment5>( Compiler::Stats::opcodeBecomeWerewolfExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeUndoWerewolf); interpreter.installSegment5>(Compiler::Stats::opcodeUndoWerewolfExplicit); interpreter.installSegment5>( Compiler::Stats::opcodeSetWerewolfAcrobatics); interpreter.installSegment5>( Compiler::Stats::opcodeSetWerewolfAcrobaticsExplicit); interpreter.installSegment5>(Compiler::Stats::opcodeGetStat); interpreter.installSegment5>(Compiler::Stats::opcodeGetStatExplicit); static const MagicEffect sMagicEffects[] = { { ESM::MagicEffect::ResistMagicka, ESM::MagicEffect::WeaknessToMagicka }, { ESM::MagicEffect::ResistFire, ESM::MagicEffect::WeaknessToFire }, { ESM::MagicEffect::ResistFrost, ESM::MagicEffect::WeaknessToFrost }, { ESM::MagicEffect::ResistShock, ESM::MagicEffect::WeaknessToShock }, { ESM::MagicEffect::ResistCommonDisease, ESM::MagicEffect::WeaknessToCommonDisease }, { ESM::MagicEffect::ResistBlightDisease, ESM::MagicEffect::WeaknessToBlightDisease }, { ESM::MagicEffect::ResistCorprusDisease, ESM::MagicEffect::WeaknessToCorprusDisease }, { ESM::MagicEffect::ResistPoison, ESM::MagicEffect::WeaknessToPoison }, { ESM::MagicEffect::ResistParalysis, -1 }, { ESM::MagicEffect::ResistNormalWeapons, ESM::MagicEffect::WeaknessToNormalWeapons }, { ESM::MagicEffect::WaterBreathing, -1 }, { ESM::MagicEffect::Chameleon, -1 }, { ESM::MagicEffect::WaterWalking, -1 }, { ESM::MagicEffect::SwiftSwim, -1 }, { ESM::MagicEffect::Jump, -1 }, { ESM::MagicEffect::Levitate, -1 }, { ESM::MagicEffect::Shield, -1 }, { ESM::MagicEffect::Sound, -1 }, { ESM::MagicEffect::Silence, -1 }, { ESM::MagicEffect::Blind, -1 }, { ESM::MagicEffect::Paralyze, -1 }, { ESM::MagicEffect::Invisibility, -1 }, { ESM::MagicEffect::FortifyAttack, -1 }, { ESM::MagicEffect::Sanctuary, -1 }, }; for (int i = 0; i < 24; ++i) { int positive = sMagicEffects[i].mPositiveEffect; int negative = sMagicEffects[i].mNegativeEffect; interpreter.installSegment5>( Compiler::Stats::opcodeGetMagicEffect + i, positive, negative); interpreter.installSegment5>( Compiler::Stats::opcodeGetMagicEffectExplicit + i, positive, negative); interpreter.installSegment5>( Compiler::Stats::opcodeSetMagicEffect + i, positive, negative); interpreter.installSegment5>( Compiler::Stats::opcodeSetMagicEffectExplicit + i, positive, negative); interpreter.installSegment5>( Compiler::Stats::opcodeModMagicEffect + i, positive, negative); interpreter.installSegment5>( Compiler::Stats::opcodeModMagicEffectExplicit + i, positive, negative); } interpreter.installSegment5(Compiler::Stats::opcodeGetPCVisionBonus); interpreter.installSegment5(Compiler::Stats::opcodeSetPCVisionBonus); interpreter.installSegment5(Compiler::Stats::opcodeModPCVisionBonus); } } }