forked from teamnwah/openmw-tes3coop
Feature 1154 & 73: NPCs react to crime
This commit is contained in:
parent
8ce938c6f1
commit
4037f3705e
9 changed files with 140 additions and 62 deletions
|
@ -1297,6 +1297,11 @@ namespace MWClass
|
||||||
return ref->mBase->mNpdt12.mGold;
|
return ref->mBase->mNpdt12.mGold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Npc::isClass(const MWWorld::Ptr& ptr, const std::string &className) const
|
||||||
|
{
|
||||||
|
return ptr.get<ESM::NPC>()->mBase->mClass == className;
|
||||||
|
}
|
||||||
|
|
||||||
const ESM::GameSetting *Npc::fMinWalkSpeed;
|
const ESM::GameSetting *Npc::fMinWalkSpeed;
|
||||||
const ESM::GameSetting *Npc::fMaxWalkSpeed;
|
const ESM::GameSetting *Npc::fMaxWalkSpeed;
|
||||||
const ESM::GameSetting *Npc::fEncumberedMoveEffect;
|
const ESM::GameSetting *Npc::fEncumberedMoveEffect;
|
||||||
|
|
|
@ -168,6 +168,8 @@ namespace MWClass
|
||||||
///< Write additional state from \a ptr into \a state.
|
///< Write additional state from \a ptr into \a state.
|
||||||
|
|
||||||
virtual int getBaseGold(const MWWorld::Ptr& ptr) const;
|
virtual int getBaseGold(const MWWorld::Ptr& ptr) const;
|
||||||
|
|
||||||
|
virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,9 @@
|
||||||
#include "steering.hpp"
|
#include "steering.hpp"
|
||||||
#include "movement.hpp"
|
#include "movement.hpp"
|
||||||
|
|
||||||
MWMechanics::AiActivate::AiActivate(const std::string &objectId)
|
MWMechanics::AiActivate::AiActivate(const std::string &objectId, int arg)
|
||||||
: mObjectId(objectId)
|
: mObjectId(objectId),
|
||||||
|
mArg(arg)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const
|
MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const
|
||||||
|
@ -25,6 +26,10 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration)
|
||||||
Movement &movement = actor.getClass().getMovementSettings(actor);
|
Movement &movement = actor.getClass().getMovementSettings(actor);
|
||||||
const ESM::Cell *cell = actor.getCell()->getCell();
|
const ESM::Cell *cell = actor.getCell()->getCell();
|
||||||
|
|
||||||
|
// Make guard chase player
|
||||||
|
//if (mArg == 1)
|
||||||
|
// actor.getClass().getNpcStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||||
|
|
||||||
MWWorld::Ptr player = world->getPlayerPtr();
|
MWWorld::Ptr player = world->getPlayerPtr();
|
||||||
if(cell->mData.mX != player.getCell()->getCell()->mData.mX)
|
if(cell->mData.mX != player.getCell()->getCell()->mData.mX)
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace MWMechanics
|
||||||
class AiActivate : public AiPackage
|
class AiActivate : public AiPackage
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AiActivate(const std::string &objectId);
|
AiActivate(const std::string &objectId, int arg);
|
||||||
virtual AiActivate *clone() const;
|
virtual AiActivate *clone() const;
|
||||||
virtual bool execute (const MWWorld::Ptr& actor,float duration);
|
virtual bool execute (const MWWorld::Ptr& actor,float duration);
|
||||||
///< \return Package completed?
|
///< \return Package completed?
|
||||||
|
@ -20,6 +20,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string mObjectId;
|
std::string mObjectId;
|
||||||
|
int mArg;
|
||||||
|
|
||||||
PathFinder mPathFinder;
|
PathFinder mPathFinder;
|
||||||
int mCellX;
|
int mCellX;
|
||||||
|
|
|
@ -161,7 +161,7 @@ void MWMechanics::AiSequence::fill(const ESM::AIPackageList &list)
|
||||||
else if (it->mType == ESM::AI_Activate)
|
else if (it->mType == ESM::AI_Activate)
|
||||||
{
|
{
|
||||||
ESM::AIActivate data = it->mActivate;
|
ESM::AIActivate data = it->mActivate;
|
||||||
package = new MWMechanics::AiActivate(data.mName.toString());
|
package = new MWMechanics::AiActivate(data.mName.toString(), 0);
|
||||||
}
|
}
|
||||||
else //if (it->mType == ESM::AI_Follow)
|
else //if (it->mType == ESM::AI_Follow)
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/player.hpp"
|
#include "../mwworld/player.hpp"
|
||||||
|
|
||||||
|
#include "aicombat.hpp"
|
||||||
|
#include "aiactivate.hpp"
|
||||||
|
|
||||||
#include <OgreSceneNode.h>
|
#include <OgreSceneNode.h>
|
||||||
|
|
||||||
#include "spellcasting.hpp"
|
#include "spellcasting.hpp"
|
||||||
|
@ -801,42 +804,121 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool MechanicsManager::commitCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg)
|
bool MechanicsManager::commitCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg)
|
||||||
{
|
{
|
||||||
if (ptr.getRefData().getHandle() != "player")
|
// NOTE: int arg can be from itemTaken() so DON'T modify it, since it is
|
||||||
|
// passed to reportCrime later on in this function.
|
||||||
|
|
||||||
|
// Only player can commit crime and no victimless crimes
|
||||||
|
if (ptr.getRefData().getHandle() != "player" || victim.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool reported=false;
|
const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
|
||||||
for (Actors::PtrControllerMap::const_iterator it = mActors.begin(); it != mActors.end(); ++it)
|
|
||||||
{
|
|
||||||
if (it->first != ptr &&
|
|
||||||
MWBase::Environment::get().getWorld()->getLOS(ptr, it->first) &&
|
|
||||||
awarenessCheck(ptr, it->first))
|
|
||||||
{
|
|
||||||
// NPCs will always curse you when they notice you steal their items, even if they don't report the crime
|
|
||||||
if (it->first == victim && type == OT_Theft)
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getDialogueManager()->say(victim, "Thief");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actor has witnessed a crime. Will he report it?
|
// What amount of alarm did this crime generate?
|
||||||
// (not sure, is > 0 correct?)
|
int alarm;
|
||||||
if (it->first.getClass().getCreatureStats(it->first).getAiSetting(CreatureStats::AI_Alarm).getModified() > 0)
|
if (type == OT_Trespassing || type == OT_SleepingInOwnedBed)
|
||||||
{
|
alarm = esmStore.get<ESM::GameSetting>().find("iAlarmTresspass")->getInt();
|
||||||
// TODO: stats.setAlarmed(true) on NPCs within earshot
|
else if (type == OT_Pickpocket)
|
||||||
// fAlarmRadius ?
|
alarm = esmStore.get<ESM::GameSetting>().find("iAlarmPickPocket")->getInt();
|
||||||
reported=true;
|
else if (type == OT_Assault)
|
||||||
break;
|
alarm = esmStore.get<ESM::GameSetting>().find("iAlarmAttack")->getInt();
|
||||||
}
|
else if (type == OT_Murder)
|
||||||
}
|
alarm = esmStore.get<ESM::GameSetting>().find("iAlarmKilling")->getInt();
|
||||||
}
|
else if (type == OT_Theft)
|
||||||
|
alarm = esmStore.get<ESM::GameSetting>().find("iAlarmStealing")->getInt();
|
||||||
|
|
||||||
if (reported)
|
// Innocent until proven guilty
|
||||||
|
bool reported = false;
|
||||||
|
|
||||||
|
// Find all the NPC's close enough, ie. within fAlarmRadius of the player
|
||||||
|
std::vector<MWWorld::Ptr> neighbors;
|
||||||
|
mActors.getObjectsInRange(Ogre::Vector3(ptr.getRefData().getPosition().pos),
|
||||||
|
esmStore.get<ESM::GameSetting>().find("fAlarmRadius")->getInt(), neighbors);
|
||||||
|
|
||||||
|
// Did anyone see the crime?
|
||||||
|
for (std::vector<MWWorld::Ptr>::iterator it = neighbors.begin(); it != neighbors.end(); ++it)
|
||||||
|
{
|
||||||
|
if (*it == ptr) // Not the player
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CreatureStats& creatureStats = MWWorld::Class::get(*it).getCreatureStats(*it);
|
||||||
|
|
||||||
|
// Did the witness see the crime?
|
||||||
|
if ( MWBase::Environment::get().getWorld()->getLOS(ptr, *it) && awarenessCheck(ptr, *it) )
|
||||||
|
{
|
||||||
|
// Say something!
|
||||||
|
// TODO: Add more messages
|
||||||
|
if (type == OT_Theft)
|
||||||
|
MWBase::Environment::get().getDialogueManager()->say(*it, "Thief");
|
||||||
|
|
||||||
|
// Will the witness report the crime?
|
||||||
|
if (creatureStats.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm)
|
||||||
|
{
|
||||||
|
creatureStats.setAlarmed(true);
|
||||||
|
reported = true;
|
||||||
reportCrime(ptr, victim, type, arg);
|
reportCrime(ptr, victim, type, arg);
|
||||||
|
|
||||||
|
// Is it a guard? Or will the witness fight?
|
||||||
|
if (it->getClass().isClass(*it, "Guard"))
|
||||||
|
{
|
||||||
|
// TODO: Persue player, concider bounty?
|
||||||
|
creatureStats.getAiSequence().stack(AiActivate(ptr.getClass().getId(ptr), 1));
|
||||||
|
creatureStats.getAiSequence().execute(*it,0);
|
||||||
|
}
|
||||||
|
else if (creatureStats.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm)
|
||||||
|
{
|
||||||
|
creatureStats.getAiSequence().stack(AiCombat(ptr));
|
||||||
|
creatureStats.setHostile(true);
|
||||||
|
creatureStats.getAiSequence().execute(*it,0);
|
||||||
|
}
|
||||||
|
else if (type == OT_Assault)
|
||||||
|
{
|
||||||
|
creatureStats.getAiSequence().stack(AiCombat(ptr));
|
||||||
|
creatureStats.setHostile(true);
|
||||||
|
creatureStats.getAiSequence().execute(*it,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell everyone else
|
||||||
|
for (std::vector<MWWorld::Ptr>::iterator it1 = neighbors.begin(); it1 != neighbors.end(); ++it1)
|
||||||
|
{
|
||||||
|
if (it == it1 || // Don't tell the witness or the player
|
||||||
|
ptr == *it1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Is it a guard? Or will the witness fight?
|
||||||
|
CreatureStats& creatureStats1 = MWWorld::Class::get(*it1).getCreatureStats(*it1);
|
||||||
|
if (it1->getClass().isClass(*it1, "Guard"))
|
||||||
|
{
|
||||||
|
// TODO: Persue player, concider bounty?
|
||||||
|
creatureStats1.getAiSequence().stack(AiActivate(ptr.getClass().getId(ptr), 1));
|
||||||
|
creatureStats1.getAiSequence().execute(*it1,0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (creatureStats1.getAiSetting(CreatureStats::AI_Alarm).getBase() >= alarm)
|
||||||
|
{
|
||||||
|
creatureStats1.getAiSequence().stack(AiCombat(ptr));
|
||||||
|
creatureStats1.setHostile(true);
|
||||||
|
creatureStats1.getAiSequence().execute(*it1,0);
|
||||||
|
}
|
||||||
|
else if (type == OT_Assault)
|
||||||
|
{
|
||||||
|
creatureStats.getAiSequence().stack(AiCombat(ptr));
|
||||||
|
creatureStats.setHostile(true);
|
||||||
|
creatureStats.getAiSequence().execute(*it,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break; // Someone saw the crime and everyone has been told
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return reported;
|
return reported;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg)
|
void MechanicsManager::reportCrime(const MWWorld::Ptr &ptr, const MWWorld::Ptr &victim, OffenseType type, int arg)
|
||||||
{
|
{
|
||||||
const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||||
|
|
||||||
// Bounty for each type of crime
|
// Bounty for each type of crime
|
||||||
if (type == OT_Trespassing || type == OT_SleepingInOwnedBed)
|
if (type == OT_Trespassing || type == OT_SleepingInOwnedBed)
|
||||||
arg = store.find("iCrimeTresspass")->getInt();
|
arg = store.find("iCrimeTresspass")->getInt();
|
||||||
|
@ -849,32 +931,10 @@ namespace MWMechanics
|
||||||
else if (type == OT_Theft)
|
else if (type == OT_Theft)
|
||||||
arg *= store.find("fCrimeStealing")->getFloat();
|
arg *= store.find("fCrimeStealing")->getFloat();
|
||||||
|
|
||||||
// TODO: In some cases (type == Assault), if no NPCs are within earshot, the report will have no effect.
|
|
||||||
// however other crime types seem to be always produce a bounty.
|
|
||||||
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}");
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}");
|
||||||
ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty()
|
ptr.getClass().getNpcStats(ptr).setBounty(ptr.getClass().getNpcStats(ptr).getBounty()
|
||||||
+ arg);
|
+ arg);
|
||||||
|
|
||||||
if (!victim.isEmpty())
|
|
||||||
{
|
|
||||||
int fight = 0;
|
|
||||||
// Increase in fight rating for each type of crime
|
|
||||||
if (type == OT_Trespassing || type == OT_SleepingInOwnedBed)
|
|
||||||
fight = store.find("iFightTrespass")->getFloat();
|
|
||||||
else if (type == OT_Pickpocket)
|
|
||||||
fight = store.find("iFightPickpocket")->getInt();
|
|
||||||
else if (type == OT_Assault)
|
|
||||||
fight = store.find("iFightAttack")->getInt();
|
|
||||||
else if (type == OT_Murder)
|
|
||||||
fight = store.find("iFightKilling")->getInt();
|
|
||||||
else if (type == OT_Theft)
|
|
||||||
fight = store.find("fFightStealing")->getFloat();
|
|
||||||
// Not sure if this should be permanent?
|
|
||||||
fight = victim.getClass().getCreatureStats(victim).getAiSetting(CreatureStats::AI_Fight).getBase() + fight;
|
|
||||||
victim.getClass().getCreatureStats(victim).setAiSetting(CreatureStats::AI_Fight, fight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If committing a crime against a faction member, expell from the faction
|
// If committing a crime against a faction member, expell from the faction
|
||||||
if (!victim.isEmpty() && victim.getClass().isNpc())
|
if (!victim.isEmpty() && victim.getClass().isNpc())
|
||||||
{
|
{
|
||||||
|
@ -886,8 +946,6 @@ namespace MWMechanics
|
||||||
ptr.getClass().getNpcStats(ptr).expell(factionID);
|
ptr.getClass().getNpcStats(ptr).expell(factionID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make any guards in the area try to arrest the player
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer)
|
bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer)
|
||||||
|
|
|
@ -47,7 +47,7 @@ namespace MWScript
|
||||||
// discard additional arguments (reset), because we have no idea what they mean.
|
// discard additional arguments (reset), because we have no idea what they mean.
|
||||||
for (unsigned int i=0; i<arg0; ++i) runtime.pop();
|
for (unsigned int i=0; i<arg0; ++i) runtime.pop();
|
||||||
|
|
||||||
MWMechanics::AiActivate activatePackage(objectID);
|
MWMechanics::AiActivate activatePackage(objectID, 0);
|
||||||
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(activatePackage);
|
MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().stack(activatePackage);
|
||||||
std::cout << "AiActivate" << std::endl;
|
std::cout << "AiActivate" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,4 +401,9 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
throw std::runtime_error("class does not support base gold");
|
throw std::runtime_error("class does not support base gold");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Class::isClass(const MWWorld::Ptr& ptr, const std::string &className) const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,6 +334,8 @@ namespace MWWorld
|
||||||
static void registerClass (const std::string& key, boost::shared_ptr<Class> instance);
|
static void registerClass (const std::string& key, boost::shared_ptr<Class> instance);
|
||||||
|
|
||||||
virtual int getBaseGold(const MWWorld::Ptr& ptr) const;
|
virtual int getBaseGold(const MWWorld::Ptr& ptr) const;
|
||||||
|
|
||||||
|
virtual bool isClass(const MWWorld::Ptr& ptr, const std::string &className) const;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue