Implement crime disposition modifier (bug 4683)

macos_ci_fix
Andrei Kortunov 1 year ago
parent b39a6ae9cd
commit ffffb427f5

@ -11,6 +11,7 @@
Bug #4382: Sound output device does not change when it should Bug #4382: Sound output device does not change when it should
Bug #4508: Can't stack enchantment buffs from different instances of the same self-cast generic magic apparel Bug #4508: Can't stack enchantment buffs from different instances of the same self-cast generic magic apparel
Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely
Bug #4683: Disposition decrease when player commits crime is not implemented properly
Bug #4742: Actors with wander never stop walking after Loopgroup Walkforward Bug #4742: Actors with wander never stop walking after Loopgroup Walkforward
Bug #4743: PlayGroup doesn't play non-looping animations correctly Bug #4743: PlayGroup doesn't play non-looping animations correctly
Bug #4754: Stack of ammunition cannot be equipped partially Bug #4754: Stack of ammunition cannot be equipped partially

@ -23,9 +23,11 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp" #include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "bookpage.hpp" #include "bookpage.hpp"
#include "textcolours.hpp" #include "textcolours.hpp"
@ -736,6 +738,15 @@ namespace MWGui
bool dispositionVisible = false; bool dispositionVisible = false;
if (!mPtr.isEmpty() && mPtr.getClass().isNpc()) if (!mPtr.isEmpty() && mPtr.getClass().isNpc())
{ {
// If actor was a witness to a crime which was payed off,
// restore original disposition immediately.
MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats(mPtr);
if (npcStats.getCrimeId() != -1 && npcStats.getCrimeDispositionModifier() != 0)
{
if (npcStats.getCrimeId() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId())
npcStats.setCrimeDispositionModifier(0);
}
dispositionVisible = true; dispositionVisible = true;
mDispositionBar->setProgressRange(100); mDispositionBar->setProgressRange(100);
mDispositionBar->setProgressPosition( mDispositionBar->setProgressPosition(

@ -1164,6 +1164,9 @@ namespace MWMechanics
creatureStats.setAlarmed(false); creatureStats.setAlarmed(false);
creatureStats.setAiSetting(AiSetting::Fight, ptr.getClass().getBaseFightRating(ptr)); creatureStats.setAiSetting(AiSetting::Fight, ptr.getClass().getBaseFightRating(ptr));
// Restore original disposition
npcStats.setCrimeDispositionModifier(0);
// Update witness crime id // Update witness crime id
npcStats.setCrimeId(-1); npcStats.setCrimeId(-1);
} }

@ -473,8 +473,8 @@ namespace MWMechanics
int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr, bool clamp) int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr, bool clamp)
{ {
const MWMechanics::NpcStats& npcSkill = ptr.getClass().getNpcStats(ptr); const MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats(ptr);
float x = static_cast<float>(npcSkill.getBaseDisposition()); float x = static_cast<float>(npcStats.getBaseDisposition() + npcStats.getCrimeDispositionModifier());
MWWorld::LiveCellRef<ESM::NPC>* npc = ptr.get<ESM::NPC>(); MWWorld::LiveCellRef<ESM::NPC>* npc = ptr.get<ESM::NPC>();
MWWorld::Ptr playerPtr = getPlayer(); MWWorld::Ptr playerPtr = getPlayer();
@ -1287,62 +1287,134 @@ namespace MWMechanics
if (!canReportCrime(actor, victim, playerFollowers)) if (!canReportCrime(actor, victim, playerFollowers))
continue; continue;
if (reported && actor.getClass().isClass(actor, "guard")) NpcStats& observerStats = actor.getClass().getNpcStats(actor);
int alarm = observerStats.getAiSetting(AiSetting::Alarm).getBase();
float alarmTerm = 0.01f * alarm;
bool isActorVictim = actor == victim;
float dispTerm = isActorVictim ? dispVictim : disp;
bool isActorGuard = actor.getClass().isClass(actor, "guard");
int currentDisposition = getDerivedDisposition(actor);
bool isPermanent = false;
bool applyOnlyIfHostile = false;
int dispositionModifier = 0;
// Murdering and trespassing seem to do not affect disposition
if (type == OT_Theft)
{
dispositionModifier = static_cast<int>(dispTerm * alarmTerm);
}
else if (type == OT_Pickpocket)
{
if (alarm >= 100 && isActorGuard)
dispositionModifier = static_cast<int>(dispTerm);
else if (isActorVictim && isActorGuard)
{
isPermanent = true;
dispositionModifier = static_cast<int>(dispTerm * alarmTerm);
}
else if (isActorVictim)
{
isPermanent = true;
dispositionModifier = static_cast<int>(dispTerm);
}
}
else if (type == OT_Assault)
{
if (isActorVictim && !isActorGuard)
{
isPermanent = true;
dispositionModifier = static_cast<int>(dispTerm);
}
else if (alarm >= 100)
dispositionModifier = static_cast<int>(dispTerm);
else if (isActorVictim && isActorGuard)
{
isPermanent = true;
dispositionModifier = static_cast<int>(dispTerm * alarmTerm);
}
else
{
applyOnlyIfHostile = true;
dispositionModifier = static_cast<int>(dispTerm * alarmTerm);
}
}
bool setCrimeId = false;
if (isPermanent && dispositionModifier != 0 && !applyOnlyIfHostile)
{
setCrimeId = true;
dispositionModifier = std::clamp(dispositionModifier, -currentDisposition, 100 - currentDisposition);
int baseDisposition = observerStats.getBaseDisposition();
observerStats.setBaseDisposition(baseDisposition + dispositionModifier);
}
else if (dispositionModifier != 0 && !applyOnlyIfHostile)
{
setCrimeId = true;
dispositionModifier = std::clamp(dispositionModifier, -currentDisposition, 100 - currentDisposition);
observerStats.modCrimeDispositionModifier(dispositionModifier);
}
if (isActorGuard && alarm >= 100)
{ {
// Mark as Alarmed for dialogue // Mark as Alarmed for dialogue
actor.getClass().getCreatureStats(actor).setAlarmed(true); observerStats.setAlarmed(true);
// Set the crime ID, which we will use to calm down participants setCrimeId = true;
// once the bounty has been paid.
actor.getClass().getNpcStats(actor).setCrimeId(id);
if (!actor.getClass().getCreatureStats(actor).getAiSequence().isInPursuit()) if (!observerStats.getAiSequence().isInPursuit())
{ {
actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiPursue(player), actor); observerStats.getAiSequence().stack(AiPursue(player), actor);
} }
} }
else else
{ {
float dispTerm = (actor == victim) ? dispVictim : disp; // If Alarm is 0, treat it like 100 to calculate a Fight modifier for a victim of pickpocketing.
// Observers which do not try to arrest player do not care about pickpocketing at all.
float alarmTerm if (type == OT_Pickpocket && isActorVictim && alarmTerm == 0.0)
= 0.01f * actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Alarm).getBase();
if (type == OT_Pickpocket && alarmTerm <= 0)
alarmTerm = 1.0; alarmTerm = 1.0;
else if (type == OT_Pickpocket && !isActorVictim)
alarmTerm = 0.0;
if (actor != victim) float fightTerm = static_cast<float>(isActorVictim ? fightVictim : fight);
dispTerm *= alarmTerm;
float fightTerm = static_cast<float>((actor == victim) ? fightVictim : fight);
fightTerm += getFightDispositionBias(dispTerm); fightTerm += getFightDispositionBias(dispTerm);
fightTerm += getFightDistanceBias(actor, player); fightTerm += getFightDistanceBias(actor, player);
fightTerm *= alarmTerm; fightTerm *= alarmTerm;
const int observerFightRating const int observerFightRating = observerStats.getAiSetting(AiSetting::Fight).getBase();
= actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Fight).getBase();
if (observerFightRating + fightTerm > 100) if (observerFightRating + fightTerm > 100)
fightTerm = static_cast<float>(100 - observerFightRating); fightTerm = static_cast<float>(100 - observerFightRating);
fightTerm = std::max(0.f, fightTerm); fightTerm = std::max(0.f, fightTerm);
if (observerFightRating + fightTerm >= 100) if (observerFightRating + fightTerm >= 100)
{ {
if (dispositionModifier != 0 && applyOnlyIfHostile)
{
dispositionModifier
= std::clamp(dispositionModifier, -currentDisposition, 100 - currentDisposition);
observerStats.modCrimeDispositionModifier(dispositionModifier);
}
startCombat(actor, player); startCombat(actor, player);
NpcStats& observerStats = actor.getClass().getNpcStats(actor);
// Apply aggression value to the base Fight rating, so that the actor can continue fighting // Apply aggression value to the base Fight rating, so that the actor can continue fighting
// after a Calm spell wears off // after a Calm spell wears off
observerStats.setAiSetting(AiSetting::Fight, observerFightRating + static_cast<int>(fightTerm)); observerStats.setAiSetting(AiSetting::Fight, observerFightRating + static_cast<int>(fightTerm));
observerStats.setBaseDisposition(observerStats.getBaseDisposition() + static_cast<int>(dispTerm)); setCrimeId = true;
// Set the crime ID, which we will use to calm down participants
// once the bounty has been paid.
observerStats.setCrimeId(id);
// Mark as Alarmed for dialogue // Mark as Alarmed for dialogue
observerStats.setAlarmed(true); observerStats.setAlarmed(true);
} }
} }
// Set the crime ID, which we will use to calm down participants
// once the bounty has been paid and restore their disposition to player character.
if (setCrimeId)
observerStats.setCrimeId(id);
} }
if (reported) if (reported)

