Bring jail time skill changes over to lua

This commit is contained in:
Mads Buvik Sandvei 2025-08-02 08:39:13 +00:00 committed by psi29a
parent b160cee0b7
commit 978d8668f4
13 changed files with 119 additions and 69 deletions

View File

@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 50) set(OPENMW_VERSION_MINOR 50)
set(OPENMW_VERSION_RELEASE 0) set(OPENMW_VERSION_RELEASE 0)
set(OPENMW_LUA_API_REVISION 86) set(OPENMW_LUA_API_REVISION 87)
set(OPENMW_POSTPROCESSING_API_REVISION 3) set(OPENMW_POSTPROCESSING_API_REVISION 3)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")

View File

@ -75,6 +75,7 @@ namespace MWBase
const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback)
= 0; = 0;
virtual void jailTimeServed(const MWWorld::Ptr& actor, int days) = 0;
virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0; virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0;
virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0;
virtual void onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon, virtual void onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon,

View File

@ -4,6 +4,7 @@
#include <components/misc/strings/format.hpp> #include <components/misc/strings/format.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -86,46 +87,6 @@ namespace MWGui
// We should not worsen corprus when in prison // We should not worsen corprus when in prison
player.getClass().getCreatureStats(player).getActiveSpells().skipWorsenings(mDays * 24); player.getClass().getCreatureStats(player).getActiveSpells().skipWorsenings(mDays * 24);
MWBase::Environment::get().getLuaManager()->jailTimeServed(player, mDays);
const auto& skillStore = MWBase::Environment::get().getESMStore()->get<ESM::Skill>();
std::set<const ESM::Skill*> skills;
for (int day = 0; day < mDays; ++day)
{
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
const ESM::Skill* skill = skillStore.searchRandom({}, prng);
skills.insert(skill);
MWMechanics::SkillValue& value = player.getClass().getNpcStats(player).getSkill(skill->mId);
if (skill->mId == ESM::Skill::Security || skill->mId == ESM::Skill::Sneak)
value.setBase(std::min(100.f, value.getBase() + 1));
else
value.setBase(std::max(0.f, value.getBase() - 1));
}
const MWWorld::Store<ESM::GameSetting>& gmst
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
std::string message;
if (mDays == 1)
message = gmst.find("sNotifyMessage42")->mValue.getString();
else
message = gmst.find("sNotifyMessage43")->mValue.getString();
message = Misc::StringUtils::format(message, mDays);
for (const ESM::Skill* skill : skills)
{
int skillValue = player.getClass().getNpcStats(player).getSkill(skill->mId).getBase();
std::string skillMsg = gmst.find("sNotifyMessage44")->mValue.getString();
if (skill->mId == ESM::Skill::Sneak || skill->mId == ESM::Skill::Security)
skillMsg = gmst.find("sNotifyMessage39")->mValue.getString();
skillMsg = Misc::StringUtils::format(skillMsg, skill->mName, skillValue);
message += "\n" + skillMsg;
}
std::vector<std::string> buttons;
buttons.emplace_back("#{Interface:OK}");
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons);
} }
} }

View File

@ -113,6 +113,15 @@ namespace MWLua
scripts->onSkillLevelUp(event.mSkill, event.mSource); scripts->onSkillLevelUp(event.mSkill, event.mSource);
} }
void operator()(const OnJailTimeServed& event) const
{
MWWorld::Ptr actor = getPtr(event.mActor);
if (actor.isEmpty())
return;
if (auto* scripts = getLocalScripts(actor))
scripts->onJailTimeServed(event.mDays);
}
private: private:
MWWorld::Ptr getPtr(ESM::RefNum id) const MWWorld::Ptr getPtr(ESM::RefNum id) const
{ {

View File

@ -70,8 +70,13 @@ namespace MWLua
std::string mSkill; std::string mSkill;
std::string mSource; std::string mSource;
}; };
struct OnJailTimeServed
{
ESM::RefNum mActor;
int mDays;
};
using Event = std::variant<OnActive, OnInactive, OnConsume, OnActivate, OnUseItem, OnNewExterior, OnTeleported, using Event = std::variant<OnActive, OnInactive, OnConsume, OnActivate, OnUseItem, OnNewExterior, OnTeleported,
OnAnimationTextKey, OnSkillUse, OnSkillLevelUp>; OnAnimationTextKey, OnSkillUse, OnSkillLevelUp, OnJailTimeServed>;
void clear() { mQueue.clear(); } void clear() { mQueue.clear(); }
void addToQueue(Event e) { mQueue.push_back(std::move(e)); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); }

