mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-09-09 20:35:48 -04:00
add OFFENSE_TYPE and commitCrime to lua
This commit is contained in:
parent
34e32b7392
commit
9248e37156
@ -1,10 +1,14 @@
|
|||||||
#include "types.hpp"
|
#include "types.hpp"
|
||||||
|
|
||||||
|
#include <components/esm3/loadbsgn.hpp>
|
||||||
|
#include <components/esm3/loadfact.hpp>
|
||||||
|
|
||||||
#include "../birthsignbindings.hpp"
|
#include "../birthsignbindings.hpp"
|
||||||
#include "../luamanagerimp.hpp"
|
#include "../luamanagerimp.hpp"
|
||||||
|
|
||||||
#include "apps/openmw/mwbase/inputmanager.hpp"
|
#include "apps/openmw/mwbase/inputmanager.hpp"
|
||||||
#include "apps/openmw/mwbase/journal.hpp"
|
#include "apps/openmw/mwbase/journal.hpp"
|
||||||
|
#include "apps/openmw/mwbase/mechanicsmanager.hpp"
|
||||||
#include "apps/openmw/mwbase/world.hpp"
|
#include "apps/openmw/mwbase/world.hpp"
|
||||||
#include "apps/openmw/mwmechanics/npcstats.hpp"
|
#include "apps/openmw/mwmechanics/npcstats.hpp"
|
||||||
#include "apps/openmw/mwworld/class.hpp"
|
#include "apps/openmw/mwworld/class.hpp"
|
||||||
@ -12,8 +16,6 @@
|
|||||||
#include "apps/openmw/mwworld/globals.hpp"
|
#include "apps/openmw/mwworld/globals.hpp"
|
||||||
#include "apps/openmw/mwworld/player.hpp"
|
#include "apps/openmw/mwworld/player.hpp"
|
||||||
|
|
||||||
#include <components/esm3/loadbsgn.hpp>
|
|
||||||
|
|
||||||
namespace MWLua
|
namespace MWLua
|
||||||
{
|
{
|
||||||
struct Quests
|
struct Quests
|
||||||
@ -51,6 +53,14 @@ namespace
|
|||||||
throw std::runtime_error("Failed to find birth sign: " + std::string(textId));
|
throw std::runtime_error("Failed to find birth sign: " + std::string(textId));
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ESM::RefId parseFactionId(std::string_view faction)
|
||||||
|
{
|
||||||
|
ESM::RefId id = ESM::RefId::deserializeText(faction);
|
||||||
|
if (!MWBase::Environment::get().getESMStore()->get<ESM::Faction>().search(id))
|
||||||
|
return ESM::RefId();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWLua
|
namespace MWLua
|
||||||
@ -61,6 +71,12 @@ namespace MWLua
|
|||||||
throw std::runtime_error("The argument must be a player!");
|
throw std::runtime_error("The argument must be a player!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void verifyNpc(const MWWorld::Class& cls)
|
||||||
|
{
|
||||||
|
if (!cls.isNpc())
|
||||||
|
throw std::runtime_error("The argument must be a NPC!");
|
||||||
|
}
|
||||||
|
|
||||||
void addPlayerBindings(sol::table player, const Context& context)
|
void addPlayerBindings(sol::table player, const Context& context)
|
||||||
{
|
{
|
||||||
MWBase::Journal* const journal = MWBase::Environment::get().getJournal();
|
MWBase::Journal* const journal = MWBase::Environment::get().getJournal();
|
||||||
@ -201,6 +217,36 @@ namespace MWLua
|
|||||||
return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1;
|
return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
player["OFFENSE_TYPE"]
|
||||||
|
= LuaUtil::makeStrictReadOnly(LuaUtil::tableFromPairs<std::string_view, int>(context.sol(),
|
||||||
|
{ { "Theft", MWBase::MechanicsManager::OffenseType::OT_Theft },
|
||||||
|
{ "Assault", MWBase::MechanicsManager::OffenseType::OT_Assault },
|
||||||
|
{ "Murder", MWBase::MechanicsManager::OffenseType::OT_Murder },
|
||||||
|
{ "Trespassing", MWBase::MechanicsManager::OffenseType::OT_Trespassing },
|
||||||
|
{ "SleepingInOwnedBed", MWBase::MechanicsManager::OffenseType::OT_SleepingInOwnedBed },
|
||||||
|
{ "Pickpocket", MWBase::MechanicsManager::OffenseType::OT_Pickpocket } }));
|
||||||
|
player["_runStandardCommitCrime"] = [](const Object& o, const sol::optional<Object> victim, int type,
|
||||||
|
std::string_view faction, int arg = 0, bool victimAware = false) {
|
||||||
|
verifyPlayer(o);
|
||||||
|
if (victim.has_value() && !victim->ptrOrEmpty().isEmpty())
|
||||||
|
verifyNpc(victim->ptrOrEmpty().getClass());
|
||||||
|
if (!dynamic_cast<const GObject*>(&o))
|
||||||
|
throw std::runtime_error("Only global scripts can commit crime");
|
||||||
|
if (type < 0 || type > MWBase::MechanicsManager::OffenseType::OT_Pickpocket)
|
||||||
|
throw std::runtime_error("Invalid offense type");
|
||||||
|
|
||||||
|
ESM::RefId factionId = parseFactionId(faction);
|
||||||
|
// If the faction is provided but not found, error out
|
||||||
|
if (faction != "" && factionId == ESM::RefId())
|
||||||
|
throw std::runtime_error("Faction does not exist");
|
||||||
|
|
||||||
|
MWWorld::Ptr victimObj = nullptr;
|
||||||
|
if (victim.has_value())
|
||||||
|
victimObj = victim->ptrOrEmpty();
|
||||||
|
return MWBase::Environment::get().getMechanicsManager()->commitCrime(o.ptr(), victimObj,
|
||||||
|
static_cast<MWBase::MechanicsManager::OffenseType>(type), factionId, arg, victimAware);
|
||||||
|
};
|
||||||
|
|
||||||
player["birthSigns"] = initBirthSignRecordBindings(context);
|
player["birthSigns"] = initBirthSignRecordBindings(context);
|
||||||
player["getBirthSign"] = [](const Object& player) -> std::string {
|
player["getBirthSign"] = [](const Object& player) -> std::string {
|
||||||
verifyPlayer(player);
|
verifyPlayer(player);
|
||||||
|
@ -11,5 +11,6 @@ paths=(
|
|||||||
scripts/omw/ui.lua
|
scripts/omw/ui.lua
|
||||||
scripts/omw/usehandlers.lua
|
scripts/omw/usehandlers.lua
|
||||||
scripts/omw/skillhandlers.lua
|
scripts/omw/skillhandlers.lua
|
||||||
|
scripts/omw/crimes.lua
|
||||||
)
|
)
|
||||||
printf '%s\n' "${paths[@]}"
|
printf '%s\n' "${paths[@]}"
|
||||||
|
@ -45,6 +45,7 @@ Lua API reference
|
|||||||
interface_settings
|
interface_settings
|
||||||
interface_skill_progression
|
interface_skill_progression
|
||||||
interface_ui
|
interface_ui
|
||||||
|
interface_crimes
|
||||||
iterables
|
iterables
|
||||||
|
|
||||||
|
|
||||||
|
5
docs/source/reference/lua-scripting/interface_crimes.rst
Normal file
5
docs/source/reference/lua-scripting/interface_crimes.rst
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Interface Crimes
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
:file: generated_html/scripts_omw_crimes.html
|
@ -43,3 +43,6 @@
|
|||||||
- by player scripts
|
- by player scripts
|
||||||
- | High-level UI modes interface. Allows to override parts
|
- | High-level UI modes interface. Allows to override parts
|
||||||
| of the interface.
|
| of the interface.
|
||||||
|
* - :ref:`Crimes <Interface Crimes>`
|
||||||
|
- by global scripts
|
||||||
|
- Commit crimes.
|
||||||
|
@ -110,6 +110,7 @@ set(BUILTIN_DATA_FILES
|
|||||||
scripts/omw/mwui/space.lua
|
scripts/omw/mwui/space.lua
|
||||||
scripts/omw/mwui/init.lua
|
scripts/omw/mwui/init.lua
|
||||||
scripts/omw/skillhandlers.lua
|
scripts/omw/skillhandlers.lua
|
||||||
|
scripts/omw/crimes.lua
|
||||||
scripts/omw/ui.lua
|
scripts/omw/ui.lua
|
||||||
scripts/omw/usehandlers.lua
|
scripts/omw/usehandlers.lua
|
||||||
scripts/omw/worldeventhandlers.lua
|
scripts/omw/worldeventhandlers.lua
|
||||||
|
@ -11,6 +11,7 @@ GLOBAL: scripts/omw/activationhandlers.lua
|
|||||||
GLOBAL: scripts/omw/cellhandlers.lua
|
GLOBAL: scripts/omw/cellhandlers.lua
|
||||||
GLOBAL: scripts/omw/usehandlers.lua
|
GLOBAL: scripts/omw/usehandlers.lua
|
||||||
GLOBAL: scripts/omw/worldeventhandlers.lua
|
GLOBAL: scripts/omw/worldeventhandlers.lua
|
||||||
|
GLOBAL: scripts/omw/crimes.lua
|
||||||
CREATURE, NPC, PLAYER: scripts/omw/mechanics/animationcontroller.lua
|
CREATURE, NPC, PLAYER: scripts/omw/mechanics/animationcontroller.lua
|
||||||
PLAYER: scripts/omw/skillhandlers.lua
|
PLAYER: scripts/omw/skillhandlers.lua
|
||||||
PLAYER: scripts/omw/mechanics/playercontroller.lua
|
PLAYER: scripts/omw/mechanics/playercontroller.lua
|
||||||
|
63
files/data/scripts/omw/crimes.lua
Normal file
63
files/data/scripts/omw/crimes.lua
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
local types = require('openmw.types')
|
||||||
|
local I = require('openmw.interfaces')
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Table with information needed to commit crimes.
|
||||||
|
-- @type CommitCrimeInputs
|
||||||
|
-- @field openmw.core#GameObject victim The victim of the crime (optional)
|
||||||
|
-- @field openmw.types#OFFENSE_TYPE type The type of the crime to commit. See @{openmw.types#OFFENSE_TYPE} (required)
|
||||||
|
-- @field #string faction ID of the faction the crime is committed against (optional)
|
||||||
|
-- @field #number arg The amount to increase the player bounty by, if the crime type is theft. Ignored otherwise (optional, defaults to 0)
|
||||||
|
-- @field #boolean victimAware Whether the victim is aware of the crime (optional, defaults to false)
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Table containing information returned by the engine after committing a crime
|
||||||
|
-- @type CommitCrimeOutputs
|
||||||
|
-- @field #boolean wasCrimeSeen Whether the crime was seen
|
||||||
|
|
||||||
|
return {
|
||||||
|
interfaceName = 'Crimes',
|
||||||
|
---
|
||||||
|
-- Allows to utilize built-in crime mechanics.
|
||||||
|
-- @module Crimes
|
||||||
|
-- @usage require('openmw.interfaces').Crimes
|
||||||
|
interface = {
|
||||||
|
--- Interface version
|
||||||
|
-- @field [parent=#Crimes] #number version
|
||||||
|
version = 1,
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Commits a crime as if done through an in-game action. Can only be used in global context.
|
||||||
|
-- @function [parent=#Crimes] commitCrime
|
||||||
|
-- @param openmw.core#GameObject player The player committing the crime
|
||||||
|
-- @param CommitCrimeInputs options A table of parameters describing the committed crime
|
||||||
|
-- @return CommitCrimeOutputs A table containing information about the committed crime
|
||||||
|
commitCrime = function(player, options)
|
||||||
|
assert(types.Player.objectIsInstance(player), "commitCrime requires a player game object")
|
||||||
|
|
||||||
|
local returnTable = {}
|
||||||
|
local options = options or {}
|
||||||
|
|
||||||
|
assert(type(options.faction) == "string" or options.faction == nil,
|
||||||
|
"faction id passed to commitCrime must be a string or nil")
|
||||||
|
assert(type(options.arg) == "number" or options.arg == nil,
|
||||||
|
"arg value passed to commitCrime must be a number or nil")
|
||||||
|
assert(type(options.victimAware) == "number" or options.victimAware == nil,
|
||||||
|
"victimAware value passed to commitCrime must be a boolean or nil")
|
||||||
|
|
||||||
|
assert(options.type ~= nil, "crime type passed to commitCrime cannot be nil")
|
||||||
|
assert(type(options.type) == "number", "crime type passed to commitCrime must be a number")
|
||||||
|
|
||||||
|
assert(options.victim == nil or types.NPC.objectIsInstance(options.victim),
|
||||||
|
"victim passed to commitCrime must be an NPC or nil")
|
||||||
|
|
||||||
|
returnTable.wasCrimeSeen = types.Player._runStandardCommitCrime(player, options.victim, options.type,
|
||||||
|
options.faction or "",
|
||||||
|
options.arg or 0, options.victimAware or false)
|
||||||
|
return returnTable
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
eventHandlers = {
|
||||||
|
CommitCrime = function(data) I.Crimes.commitCrime(data.player, data) end,
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,9 @@
|
|||||||
---
|
---
|
||||||
-- @field [parent=#interfaces] scripts.omw.skillhandlers#scripts.omw.skillhandlers SkillProgression
|
-- @field [parent=#interfaces] scripts.omw.skillhandlers#scripts.omw.skillhandlers SkillProgression
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @field [parent=#interfaces] scripts.omw.crimes#scripts.omw.crimes Crimes
|
||||||
|
|
||||||
---
|
---
|
||||||
-- @function [parent=#interfaces] __index
|
-- @function [parent=#interfaces] __index
|
||||||
-- @param #interfaces self
|
-- @param #interfaces self
|
||||||
|
@ -1169,6 +1169,19 @@
|
|||||||
-- @param openmw.core#GameObject player
|
-- @param openmw.core#GameObject player
|
||||||
-- @param #number crimeLevel The requested crime level
|
-- @param #number crimeLevel The requested crime level
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @type OFFENSE_TYPE
|
||||||
|
-- @field #number Theft
|
||||||
|
-- @field #number Assault
|
||||||
|
-- @field #number Murder
|
||||||
|
-- @field #number Trespassing
|
||||||
|
-- @field #number SleepingInOwnedBed
|
||||||
|
-- @field #number Pickpocket
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Available @{#OFFENSE_TYPE} values. Used in `I.Crimes.commitCrime`.
|
||||||
|
-- @field [parent=#Player] #OFFENSE_TYPE OFFENSE_TYPE
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Whether the character generation for this player is finished.
|
-- Whether the character generation for this player is finished.
|
||||||
-- @function [parent=#Player] isCharGenFinished
|
-- @function [parent=#Player] isCharGenFinished
|
||||||
|
@ -5,6 +5,7 @@ local util = require('openmw.util')
|
|||||||
local types = require('openmw.types')
|
local types = require('openmw.types')
|
||||||
local vfs = require('openmw.vfs')
|
local vfs = require('openmw.vfs')
|
||||||
local world = require('openmw.world')
|
local world = require('openmw.world')
|
||||||
|
local I = require('openmw.interfaces')
|
||||||
|
|
||||||
local function testTimers()
|
local function testTimers()
|
||||||
testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result')
|
testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result')
|
||||||
@ -261,6 +262,29 @@ local function testVFS()
|
|||||||
testing.expectEqual(vfs.type(handle), 'closed file', 'File should be closed')
|
testing.expectEqual(vfs.type(handle), 'closed file', 'File should be closed')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function testCommitCrime()
|
||||||
|
initPlayer()
|
||||||
|
local player = world.players[1]
|
||||||
|
testing.expectEqual(player == nil, false, 'A viable player reference should exist to run `testCommitCrime`')
|
||||||
|
testing.expectEqual(I.Crimes == nil, false, 'Crimes interface should be available in global contexts')
|
||||||
|
|
||||||
|
-- Reset crime level to have a clean slate
|
||||||
|
types.Player.setCrimeLevel(player, 0)
|
||||||
|
testing.expectEqual(I.Crimes.commitCrime(player, { type = types.Player.OFFENSE_TYPE.Theft, victim = player, arg = 100}).wasCrimeSeen, false, "Running the crime with the player as the victim should not result in a seen crime")
|
||||||
|
testing.expectEqual(I.Crimes.commitCrime(player, { type = types.Player.OFFENSE_TYPE.Theft, arg = 50 }).wasCrimeSeen, false, "Running the crime with no victim and a type shouldn't raise errors")
|
||||||
|
testing.expectEqual(I.Crimes.commitCrime(player, { type = types.Player.OFFENSE_TYPE.Murder }).wasCrimeSeen, false, "Running a murder crime should work even without a victim")
|
||||||
|
|
||||||
|
-- Create a mockup target for crimes
|
||||||
|
local victim = world.createObject(types.NPC.record(player).id)
|
||||||
|
victim:teleport(player.cell, player.position + util.vector3(0, 300, 0))
|
||||||
|
coroutine.yield()
|
||||||
|
|
||||||
|
-- Reset crime level for testing with a valid victim
|
||||||
|
types.Player.setCrimeLevel(player, 0)
|
||||||
|
testing.expectEqual(I.Crimes.commitCrime(player, { victim = victim, type = types.Player.OFFENSE_TYPE.Theft, arg = 50 }).wasCrimeSeen, true, "Running a crime with a valid victim should notify them when the player is not sneaking, even if it's not explicitly passed in")
|
||||||
|
testing.expectEqual(types.Player.getCrimeLevel(player), 0, "Crime level should not change if the victim's alarm value is low and there's no other witnesses")
|
||||||
|
end
|
||||||
|
|
||||||
tests = {
|
tests = {
|
||||||
{'timers', testTimers},
|
{'timers', testTimers},
|
||||||
{'rotating player with controls.yawChange should change rotation', function()
|
{'rotating player with controls.yawChange should change rotation', function()
|
||||||
@ -321,6 +345,7 @@ tests = {
|
|||||||
testing.runLocalTest(player, 'playerWeaponAttack')
|
testing.runLocalTest(player, 'playerWeaponAttack')
|
||||||
end},
|
end},
|
||||||
{'vfs', testVFS},
|
{'vfs', testVFS},
|
||||||
|
{'testCommitCrime', testCommitCrime}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user