mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-16 03:29:55 +00:00
add OFFENSE_TYPE and commitCrime to lua
This commit is contained in:
parent
34e32b7392
commit
9248e37156
11 changed files with 164 additions and 2 deletions
|
@ -1,10 +1,14 @@
|
|||
#include "types.hpp"
|
||||
|
||||
#include <components/esm3/loadbsgn.hpp>
|
||||
#include <components/esm3/loadfact.hpp>
|
||||
|
||||
#include "../birthsignbindings.hpp"
|
||||
#include "../luamanagerimp.hpp"
|
||||
|
||||
#include "apps/openmw/mwbase/inputmanager.hpp"
|
||||
#include "apps/openmw/mwbase/journal.hpp"
|
||||
#include "apps/openmw/mwbase/mechanicsmanager.hpp"
|
||||
#include "apps/openmw/mwbase/world.hpp"
|
||||
#include "apps/openmw/mwmechanics/npcstats.hpp"
|
||||
#include "apps/openmw/mwworld/class.hpp"
|
||||
|
@ -12,8 +16,6 @@
|
|||
#include "apps/openmw/mwworld/globals.hpp"
|
||||
#include "apps/openmw/mwworld/player.hpp"
|
||||
|
||||
#include <components/esm3/loadbsgn.hpp>
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
struct Quests
|
||||
|
@ -51,6 +53,14 @@ namespace
|
|||
throw std::runtime_error("Failed to find birth sign: " + std::string(textId));
|
||||
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
|
||||
|
@ -61,6 +71,12 @@ namespace MWLua
|
|||
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)
|
||||
{
|
||||
MWBase::Journal* const journal = MWBase::Environment::get().getJournal();
|
||||
|
@ -201,6 +217,36 @@ namespace MWLua
|
|||
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["getBirthSign"] = [](const Object& player) -> std::string {
|
||||
verifyPlayer(player);
|
||||
|
|
|
@ -11,5 +11,6 @@ paths=(
|
|||
scripts/omw/ui.lua
|
||||
scripts/omw/usehandlers.lua
|
||||
scripts/omw/skillhandlers.lua
|
||||
scripts/omw/crimes.lua
|
||||
)
|
||||
printf '%s\n' "${paths[@]}"
|
||||
|
|
|
@ -45,6 +45,7 @@ Lua API reference
|
|||
interface_settings
|
||||
interface_skill_progression
|
||||
interface_ui
|
||||
interface_crimes
|
||||
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
|
||||
- | High-level UI modes interface. Allows to override parts
|
||||
| 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/init.lua
|
||||
scripts/omw/skillhandlers.lua
|
||||
scripts/omw/crimes.lua
|
||||
scripts/omw/ui.lua
|
||||
scripts/omw/usehandlers.lua
|
||||
scripts/omw/worldeventhandlers.lua
|
||||
|
|
|
@ -11,6 +11,7 @@ GLOBAL: scripts/omw/activationhandlers.lua
|
|||
GLOBAL: scripts/omw/cellhandlers.lua
|
||||
GLOBAL: scripts/omw/usehandlers.lua
|
||||
GLOBAL: scripts/omw/worldeventhandlers.lua
|
||||
GLOBAL: scripts/omw/crimes.lua
|
||||
CREATURE, NPC, PLAYER: scripts/omw/mechanics/animationcontroller.lua
|
||||
PLAYER: scripts/omw/skillhandlers.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.crimes#scripts.omw.crimes Crimes
|
||||
|
||||
---
|
||||
-- @function [parent=#interfaces] __index
|
||||
-- @param #interfaces self
|
||||
|
|
|
@ -1169,6 +1169,19 @@
|
|||
-- @param openmw.core#GameObject player
|
||||
-- @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.
|
||||
-- @function [parent=#Player] isCharGenFinished
|
||||
|
|
|
@ -5,6 +5,7 @@ local util = require('openmw.util')
|
|||
local types = require('openmw.types')
|
||||
local vfs = require('openmw.vfs')
|
||||
local world = require('openmw.world')
|
||||
local I = require('openmw.interfaces')
|
||||
|
||||
local function testTimers()
|
||||
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')
|
||||
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 = {
|
||||
{'timers', testTimers},
|
||||
{'rotating player with controls.yawChange should change rotation', function()
|
||||
|
@ -321,6 +345,7 @@ tests = {
|
|||
testing.runLocalTest(player, 'playerWeaponAttack')
|
||||
end},
|
||||
{'vfs', testVFS},
|
||||
{'testCommitCrime', testCommitCrime}
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
Loading…
Reference in a new issue