mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-16 03:59:56 +00:00
Implement crime disposition modifier (bug 4683)
This commit is contained in:
parent
b39a6ae9cd
commit
ffffb427f5
10 changed files with 149 additions and 29 deletions
|
@ -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…
Reference in a new issue