Expose Wander option values to the Lua API (#7916)

pull/3235/head
Hristos N. Triantafillou 8 months ago committed by psi29a
parent a57c350c08
commit 63a27bbf99

@ -81,7 +81,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_MINOR 49)
set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 60) set(OPENMW_LUA_API_REVISION 61)
set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_POSTPROCESSING_API_REVISION 1)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")

@ -104,7 +104,27 @@ namespace MWLua
}); });
aiPackage["sideWithTarget"] = sol::readonly_property([](const AiPackage& p) { return p.sideWithTarget(); }); aiPackage["sideWithTarget"] = sol::readonly_property([](const AiPackage& p) { return p.sideWithTarget(); });
aiPackage["destPosition"] = sol::readonly_property([](const AiPackage& p) { return p.getDestination(); }); aiPackage["destPosition"] = sol::readonly_property([](const AiPackage& p) { return p.getDestination(); });
aiPackage["distance"] = sol::readonly_property([](const AiPackage& p) { return p.getDistance(); });
aiPackage["duration"] = sol::readonly_property([](const AiPackage& p) { return p.getDuration(); });
aiPackage["idle"] = sol::readonly_property([context](const AiPackage& p) -> sol::optional<sol::table> {
if (p.getTypeId() == MWMechanics::AiPackageTypeId::Wander)
{
sol::table idles(context.mLua->sol(), sol::create);
const std::vector<unsigned char>& idle = static_cast<const MWMechanics::AiWander&>(p).getIdle();
if (!idle.empty())
{
for (size_t i = 0; i < idle.size(); ++i)
{
std::string_view groupName = MWMechanics::AiWander::getIdleGroupName(i);
idles[groupName] = idle[i];
}
return idles;
}
}
return sol::nullopt;
});
aiPackage["isRepeat"] = sol::readonly_property([](const AiPackage& p) { return p.getRepeat(); });
selfAPI["_getActiveAiPackage"] = [](SelfObject& self) -> sol::optional<std::shared_ptr<AiPackage>> { selfAPI["_getActiveAiPackage"] = [](SelfObject& self) -> sol::optional<std::shared_ptr<AiPackage>> {
const MWWorld::Ptr& ptr = self.ptr(); const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
@ -132,13 +152,25 @@ namespace MWLua
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
ai.stack(MWMechanics::AiPursue(target.ptr()), ptr, cancelOther); ai.stack(MWMechanics::AiPursue(target.ptr()), ptr, cancelOther);
}; };
selfAPI["_startAiFollow"] = [](SelfObject& self, const LObject& target, bool cancelOther) { selfAPI["_startAiFollow"] = [](SelfObject& self, const LObject& target, sol::optional<LCell> cell,
float duration, const osg::Vec3f& dest, bool repeat, bool cancelOther) {
const MWWorld::Ptr& ptr = self.ptr(); const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
ai.stack(MWMechanics::AiFollow(target.ptr()), ptr, cancelOther); if (cell)
{
ai.stack(MWMechanics::AiFollow(target.ptr().getCellRef().getRefId(),
cell->mStore->getCell()->getNameId(), duration, dest.x(), dest.y(), dest.z(), repeat),
ptr, cancelOther);
}
else
{
ai.stack(MWMechanics::AiFollow(
target.ptr().getCellRef().getRefId(), duration, dest.x(), dest.y(), dest.z(), repeat),
ptr, cancelOther);
}
}; };
selfAPI["_startAiEscort"] = [](SelfObject& self, const LObject& target, LCell cell, float duration, selfAPI["_startAiEscort"] = [](SelfObject& self, const LObject& target, LCell cell, float duration,
const osg::Vec3f& dest, bool cancelOther) { const osg::Vec3f& dest, bool repeat, bool cancelOther) {
const MWWorld::Ptr& ptr = self.ptr(); const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
// TODO: change AiEscort implementation to accept ptr instead of a non-unique refId. // TODO: change AiEscort implementation to accept ptr instead of a non-unique refId.
@ -146,23 +178,27 @@ namespace MWLua
int gameHoursDuration = static_cast<int>(std::ceil(duration / 3600.0)); int gameHoursDuration = static_cast<int>(std::ceil(duration / 3600.0));
auto* esmCell = cell.mStore->getCell(); auto* esmCell = cell.mStore->getCell();
if (esmCell->isExterior()) if (esmCell->isExterior())
ai.stack(MWMechanics::AiEscort(refId, gameHoursDuration, dest.x(), dest.y(), dest.z(), false), ptr, ai.stack(MWMechanics::AiEscort(refId, gameHoursDuration, dest.x(), dest.y(), dest.z(), repeat), ptr,
cancelOther); cancelOther);
else else
ai.stack(MWMechanics::AiEscort( ai.stack(MWMechanics::AiEscort(
refId, esmCell->getNameId(), gameHoursDuration, dest.x(), dest.y(), dest.z(), false), refId, esmCell->getNameId(), gameHoursDuration, dest.x(), dest.y(), dest.z(), repeat),
ptr, cancelOther); ptr, cancelOther);
}; };
selfAPI["_startAiWander"] = [](SelfObject& self, int distance, float duration, bool cancelOther) { selfAPI["_startAiWander"]
= [](SelfObject& self, int distance, int duration, sol::table luaIdle, bool repeat, bool cancelOther) {
const MWWorld::Ptr& ptr = self.ptr(); const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
int gameHoursDuration = static_cast<int>(std::ceil(duration / 3600.0)); std::vector<unsigned char> idle;
ai.stack(MWMechanics::AiWander(distance, gameHoursDuration, 0, {}, false), ptr, cancelOther); // Lua index starts at 1
for (size_t i = 1; i <= luaIdle.size(); i++)
idle.emplace_back(luaIdle.get<unsigned char>(i));
ai.stack(MWMechanics::AiWander(distance, duration, 0, idle, repeat), ptr, cancelOther);
}; };
selfAPI["_startAiTravel"] = [](SelfObject& self, const osg::Vec3f& target, bool cancelOther) { selfAPI["_startAiTravel"] = [](SelfObject& self, const osg::Vec3f& target, bool repeat, bool cancelOther) {
const MWWorld::Ptr& ptr = self.ptr(); const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence(); MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
ai.stack(MWMechanics::AiTravel(target.x(), target.y(), target.z(), false), ptr, cancelOther); ai.stack(MWMechanics::AiTravel(target.x(), target.y(), target.z(), repeat), ptr, cancelOther);
}; };
selfAPI["_enableLuaAnimations"] = [](SelfObject& self, bool enable) { selfAPI["_enableLuaAnimations"] = [](SelfObject& self, bool enable) {
const MWWorld::Ptr& ptr = self.ptr(); const MWWorld::Ptr& ptr = self.ptr();

@ -51,6 +51,8 @@ namespace MWMechanics
osg::Vec3f getDestination() const override { return osg::Vec3f(mX, mY, mZ); } osg::Vec3f getDestination() const override { return osg::Vec3f(mX, mY, mZ); }
std::optional<float> getDuration() const override { return mDuration; }
private: private:
const std::string mCellId; const std::string mCellId;
const float mX; const float mX;

@ -110,6 +110,10 @@ namespace MWMechanics
virtual osg::Vec3f getDestination() const { return osg::Vec3f(0, 0, 0); } virtual osg::Vec3f getDestination() const { return osg::Vec3f(0, 0, 0); }
virtual std::optional<int> getDistance() const { return std::nullopt; }
virtual std::optional<float> getDuration() const { return std::nullopt; }
/// Return true if any loaded actor with this AI package must be active. /// Return true if any loaded actor with this AI package must be active.
bool alwaysActive() const { return mOptions.mAlwaysActive; } bool alwaysActive() const { return mOptions.mAlwaysActive; }

@ -113,6 +113,14 @@ namespace MWMechanics
bool isStationary() const { return mDistance == 0; } bool isStationary() const { return mDistance == 0; }
std::optional<int> getDistance() const override { return mDistance; }
std::optional<float> getDuration() const override { return static_cast<float>(mDuration); }
const std::vector<unsigned char>& getIdle() const { return mIdle; }
static std::string_view getIdleGroupName(size_t index) { return sIdleSelectToGroupName[index]; }
private: private:
void stopWalking(const MWWorld::Ptr& actor); void stopWalking(const MWWorld::Ptr& actor);

@ -99,6 +99,18 @@ Follow another actor.
* - target * - target
- `GameObject <openmw_core.html##(GameObject)>`_ [required] - `GameObject <openmw_core.html##(GameObject)>`_ [required]
- the actor to follow - the actor to follow
* - destCell
- Cell [optional]
- the destination cell
* - duration
- number [optional]
- duration in game time (will be rounded up to the next hour)
* - destPosition
- `3d vector <openmw_util.html##(Vector3)>`_ [optional]
- the destination point
* - isRepeat
- boolean [optional]
- Will the package repeat (true or false)
Escort Escort
------ ------
@ -126,6 +138,9 @@ Escort another actor to the given location.
* - duration * - duration
- number [optional] - number [optional]
- duration in game time (will be rounded up to the next hour) - duration in game time (will be rounded up to the next hour)
* - isRepeat
- boolean [optional]
- Will the package repeat (true or false)
**Example** **Example**
@ -136,6 +151,7 @@ Escort another actor to the given location.
target = object.self, target = object.self,
destPosition = util.vector3(x, y, z), destPosition = util.vector3(x, y, z),
duration = 3 * time.hour, duration = 3 * time.hour,
isRepeat = true
}) })
Wander Wander
@ -158,6 +174,34 @@ Wander nearby current position.
* - duration * - duration
- number [optional] - number [optional]
- duration in game time (will be rounded up to the next hour) - duration in game time (will be rounded up to the next hour)
* - idle
- table [optional]
- Idle chance values, up to 8
* - isRepeat
- boolean [optional]
- Will the package repeat (true or false)
**Example**
.. code-block:: Lua
local idleTable = {
idle2 = 60,
idle3 = 50,
idle4 = 40,
idle5 = 30,
idle6 = 20,
idle7 = 10,
idle8 = 0,
idle9 = 25
}
actor:sendEvent('StartAIPackage', {
type = 'Wander',
distance = 5000,
duration = 5 * time.hour,
idle = idleTable,
isRepeat = true
})
Travel Travel
------ ------
@ -176,4 +220,6 @@ Go to given location.
* - destPosition * - destPosition
- `3d vector <openmw_util.html##(Vector3)>`_ [required] - `3d vector <openmw_util.html##(Vector3)>`_ [required]
- the point to travel to - the point to travel to
* - isRepeat
- boolean [optional]
- Will the package repeat (true or false)

