Skip to content

Commit

Permalink
Expose Wander option values to the Lua API (#7916)
Browse files Browse the repository at this point in the history
  • Loading branch information
hristoast authored and psi29a committed May 3, 2024
1 parent a57c350 commit 63a27bb
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 18 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 "")
Expand Down
62 changes: 49 additions & 13 deletions apps/openmw/mwlua/localscripts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -132,37 +152,53 @@ 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.
const ESM::RefId& refId = target.ptr().getCellRef().getRefId();
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();
Expand Down
2 changes: 2 additions & 0 deletions apps/openmw/mwmechanics/aiescort.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions apps/openmw/mwmechanics/aipackage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
8 changes: 8 additions & 0 deletions apps/openmw/mwmechanics/aiwander.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
48 changes: 47 additions & 1 deletion docs/source/reference/lua-scripting/aipackages.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
------
Expand Down Expand Up @@ -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**

Expand All @@ -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
Expand All @@ -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
------
Expand All @@ -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)
29 changes: 26 additions & 3 deletions files/data/scripts/omw/ai.lua
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 63a27bb

Please sign in to comment.