Merge branch 'ai-exposed' into 'master'

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

See merge request OpenMW/openmw!4006
pull/3235/head
psi29a 8 months ago
commit 739ff70a82

@ -81,7 +81,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 49)
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_VERSION_COMMITHASH "")

@ -104,7 +104,27 @@ namespace MWLua
});
aiPackage["sideWithTarget"] = sol::readonly_property([](const AiPackage& p) { return p.sideWithTarget(); });
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>> {
const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
@ -132,13 +152,25 @@ namespace MWLua
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
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();
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,
const osg::Vec3f& dest, bool cancelOther) {
const osg::Vec3f& dest, bool repeat, bool cancelOther) {
const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
// 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));
auto* esmCell = cell.mStore->getCell();
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);
else
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);
};
selfAPI["_startAiWander"] = [](SelfObject& self, int distance, float duration, bool cancelOther) {
const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
int gameHoursDuration = static_cast<int>(std::ceil(duration / 3600.0));
ai.stack(MWMechanics::AiWander(distance, gameHoursDuration, 0, {}, false), ptr, cancelOther);
};
selfAPI["_startAiTravel"] = [](SelfObject& self, const osg::Vec3f& target, bool cancelOther) {
selfAPI["_startAiWander"]
= [](SelfObject& self, int distance, int duration, sol::table luaIdle, bool repeat, bool cancelOther) {
const MWWorld::Ptr& ptr = self.ptr();
MWMechanics::AiSequence& ai = ptr.getClass().getCreatureStats(ptr).getAiSequence();
std::vector<unsigned char> idle;
// 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 repeat, bool cancelOther) {
const MWWorld::Ptr& ptr = self.ptr();
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) {
const MWWorld::Ptr& ptr = self.ptr();

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

@ -110,6 +110,10 @@ namespace MWMechanics
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.
bool alwaysActive() const { return mOptions.mAlwaysActive; }

@ -113,6 +113,14 @@ namespace MWMechanics
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:
void stopWalking(const MWWorld::Ptr& actor);

@ -99,6 +99,18 @@ Follow another actor.
* - target
- `GameObject <openmw_core.html##(GameObject)>`_ [required]
- 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
------
@ -126,6 +138,9 @@ Escort another actor to the given location.
* - duration
- number [optional]
- duration in game time (will be rounded up to the next hour)
* - isRepeat
- boolean [optional]
- Will the package repeat (true or false)
**Example**
@ -136,6 +151,7 @@ Escort another actor to the given location.
target = object.self,
destPosition = util.vector3(x, y, z),
duration = 3 * time.hour,
isRepeat = true
})
Wander
@ -158,6 +174,34 @@ Wander nearby current position.
* - duration
- number [optional]
- 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
------
@ -176,4 +220,6 @@ Go to given location.
* - destPosition
- `3d vector <openmw_util.html##(Vector3)>`_ [required]
- 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 interfaces = require('openmw.interfaces')
local util = require('openmw.util')
local function startPackage(args)
local cancelOther = args.cancelOther
@ -12,16 +13,34 @@ local function startPackage(args)
self:_startAiPursue(args.target, cancelOther)
elseif args.type == 'Follow' then
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
if not args.target then error("target 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)
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
if not args.destPosition then error("destPosition required") end
self:_startAiTravel(args.destPosition, cancelOther)
self:_startAiTravel(args.destPosition, args.isRepeat or false, cancelOther)
else
error('Unsupported AI Package: ' .. args.type)
end
@ -47,6 +66,10 @@ return {
-- @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 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).
-- @function [parent=#AI] getActivePackage

Loading…
Cancel
Save