From 2477456f993acdc1f26215da51603d36b7acdfcd Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 17 Jun 2014 03:54:41 +0200 Subject: [PATCH] Implement Murder crimes and OnMurder instruction (Fixes #1315) --- apps/openmw/mwclass/npc.cpp | 13 ++++++ apps/openmw/mwmechanics/activespells.hpp | 8 ++-- apps/openmw/mwmechanics/actors.cpp | 40 +++++++++++++++++++ apps/openmw/mwmechanics/creaturestats.cpp | 19 ++++++++- apps/openmw/mwmechanics/creaturestats.hpp | 7 ++++ .../mwmechanics/mechanicsmanagerimp.cpp | 15 +++++-- apps/openmw/mwscript/docs/vmformat.txt | 4 +- apps/openmw/mwscript/statsextensions.cpp | 21 ++++++++++ components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 2 + components/esm/creaturestats.cpp | 6 +++ components/esm/creaturestats.hpp | 1 + 12 files changed, 128 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 2b4c54e245..1a33747f21 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -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 diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 6c4c93128f..9c1a5e613b 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -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(); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index dcbb6b8bde..0c5a2abec0 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -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::const_iterator effectIt = spell.mEffects.begin(); + effectIt != spell.mEffects.end(); ++effectIt) + { + int effectId = effectIt->mEffectId; + bool isDamageEffect = false; + for (unsigned int i=0; igetPlayerPtr(); + 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 diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index e26e7a8ba2..af35a109a6 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -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; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index d02af8fb6f..b365a0b89c 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -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; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index cb59d188f7..612a4ea84a 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -868,10 +868,16 @@ namespace MWMechanics // Find actors who directly witnessed the crime for (std::vector::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& store = MWBase::Environment::get().getWorld()->getStore().get(); + 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::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; diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index ab69bc4b41..c125985c05 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -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 diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 8b61237a1d..a3bc223aa6 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -1072,6 +1072,25 @@ namespace MWScript } }; + template + 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 OpOnKnockout : public Interpreter::Opcode0 { @@ -1264,6 +1283,8 @@ namespace MWScript interpreter.installSegment5 (Compiler::Stats::opcodeOnDeath, new OpOnDeath); interpreter.installSegment5 (Compiler::Stats::opcodeOnDeathExplicit, new OpOnDeath); + interpreter.installSegment5 (Compiler::Stats::opcodeOnMurder, new OpOnMurder); + interpreter.installSegment5 (Compiler::Stats::opcodeOnMurderExplicit, new OpOnMurder); interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockout, new OpOnKnockout); interpreter.installSegment5 (Compiler::Stats::opcodeOnKnockoutExplicit, new OpOnKnockout); diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 5733a41702..2d140044c5 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -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); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 8716cdd182..ed6f1df923 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -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; diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 84902d8c29..0a9a361093 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -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); diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 628bfee0a9..7ae57da16c 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -38,6 +38,7 @@ namespace ESM bool mDead; bool mDied; + bool mMurdered; int mFriendlyHits; bool mTalkedTo; bool mAlarmed;