Commit bea88c36 authored by scrawl's avatar scrawl

Stolen item tracking overhaul part 2 (Fixes #2338)

parent c1862cbf
......@@ -138,9 +138,6 @@ namespace MWBase
/// @return was it illegal, and someone saw you doing it?
virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) = 0;
/// @return is \a ptr allowed to take/use \a item or is it a crime?
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, MWWorld::Ptr& victim) = 0;
enum PersuasionType
{
PT_Admire,
......@@ -205,6 +202,15 @@ namespace MWBase
virtual void keepPlayerAlive() = 0;
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0;
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) = 0;
/// List the owners that the player has stolen this item from (the owner can be an NPC or a faction).
/// <Owner, item count>
virtual std::vector<std::pair<std::string, int> > getStolenItemOwners(const std::string& itemid) = 0;
/// Has the player stolen this item from the given owner?
virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid) = 0;
};
}
......
......@@ -59,8 +59,10 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Container> *ref =
ptr.get<ESM::Container>();
// setting ownership not needed, since taking items from a container inherits the
// container's owner automatically
data->mContainerStore.fill(
ref->mBase->mInventory, ptr.getCellRef().getOwner(), ptr.getCellRef().getFaction(), ptr.getCellRef().getFactionRank(), MWBase::Environment::get().getWorld()->getStore());
ref->mBase->mInventory, "");
// store
ptr.getRefData().setCustomData (data.release());
......@@ -82,7 +84,10 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
const ESM::InventoryList& list = ref->mBase->mInventory;
MWWorld::ContainerStore& store = getContainerStore(ptr);
store.restock(list, ptr, ptr.getCellRef().getOwner(), ptr.getCellRef().getFaction(), ptr.getCellRef().getFactionRank());
// setting ownership not needed, since taking items from a container inherits the
// container's owner automatically
store.restock(list, ptr, "");
}
void Container::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const
......
......@@ -139,8 +139,7 @@ namespace MWClass
// store
ptr.getRefData().setCustomData(data.release());
getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr), "", -1,
MWBase::Environment::get().getWorld()->getStore());
getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr));
if (ref->mBase->mFlags & ESM::Creature::Weapon)
getInventoryStore(ptr).autoEquip(ptr);
......@@ -886,7 +885,7 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
const ESM::InventoryList& list = ref->mBase->mInventory;
MWWorld::ContainerStore& store = getContainerStore(ptr);
store.restock(list, ptr, ptr.getCellRef().getRefId(), "", -1);
store.restock(list, ptr, ptr.getCellRef().getRefId());
}
int Creature::getBaseFightRating(const MWWorld::Ptr &ptr) const
......
......@@ -384,8 +384,8 @@ namespace MWClass
}
// inventory
data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr), "", -1,
MWBase::Environment::get().getWorld()->getStore());
// setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items
data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr));
data->mNpcStats.setGoldPool(gold);
......@@ -1328,7 +1328,7 @@ namespace MWClass
MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
const ESM::InventoryList& list = ref->mBase->mInventory;
MWWorld::ContainerStore& store = getContainerStore(ptr);
store.restock(list, ptr, ptr.getCellRef().getRefId(), "", -1);
store.restock(list, ptr, ptr.getCellRef().getRefId());
}
int Npc::getBaseFightRating (const MWWorld::Ptr& ptr) const
......
......@@ -339,7 +339,8 @@ namespace MWGui
for (int i=0; i<2; ++i)
{
MWWorld::Ptr item = (i == 0) ? mEnchanting.getOldItem() : mEnchanting.getGem();
if (Misc::StringUtils::ciEqual(item.getCellRef().getOwner(), mPtr.getCellRef().getRefId()))
if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(item.getCellRef().getRefId(),
mPtr.getCellRef().getRefId()))
{
std::string msg = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage49")->getString();
if (msg.find("%s") != std::string::npos)
......
......@@ -65,16 +65,7 @@ MWWorld::Ptr InventoryItemModel::moveItem(const ItemStack &item, size_t count, I
if (item.mFlags & ItemStack::Flag_Bound)
return MWWorld::Ptr();
bool setNewOwner = false;
// Are you dead? Then you wont need that anymore
if (mActor.getClass().isActor() && mActor.getClass().getCreatureStats(mActor).isDead()
// Make sure that the item is actually owned by the dead actor
// Prevents a potential exploit for resetting the owner of any item, by placing the item in a corpse
&& Misc::StringUtils::ciEqual(item.mBase.getCellRef().getOwner(), mActor.getCellRef().getRefId()))
setNewOwner = true;
MWWorld::Ptr ret = otherModel->copyItem(item, count, setNewOwner);
MWWorld::Ptr ret = otherModel->copyItem(item, count, false);
removeItem(item, count);
return ret;
}
......
......@@ -46,20 +46,27 @@ namespace MWGui
ItemModel();
virtual ~ItemModel() {}
typedef int ModelIndex;
typedef int ModelIndex; // -1 means invalid index
/// Throws for invalid index or out of range index
virtual ItemStack getItem (ModelIndex index) = 0;
///< throws for invalid index
/// The number of items in the model, this specifies the range of indices you can pass to
/// the getItem function (but this range is only valid until the next call to update())
virtual size_t getItemCount() = 0;
/// Returns an invalid index if the item was not found
virtual ModelIndex getIndex (ItemStack item) = 0;
/// Rebuild the item model, this will invalidate existing model indices
virtual void update() = 0;
/// Move items from this model to \a otherModel.
/// @note Derived implementations may return an empty Ptr if the move was unsuccessful.
virtual MWWorld::Ptr moveItem (const ItemStack& item, size_t count, ItemModel* otherModel);
/// @param setNewOwner Set the copied item's owner to the actor we are copying to, or keep the original owner?
/// @param setNewOwner If true, set the copied item's owner to the actor we are copying to,
/// otherwise reset owner to ""
virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) = 0;
virtual void removeItem (const ItemStack& item, size_t count) = 0;
......
......@@ -13,6 +13,7 @@
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
......@@ -586,6 +587,15 @@ namespace MWGui
ret += getMiscString(cellref.getFaction(), "Faction");
if (cellref.getFactionRank() > 0)
ret += getValueString(cellref.getFactionRank(), "Rank");
std::vector<std::pair<std::string, int> > itemOwners =
MWBase::Environment::get().getMechanicsManager()->getStolenItemOwners(cellref.getRefId());
for (std::vector<std::pair<std::string, int> >::const_iterator it = itemOwners.begin(); it != itemOwners.end(); ++it)
{
ret += std::string("\nStolen ") + MyGUI::utility::toString(it->second) + " from " + it->first;
}
ret += getMiscString(cellref.getGlobalVariable(), "Global");
return ret;
}
......
......@@ -135,11 +135,9 @@ namespace MWGui
if (i == sourceModel->getItemCount())
throw std::runtime_error("The borrowed item disappeared");
// reset owner while copying, but only for items bought by the player
bool setNewOwner = (mMerchant.isEmpty());
const ItemStack& item = sourceModel->getItem(i);
// copy the borrowed items to our model
copyItem(item, it->mCount, setNewOwner);
copyItem(item, it->mCount);
// then remove them from the source model
sourceModel->removeItem(item, it->mCount);
}
......
......@@ -300,7 +300,8 @@ namespace MWGui
// check if the player is attempting to sell back an item stolen from this actor
for (std::vector<ItemStack>::iterator it = merchantBought.begin(); it != merchantBought.end(); ++it)
{
if (Misc::StringUtils::ciEqual(it->mBase.getCellRef().getOwner(), mPtr.getCellRef().getRefId()))
if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(it->mBase.getCellRef().getRefId(),
mPtr.getCellRef().getRefId()))
{
std::string msg = gmst.find("sNotifyMessage49")->getString();
if (msg.find("%s") != std::string::npos)
......@@ -315,6 +316,8 @@ namespace MWGui
}
}
// TODO: move to mwmechanics
// Is the player buying?
bool buying = (mCurrentMerchantOffer < 0);
......
......@@ -2,6 +2,8 @@
#include "mechanicsmanagerimp.hpp"
#include "npcstats.hpp"
#include <components/esm/stolenitems.hpp>
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
......@@ -873,31 +875,31 @@ namespace MWMechanics
mAI = true;
}
bool MechanicsManager::isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, MWWorld::Ptr& victim)
bool MechanicsManager::isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim)
{
const std::string& owner = item.getCellRef().getOwner();
const std::string& owner = cellref.getOwner();
bool isOwned = !owner.empty() && owner != "player";
const std::string& faction = item.getCellRef().getFaction();
const std::string& faction = cellref.getFaction();
bool isFactionOwned = false;
if (!faction.empty() && ptr.getClass().isNpc())
{
const std::map<std::string, int>& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks();
std::map<std::string, int>::const_iterator found = factions.find(Misc::StringUtils::lowerCase(faction));
if (found == factions.end()
|| found->second < item.getCellRef().getFactionRank())
|| found->second < cellref.getFactionRank())
isFactionOwned = true;
}
const std::string& globalVariable = item.getCellRef().getGlobalVariable();
const std::string& globalVariable = cellref.getGlobalVariable();
if (!globalVariable.empty() && MWBase::Environment::get().getWorld()->getGlobalInt(Misc::StringUtils::lowerCase(globalVariable)) == 1)
{
isOwned = false;
isFactionOwned = false;
}
if (!item.getCellRef().getOwner().empty())
victim = MWBase::Environment::get().getWorld()->searchPtr(item.getCellRef().getOwner(), true);
if (!cellref.getOwner().empty())
victim = MWBase::Environment::get().getWorld()->searchPtr(cellref.getOwner(), true);
return (!isOwned && !isFactionOwned);
}
......@@ -916,7 +918,7 @@ namespace MWMechanics
}
MWWorld::Ptr victim;
if (isAllowedToUse(ptr, bed, victim))
if (isAllowedToUse(ptr, bed.getCellRef(), victim))
return false;
if(commitCrime(ptr, victim, OT_SleepingInOwnedBed))
......@@ -931,27 +933,82 @@ namespace MWMechanics
void MechanicsManager::objectOpened(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item)
{
MWWorld::Ptr victim;
if (isAllowedToUse(ptr, item, victim))
if (isAllowedToUse(ptr, item.getCellRef(), victim))
return;
commitCrime(ptr, victim, OT_Trespassing);
}
std::vector<std::pair<std::string, int> > MechanicsManager::getStolenItemOwners(const std::string& itemid)
{
std::vector<std::pair<std::string, int> > result;
StolenItemsMap::const_iterator it = mStolenItems.find(Misc::StringUtils::lowerCase(itemid));
if (it == mStolenItems.end())
return result;
else
{
const OwnerMap& owners = it->second;
for (OwnerMap::const_iterator ownerIt = owners.begin(); ownerIt != owners.end(); ++ownerIt)
result.push_back(std::make_pair(ownerIt->first.first, ownerIt->second));
return result;
}
}
bool MechanicsManager::isItemStolenFrom(const std::string &itemid, const std::string &ownerid)
{
StolenItemsMap::const_iterator it = mStolenItems.find(Misc::StringUtils::lowerCase(itemid));
if (it == mStolenItems.end())
return false;
const OwnerMap& owners = it->second;
OwnerMap::const_iterator ownerFound = owners.find(std::make_pair(Misc::StringUtils::lowerCase(ownerid), false));
return ownerFound != owners.end();
}
void MechanicsManager::confiscateStolenItems(const MWWorld::Ptr &player, const MWWorld::Ptr &targetContainer)
{
MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
{
StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getClass().getId(*it)));
if (stolenIt == mStolenItems.end())
continue;
OwnerMap& owners = stolenIt->second;
int itemCount = it->getRefData().getCount();
for (OwnerMap::iterator ownerIt = owners.begin(); ownerIt != owners.end();)
{
int toRemove = std::min(itemCount, ownerIt->second);
itemCount -= toRemove;
ownerIt->second -= toRemove;
if (ownerIt->second == 0)
owners.erase(ownerIt++);
else
++ownerIt;
}
int toMove = it->getRefData().getCount() - itemCount;
targetContainer.getClass().getContainerStore(targetContainer).add(*it, toMove, targetContainer);
store.remove(*it, toMove, player);
}
// TODO: unhardcode the locklevel
targetContainer.getClass().lock(targetContainer,50);
}
void MechanicsManager::itemTaken(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item, const MWWorld::Ptr& container,
int count)
{
if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr())
return;
MWWorld::Ptr victim;
const MWWorld::CellRef* ownerCellRef = &item.getCellRef();
if (!container.isEmpty())
{
// Inherit the owner of the container
if (isAllowedToUse(ptr, container, victim))
return;
ownerCellRef = &container.getCellRef();
}
else
{
if (isAllowedToUse(ptr, item, victim))
return;
if (!item.getCellRef().hasContentFile())
{
// this is a manually placed item, which means it was already stolen
......@@ -959,6 +1016,20 @@ namespace MWMechanics
}
}
if (isAllowedToUse(ptr, *ownerCellRef, victim))
return;
Owner owner;
owner.first = ownerCellRef->getOwner();
owner.second = false;
if (owner.first.empty())
{
owner.first = ownerCellRef->getFaction();
owner.second = true;
}
Misc::StringUtils::toLower(owner.first);
mStolenItems[Misc::StringUtils::lowerCase(item.getClass().getId(item))][owner] += count;
commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count);
}
......@@ -967,7 +1038,7 @@ namespace MWMechanics
// NOTE: victim may be empty
// Only player can commit crime
if (player.getRefData().getHandle() != "player")
if (player != MWBase::Environment::get().getWorld()->getPlayerPtr())
return false;
// Find all the actors within the alarm radius
......@@ -1369,22 +1440,37 @@ namespace MWMechanics
int MechanicsManager::countSavedGameRecords() const
{
return 1; // Death counter
return 1 // Death counter
+1; // Stolen items
}
void MechanicsManager::write(ESM::ESMWriter &writer, Loading::Listener &listener) const
{
mActors.write(writer, listener);
ESM::StolenItems items;
items.mStolenItems = mStolenItems;
writer.startRecord(ESM::REC_STLN);
items.write(writer);
writer.endRecord(ESM::REC_STLN);
}
void MechanicsManager::readRecord(ESM::ESMReader &reader, uint32_t type)
{
mActors.readRecord(reader, type);
if (type == ESM::REC_STLN)
{
ESM::StolenItems items;
items.load(reader);
mStolenItems = items.mStolenItems;
}
else
mActors.readRecord(reader, type);
}
void MechanicsManager::clear()
{
mActors.clear();
mStolenItems.clear();
}
bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target)
......
......@@ -35,6 +35,11 @@ namespace MWMechanics
Objects mObjects;
Actors mActors;
typedef std::pair<std::string, bool> Owner; // < Owner id, bool isFaction >
typedef std::map<Owner, int> OwnerMap; // < Owner, number of stolen items with this id from this owner >
typedef std::map<std::string, OwnerMap> StolenItemsMap;
StolenItemsMap mStolenItems;
public:
void buildPlayer();
......@@ -130,9 +135,6 @@ namespace MWMechanics
/// @return was it illegal, and someone saw you doing it? Also returns fail when enemies are nearby
virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed);
/// @return is \a ptr allowed to take/use \a item or is it a crime?
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, MWWorld::Ptr& victim);
virtual void forceStateUpdate(const MWWorld::Ptr &ptr);
virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);
......@@ -170,9 +172,21 @@ namespace MWMechanics
virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const;
virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer);
/// List the owners that the player has stolen this item from (the owner can be an NPC or a faction).
/// <Owner, item count>
virtual std::vector<std::pair<std::string, int> > getStolenItemOwners(const std::string& itemid);
/// Has the player stolen this item from the given owner?
virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid);
private:
void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim,
OffenseType type, int arg=0);
/// @return is \a ptr allowed to take/use \a cellref or is it a crime?
virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim);
};
}
......
......@@ -421,6 +421,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
break;
case ESM::REC_DCOU:
case ESM::REC_STLN:
MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.val);
break;
......
......@@ -117,6 +117,15 @@ namespace MWWorld
return mCellRef.mGlobalVariable;
}
void CellRef::resetGlobalVariable()
{
if (!mCellRef.mGlobalVariable.empty())
{
mChanged = true;
mCellRef.mGlobalVariable.erase();
}
}
void CellRef::setFactionRank(int factionRank)
{
if (factionRank != mCellRef.mFactionRank)
......
......@@ -75,6 +75,8 @@ namespace MWWorld
// Used by bed rent scripts to allow the player to use the bed for the duration of the rent.
std::string getGlobalVariable() const;
void resetGlobalVariable();
// ID of creature trapped in this soul gem
std::string getSoul() const;
void setSoul(const std::string& soul);
......
......@@ -80,6 +80,8 @@ namespace MWWorld
virtual std::string getId (const Ptr& ptr) const;
///< Return ID of \a ptr or throw an exception, if class does not support ID retrieval
/// (default implementation: throw an exception)
/// @note This function is currently redundant; the same ID can be retrieved by CellRef::getRefId.
/// Leaving it here for now in case we want to optimize later.
virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const;
virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWWorld::PhysicsSystem& physics) const;
......
......@@ -222,29 +222,22 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
MWWorld::ContainerStoreIterator it = end();
if (setOwner && actorPtr.getClass().isActor())
// HACK: Set owner on the original item, then reset it after we have copied it
// If we set the owner on the copied item, it would not stack correctly...
std::string oldOwner = itemPtr.getCellRef().getOwner();
if (!setOwner || actorPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) // No point in setting owner to the player - NPCs will not respect this anyway
{
// HACK: Set owner on the original item, then reset it after we have copied it
// If we set the owner on the copied item, it would not stack correctly...
std::string oldOwner = itemPtr.getCellRef().getOwner();
if (actorPtr == player)
{
// No point in setting owner to the player - NPCs will not respect this anyway
// Additionally, setting it to "player" would make those items not stack with items that don't have an owner
itemPtr.getCellRef().setOwner("");
}
else
itemPtr.getCellRef().setOwner(actorPtr.getCellRef().getRefId());
it = addImp(itemPtr, count);
itemPtr.getCellRef().setOwner(oldOwner);
itemPtr.getCellRef().setOwner("");
}
else
{
it = addImp(itemPtr, count);
itemPtr.getCellRef().setOwner(actorPtr.getCellRef().getRefId());
}
it = addImp(itemPtr, count);
itemPtr.getCellRef().setOwner(oldOwner);
// The copy of the original item we just made
MWWorld::Ptr item = *it;
......@@ -258,6 +251,14 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
pos.pos[1] = 0;
pos.pos[2] = 0;
item.getCellRef().setPosition(pos);
// reset ownership stuff, owner was already handled above
item.getCellRef().resetGlobalVariable();
item.getCellRef().setFaction("");
item.getCellRef().setFactionRank(-1);
// must reset the RefNum on the copied item, so that the RefNum on the original item stays unique
// maybe we should do this in the copy constructor instead?
item.getCellRef().unsetRefNum(); // destroy link to content file
std::string script = item.getClass().getScript(item);
......@@ -399,19 +400,19 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor
return count - toRemove;
}
void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner, const std::string& faction, int factionRank, const MWWorld::ESMStore& store)
void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner)
{
for (std::vector<ESM::ContItem>::const_iterator iter (items.mList.begin()); iter!=items.mList.end();