mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-30 01:45:38 +00:00
Expose Wander option values to the Lua API (#7916)
This commit is contained in:
parent
a57c350c08
commit
63a27bbf99
7 changed files with 137 additions and 18 deletions
|
@ -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) {
|
||||
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();
|
||||
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) {
|
||||
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…
Reference in a new issue