@ -1,5 +1,6 @@
local self = require('openmw.self') local self = require('openmw.self')
local interfaces = require('openmw.interfaces') local interfaces = require('openmw.interfaces')
local util = require('openmw.util')
local function startPackage(args) local function startPackage(args)
local cancelOther = args.cancelOther local cancelOther = args.cancelOther
@ -12,16 +13,34 @@ local function startPackage(args)
self:_startAiPursue(args.target, cancelOther) self:_startAiPursue(args.target, cancelOther)
elseif args.type == 'Follow' then elseif args.type == 'Follow' then
if not args.target then error("target required") end if not args.target then error("target required") end
self:_startAiFollow(args.target, cancelOther) self:_startAiFollow(args.target, args.cellId, args.duration or 0, args.destPosition or util.vector3(0, 0, 0), args.isRepeat or false, cancelOther)
elseif args.type == 'Escort' then elseif args.type == 'Escort' then
if not args.target then error("target required") end if not args.target then error("target required") end
if not args.destPosition then error("destPosition required") end if not args.destPosition then error("destPosition required") end
self:_startAiEscort(args.target, args.destCell or self.cell, args.duration or 0, args.destPosition, cancelOther) self:_startAiEscort(args.target, args.destCell or self.cell, args.duration or 0, args.destPosition, cancelOther)
elseif args.type == 'Wander' then elseif args.type == 'Wander' then
self:_startAiWander(args.distance or 0, args.duration or 0, cancelOther) local key = "idle"
local idle = {}
local duration = 0
for i = 2, 9 do
local val = args.idle[key .. i]
if val == nil then
idle[i-1] = 0
else
local v = tonumber(val) or 0
if v < 0 or v > 100 then
error("idle values cannot exceed 100")
end
idle[i-1] = v
end
end
if args.duration then
duration = args.duration / 3600
end
self:_startAiWander(args.distance or 0, duration, idle, args.isRepeat or false, cancelOther)
elseif args.type == 'Travel' then elseif args.type == 'Travel' then
if not args.destPosition then error("destPosition required") end if not args.destPosition then error("destPosition required") end
self:_startAiTravel(args.destPosition, cancelOther) self:_startAiTravel(args.destPosition, args.isRepeat or false, cancelOther)
else else
error('Unsupported AI Package: ' .. args.type) error('Unsupported AI Package: ' .. args.type)
end end
@ -47,6 +66,10 @@ return {
-- @field openmw.core#GameObject target Target (usually an actor) of the AI package (can be nil). -- @field openmw.core#GameObject target Target (usually an actor) of the AI package (can be nil).
-- @field #boolean sideWithTarget Whether to help the target in combat (true or false). -- @field #boolean sideWithTarget Whether to help the target in combat (true or false).
-- @field openmw.util#Vector3 destPosition Destination point of the AI package. -- @field openmw.util#Vector3 destPosition Destination point of the AI package.
-- @field #number distance Distance value (can be nil).
-- @field #number duration Duration value (can be nil).
-- @field #table idle Idle value (can be nil).
-- @field #boolean isRepeat Should this package be repeated (true or false).
--- Return the currently active AI package (or `nil` if there are no AI packages). --- Return the currently active AI package (or `nil` if there are no AI packages).
-- @function [parent=#AI] getActivePackage -- @function [parent=#AI] getActivePackage

Loading…
Cancel
Save