mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-21 09:39:56 +00:00
Merge branch 'crime_disposition' into 'master'
Implement crime disposition modifier Closes #4683 See merge request OpenMW/openmw!3448
This commit is contained in:
commit
c6e5a28241
@ -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…
x
Reference in New Issue
Block a user