Implement crime disposition modifier (bug 4683)

macos_ci_fix
Andrei Kortunov 8 months ago
parent b39a6ae9cd
commit ffffb427f5

@ -11,6 +11,7 @@
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 #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 #4743: PlayGroup doesn't play non-looping animations correctly
Bug #4754: Stack of ammunition cannot be equipped partially

@ -23,9 +23,11 @@
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "bookpage.hpp"
#include "textcolours.hpp"
@ -736,6 +738,15 @@ namespace MWGui
bool dispositionVisible = false;
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;
mDispositionBar->setProgressRange(100);
mDispositionBar->setProgressPosition(

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

@ -473,8 +473,8 @@ namespace MWMechanics
int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr, bool clamp)
{
const MWMechanics::NpcStats& npcSkill = ptr.getClass().getNpcStats(ptr);
float x = static_cast<float>(npcSkill.getBaseDisposition());
const MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats(ptr);
float x = static_cast<float>(npcStats.getBaseDisposition() + npcStats.getCrimeDispositionModifier());
MWWorld::LiveCellRef<ESM::NPC>* npc = ptr.get<ESM::NPC>();
MWWorld::Ptr playerPtr = getPlayer();
@ -1287,62 +1287,134 @@ namespace MWMechanics
if (!canReportCrime(actor, victim, playerFollowers))
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
actor.getClass().getCreatureStats(actor).setAlarmed(true);
observerStats.setAlarmed(true);
// Set the crime ID, which we will use to calm down participants
// once the bounty has been paid.
actor.getClass().getNpcStats(actor).setCrimeId(id);
setCrimeId = true;
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
{
float dispTerm = (actor == victim) ? dispVictim : disp;
float alarmTerm
= 0.01f * actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Alarm).getBase();
if (type == OT_Pickpocket && alarmTerm <= 0)
// 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.
if (type == OT_Pickpocket && isActorVictim && alarmTerm == 0.0)
alarmTerm = 1.0;
else if (type == OT_Pickpocket && !isActorVictim)
alarmTerm = 0.0;
if (actor != victim)
dispTerm *= alarmTerm;
float fightTerm = static_cast<float>((actor == victim) ? fightVictim : fight);
float fightTerm = static_cast<float>(isActorVictim ? fightVictim : fight);
fightTerm += getFightDispositionBias(dispTerm);
fightTerm += getFightDistanceBias(actor, player);
fightTerm *= alarmTerm;
const int observerFightRating
= actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Fight).getBase();
const int observerFightRating = observerStats.getAiSetting(AiSetting::Fight).getBase();
if (observerFightRating + fightTerm > 100)
fightTerm = static_cast<float>(100 - observerFightRating);
fightTerm = std::max(0.f, fightTerm);
if (observerFightRating + fightTerm >= 100)
{
if (dispositionModifier != 0 && applyOnlyIfHostile)
{
dispositionModifier
= std::clamp(dispositionModifier, -currentDisposition, 100 - currentDisposition);
observerStats.modCrimeDispositionModifier(dispositionModifier);
}
startCombat(actor, player);
NpcStats& observerStats = actor.getClass().getNpcStats(actor);
// Apply aggression value to the base Fight rating, so that the actor can continue fighting
// after a Calm spell wears off
observerStats.setAiSetting(AiSetting::Fight, observerFightRating + static_cast<int>(fightTerm));
observerStats.setBaseDisposition(observerStats.getBaseDisposition() + static_cast<int>(dispTerm));
// Set the crime ID, which we will use to calm down participants
// once the bounty has been paid.
observerStats.setCrimeId(id);
setCrimeId = true;
// Mark as Alarmed for dialogue
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)

@ -20,6 +20,7 @@
MWMechanics::NpcStats::NpcStats()
: mDisposition(0)
, mCrimeDispositionModifier(0)
, mReputation(0)
, mCrimeId(-1)
, mBounty(0)
@ -43,6 +44,21 @@ void MWMechanics::NpcStats::setBaseDisposition(int 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
{
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.mDisposition = mDisposition;
state.mCrimeDispositionModifier = mCrimeDispositionModifier;
for (const auto& [id, value] : mSkills)
{
@ -528,6 +545,7 @@ void MWMechanics::NpcStats::readState(const ESM::NpcStats& state)
}
mDisposition = state.mDisposition;
mCrimeDispositionModifier = state.mCrimeDispositionModifier;
for (size_t i = 0; i < state.mSkills.size(); ++i)
{

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

@ -1346,8 +1346,10 @@ namespace MWScript
{
MWWorld::Ptr player = MWMechanics::getPlayer();
player.getClass().getNpcStats(player).setBounty(0);
MWBase::Environment::get().getWorld()->confiscateStolenItems(player);
MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId();
MWBase::World* world = MWBase::Environment::get().getWorld();
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 MaxUseEsmCellIdFormatVersion = 26;
inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27;
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29;
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 30;
inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 4;
inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21;

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

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

Loading…
Cancel
Save