mirror of
				https://github.com/TES3MP/openmw-tes3mp.git
				synced 2025-10-31 23:26:43 +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