Commit 2477456f authored by scrawl's avatar scrawl

Implement Murder crimes and OnMurder instruction (Fixes #1315)

parent 3801dfb4
......@@ -628,6 +628,8 @@ namespace MWClass
!MWBase::Environment::get().getMechanicsManager()->isAggressive(ptr, attacker))
MWBase::Environment::get().getMechanicsManager()->commitCrime(attacker, ptr, MWBase::MechanicsManager::OT_Assault);
bool wasDead = getCreatureStats(ptr).isDead();
getCreatureStats(ptr).setAttacked(true);
if(!successful)
......@@ -751,6 +753,17 @@ namespace MWClass
fatigue.setCurrent(fatigue.getCurrent() - damage, true);
getCreatureStats(ptr).setFatigue(fatigue);
}
if (!wasDead && getCreatureStats(ptr).isDead())
{
// NPC was killed
// Simple check for who attacked first: if the player attacked first, a crimeId should be set
// Doesn't handle possible edge case where no one reported the assault, but in such a case,
// for bystanders it is not possible to tell who attacked first, anyway.
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (ptr.getClass().getNpcStats(ptr).getCrimeId() != -1 && ptr != player)
MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder);
}
}
void Npc::block(const MWWorld::Ptr &ptr) const
......
......@@ -40,6 +40,10 @@ namespace MWMechanics
void readState (const ESM::ActiveSpells& state);
void writeState (ESM::ActiveSpells& state) const;
TIterator begin() const;
TIterator end() const;
private:
mutable TContainer mSpells;
......@@ -57,10 +61,6 @@ namespace MWMechanics
const TContainer& getActiveSpells() const;
TIterator begin() const;
TIterator end() const;
public:
ActiveSpells();
......
......@@ -342,6 +342,8 @@ namespace MWMechanics
CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr);
const MagicEffects &effects = creatureStats.getMagicEffects();
bool wasDead = creatureStats.isDead();
// attributes
for(int i = 0;i < ESM::Attribute::Length;++i)
{
......@@ -454,6 +456,44 @@ namespace MWMechanics
}
creatureStats.setHealth(health);
if (!wasDead && creatureStats.isDead())
{
// The actor was killed by a magic effect. Figure out if the player was responsible for it.
const ActiveSpells& spells = creatureStats.getActiveSpells();
for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it)
{
const ActiveSpells::ActiveSpellParams& spell = it->second;
for (std::vector<ActiveSpells::ActiveEffect>::const_iterator effectIt = spell.mEffects.begin();
effectIt != spell.mEffects.end(); ++effectIt)
{
int effectId = effectIt->mEffectId;
bool isDamageEffect = false;
for (unsigned int i=0; i<sizeof(damageEffects)/sizeof(int); ++i)
{
if (damageEffects[i] == effectId)
isDamageEffect = true;
}
if (effectId == ESM::MagicEffect::DamageHealth || effectId == ESM::MagicEffect::AbsorbHealth)
isDamageEffect = true;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId);
if (isDamageEffect && caster == player)
{
// Simple check for who attacked first: if the player attacked first, a crimeId should be set
// Doesn't handle possible edge case where no one reported the assault, but in such a case,
// for bystanders it is not possible to tell who attacked first, anyway.
if (ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).getCrimeId() != -1
&& ptr != player)
MWBase::Environment::get().getMechanicsManager()->commitCrime(player, ptr, MWBase::MechanicsManager::OT_Murder);
break;
}
}
}
}
// TODO: dirty flag for magic effects to avoid some unnecessary work below?
// Update bound effects
......
......@@ -14,7 +14,7 @@ namespace MWMechanics
int CreatureStats::sActorId = 0;
CreatureStats::CreatureStats()
: mLevel (0), mDead (false), mDied (false), mFriendlyHits (0),
: mLevel (0), mDead (false), mDied (false), mMurdered(false), mFriendlyHits (0),
mTalkedTo (false), mAlarmed (false),
mAttacked (false), mHostile (false),
mAttackingOrSpell(false),
......@@ -245,6 +245,21 @@ namespace MWMechanics
mDied = false;
}
bool CreatureStats::hasBeenMurdered() const
{
return mMurdered;
}
void CreatureStats::notifyMurder()
{
mMurdered = true;
}
void CreatureStats::clearHasBeenMurdered()
{
mMurdered = false;
}
void CreatureStats::resurrect()
{
if (mDead)
......@@ -479,6 +494,7 @@ namespace MWMechanics
state.mDead = mDead;
state.mDied = mDied;
state.mMurdered = mMurdered;
state.mFriendlyHits = mFriendlyHits;
state.mTalkedTo = mTalkedTo;
state.mAlarmed = mAlarmed;
......@@ -527,6 +543,7 @@ namespace MWMechanics
mDead = state.mDead;
mDied = state.mDied;
mMurdered = state.mMurdered;
mFriendlyHits = state.mFriendlyHits;
mTalkedTo = state.mTalkedTo;
mAlarmed = state.mAlarmed;
......
......@@ -35,6 +35,7 @@ namespace MWMechanics
AiSequence mAiSequence;
bool mDead;
bool mDied;
bool mMurdered;
int mFriendlyHits;
bool mTalkedTo;
bool mAlarmed;
......@@ -170,6 +171,12 @@ namespace MWMechanics
void clearHasDied();
bool hasBeenMurdered() const;
void clearHasBeenMurdered();
void notifyMurder();
void resurrect();
bool hasCommonDisease() const;
......
......@@ -868,10 +868,16 @@ namespace MWMechanics
// Find actors who directly witnessed the crime
for (std::vector<MWWorld::Ptr>::iterator it = neighbors.begin(); it != neighbors.end(); ++it)
{
if (*it == player) continue; // not the player
if (*it == player)
continue; // skip player
if (it->getClass().getCreatureStats(*it).isDead())
continue;
// Was the crime seen?
if (MWBase::Environment::get().getWorld()->getLOS(player, *it) && awarenessCheck(player, *it) )
if ((MWBase::Environment::get().getWorld()->getLOS(player, *it) && awarenessCheck(player, *it) )
// Murder crime can be reported even if no one saw it (hearing is enough, I guess).
// TODO: Add mod support for stealth executions!
|| (type == OT_Murder && *it != victim))
{
if (*it == victim)
victimAware = true;
......@@ -904,6 +910,9 @@ namespace MWMechanics
{
const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
if (type == OT_Murder && !victim.isEmpty())
victim.getClass().getCreatureStats(victim).notifyMurder();
// Bounty for each type of crime
if (type == OT_Trespassing || type == OT_SleepingInOwnedBed)
arg = store.find("iCrimeTresspass")->getInt();
......@@ -971,7 +980,7 @@ namespace MWMechanics
for (std::vector<MWWorld::Ptr>::iterator it = neighbors.begin(); it != neighbors.end(); ++it)
{
if ( *it == player
|| !it->getClass().isNpc()) continue;
|| !it->getClass().isNpc() || it->getClass().getCreatureStats(*it).isDead()) continue;
int aggression = fight;
......
......@@ -398,5 +398,7 @@ op 0x2000245: ClearInfoActor
op 0x2000246: ClearInfoActor, explicit
op 0x2000247: BetaComment
op 0x2000248: BetaComment, explicit
op 0x2000249: OnMurder
op 0x200024a: OnMurder, explicit
opcodes 0x2000249-0x3ffffff unused
opcodes 0x200024b-0x3ffffff unused
......@@ -1072,6 +1072,25 @@ namespace MWScript
}
};
template <class R>
class OpOnMurder : public Interpreter::Opcode0
{
public:
virtual void execute (Interpreter::Runtime& runtime)
{
MWWorld::Ptr ptr = R()(runtime);
Interpreter::Type_Integer value =
ptr.getClass().getCreatureStats (ptr).hasBeenMurdered();
if (value)
ptr.getClass().getCreatureStats (ptr).clearHasBeenMurdered();
runtime.push (value);
}
};
template <class R>
class OpOnKnockout : public Interpreter::Opcode0
{
......@@ -1264,6 +1283,8 @@ namespace MWScript
interpreter.installSegment5 (Compiler::Stats::opcodeOnDeath, new OpOnDeath<ImplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeOnDeathExplicit, new OpOnDeath<ExplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeOnMurder, new OpOnMurder<ImplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeOnMurderExplicit, new OpOnMurder<ExplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockout, new OpOnKnockout<ImplicitRef>);
interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockoutExplicit, new OpOnKnockout<ExplicitRef>);
......
......@@ -449,6 +449,7 @@ namespace Compiler
extensions.registerInstruction ("lowerrank", "", opcodeLowerRank, opcodeLowerRankExplicit);
extensions.registerFunction ("ondeath", 'l', "", opcodeOnDeath, opcodeOnDeathExplicit);
extensions.registerFunction ("onmurder", 'l', "", opcodeOnMurder, opcodeOnMurderExplicit);
extensions.registerFunction ("onknockout", 'l', "", opcodeOnKnockout, opcodeOnKnockoutExplicit);
extensions.registerFunction ("iswerewolf", 'l', "", opcodeIsWerewolf, opcodeIsWerewolfExplicit);
......
......@@ -385,6 +385,8 @@ namespace Compiler
const int opcodeLowerRankExplicit = 0x20001eb;
const int opcodeOnDeath = 0x20001fc;
const int opcodeOnDeathExplicit = 0x2000205;
const int opcodeOnMurder = 0x2000249;
const int opcodeOnMurderExplicit = 0x200024a;
const int opcodeOnKnockout = 0x2000240;
const int opcodeOnKnockoutExplicit = 0x2000241;
......
......@@ -21,6 +21,9 @@ void ESM::CreatureStats::load (ESMReader &esm)
mDied = false;
esm.getHNOT (mDied, "DIED");
mMurdered = false;
esm.getHNOT (mMurdered, "MURD");
mFriendlyHits = 0;
esm.getHNOT (mFriendlyHits, "FRHT");
......@@ -127,6 +130,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
if (mDied)
esm.writeHNT ("DIED", mDied);
if (mMurdered)
esm.writeHNT ("MURD", mMurdered);
if (mFriendlyHits)
esm.writeHNT ("FRHT", mFriendlyHits);
......
......@@ -38,6 +38,7 @@ namespace ESM
bool mDead;
bool mDied;
bool mMurdered;
int mFriendlyHits;
bool mTalkedTo;
bool mAlarmed;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment