mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-21 10:23:56 +00:00
Merge pull request #2085 from akortunov/herbalism
Native graphics herbalism support
This commit is contained in:
commit
74112976b2
18 changed files with 232 additions and 4 deletions
|
@ -107,6 +107,7 @@
|
|||
Feature #4968: Scalable UI widget skins
|
||||
Feature #4994: Persistent pinnable windows hiding
|
||||
Feature #5000: Compressed BSA format support
|
||||
Feature #5010: Native graphics herbalism support
|
||||
Task #4686: Upgrade media decoder to a more current FFmpeg API
|
||||
Task #4695: Optimize Distant Terrain memory consumption
|
||||
Task #4721: Add NMake support to the Windows prebuild script
|
||||
|
|
|
@ -62,7 +62,7 @@ add_openmw_dir (mwsound
|
|||
add_openmw_dir (mwworld
|
||||
refdata worldimp scene globals class action nullaction actionteleport
|
||||
containerstore actiontalk actiontake manualref player cellvisitors failedaction
|
||||
cells localscripts customdata inventorystore ptr actionopen actionread
|
||||
cells localscripts customdata inventorystore ptr actionopen actionread actionharvest
|
||||
actionequip timestamp actionalchemy cellstore actionapply actioneat
|
||||
store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
|
||||
contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager
|
||||
|
|
|
@ -578,6 +578,7 @@ namespace MWBase
|
|||
/// Return the distance between actor's weapon and target's collision box.
|
||||
virtual float getHitDistance(const MWWorld::ConstPtr& actor, const MWWorld::ConstPtr& target) = 0;
|
||||
|
||||
virtual void addContainerScripts(const MWWorld::Ptr& reference, MWWorld::CellStore* cell) = 0;
|
||||
virtual void removeContainerScripts(const MWWorld::Ptr& reference) = 0;
|
||||
|
||||
virtual bool isPlayerInJail() const = 0;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "../mwworld/customdata.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/actionharvest.hpp"
|
||||
#include "../mwworld/actionopen.hpp"
|
||||
#include "../mwworld/actiontrap.hpp"
|
||||
#include "../mwphysics/physicssystem.hpp"
|
||||
|
@ -22,6 +23,7 @@
|
|||
|
||||
#include "../mwgui/tooltips.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
#include "../mwrender/renderinginterface.hpp"
|
||||
|
||||
|
@ -40,6 +42,10 @@ namespace MWClass
|
|||
{
|
||||
return *this;
|
||||
}
|
||||
virtual const ContainerCustomData& asContainerCustomData() const
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
MWWorld::CustomData *ContainerCustomData::clone() const
|
||||
|
@ -63,9 +69,20 @@ namespace MWClass
|
|||
|
||||
// store
|
||||
ptr.getRefData().setCustomData (data.release());
|
||||
|
||||
MWBase::Environment::get().getWorld()->addContainerScripts(ptr, ptr.getCell());
|
||||
}
|
||||
}
|
||||
|
||||
bool canBeHarvested(const MWWorld::ConstPtr& ptr)
|
||||
{
|
||||
const MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
|
||||
if (animation == nullptr)
|
||||
return false;
|
||||
|
||||
return animation->canBeHarvested();
|
||||
}
|
||||
|
||||
void Container::respawn(const MWWorld::Ptr &ptr) const
|
||||
{
|
||||
MWWorld::LiveCellRef<ESM::Container> *ref =
|
||||
|
@ -175,6 +192,12 @@ namespace MWClass
|
|||
{
|
||||
if(!isTrapped)
|
||||
{
|
||||
if (canBeHarvested(ptr))
|
||||
{
|
||||
std::shared_ptr<MWWorld::Action> action (new MWWorld::ActionHarvest(ptr));
|
||||
return action;
|
||||
}
|
||||
|
||||
std::shared_ptr<MWWorld::Action> action (new MWWorld::ActionOpen(ptr));
|
||||
return action;
|
||||
}
|
||||
|
@ -225,9 +248,18 @@ namespace MWClass
|
|||
|
||||
bool Container::hasToolTip (const MWWorld::ConstPtr& ptr) const
|
||||
{
|
||||
const MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
|
||||
if (getName(ptr).empty())
|
||||
return false;
|
||||
|
||||
return (ref->mBase->mName != "");
|
||||
if (const MWWorld::CustomData* data = ptr.getRefData().getCustomData())
|
||||
return !canBeHarvested(ptr) || data->asContainerCustomData().mContainerStore.hasVisibleItems();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Container::canBeActivated(const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
return hasToolTip(ptr);
|
||||
}
|
||||
|
||||
MWGui::ToolTipInfo Container::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
|
||||
|
|
|
@ -63,6 +63,8 @@ namespace MWClass
|
|||
const;
|
||||
///< Write additional state from \a ptr into \a state.
|
||||
|
||||
virtual bool canBeActivated(const MWWorld::Ptr& ptr) const;
|
||||
|
||||
static void registerSelf();
|
||||
|
||||
virtual void respawn (const MWWorld::Ptr& ptr) const;
|
||||
|
|
|
@ -949,6 +949,9 @@ namespace MWMechanics
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!target.getClass().canBeActivated(target))
|
||||
return true;
|
||||
|
||||
// TODO: implement a better check to check if target is owned bed
|
||||
if (target.getClass().isActivator() && target.getClass().getScript(target).compare(0, 3, "Bed") != 0)
|
||||
return true;
|
||||
|
|
|
@ -131,6 +131,25 @@ namespace
|
|||
}
|
||||
};
|
||||
|
||||
class HarvestVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
HarvestVisitor()
|
||||
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void apply(osg::Switch& node)
|
||||
{
|
||||
if (node.getName() == Constants::HerbalismLabel)
|
||||
{
|
||||
node.setSingleChildOn(1);
|
||||
}
|
||||
|
||||
traverse(node);
|
||||
}
|
||||
};
|
||||
|
||||
NifOsg::TextKeyMap::const_iterator findGroupStart(const NifOsg::TextKeyMap &keys, const std::string &groupname)
|
||||
{
|
||||
NifOsg::TextKeyMap::const_iterator iter(keys.begin());
|
||||
|
@ -1970,6 +1989,30 @@ namespace MWRender
|
|||
AddSwitchCallbacksVisitor visitor;
|
||||
mObjectRoot->accept(visitor);
|
||||
}
|
||||
|
||||
if (ptr.getTypeName() == typeid(ESM::Container).name() &&
|
||||
SceneUtil::hasUserDescription(mObjectRoot, Constants::HerbalismLabel) &&
|
||||
ptr.getRefData().getCustomData() != nullptr)
|
||||
{
|
||||
const MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr);
|
||||
if (!store.hasVisibleItems())
|
||||
{
|
||||
HarvestVisitor visitor;
|
||||
mObjectRoot->accept(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ObjectAnimation::canBeHarvested() const
|
||||
{
|
||||
if (mPtr.getTypeName() != typeid(ESM::Container).name())
|
||||
return false;
|
||||
|
||||
const MWWorld::LiveCellRef<ESM::Container>* ref = mPtr.get<ESM::Container>();
|
||||
if (!(ref->mBase->mFlags & ESM::Container::Organic))
|
||||
return false;
|
||||
|
||||
return SceneUtil::hasUserDescription(mObjectRoot, Constants::HerbalismLabel);
|
||||
}
|
||||
|
||||
Animation::AnimState::~AnimState()
|
||||
|
|
|
@ -475,6 +475,7 @@ public:
|
|||
virtual float getHeadPitch() const;
|
||||
virtual float getHeadYaw() const;
|
||||
virtual void setAccurateAiming(bool enabled) {}
|
||||
virtual bool canBeHarvested() const { return false; }
|
||||
|
||||
private:
|
||||
Animation(const Animation&);
|
||||
|
@ -484,6 +485,8 @@ private:
|
|||
class ObjectAnimation : public Animation {
|
||||
public:
|
||||
ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model, Resource::ResourceSystem* resourceSystem, bool animated, bool allowLight);
|
||||
|
||||
bool canBeHarvested() const;
|
||||
};
|
||||
|
||||
class UpdateVfxCallback : public osg::NodeCallback
|
||||
|
|
93
apps/openmw/mwworld/actionharvest.cpp
Normal file
93
apps/openmw/mwworld/actionharvest.cpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
#include "actionharvest.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <MyGUI_LanguageManager.h>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "class.hpp"
|
||||
#include "containerstore.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
ActionHarvest::ActionHarvest (const MWWorld::Ptr& container)
|
||||
: Action (true, container)
|
||||
{
|
||||
setSound("Item Ingredient Up");
|
||||
}
|
||||
|
||||
void ActionHarvest::executeImp (const MWWorld::Ptr& actor)
|
||||
{
|
||||
if (!MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory))
|
||||
return;
|
||||
|
||||
MWWorld::Ptr target = getTarget();
|
||||
MWWorld::ContainerStore& store = target.getClass().getContainerStore (target);
|
||||
MWWorld::ContainerStore& actorStore = actor.getClass().getContainerStore(actor);
|
||||
std::map<std::string, int> takenMap;
|
||||
for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it)
|
||||
{
|
||||
if (!it->getClass().showsInInventory(*it))
|
||||
continue;
|
||||
|
||||
int itemCount = it->getRefData().getCount();
|
||||
// Note: it is important to check for crime before move an item from container. Otherwise owner check will not work
|
||||
// for a last item in the container - empty harvested containers are considered as "allowed to use".
|
||||
MWBase::Environment::get().getMechanicsManager()->itemTaken(actor, *it, target, itemCount);
|
||||
actorStore.add(*it, itemCount, actor);
|
||||
store.remove(*it, itemCount, getTarget());
|
||||
takenMap[it->getClass().getName(*it)]+=itemCount;
|
||||
}
|
||||
|
||||
// Spawn a messagebox (only for items added to player's inventory)
|
||||
if (actor == MWBase::Environment::get().getWorld()->getPlayerPtr())
|
||||
{
|
||||
std::ostringstream stream;
|
||||
int lineCount = 0;
|
||||
const static int maxLines = 10;
|
||||
for (auto & pair : takenMap)
|
||||
{
|
||||
std::string itemName = pair.first;
|
||||
int itemCount = pair.second;
|
||||
lineCount++;
|
||||
if (lineCount == maxLines)
|
||||
stream << "\n...";
|
||||
else if (lineCount > maxLines)
|
||||
break;
|
||||
|
||||
// The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory
|
||||
std::string msgBox;
|
||||
if (itemCount == 1)
|
||||
{
|
||||
msgBox = MyGUI::LanguageManager::getInstance().replaceTags("\n#{sNotifyMessage60}");
|
||||
Misc::StringUtils::replace(msgBox, "%s", itemName.c_str(), 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
msgBox = MyGUI::LanguageManager::getInstance().replaceTags("\n#{sNotifyMessage61}");
|
||||
Misc::StringUtils::replace(msgBox, "%d", std::to_string(itemCount).c_str(), 2);
|
||||
Misc::StringUtils::replace(msgBox, "%s", itemName.c_str(), 2);
|
||||
}
|
||||
|
||||
stream << msgBox;
|
||||
}
|
||||
std::string tooltip = stream.str();
|
||||
// remove the first newline (easier this way)
|
||||
if (tooltip.size() > 0 && tooltip[0] == '\n')
|
||||
tooltip.erase(0, 1);
|
||||
|
||||
if (tooltip.size() > 0)
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(tooltip);
|
||||
}
|
||||
|
||||
// Update animation object
|
||||
MWBase::Environment::get().getWorld()->disable(target);
|
||||
MWBase::Environment::get().getWorld()->enable(target);
|
||||
}
|
||||
}
|
19
apps/openmw/mwworld/actionharvest.hpp
Normal file
19
apps/openmw/mwworld/actionharvest.hpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#ifndef GAME_MWWORLD_ACTIONHARVEST_H
|
||||
#define GAME_MWWORLD_ACTIONHARVEST_H
|
||||
|
||||
#include "action.hpp"
|
||||
#include "ptr.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class ActionHarvest : public Action
|
||||
{
|
||||
virtual void executeImp (const MWWorld::Ptr& actor);
|
||||
|
||||
public:
|
||||
ActionHarvest (const Ptr& container);
|
||||
///< \param container The Container the Player has activated.
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ACTIONOPEN_H
|
|
@ -422,6 +422,17 @@ int MWWorld::ContainerStore::remove(const std::string& itemId, int count, const
|
|||
return count - toRemove;
|
||||
}
|
||||
|
||||
bool MWWorld::ContainerStore::hasVisibleItems() const
|
||||
{
|
||||
for (auto iter(begin()); iter != end(); ++iter)
|
||||
{
|
||||
if (iter->getClass().showsInInventory(*iter))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor)
|
||||
{
|
||||
assert(this == item.getContainerStore());
|
||||
|
|
|
@ -128,6 +128,8 @@ namespace MWWorld
|
|||
ContainerStoreIterator begin (int mask = Type_All);
|
||||
ContainerStoreIterator end();
|
||||
|
||||
bool hasVisibleItems() const;
|
||||
|
||||
virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool setOwner=false);
|
||||
///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed)
|
||||
///
|
||||
|
|
|
@ -42,6 +42,13 @@ MWClass::ContainerCustomData &CustomData::asContainerCustomData()
|
|||
throw std::logic_error(error.str());
|
||||
}
|
||||
|
||||
const MWClass::ContainerCustomData &CustomData::asContainerCustomData() const
|
||||
{
|
||||
std::stringstream error;
|
||||
error << "bad cast " << typeid(this).name() << " to ContainerCustomData";
|
||||
throw std::logic_error(error.str());
|
||||
}
|
||||
|
||||
MWClass::DoorCustomData &CustomData::asDoorCustomData()
|
||||
{
|
||||
std::stringstream error;
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace MWWorld
|
|||
virtual const MWClass::NpcCustomData& asNpcCustomData() const;
|
||||
|
||||
virtual MWClass::ContainerCustomData& asContainerCustomData();
|
||||
virtual const MWClass::ContainerCustomData& asContainerCustomData() const;
|
||||
|
||||
virtual MWClass::DoorCustomData& asDoorCustomData();
|
||||
virtual const MWClass::DoorCustomData& asDoorCustomData() const;
|
||||
|
|
|
@ -42,6 +42,11 @@ namespace
|
|||
|
||||
bool operator()(const MWWorld::Ptr& containerPtr)
|
||||
{
|
||||
// Ignore containers without generated content
|
||||
if (containerPtr.getTypeName() == typeid(ESM::Container).name() &&
|
||||
containerPtr.getRefData().getCustomData() == nullptr)
|
||||
return false;
|
||||
|
||||
MWWorld::ContainerStore& container = containerPtr.getClass().getContainerStore(containerPtr);
|
||||
for(MWWorld::ContainerStoreIterator it = container.begin(); it != container.end(); ++it)
|
||||
{
|
||||
|
|
|
@ -141,9 +141,9 @@ namespace MWWorld
|
|||
MWWorld::Ptr getFacedObject(float maxDistance, bool ignorePlayer=true);
|
||||
|
||||
public: // FIXME
|
||||
void addContainerScripts(const Ptr& reference, CellStore* cell) override;
|
||||
void removeContainerScripts(const Ptr& reference) override;
|
||||
private:
|
||||
void addContainerScripts(const Ptr& reference, CellStore* cell);
|
||||
void PCDropped (const Ptr& item);
|
||||
|
||||
void processDoors(float duration);
|
||||
|
|
|
@ -27,6 +27,9 @@ const int CellSizeInUnits = 8192;
|
|||
// A label to mark night/day visual switches
|
||||
const std::string NightDayLabel = "NightDaySwitch";
|
||||
|
||||
// A label to mark visual switches for herbalism feature
|
||||
const std::string HerbalismLabel = "HerbalismSwitch";
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -635,6 +635,8 @@ namespace NifOsg
|
|||
const Nif::NiSwitchNode* niSwitchNode = static_cast<const Nif::NiSwitchNode*>(nifNode);
|
||||
if (niSwitchNode->name == Constants::NightDayLabel && !SceneUtil::hasUserDescription(rootNode, Constants::NightDayLabel))
|
||||
rootNode->getOrCreateUserDataContainer()->addDescription(Constants::NightDayLabel);
|
||||
else if (niSwitchNode->name == Constants::HerbalismLabel && !SceneUtil::hasUserDescription(rootNode, Constants::HerbalismLabel))
|
||||
rootNode->getOrCreateUserDataContainer()->addDescription(Constants::HerbalismLabel);
|
||||
}
|
||||
|
||||
return node;
|
||||
|
|
Loading…
Reference in a new issue