@ -20,6 +20,7 @@
MWMechanics::NpcStats::NpcStats() MWMechanics::NpcStats::NpcStats()
: mDisposition(0) : mDisposition(0)
, mCrimeDispositionModifier(0)
, mReputation(0) , mReputation(0)
, mCrimeId(-1) , mCrimeId(-1)
, mBounty(0) , mBounty(0)
@ -43,6 +44,21 @@ void MWMechanics::NpcStats::setBaseDisposition(int disposition)
mDisposition = disposition; mDisposition = disposition;
} }
int MWMechanics::NpcStats::getCrimeDispositionModifier() const
{
return mCrimeDispositionModifier;
}
void MWMechanics::NpcStats::setCrimeDispositionModifier(int value)
{
mCrimeDispositionModifier = value;
}
void MWMechanics::NpcStats::modCrimeDispositionModifier(int value)
{
mCrimeDispositionModifier += value;
}
const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill(ESM::RefId id) const const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill(ESM::RefId id) const
{ {
auto it = mSkills.find(id); auto it = mSkills.find(id);
@ -464,6 +480,7 @@ void MWMechanics::NpcStats::writeState(ESM::NpcStats& state) const
state.mFactions[iter->first].mRank = iter->second; state.mFactions[iter->first].mRank = iter->second;
state.mDisposition = mDisposition; state.mDisposition = mDisposition;
state.mCrimeDispositionModifier = mCrimeDispositionModifier;
for (const auto& [id, value] : mSkills) for (const auto& [id, value] : mSkills)
{ {
@ -528,6 +545,7 @@ void MWMechanics::NpcStats::readState(const ESM::NpcStats& state)
} }
mDisposition = state.mDisposition; mDisposition = state.mDisposition;
mCrimeDispositionModifier = state.mCrimeDispositionModifier;
for (size_t i = 0; i < state.mSkills.size(); ++i) for (size_t i = 0; i < state.mSkills.size(); ++i)
{ {

@ -22,6 +22,7 @@ namespace MWMechanics
class NpcStats : public CreatureStats class NpcStats : public CreatureStats
{ {
int mDisposition; int mDisposition;
int mCrimeDispositionModifier;
std::map<ESM::RefId, SkillValue> mSkills; // SkillValue.mProgress used by the player only std::map<ESM::RefId, SkillValue> mSkills; // SkillValue.mProgress used by the player only
int mReputation; int mReputation;
@ -54,6 +55,10 @@ namespace MWMechanics
int getBaseDisposition() const; int getBaseDisposition() const;
void setBaseDisposition(int disposition); void setBaseDisposition(int disposition);
int getCrimeDispositionModifier() const;
void setCrimeDispositionModifier(int value);
void modCrimeDispositionModifier(int value);
int getReputation() const; int getReputation() const;
void setReputation(int reputation); void setReputation(int reputation);

@ -1346,8 +1346,10 @@ namespace MWScript
{ {
MWWorld::Ptr player = MWMechanics::getPlayer(); MWWorld::Ptr player = MWMechanics::getPlayer();
player.getClass().getNpcStats(player).setBounty(0); player.getClass().getNpcStats(player).setBounty(0);
MWBase::Environment::get().getWorld()->confiscateStolenItems(player); MWBase::World* world = MWBase::Environment::get().getWorld();
MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); world->confiscateStolenItems(player);
world->getPlayer().recordCrimeId();
world->getPlayer().setDrawState(MWMechanics::DrawState::Nothing);
} }
}; };

@ -24,7 +24,7 @@ namespace ESM
inline constexpr FormatVersion MaxNameIsRefIdOnlyFormatVersion = 25; inline constexpr FormatVersion MaxNameIsRefIdOnlyFormatVersion = 25;
inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26; inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26;
inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27;
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 30;
inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 4; inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 4;
inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21;

@ -37,6 +37,9 @@ namespace ESM
mDisposition = 0; mDisposition = 0;
esm.getHNOT(mDisposition, "DISP"); esm.getHNOT(mDisposition, "DISP");
mCrimeDispositionModifier = 0;
esm.getHNOT(mCrimeDispositionModifier, "DISM");
const bool intFallback = esm.getFormatVersion() <= MaxIntFallbackFormatVersion; const bool intFallback = esm.getFormatVersion() <= MaxIntFallbackFormatVersion;
for (auto& skill : mSkills) for (auto& skill : mSkills)
skill.load(esm, intFallback); skill.load(esm, intFallback);
@ -94,6 +97,9 @@ namespace ESM
if (mDisposition) if (mDisposition)
esm.writeHNT("DISP", mDisposition); esm.writeHNT("DISP", mDisposition);
if (mCrimeDispositionModifier)
esm.writeHNT("DISM", mCrimeDispositionModifier);
for (const auto& skill : mSkills) for (const auto& skill : mSkills)
skill.save(esm); skill.save(esm);
@ -141,6 +147,7 @@ namespace ESM
{ {
mIsWerewolf = false; mIsWerewolf = false;
mDisposition = 0; mDisposition = 0;
mCrimeDispositionModifier = 0;
mBounty = 0; mBounty = 0;
mReputation = 0; mReputation = 0;
mWerewolfKills = 0; mWerewolfKills = 0;

@ -33,6 +33,7 @@ namespace ESM
std::map<ESM::RefId, Faction> mFactions; std::map<ESM::RefId, Faction> mFactions;
int32_t mDisposition; int32_t mDisposition;
int32_t mCrimeDispositionModifier;
std::array<StatState<float>, ESM::Skill::Length> mSkills; std::array<StatState<float>, ESM::Skill::Length> mSkills;
int32_t mBounty; int32_t mBounty;
int32_t mReputation; int32_t mReputation;

Loading…
Cancel
Save