#include "aiextensions.hpp" #include #include #include #include #include #include #include #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/aiactivate.hpp" #include "../mwmechanics/aiescort.hpp" #include "../mwmechanics/aiface.hpp" #include "../mwmechanics/aifollow.hpp" #include "../mwmechanics/aitravel.hpp" #include "../mwmechanics/aiwander.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" #include "interpretercontext.hpp" #include "ref.hpp" namespace MWScript { namespace Ai { template class OpAiActivate : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId objectID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); // The value of the reset argument doesn't actually matter bool repeat = arg0; for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) return; MWMechanics::AiActivate activatePackage(objectID, repeat); ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(activatePackage, ptr); Log(Debug::Info) << "AiActivate"; } }; template class OpAiTravel : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); // The value of the reset argument doesn't actually matter bool repeat = arg0; for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) return; MWMechanics::AiTravel travelPackage(x, y, z, repeat); ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(travelPackage, ptr); Log(Debug::Info) << "AiTravel: " << x << ", " << y << ", " << z; } }; template class OpAiEscort : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId actorID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); Interpreter::Type_Float duration = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); // The value of the reset argument doesn't actually matter bool repeat = arg0; for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) return; MWMechanics::AiEscort escortPackage(actorID, static_cast(duration), x, y, z, repeat); ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(escortPackage, ptr); Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; } }; template class OpAiEscortCell : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId actorID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); std::string_view cellID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float duration = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); // The value of the reset argument doesn't actually matter bool repeat = arg0; for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) return; if (cellID.empty()) return; if (!MWBase::Environment::get().getESMStore()->get().search(cellID)) return; MWMechanics::AiEscort escortPackage(actorID, cellID, static_cast(duration), x, y, z, repeat); ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(escortPackage, ptr); Log(Debug::Info) << "AiEscort: " << x << ", " << y << ", " << z << ", " << duration; } }; template class OpGetAiPackageDone : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); bool done = false; if (ptr.getClass().isActor()) done = ptr.getClass().getCreatureStats(ptr).getAiSequence().isPackageDone(); runtime.push(done); } }; template class OpAiWander : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer range = static_cast(runtime[0].mFloat); runtime.pop(); Interpreter::Type_Integer duration = static_cast(runtime[0].mFloat); runtime.pop(); Interpreter::Type_Integer time = static_cast(runtime[0].mFloat); runtime.pop(); // Chance for Idle is unused if (arg0) { --arg0; runtime.pop(); } std::vector idleList; bool repeat = false; // Chances for Idle2-Idle9 for (int i = 2; i <= 9 && arg0; ++i) { if (!repeat) repeat = true; Interpreter::Type_Integer idleValue = std::clamp(runtime[0].mInteger, 0, 255); idleList.push_back(idleValue); runtime.pop(); --arg0; } if (arg0) { repeat = runtime[0].mInteger != 0; runtime.pop(); --arg0; } // discard additional arguments, because we have no idea what they mean. for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) return; MWMechanics::AiWander wanderPackage(range, duration, time, idleList, repeat); ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(wanderPackage, ptr); } }; template class OpGetAiSetting : public Interpreter::Opcode0 { MWMechanics::AiSetting mIndex; public: OpGetAiSetting(MWMechanics::AiSetting index) : mIndex(index) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = 0; if (ptr.getClass().isActor()) value = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex).getModified(false); runtime.push(value); } }; template class OpModAiSetting : public Interpreter::Opcode0 { MWMechanics::AiSetting mIndex; public: OpModAiSetting(MWMechanics::AiSetting index) : mIndex(index) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); if (!ptr.getClass().isActor()) return; int modified = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex).getBase() + value; ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, modified); ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, modified); } }; template class OpSetAiSetting : public Interpreter::Opcode0 { MWMechanics::AiSetting mIndex; public: OpSetAiSetting(MWMechanics::AiSetting index) : mIndex(index) { } void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); if (ptr.getClass().isActor()) { ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, value); ptr.getClass().setBaseAISetting(ptr.getCellRef().getRefId(), mIndex, value); } } }; template class OpAiFollow : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId actorID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); Interpreter::Type_Float duration = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); // The value of the reset argument doesn't actually matter bool repeat = arg0; for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) return; MWMechanics::AiFollow followPackage(actorID, duration, x, y, z, repeat); ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(followPackage, ptr); Log(Debug::Info) << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration; } }; template class OpAiFollowCell : public Interpreter::Opcode1 { public: void execute(Interpreter::Runtime& runtime, unsigned int arg0) override { MWWorld::Ptr ptr = R()(runtime); ESM::RefId actorID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); std::string_view cellID = runtime.getStringLiteral(runtime[0].mInteger); runtime.pop(); Interpreter::Type_Float duration = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float z = runtime[0].mFloat; runtime.pop(); // The value of the reset argument doesn't actually matter bool repeat = arg0; for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); if (!ptr.getClass().isActor() || ptr == MWMechanics::getPlayer()) return; MWMechanics::AiFollow followPackage(actorID, cellID, duration, x, y, z, repeat); ptr.getClass().getCreatureStats(ptr).getAiSequence().stack(followPackage, ptr); Log(Debug::Info) << "AiFollow: " << actorID << ", " << x << ", " << y << ", " << z << ", " << duration; } }; template class OpGetCurrentAIPackage : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); Interpreter::Type_Integer value = -1; if (ptr.getClass().isActor()) { const auto& stats = ptr.getClass().getCreatureStats(ptr); if (!stats.isDead() || !stats.isDeathAnimationFinished()) { value = static_cast(stats.getAiSequence().getLastRunTypeId()); } } runtime.push(value); } }; template class OpGetDetected : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr observer = R()(runtime, false); // required=false ESM::RefId actorID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); MWWorld::Ptr actor = MWBase::Environment::get().getWorld()->searchPtr(actorID, true, false); Interpreter::Type_Integer value = 0; if (!actor.isEmpty()) value = MWBase::Environment::get().getMechanicsManager()->isActorDetected(actor, observer); runtime.push(value); } }; template class OpGetLineOfSight : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr source = R()(runtime); ESM::RefId actorID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); MWWorld::Ptr dest = MWBase::Environment::get().getWorld()->searchPtr(actorID, true, false); bool value = false; if (!dest.isEmpty() && source.getClass().isActor() && dest.getClass().isActor()) { value = MWBase::Environment::get().getWorld()->getLOS(source, dest); } runtime.push(value); } }; template class OpGetTarget : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr actor = R()(runtime); ESM::RefId testedTargetId = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); bool targetsAreEqual = false; if (actor.getClass().isActor()) { const MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor); MWWorld::Ptr targetPtr; if (creatureStats.getAiSequence().getCombatTarget(targetPtr)) { if (!targetPtr.isEmpty() && targetPtr.getCellRef().getRefId() == testedTargetId) targetsAreEqual = true; } else if (testedTargetId == "Player") // Currently the player ID is hardcoded { MWBase::MechanicsManager* mechMgr = MWBase::Environment::get().getMechanicsManager(); bool greeting = mechMgr->getGreetingState(actor) == MWMechanics::Greet_InProgress; bool sayActive = MWBase::Environment::get().getSoundManager()->sayActive(actor); targetsAreEqual = (greeting && sayActive) || mechMgr->isTurningToPlayer(actor); } } runtime.push(targetsAreEqual); } }; template class OpStartCombat : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr actor = R()(runtime); ESM::RefId targetID = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(targetID, true, false); if (!target.isEmpty()) MWBase::Environment::get().getMechanicsManager()->startCombat(actor, target); } }; template class OpStopCombat : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr actor = R()(runtime); if (!actor.getClass().isActor()) return; MWBase::Environment::get().getMechanicsManager()->stopCombat(actor); } }; class OpToggleAI : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { bool enabled = MWBase::Environment::get().getMechanicsManager()->toggleAI(); runtime.getContext().report(enabled ? "AI -> On" : "AI -> Off"); } }; template class OpFace : public Interpreter::Opcode0 { public: void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr actor = R()(runtime); Interpreter::Type_Float x = runtime[0].mFloat; runtime.pop(); Interpreter::Type_Float y = runtime[0].mFloat; runtime.pop(); if (!actor.getClass().isActor() || actor == MWMechanics::getPlayer()) return; MWMechanics::AiFace facePackage(x, y); actor.getClass().getCreatureStats(actor).getAiSequence().stack(facePackage, actor); } }; void installOpcodes(Interpreter::Interpreter& interpreter) { interpreter.installSegment3>(Compiler::Ai::opcodeAIActivate); interpreter.installSegment3>(Compiler::Ai::opcodeAIActivateExplicit); interpreter.installSegment3>(Compiler::Ai::opcodeAiTravel); interpreter.installSegment3>(Compiler::Ai::opcodeAiTravelExplicit); interpreter.installSegment3>(Compiler::Ai::opcodeAiEscort); interpreter.installSegment3>(Compiler::Ai::opcodeAiEscortExplicit); interpreter.installSegment3>(Compiler::Ai::opcodeAiEscortCell); interpreter.installSegment3>(Compiler::Ai::opcodeAiEscortCellExplicit); interpreter.installSegment3>(Compiler::Ai::opcodeAiWander); interpreter.installSegment3>(Compiler::Ai::opcodeAiWanderExplicit); interpreter.installSegment3>(Compiler::Ai::opcodeAiFollow); interpreter.installSegment3>(Compiler::Ai::opcodeAiFollowExplicit); interpreter.installSegment3>(Compiler::Ai::opcodeAiFollowCell); interpreter.installSegment3>(Compiler::Ai::opcodeAiFollowCellExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeGetAiPackageDone); interpreter.installSegment5>(Compiler::Ai::opcodeGetAiPackageDoneExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeGetCurrentAiPackage); interpreter.installSegment5>( Compiler::Ai::opcodeGetCurrentAiPackageExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeGetDetected); interpreter.installSegment5>(Compiler::Ai::opcodeGetDetectedExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeGetLineOfSight); interpreter.installSegment5>(Compiler::Ai::opcodeGetLineOfSightExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeGetTarget); interpreter.installSegment5>(Compiler::Ai::opcodeGetTargetExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeStartCombat); interpreter.installSegment5>(Compiler::Ai::opcodeStartCombatExplicit); interpreter.installSegment5>(Compiler::Ai::opcodeStopCombat); interpreter.installSegment5>(Compiler::Ai::opcodeStopCombatExplicit); interpreter.installSegment5(Compiler::Ai::opcodeToggleAI); interpreter.installSegment5>( Compiler::Ai::opcodeSetHello, MWMechanics::AiSetting::Hello); interpreter.installSegment5>( Compiler::Ai::opcodeSetHelloExplicit, MWMechanics::AiSetting::Hello); interpreter.installSegment5>( Compiler::Ai::opcodeSetFight, MWMechanics::AiSetting::Fight); interpreter.installSegment5>( Compiler::Ai::opcodeSetFightExplicit, MWMechanics::AiSetting::Fight); interpreter.installSegment5>( Compiler::Ai::opcodeSetFlee, MWMechanics::AiSetting::Flee); interpreter.installSegment5>( Compiler::Ai::opcodeSetFleeExplicit, MWMechanics::AiSetting::Flee); interpreter.installSegment5>( Compiler::Ai::opcodeSetAlarm, MWMechanics::AiSetting::Alarm); interpreter.installSegment5>( Compiler::Ai::opcodeSetAlarmExplicit, MWMechanics::AiSetting::Alarm); interpreter.installSegment5>( Compiler::Ai::opcodeModHello, MWMechanics::AiSetting::Hello); interpreter.installSegment5>( Compiler::Ai::opcodeModHelloExplicit, MWMechanics::AiSetting::Hello); interpreter.installSegment5>( Compiler::Ai::opcodeModFight, MWMechanics::AiSetting::Fight); interpreter.installSegment5>( Compiler::Ai::opcodeModFightExplicit, MWMechanics::AiSetting::Fight); interpreter.installSegment5>( Compiler::Ai::opcodeModFlee, MWMechanics::AiSetting::Flee); interpreter.installSegment5>( Compiler::Ai::opcodeModFleeExplicit, MWMechanics::AiSetting::Flee); interpreter.installSegment5>( Compiler::Ai::opcodeModAlarm, MWMechanics::AiSetting::Alarm); interpreter.installSegment5>( Compiler::Ai::opcodeModAlarmExplicit, MWMechanics::AiSetting::Alarm); interpreter.installSegment5>( Compiler::Ai::opcodeGetHello, MWMechanics::AiSetting::Hello); interpreter.installSegment5>( Compiler::Ai::opcodeGetHelloExplicit, MWMechanics::AiSetting::Hello); interpreter.installSegment5>( Compiler::Ai::opcodeGetFight, MWMechanics::AiSetting::Fight); interpreter.installSegment5>( Compiler::Ai::opcodeGetFightExplicit, MWMechanics::AiSetting::Fight); interpreter.installSegment5>( Compiler::Ai::opcodeGetFlee, MWMechanics::AiSetting::Flee); interpreter.installSegment5>( Compiler::Ai::opcodeGetFleeExplicit, MWMechanics::AiSetting::Flee); interpreter.installSegment5>( Compiler::Ai::opcodeGetAlarm, MWMechanics::AiSetting::Alarm); interpreter.installSegment5>( Compiler::Ai::opcodeGetAlarmExplicit, MWMechanics::AiSetting::Alarm); interpreter.installSegment5>(Compiler::Ai::opcodeFace); interpreter.installSegment5>(Compiler::Ai::opcodeFaceExplicit); } } }