View File

@ -232,7 +232,7 @@ namespace MWLua
[&](LuaUtil::LuaView& view) { addPackage("openmw.self", sol::make_object(view.sol(), &mData)); }); [&](LuaUtil::LuaView& view) { addPackage("openmw.self", sol::make_object(view.sol(), &mData)); });
registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers,
&mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse, &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse,
&mOnSkillLevelUp }); &mOnSkillLevelUp, &mOnJailTimeServed });
} }
void LocalScripts::setActive(bool active, bool callHandlers) void LocalScripts::setActive(bool active, bool callHandlers)

View File

@ -89,6 +89,7 @@ namespace MWLua
{ {
callEngineHandlers(mOnSkillLevelUp, skillId, source); callEngineHandlers(mOnSkillLevelUp, skillId, source);
} }
void onJailTimeServed(int days) { callEngineHandlers(mOnJailTimeServed, days); }
void applyStatsCache(); void applyStatsCache();
@ -118,6 +119,7 @@ namespace MWLua
EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" }; EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" };
EngineHandlerList mOnSkillUse{ "_onSkillUse" }; EngineHandlerList mOnSkillUse{ "_onSkillUse" };
EngineHandlerList mOnSkillLevelUp{ "_onSkillLevelUp" }; EngineHandlerList mOnSkillLevelUp{ "_onSkillLevelUp" };
EngineHandlerList mOnJailTimeServed{ "_onJailTimeServed" };
}; };
} }

View File

@ -490,6 +490,11 @@ namespace MWLua
EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) }); EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) });
} }
void LuaManager::jailTimeServed(const MWWorld::Ptr& actor, int days)
{
mEngineEvents.addToQueue(EngineEvents::OnJailTimeServed{ getId(actor), days });
}
void LuaManager::onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon, void LuaManager::onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon,
const MWWorld::Ptr& ammo, int attackType, float attackStrength, float damage, bool isHealth, const MWWorld::Ptr& ammo, int attackType, float attackStrength, float damage, bool isHealth,
const osg::Vec3f& hitPos, bool successful, MWMechanics::DamageSourceType sourceType) const osg::Vec3f& hitPos, bool successful, MWMechanics::DamageSourceType sourceType)

View File

@ -92,6 +92,7 @@ namespace MWLua
bool loopfallback) override; bool loopfallback) override;
void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) override; void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) override;
void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) override; void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) override;
void jailTimeServed(const MWWorld::Ptr& actor, int days) override;
void onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon, void onHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& weapon,
const MWWorld::Ptr& ammo, int attackType, float attackStrength, float damage, bool isHealth, const MWWorld::Ptr& ammo, int attackType, float attackStrength, float damage, bool isHealth,
const osg::Vec3f& hitPos, bool successful, MWMechanics::DamageSourceType sourceType) override; const osg::Vec3f& hitPos, bool successful, MWMechanics::DamageSourceType sourceType) override;

View File

@ -108,6 +108,10 @@ namespace MWLua
} }
luaManager->addUIMessage(message, mode); luaManager->addUIMessage(message, mode);
}; };
api["_showInteractiveMessage"] = [windowManager](std::string_view message, sol::optional<sol::table>) {
windowManager->interactiveMessageBox(message, { "#{Interface:OK}" });
};
api["CONSOLE_COLOR"] = LuaUtil::makeStrictReadOnly(LuaUtil::tableFromPairs<std::string, Misc::Color>(lua, api["CONSOLE_COLOR"] = LuaUtil::makeStrictReadOnly(LuaUtil::tableFromPairs<std::string, Misc::Color>(lua,
{ {
{ "Default", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Default.substr(1)) }, { "Default", Misc::Color::fromHex(MWBase::WindowManager::sConsoleColor_Default.substr(1)) },

View File

@ -39,7 +39,8 @@ end
local function skillLevelUpHandler(skillid, source, params) local function skillLevelUpHandler(skillid, source, params)
local skillStat = NPC.stats.skills[skillid](self) local skillStat = NPC.stats.skills[skillid](self)
if skillStat.base >= 100 then if (skillStat.base >= 100 and params.skillIncreaseValue > 0) or
(skillStat.base <= 0 and params.skillIncreaseValue < 0) then
return false return false
end end
@ -62,25 +63,67 @@ local function skillLevelUpHandler(skillid, source, params)
= levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + params.levelUpSpecializationIncreaseValue; = levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + params.levelUpSpecializationIncreaseValue;
end end
local skillRecord = Skill.record(skillid) if source ~= 'jail' then
local npcRecord = NPC.record(self) local skillRecord = Skill.record(skillid)
local class = NPC.classes.record(npcRecord.class) local npcRecord = NPC.record(self)
local class = NPC.classes.record(npcRecord.class)
ambient.playSound("skillraise") ambient.playSound("skillraise")
local message = string.format(core.getGMST('sNotifyMessage39'),skillRecord.name,skillStat.base) local message = string.format(core.getGMST('sNotifyMessage39'),skillRecord.name,skillStat.base)
if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then
message = '#{sBookSkillMessage}\n'..message message = '#{sBookSkillMessage}\n'..message
end
ui.showMessage(message, { showInDialogue = false })
if levelStat.progress >= core.getGMST('iLevelUpTotal') then
ui.showMessage('#{sLevelUpMsg}', { showInDialogue = false })
end
if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end
end end
end
ui.showMessage(message, { showInDialogue = false }) local function jailTimeServed(days)
if not days or days <= 0 then
return
end
if levelStat.progress >= core.getGMST('iLevelUpTotal') then local oldSkillLevels = {}
ui.showMessage('#{sLevelUpMsg}', { showInDialogue = false }) local skillByNumber = {}
for skillid, skillStat in pairs(NPC.stats.skills) do
oldSkillLevels[skillid] = skillStat(self).base
skillByNumber[#skillByNumber+1] = skillid
end
math.randomseed(core.getSimulationTime())
for day=1,days do
local skillid = skillByNumber[math.random(#skillByNumber)]
-- skillLevelUp() handles skill-based increase/decrease
I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Jail)
end end
if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end local message = ''
if days == 1 then
message = string.format(core.getGMST('sNotifyMessage42'), days)
else
message = string.format(core.getGMST('sNotifyMessage43'), days)
end
for skillid, skillStat in pairs(NPC.stats.skills) do
local diff = skillStat(self).base - oldSkillLevels[skillid]
if diff ~= 0 then
local skillMsg = core.getGMST('sNotifyMessage39')
if diff < 0 then
skillMsg = core.getGMST('sNotifyMessage44')
end
local skillRecord = Skill.record(skillid)
message = message..'\n'..string.format(skillMsg, skillRecord.name, skillStat(self).base)
end
end
I.UI.showInteractiveMessage(message)
end end
local function skillUsedHandler(skillid, params) local function skillUsedHandler(skillid, params)
@ -114,6 +157,7 @@ I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler)
return { return {
engineHandlers = { engineHandlers = {
onUpdate = onUpdate, onUpdate = onUpdate,
_onJailTimeServed = jailTimeServed,
}, },
eventHandlers = { eventHandlers = {

View File

@ -38,6 +38,7 @@ local Skill = core.stats.Skill
-- Table of all existing sources for skill increases. Any sources not listed below will be treated as equal to Trainer. -- Table of all existing sources for skill increases. Any sources not listed below will be treated as equal to Trainer.
-- @type SkillLevelUpSource -- @type SkillLevelUpSource
-- @field #string Book book -- @field #string Book book
-- @field #string Jail jail
-- @field #string Trainer trainer -- @field #string Trainer trainer
-- @field #string Usage usage -- @field #string Usage usage
@ -131,15 +132,17 @@ local function skillLevelUp(skillid, source)
levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute') levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute')
end end
local options = local options = {}
{ if source == 'jail' and not (skillid == 'security' or skillid == 'sneak') then
skillIncreaseValue = 1, options.skillIncreaseValue = -1
levelUpProgress = levelUpProgress, else
levelUpAttribute = skillRecord.attribute, options.skillIncreaseValue = 1
levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue, options.levelUpProgress = levelUpProgress
levelUpSpecialization = skillRecord.specialization, options.levelUpAttribute = skillRecord.attribute
levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization'), options.levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue
} options.levelUpSpecialization = skillRecord.specialization
options.levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization')
end
for i = #skillLevelUpHandlers, 1, -1 do for i = #skillLevelUpHandlers, 1, -1 do
if skillLevelUpHandlers[i](skillid, source, options) == false then if skillLevelUpHandlers[i](skillid, source, options) == false then
@ -156,8 +159,15 @@ return {
-- @context local -- @context local
-- @usage local I = require('openmw.interfaces') -- @usage local I = require('openmw.interfaces')
-- --
-- -- Make jail time hurt sneak skill instead of benefitting it
-- I.SkillProgression.addSkillLevelUpHandler(function(skillid, source, options)
-- if skillid == 'sneak' and source == 'jail' and options.skillIncreaseValue > 0 then
-- options.skillIncreaseValue = -options.skillIncreaseValue
-- end
-- end)
--
-- -- Forbid increasing destruction skill past 50 -- -- Forbid increasing destruction skill past 50
-- I.SkillProgression.addSkillLevelUpHandler(function(skillid, options) -- I.SkillProgression.addSkillLevelUpHandler(function(skillid, source, options)
-- if skillid == 'destruction' and types.NPC.stats.skills.destruction(self).base >= 50 then -- if skillid == 'destruction' and types.NPC.stats.skills.destruction(self).base >= 50 then
-- return false -- return false
-- end -- end
@ -187,7 +197,7 @@ return {
-- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers. -- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers.
-- These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: -- These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values:
-- --
-- * `skillIncreaseValue` - The numeric amount of skill levels gained. -- * `skillIncreaseValue` - The numeric amount of skill levels gained. By default this is 1, except when the source is jail in which case it will instead be -1 for all skills except sneak and security.
-- * `levelUpProgress` - The numeric amount of level up progress gained. -- * `levelUpProgress` - The numeric amount of level up progress gained.
-- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up. -- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up.
-- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up. -- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up.
@ -263,7 +273,7 @@ return {
--- Trigger a skill level up, activating relevant handlers --- Trigger a skill level up, activating relevant handlers
-- @function [parent=#SkillProgression] skillLevelUp -- @function [parent=#SkillProgression] skillLevelUp
-- @param #string skillid The id of the skill to level up. -- @param #string skillid The id of the skill to level up.
-- @param #SkillLevelUpSource source The source of the skill increase. -- @param #SkillLevelUpSource source The source of the skill increase. Note that passing a value of @{#SkillLevelUpSource.Jail} will cause a skill decrease for all skills except sneak and security.
skillLevelUp = skillLevelUp, skillLevelUp = skillLevelUp,
--- @{#SkillLevelUpSource} --- @{#SkillLevelUpSource}
@ -272,6 +282,7 @@ return {
Book = 'book', Book = 'book',
Usage = 'usage', Usage = 'usage',
Trainer = 'trainer', Trainer = 'trainer',
Jail = 'jail',
}, },
--- Compute the total skill gain required to level up a skill based on its current level, and other modifying factors such as major skills and specialization. --- Compute the total skill gain required to level up a skill based on its current level, and other modifying factors such as major skills and specialization.

View File

@ -171,7 +171,7 @@ return {
interface = { interface = {
--- Interface version --- Interface version
-- @field [parent=#UI] #number version -- @field [parent=#UI] #number version
version = 2, version = 3,
--- All available UI modes. --- All available UI modes.
-- Use `view(I.UI.MODE)` in `luap` console mode to see the list. -- Use `view(I.UI.MODE)` in `luap` console mode to see the list.
@ -254,6 +254,13 @@ return {
-- @return #boolean -- @return #boolean
isWindowVisible = isWindowVisible, isWindowVisible = isWindowVisible,
---
-- Shows a message as an interactive message box pausing the game, with a single button with the localized text OK.
-- @function [parent=#UI] showInteractiveMessage
-- @param #string message Message to display
-- @param #table options Options (none yet)
showInteractiveMessage = ui._showInteractiveMessage
-- TODO -- TODO
-- registerHudElement = function(name, showFn, hideFn) end, -- registerHudElement = function(name, showFn, hideFn) end,
-- showHudElement = function(name, bool) end, -- showHudElement = function(name, bool) end,