diff --git a/AUTHORS.md b/AUTHORS.md index 5b848fab4..c64b83dab 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -22,6 +22,7 @@ Programmers alexanderkjall Alexander Nadeau (wareya) Alexander Olofsson (Ace) + Alex Rice Alex S (docwest) Allofich Andrei Kortunov (akortunov) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29e310b58..74ad52cd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Bug #3765: DisableTeleporting makes Mark/Recall/Intervention effects undetectable Bug #3778: [Mod] Improved Thrown Weapon Projectiles - weapons have wrong transformation during throw animation Bug #3812: Wrong multiline tooltips width when word-wrapping is enabled + Bug #3894: Hostile spell effects not detected/present on first frame of OnPCHitMe Bug #4202: Open .omwaddon files without needing toopen openmw-cs first Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect Bug #4276: Resizing character window differs from vanilla @@ -24,7 +25,7 @@ Bug #4384: Resist Normal Weapons only checks ammunition for ranged weapons Bug #4411: Reloading a saved game while falling prevents damage in some cases Bug #4540: Rain delay when exiting water - Bug #4600: Crash when no sound output is available or --no-sound is used. + Bug #4600: Crash when no sound output is available or --no-sound is used. Bug #4639: Black screen after completing first mages guild mission + training Bug #4701: PrisonMarker record is not hardcoded like other markers Bug #4703: Editor: it's possible to preview levelled list records @@ -84,6 +85,7 @@ Bug #4945: Poor random magic magnitude distribution Bug #4947: Player character doesn't use lip animation Bug #4948: Footstep sounds while levitating on ground level + Bug #4952: Torches held by NPCs flicker too quickly Bug #4961: Flying creature combat engagement takes z-axis into account Bug #4963: Enchant skill progress is incorrect Bug #4964: Multiple effect spell projectile sounds play louder than vanilla @@ -96,6 +98,7 @@ Bug #4984: "Friendly hits" feature should be used only for player's followers Bug #4989: Object dimension-dependent VFX scaling behavior is inconsistent Bug #4990: Dead bodies prevent you from hitting + Bug #4991: Jumping occasionally takes too much fatigue Bug #4999: Drop instruction behaves differently from vanilla Bug #5001: Possible data race in the Animation::setAlpha() Bug #5004: Werewolves shield their eyes during storm @@ -105,18 +108,32 @@ Bug #5038: Enchanting success chance calculations are blatantly wrong Bug #5047: # in cell names sets color Bug #5050: Invalid spell effects are not handled gracefully + Bug #5055: Mark, Recall, Intervention magic effect abilities have no effect when added and removed in the same frame Bug #5056: Calling Cast function on player doesn't equip the spell but casts it Bug #5060: Magic effect visuals stop when death animation begins instead of when it ends Bug #5063: Shape named "Tri Shadow" in creature mesh is visible if it isn't hidden + Bug #5067: Ranged attacks on unaware opponents ("critical hits") differ from the vanilla engine Bug #5069: Blocking creatures' attacks doesn't degrade shields Bug #5074: Paralyzed actors greet the player Bug #5075: Enchanting cast style can be changed if there's no object + Bug #5078: DisablePlayerLooking is broken Bug #5082: Scrolling with controller in GUI mode is broken + Bug #5089: Swimming/Underwater creatures only swim around ground level Bug #5092: NPCs with enchanted weapons play sound when out of charges Bug #5093: Hand to hand sound plays on knocked out enemies Bug #5099: Non-swimming enemies will enter water if player is water walking + Bug #5103: Sneaking state behavior is still inconsistent + Bug #5104: Black Dart's enchantment doesn't trigger at low Enchant levels Bug #5105: NPCs start combat with werewolves from any distance + Bug #5106: Still can jump even when encumbered Bug #5110: ModRegion with a redundant numerical argument breaks script execution + Bug #5112: Insufficient magicka for current spell not reflected on HUD icon + Bug #5123: Script won't run on respawn + Bug #5124: Arrow remains attached to actor if pulling animation was cancelled + Bug #5126: Swimming creatures without RunForward animations are motionless during combat + Bug #5134: Doors rotation by "Lock" console command is inconsistent + Bug #5126: Swimming creatures without RunForward animations are motionless during combat + Bug #5137: Textures with Clamp Mode set to Clamp instead of Wrap are too dark outside the boundaries Feature #1774: Handle AvoidNode Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls @@ -137,6 +154,7 @@ Feature #4812: Support NiSwitchNode Feature #4836: Daytime node switch Feature #4859: Make water reflections more configurable + Feature #4882: Support for NiPalette node Feature #4887: Add openmw command option to set initial random seed Feature #4890: Make Distant Terrain configurable Feature #4958: Support eight blood types @@ -152,7 +170,13 @@ Feature #5036: Allow scripted faction leaving Feature #5046: Gamepad thumbstick cursor speed Feature #5051: Provide a separate textures for scrollbars + Feature #5091: Human-readable light source duration Feature #5094: Unix like console hotkeys + Feature #5098: Allow user controller bindings + Feature #5121: Handle NiTriStrips and NiTriStripsData + Feature #5122: Use magic glow for enchanted arrows + Feature #5131: Custom skeleton bones + Feature #5132: Unique animations for different weapon types Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4695: Optimize Distant Terrain memory consumption Task #4789: Optimize cell transitions diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e7647aa6..ceb4b2c5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -647,9 +647,7 @@ endif(WIN32) # Extern IF(BUILD_OPENMW OR BUILD_OPENCS) -set(RECASTNAVIGATION_DEMO OFF CACHE BOOL "Do not build RecastDemo") set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries") -set(RECASTNAVIGATION_TESTS OFF CACHE BOOL "Do not build recastnavigation tests") add_subdirectory (extern/recastnavigation EXCLUDE_FROM_ALL) add_subdirectory (extern/osg-ffmpeg-videoplayer) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index e790d23f5..450b16694 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -83,7 +83,7 @@ add_openmw_dir (mwmechanics drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe aicast aiescort aiface aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning - character actors objects aistate coordinateconverter trading weaponpriority spellpriority + character actors objects aistate coordinateconverter trading weaponpriority spellpriority weapontype ) add_openmw_dir (mwstate diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 928e90596..f8854f292 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -636,8 +636,17 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) controllerFileName = "gamecontrollerdb.txt"; } + const std::string userdefault = mCfgMgr.getUserConfigPath().string() + "/" + controllerFileName; const std::string localdefault = mCfgMgr.getLocalPath().string() + "/" + controllerFileName; const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/" + controllerFileName; + + std::string userGameControllerdb; + if (boost::filesystem::exists(userdefault)){ + userGameControllerdb = userdefault; + } + else + userGameControllerdb = ""; + std::string gameControllerdb; if (boost::filesystem::exists(localdefault)) gameControllerdb = localdefault; @@ -646,7 +655,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) else gameControllerdb = ""; //if it doesn't exist, pass in an empty string - MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, gameControllerdb, mGrab); + MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); mEnvironment.setInputManager (input); std::string myguiResources = (mResDir / "mygui").string(); diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 6f26ff757..4592414a6 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -559,7 +559,6 @@ namespace MWBase virtual void togglePreviewMode(bool enable) = 0; virtual bool toggleVanityMode(bool enable) = 0; virtual void allowVanityMode(bool allow) = 0; - virtual void togglePlayerLooking(bool enable) = 0; virtual void changeVanityModeScale(float factor) = 0; virtual bool vanityRotateCamera(float * rot) = 0; virtual void setCameraDistance(float dist, bool adjust = false, bool override = true)=0; diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index eabac1644..411b720fe 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -103,7 +103,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count); std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 7241a5f3b..6afd51071 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -138,7 +138,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 1208ba9e1..6cda0732c 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -33,6 +33,7 @@ #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/weapontype.hpp" #include "../mwgui/tooltips.hpp" @@ -244,7 +245,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; @@ -378,21 +379,13 @@ namespace MWClass if(*slot == MWWorld::InventoryStore::Slot_CarriedLeft) { MWWorld::ConstContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - - if(weapon == invStore.end()) - return std::make_pair(1,""); - - if(weapon->getTypeName() == typeid(ESM::Weapon).name() && - (weapon->get()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand || - weapon->get()->mBase->mData.mType == ESM::Weapon::BluntTwoClose || - weapon->get()->mBase->mData.mType == ESM::Weapon::BluntTwoWide || - weapon->get()->mBase->mData.mType == ESM::Weapon::SpearTwoWide || - weapon->get()->mBase->mData.mType == ESM::Weapon::AxeTwoHand || - weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanBow || - weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)) + if(weapon != invStore.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()) { - return std::make_pair(3,""); + const MWWorld::LiveCellRef *ref = weapon->get(); + if (MWMechanics::getWeaponType(ref->mBase->mData.mType)->mFlags & ESM::WeaponType::TwoHanded) + return std::make_pair(3,""); } + return std::make_pair(1,""); } } diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index c37ce6dde..d67e922c6 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -154,7 +154,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index a5f5b005a..733ca9794 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -202,7 +202,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 4192f6e6a..b06f5afa8 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -335,7 +335,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName; + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName); std::string text; int lockLevel = ptr.getCellRef().getLockLevel(); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4ddaf0148..ef8e570d5 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -42,6 +42,7 @@ #include "../mwworld/containerstore.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/localscripts.hpp" #include "../mwrender/renderinginterface.hpp" #include "../mwrender/objects.hpp" @@ -735,7 +736,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName; + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName); std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) @@ -994,7 +995,12 @@ namespace MWClass if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) + { ptr.getRefData().setCount(1); + const std::string& script = getScript(ptr); + if(!script.empty()) + MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); + } MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(nullptr); diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index d24496a9c..d9e1bb599 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -357,7 +357,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName; + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName); std::string text; diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index ee385ebb1..0bb20d86d 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -150,7 +150,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 38b7137f3..f31df3aed 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -151,18 +151,14 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; - if (Settings::Manager::getBool("show effect duration","Game")) - { - // -1 is infinite light source, so duration makes no sense here. Other negative values are treated as 0. - float remainingTime = ptr.getClass().getRemainingUsageTime(ptr); - if (remainingTime != -1.0f) - text += "\n#{sDuration}: " + MWGui::ToolTips::toString(std::max(0.f, remainingTime)); - } + // Don't show duration for infinite light sources. + if (Settings::Manager::getBool("show effect duration","Game") && ptr.getClass().getRemainingUsageTime(ptr) != -1) + text += MWGui::ToolTips::getDurationString(ptr.getClass().getRemainingUsageTime(ptr), "\n#{sDuration}"); text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}"); text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 013808bb2..33d4632ca 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -116,7 +116,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 8a0644349..c4bec64a9 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -192,7 +192,7 @@ namespace MWClass else // gold displays its count also if it's 1. countString = " (" + std::to_string(count) + ")"; - info.caption = ref->mBase->mName + countString; + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + countString; info.icon = ref->mBase->mIcon; if (ref->mRef.getSoul() != "") diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b1c817e6a..560445e9c 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1,4 +1,4 @@ -#include "npc.hpp" +#include "npc.hpp" #include @@ -41,7 +41,7 @@ #include "../mwmechanics/combat.hpp" #include "../mwmechanics/autocalcspell.hpp" #include "../mwmechanics/difficultyscaling.hpp" -#include "../mwmechanics/character.hpp" +#include "../mwmechanics/weapontype.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwworld/ptr.hpp" @@ -52,6 +52,7 @@ #include "../mwworld/customdata.hpp" #include "../mwphysics/physicssystem.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/localscripts.hpp" #include "../mwrender/objects.hpp" #include "../mwrender/renderinginterface.hpp" @@ -1124,10 +1125,9 @@ namespace MWClass const float normalizedEncumbrance = getNormalizedEncumbrance(ptr); bool swimming = world->isSwimming(ptr); + bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); + bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run); bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(ptr); - bool sneaking = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); - sneaking = sneaking && (inair || MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr)); - bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run); running = running && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr)); float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()* @@ -1255,11 +1255,11 @@ namespace MWClass bool fullHelp = MWBase::Environment::get().getWindowManager()->getFullHelp(); MWGui::ToolTipInfo info; - info.caption = getName(ptr); + info.caption = MyGUI::TextIterator::toTagsString(getName(ptr)); if(fullHelp && ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf()) { info.caption += " ("; - info.caption += ref->mBase->mName; + info.caption += MyGUI::TextIterator::toTagsString(ref->mBase->mName); info.caption += ")"; } @@ -1412,9 +1412,9 @@ namespace MWClass if (getNpcStats(ptr).isWerewolf() && getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run)) { - MWMechanics::WeaponType weaponType = MWMechanics::WeapType_None; - MWMechanics::getActiveWeapon(getCreatureStats(ptr), getInventoryStore(ptr), &weaponType); - if (weaponType == MWMechanics::WeapType_None) + int weaponType = ESM::Weapon::None; + MWMechanics::getActiveWeapon(ptr, &weaponType); + if (weaponType == ESM::Weapon::None) return std::string(); } @@ -1561,7 +1561,12 @@ namespace MWClass if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) + { ptr.getRefData().setCount(1); + const std::string& script = getScript(ptr); + if (!script.empty()) + MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); + } MWBase::Environment::get().getWorld()->removeContainerScripts(ptr); ptr.getRefData().setCustomData(nullptr); diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 796fabb60..7f83aec6c 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -143,7 +143,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index b53e2418a..0a0c1548f 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -116,7 +116,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index d4181eeb2..304a910b6 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -150,7 +150,7 @@ namespace MWClass const MWWorld::LiveCellRef *ref = ptr.get(); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; std::string text; diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 6bbf6c4fa..3c74524a4 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -29,6 +29,8 @@ #include "../mwphysics/physicssystem.hpp" #include "../mwworld/nullaction.hpp" +#include "../mwmechanics/weapontype.hpp" + #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" @@ -98,8 +100,9 @@ namespace MWClass bool Weapon::hasItemHealth (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); + int type = ref->mBase->mData.mType; - return (ref->mBase->mData.mType < ESM::Weapon::MarksmanThrown); // thrown weapons and arrows/bolts don't have health, only quantity + return MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::HasHealth; } int Weapon::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const @@ -120,16 +123,17 @@ namespace MWClass std::pair, bool> Weapon::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); + ESM::WeaponType::Class weapClass = MWMechanics::getWeaponType(ref->mBase->mData.mType)->mWeaponClass; std::vector slots_; bool stack = false; - if (ref->mBase->mData.mType==ESM::Weapon::Arrow || ref->mBase->mData.mType==ESM::Weapon::Bolt) + if (weapClass == ESM::WeaponType::Ammo) { slots_.push_back (int (MWWorld::InventoryStore::Slot_Ammunition)); stack = true; } - else if (ref->mBase->mData.mType==ESM::Weapon::MarksmanThrown) + else if (weapClass == ESM::WeaponType::Thrown) { slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight)); stack = true; @@ -143,30 +147,9 @@ namespace MWClass int Weapon::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); + int type = ref->mBase->mData.mType; - const int size = 12; - - static const int sMapping[size][2] = - { - { ESM::Weapon::ShortBladeOneHand, ESM::Skill::ShortBlade }, - { ESM::Weapon::LongBladeOneHand, ESM::Skill::LongBlade }, - { ESM::Weapon::LongBladeTwoHand, ESM::Skill::LongBlade }, - { ESM::Weapon::BluntOneHand, ESM::Skill::BluntWeapon }, - { ESM::Weapon::BluntTwoClose, ESM::Skill::BluntWeapon }, - { ESM::Weapon::BluntTwoWide, ESM::Skill::BluntWeapon }, - { ESM::Weapon::SpearTwoWide, ESM::Skill::Spear }, - { ESM::Weapon::AxeOneHand, ESM::Skill::Axe }, - { ESM::Weapon::AxeTwoHand, ESM::Skill::Axe }, - { ESM::Weapon::MarksmanBow, ESM::Skill::Marksman }, - { ESM::Weapon::MarksmanCrossbow, ESM::Skill::Marksman }, - { ESM::Weapon::MarksmanThrown, ESM::Skill::Marksman } - }; - - for (int i=0; imBase->mData.mType) - return sMapping[i][1]; - - return -1; + return MWMechanics::getWeaponType(type)->mSkill; } int Weapon::getValue (const MWWorld::ConstPtr& ptr) const @@ -186,89 +169,17 @@ namespace MWClass std::string Weapon::getUpSoundId (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); - int type = ref->mBase->mData.mType; - // Ammo - if (type == 12 || type == 13) - { - return std::string("Item Ammo Up"); - } - // Bow - if (type == 9) - { - return std::string("Item Weapon Bow Up"); - } - // Crossbow - if (type == 10) - { - return std::string("Item Weapon Crossbow Up"); - } - // Longblades, One hand and Two - if (type == 1 || type == 2) - { - return std::string("Item Weapon Longblade Up"); - } - // Shortblade - if (type == 0) - { - return std::string("Item Weapon Shortblade Up"); - } - // Spear - if (type == 6) - { - return std::string("Item Weapon Spear Up"); - } - // Blunts, Axes and Thrown weapons - if (type == 3 || type == 4 || type == 5 || type == 7 || type == 8 || type == 11) - { - return std::string("Item Weapon Blunt Up"); - } - - return std::string("Item Misc Up"); + std::string soundId = MWMechanics::getWeaponType(type)->mSoundId; + return soundId + " Up"; } std::string Weapon::getDownSoundId (const MWWorld::ConstPtr& ptr) const { const MWWorld::LiveCellRef *ref = ptr.get(); - int type = ref->mBase->mData.mType; - // Ammo - if (type == 12 || type == 13) - { - return std::string("Item Ammo Down"); - } - // Bow - if (type == 9) - { - return std::string("Item Weapon Bow Down"); - } - // Crossbow - if (type == 10) - { - return std::string("Item Weapon Crossbow Down"); - } - // Longblades, One hand and Two - if (type == 1 || type == 2) - { - return std::string("Item Weapon Longblade Down"); - } - // Shortblade - if (type == 0) - { - return std::string("Item Weapon Shortblade Down"); - } - // Spear - if (type == 6) - { - return std::string("Item Weapon Spear Down"); - } - // Blunts, Axes and Thrown weapons - if (type == 3 || type == 4 || type == 5 || type == 7 || type == 8 || type == 11) - { - return std::string("Item Weapon Blunt Down"); - } - - return std::string("Item Misc Down"); + std::string soundId = MWMechanics::getWeaponType(type)->mSoundId; + return soundId + " Down"; } std::string Weapon::getInventoryIcon (const MWWorld::ConstPtr& ptr) const @@ -288,9 +199,10 @@ namespace MWClass MWGui::ToolTipInfo Weapon::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const { const MWWorld::LiveCellRef *ref = ptr.get(); + const ESM::WeaponType* weaponType = MWMechanics::getWeaponType(ref->mBase->mData.mType); MWGui::ToolTipInfo info; - info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count); + info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count); info.icon = ref->mBase->mIcon; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); @@ -298,37 +210,26 @@ namespace MWClass std::string text; // weapon type & damage - if ((ref->mBase->mData.mType < ESM::Weapon::Arrow || Settings::Manager::getBool("show projectile damage", "Game")) && ref->mBase->mData.mType <= ESM::Weapon::Bolt) + if (weaponType->mWeaponClass != ESM::WeaponType::Ammo || Settings::Manager::getBool("show projectile damage", "Game")) { text += "\n#{sType} "; - static std::map > mapping; - if (mapping.empty()) + int skill = MWMechanics::getWeaponType(ref->mBase->mData.mType)->mSkill; + const std::string type = ESM::Skill::sSkillNameIds[skill]; + std::string oneOrTwoHanded; + if (weaponType->mWeaponClass == ESM::WeaponType::Melee) { - mapping[ESM::Weapon::ShortBladeOneHand] = std::make_pair("sSkillShortblade", "sOneHanded"); - mapping[ESM::Weapon::LongBladeOneHand] = std::make_pair("sSkillLongblade", "sOneHanded"); - mapping[ESM::Weapon::LongBladeTwoHand] = std::make_pair("sSkillLongblade", "sTwoHanded"); - mapping[ESM::Weapon::BluntOneHand] = std::make_pair("sSkillBluntweapon", "sOneHanded"); - mapping[ESM::Weapon::BluntTwoClose] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); - mapping[ESM::Weapon::BluntTwoWide] = std::make_pair("sSkillBluntweapon", "sTwoHanded"); - mapping[ESM::Weapon::SpearTwoWide] = std::make_pair("sSkillSpear", "sTwoHanded"); - mapping[ESM::Weapon::AxeOneHand] = std::make_pair("sSkillAxe", "sOneHanded"); - mapping[ESM::Weapon::AxeTwoHand] = std::make_pair("sSkillAxe", "sTwoHanded"); - mapping[ESM::Weapon::MarksmanBow] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::MarksmanCrossbow] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::MarksmanThrown] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::Arrow] = std::make_pair("sSkillMarksman", ""); - mapping[ESM::Weapon::Bolt] = std::make_pair("sSkillMarksman", ""); + if (weaponType->mFlags & ESM::WeaponType::TwoHanded) + oneOrTwoHanded = "sTwoHanded"; + else + oneOrTwoHanded = "sOneHanded"; } - const std::string type = mapping[ref->mBase->mData.mType].first; - const std::string oneOrTwoHanded = mapping[ref->mBase->mData.mType].second; - text += store.get().find(type)->mValue.getString() + ((oneOrTwoHanded != "") ? ", " + store.get().find(oneOrTwoHanded)->mValue.getString() : ""); // weapon damage - if (ref->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + if (weaponType->mWeaponClass == ESM::WeaponType::Thrown) { // Thrown weapons have 2x real damage applied // as they're both the weapon and the ammo @@ -336,14 +237,7 @@ namespace MWClass + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0] * 2)) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1] * 2)); } - else if (ref->mBase->mData.mType >= ESM::Weapon::MarksmanBow) - { - // marksman - text += "\n#{sAttack}: " - + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0])) - + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1])); - } - else + else if (weaponType->mWeaponClass == ESM::WeaponType::Melee) { // Chop text += "\n#{sChop}: " @@ -358,6 +252,13 @@ namespace MWClass + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mThrust[0])) + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mThrust[1])); } + else + { + // marksman + text += "\n#{sAttack}: " + + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[0])) + + " - " + MWGui::ToolTips::toString(static_cast(ref->mBase->mData.mChop[1])); + } } if (hasItemHealth(ptr)) @@ -369,7 +270,7 @@ namespace MWClass const bool verbose = Settings::Manager::getBool("show melee info", "Game"); // add reach for melee weapon - if (ref->mBase->mData.mType < ESM::Weapon::MarksmanBow && verbose) + if (weaponType->mWeaponClass == ESM::WeaponType::Melee && verbose) { // display value in feet const float combatDistance = store.get().find("fCombatDistance")->mValue.getFloat() * ref->mBase->mData.mReach; @@ -378,7 +279,7 @@ namespace MWClass } // add attack speed for any weapon excepts arrows and bolts - if (ref->mBase->mData.mType < ESM::Weapon::Arrow && verbose) + if (weaponType->mWeaponClass != ESM::WeaponType::Ammo && verbose) { text += MWGui::ToolTips::getPercentString(ref->mBase->mData.mSpeed, "#{sAttributeSpeed}"); } @@ -449,13 +350,8 @@ namespace MWClass if (slots_.first.empty()) return std::make_pair (0, ""); - if(ptr.get()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand || - ptr.get()->mBase->mData.mType == ESM::Weapon::BluntTwoClose || - ptr.get()->mBase->mData.mType == ESM::Weapon::BluntTwoWide || - ptr.get()->mBase->mData.mType == ESM::Weapon::SpearTwoWide || - ptr.get()->mBase->mData.mType == ESM::Weapon::AxeTwoHand || - ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanBow || - ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + int type = ptr.get()->mBase->mData.mType; + if(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded) { return std::make_pair (2, ""); } diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 64eecdb79..8f8ca4bb5 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -112,10 +112,7 @@ struct TypesetBookImpl : TypesetBook if (i->empty()) return Range (Utf8Point (nullptr), Utf8Point (nullptr)); - Utf8Point begin = &i->front (); - Utf8Point end = &i->front () + i->size (); - - return Range (begin, end); + return Range (i->data(), i->data() + i->size()); } size_t pageCount () const { return mPages.size (); } @@ -346,8 +343,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter assert (end <= mCurrentContent->size ()); assert (begin <= mCurrentContent->size ()); - Utf8Point begin_ = &mCurrentContent->front () + begin; - Utf8Point end_ = &mCurrentContent->front () + end ; + Utf8Point begin_ = mCurrentContent->data() + begin; + Utf8Point end_ = mCurrentContent->data() + end; writeImpl (static_cast (style), begin_, end_); } diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 9e0eb3737..a1fa16afd 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -25,34 +26,52 @@ namespace { + struct Response + { + const std::string mText; + const ESM::Class::Specialization mSpecialization; + }; + struct Step { const std::string mText; - const std::string mButtons[3]; + const Response mResponses[3]; const std::string mSound; }; - const ESM::Class::Specialization mSpecializations[3]={ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}; // The specialization for each answer Step sGenerateClassSteps(int number) { number++; - Step step = { - Fallback::Map::getString("Question_"+MyGUI::utility::toString(number)+"_Question"), - {Fallback::Map::getString("Question_"+MyGUI::utility::toString(number)+"_AnswerOne"), - Fallback::Map::getString("Question_"+MyGUI::utility::toString(number)+"_AnswerTwo"), - Fallback::Map::getString("Question_"+MyGUI::utility::toString(number)+"_AnswerThree")}, - "vo\\misc\\chargen qa"+MyGUI::utility::toString(number)+".wav" - }; - return step; - } - struct ClassPoint - { - const char *id; - // Specialization points to match, in order: Stealth, Combat, Magic - // Note: Order is taken from http://www.uesp.net/wiki/Morrowind:Class_Quiz - unsigned int points[3]; - }; + std::string question = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_Question"); + std::string answer0 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerOne"); + std::string answer1 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerTwo"); + std::string answer2 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerThree"); + std::string sound = "vo\\misc\\chargen qa" + MyGUI::utility::toString(number) + ".wav"; + + Response r0 = {answer0, ESM::Class::Combat}; + Response r1 = {answer1, ESM::Class::Magic}; + Response r2 = {answer2, ESM::Class::Stealth}; + + // randomize order in which responses are displayed + int order = Misc::Rng::rollDice(6); + + switch (order) + { + case 0: + return {question, {r0, r1, r2}, sound}; + case 1: + return {question, {r0, r2, r1}, sound}; + case 2: + return {question, {r1, r0, r2}, sound}; + case 3: + return {question, {r1, r2, r0}, sound}; + case 4: + return {question, {r2, r0, r1}, sound}; + default: + return {question, {r2, r1, r0}, sound}; + } + } void updatePlayerHealth() { @@ -80,6 +99,9 @@ namespace MWGui , mGenerateClassStep(0) { mCreationStage = CSE_NotStarted; + mGenerateClassResponses[0] = ESM::Class::Combat; + mGenerateClassResponses[1] = ESM::Class::Magic; + mGenerateClassResponses[2] = ESM::Class::Stealth; mGenerateClassSpecializations[0] = 0; mGenerateClassSpecializations[1] = 0; mGenerateClassSpecializations[2] = 0; @@ -550,12 +572,12 @@ namespace MWGui return; } - ESM::Class::Specialization specialization = mSpecializations[_index]; - if (specialization == ESM::Class::Stealth) + ESM::Class::Specialization specialization = mGenerateClassResponses[_index]; + if (specialization == ESM::Class::Combat) ++mGenerateClassSpecializations[0]; - else if (specialization == ESM::Class::Combat) - ++mGenerateClassSpecializations[1]; else if (specialization == ESM::Class::Magic) + ++mGenerateClassSpecializations[1]; + else if (specialization == ESM::Class::Stealth) ++mGenerateClassSpecializations[2]; ++mGenerateClassStep; showClassQuestionDialog(); @@ -565,57 +587,96 @@ namespace MWGui { if (mGenerateClassStep == 10) { - static std::array classes = { { - {"Acrobat", {6, 2, 2}}, - {"Agent", {6, 1, 3}}, - {"Archer", {3, 5, 2}}, - {"Archer", {5, 5, 0}}, - {"Assassin", {6, 3, 1}}, - {"Barbarian", {3, 6, 1}}, - {"Bard", {3, 3, 3}}, - {"Battlemage", {1, 3, 6}}, - {"Crusader", {1, 6, 3}}, - {"Healer", {3, 1, 6}}, - {"Knight", {2, 6, 2}}, - {"Monk", {5, 3, 2}}, - {"Nightblade", {4, 2, 4}}, - {"Pilgrim", {5, 2, 3}}, - {"Rogue", {3, 4, 3}}, - {"Rogue", {4, 4, 2}}, - {"Rogue", {5, 4, 1}}, - {"Scout", {2, 5, 3}}, - {"Sorcerer", {2, 2, 6}}, - {"Spellsword", {2, 4, 4}}, - {"Spellsword", {5, 1, 4}}, - {"Witchhunter", {2, 3, 5}}, - {"Witchhunter", {5, 0, 5}} - } }; + unsigned combat = mGenerateClassSpecializations[0]; + unsigned magic = mGenerateClassSpecializations[1]; + unsigned stealth = mGenerateClassSpecializations[2]; - int match = -1; - for (unsigned i = 0; i < classes.size(); ++i) + if (combat > 7) { - if (mGenerateClassSpecializations[0] == classes[i].points[0] && - mGenerateClassSpecializations[1] == classes[i].points[1] && - mGenerateClassSpecializations[2] == classes[i].points[2]) - { - match = i; - mGenerateClass = classes[i].id; - break; - } + mGenerateClass = "Warrior"; } - - if (match == -1) + else if (magic > 7) { - if (mGenerateClassSpecializations[0] >= 7) - mGenerateClass = "Thief"; - else if (mGenerateClassSpecializations[1] >= 7) - mGenerateClass = "Warrior"; - else if (mGenerateClassSpecializations[2] >= 7) - mGenerateClass = "Mage"; - else + mGenerateClass = "Mage"; + } + else if (stealth > 7) + { + mGenerateClass = "Thief"; + } + else + { + switch (combat) { - Log(Debug::Warning) << "Failed to deduce class from chosen answers in generate class dialog."; - mGenerateClass = "Thief"; + case 4: + mGenerateClass = "Rogue"; + break; + case 5: + if (stealth == 3) + mGenerateClass = "Scout"; + else + mGenerateClass = "Archer"; + break; + case 6: + if (stealth == 1) + mGenerateClass = "Barbarian"; + else if (stealth == 3) + mGenerateClass = "Crusader"; + else + mGenerateClass = "Knight"; + break; + case 7: + mGenerateClass = "Warrior"; + break; + default: + switch (magic) + { + case 4: + mGenerateClass = "Spellsword"; + break; + case 5: + mGenerateClass = "Witchhunter"; + break; + case 6: + if (combat == 2) + mGenerateClass = "Sorcerer"; + else if (combat == 3) + mGenerateClass = "Healer"; + else + mGenerateClass = "Battlemage"; + break; + case 7: + mGenerateClass = "Mage"; + break; + default: + switch (stealth) + { + case 3: + if (magic == 3) + mGenerateClass = "Bard"; // unreachable + else + mGenerateClass = "Warrior"; + break; + case 5: + if (magic == 3) + mGenerateClass = "Monk"; + else + mGenerateClass = "Pilgrim"; + break; + case 6: + if (magic == 1) + mGenerateClass = "Agent"; + else if (magic == 3) + mGenerateClass = "Assassin"; + else + mGenerateClass = "Acrobat"; + break; + case 7: + mGenerateClass = "Thief"; + break; + default: + mGenerateClass = "Warrior"; + } + } } } @@ -642,16 +703,21 @@ namespace MWGui mGenerateClassQuestionDialog = new InfoBoxDialog(); + Step step = sGenerateClassSteps(mGenerateClassStep); + mGenerateClassResponses[0] = step.mResponses[0].mSpecialization; + mGenerateClassResponses[1] = step.mResponses[1].mSpecialization; + mGenerateClassResponses[2] = step.mResponses[2].mSpecialization; + InfoBoxDialog::ButtonList buttons; - mGenerateClassQuestionDialog->setText(sGenerateClassSteps(mGenerateClassStep).mText); - buttons.push_back(sGenerateClassSteps(mGenerateClassStep).mButtons[0]); - buttons.push_back(sGenerateClassSteps(mGenerateClassStep).mButtons[1]); - buttons.push_back(sGenerateClassSteps(mGenerateClassStep).mButtons[2]); + mGenerateClassQuestionDialog->setText(step.mText); + buttons.push_back(step.mResponses[0].mText); + buttons.push_back(step.mResponses[1].mText); + buttons.push_back(step.mResponses[2].mText); mGenerateClassQuestionDialog->setButtons(buttons); mGenerateClassQuestionDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassQuestionChosen); mGenerateClassQuestionDialog->setVisible(true); - MWBase::Environment::get().getSoundManager()->say(sGenerateClassSteps(mGenerateClassStep).mSound); + MWBase::Environment::get().getSoundManager()->say(step.mSound); } void CharacterCreation::selectGeneratedClass() diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp index 0130222f3..d5739c3d0 100644 --- a/apps/openmw/mwgui/charactercreation.hpp +++ b/apps/openmw/mwgui/charactercreation.hpp @@ -75,8 +75,9 @@ namespace MWGui //Class generation vars unsigned mGenerateClassStep; // Keeps track of current step in Generate Class dialog + ESM::Class::Specialization mGenerateClassResponses[3]; unsigned mGenerateClassSpecializations[3]; // A counter for each specialization which is increased when an answer is chosen - std::string mGenerateClass; // In order: Stealth, Combat, Magic + std::string mGenerateClass; // In order: Combat, Magic, Stealth ////Dialog events //Name dialog diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 51508cd06..8a501e598 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -137,27 +137,8 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", "") ); } } - if (effectInfo.mRemainingTime > -1 && - Settings::Manager::getBool("show effect duration","Game")) { - sourcesDescription += " #{sDuration}: "; - float duration = effectInfo.mRemainingTime; - if (duration > 3600) - { - int hour = duration / 3600; - duration -= hour*3600; - sourcesDescription += MWGui::ToolTips::toString(hour) + "h"; - } - if (duration > 60) - { - int minute = duration / 60; - duration -= minute*60; - sourcesDescription += MWGui::ToolTips::toString(minute) + "m"; - } - if (duration > 0.1) - { - sourcesDescription += MWGui::ToolTips::toString(duration) + "s"; - } - } + if (effectInfo.mRemainingTime > -1 && Settings::Manager::getBool("show effect duration","Game")) + sourcesDescription += MWGui::ToolTips::getDurationString(effectInfo.mRemainingTime, " #{sDuration}"); addNewLine = true; } diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 2dd6cdc00..cbe664ab1 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -71,7 +71,7 @@ namespace MWGui { newSpell.mType = Spell::Type_Spell; std::string cost = std::to_string(spell->mData.mCost); - std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor, nullptr, true, true))); + std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor))); newSpell.mCostColumn = cost + "/" + chance; } else diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index dc7a9668d..7d8a07c21 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -667,6 +667,60 @@ namespace MWGui return ret; } + std::string ToolTips::getDurationString(float duration, const std::string& prefix) + { + std::string ret; + ret = prefix + ": "; + + if (duration < 1.f) + { + ret += "0 s"; + return ret; + } + + constexpr int secondsPerMinute = 60; // 60 seconds + constexpr int secondsPerHour = secondsPerMinute * 60; // 60 minutes + constexpr int secondsPerDay = secondsPerHour * 24; // 24 hours + constexpr int secondsPerMonth = secondsPerDay * 30; // 30 days + constexpr int secondsPerYear = secondsPerDay * 365; + int fullDuration = static_cast(duration); + int units = 0; + int years = fullDuration / secondsPerYear; + int months = fullDuration % secondsPerYear / secondsPerMonth; + int days = fullDuration % secondsPerYear % secondsPerMonth / secondsPerDay; // Because a year is not exactly 12 "months" + int hours = fullDuration % secondsPerDay / secondsPerHour; + int minutes = fullDuration % secondsPerHour / secondsPerMinute; + int seconds = fullDuration % secondsPerMinute; + if (years) + { + units++; + ret += toString(years) + " y "; + } + if (months) + { + units++; + ret += toString(months) + " mo "; + } + if (units < 2 && days) + { + units++; + ret += toString(days) + " d "; + } + if (units < 2 && hours) + { + units++; + ret += toString(hours) + " h "; + } + if (units >= 2) + return ret; + if (minutes) + ret += toString(minutes) + " min "; + if (seconds) + ret += toString(seconds) + " s "; + + return ret; + } + bool ToolTips::toggleFullHelp() { mFullHelp = !mFullHelp; diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 43187dc5c..afdc7dec0 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -84,6 +84,9 @@ namespace MWGui static std::string getCellRefString(const MWWorld::CellRef& cellref); ///< Returns a string containing debug tooltip information about the given cellref. + static std::string getDurationString (float duration, const std::string& prefix); + ///< Returns duration as two largest time units, rounded down. Note: not localized; no line break. + // these do not create an actual tooltip, but they fill in the data that is required so the tooltip // system knows what to show in case this widget is hovered static void createSkillToolTip(MyGUI::Widget* widget, int skillId); diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 8456643d0..10b850541 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -51,6 +51,7 @@ namespace MWInput osg::ref_ptr screenCaptureHandler, osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, + const std::string& userControllerBindingsFile, const std::string& controllerBindingsFile, bool grab) : mWindow(window) , mWindowVisible(true) @@ -125,10 +126,14 @@ namespace MWInput // Load controller mappings #if SDL_VERSION_ATLEAST(2,0,2) - if(controllerBindingsFile!="") + if(!controllerBindingsFile.empty()) { SDL_GameControllerAddMappingsFromFile(controllerBindingsFile.c_str()); } + if(!userControllerBindingsFile.empty()) + { + SDL_GameControllerAddMappingsFromFile(userControllerBindingsFile.c_str()); + } #endif // Open all presently connected sticks @@ -601,7 +606,7 @@ namespace MWInput rot[2] = xAxis * (dt * 100.0f) * 10.0f * mCameraSensitivity * (1.0f/256.f) * (mInvertX ? -1 : 1); // Only actually turn player when we're not in vanity mode - if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot)) + if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && mControlSwitch["playerlooking"]) { mPlayer->yaw(rot[2]); mPlayer->pitch(rot[0]); @@ -839,9 +844,6 @@ namespace MWInput void InputManager::toggleControlSwitch (const std::string& sw, bool value) { - if (mControlSwitch[sw] == value) { - return; - } /// \note 7 switches at all, if-else is relevant if (sw == "playercontrols" && !value) { mPlayer->setLeftRight(0); @@ -853,8 +855,8 @@ namespace MWInput mPlayer->setUpDown(0); } else if (sw == "vanitymode") { MWBase::Environment::get().getWorld()->allowVanityMode(value); - } else if (sw == "playerlooking") { - MWBase::Environment::get().getWorld()->togglePlayerLooking(value); + } else if (sw == "playerlooking" && !value) { + MWBase::Environment::get().getWorld()->rotateObject(mPlayer->getPlayer(), 0.f, 0.f, 0.f); } mControlSwitch[sw] = value; } @@ -989,7 +991,7 @@ namespace MWInput rot[2] = -x; // Only actually turn player when we're not in vanity mode - if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot)) + if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && mControlSwitch["playerlooking"]) { mPlayer->yaw(x); mPlayer->pitch(y); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index caf57681d..eaa081f99 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -76,6 +76,7 @@ namespace MWInput osg::ref_ptr screenCaptureHandler, osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, + const std::string& userControllerBindingsFile, const std::string& controllerBindingsFile, bool grab); virtual ~InputManager(); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 2928da1a5..d4f74ea0a 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2025,14 +2025,7 @@ namespace MWMechanics MWWorld::Ptr player = getPlayer(); - CreatureStats& stats = player.getClass().getCreatureStats(player); - MWBase::World* world = MWBase::Environment::get().getWorld(); - - bool sneaking = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); - bool inair = !world->isOnGround(player) && !world->isSwimming(player) && !world->isFlying(player); - sneaking = sneaking && (ctrl->isSneaking() || inair); - - if (!sneaking) + if (!MWBase::Environment::get().getMechanicsManager()->isSneaking(player)) { MWBase::Environment::get().getWindowManager()->setSneakVisibility(false); return; @@ -2040,6 +2033,7 @@ namespace MWMechanics static float sneakSkillTimer = 0.f; // Times sneak skill progress from "avoid notice" + MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::Store& gmst = world->getStore().get(); static const float fSneakUseDist = gmst.find("fSneakUseDist")->mValue.getFloat(); static const float fSneakUseDelay = gmst.find("fSneakUseDelay")->mValue.getFloat(); diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index ae6727063..a7583d294 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -511,10 +511,9 @@ namespace MWMechanics if (targetClass.hasInventoryStore(target)) { - MWMechanics::WeaponType weapType = WeapType_None; - MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(targetClass.getCreatureStats(target), targetClass.getInventoryStore(target), &weapType); - if (weapType != WeapType_PickProbe && weapType != WeapType_Spell && weapType != WeapType_None && weapType != WeapType_HandToHand) + int weapType = ESM::Weapon::None; + MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(target, &weapType); + if (weapType > ESM::Weapon::None) targetWeapon = *weaponSlot; } @@ -729,7 +728,7 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); // get projectile speed (depending on weapon type) - if (weapType == ESM::Weapon::MarksmanThrown) + if (MWMechanics::getWeaponType(weapType)->mWeaponClass == ESM::WeaponType::Thrown) { static float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->mValue.getFloat(); static float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat(); diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 167e13128..9f698b630 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -18,6 +18,7 @@ #include "combat.hpp" #include "weaponpriority.hpp" #include "spellpriority.hpp" +#include "weapontype.hpp" namespace MWMechanics { @@ -125,8 +126,7 @@ namespace MWMechanics } const ESM::Weapon* weapon = mWeapon.get()->mBase; - - if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) + if (MWMechanics::getWeaponType(weapon->mData.mType)->mWeaponClass != ESM::WeaponType::Melee) { isRanged = true; return fProjectileMaxSpeed; @@ -194,11 +194,12 @@ namespace MWMechanics if (rating > bestActionRating) { const ESM::Weapon* weapon = it->get()->mBase; + int ammotype = getWeaponType(weapon->mData.mType)->mAmmoType; MWWorld::Ptr ammo; - if (weapon->mData.mType == ESM::Weapon::MarksmanBow) + if (ammotype == ESM::Weapon::Arrow) ammo = bestArrow; - else if (weapon->mData.mType == ESM::Weapon::MarksmanCrossbow) + else if (ammotype == ESM::Weapon::Bolt) ammo = bestBolt; bestActionRating = rating; @@ -367,7 +368,7 @@ namespace MWMechanics else if (!activeWeapon.isEmpty()) { const ESM::Weapon* esmWeap = activeWeapon.get()->mBase; - if (esmWeap->mData.mType >= ESM::Weapon::MarksmanBow) + if (MWMechanics::getWeaponType(esmWeap->mData.mType)->mWeaponClass != ESM::WeaponType::Melee) { static const float fTargetSpellMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat(); dist = fTargetSpellMaxSpeed; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 94a502544..598292fc3 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -301,7 +301,7 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoin return true; const float actorSpeed = actor.getClass().getSpeed(actor); - const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability + const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / getAngularVelocity(actorSpeed) * 2; // *2 - for reliability const float distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length(); const float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2; @@ -363,7 +363,7 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act // get actor's shortest radius for moving in circle float speed = actor.getClass().getSpeed(actor); speed += speed * 0.1f; // 10% real speed inaccuracy - float radius = speed / MAX_VEL_ANGULAR_RADIANS; + float radius = speed / getAngularVelocity(speed); // get radius direction to the center const float* rot = actor.getRefData().getPosition().rot; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index d00bbeb5f..cd13b1820 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -448,7 +448,7 @@ namespace MWMechanics else { // have not yet reached the destination - evadeObstacles(actor, storage); + evadeObstacles(actor, duration, storage); } } @@ -479,8 +479,17 @@ namespace MWMechanics storage.setState(AiWanderStorage::Wander_IdleNow); } - void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage) + void AiWander::evadeObstacles(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage) { + if (mUsePathgrid) + { + const auto halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); + const float actorTolerance = 2 * actor.getClass().getSpeed(actor) * duration + + 1.2 * std::max(halfExtents.x(), halfExtents.y()); + const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance); + mPathFinder.buildPathByNavMeshToNextPoint(actor, halfExtents, getNavigatorFlags(actor), pointTolerance); + } + if (mObstacleCheck.isEvading()) { // first check if we're walking into a door diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index d586bb0bc..0b967983f 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -137,7 +137,7 @@ namespace MWMechanics short unsigned getRandomIdle(); void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage); - void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); + void evadeObstacles(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage); void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 9b60987f8..7f2028477 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -214,33 +214,6 @@ public: }; -static const struct WeaponInfo { - WeaponType type; - const char shortgroup[16]; - const char longgroup[16]; -} sWeaponTypeList[] = { - { WeapType_HandToHand, "hh", "handtohand" }, - { WeapType_OneHand, "1h", "weapononehand" }, - { WeapType_TwoHand, "2c", "weapontwohand" }, - { WeapType_TwoWide, "2w", "weapontwowide" }, - { WeapType_BowAndArrow, "1h", "bowandarrow" }, - { WeapType_Crossbow, "crossbow", "crossbow" }, - { WeapType_Thrown, "1h", "throwweapon" }, - { WeapType_PickProbe, "1h", "pickprobe" }, - { WeapType_Spell, "spell", "spellcast" }, -}; -static const WeaponInfo *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])]; - -class FindWeaponType { - WeaponType type; - -public: - FindWeaponType(WeaponType _type) : type(_type) { } - - bool operator()(const WeaponInfo &weap) const - { return weap.type == type; } -}; - std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const { int numAnims=0; @@ -331,6 +304,8 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) { mAnimation->disable(mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; + if (mWeaponType > ESM::Weapon::None) + mAnimation->showWeapons(true); } else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) { @@ -361,7 +336,7 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) idle = CharState_None; } -void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, bool force) +void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force) { if (!force && jump == mJumpState && idle == CharState_None) return; @@ -371,22 +346,17 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState if (jump != JumpState_None) { jumpAnimName = "jump"; - if(weap != sWeaponTypeListEnd) + if(!weapShortGroup.empty()) { - jumpAnimName += weap->shortgroup; + jumpAnimName += weapShortGroup; if(!mAnimation->hasAnimation(jumpAnimName)) { - jumpmask = MWRender::Animation::BlendMask_LowerBody; - jumpAnimName = "jump"; + jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask); - // Since we apply movement only for lower body, do not reset idle animations. + // If we apply jump only for lower body, do not reset idle animations. // For upper body there will be idle animation. - if (idle == CharState_None) + if (jumpmask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) idle = CharState_Idle; - - // For crossbow animations use 1h ones as fallback - if (mWeaponType == WeapType_Crossbow) - jumpAnimName += "1h"; } } } @@ -460,7 +430,65 @@ void CharacterController::onClose() } } -void CharacterController::refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, CharacterState& idle, bool force) +std::string CharacterController::getWeaponAnimation(int weaponType) const +{ + std::string weaponGroup = getWeaponType(weaponType)->mLongGroup; + bool isRealWeapon = weaponType != ESM::Weapon::HandToHand && weaponType != ESM::Weapon::Spell && weaponType != ESM::Weapon::None; + if (isRealWeapon && !mAnimation->hasAnimation(weaponGroup)) + { + static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup; + static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup; + + const ESM::WeaponType* weapInfo = getWeaponType(weaponType); + + // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones + if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) + weaponGroup = twoHandFallback; + else if (isRealWeapon) + weaponGroup = oneHandFallback; + } + + return weaponGroup; +} + +std::string CharacterController::fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask) +{ + bool isRealWeapon = mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None; + if (!isRealWeapon) + { + if (blendMask != nullptr) + *blendMask = MWRender::Animation::BlendMask_LowerBody; + + return baseGroupName; + } + + static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mShortGroup; + static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mShortGroup; + + std::string groupName = baseGroupName; + const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType); + + // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones + if (isRealWeapon && weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee) + groupName += twoHandFallback; + else if (isRealWeapon) + groupName += oneHandFallback; + + // Special case for crossbows - we shouls apply 1h animations a fallback only for lower body + if (mWeaponType == ESM::Weapon::MarksmanCrossbow && blendMask != nullptr) + *blendMask = MWRender::Animation::BlendMask_LowerBody; + + if (!mAnimation->hasAnimation(groupName)) + { + groupName = baseGroupName; + if (blendMask != nullptr) + *blendMask = MWRender::Animation::BlendMask_LowerBody; + } + + return groupName; +} + +void CharacterController::refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force) { if (movement == mMovementState && idle == mIdleState && !force) return; @@ -474,15 +502,15 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character if(movestate != sMovementListEnd) { movementAnimName = movestate->groupname; - if(weap != sWeaponTypeListEnd) + if(!weapShortGroup.empty()) { std::string::size_type swimpos = movementAnimName.find("swim"); if (swimpos == std::string::npos) { - if (mWeaponType == WeapType_Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case - movementAnimName = weap->shortgroup + movementAnimName; + if (mWeaponType == ESM::Weapon::Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case + movementAnimName = weapShortGroup + movementAnimName; else - movementAnimName += weap->shortgroup; + movementAnimName += weapShortGroup; } if(!mAnimation->hasAnimation(movementAnimName)) @@ -490,15 +518,12 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character movementAnimName = movestate->groupname; if (swimpos == std::string::npos) { - movemask = MWRender::Animation::BlendMask_LowerBody; - // Since we apply movement only for lower body, do not reset idle animations. - // For upper body there will be idle animation. - if (idle == CharState_None) - idle = CharState_Idle; + movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); - // For crossbow animations use 1h ones as fallback - if (mWeaponType == WeapType_Crossbow) - movementAnimName += "1h"; + // If we apply movement only for lower body, do not reset idle animations. + // For upper body there will be idle animation. + if (movemask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) + idle = CharState_Idle; } else if (idle == CharState_None) { @@ -520,7 +545,20 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character if(!mAnimation->hasAnimation(movementAnimName)) { std::string::size_type swimpos = movementAnimName.find("swim"); - if(swimpos == std::string::npos) + if (swimpos != std::string::npos) + { + movementAnimName.erase(swimpos, 4); + if (!weapShortGroup.empty()) + { + std::string weapMovementAnimName = movementAnimName + weapShortGroup; + if(mAnimation->hasAnimation(weapMovementAnimName)) + movementAnimName = weapMovementAnimName; + else + movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); + } + } + + if (swimpos == std::string::npos || !mAnimation->hasAnimation(movementAnimName)) { std::string::size_type runpos = movementAnimName.find("run"); if (runpos != std::string::npos) @@ -532,25 +570,6 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character else movementAnimName.clear(); } - else - { - // For crossbow animations use 1h ones as fallback - if (mWeaponType == WeapType_Crossbow) - movementAnimName += "1h"; - - movementAnimName.erase(swimpos, 4); - if (weap != sWeaponTypeListEnd) - { - std::string weapMovementAnimName = movementAnimName + weap->shortgroup; - if(mAnimation->hasAnimation(weapMovementAnimName)) - movementAnimName = weapMovementAnimName; - else - movemask = MWRender::Animation::BlendMask_LowerBody; - } - - if (!mAnimation->hasAnimation(movementAnimName)) - movementAnimName.clear(); - } } } @@ -565,10 +584,6 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character mCurrentMovement = movementAnimName; if(!mCurrentMovement.empty()) { - bool isflying = MWBase::Environment::get().getWorld()->isFlying(mPtr); - bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !isflying; - bool issneaking = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !isflying; - // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. std::string anim = mCurrentMovement; @@ -601,7 +616,7 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character // The first person anims don't have any velocity to calculate a speed multiplier from. // We use the third person velocities instead. // FIXME: should be pulled from the actual animation, but it is not presently loaded. - mMovementAnimSpeed = (issneaking ? 33.5452f : (isrunning ? 222.857f : 154.064f)); + mMovementAnimSpeed = (isSneaking() ? 33.5452f : (isRunning() ? 222.857f : 154.064f)); mMovementAnimationControlled = false; } } @@ -612,7 +627,7 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character } } -void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force) +void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force) { // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), // the idle animation should be displayed @@ -644,11 +659,13 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat else if(mIdleState != CharState_None) { idleGroup = "idle"; - if(weap != sWeaponTypeListEnd) + if(!weapShortGroup.empty()) { - idleGroup += weap->shortgroup; + idleGroup += weapShortGroup; if(!mAnimation->hasAnimation(idleGroup)) - idleGroup = "idle"; + { + idleGroup = fallbackShortWeaponGroup("idle"); + } // play until the Loop Stop key 2 to 5 times, then play until the Stop key // this replicates original engine behavior for the "Idle1h" 1st-person animation @@ -683,9 +700,9 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if (mPtr.getClass().isActor()) refreshHitRecoilAnims(idle); - const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); - if (!mPtr.getClass().hasInventoryStore(mPtr)) - weap = sWeaponTypeListEnd; + std::string weap; + if (mPtr.getClass().hasInventoryStore(mPtr)) + weap = getWeaponType(mWeaponType)->mShortGroup; refreshJumpAnims(weap, jump, idle, force); refreshMovementAnims(weap, movement, idle, force); @@ -694,77 +711,6 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat refreshIdleAnims(weap, idle, force); } - -void getWeaponGroup(WeaponType weaptype, std::string &group) -{ - const WeaponInfo *info = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(weaptype)); - if(info != sWeaponTypeListEnd) - group = info->longgroup; - else - group.clear(); -} - - -MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype) -{ - if(stats.getDrawState() == DrawState_Spell) - { - *weaptype = WeapType_Spell; - return inv.end(); - } - - if(stats.getDrawState() == MWMechanics::DrawState_Weapon) - { - MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end()) - *weaptype = WeapType_HandToHand; - else - { - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) - *weaptype = WeapType_PickProbe; - else if(type == typeid(ESM::Weapon).name()) - { - MWWorld::LiveCellRef *ref = weapon->get(); - ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; - switch(weaponType) - { - case ESM::Weapon::ShortBladeOneHand: - case ESM::Weapon::LongBladeOneHand: - case ESM::Weapon::BluntOneHand: - case ESM::Weapon::AxeOneHand: - case ESM::Weapon::Arrow: - case ESM::Weapon::Bolt: - *weaptype = WeapType_OneHand; - break; - case ESM::Weapon::LongBladeTwoHand: - case ESM::Weapon::BluntTwoClose: - case ESM::Weapon::AxeTwoHand: - *weaptype = WeapType_TwoHand; - break; - case ESM::Weapon::BluntTwoWide: - case ESM::Weapon::SpearTwoWide: - *weaptype = WeapType_TwoWide; - break; - case ESM::Weapon::MarksmanBow: - *weaptype = WeapType_BowAndArrow; - break; - case ESM::Weapon::MarksmanCrossbow: - *weaptype = WeapType_Crossbow; - break; - case ESM::Weapon::MarksmanThrown: - *weaptype = WeapType_Thrown; - break; - } - } - } - - return weapon; - } - - return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); -} - void CharacterController::playDeath(float startpoint, CharacterState death) { // Make sure the character was swimming upon death for forward-compatibility @@ -909,7 +855,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mHitState(CharState_None) , mUpperBodyState(UpperCharState_Nothing) , mJumpState(JumpState_None) - , mWeaponType(WeapType_None) + , mWeaponType(ESM::Weapon::None) , mAttackStrength(0.f) , mSkipAnim(false) , mSecondsOfSwimming(0) @@ -933,19 +879,20 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim if (cls.hasInventoryStore(mPtr)) { - getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); - if (mWeaponType != WeapType_None) + getActiveWeapon(mPtr, &mWeaponType); + if (mWeaponType != ESM::Weapon::None) { mUpperBodyState = UpperCharState_WeapEquiped; - getWeaponGroup(mWeaponType, mCurrentWeapon); + mCurrentWeapon = getWeaponAnimation(mWeaponType); } - if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) + if(mWeaponType != ESM::Weapon::None && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::HandToHand) { mAnimation->showWeapons(true); // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly, // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example) - bool useRelativeDuration = mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Crossbow; + ESM::WeaponType::Class weaponClass = getWeaponType(mWeaponType)->mWeaponClass; + bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged; mAnimation->setWeaponGroup(mCurrentWeapon, useRelativeDuration); } @@ -1185,11 +1132,11 @@ bool CharacterController::updateCreatureState() const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); - WeaponType weapType = WeapType_None; + int weapType = ESM::Weapon::None; if(stats.getDrawState() == DrawState_Weapon) - weapType = WeapType_HandToHand; + weapType = ESM::Weapon::HandToHand; else if (stats.getDrawState() == DrawState_Spell) - weapType = WeapType_Spell; + weapType = ESM::Weapon::Spell; if (weapType != mWeaponType) { @@ -1206,7 +1153,7 @@ bool CharacterController::updateCreatureState() std::string startKey = "start"; std::string stopKey = "stop"; - if (weapType == WeapType_Spell) + if (weapType == ESM::Weapon::Spell) { const std::string spellid = stats.getSpells().getSelectedSpell(); bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr); @@ -1272,7 +1219,7 @@ bool CharacterController::updateCreatureState() mCurrentWeapon = ""; } - if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation + if (weapType != ESM::Weapon::Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation { mCurrentWeapon = chooseRandomAttackAnimation(); } @@ -1287,7 +1234,7 @@ bool CharacterController::updateCreatureState() mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); - if (weapType == WeapType_HandToHand) + if (weapType == ESM::Weapon::HandToHand) playSwishSound(0.0f); } } @@ -1301,34 +1248,23 @@ bool CharacterController::updateCreatureState() return false; } -bool CharacterController::updateCarriedLeftVisible(WeaponType weaptype) const +bool CharacterController::updateCarriedLeftVisible(const int weaptype) const { // Shields/torches shouldn't be visible during any operation involving two hands // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", // but they are also present in weapon drawing animation. - switch (weaptype) - { - case WeapType_Spell: - case WeapType_BowAndArrow: - case WeapType_Crossbow: - case WeapType_HandToHand: - case WeapType_TwoHand: - case WeapType_TwoWide: - return false; - default: - return true; - } + return !(getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded); } bool CharacterController::updateWeaponState(CharacterState& idle) { const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); - WeaponType weaptype = WeapType_None; + int weaptype = ESM::Weapon::None; if(stats.getDrawState() == DrawState_Weapon) - weaptype = WeapType_HandToHand; + weaptype = ESM::Weapon::HandToHand; else if (stats.getDrawState() == DrawState_Spell) - weaptype = WeapType_Spell; + weaptype = ESM::Weapon::Spell; const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); @@ -1338,18 +1274,18 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + MWWorld::ContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); if(stats.getDrawState() == DrawState_Spell) weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon != inv.end() && mWeaponType != WeapType_HandToHand && weaptype > WeapType_HandToHand && weaptype < WeapType_Spell) + if(weapon != inv.end() && mWeaponType != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None) upSoundId = weapon->getClass().getUpSoundId(*weapon); - if(weapon != inv.end() && mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell) + if(weapon != inv.end() && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None) downSoundId = weapon->getClass().getDownSoundId(*weapon); // weapon->HtH switch: weapon is empty already, so we need to take sound from previous weapon - if(weapon == inv.end() && !mWeapon.isEmpty() && weaptype == WeapType_HandToHand && mWeaponType != WeapType_Spell) + if(weapon == inv.end() && !mWeapon.isEmpty() && weaptype == ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell) downSoundId = mWeapon.getClass().getDownSoundId(mWeapon); MWWorld::Ptr newWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr(); @@ -1370,8 +1306,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle) bool forcestateupdate = false; // We should not play equipping animation and sound during weapon->weapon transition - bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell && - mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell; + bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None && + mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None; // If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires), // we should force actor to the "weapon equipped" state, interrupt attack and update animations. @@ -1381,6 +1317,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) mUpperBodyState = UpperCharState_WeapEquiped; mAttackingOrSpell = false; mAnimation->disable(mCurrentWeapon); + mAnimation->showWeapons(true); if (mPtr == getPlayer()) MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); } @@ -1388,7 +1325,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if(!isKnockedOut() && !isKnockedDown() && !isRecovery()) { std::string weapgroup; - if ((!isWerewolf || mWeaponType != WeapType_Spell) + if ((!isWerewolf || mWeaponType != ESM::Weapon::Spell) && weaptype != mWeaponType && mUpperBodyState != UpperCharState_UnEquipingWeap && !isStillWeapon) @@ -1397,7 +1334,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if (!weaponChanged) { // Note: we do not disable unequipping animation automatically to avoid body desync - getWeaponGroup(mWeaponType, weapgroup); + weapgroup = getWeaponAnimation(mWeaponType); mAnimation->play(weapgroup, priorityWeapon, MWRender::Animation::BlendMask_All, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0); @@ -1425,17 +1362,18 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { forcestateupdate = true; mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); + weapgroup = getWeaponAnimation(weaptype); - getWeaponGroup(weaptype, weapgroup); // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly, // for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example) - bool useRelativeDuration = weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow; + ESM::WeaponType::Class weaponClass = getWeaponType(weaptype)->mWeaponClass; + bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged; mAnimation->setWeaponGroup(weapgroup, useRelativeDuration); if (!isStillWeapon) { mAnimation->disable(mCurrentWeapon); - if (weaptype != WeapType_None) + if (weaptype != ESM::Weapon::None) { mAnimation->showWeapons(false); mAnimation->play(weapgroup, priorityWeapon, @@ -1444,7 +1382,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) mUpperBodyState = UpperCharState_EquipingWeap; // If we do not have the "equip attach" key, show weapon manually. - if (weaptype != WeapType_Spell) + if (weaptype != ESM::Weapon::Spell) { if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0) mAnimation->showWeapons(true); @@ -1464,7 +1402,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) } mWeaponType = weaptype; - getWeaponGroup(mWeaponType, mCurrentWeapon); + mCurrentWeapon = getWeaponAnimation(mWeaponType); if(!upSoundId.empty() && !isStillWeapon) { @@ -1478,8 +1416,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { mUpperBodyState = UpperCharState_Nothing; mAnimation->disable(mCurrentWeapon); - mWeaponType = WeapType_None; - getWeaponGroup(mWeaponType, mCurrentWeapon); + mWeaponType = ESM::Weapon::None; + mCurrentWeapon = getWeaponAnimation(mWeaponType); } } } @@ -1490,7 +1428,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && mHasMovedInXY && !MWBase::Environment::get().getWorld()->isSwimming(mPtr) - && mWeaponType == WeapType_None) + && mWeaponType == ESM::Weapon::None) { if(!sndMgr->getSoundPlaying(mPtr, "WolfRun")) sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWSound::Type::Sfx, @@ -1507,16 +1445,17 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if (mPtr.getClass().hasInventoryStore(mPtr)) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()); - if(isWeapon) + if (isWeapon) + { weapSpeed = weapon->get()->mBase->mData.mSpeed; + MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + int ammotype = getWeaponType(weapon->get()->mBase->mData.mType)->mAmmoType; + if (ammotype != ESM::Weapon::None && (ammo == inv.end() || ammo->get()->mBase->mData.mType != ammotype)) + ammunition = false; + } - MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (mWeaponType == WeapType_Crossbow) - ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt); - else if (mWeaponType == WeapType_BowAndArrow) - ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow); if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) { mAnimation->disable(mCurrentWeapon); @@ -1530,6 +1469,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) float complete; bool animPlaying; + ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; if(mAttackingOrSpell) { MWWorld::Ptr player = getPlayer(); @@ -1548,7 +1488,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) mCurrentWeapon = chooseRandomAttackAnimation(); } - if(mWeaponType == WeapType_Spell) + if(mWeaponType == ESM::Weapon::Spell) { // Unset casting flag, otherwise pressing the mouse button down would // continue casting every frame if there is no animation @@ -1673,7 +1613,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) resetIdle = false; } } - else if(mWeaponType == WeapType_PickProbe) + else if(mWeaponType == ESM::Weapon::PickProbe) { MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::Ptr item = *weapon; @@ -1703,7 +1643,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { std::string startKey; std::string stopKey; - if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown) + + if(weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) { mAttackType = "shoot"; startKey = mAttackType+" start"; @@ -1785,7 +1726,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); } - if(mWeaponType != WeapType_Crossbow && mWeaponType != WeapType_BowAndArrow) + if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); @@ -1815,15 +1756,17 @@ bool CharacterController::updateWeaponState(CharacterState& idle) else if (isKnockedDown()) { if (mUpperBodyState > UpperCharState_WeapEquiped) + { mUpperBodyState = UpperCharState_WeapEquiped; + if (mWeaponType > ESM::Weapon::None) + mAnimation->showWeapons(true); + } mAnimation->disable(mCurrentWeapon); } } mAnimation->setPitchFactor(0.f); - if (mWeaponType == WeapType_BowAndArrow || - mWeaponType == WeapType_Thrown || - mWeaponType == WeapType_Crossbow) + if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) { switch (mUpperBodyState) { @@ -1840,7 +1783,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { // technically we do not need a pitch for crossbow reload animation, // but we should avoid abrupt repositioning - if (mWeaponType == WeapType_Crossbow) + if (mWeaponType == ESM::Weapon::MarksmanCrossbow) mAnimation->setPitchFactor(std::max(0.f, 1.f-complete*10.f)); else mAnimation->setPitchFactor(1.f-complete); @@ -1857,7 +1800,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) mUpperBodyState == UpperCharState_FollowStartToFollowStop || mUpperBodyState == UpperCharState_CastingSpell) { - if (ammunition && mWeaponType == WeapType_Crossbow) + if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow) mAnimation->attachArrow(); mUpperBodyState = UpperCharState_WeapEquiped; @@ -1934,7 +1877,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if(!start.empty()) { int mask = MWRender::Animation::BlendMask_All; - if (mWeaponType == WeapType_Crossbow) + if (mWeaponType == ESM::Weapon::MarksmanCrossbow) mask = MWRender::Animation::BlendMask_UpperBody; mAnimation->disable(mCurrentWeapon); @@ -2187,7 +2130,8 @@ void CharacterController::update(float duration, bool animationOnly) cls.getCreatureStats(mPtr).setFatigue(fatigue); } - if(sneak || inwater || flying || incapacitated || !solid) + float z = cls.getJump(mPtr); + if(sneak || inwater || flying || incapacitated || !solid || z <= 0) vec.z() = 0.0f; bool inJump = true; @@ -2210,7 +2154,6 @@ void CharacterController::update(float duration, bool animationOnly) else if(vec.z() > 0.0f && mJumpState != JumpState_InAir) { // Started a jump. - float z = cls.getJump(mPtr); if (z > 0) { if(vec.x() == 0 && vec.y() == 0) @@ -2221,28 +2164,6 @@ void CharacterController::update(float duration, bool animationOnly) lat.normalize(); vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f; } - - // advance acrobatics - // also set jumping flag to allow GetPCJumping works - if (isPlayer) - { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); - MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); - } - - // decrease fatigue - const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat(); - const float fatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat(); - float normalizedEncumbrance = mPtr.getClass().getNormalizedEncumbrance(mPtr); - if (normalizedEncumbrance > 1) - normalizedEncumbrance = 1; - const float fatigueDecrease = fatigueJumpBase + normalizedEncumbrance * fatigueJumpMult; - - if (!godmode) - { - fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); - cls.getCreatureStats(mPtr).setFatigue(fatigue); - } } } else if(mJumpState == JumpState_InAir && !inwater && !flying && solid) @@ -2455,10 +2376,10 @@ void CharacterController::update(float duration, bool animationOnly) world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); movement = vec; - cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; - - // Can't reset jump state (mPosition[2]) here; we don't know for sure whether the PhysicSystem will actually handle it in this frame + if (movement.z() == 0.f) + cls.getMovementSettings(mPtr).mPosition[2] = 0; + // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicSystem will actually handle it in this frame // due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled. if (!mSkipAnim) @@ -2775,7 +2696,7 @@ void CharacterController::resurrect() mAnimation->disable(mCurrentDeath); mCurrentDeath.clear(); mDeathState = CharState_None; - mWeaponType = WeapType_None; + mWeaponType = ESM::Weapon::None; } void CharacterController::updateContinuousVfx() diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index f2b1f33a7..614beca1c 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -10,6 +10,8 @@ #include "../mwrender/animation.hpp" +#include "weapontype.hpp" + namespace MWWorld { class InventoryStore; @@ -113,21 +115,6 @@ enum CharacterState { CharState_Block }; -enum WeaponType { - WeapType_None, - - WeapType_HandToHand, - WeapType_OneHand, - WeapType_TwoHand, - WeapType_TwoWide, - WeapType_BowAndArrow, - WeapType_Crossbow, - WeapType_Thrown, - WeapType_PickProbe, - - WeapType_Spell -}; - enum UpperBodyCharacterState { UpperCharState_Nothing, UpperCharState_EquipingWeap, @@ -186,7 +173,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener JumpingState mJumpState; std::string mCurrentJump; - WeaponType mWeaponType; + int mWeaponType; std::string mCurrentWeapon; float mAttackStrength; @@ -212,9 +199,9 @@ class CharacterController : public MWRender::Animation::TextKeyListener void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); void refreshHitRecoilAnims(CharacterState& idle); - void refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, bool force=false); - void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, CharacterState& idle, bool force=false); - void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false); + void refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force=false); + void refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force=false); + void refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force=false); void clearAnimQueue(bool clearPersistAnims = false); @@ -241,7 +228,11 @@ class CharacterController : public MWRender::Animation::TextKeyListener /// @param num if non-nullptr, the chosen animation number will be written here std::string chooseRandomGroup (const std::string& prefix, int* num = nullptr) const; - bool updateCarriedLeftVisible(WeaponType weaptype) const; + bool updateCarriedLeftVisible(int weaptype) const; + + std::string fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask = nullptr); + + std::string getWeaponAnimation(int weaponType) const; public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); @@ -312,8 +303,6 @@ public: void playSwishSound(float attackStrength); }; - - MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype); } #endif /* GAME_MWMECHANICS_CHARACTER_HPP */ diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 50096cf7b..0bc8a663c 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -311,23 +311,18 @@ namespace MWMechanics applyWerewolfDamageMult(victim, projectile, damage); if (attacker == getPlayer()) - { attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0); - const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence(); - bool unaware = !sequence.isInCombat() - && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim); - - if (unaware) - { - damage *= gmst.find("fCombatCriticalStrikeMult")->mValue.getFloat(); - MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}"); - MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); - } - } - - if (victim.getClass().getCreatureStats(victim).getKnockedDown()) + const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence(); + bool unaware = attacker == getPlayer() && !sequence.isInCombat() + && !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim); + bool knockedDown = victim.getClass().getCreatureStats(victim).getKnockedDown(); + if (knockedDown || unaware) + { damage *= gmst.find("fCombatKODamageMult")->mValue.getFloat(); + if (!knockedDown) + MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f); + } } reduceWeaponCondition(damage, validVictim, weapon, attacker); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index f01b4ea7b..24b06172b 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -490,7 +490,12 @@ namespace MWMechanics bool MechanicsManager::isSneaking(const MWWorld::Ptr& ptr) { - return mActors.isSneaking(ptr); + CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + MWBase::World* world = MWBase::Environment::get().getWorld(); + bool animActive = mActors.isSneaking(ptr); + bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr); + return stanceOn && (animActive || inair); } void MechanicsManager::rest(double hours, bool sleep) @@ -1027,8 +1032,7 @@ namespace MWMechanics return true; // check if a player tries to pickpocket a target NPC - if(ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak) - || target.getClass().getCreatureStats(target).getKnockedDown()) + if (target.getClass().getCreatureStats(target).getKnockedDown() || isSneaking(ptr)) return false; return true; @@ -1679,9 +1683,7 @@ namespace MWMechanics return false; float sneakTerm = 0; - if (ptr.getClass().getCreatureStats(ptr).getStance(CreatureStats::Stance_Sneak) - && !MWBase::Environment::get().getWorld()->isSwimming(ptr) - && MWBase::Environment::get().getWorld()->isOnGround(ptr)) + if (isSneaking(ptr)) { static float fSneakSkillMult = store.find("fSneakSkillMult")->mValue.getFloat(); static float fSneakBootMult = store.find("fSneakBootMult")->mValue.getFloat(); @@ -1689,7 +1691,7 @@ namespace MWMechanics int agility = stats.getAttribute(ESM::Attribute::Agility).getModified(); int luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); float bootWeight = 0; - if (ptr.getClass().isNpc()) + if (ptr.getClass().isNpc() && MWBase::Environment::get().getWorld()->isOnGround(ptr)) { const MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); MWWorld::ConstContainerStoreIterator it = inv.getSlot(MWWorld::InventoryStore::Slot_Boots); diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 63167e302..b8676a883 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -2,8 +2,6 @@ #include -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" @@ -78,10 +76,8 @@ namespace MWMechanics return MWWorld::Ptr(); // none found } - ObstacleCheck::ObstacleCheck(): - mPrevX(0) // to see if the moved since last time - , mPrevY(0) - , mWalkState(State_Norm) + ObstacleCheck::ObstacleCheck() + : mWalkState(State_Norm) , mStuckDuration(0) , mEvadeDuration(0) , mDistSameSpot(-1) // avoid calculating it each time @@ -125,21 +121,15 @@ namespace MWMechanics */ void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration) { - const ESM::Position pos = actor.getRefData().getPosition(); + const osg::Vec3f pos = actor.getRefData().getPosition().asVec3(); if (mDistSameSpot == -1) - { - const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor); - mDistSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor) + 1.2 * std::max(halfExtents.x(), halfExtents.y()); - } + mDistSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor); const float distSameSpot = mDistSameSpot * duration; - const float squaredMovedDistance = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2(); - const bool samePosition = squaredMovedDistance < distSameSpot * distSameSpot; + const bool samePosition = (pos - mPrev).length2() < distSameSpot * distSameSpot; - // update position - mPrevX = pos.pos[0]; - mPrevY = pos.pos[1]; + mPrev = pos; switch(mWalkState) { diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 46c1bc83d..2934ceb1f 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MECHANICS_OBSTACLE_H #define OPENMW_MECHANICS_OBSTACLE_H +#include + namespace MWWorld { class Ptr; @@ -37,9 +39,8 @@ namespace MWMechanics private: - // for checking if we're stuck (ignoring Z axis) - float mPrevX; - float mPrevY; + // for checking if we're stuck + osg::Vec3f mPrev; // directions to try moving in when get stuck static const float evadeDirections[NUM_EVADE_DIRECTIONS][2]; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 4ff28e01a..900f97a67 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -73,6 +73,13 @@ namespace { return sqrDistance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y())); } + + float getPathStepSize(const MWWorld::ConstPtr& actor) + { + const auto world = MWBase::Environment::get().getWorld(); + const auto realHalfExtents = world->getHalfExtents(actor); + return 2 * std::max(realHalfExtents.x(), realHalfExtents.y()); + } } namespace MWMechanics @@ -320,8 +327,7 @@ namespace MWMechanics try { const auto world = MWBase::Environment::get().getWorld(); - const auto realHalfExtents = world->getHalfExtents(actor); // This may differ from halfExtents argument - const auto stepSize = 2 * std::max(realHalfExtents.x(), realHalfExtents.y()); + const auto stepSize = getPathStepSize(actor); const auto navigator = world->getNavigator(); navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, out); } @@ -333,4 +339,39 @@ namespace MWMechanics << DetourNavigator::WriteFlags {flags} << ")"; } } + + void PathFinder::buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, + const DetourNavigator::Flags flags, const float pointTolerance) + { + if (mPath.empty()) + return; + + const auto stepSize = getPathStepSize(actor); + const auto startPoint = actor.getRefData().getPosition().asVec3(); + + if (sqrDistanceIgnoreZ(mPath.front(), startPoint) <= 4 * stepSize * stepSize) + return; + + try + { + const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); + std::deque prePath; + navigator->findPath(halfExtents, stepSize, startPoint, mPath.front(), flags, std::back_inserter(prePath)); + + while (!prePath.empty() && sqrDistanceIgnoreZ(prePath.front(), startPoint) < stepSize * stepSize) + prePath.pop_front(); + + while (!prePath.empty() && sqrDistanceIgnoreZ(prePath.back(), mPath.front()) < stepSize * stepSize) + prePath.pop_back(); + + std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath)); + } + catch (const DetourNavigator::NavigatorException& exception) + { + Log(Debug::Debug) << "Build path by navigator exception: \"" << exception.what() + << "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase() + << ") from " << startPoint << " to " << mPath.front() << " with flags (" + << DetourNavigator::WriteFlags {flags} << ")"; + } + } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 1dc85c5e5..f762b6f18 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -84,6 +84,9 @@ namespace MWMechanics const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags); + void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents, + const DetourNavigator::Flags flags, const float pointTolerance); + /// Remove front point if exist and within tolerance void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 04abb5344..bbbf43b71 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -43,6 +43,7 @@ #include "npcstats.hpp" #include "actorutil.hpp" #include "aifollow.hpp" +#include "weapontype.hpp" namespace MWMechanics { @@ -258,7 +259,7 @@ namespace MWMechanics float castChance = 100.f; if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor()) { - castChance = getSpellSuccessChance(spell, caster, nullptr, false); // Uncapped casting chance + castChance = getSpellSuccessChance(spell, caster, nullptr, false, false); // Uncapped casting chance } if (castChance > 0) x *= 50 / castChance; @@ -936,18 +937,23 @@ namespace MWMechanics mStack = false; bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); + bool isProjectile = false; + if (item.getTypeName() == typeid(ESM::Weapon).name()) + { + int type = item.get()->mBase->mData.mType; + ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass; + isProjectile = (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ranged); + } + int type = enchantment->mData.mType; // Check if there's enough charge left - if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + if (!godmode && (type == ESM::Enchantment::WhenUsed || (!isProjectile && type == ESM::Enchantment::WhenStrikes))) { int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), mCaster); if (item.getCellRef().getEnchantmentCharge() == -1) item.getCellRef().setEnchantmentCharge(static_cast(enchantment->mData.mCharge)); - if (godmode) - castCost = 0; - if (item.getCellRef().getEnchantmentCharge() < castCost) { if (mCaster == getPlayer()) @@ -975,17 +981,17 @@ namespace MWMechanics item.getCellRef().setEnchantmentCharge(item.getCellRef().getEnchantmentCharge() - castCost); } - if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) + if (type == ESM::Enchantment::WhenUsed) { if (mCaster == getPlayer()) mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1); } - else if (enchantment->mData.mType == ESM::Enchantment::CastOnce) + else if (type == ESM::Enchantment::CastOnce) { if (!godmode) item.getContainerStore()->remove(item, 1, mCaster); } - else if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + else if (type == ESM::Enchantment::WhenStrikes) { if (mCaster == getPlayer()) mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3); @@ -993,13 +999,6 @@ namespace MWMechanics inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); - bool isProjectile = false; - if (item.getTypeName() == typeid(ESM::Weapon).name()) - { - const MWWorld::LiveCellRef *ref = item.get(); - isProjectile = ref->mBase->mData.mType == ESM::Weapon::Arrow || ref->mBase->mData.mType == ESM::Weapon::Bolt || ref->mBase->mData.mType == ESM::Weapon::MarksmanThrown; - } - if (isProjectile || !mTarget.isEmpty()) inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 8c4b50fe2..5f0e4a1cf 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -39,8 +39,8 @@ namespace MWMechanics * @note actor can be an NPC or a creature * @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned. */ - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=false); - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=false); + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true); + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true); int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor); int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 6cda51ac8..7b5c38592 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -16,6 +16,8 @@ #include "creaturestats.hpp" #include "spellcasting.hpp" +#include "weapontype.hpp" +#include "combat.hpp" namespace { @@ -122,9 +124,6 @@ namespace MWMechanics return 0.f; } - if (spell->mData.mCost > stats.getMagicka().getCurrent()) - return 0.f; - // Spells don't stack, so early out if the spell is still active on the target int types = getRangeTypes(spell->mEffects); if ((types & Self) && stats.getActiveSpells().isSpellActive(spell->mId)) @@ -379,7 +378,7 @@ namespace MWMechanics case ESM::MagicEffect::BoundLongbow: // AI should not summon the bow if there is no suitable ammo. - if (rateAmmo(actor, enemy, ESM::Weapon::Arrow) <= 0.f) + if (rateAmmo(actor, enemy, getWeaponType(ESM::Weapon::MarksmanBow)->mAmmoType) <= 0.f) return 0.f; break; diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp index 1ef46e1ae..b08a90220 100644 --- a/apps/openmw/mwmechanics/steering.cpp +++ b/apps/openmw/mwmechanics/steering.cpp @@ -32,7 +32,7 @@ bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, f if (absDiff < epsilonRadians) return true; - float limit = MAX_VEL_ANGULAR_RADIANS * MWBase::Environment::get().getFrameDuration(); + float limit = getAngularVelocity(actor.getClass().getSpeed(actor)) * MWBase::Environment::get().getFrameDuration(); if (absDiff > limit) diff = osg::sign(diff) * limit; diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp index 632ab3611..f305a6961 100644 --- a/apps/openmw/mwmechanics/steering.hpp +++ b/apps/openmw/mwmechanics/steering.hpp @@ -3,6 +3,8 @@ #include +#include + namespace MWWorld { class Ptr; @@ -12,7 +14,12 @@ namespace MWMechanics { // Max rotating speed, radian/sec -const float MAX_VEL_ANGULAR_RADIANS(10); +inline float getAngularVelocity(const float actorSpeed) +{ + const float baseAngluarVelocity = 10; + const float baseSpeed = 200; + return baseAngluarVelocity * std::max(actorSpeed / baseSpeed, 1.0f); +} /// configure rotation settings for an actor to reach this target angle (eventually) /// @return have we reached the target angle? diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 118c0297f..2e6501225 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -14,6 +14,7 @@ #include "aicombataction.hpp" #include "spellpriority.hpp" #include "spellcasting.hpp" +#include "weapontype.hpp" namespace MWMechanics { @@ -34,14 +35,15 @@ namespace MWMechanics const MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::Store& gmst = world->getStore().get(); - if (type == -1 && (weapon->mData.mType == ESM::Weapon::Arrow || weapon->mData.mType == ESM::Weapon::Bolt)) + ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(weapon->mData.mType)->mWeaponClass; + if (type == -1 && weapclass == ESM::WeaponType::Ammo) return 0.f; float rating=0.f; static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat(); float ratingMult = fAIMeleeWeaponMult; - if (weapon->mData.mType >= ESM::Weapon::MarksmanBow && weapon->mData.mType <= ESM::Weapon::MarksmanThrown) + if (weapclass != ESM::WeaponType::Melee) { // Underwater ranged combat is impossible if (world->isUnderwater(MWWorld::ConstPtr(actor), 0.75f) @@ -59,11 +61,11 @@ namespace MWMechanics const float chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f; // We need to account for the fact that thrown weapons have 2x real damage applied to the target // as they're both the weapon and the ammo of the hit - if (weapon->mData.mType == ESM::Weapon::MarksmanThrown) + if (weapclass == ESM::WeaponType::Thrown) { rating = chop * 2; } - else if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) + else if (weapclass != ESM::WeaponType::Melee) { rating = chop; } @@ -76,24 +78,28 @@ namespace MWMechanics adjustWeaponDamage(rating, item, actor); - if (weapon->mData.mType != ESM::Weapon::MarksmanBow && weapon->mData.mType != ESM::Weapon::MarksmanCrossbow) + if (weapclass != ESM::WeaponType::Ranged) { resistNormalWeapon(enemy, actor, item, rating); applyWerewolfDamageMult(enemy, item, rating); } - else if (weapon->mData.mType == ESM::Weapon::MarksmanBow) + else { - if (arrowRating <= 0.f) - rating = 0.f; - else - rating += arrowRating; - } - else if (weapon->mData.mType == ESM::Weapon::MarksmanCrossbow) - { - if (boltRating <= 0.f) - rating = 0.f; - else - rating += boltRating; + int ammotype = MWMechanics::getWeaponType(weapon->mData.mType)->mAmmoType; + if (ammotype == ESM::Weapon::Arrow) + { + if (arrowRating <= 0.f) + rating = 0.f; + else + rating += arrowRating; + } + else if (ammotype == ESM::Weapon::Bolt) + { + if (boltRating <= 0.f) + rating = 0.f; + else + rating += boltRating; + } } if (!weapon->mEnchant.empty()) @@ -102,8 +108,9 @@ namespace MWMechanics if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); + float charge = item.getCellRef().getEnchantmentCharge(); - if (item.getCellRef().getEnchantmentCharge() == -1 || item.getCellRef().getEnchantmentCharge() >= castCost) + if (charge == -1 || charge >= castCost || weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo) rating += rateEffects(enchantment->mEffects, actor, enemy); } } @@ -125,13 +132,13 @@ namespace MWMechanics float chance = getHitChance(actor, enemy, value) / 100.f; rating *= std::min(1.f, std::max(0.01f, chance)); - if (weapon->mData.mType < ESM::Weapon::Arrow) + if (weapclass != ESM::WeaponType::Ammo) rating *= weapon->mData.mSpeed; return rating * ratingMult; } - float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType) + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, int ammoType) { float bestAmmoRating = 0.f; if (!actor.getClass().hasInventoryStore(actor)) @@ -152,7 +159,7 @@ namespace MWMechanics return bestAmmoRating; } - float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, ESM::Weapon::Type ammoType) + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, int ammoType) { MWWorld::Ptr emptyPtr; return rateAmmo(actor, enemy, emptyPtr, ammoType); @@ -174,8 +181,8 @@ namespace MWMechanics float bonusDamage = 0.f; const ESM::Weapon* esmWeap = weapon.get()->mBase; - - if (esmWeap->mData.mType >= ESM::Weapon::MarksmanBow) + int type = esmWeap->mData.mType; + if (getWeaponType(type)->mWeaponClass != ESM::WeaponType::Melee) { if (!ammo.isEmpty() && !MWBase::Environment::get().getWorld()->isSwimming(enemy)) { diff --git a/apps/openmw/mwmechanics/weaponpriority.hpp b/apps/openmw/mwmechanics/weaponpriority.hpp index f5c9a1159..67de7b50f 100644 --- a/apps/openmw/mwmechanics/weaponpriority.hpp +++ b/apps/openmw/mwmechanics/weaponpriority.hpp @@ -10,8 +10,8 @@ namespace MWMechanics float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, int type=-1, float arrowRating=0.f, float boltRating=0.f); - float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType); - float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, ESM::Weapon::Type ammoType); + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, int ammoType); + float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, int ammoType); float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); } diff --git a/apps/openmw/mwmechanics/weapontype.cpp b/apps/openmw/mwmechanics/weapontype.cpp new file mode 100644 index 000000000..07345557f --- /dev/null +++ b/apps/openmw/mwmechanics/weapontype.cpp @@ -0,0 +1,53 @@ +#include "weapontype.hpp" + +#include "../mwworld/class.hpp" + +namespace MWMechanics +{ + static const ESM::WeaponType *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])]; + + MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype) + { + MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor); + CreatureStats &stats = actor.getClass().getCreatureStats(actor); + if(stats.getDrawState() == MWMechanics::DrawState_Spell) + { + *weaptype = ESM::Weapon::Spell; + return inv.end(); + } + + if(stats.getDrawState() == MWMechanics::DrawState_Weapon) + { + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon == inv.end()) + *weaptype = ESM::Weapon::HandToHand; + else + { + const std::string &type = weapon->getTypeName(); + if(type == typeid(ESM::Weapon).name()) + { + const MWWorld::LiveCellRef *ref = weapon->get(); + *weaptype = ref->mBase->mData.mType; + } + else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) + *weaptype = ESM::Weapon::PickProbe; + } + + return weapon; + } + + return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + } + + const ESM::WeaponType* getWeaponType(const int weaponType) + { + std::map::const_iterator found = sWeaponTypeList.find(weaponType); + if (found == sWeaponTypeList.end()) + { + // Use one-handed short blades as fallback + return &sWeaponTypeList[0]; + } + + return &found->second; + } +} diff --git a/apps/openmw/mwmechanics/weapontype.hpp b/apps/openmw/mwmechanics/weapontype.hpp new file mode 100644 index 000000000..32e321d45 --- /dev/null +++ b/apps/openmw/mwmechanics/weapontype.hpp @@ -0,0 +1,271 @@ +#ifndef GAME_MWMECHANICS_WEAPONTYPE_H +#define GAME_MWMECHANICS_WEAPONTYPE_H + +#include "../mwworld/inventorystore.hpp" + +#include "creaturestats.hpp" + +namespace MWMechanics +{ + static std::map sWeaponTypeList = + { + { + ESM::Weapon::None, + { + /* short group */ "", + /* long group */ "", + /* sound ID */ "", + /* attach bone */ "", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::HandToHand, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 + } + }, + { + ESM::Weapon::PickProbe, + { + /* short group */ "1h", + /* long group */ "pickprobe", + /* sound ID */ "", + /* attach bone */ "", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::Security, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 + } + }, + { + ESM::Weapon::Spell, + { + /* short group */ "spell", + /* long group */ "spellcast", + /* sound ID */ "", + /* attach bone */ "", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::HandToHand, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::HandToHand, + { + /* short group */ "hh", + /* long group */ "handtohand", + /* sound ID */ "", + /* attach bone */ "", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::HandToHand, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::ShortBladeOneHand, + { + /* short group */ "1s", + /* long group */ "shortbladeonehand", + /* sound ID */ "Item Weapon Shortblade", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 ShortBladeOneHand", + /* usage skill */ ESM::Skill::ShortBlade, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth + } + }, + { + ESM::Weapon::LongBladeOneHand, + { + /* short group */ "1h", + /* long group */ "weapononehand", + /* sound ID */ "Item Weapon Longblade", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 LongBladeOneHand", + /* usage skill */ ESM::Skill::LongBlade, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth + } + }, + { + ESM::Weapon::BluntOneHand, + { + /* short group */ "1b", + /* long group */ "bluntonehand", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 BluntOneHand", + /* usage skill */ ESM::Skill::BluntWeapon, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth + } + }, + { + ESM::Weapon::AxeOneHand, + { + /* short group */ "1b", + /* long group */ "bluntonehand", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 LongBladeOneHand", + /* usage skill */ ESM::Skill::Axe, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth + } + }, + { + ESM::Weapon::LongBladeTwoHand, + { + /* short group */ "2c", + /* long group */ "weapontwohand", + /* sound ID */ "Item Weapon Longblade", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 LongBladeTwoClose", + /* usage skill */ ESM::Skill::LongBlade, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::AxeTwoHand, + { + /* short group */ "2b", + /* long group */ "blunttwohand", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 AxeTwoClose", + /* usage skill */ ESM::Skill::Axe, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::BluntTwoClose, + { + /* short group */ "2b", + /* long group */ "blunttwohand", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 BluntTwoClose", + /* usage skill */ ESM::Skill::BluntWeapon, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::BluntTwoWide, + { + /* short group */ "2w", + /* long group */ "weapontwowide", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 BluntTwoWide", + /* usage skill */ ESM::Skill::BluntWeapon, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::SpearTwoWide, + { + /* short group */ "2w", + /* long group */ "weapontwowide", + /* sound ID */ "Item Weapon Spear", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 SpearTwoWide", + /* usage skill */ ESM::Skill::Spear, + /* weapon class*/ ESM::WeaponType::Melee, + /* ammo type */ ESM::Weapon::None, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::MarksmanBow, + { + /* short group */ "bow", + /* long group */ "bowandarrow", + /* sound ID */ "Item Weapon Bow", + /* attach bone */ "Weapon Bone Left", + /* sheath bone */ "Bip01 MarksmanBow", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Ranged, + /* ammo type */ ESM::Weapon::Arrow, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::MarksmanCrossbow, + { + /* short group */ "crossbow", + /* long group */ "crossbow", + /* sound ID */ "Item Weapon Crossbow", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 MarksmanCrossbow", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Ranged, + /* ammo type */ ESM::Weapon::Bolt, + /* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded + } + }, + { + ESM::Weapon::MarksmanThrown, + { + /* short group */ "1t", + /* long group */ "throwweapon", + /* sound ID */ "Item Weapon Blunt", + /* attach bone */ "Weapon Bone", + /* sheath bone */ "Bip01 MarksmanThrown", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Thrown, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 + } + }, + { + ESM::Weapon::Arrow, + { + /* short group */ "", + /* long group */ "", + /* sound ID */ "Item Ammo", + /* attach bone */ "ArrowBone", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Ammo, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 + } + }, + { + ESM::Weapon::Bolt, + { + /* short group */ "", + /* long group */ "", + /* sound ID */ "Item Ammo", + /* attach bone */ "ArrowBone", + /* sheath bone */ "", + /* usage skill */ ESM::Skill::Marksman, + /* weapon class*/ ESM::WeaponType::Ammo, + /* ammo type */ ESM::Weapon::None, + /* flags */ 0 + } + } + }; + + MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype); + + const ESM::WeaponType* getWeaponType(const int weaponType); +} + +#endif diff --git a/apps/openmw/mwmp/MechanicsHelper.cpp b/apps/openmw/mwmp/MechanicsHelper.cpp index 66dc6c961..6f754fbc4 100644 --- a/apps/openmw/mwmp/MechanicsHelper.cpp +++ b/apps/openmw/mwmp/MechanicsHelper.cpp @@ -235,7 +235,7 @@ void MechanicsHelper::resetAttack(Attack* attack) bool MechanicsHelper::getSpellSuccess(std::string spellId, const MWWorld::Ptr& caster) { - return Misc::Rng::roll0to99() < MWMechanics::getSpellSuccessChance(spellId, caster); + return Misc::Rng::roll0to99() < MWMechanics::getSpellSuccessChance(spellId, caster, nullptr, true, false); } void MechanicsHelper::processAttack(Attack attack, const MWWorld::Ptr& attacker) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index d00e0c8cd..efb0a26ce 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -34,6 +34,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/player.hpp" #include "../mwrender/bulletdebugdraw.hpp" @@ -326,7 +327,30 @@ namespace MWPhysics if (movement.z() > 0 && ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel) velocity = osg::Vec3f(0,0,1) * 25; - ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; + if (ptr.getClass().getMovementSettings(ptr).mPosition[2]) + { + const bool isPlayer = (ptr == MWMechanics::getPlayer()); + // Advance acrobatics and set flag for GetPCJumping + if (isPlayer) + { + ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0); + MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); + } + + // Decrease fatigue + if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState()) + { + const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat(); + const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat(); + const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr)); + const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult; + MWMechanics::DynamicStat fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue(); + fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); + ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue); + } + ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; + } // Now that we have the effective movement vector, apply wind forces to it if (MWBase::Environment::get().getWorld()->isInStorm()) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index c54e28426..abc2e4eb3 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -28,6 +28,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/weapontype.hpp" #include "vismask.hpp" @@ -51,8 +52,6 @@ ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr // Make sure we cleaned object from effects, just in cast if we re-use node removeEffects(); - - mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); } ActorAnimation::~ActorAnimation() @@ -79,12 +78,12 @@ PartHolderPtr ActorAnimation::getWeaponPart(const std::string& model, const std: return PartHolderPtr(); if (enchantedGlow) - addGlow(instance, *glowColor); + mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor); return PartHolderPtr(new PartHolder(instance)); } -osg::Group* ActorAnimation::getBoneByName(std::string boneName) +osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) { if (!mObjectRoot) return nullptr; @@ -105,93 +104,13 @@ std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& if(type == typeid(ESM::Weapon).name()) { const MWWorld::LiveCellRef *ref = weapon.get(); - ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; - return getHolsteredWeaponBoneName(weaponType); + int weaponType = ref->mBase->mData.mType; + return MWMechanics::getWeaponType(weaponType)->mSheathingBone; } return boneName; } -std::string ActorAnimation::getHolsteredWeaponBoneName(const unsigned int weaponType) -{ - std::string boneName; - - switch(weaponType) - { - case ESM::Weapon::ShortBladeOneHand: - boneName = "Bip01 ShortBladeOneHand"; - break; - case ESM::Weapon::LongBladeOneHand: - boneName = "Bip01 LongBladeOneHand"; - break; - case ESM::Weapon::BluntOneHand: - boneName = "Bip01 BluntOneHand"; - break; - case ESM::Weapon::AxeOneHand: - boneName = "Bip01 LongBladeOneHand"; - break; - case ESM::Weapon::LongBladeTwoHand: - boneName = "Bip01 LongBladeTwoClose"; - break; - case ESM::Weapon::BluntTwoClose: - boneName = "Bip01 BluntTwoClose"; - break; - case ESM::Weapon::AxeTwoHand: - boneName = "Bip01 AxeTwoClose"; - break; - case ESM::Weapon::BluntTwoWide: - boneName = "Bip01 BluntTwoWide"; - break; - case ESM::Weapon::SpearTwoWide: - boneName = "Bip01 SpearTwoWide"; - break; - case ESM::Weapon::MarksmanBow: - boneName = "Bip01 MarksmanBow"; - break; - case ESM::Weapon::MarksmanCrossbow: - boneName = "Bip01 MarksmanCrossbow"; - break; - case ESM::Weapon::MarksmanThrown: - boneName = "Bip01 MarksmanThrown"; - break; - default: - break; - } - - return boneName; -} - -void ActorAnimation::injectWeaponBones() -{ - if (!mResourceSystem->getVFS()->exists("meshes\\xbase_anim_sh.nif")) - { - mWeaponSheathing = false; - return; - } - - osg::ref_ptr sheathSkeleton = mResourceSystem->getSceneManager()->getInstance("meshes\\xbase_anim_sh.nif"); - - for (unsigned int type=0; type<=ESM::Weapon::MarksmanThrown; ++type) - { - const std::string holsteredBoneName = getHolsteredWeaponBoneName(type); - - SceneUtil::FindByNameVisitor findVisitor (holsteredBoneName); - sheathSkeleton->accept(findVisitor); - osg::ref_ptr sheathNode = findVisitor.mFoundNode; - - if (sheathNode && sheathNode.get()->getNumParents()) - { - osg::Group* sheathParent = getBoneByName(sheathNode.get()->getParent(0)->getName()); - - if (sheathParent) - { - sheathNode.get()->getParent(0)->removeChild(sheathNode); - sheathParent->addChild(sheathNode); - } - } - } -} - void ActorAnimation::resetControllers(osg::Node* node) { if (node == nullptr) @@ -205,7 +124,8 @@ void ActorAnimation::resetControllers(osg::Node* node) void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) { - if (!mWeaponSheathing) + static const bool weaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); + if (!weaponSheathing) return; if (!mPtr.getClass().hasInventoryStore(mPtr)) @@ -219,7 +139,8 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) return; // Since throwing weapons stack themselves, do not show such weapon itself - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + int type = weapon->get()->mBase->mData.mType; + if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) showHolsteredWeapons = false; std::string mesh = weapon->getClass().getModel(*weapon); @@ -238,7 +159,7 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) { if (showHolsteredWeapons) { - osg::Vec4f glowColor = getEnchantmentColor(*weapon); + osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor); if (mScabbard) resetControllers(mScabbard->getNode()); @@ -271,15 +192,16 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons) if (isEnchanted) { - osg::Vec4f glowColor = getEnchantmentColor(*weapon); - addGlow(weaponNode, glowColor); + osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); + mGlowUpdater = SceneUtil::addEnchantedGlow(weaponNode, mResourceSystem, glowColor); } } } void ActorAnimation::updateQuiver() { - if (!mWeaponSheathing) + static const bool weaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); + if (!weaponSheathing) return; if (!mPtr.getClass().hasInventoryStore(mPtr)) @@ -303,10 +225,12 @@ void ActorAnimation::updateQuiver() bool suitableAmmo = false; MWWorld::ConstContainerStoreIterator ammo = weapon; unsigned int ammoCount = 0; - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + int type = weapon->get()->mBase->mData.mType; + const auto& weaponType = MWMechanics::getWeaponType(type); + if (weaponType->mWeaponClass == ESM::WeaponType::Thrown) { ammoCount = ammo->getRefData().getCount(); - osg::Group* throwingWeaponNode = getBoneByName("Weapon Bone"); + osg::Group* throwingWeaponNode = getBoneByName(weaponType->mAttachBone); if (throwingWeaponNode && throwingWeaponNode->getNumChildren()) ammoCount--; @@ -323,10 +247,7 @@ void ActorAnimation::updateQuiver() if (arrowAttached) ammoCount--; - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) - suitableAmmo = ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt; - else if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanBow) - suitableAmmo = ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow; + suitableAmmo = ammo->get()->mBase->mData.mType == weaponType->mAmmoType; } if (!suitableAmmo) @@ -347,14 +268,14 @@ void ActorAnimation::updateQuiver() } // Add new ones - osg::Vec4f glowColor = getEnchantmentColor(*ammo); + osg::Vec4f glowColor = ammo->getClass().getEnchantmentColor(*ammo); std::string model = ammo->getClass().getModel(*ammo); for (unsigned int i=0; i arrowNode = ammoNode->getChild(i)->asGroup(); osg::ref_ptr arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode); if (!ammo->getClass().getEnchantment(*ammo).empty()) - addGlow(arrow, glowColor); + mGlowUpdater = SceneUtil::addEnchantedGlow(arrow, mResourceSystem, glowColor); } } @@ -379,7 +300,8 @@ void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/) return; MWWorld::ConstContainerStoreIterator ammo = inv.end(); - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + int type = weapon->get()->mBase->mData.mType; + if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) ammo = weapon; else ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); @@ -412,7 +334,8 @@ void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/) return; MWWorld::ConstContainerStoreIterator ammo = inv.end(); - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + int type = weapon->get()->mBase->mData.mType; + if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown) ammo = weapon; else ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index 2146a0260..f1f6f6ca8 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -40,13 +40,10 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener virtual bool isArrowAttached() const { return false; } protected: - bool mWeaponSheathing; - osg::Group* getBoneByName(std::string boneName); + osg::Group* getBoneByName(const std::string& boneName); virtual void updateHolsteredWeapon(bool showHolsteredWeapons); - virtual void injectWeaponBones(); virtual void updateQuiver(); virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); - virtual std::string getHolsteredWeaponBoneName(const unsigned int weaponType); virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor); virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e341778ac..56c3846af 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -3,8 +3,6 @@ #include #include -#include -#include #include #include #include @@ -16,18 +14,18 @@ #include -#include #include #include -#include #include +#include #include // KeyframeHolder #include #include +#include #include #include #include @@ -238,6 +236,28 @@ namespace std::vector > mToRemove; }; + class GetExtendedBonesVisitor : public osg::NodeVisitor + { + public: + GetExtendedBonesVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + void apply(osg::Node& node) + { + if (SceneUtil::hasUserDescription(&node, "CustomBone")) + { + mFoundBones.emplace_back(&node, node.getParent(0)); + return; + } + + traverse(node); + } + + std::vector > mFoundBones; + }; + class RemoveFinishedCallbackVisitor : public RemoveVisitor { public: @@ -519,135 +539,6 @@ namespace MWRender float mAlpha; }; - class GlowUpdater : public SceneUtil::StateSetUpdater - { - public: - GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector >& textures, - osg::Node* node, float duration, Resource::ResourceSystem* resourcesystem) - : mTexUnit(texUnit) - , mColor(color) - , mOriginalColor(color) - , mTextures(textures) - , mNode(node) - , mDuration(duration) - , mOriginalDuration(duration) - , mStartingTime(0) - , mResourceSystem(resourcesystem) - , mColorChanged(false) - , mDone(false) - { - } - - virtual void setDefaults(osg::StateSet *stateset) - { - if (mDone) - removeTexture(stateset); - else - { - stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); - osg::TexGen* texGen = new osg::TexGen; - texGen->setMode(osg::TexGen::SPHERE_MAP); - - stateset->setTextureAttributeAndModes(mTexUnit, texGen, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; - texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); - texEnv->setConstantColor(mColor); - texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); - texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); - texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); - - stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("envMapColor", mColor)); - } - } - - void removeTexture(osg::StateSet* stateset) - { - stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXTURE); - stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXGEN); - stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXENV); - stateset->removeTextureMode(mTexUnit, GL_TEXTURE_2D); - stateset->removeUniform("envMapColor"); - - osg::StateSet::TextureAttributeList& list = stateset->getTextureAttributeList(); - while (list.size() && list.rbegin()->empty()) - list.pop_back(); - } - - virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) - { - if (mColorChanged){ - this->reset(); - setDefaults(stateset); - mColorChanged = false; - } - if (mDone) - return; - - // Set the starting time to measure glow duration from if this is a temporary glow - if ((mDuration >= 0) && mStartingTime == 0) - mStartingTime = nv->getFrameStamp()->getSimulationTime(); - - float time = nv->getFrameStamp()->getSimulationTime(); - int index = (int)(time*16) % mTextures.size(); - stateset->setTextureAttribute(mTexUnit, mTextures[index], osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); - - if ((mDuration >= 0) && (time - mStartingTime > mDuration)) // If this is a temporary glow and it has finished its duration - { - if (mOriginalDuration >= 0) // if this glowupdater was a temporary glow since its creation - { - removeTexture(stateset); - this->reset(); - mDone = true; - mResourceSystem->getSceneManager()->recreateShaders(mNode); - } - if (mOriginalDuration < 0) // if this glowupdater was originally a permanent glow - { - mDuration = mOriginalDuration; - mStartingTime = 0; - mColor = mOriginalColor; - this->reset(); - setDefaults(stateset); - } - } - } - - bool isPermanentGlowUpdater() - { - return (mDuration < 0); - } - - bool isDone() - { - return mDone; - } - - void setColor(const osg::Vec4f& color) - { - mColor = color; - mColorChanged = true; - } - - void setDuration(float duration) - { - mDuration = duration; - } - - private: - int mTexUnit; - osg::Vec4f mColor; - osg::Vec4f mOriginalColor; // for restoring the color of a permanent glow after a temporary glow on the object finishes - std::vector > mTextures; - osg::Node* mNode; - float mDuration; - float mOriginalDuration; // for recording that this is originally a permanent glow if it is changed to a temporary one - float mStartingTime; - Resource::ResourceSystem* mResourceSystem; - bool mColorChanged; - bool mDone; - }; - struct Animation::AnimSource { osg::ref_ptr mKeyframes; @@ -1469,8 +1360,62 @@ namespace MWRender state->second.mLoopingEnabled = enabled; } - osg::ref_ptr getModelInstance(Resource::SceneManager* sceneMgr, const std::string& model, bool baseonly) + void loadBonesFromFile(osg::ref_ptr& baseNode, const std::string &model, Resource::ResourceSystem* resourceSystem) { + const osg::Node* node = resourceSystem->getSceneManager()->getTemplate(model).get(); + osg::ref_ptr sheathSkeleton (const_cast(node)); // const-trickery required because there is no const version of NodeVisitor + + GetExtendedBonesVisitor getBonesVisitor; + sheathSkeleton->accept(getBonesVisitor); + for (auto& nodePair : getBonesVisitor.mFoundBones) + { + SceneUtil::FindByNameVisitor findVisitor (nodePair.second->getName()); + baseNode->accept(findVisitor); + + osg::Group* sheathParent = findVisitor.mFoundNode; + if (sheathParent) + { + osg::Node* copy = osg::clone(nodePair.first, osg::CopyOp::DEEP_COPY_NODES); + sheathParent->addChild(copy); + } + } + } + + void injectCustomBones(osg::ref_ptr& node, const std::string& model, Resource::ResourceSystem* resourceSystem) + { + if (model.empty()) + return; + + const std::map& index = resourceSystem->getVFS()->getIndex(); + + std::string animationPath = model; + if (animationPath.find("meshes") == 0) + { + animationPath.replace(0, 6, "animations"); + } + animationPath.replace(animationPath.size()-4, 4, "/"); + + resourceSystem->getVFS()->normalizeFilename(animationPath); + + std::map::const_iterator found = index.lower_bound(animationPath); + while (found != index.end()) + { + const std::string& name = found->first; + if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath) + { + size_t pos = name.find_last_of('.'); + if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".nif") == 0) + loadBonesFromFile(node, name, resourceSystem); + } + else + break; + ++found; + } + } + + osg::ref_ptr getModelInstance(Resource::ResourceSystem* resourceSystem, const std::string& model, bool baseonly, bool inject, const std::string& defaultSkeleton) + { + Resource::SceneManager* sceneMgr = resourceSystem->getSceneManager(); if (baseonly) { typedef std::map > Cache; @@ -1480,6 +1425,12 @@ namespace MWRender { osg::ref_ptr created = sceneMgr->getInstance(model); + if (inject) + { + injectCustomBones(created, defaultSkeleton, resourceSystem); + injectCustomBones(created, model, resourceSystem); + } + SceneUtil::CleanObjectRootVisitor removeDrawableVisitor; created->accept(removeDrawableVisitor); removeDrawableVisitor.remove(); @@ -1492,7 +1443,17 @@ namespace MWRender return sceneMgr->createInstance(found->second); } else - return sceneMgr->getInstance(model); + { + osg::ref_ptr created = sceneMgr->getInstance(model); + + if (inject) + { + injectCustomBones(created, defaultSkeleton, resourceSystem); + injectCustomBones(created, model, resourceSystem); + } + + return created; + } } void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature) @@ -1514,9 +1475,45 @@ namespace MWRender mAccumRoot = nullptr; mAccumCtrl = nullptr; + static const bool useAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game"); + std::string defaultSkeleton; + bool inject = false; + + if (useAdditionalSources && mPtr.getClass().isActor()) + { + if (isCreature) + { + MWWorld::LiveCellRef *ref = mPtr.get(); + if(ref->mBase->mFlags & ESM::Creature::Bipedal) + { + defaultSkeleton = "meshes\\xbase_anim.nif"; + inject = true; + } + } + else + { + inject = true; + MWWorld::LiveCellRef *ref = mPtr.get(); + if (!ref->mBase->mModel.empty()) + { + // If NPC has a custom animation model attached, we should inject bones from default skeleton for given race and gender as well + // Since it is a quite rare case, there should not be a noticable performance loss + // Note: consider that player and werewolves have no custom animation files attached for now + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Race *race = store.get().find(ref->mBase->mRace); + + bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; + bool isFemale = !ref->mBase->isMale(); + + defaultSkeleton = SceneUtil::getActorSkeleton(false, isFemale, isBeast, false); + defaultSkeleton = Misc::ResourceHelpers::correctActorModelPath(defaultSkeleton, mResourceSystem->getVFS()); + } + } + } + if (!forceskeleton) { - osg::ref_ptr created = getModelInstance(mResourceSystem->getSceneManager(), model, baseonly); + osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); mInsert->addChild(created); mObjectRoot = created->asGroup(); if (!mObjectRoot) @@ -1532,7 +1529,7 @@ namespace MWRender } else { - osg::ref_ptr created = getModelInstance(mResourceSystem->getSceneManager(), model, baseonly); + osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); osg::ref_ptr skel = dynamic_cast(created.get()); if (!skel) { @@ -1574,25 +1571,6 @@ namespace MWRender return mObjectRoot.get(); } - class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor - { - public: - FindLowestUnusedTexUnitVisitor() - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mLowestUnusedTexUnit(0) - { - } - - virtual void apply(osg::Node& node) - { - if (osg::StateSet* stateset = node.getStateSet()) - mLowestUnusedTexUnit = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size())); - - traverse(node); - } - int mLowestUnusedTexUnit; - }; - void Animation::addSpellCastGlow(const ESM::MagicEffect *effect, float glowDuration) { osg::Vec4f glowColor(1,1,1,1); @@ -1611,78 +1589,10 @@ namespace MWRender mGlowUpdater->setDuration(glowDuration); } else - addGlow(mObjectRoot, glowColor, glowDuration); + mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, glowColor, glowDuration); } } - void Animation::addGlow(osg::ref_ptr node, osg::Vec4f glowColor, float glowDuration) - { - std::vector > textures; - for (int i=0; i<32; ++i) - { - std::stringstream stream; - stream << "textures/magicitem/caust"; - stream << std::setw(2); - stream << std::setfill('0'); - stream << i; - stream << ".dds"; - - osg::ref_ptr image = mResourceSystem->getImageManager()->getImage(stream.str()); - osg::ref_ptr tex (new osg::Texture2D(image)); - tex->setName("envMap"); - tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); - tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); - mResourceSystem->getSceneManager()->applyFilterSettings(tex); - textures.push_back(tex); - } - - FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; - node->accept(findLowestUnusedTexUnitVisitor); - int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; - - osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, mResourceSystem); - mGlowUpdater = glowUpdater; - node->addUpdateCallback(glowUpdater); - - // set a texture now so that the ShaderVisitor can find it - osg::ref_ptr writableStateSet = nullptr; - if (!node->getStateSet()) - writableStateSet = node->getOrCreateStateSet(); - else - { - writableStateSet = new osg::StateSet(*node->getStateSet(), osg::CopyOp::SHALLOW_COPY); - node->setStateSet(writableStateSet); - } - writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); - writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); - mResourceSystem->getSceneManager()->recreateShaders(node); - } - - // TODO: Should not be here - osg::Vec4f Animation::getEnchantmentColor(const MWWorld::ConstPtr& item) const - { - osg::Vec4f result(1,1,1,1); - std::string enchantmentName = item.getClass().getEnchantment(item); - if (enchantmentName.empty()) - return result; - - const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().search(enchantmentName); - if (!enchantment) - return result; - - assert (enchantment->mEffects.mList.size()); - - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().search( - enchantment->mEffects.mList.front().mEffectID); - if (!magicEffect) - return result; - - result.x() = magicEffect->mData.mRed / 255.f; - result.y() = magicEffect->mData.mGreen / 255.f; - result.z() = magicEffect->mData.mBlue / 255.f; - return result; - } - void Animation::addExtraLight(osg::ref_ptr parent, const ESM::Light *esmLight) { bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior(); @@ -2001,7 +1911,7 @@ namespace MWRender addAnimSource(model, model); if (!ptr.getClass().getEnchantment(ptr).empty()) - addGlow(mObjectRoot, getEnchantmentColor(ptr)); + mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); } if (ptr.getTypeName() == typeid(ESM::Light).name() && allowLight) addExtraLight(getOrCreateObjectRoot(), ptr.get()->mBase); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 95007d888..763c7a917 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -4,6 +4,7 @@ #include "../mwworld/ptr.hpp" #include +#include namespace ESM { @@ -34,7 +35,6 @@ namespace MWRender class ResetAccumRootCallback; class RotateController; -class GlowUpdater; class TransparencyUpdater; class EffectAnimationTime : public SceneUtil::ControllerSource @@ -266,7 +266,7 @@ protected: bool mHasMagicEffects; osg::ref_ptr mGlowLight; - osg::ref_ptr mGlowUpdater; + osg::ref_ptr mGlowUpdater; osg::ref_ptr mTransparencyUpdater; float mAlpha; @@ -330,10 +330,6 @@ protected: */ virtual void addControllers(); - osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const; - - void addGlow(osg::ref_ptr node, osg::Vec4f glowColor, float glowDuration = -1); - /// Set the render bin for this animation's object root. May be customized by subclasses. virtual void setRenderBin(); diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 74f2ba911..e0818101d 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -48,7 +48,6 @@ namespace MWRender mAnimation(nullptr), mFirstPersonView(true), mPreviewMode(false), - mFreeLook(true), mNearest(30.f), mFurthest(800.f), mIsNearest(false), @@ -393,11 +392,6 @@ namespace MWRender camera = focal + offset; } - void Camera::togglePlayerLooking(bool enable) - { - mFreeLook = enable; - } - bool Camera::isVanityOrPreviewModeEnabled() { return mPreviewMode || mVanity.enabled; diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index db5ad25f0..573cf936f 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -37,7 +37,6 @@ namespace MWRender bool mFirstPersonView; bool mPreviewMode; - bool mFreeLook; float mNearest; float mFurthest; bool mIsNearest; @@ -119,8 +118,6 @@ namespace MWRender /// Stores focal and camera world positions in passed arguments void getPosition(osg::Vec3f &focal, osg::Vec3f &camera); - void togglePlayerLooking(bool enable); - bool isVanityOrPreviewModeEnabled(); bool isNearest(); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index c4afdde9c..b2552e598 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -24,6 +24,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/weapontype.hpp" #include "npcanimation.hpp" #include "vismask.hpp" @@ -290,55 +291,36 @@ namespace MWRender MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter); MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - std::string groupname; + std::string groupname = "inventoryhandtohand"; bool showCarriedLeft = true; - if(iter == inv.end()) - groupname = "inventoryhandtohand"; - else + if(iter != inv.end()) { - const std::string &typeName = iter->getTypeName(); - if(typeName == typeid(ESM::Lockpick).name() || typeName == typeid(ESM::Probe).name()) - groupname = "inventoryweapononehand"; - else if(typeName == typeid(ESM::Weapon).name()) + groupname = "inventoryweapononehand"; + if(iter->getTypeName() == typeid(ESM::Weapon).name()) { MWWorld::LiveCellRef *ref = iter->get(); - int type = ref->mBase->mData.mType; - if(type == ESM::Weapon::ShortBladeOneHand || - type == ESM::Weapon::LongBladeOneHand || - type == ESM::Weapon::BluntOneHand || - type == ESM::Weapon::AxeOneHand || - type == ESM::Weapon::MarksmanThrown) - { - groupname = "inventoryweapononehand"; - } - else if(type == ESM::Weapon::MarksmanCrossbow || - type == ESM::Weapon::MarksmanBow) - { - groupname = "inventoryweapononehand"; - showCarriedLeft = false; - } - else if(type == ESM::Weapon::LongBladeTwoHand || - type == ESM::Weapon::BluntTwoClose || - type == ESM::Weapon::AxeTwoHand) - { - groupname = "inventoryweapontwohand"; - showCarriedLeft = false; - } - else if(type == ESM::Weapon::BluntTwoWide || - type == ESM::Weapon::SpearTwoWide) - { - groupname = "inventoryweapontwowide"; - showCarriedLeft = false; - } + const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(type); + showCarriedLeft = !(weaponInfo->mFlags & ESM::WeaponType::TwoHanded); + + std::string inventoryGroup = weaponInfo->mLongGroup; + inventoryGroup = "inventory" + inventoryGroup; + + // We still should use one-handed animation as fallback + if (mAnimation->hasAnimation(inventoryGroup)) + groupname = inventoryGroup; else { - groupname = "inventoryhandtohand"; - showCarriedLeft = false; + static const std::string oneHandFallback = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup; + static const std::string twoHandFallback = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup; + + // For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones + if (weaponInfo->mFlags & ESM::WeaponType::TwoHanded && weaponInfo->mWeaponClass == ESM::WeaponType::Melee) + groupname = twoHandFallback; + else + groupname = oneHandFallback; } } - else - groupname = "inventoryhandtohand"; } mAnimation->showCarriedLeft(showCarriedLeft); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index aeee69ab0..6bece05ec 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -15,6 +15,8 @@ #include "../mwbase/world.hpp" +#include "../mwmechanics/weapontype.hpp" + #include "../mwworld/class.hpp" namespace MWRender @@ -50,9 +52,6 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const if((ref->mBase->mFlags&ESM::Creature::Bipedal)) { - if (mWeaponSheathing) - injectWeaponBones(); - addAnimSource("meshes\\xbase_anim.nif", model); } addAnimSource(model, model); @@ -115,7 +114,22 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) std::string bonename; if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - bonename = "Weapon Bone"; + { + if(item.getTypeName() == typeid(ESM::Weapon).name()) + { + int type = item.get()->mBase->mData.mType; + bonename = MWMechanics::getWeaponType(type)->mAttachBone; + if (bonename != "Weapon Bone") + { + const NodeMap& nodeMap = getNodeMap(); + NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); + if (found == nodeMap.end()) + bonename = "Weapon Bone"; + } + } + else + bonename = "Weapon Bone"; + } else bonename = "Shield Bone"; @@ -132,7 +146,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) scene.reset(new PartHolder(attached)); if (!item.getClass().getEnchantment(item).empty()) - addGlow(attached, getEnchantmentColor(item)); + mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, item.getClass().getEnchantmentColor(item)); // Crossbows start out with a bolt attached // FIXME: code duplicated from NpcAnimation @@ -140,8 +154,9 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) item.getTypeName() == typeid(ESM::Weapon).name() && item.get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { + const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow); MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == weaponInfo->mAmmoType) attachArrow(); else mAmmunition.reset(); @@ -173,6 +188,16 @@ bool CreatureWeaponAnimation::isArrowAttached() const void CreatureWeaponAnimation::attachArrow() { WeaponAnimation::attachArrow(mPtr); + + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && !ammo->getClass().getEnchantment(*ammo).empty()) + { + osg::Group* bone = getArrowBone(); + if (bone != nullptr && bone->getNumChildren()) + SceneUtil::addEnchantedGlow(bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo)); + } + updateQuiver(); } @@ -187,7 +212,19 @@ osg::Group *CreatureWeaponAnimation::getArrowBone() if (!mWeapon) return nullptr; - SceneUtil::FindByNameVisitor findVisitor ("ArrowBone"); + if (!mPtr.getClass().hasInventoryStore(mPtr)) + return nullptr; + + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) + return nullptr; + + int type = weapon->get()->mBase->mData.mType; + int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; + + SceneUtil::FindByNameVisitor findVisitor (MWMechanics::getWeaponType(ammoType)->mAttachBone); + mWeapon->getNode()->accept(findVisitor); return findVisitor.mFoundNode; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e7f7d878b..09c2c8e1c 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -31,6 +31,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/weapontype.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -275,7 +276,7 @@ static NpcAnimation::PartBoneMap createPartListMap() result.insert(std::make_pair(ESM::PRT_LLeg, "Left Upper Leg")); result.insert(std::make_pair(ESM::PRT_RPauldron, "Right Clavicle")); result.insert(std::make_pair(ESM::PRT_LPauldron, "Left Clavicle")); - result.insert(std::make_pair(ESM::PRT_Weapon, "Weapon Bone")); + result.insert(std::make_pair(ESM::PRT_Weapon, "Weapon Bone")); // Fallback. The real node name depends on the current weapon type. result.insert(std::make_pair(ESM::PRT_Tail, "Tail")); return result; } @@ -318,12 +319,6 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode) if(mViewMode == viewMode) return; - // Disable weapon sheathing in the 1st-person mode - if (viewMode == VM_FirstPerson) - mWeaponSheathing = false; - else - mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game"); - mViewMode = viewMode; MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change @@ -485,9 +480,6 @@ void NpcAnimation::updateNpcBase() setObjectRoot(smodel, true, true, false); - if (mWeaponSheathing) - injectWeaponBones(); - updateParts(); if(!is1stPerson) @@ -572,7 +564,7 @@ void NpcAnimation::updateParts() int prio = 1; bool enchantedGlow = !store->getClass().getEnchantment(*store).empty(); - osg::Vec4f glowColor = getEnchantmentColor(*store); + osg::Vec4f glowColor = store->getClass().getEnchantmentColor(*store); if(store->getTypeName() == typeid(ESM::Clothing).name()) { prio = ((slotlist[i].mBasePriority+1)<<1) + 0; @@ -664,7 +656,7 @@ PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const st osg::ref_ptr attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, found->second); if (enchantedGlow) - addGlow(attached, *glowColor); + mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor); return PartHolderPtr(new PartHolder(attached)); } @@ -745,7 +737,26 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g mPartPriorities[type] = priority; try { - const std::string& bonename = sPartList.at(type); + std::string bonename = sPartList.at(type); + if (type == ESM::PRT_Weapon) + { + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()) + { + int weaponType = weapon->get()->mBase->mData.mType; + const std::string weaponBonename = MWMechanics::getWeaponType(weaponType)->mAttachBone; + + if (weaponBonename != bonename) + { + const NodeMap& nodeMap = getNodeMap(); + NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(weaponBonename)); + if (found != nodeMap.end()) + bonename = weaponBonename; + } + } + } + // PRT_Hair seems to be the only type that breaks consistency and uses a filter that's different from the attachment bone const std::string bonefilter = (type == ESM::PRT_Hair) ? "hair" : bonename; mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor); @@ -897,7 +908,7 @@ void NpcAnimation::showWeapons(bool showWeapon) MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon != inv.end()) { - osg::Vec4f glowColor = getEnchantmentColor(*weapon); + osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon); std::string mesh = weapon->getClass().getModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); @@ -906,8 +917,9 @@ void NpcAnimation::showWeapons(bool showWeapon) if (weapon->getTypeName() == typeid(ESM::Weapon).name() && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) { + int ammotype = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow)->mAmmoType; MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ammotype) attachArrow(); } } @@ -931,7 +943,7 @@ void NpcAnimation::showCarriedLeft(bool show) MWWorld::ConstContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(show && iter != inv.end()) { - osg::Vec4f glowColor = getEnchantmentColor(*iter); + osg::Vec4f glowColor = iter->getClass().getEnchantmentColor(*iter); std::string mesh = iter->getClass().getModel(*iter); if (addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor)) @@ -947,6 +959,16 @@ void NpcAnimation::showCarriedLeft(bool show) void NpcAnimation::attachArrow() { WeaponAnimation::attachArrow(mPtr); + + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && !ammo->getClass().getEnchantment(*ammo).empty()) + { + osg::Group* bone = getArrowBone(); + if (bone != nullptr && bone->getNumChildren()) + SceneUtil::addEnchantedGlow(bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo)); + } + updateQuiver(); } @@ -962,7 +984,15 @@ osg::Group* NpcAnimation::getArrowBone() if (!part) return nullptr; - SceneUtil::FindByNameVisitor findVisitor ("ArrowBone"); + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name()) + return nullptr; + + int type = weapon->get()->mBase->mData.mType; + int ammoType = MWMechanics::getWeaponType(type)->mAmmoType; + + SceneUtil::FindByNameVisitor findVisitor (MWMechanics::getWeaponType(ammoType)->mAttachBone); part->getNode()->accept(findVisitor); return findVisitor.mFoundNode; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 0d9cbd9b0..c361ddf03 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1353,11 +1353,6 @@ namespace MWRender mCamera->allowVanityMode(allow); } - void RenderingManager::togglePlayerLooking(bool enable) - { - mCamera->togglePlayerLooking(enable); - } - void RenderingManager::changeVanityModeScale(float factor) { if(mCamera->isVanityOrPreviewModeEnabled()) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 7fb436863..49da50be3 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -208,7 +208,6 @@ namespace MWRender void togglePreviewMode(bool enable); bool toggleVanityMode(bool enable); void allowVanityMode(bool allow); - void togglePlayerLooking(bool enable); void changeVanityModeScale(float factor); /// temporarily override the field of view with given value. diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 3b09706eb..4b9aad998 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -25,6 +25,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/combat.hpp" +#include "../mwmechanics/weapontype.hpp" #include "animation.hpp" #include "rotatecontroller.hpp" @@ -77,8 +78,10 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor) return; if (weaponSlot->getTypeName() != typeid(ESM::Weapon).name()) return; - int weaponType = weaponSlot->get()->mBase->mData.mType; - if (weaponType == ESM::Weapon::MarksmanThrown) + + int type = weaponSlot->get()->mBase->mData.mType; + ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass; + if (weapclass == ESM::WeaponType::Thrown) { std::string soundid = weaponSlot->getClass().getUpSoundId(*weaponSlot); if(!soundid.empty()) @@ -88,7 +91,7 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor) } showWeapon(true); } - else if (weaponType == ESM::Weapon::MarksmanBow || weaponType == ESM::Weapon::MarksmanCrossbow) + else if (weapclass == ESM::WeaponType::Ranged) { osg::Group* parent = getArrowBone(); if (!parent) @@ -154,7 +157,7 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) MWMechanics::applyFatigueLoss(actor, *weapon, attackStrength); - if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + if (MWMechanics::getWeaponType(weapon->get()->mBase->mData.mType)->mWeaponClass == ESM::WeaponType::Thrown) { /* Start of tes3mp addition diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index 0d5b1bf3b..956792863 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -186,14 +186,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - MWBase::World* world = MWBase::Environment::get().getWorld(); - - bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak); - bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr); - bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr); - - runtime.push(stanceOn && (sneaking || inair)); + runtime.push(MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr)); } }; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index df21deff6..fa3dff9f1 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -248,12 +248,6 @@ namespace MWScript if (ptr.getTypeName() == typeid(ESM::Door).name() && !ptr.getCellRef().getTeleport()) { MWBase::Environment::get().getWorld()->activateDoor(ptr, 0); - - float xr = ptr.getCellRef().getPosition().rot[0]; - float yr = ptr.getCellRef().getPosition().rot[1]; - float zr = ptr.getCellRef().getPosition().rot[2]; - - MWBase::Environment::get().getWorld()->rotateObject(ptr, xr, yr, zr); } } }; @@ -529,7 +523,16 @@ namespace MWScript if(key < 0 || key > 32767 || *end != '\0') key = ESM::MagicEffect::effectStringToId(effect); - const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + + MWMechanics::MagicEffects effects = stats.getSpells().getMagicEffects(); + effects += stats.getActiveSpells().getMagicEffects(); + if (ptr.getClass().isNpc()) + { + MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + effects += store.getMagicEffects(); + } + for (MWMechanics::MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) { if (it->first.mId == key && it->second.getModifier() > 0) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index ea8e479a7..ff712babe 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -36,6 +36,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "ref.hpp" @@ -495,6 +496,16 @@ namespace MWScript { // Apply looping particles immediately for constant effects MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); + + // The spell may have an instant effect which must be handled immediately. + for (const auto& effect : creatureStats.getSpells().getMagicEffects()) + { + if (effect.second.getMagnitude() <= 0) + continue; + MWMechanics::CastSpell cast(ptr, ptr); + if (cast.applyInstantEffect(ptr, ptr, effect.first, effect.second.getMagnitude())) + creatureStats.getSpells().purgeEffect(effect.first.mId); + } } } }; diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 22989a33d..4c41a5b40 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -798,7 +798,7 @@ bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname if(alGetError() == AL_NO_ERROR) Log(Debug::Info) << "Standard Reverb supported"; } - EFXEAXREVERBPROPERTIES props = EFX_REVERB_PRESET_GENERIC; + EFXEAXREVERBPROPERTIES props = EFX_REVERB_PRESET_LIVINGROOM; props.flGain = 0.0f; LoadEffect(mDefaultEffect, props); } diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index abee49a6e..1cb97c231 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -520,4 +520,28 @@ namespace MWWorld { throw std::runtime_error("class does not support armor ratings"); } + + osg::Vec4f Class::getEnchantmentColor(const MWWorld::ConstPtr& item) const + { + osg::Vec4f result(1,1,1,1); + std::string enchantmentName = item.getClass().getEnchantment(item); + if (enchantmentName.empty()) + return result; + + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().search(enchantmentName); + if (!enchantment) + return result; + + assert (enchantment->mEffects.mList.size()); + + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().search( + enchantment->mEffects.mList.front().mEffectID); + if (!magicEffect) + return result; + + result.x() = magicEffect->mData.mRed / 255.f; + result.y() = magicEffect->mData.mGreen / 255.f; + result.z() = magicEffect->mData.mBlue / 255.f; + return result; + } } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 6112bc5a1..fd29acd79 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -6,6 +6,8 @@ #include #include +#include + #include "ptr.hpp" namespace ESM @@ -378,6 +380,8 @@ namespace MWWorld /// Get the effective armor rating, factoring in the actor's skills, for the given armor. virtual float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const; + + virtual osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const; }; } diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index e5b4c29e0..f3cdcb413 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -29,7 +29,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/actorutil.hpp" - +#include "../mwmechanics/weapontype.hpp" #include "esmstore.hpp" #include "class.hpp" @@ -357,7 +357,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots const ESM::Weapon* esmWeapon = iter->get()->mBase; - if (esmWeapon->mData.mType == ESM::Weapon::Arrow || esmWeapon->mData.mType == ESM::Weapon::Bolt) + if (MWMechanics::getWeaponType(esmWeapon->mData.mType)->mWeaponClass == ESM::WeaponType::Ammo) continue; if (iter->getClass().getEquipmentSkill(*iter) == weaponSkills[maxWeaponSkill]) @@ -382,31 +382,21 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots } } - bool isBow = false; - bool isCrossbow = false; - if (weapon != end()) - { - const MWWorld::LiveCellRef *ref = weapon->get(); - ESM::Weapon::Type type = (ESM::Weapon::Type)ref->mBase->mData.mType; - - if (type == ESM::Weapon::MarksmanBow) - isBow = true; - else if (type == ESM::Weapon::MarksmanCrossbow) - isCrossbow = true; - } - if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, actor).first) { // Do not equip ranged weapons, if there is no suitable ammo bool hasAmmo = true; - if (isBow == true) + const MWWorld::LiveCellRef *ref = weapon->get(); + int type = ref->mBase->mData.mType; + int ammotype = MWMechanics::getWeaponType(type)->mAmmoType; + if (ammotype == ESM::Weapon::Arrow) { if (arrow == end()) hasAmmo = false; else slots_[Slot_Ammunition] = arrow; } - if (isCrossbow == true) + else if (ammotype == ESM::Weapon::Bolt) { if (bolt == end()) hasAmmo = false; @@ -431,7 +421,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots int slot = itemsSlots.first.front(); slots_[slot] = weapon; - if (!isBow && !isCrossbow) + if (ammotype == ESM::Weapon::None) slots_[Slot_Ammunition] = end(); } diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 19a255d8d..2da8140f2 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -32,6 +32,7 @@ #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/aipackage.hpp" +#include "../mwmechanics/weapontype.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/vismask.hpp" @@ -317,12 +318,16 @@ namespace MWWorld state.mIdArrow = projectile.getCellRef().getRefId(); state.mCasterHandle = actor; state.mAttackStrength = attackStrength; - state.mThrown = projectile.get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown; + + int type = projectile.get()->mBase->mData.mType; + state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId()); MWWorld::Ptr ptr = ref.getPtr(); createModel(state, ptr.getClass().getModel(ptr), pos, orient, false, false, osg::Vec4(0,0,0,0)); + if (!ptr.getClass().getEnchantment(ptr).empty()) + SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); mProjectiles.push_back(state); } @@ -602,7 +607,9 @@ namespace MWWorld MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); - state.mThrown = ptr.get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown; + + int weaponType = ptr.get()->mBase->mData.mType; + state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; } catch(...) { diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index c6ef401ed..c595e3ecb 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -388,6 +388,30 @@ namespace MWWorld iterator end() const; }; + template <> + class Store : public StoreBase + { + std::map mStatic; + + public: + typedef std::map::const_iterator iterator; + + Store(); + + const ESM::WeaponType *search(const int id) const; + const ESM::WeaponType *find(const int id) const; + + RecordId load(ESM::ESMReader &esm) { return RecordId(0, false); } + + ESM::WeaponType* insert(const ESM::WeaponType &weaponType); + + void setUp(); + + size_t getSize() const; + iterator begin() const; + iterator end() const; + }; + } //end namespace diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 6bdfaf0be..cdbb7ee74 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1603,7 +1603,7 @@ namespace MWWorld pos.z() += 20; // place slightly above. will snap down to ground with code below - if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && isActorCollisionEnabled(ptr))) + if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !isSwimming(ptr) && isActorCollisionEnabled(ptr))) { osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits); if (traced.z() < pos.z()) @@ -1660,6 +1660,9 @@ namespace MWWorld { mRendering->rotateObject(ptr, rotate); mPhysics->updateRotation(ptr); + + if (const auto object = mPhysics->getObject(ptr)) + updateNavigatorObject(object); } } @@ -1844,6 +1847,45 @@ namespace MWWorld return result.mHit; } + bool World::rotateDoor(const Ptr door, int state, float duration) + { + const ESM::Position& objPos = door.getRefData().getPosition(); + float oldRot = objPos.rot[2]; + + float minRot = door.getCellRef().getPosition().rot[2]; + float maxRot = minRot + osg::DegreesToRadians(90.f); + + float diff = duration * osg::DegreesToRadians(90.f); + float targetRot = std::min(std::max(minRot, oldRot + diff * (state == 1 ? 1 : -1)), maxRot); + rotateObject(door, objPos.rot[0], objPos.rot[1], targetRot); + + bool reached = (targetRot == maxRot && state) || targetRot == minRot; + + /// \todo should use convexSweepTest here + std::vector collisions = mPhysics->getCollisions(door, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor); + for (MWWorld::Ptr& ptr : collisions) + { + if (ptr.getClass().isActor()) + { + // Collided with actor, ask actor to try to avoid door + if(ptr != getPlayerPtr() ) + { + MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) //Only add it once + seq.stack(MWMechanics::AiAvoidDoor(door),ptr); + } + + // we need to undo the rotation + rotateObject(door, objPos.rot[0], objPos.rot[1], oldRot); + reached = false; + } + } + + // the rotation order we want to use + mWorldScene->updateObjectRotation(door, false); + return reached; + } + void World::processDoors(float duration) { std::map::iterator it = mDoorStates.begin(); @@ -1858,40 +1900,7 @@ namespace MWWorld } else { - const ESM::Position& objPos = it->first.getRefData().getPosition(); - float oldRot = objPos.rot[2]; - - float minRot = it->first.getCellRef().getPosition().rot[2]; - float maxRot = minRot + osg::DegreesToRadians(90.f); - - float diff = duration * osg::DegreesToRadians(90.f); - float targetRot = std::min(std::max(minRot, oldRot + diff * (it->second == 1 ? 1 : -1)), maxRot); - rotateObject(it->first, objPos.rot[0], objPos.rot[1], targetRot); - - bool reached = (targetRot == maxRot && it->second) || targetRot == minRot; - - /// \todo should use convexSweepTest here - std::vector collisions = mPhysics->getCollisions(it->first, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor); - for (MWWorld::Ptr& ptr : collisions) - { - if (ptr.getClass().isActor()) - { - // Collided with actor, ask actor to try to avoid door - if(ptr != getPlayerPtr() ) - { - MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); - if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) //Only add it once - seq.stack(MWMechanics::AiAvoidDoor(it->first),ptr); - } - - // we need to undo the rotation - rotateObject(it->first, objPos.rot[0], objPos.rot[1], oldRot); - reached = false; - } - } - - // the rotation order we want to use - mWorldScene->updateObjectRotation(it->first, false); + bool reached = rotateDoor(it->first, it->second, duration); if (reached) { @@ -2681,11 +2690,6 @@ namespace MWWorld mRendering->allowVanityMode(allow); } - void World::togglePlayerLooking(bool enable) - { - mRendering->togglePlayerLooking(enable); - } - void World::changeVanityModeScale(float factor) { mRendering->changeVanityModeScale(factor); @@ -2853,7 +2857,10 @@ namespace MWWorld door.getClass().setDoorState(door, state); mDoorStates[door] = state; if (state == 0) + { mDoorStates.erase(door); + rotateDoor(door, state, 1); + } } /* diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 406c6b5b8..7a2ed223e 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -155,6 +155,8 @@ namespace MWWorld void addContainerScripts(const Ptr& reference, CellStore* cell) override; void removeContainerScripts(const Ptr& reference) override; private: + bool rotateDoor(const Ptr door, int state, float duration); + void processDoors(float duration); ///< Run physics simulation and modify \a world accordingly. @@ -664,8 +666,6 @@ namespace MWWorld void allowVanityMode(bool allow) override; - void togglePlayerLooking(bool enable) override; - void changeVanityModeScale(float factor) override; bool vanityRotateCamera(float * rot) override; diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index dfe2b695a..90431756d 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -3,6 +3,8 @@ #include +#include "loadskil.hpp" + namespace ESM { @@ -21,6 +23,10 @@ struct Weapon enum Type { + PickProbe = -4, + HandToHand = -3, + Spell = -2, + None = -1, ShortBladeOneHand = 0, LongBladeOneHand = 1, LongBladeTwoHand = 2, @@ -75,5 +81,34 @@ struct Weapon void blank(); ///< Set record to default state (does not touch the ID). }; + +struct WeaponType +{ + enum Flags + { + TwoHanded = 0x01, + HasHealth = 0x02 + }; + + enum Class + { + Melee = 0, + Ranged = 1, + Thrown = 2, + Ammo = 3 + }; + + //std::string mDisplayName; // TODO: will be needed later for editor + std::string mShortGroup; + std::string mLongGroup; + std::string mSoundId; + std::string mAttachBone; + std::string mSheathingBone; + ESM::Skill::SkillEnum mSkill; + Class mWeaponClass; + int mAmmoType; + int mFlags; +}; + } #endif diff --git a/components/files/linuxpath.cpp b/components/files/linuxpath.cpp index 5f34539d5..c3dead296 100644 --- a/components/files/linuxpath.cpp +++ b/components/files/linuxpath.cpp @@ -87,7 +87,7 @@ boost::filesystem::path LinuxPath::getLocalPath() const { if (readlink(path, &binPath[0], binPath.size()) != -1) { - localPath = boost::filesystem::path(binPath).parent_path(); + localPath = boost::filesystem::path(binPath).parent_path() / "/"; break; } } diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index 516f26021..7f15a7efe 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -85,7 +85,7 @@ boost::filesystem::path WindowsPath::getLocalPath() const if (GetModuleFileNameW(nullptr, path, MAX_PATH + 1) > 0) { - localPath = boost::filesystem::path(bconv::utf_to_utf(path)).parent_path(); + localPath = boost::filesystem::path(bconv::utf_to_utf(path)).parent_path() / "/"; } // lookup exe path diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 086021930..5f7722794 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -90,6 +90,26 @@ void NiTriShapeData::read(NIFStream *nif) } } +void NiTriStripsData::read(NIFStream *nif) +{ + ShapeData::read(nif); + + // Every strip with n points defines n-2 triangles, so this should be unnecessary. + /*int tris =*/ nif->getUShort(); + // Number of triangle strips + int numStrips = nif->getUShort(); + + std::vector lengths; + nif->getUShorts(lengths, numStrips); + + for (int i = 0; i < numStrips; i++) + { + std::vector strip; + nif->getUShorts(strip, lengths[i]); + strips.emplace_back(strip); + } +} + void NiAutoNormalParticlesData::read(NIFStream *nif) { ShapeData::read(nif); @@ -143,22 +163,23 @@ void NiPixelData::read(NIFStream *nif) { fmt = (Format)nif->getUInt(); - rmask = nif->getInt(); // usually 0xff - gmask = nif->getInt(); // usually 0xff00 - bmask = nif->getInt(); // usually 0xff0000 - amask = nif->getInt(); // usually 0xff000000 or zero + rmask = nif->getUInt(); // usually 0xff + gmask = nif->getUInt(); // usually 0xff00 + bmask = nif->getUInt(); // usually 0xff0000 + amask = nif->getUInt(); // usually 0xff000000 or zero - bpp = nif->getInt(); + bpp = nif->getUInt(); - // Unknown - nif->skip(12); + // 8 bytes of "Old Fast Compare". Whatever that means. + nif->skip(8); + palette.read(nif); - numberOfMipmaps = nif->getInt(); + numberOfMipmaps = nif->getUInt(); // Bytes per pixel, should be bpp * 8 - /* int bytes = */ nif->getInt(); + /* int bytes = */ nif->getUInt(); - for(int i=0; igetInt(); + unsigned int dataSize = nif->getUInt(); data.reserve(dataSize); for (unsigned i=0; igetChar()); } +void NiPixelData::post(NIFFile *nif) +{ + palette.post(nif); +} + void NiColorData::read(NIFStream *nif) { mKeyMap = std::make_shared(); @@ -258,4 +284,14 @@ void NiKeyframeData::read(NIFStream *nif) mScales->read(nif); } +void NiPalette::read(NIFStream *nif) +{ + unsigned int alphaMask = !nif->getChar() ? 0xFF000000 : 0; + // Fill the entire palette with black even if there isn't enough entries. + colors.resize(256); + unsigned int numEntries = nif->getUInt(); + for (unsigned int i = 0; i < numEntries; i++) + colors[i] = nif->getUInt() | alphaMask; +} + } // Namespace diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 6b7aa579b..a0d4960e0 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -53,6 +53,15 @@ public: void read(NIFStream *nif); }; +class NiTriStripsData : public ShapeData +{ +public: + // Triangle strips, series of vertex indices. + std::vector> strips; + + void read(NIFStream *nif); +}; + class NiAutoNormalParticlesData : public ShapeData { public: @@ -107,6 +116,7 @@ public: NIPXFMT_RGB8, NIPXFMT_RGBA8, NIPXFMT_PAL8, + NIPXFMT_PALA8, NIPXFMT_DXT1, NIPXFMT_DXT3, NIPXFMT_DXT5, @@ -114,8 +124,10 @@ public: }; Format fmt; - unsigned int rmask, gmask, bmask, amask; - int bpp, numberOfMipmaps; + unsigned int rmask, gmask, bmask, amask, bpp; + + NiPalettePtr palette; + unsigned int numberOfMipmaps; struct Mipmap { @@ -127,6 +139,7 @@ public: std::vector data; void read(NIFStream *nif); + void post(NIFFile *nif); }; class NiColorData : public Record @@ -210,5 +223,14 @@ struct NiKeyframeData : public Record void read(NIFStream *nif); }; +class NiPalette : public Record +{ +public: + // 32-bit RGBA colors that correspond to 8-bit indices + std::vector colors; + + void read(NIFStream *nif); +}; + } // Namespace #endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 6675fef08..d4f1203cc 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -54,6 +54,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiBSAnimationNode", &construct , RC_NiBSAnimationNode )); newFactory.insert(makeEntry("NiBillboardNode", &construct , RC_NiBillboardNode )); newFactory.insert(makeEntry("NiTriShape", &construct , RC_NiTriShape )); + newFactory.insert(makeEntry("NiTriStrips", &construct , RC_NiTriStrips )); newFactory.insert(makeEntry("NiRotatingParticles", &construct , RC_NiRotatingParticles )); newFactory.insert(makeEntry("NiAutoNormalParticles", &construct , RC_NiAutoNormalParticles )); newFactory.insert(makeEntry("NiCamera", &construct , RC_NiCamera )); @@ -96,6 +97,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiParticleRotation", &construct , RC_NiParticleRotation )); newFactory.insert(makeEntry("NiFloatData", &construct , RC_NiFloatData )); newFactory.insert(makeEntry("NiTriShapeData", &construct , RC_NiTriShapeData )); + newFactory.insert(makeEntry("NiTriStripsData", &construct , RC_NiTriStripsData )); newFactory.insert(makeEntry("NiVisData", &construct , RC_NiVisData )); newFactory.insert(makeEntry("NiColorData", &construct , RC_NiColorData )); newFactory.insert(makeEntry("NiPixelData", &construct , RC_NiPixelData )); @@ -110,6 +112,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiSourceTexture", &construct , RC_NiSourceTexture )); newFactory.insert(makeEntry("NiSkinInstance", &construct , RC_NiSkinInstance )); newFactory.insert(makeEntry("NiLookAtController", &construct , RC_NiLookAtController )); + newFactory.insert(makeEntry("NiPalette", &construct , RC_NiPalette )); return newFactory; } diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 0fdf4b440..b685c5662 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -160,9 +160,9 @@ public: { std::vector str(length + 1, 0); - inp->read(&str[0], length); + inp->read(str.data(), length); - return &str[0]; + return str.data(); } ///Read in a string of the length specified in the file std::string getString() @@ -181,34 +181,34 @@ public: void getUShorts(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, &vec.front(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getFloats(std::vector &vec, size_t size) { vec.resize(size); - readLittleEndianDynamicBufferOfType(inp, &vec.front(), size); + readLittleEndianDynamicBufferOfType(inp, vec.data(), size); } void getVector2s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec2f is 2 floats exactly */ - readLittleEndianDynamicBufferOfType(inp,(float*) &vec.front(), size*2); + readLittleEndianDynamicBufferOfType(inp,(float*)vec.data(), size*2); } void getVector3s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec3f is 3 floats exactly */ - readLittleEndianDynamicBufferOfType(inp, (float*) &vec.front(), size*3); + readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*3); } void getVector4s(std::vector &vec, size_t size) { vec.resize(size); /* The packed storage of each Vec4f is 4 floats exactly */ - readLittleEndianDynamicBufferOfType(inp, (float*) &vec.front(), size*4); + readLittleEndianDynamicBufferOfType(inp, (float*)vec.data(), size*4); } void getQuaternions(std::vector &quat, size_t size) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index cc1871d83..5e5f445cf 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -156,6 +156,29 @@ struct NiTriShape : Node } }; +struct NiTriStrips : Node +{ + NiTriStripsDataPtr data; + NiSkinInstancePtr skin; + + void read(NIFStream *nif) + { + Node::read(nif); + data.read(nif); + skin.read(nif); + } + + void post(NIFFile *nif) + { + Node::post(nif); + data.post(nif); + skin.post(nif); + if (!skin.empty()) + nif->setUseSkinning(true); + } +}; + + struct NiCamera : Node { struct Camera diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 4a044ac47..67ffbc574 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -41,6 +41,7 @@ enum RecordType RC_NiBillboardNode, RC_AvoidNode, RC_NiTriShape, + RC_NiTriStrips, RC_NiRotatingParticles, RC_NiAutoNormalParticles, RC_NiBSParticleNode, @@ -80,6 +81,7 @@ enum RecordType RC_NiParticleRotation, RC_NiFloatData, RC_NiTriShapeData, + RC_NiTriStripsData, RC_NiVisData, RC_NiColorData, RC_NiPixelData, @@ -95,7 +97,8 @@ enum RecordType RC_NiSkinInstance, RC_RootCollisionNode, RC_NiSphericalCollider, - RC_NiLookAtController + RC_NiLookAtController, + RC_NiPalette }; /// Base class for all records diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index e8aa8cb5b..e23beb786 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -135,10 +135,12 @@ class NiPixelData; class NiColorData; struct NiKeyframeData; class NiTriShapeData; +class NiTriStripsData; class NiSkinInstance; class NiSourceTexture; class NiRotatingParticlesData; class NiAutoNormalParticlesData; +class NiPalette; typedef RecordPtrT NodePtr; typedef RecordPtrT ExtraPtr; @@ -154,10 +156,12 @@ typedef RecordPtrT NiFloatDataPtr; typedef RecordPtrT NiColorDataPtr; typedef RecordPtrT NiKeyframeDataPtr; typedef RecordPtrT NiTriShapeDataPtr; +typedef RecordPtrT NiTriStripsDataPtr; typedef RecordPtrT NiSkinInstancePtr; typedef RecordPtrT NiSourceTexturePtr; typedef RecordPtrT NiRotatingParticlesDataPtr; typedef RecordPtrT NiAutoNormalParticlesDataPtr; +typedef RecordPtrT NiPalettePtr; typedef RecordListT NodeList; typedef RecordListT PropertyList; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 8f98174ab..2f24a4067 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -54,9 +54,57 @@ void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriShapeDa } } -void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data) +void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, const osg::Matrixf &transform) { - fillTriangleMeshWithTransform(mesh, data, osg::Matrixf()); + const std::vector &vertices = data.vertices; + const std::vector> &strips = data.strips; + if (vertices.empty() || strips.empty()) + return; + mesh.preallocateVertices(static_cast(data.vertices.size())); + int numTriangles = 0; + for (const std::vector& strip : strips) + { + // Each strip with N points contains information about N-2 triangles. + if (strip.size() >= 3) + numTriangles += static_cast(strip.size()-2); + } + mesh.preallocateIndices(static_cast(numTriangles)); + + // It's triangulation time. Totally not a NifSkope spell ripoff. + for (const std::vector& strip : strips) + { + // Can't make a triangle from less than 3 points. + if (strip.size() < 3) + continue; + + unsigned short a = strip[0], b = strip[0], c = strip[1]; + for (int i = 2; i < static_cast(strip.size()); i++) + { + a = b; + b = c; + c = strip[i]; + if (a != b && b != c && a != c) + { + if (i%2==0) + mesh.addTriangle(getbtVector(vertices[a]), getbtVector(vertices[b]), getbtVector(vertices[c])); + else + mesh.addTriangle(getbtVector(vertices[a]), getbtVector(vertices[c]), getbtVector(vertices[b])); + } + } + } +} + +void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::Node* nifNode, const osg::Matrixf &transform) +{ + if (nifNode->recType == Nif::RC_NiTriShape) + fillTriangleMeshWithTransform(mesh, static_cast(nifNode)->data.get(), transform); + else // if (nifNode->recType == Nif::RC_NiTriStrips) + fillTriangleMeshWithTransform(mesh, static_cast(nifNode)->data.get(), transform); +} + +void fillTriangleMesh(btTriangleMesh& mesh, const Nif::Node* node) +{ + fillTriangleMeshWithTransform(mesh, node, osg::Matrixf()); } } @@ -244,9 +292,9 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) - if(!node->hasBounds && node->recType == Nif::RC_NiTriShape) + if(!node->hasBounds && (node->recType == Nif::RC_NiTriShape || node->recType == Nif::RC_NiTriStrips)) { - handleNiTriShape(static_cast(node), flags, getWorldTransform(node), isAnimated, avoid); + handleNiTriShape(node, flags, getWorldTransform(node), isAnimated, avoid); } } @@ -263,25 +311,33 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n } } -void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf &transform, +void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, const osg::Matrixf &transform, bool isAnimated, bool avoid) { - assert(shape != nullptr); + assert(nifNode != nullptr); // If the object was marked "NCO" earlier, it shouldn't collide with // anything. So don't do anything. if ((flags & 0x800)) - { return; + + if (nifNode->recType == Nif::RC_NiTriShape) + { + const Nif::NiTriShape* shape = static_cast(nifNode); + if (!shape->skin.empty()) + isAnimated = false; + if (shape->data.empty() || shape->data->triangles.empty()) + return; + } + else + { + const Nif::NiTriStrips* shape = static_cast(nifNode); + if (!shape->skin.empty()) + isAnimated = false; + if (shape->data.empty() || shape->data->strips.empty()) + return; } - if (!shape->skin.empty()) - isAnimated = false; - - if (shape->data.empty()) - return; - if (shape->data->triangles.empty()) - return; if (isAnimated) { @@ -290,13 +346,13 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, std::unique_ptr childMesh(new btTriangleMesh); - fillTriangleMesh(*childMesh, shape->data.get()); + fillTriangleMesh(*childMesh, nifNode); std::unique_ptr childShape(new Resource::TriangleMeshShape(childMesh.get(), true)); childMesh.release(); - float scale = shape->trafo.scale; - const Nif::Node* parent = shape; + float scale = nifNode->trafo.scale; + const Nif::Node* parent = nifNode; while (parent->parent) { parent = parent->parent; @@ -308,7 +364,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, btTransform trans(btQuaternion(q.x(), q.y(), q.z(), q.w()), btVector3(v.x(), v.y(), v.z())); - mShape->mAnimatedShapes.insert(std::make_pair(shape->recIndex, mCompoundShape->getNumChildShapes())); + mShape->mAnimatedShapes.emplace(nifNode->recIndex, mCompoundShape->getNumChildShapes()); mCompoundShape->addChildShape(trans, childShape.get()); childShape.release(); @@ -318,7 +374,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, if (!mAvoidStaticMesh) mAvoidStaticMesh.reset(new btTriangleMesh(false)); - fillTriangleMeshWithTransform(*mAvoidStaticMesh, shape->data.get(), transform); + fillTriangleMeshWithTransform(*mAvoidStaticMesh, nifNode, transform); } else { @@ -326,7 +382,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, mStaticMesh.reset(new btTriangleMesh(false)); // Static shape, just transform all vertices into position - fillTriangleMeshWithTransform(*mStaticMesh, shape->data.get(), transform); + fillTriangleMeshWithTransform(*mStaticMesh, nifNode, transform); } } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index de7e6bdcd..e423e5149 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -26,6 +26,7 @@ namespace Nif class Node; struct Transformation; struct NiTriShape; + struct NiTriStrips; } namespace NifBullet @@ -58,7 +59,7 @@ private: bool hasAutoGeneratedCollision(const Nif::Node *rootNode); - void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid); + void handleNiTriShape(const Nif::Node *nifNode, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid); std::unique_ptr mCompoundShape; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 9b31a1b93..31dcd2a55 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -408,8 +408,8 @@ namespace NifOsg unsigned int clamp = static_cast(textureEffect->clamp); int wrapT = (clamp) & 0x1; int wrapS = (clamp >> 1) & 0x1; - texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); - texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); osg::ref_ptr texEnv = new osg::TexEnvCombine; texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE); @@ -452,6 +452,7 @@ namespace NifOsg break; } case Nif::RC_NiTriShape: + case Nif::RC_NiTriStrips: case Nif::RC_NiAutoNormalParticles: case Nif::RC_NiRotatingParticles: // Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children. @@ -540,6 +541,10 @@ namespace NifOsg // Marker objects. These meshes are only visible in the editor. hasMarkers = true; } + else if(sd->string == "BONE") + { + node->getOrCreateUserDataContainer()->addDescription("CustomBone"); + } } } @@ -575,7 +580,7 @@ namespace NifOsg node->setDataVariance(osg::Object::DYNAMIC); } - if (nifNode->recType == Nif::RC_NiTriShape && isAnimated) // the same thing for animated NiTriShapes + if ((nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips) && isAnimated) // Same thing for animated shapes { node->setDataVariance(osg::Object::DYNAMIC); } @@ -584,20 +589,25 @@ namespace NifOsg applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags); - if (nifNode->recType == Nif::RC_NiTriShape && !skipMeshes) + if ((nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips) && !skipMeshes) { - const Nif::NiTriShape* triShape = static_cast(nifNode); - const std::string nodeName = Misc::StringUtils::lowerCase(triShape->name); + const std::string nodeName = Misc::StringUtils::lowerCase(nifNode->name); static const std::string markerName = "tri editormarker"; static const std::string shadowName = "shadow"; static const std::string shadowName2 = "tri shadow"; const bool isMarker = hasMarkers && !nodeName.compare(0, markerName.size(), markerName); if (!isMarker && nodeName.compare(0, shadowName.size(), shadowName) && nodeName.compare(0, shadowName2.size(), shadowName2)) { - if (triShape->skin.empty()) - handleTriShape(triShape, node, composite, boundTextures, animflags); + Nif::NiSkinInstancePtr skin; + if (nifNode->recType == Nif::RC_NiTriShape) + skin = static_cast(nifNode)->skin; + else // if (nifNode->recType == Nif::RC_NiTriStrips) + skin = static_cast(nifNode)->skin; + + if (skin.empty()) + handleTriShape(nifNode, node, composite, boundTextures, animflags); else - handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags); + handleSkinnedTriShape(nifNode, node, composite, boundTextures, animflags); if (!nifNode->controller.empty()) handleMeshControllers(nifNode, node, composite, boundTextures, animflags); @@ -612,7 +622,8 @@ namespace NifOsg // Note: NiTriShapes are not allowed to have KeyframeControllers (the vanilla engine just crashes when there is one). // We can take advantage of this constraint for optimizations later. - if (nifNode->recType != Nif::RC_NiTriShape && !nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC) + if (nifNode->recType != Nif::RC_NiTriShape && nifNode->recType != Nif::RC_NiTriStrips + && !nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC) handleNodeControllers(nifNode, static_cast(node.get()), animflags); const Nif::NiNode *ninode = dynamic_cast(nifNode); @@ -766,8 +777,8 @@ namespace NifOsg // inherit wrap settings from the target slot osg::Texture2D* inherit = dynamic_cast(stateset->getTextureAttribute(flipctrl->mTexSlot, osg::StateAttribute::TEXTURE)); - osg::Texture2D::WrapMode wrapS = osg::Texture2D::CLAMP; - osg::Texture2D::WrapMode wrapT = osg::Texture2D::CLAMP; + osg::Texture2D::WrapMode wrapS = osg::Texture2D::CLAMP_TO_EDGE; + osg::Texture2D::WrapMode wrapT = osg::Texture2D::CLAMP_TO_EDGE; if (inherit) { wrapS = inherit->getWrap(osg::Texture2D::WRAP_S); @@ -1030,57 +1041,90 @@ namespace NifOsg } } - void triShapeToGeometry(const Nif::NiTriShape *triShape, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + void triCommonToGeometry(osg::Geometry *geometry, const std::vector& vertices, const std::vector& normals, const std::vector>& uvlist, const std::vector& colors, const std::vector& boundTextures, const std::string& name) { - const Nif::NiTriShapeData* data = triShape->data.getPtr(); - - { - geometry->setVertexArray(new osg::Vec3Array(data->vertices.size(), &data->vertices[0])); - if (!data->normals.empty()) - geometry->setNormalArray(new osg::Vec3Array(data->normals.size(), &data->normals[0]), osg::Array::BIND_PER_VERTEX); - } + if (!vertices.empty()) + geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data())); + if (!normals.empty()) + geometry->setNormalArray(new osg::Vec3Array(normals.size(), normals.data()), osg::Array::BIND_PER_VERTEX); + if (!colors.empty()) + geometry->setColorArray(new osg::Vec4Array(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX); int textureStage = 0; - for (std::vector::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it,++textureStage) + for (const int uvSet : boundTextures) { - int uvSet = *it; - if (uvSet >= (int)data->uvlist.size()) + if (uvSet >= (int)uvlist.size()) { - Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename; - if (!data->uvlist.empty()) - geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[0].size(), &data->uvlist[0][0]), osg::Array::BIND_PER_VERTEX); + Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on shape \"" << name << "\" in " << mFilename; + if (!uvlist.empty()) + geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[0].size(), uvlist[0].data()), osg::Array::BIND_PER_VERTEX); continue; } - geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[uvSet].size(), &data->uvlist[uvSet][0]), osg::Array::BIND_PER_VERTEX); + geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[uvSet].size(), uvlist[uvSet].data()), osg::Array::BIND_PER_VERTEX); + textureStage++; } + } - if (!data->colors.empty()) - geometry->setColorArray(new osg::Vec4Array(data->colors.size(), &data->colors[0]), osg::Array::BIND_PER_VERTEX); - - geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, - data->triangles.size(), - (unsigned short*)&data->triangles[0])); + void triShapeToGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + { + bool vertexColorsPresent = false; + if (nifNode->recType == Nif::RC_NiTriShape) + { + const Nif::NiTriShape* triShape = static_cast(nifNode); + const Nif::NiTriShapeData* data = triShape->data.getPtr(); + vertexColorsPresent = !data->colors.empty(); + triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triShape->name); + if (!data->triangles.empty()) + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, data->triangles.size(), + (unsigned short*)data->triangles.data())); + } + else + { + const Nif::NiTriStrips* triStrips = static_cast(nifNode); + const Nif::NiTriStripsData* data = triStrips->data.getPtr(); + vertexColorsPresent = !data->colors.empty(); + triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triStrips->name); + if (!data->strips.empty()) + { + for (const std::vector& strip : data->strips) + { + // Can't make a triangle from less than three vertices. + if (strip.size() < 3) + continue; + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(), + (unsigned short*)strip.data())); + } + } + } // osg::Material properties are handled here for two reasons: // - if there are no vertex colors, we need to disable colorMode. // - there are 3 "overlapping" nif properties that all affect the osg::Material, handling them // above the actual renderable would be tedious. std::vector drawableProps; - collectDrawableProperties(triShape, drawableProps); - applyDrawableProperties(parentNode, drawableProps, composite, !data->colors.empty(), animflags, false); + collectDrawableProperties(nifNode, drawableProps); + applyDrawableProperties(parentNode, drawableProps, composite, vertexColorsPresent, animflags, false); } - void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + void handleTriShape(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { + assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips); osg::ref_ptr drawable; - for (Nif::ControllerPtr ctrl = triShape->controller; !ctrl.empty(); ctrl = ctrl->next) + osg::ref_ptr geom (new osg::Geometry); + triShapeToGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); + Nif::ControllerPtr ctrl; + if (nifNode->recType == Nif::RC_NiTriShape) + ctrl = static_cast(nifNode)->controller; + else + ctrl = static_cast(nifNode)->controller; + for (; !ctrl.empty(); ctrl = ctrl->next) { if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) continue; if(ctrl->recType == Nif::RC_NiGeomMorpherController) { - drawable = handleMorphGeometry(static_cast(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags); + drawable = handleMorphGeometry(static_cast(ctrl.getPtr()), geom, parentNode, composite, boundTextures, animflags); osg::ref_ptr morphctrl = new GeomMorpherController( static_cast(ctrl.getPtr())->data.getPtr()); @@ -1089,25 +1133,15 @@ namespace NifOsg break; } } - if (!drawable.get()) - { - osg::ref_ptr geom (new osg::Geometry); drawable = geom; - triShapeToGeometry(triShape, geom, parentNode, composite, boundTextures, animflags); - } - - drawable->setName(triShape->name); - + drawable->setName(nifNode->name); parentNode->addChild(drawable); } - osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, osg::ref_ptr sourceGeometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { osg::ref_ptr morphGeom = new SceneUtil::MorphGeometry; - - osg::ref_ptr sourceGeometry (new osg::Geometry); - triShapeToGeometry(triShape, sourceGeometry, parentNode, composite, boundTextures, animflags); morphGeom->setSourceGeometry(sourceGeometry); const std::vector& morphs = morpher->data.getPtr()->mMorphs; @@ -1115,26 +1149,30 @@ namespace NifOsg return morphGeom; // Note we are not interested in morph 0, which just contains the original vertices for (unsigned int i = 1; i < morphs.size(); ++i) - morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0]), 0.f); + morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), morphs[i].mVertices.data()), 0.f); return morphGeom; } - void handleSkinnedTriShape(const Nif::NiTriShape *triShape, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, + void handleSkinnedTriShape(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { + assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips); osg::ref_ptr geometry (new osg::Geometry); - triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags); - + triShapeToGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geometry); - rig->setName(triShape->name); - - const Nif::NiSkinInstance *skin = triShape->skin.getPtr(); + rig->setName(nifNode->name); // Assign bone weights osg::ref_ptr map (new SceneUtil::RigGeometry::InfluenceMap); + Nif::NiSkinInstancePtr skinPtr; + if (nifNode->recType == Nif::RC_NiTriShape) + skinPtr = static_cast(nifNode)->skin; + else + skinPtr = static_cast(nifNode)->skin; + const Nif::NiSkinInstance *skin = skinPtr.getPtr(); const Nif::NiSkinData *data = skin->data.getPtr(); const Nif::NodeList &bones = skin->bones; for(size_t i = 0;i < bones.length();i++) @@ -1238,9 +1276,11 @@ namespace NifOsg switch (pixelData->fmt) { case Nif::NiPixelData::NIPXFMT_RGB8: + case Nif::NiPixelData::NIPXFMT_PAL8: pixelformat = GL_RGB; break; case Nif::NiPixelData::NIPXFMT_RGBA8: + case Nif::NiPixelData::NIPXFMT_PALA8: pixelformat = GL_RGBA; break; default: @@ -1255,7 +1295,7 @@ namespace NifOsg int height = 0; std::vector mipmapVector; - for (unsigned int i=0; imipmaps.size()-3; ++i) + for (unsigned int i=0; imipmaps.size(); ++i) { const Nif::NiPixelData::Mipmap& mip = pixelData->mipmaps[i]; @@ -1281,10 +1321,59 @@ namespace NifOsg return nullptr; } - unsigned char* data = new unsigned char[pixelData->data.size()]; - memcpy(data, &pixelData->data[0], pixelData->data.size()); + const std::vector& pixels = pixelData->data; + switch (pixelData->fmt) + { + case Nif::NiPixelData::NIPXFMT_RGB8: + case Nif::NiPixelData::NIPXFMT_RGBA8: + { + unsigned char* data = new unsigned char[pixels.size()]; + memcpy(data, pixels.data(), pixels.size()); + image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); + break; + } + case Nif::NiPixelData::NIPXFMT_PAL8: + case Nif::NiPixelData::NIPXFMT_PALA8: + { + if (pixelData->palette.empty() || pixelData->bpp != 8) + { + Log(Debug::Info) << "Palettized texture in " << mFilename << " is invalid, ignoring"; + return nullptr; + } + // We're going to convert the indices that pixel data contains + // into real colors using the palette. + const std::vector& palette = pixelData->palette->colors; + if (pixelData->fmt == Nif::NiPixelData::NIPXFMT_PAL8) + { + unsigned char* data = new unsigned char[pixels.size() * 3]; + for (size_t i = 0; i < pixels.size(); i++) + { + unsigned int color = palette[pixels[i]]; + data[i * 3 + 0] = (color >> 0) & 0xFF; + data[i * 3 + 1] = (color >> 8) & 0xFF; + data[i * 3 + 2] = (color >> 16) & 0xFF; + } + image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); + } + else // if (fmt = NIPXFMT_PALA8) + { + unsigned char* data = new unsigned char[pixels.size() * 4]; + for (size_t i = 0; i < pixels.size(); i++) + { + unsigned int color = palette[pixels[i]]; + data[i * 4 + 0] = (color >> 0) & 0xFF; + data[i * 4 + 1] = (color >> 8) & 0xFF; + data[i * 4 + 2] = (color >> 16) & 0xFF; + data[i * 4 + 3] = (color >> 24) & 0xFF; + } + image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); + } + break; + } + default: + return nullptr; + } - image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE); image->setMipmapLevels(mipmapVector); image->flipVertical(); @@ -1354,8 +1443,8 @@ namespace NifOsg int wrapT = (clamp) & 0x1; int wrapS = (clamp >> 1) & 0x1; - texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); - texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); int texUnit = boundTextures.size(); diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index 199b408d7..c759fabc7 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -8,48 +8,16 @@ #include -namespace -{ - - float pulseAmplitude(float time) - { - return std::sin(time); - } - - float flickerAmplitude(float time) - { - static const float fb = 1.17024f; - static const float f[3] = { 1.5708f, 4.18774f, 5.19934f }; - static const float o[3] = { 0.804248f, 2.11115f, 3.46832f }; - static const float m[3] = { 1.0f, 0.785f, 0.876f }; - static const float s = 0.394f; - - float v = 0.0f; - for(int i = 0;i < 3;++i) - v += std::sin(fb*time*f[i] + o[i])*m[i]; - return v * s; - } - - float flickerFrequency(float phase) - { - static const float fa = 0.785398f; - static const float tdo = 0.94f; - static const float tdm = 2.48f; - - return tdo + tdm*std::sin(fa * phase); - } - -} - namespace SceneUtil { LightController::LightController() : mType(LT_Normal) - , mPhase((Misc::Rng::rollClosedProbability() * 2.f - 1.f) * 500.f) - , mDeltaCount(0.f) - , mDirection(1.f) + , mPhase(0.25f + Misc::Rng::rollClosedProbability() * 0.75f) + , mBrightness(0.675f) + , mStartTime(0.0) , mLastTime(0.0) + , mTicksToAdvance(0.f) { } @@ -61,66 +29,40 @@ namespace SceneUtil void LightController::operator ()(osg::Node* node, osg::NodeVisitor* nv) { double time = nv->getFrameStamp()->getSimulationTime(); + if (mStartTime == 0) + mStartTime = time; // disabled early out, light state needs to be set every frame regardless of change, due to the double buffering //if (time == mLastTime) // return; - float dt = static_cast(time - mLastTime); - mLastTime = time; - - float brightness = 1.0f; - float cycle_time; - float time_distortion; - - if(mType == LT_Pulse || mType == LT_PulseSlow) + if (mType == LT_Normal) { - cycle_time = 2.0f * osg::PI; - time_distortion = 3.0f; + static_cast(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor); + traverse(node, nv); + return; } + + // Updating flickering at 15 FPS like vanilla. + constexpr float updateRate = 15.f; + mTicksToAdvance = static_cast(time - mStartTime - mLastTime) * updateRate * 0.25f + mTicksToAdvance * 0.75f; + mLastTime = time - mStartTime; + + float speed = (mType == LT_Flicker || mType == LT_Pulse) ? 0.1f : 0.05f; + if (mBrightness >= mPhase) + mBrightness -= mTicksToAdvance * speed; else - { - static const float fa = osg::PI / 4.0f; - static const float phase_wavelength = 120.0f * osg::PI / fa; + mBrightness += mTicksToAdvance * speed; - cycle_time = 500.0f; - mPhase = std::fmod(mPhase + dt, phase_wavelength); - time_distortion = flickerFrequency(mPhase); + if (std::abs(mBrightness - mPhase) < speed) + { + if (mType == LT_Flicker || mType == LT_FlickerSlow) + mPhase = 0.25f + Misc::Rng::rollClosedProbability() * 0.75f; + else // if (mType == LT_Pulse || mType == LT_PulseSlow) + mPhase = mPhase <= 0.5f ? 1.f : 0.25f; } - mDeltaCount += mDirection*dt*time_distortion; - if(mDirection > 0 && mDeltaCount > +cycle_time) - { - mDirection = -1.0f; - float extra = mDeltaCount - cycle_time; - mDeltaCount -= 2*extra; - } - if(mDirection < 0 && mDeltaCount < -cycle_time) - { - mDirection = +1.0f; - float extra = cycle_time - mDeltaCount; - mDeltaCount += 2*extra; - } - - static const float fast = 4.0f/1.0f; - static const float slow = 1.0f/1.0f; - - // These formulas are just guesswork, but they work pretty well - if(mType == LT_Normal) - { - // Less than 1/255 light modifier for a constant light: - brightness = 1.0f + flickerAmplitude(mDeltaCount*slow)/255.0f; - } - else if(mType == LT_Flicker) - brightness = 0.75f + flickerAmplitude(mDeltaCount*fast)*0.25f; - else if(mType == LT_FlickerSlow) - brightness = 0.75f + flickerAmplitude(mDeltaCount*slow)*0.25f; - else if(mType == LT_Pulse) - brightness = 0.7f + pulseAmplitude(mDeltaCount*fast)*0.3f; - else if(mType == LT_PulseSlow) - brightness = 0.7f + pulseAmplitude(mDeltaCount*slow)*0.3f; - - static_cast(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * brightness); + static_cast(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness); traverse(node, nv); } diff --git a/components/sceneutil/lightcontroller.hpp b/components/sceneutil/lightcontroller.hpp index 8f70af343..3292ae541 100644 --- a/components/sceneutil/lightcontroller.hpp +++ b/components/sceneutil/lightcontroller.hpp @@ -32,9 +32,10 @@ namespace SceneUtil LightType mType; osg::Vec4f mDiffuseColor; float mPhase; - float mDeltaCount; - int mDirection; + float mBrightness; + double mStartTime; double mLastTime; + float mTicksToAdvance; }; } diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index a1dc84994..e9be05908 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -22,29 +22,35 @@ namespace SceneUtil float linearAttenuation = 0.f; float constantAttenuation = 0.f; - const bool useConstant = Fallback::Map::getBool("LightAttenuation_UseConstant"); - if (useConstant) - { - constantAttenuation = Fallback::Map::getFloat("LightAttenuation_ConstantValue"); - } + static const bool useConstant = Fallback::Map::getBool("LightAttenuation_UseConstant"); + static const bool useLinear = Fallback::Map::getBool("LightAttenuation_UseLinear"); + static const bool useQuadratic = Fallback::Map::getBool("LightAttenuation_UseQuadratic"); + static const float constantValue = Fallback::Map::getFloat("LightAttenuation_ConstantValue"); + static const float linearValue = Fallback::Map::getFloat("LightAttenuation_LinearValue"); + static const float quadraticValue = Fallback::Map::getFloat("LightAttenuation_QuadraticValue"); + static const float linearRadiusMult = Fallback::Map::getFloat("LightAttenuation_LinearRadiusMult"); + static const float quadraticRadiusMult = Fallback::Map::getFloat("LightAttenuation_QuadraticRadiusMult"); + static const int linearMethod = Fallback::Map::getInt("LightAttenuation_LinearMethod"); + static const int quadraticMethod = Fallback::Map::getInt("LightAttenuation_QuadraticMethod"); + static const bool outQuadInLin = Fallback::Map::getBool("LightAttenuation_OutQuadInLin"); + + if (useConstant) + constantAttenuation = constantValue; - const bool useLinear = Fallback::Map::getBool("LightAttenuation_UseLinear"); if (useLinear) { - const float linearValue = Fallback::Map::getFloat("LightAttenuation_LinearValue"); - const float linearRadiusMult = Fallback::Map::getFloat("LightAttenuation_LinearRadiusMult"); + linearAttenuation = linearMethod == 0 ? linearValue : 0.01f; float r = radius * linearRadiusMult; - if (r) linearAttenuation = linearValue / r; + if (r && (linearMethod == 1 || linearMethod == 2)) + linearAttenuation = linearValue / std::pow(r, linearMethod); } - const bool useQuadratic = Fallback::Map::getBool("LightAttenuation_UseQuadratic"); - const bool outQuadInLin = Fallback::Map::getBool("LightAttenuation_OutQuadInLin"); if (useQuadratic && (!outQuadInLin || isExterior)) { - const float quadraticValue = Fallback::Map::getFloat("LightAttenuation_QuadraticValue"); - const float quadraticRadiusMult = Fallback::Map::getFloat("LightAttenuation_QuadraticRadiusMult"); + quadraticAttenuation = quadraticMethod == 0 ? quadraticValue : 0.01f; float r = radius * quadraticRadiusMult; - if (r) quadraticAttenuation = quadraticValue / std::pow(r, 2); + if (r && (quadraticMethod == 1 || quadraticMethod == 2)) + quadraticAttenuation = quadraticValue / std::pow(r, quadraticMethod); } light->setConstantAttenuation(constantAttenuation); diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index a9857bed3..ff46329e5 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -1,10 +1,151 @@ #include "util.hpp" +#include +#include +#include + #include +#include +#include +#include + +#include +#include namespace SceneUtil { +class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor +{ +public: + FindLowestUnusedTexUnitVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mLowestUnusedTexUnit(0) + { + } + + virtual void apply(osg::Node& node) + { + if (osg::StateSet* stateset = node.getStateSet()) + mLowestUnusedTexUnit = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size())); + + traverse(node); + } + int mLowestUnusedTexUnit; +}; + +GlowUpdater::GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector >& textures, + osg::Node* node, float duration, Resource::ResourceSystem* resourcesystem) + : mTexUnit(texUnit) + , mColor(color) + , mOriginalColor(color) + , mTextures(textures) + , mNode(node) + , mDuration(duration) + , mOriginalDuration(duration) + , mStartingTime(0) + , mResourceSystem(resourcesystem) + , mColorChanged(false) + , mDone(false) +{ +} + +void GlowUpdater::setDefaults(osg::StateSet *stateset) +{ + if (mDone) + removeTexture(stateset); + else + { + stateset->setTextureMode(mTexUnit, GL_TEXTURE_2D, osg::StateAttribute::ON); + osg::TexGen* texGen = new osg::TexGen; + texGen->setMode(osg::TexGen::SPHERE_MAP); + + stateset->setTextureAttributeAndModes(mTexUnit, texGen, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + osg::TexEnvCombine* texEnv = new osg::TexEnvCombine; + texEnv->setSource0_RGB(osg::TexEnvCombine::CONSTANT); + texEnv->setConstantColor(mColor); + texEnv->setCombine_RGB(osg::TexEnvCombine::INTERPOLATE); + texEnv->setSource2_RGB(osg::TexEnvCombine::TEXTURE); + texEnv->setOperand2_RGB(osg::TexEnvCombine::SRC_COLOR); + + stateset->setTextureAttributeAndModes(mTexUnit, texEnv, osg::StateAttribute::ON); + stateset->addUniform(new osg::Uniform("envMapColor", mColor)); + } +} + +void GlowUpdater::removeTexture(osg::StateSet* stateset) +{ + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXTURE); + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXGEN); + stateset->removeTextureAttribute(mTexUnit, osg::StateAttribute::TEXENV); + stateset->removeTextureMode(mTexUnit, GL_TEXTURE_2D); + stateset->removeUniform("envMapColor"); + + osg::StateSet::TextureAttributeList& list = stateset->getTextureAttributeList(); + while (list.size() && list.rbegin()->empty()) + list.pop_back(); +} + +void GlowUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) +{ + if (mColorChanged){ + this->reset(); + setDefaults(stateset); + mColorChanged = false; + } + if (mDone) + return; + + // Set the starting time to measure glow duration from if this is a temporary glow + if ((mDuration >= 0) && mStartingTime == 0) + mStartingTime = nv->getFrameStamp()->getSimulationTime(); + + float time = nv->getFrameStamp()->getSimulationTime(); + int index = (int)(time*16) % mTextures.size(); + stateset->setTextureAttribute(mTexUnit, mTextures[index], osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + + if ((mDuration >= 0) && (time - mStartingTime > mDuration)) // If this is a temporary glow and it has finished its duration + { + if (mOriginalDuration >= 0) // if this glowupdater was a temporary glow since its creation + { + removeTexture(stateset); + this->reset(); + mDone = true; + mResourceSystem->getSceneManager()->recreateShaders(mNode); + } + if (mOriginalDuration < 0) // if this glowupdater was originally a permanent glow + { + mDuration = mOriginalDuration; + mStartingTime = 0; + mColor = mOriginalColor; + this->reset(); + setDefaults(stateset); + } + } +} + +bool GlowUpdater::isPermanentGlowUpdater() +{ + return (mDuration < 0); +} + +bool GlowUpdater::isDone() +{ + return mDone; +} + +void GlowUpdater::setColor(const osg::Vec4f& color) +{ + mColor = color; + mColorChanged = true; +} + +void GlowUpdater::setDuration(float duration) +{ + mDuration = duration; +} + void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere) { osg::BoundingSphere::vec_type xdash = bsphere._center; @@ -73,4 +214,48 @@ bool hasUserDescription(const osg::Node* node, const std::string pattern) return false; } +osg::ref_ptr addEnchantedGlow(osg::ref_ptr node, Resource::ResourceSystem* resourceSystem, osg::Vec4f glowColor, float glowDuration) +{ + std::vector > textures; + for (int i=0; i<32; ++i) + { + std::stringstream stream; + stream << "textures/magicitem/caust"; + stream << std::setw(2); + stream << std::setfill('0'); + stream << i; + stream << ".dds"; + + osg::ref_ptr image = resourceSystem->getImageManager()->getImage(stream.str()); + osg::ref_ptr tex (new osg::Texture2D(image)); + tex->setName("envMap"); + tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT); + resourceSystem->getSceneManager()->applyFilterSettings(tex); + textures.push_back(tex); + } + + FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor; + node->accept(findLowestUnusedTexUnitVisitor); + int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit; + + osg::ref_ptr glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, resourceSystem); + node->addUpdateCallback(glowUpdater); + + // set a texture now so that the ShaderVisitor can find it + osg::ref_ptr writableStateSet = nullptr; + if (!node->getStateSet()) + writableStateSet = node->getOrCreateStateSet(); + else + { + writableStateSet = new osg::StateSet(*node->getStateSet(), osg::CopyOp::SHALLOW_COPY); + node->setStateSet(writableStateSet); + } + writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON); + writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor)); + resourceSystem->getSceneManager()->recreateShaders(node); + + return glowUpdater; +} + } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index f293b9975..5cc8e3a9d 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -3,10 +3,48 @@ #include #include +#include +#include #include +#include + +#include "statesetupdater.hpp" + namespace SceneUtil { + class GlowUpdater : public SceneUtil::StateSetUpdater + { + public: + GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector >& textures, + osg::Node* node, float duration, Resource::ResourceSystem* resourcesystem); + + virtual void setDefaults(osg::StateSet *stateset); + + void removeTexture(osg::StateSet* stateset); + virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv); + + bool isPermanentGlowUpdater(); + + bool isDone(); + + void setColor(const osg::Vec4f& color); + + void setDuration(float duration); + + private: + int mTexUnit; + osg::Vec4f mColor; + osg::Vec4f mOriginalColor; // for restoring the color of a permanent glow after a temporary glow on the object finishes + std::vector > mTextures; + osg::Node* mNode; + float mDuration; + float mOriginalDuration; // for recording that this is originally a permanent glow if it is changed to a temporary one + float mStartingTime; + Resource::ResourceSystem* mResourceSystem; + bool mColorChanged; + bool mDone; + }; // Transform a bounding sphere by a matrix // based off private code in osg::Transform @@ -20,6 +58,8 @@ namespace SceneUtil float makeOsgColorComponent (unsigned int value, unsigned int shift); bool hasUserDescription(const osg::Node* node, const std::string pattern); + + osg::ref_ptr addEnchantedGlow(osg::ref_ptr node, Resource::ResourceSystem* resourceSystem, osg::Vec4f glowColor, float glowDuration=-1); } #endif diff --git a/docs/source/reference/modding/extended.rst b/docs/source/reference/modding/extended.rst index 9d71f384d..c17afcf79 100644 --- a/docs/source/reference/modding/extended.rst +++ b/docs/source/reference/modding/extended.rst @@ -203,8 +203,79 @@ Shield holstering is not supported at the moment since it conflicts with any mod An example of a mod which uses this feature is `Weapon Sheathing`_. + +Skeleton extensions +------------------- + +It is possible to inject custom bones into actor skeletons: + +:: + + [Game] + use additional anim sources = true + +If this setting is enabled, OpenMW will seek for modified skeletons in the ``Animations/[skeleton name]`` folder in your ``Data Files``. +For example, the biped creature skeleton folder is ``Animations/xbase_anim``, the female NPCs skeleton folder is ``Animations/xbase_anim_female``, +the beast race skeleton folder is ``Animations/xbase_anim_kna``. +Note that these are the third person view skeletons, and the first person view skeleton will have a different name. + +OpenMW scans every NIF file in such a folder for nodes which have "BONE" NiStringExtraData. +It is recommended to give such nodes names that start with "Bip01 " so that the mesh optimizer doesn't try to optimize them out. +Then OpenMW copies all found nodes to related skeleton. To determine the bone to which the new node should be attached, +OpenMW checks the name of the parent node of the new node in the original NIF file. +For example, to attach a custom weapon bone, you'll need to follow this NIF record hierarchy: + +:: + +NiNode "root" + NiNode "Bip01 L Hand" + NiNode "Weapon Bone Left" + NiStringExtraData "BONE" + +OpenMW will detect ``Weapon Bone Left`` node and attach it to ``Bip01 L Hand`` bone of the target skeleton. + +An example of a mod which uses this feature is `Weapon Sheathing`_. + + +Extended weapon animations +-------------------------- + +It is possible to use unique animation groups for different weapon types. +They are not mandatory, and the currently hardcoded weapon types will fall back to existing generic animations. +Every weapon type has an attack animation group and a suffix for the movement animation groups. +For example, long blades use ``weapononehand`` attack animation group, ``idle1h`` idle animation group, ``jump1h`` jumping animation group, etc. +This is the full table of supported animation groups: + ++---------------+-------------------+------------------+----------------------+-----------------------+ +| Weapon type | Animation group | Movement suffix | Attack (fallback) | Suffix (fallback) | ++===============+===================+==================+======================+=======================+ +| Short blade | shortbladeonehand | 1s | weapononehand | 1h | ++---------------+-------------------+------------------+----------------------+-----------------------+ +| Long blade 1H | weapononehand | 1h | | | ++---------------+-------------------+------------------+----------------------+-----------------------+ +| Long blade 2H | weapontwohand | 2c | | | ++---------------+-------------------+------------------+----------------------+-----------------------+ +| Blunt 1H | bluntonehand | 1b | weapononehand | 1h | ++---------------+-------------------+------------------+----------------------+-----------------------+ +| Blunt 2H | blunttwohand | 2b | weapontwohand | 2c | ++---------------+-------------------+------------------+----------------------+-----------------------+ +| Axe 1H | bluntonehand | 1b | weapononehand | 1h | ++---------------+-------------------+------------------+----------------------+-----------------------+ +| Axe 2H | blunttwohand | 2b | weapontwohand | 2c | ++---------------+-------------------+------------------+----------------------+-----------------------+ +| Blunt 2H wide | weapontwowide | 2w | weapontwohand | 2c | ++---------------+-------------------+------------------+----------------------+-----------------------+ +| Spear | weapontwowide | 2w | weapontwohand | 2c | ++---------------+-------------------+------------------+----------------------+-----------------------+ +| Bow | bowandarrow | bow | | 1h | ++---------------+-------------------+------------------+----------------------+-----------------------+ +| Crossbow | crossbow | crossbow | | 1h | ++---------------+-------------------+------------------+----------------------+-----------------------+ +| Thrown | throwweapon | 1t | | 1h | ++---------------+-------------------+------------------+----------------------+-----------------------+ + .. _`Graphic Herbalism`: https://www.nexusmods.com/morrowind/mods/46599 .. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232 .. _`Glow in the Dahrk`: https://www.nexusmods.com/morrowind/mods/45886 .. _`Almalexia's Cast for Beasts`: https://www.nexusmods.com/morrowind/mods/45853 -.. _`Weapon sheathing`: https://www.nexusmods.com/morrowind/mods/46069` +.. _`Weapon sheathing`: https://www.nexusmods.com/morrowind/mods/46069 diff --git a/extern/recastnavigation/.gitignore b/extern/recastnavigation/.gitignore index 98f17e4b7..7c12d58f0 100644 --- a/extern/recastnavigation/.gitignore +++ b/extern/recastnavigation/.gitignore @@ -9,16 +9,6 @@ *.so *.idb -## Linux exes have no extension -RecastDemo/Bin/RecastDemo -RecastDemo/Bin/Tests - -# Build directory -RecastDemo/Build - -# Ignore meshes -RecastDemo/Bin/Meshes/* - ## Logs and databases # *.log *.sql @@ -38,9 +28,6 @@ Thumbs.db ## xcode specific *xcuserdata* -## SDL contrib -RecastDemo/Contrib/SDL/* - ## Generated doc files Docs/html diff --git a/extern/recastnavigation/.travis.yml b/extern/recastnavigation/.travis.yml deleted file mode 100644 index 0e63abad1..000000000 --- a/extern/recastnavigation/.travis.yml +++ /dev/null @@ -1,72 +0,0 @@ -language: cpp -branches: - only: - - master - - coverity_scan - - /recast-.*$/ - -sudo: false - -addons: - apt: - sources: - - ubuntu-toolchain-r-test - - llvm-toolchain-xenial-7 - packages: [ cmake, clang-7, clang-tools-7, gcc-8, g++-8, libsdl2-dev ] - -matrix: - include: - - name: Recastnavigation (all) on MacOS xcode9.4 - os: osx - osx_image: xcode9.4 - before_install: - - brew update - - brew install sdl2 - if: branch != coverity_scan - - name: Recastnavigation on Ubuntu Xenial GCC-5 - os: linux - dist: xenial - sudo: required - if: branch != coverity_scan - - name: Recastnavigation on Ubuntu Xenial GCC-8 - os: linux - dist: xenial - sudo: required - env: - - MATRIX_EVAL="CC=gcc-8 && CXX=g++-8" - if: branch != coverity_scan - - name: Recastnavigation on Ubuntu Xenial GCC-5 using Premake5 - os: linux - dist: xenial - sudo: required - if: branch != coverity_scan - before_install: - - wget https://github.com/premake/premake-core/releases/download/v5.0.0-alpha12/premake-5.0.0-alpha12-linux.tar.gz -O premake.tar.gz - - tar -xf premake.tar.gz - env: - - PREMAKE=1 - - name: Recastnavigation on Ubuntu Xenial Clang-7 with Static Analysis - os: linux - dist: xenial - sudo: required - env: - - MATRIX_EVAL="CC=clang-7 && CXX=clang++-7" - - ANALYZE="scan-build-7 --force-analyze-debug-code --use-cc clang-7 --use-c++ clang++-7" - if: branch != coverity_scan - compiler: clang - - name: Recastnavigation Coverity Scan - os: linux - dist: xenial - sudo: required - if: branch = coverity_scan - -before_script: - - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then eval "${MATRIX_EVAL}"; fi - - if [ "${PREMAKE}" = "1" ]; then cd RecastDemo && ../premake5 gmake && cd ..; fi - - if [ "${PREMAKE}" != "1" ]; then mkdir -p build && cd build && ${ANALYZE} cmake ../ && cd ..; fi - -script: # 2 CPUs on Travis-CI + 1 extra for IO bound process - - if [ "${PREMAKE}" = "1" ]; then make -C RecastDemo/Build/gmake -j3; fi - - if [ "${PREMAKE}" != "1" ]; then make -C build -j3; fi - - if [ "${PREMAKE}" = "1" ]; then RecastDemo/Bin/Tests; fi - - if [ "${PREMAKE}" != "1" ]; then cd build && ctest; fi diff --git a/extern/recastnavigation/CMakeLists.txt b/extern/recastnavigation/CMakeLists.txt index d23859dfc..4952e51da 100644 --- a/extern/recastnavigation/CMakeLists.txt +++ b/extern/recastnavigation/CMakeLists.txt @@ -6,9 +6,6 @@ project(RecastNavigation) SET(SOVERSION 1) SET(VERSION 1.0.0) -option(RECASTNAVIGATION_DEMO "Build demo" ON) -option(RECASTNAVIGATION_TESTS "Build tests" ON) -option(RECASTNAVIGATION_EXAMPLES "Build examples" ON) option(RECASTNAVIGATION_STATIC "Build static libraries" ON) add_subdirectory(DebugUtils) @@ -16,12 +13,3 @@ add_subdirectory(Detour) add_subdirectory(DetourCrowd) add_subdirectory(DetourTileCache) add_subdirectory(Recast) - -if (RECASTNAVIGATION_DEMO) - add_subdirectory(RecastDemo) -endif () - -if (RECASTNAVIGATION_TESTS) - enable_testing() - add_subdirectory(Tests) -endif () diff --git a/extern/recastnavigation/CONTRIBUTING.md b/extern/recastnavigation/CONTRIBUTING.md deleted file mode 100644 index 3cfdc2160..000000000 --- a/extern/recastnavigation/CONTRIBUTING.md +++ /dev/null @@ -1,185 +0,0 @@ -# Contributing to Recast and Detour - -We'd love for you to contribute to our source code and to make Recast and Detour even better than they are -today! Here are the guidelines we'd like you to follow: - - - [Code of Conduct](#coc) - - [Question or Problem?](#question) - - [Issues and Bugs](#issue) - - [Feature Requests](#feature) - - [Submission Guidelines](#submission-guidelines) - - [Git Commit Guidelines](#git-commit-guidelines) - -## Code of Conduct -This project adheres to the [Open Code of Conduct][code-of-conduct]. -By participating, you are expected to honor this code. - -## Got a Question or Problem? - -If you have questions about how to use Recast or Detour, please direct these to the [Google Group][groups] -discussion list. We are also available on [Gitter][gitter]. - -## Found an Issue? -If you find a bug in the source code or a mistake in the documentation, you can help us by -submitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request -with a fix. - -**Please see the Submission Guidelines below**. - -## Want a Feature? -You can request a new feature by submitting an issue to our [GitHub Repository][github]. If you -would like to implement a new feature then consider what kind of change it is: - -* **Major Changes** that you wish to contribute to the project should be discussed first on our -[Google Group][groups] or in [GitHub Issues][github-issues] so that we can better coordinate our efforts, prevent -duplication of work, and help you to craft the change so that it is successfully accepted into the -project. -* **Small Changes** can be crafted and submitted to the [GitHub Repository][github] as a Pull Request. - -## Submission Guidelines - -### Submitting an Issue -Before you submit your issue search the [GitHub Issues][github-issues] archive, -maybe your question was already answered. - -If your issue appears to be a bug, and hasn't been reported, open a new issue. -Help us to maximize the effort we can spend fixing issues and adding new -features, by not reporting duplicate issues. Providing the following information will increase the -chances of your issue being dealt with quickly: - -* **Overview of the Issue** - what type of issue is it, and why is it an issue for you? -* **Callstack** - if it's a crash or other runtime error, a callstack will help diagnosis -* **Screenshots** - for navmesh generation problems, a picture really is worth a thousand words. - Implement `duDebugDraw` and call some methods from DetourDebugDraw.h. Seriously, just do it, we'll definitely ask you to if you haven't! -* **Logs** - stdout and stderr from the console, or log files if there are any. - If integrating into your own codebase, be sure to implement the log callbacks in `rcContext`. -* **Reproduction steps** - a minimal, unambigious set of steps including input, that causes the error for you. - e.g. input geometry and settings you can use to input into RecastDemo to get it to fail. - Note: These can be saved by pressing the 9 key in RecastDemo, and the resulting .gset file can be shared (with the .obj if it is not one of the default ones). -* **Recast version(s) and/or git commit hash** - particularly if you can find the point at which the error first started happening -* **Environment** - operating system, compiler etc. -* **Related issues** - has a similar issue been reported before? -* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be - causing the problem (line of code or commit) - -Here is a great example of a well defined issue: https://github.com/recastnavigation/recastnavigation/issues/12 - -**If you get help, help others. Good karma rulez!** - -### Submitting a Pull Request -Before you submit your pull request consider the following guidelines: - -* Search [GitHub Pull Requests][github-pulls] for an open or closed Pull Request - that relates to your submission. You don't want to duplicate effort. -* Make your changes in a new git branch: - - ```shell - git checkout -b my-fix-branch master - ``` - -* Implement your changes, **including appropriate tests if appropriate/possible**. -* Commit your changes using a descriptive commit message that follows our - [commit message conventions](#commit-message-format). - - ```shell - git commit -a - ``` - Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. - -* Squash any work-in-progress commits (by rebasing) to form a series of commits that make sense individually. - Ideally the pull request will be small and focused enough that it fits sensibly in one commit. - - ```shell - git rebase -i origin/master - ``` - -* Push your branch to GitHub: - - ```shell - git push origin my-fix-branch - ``` - -* In GitHub, send a pull request to `recastnavigation:master`. -* If we suggest changes then: - * Make the required updates. - * Commit your changes to your branch (e.g. `my-fix-branch`). - * Squash the changes, overwriting history in your fix branch - we don't want history to include incomplete work. - * Push the changes to your GitHub repository (this will update your Pull Request). - -If you have rebased to squash commits together, you will need to force push to update the PR: - - ```shell - git rebase master -i - git push origin my-fix-branch -f - ``` - -That's it! Thank you for your contribution! - -#### After your pull request is merged - -After your pull request is merged, you can safely delete your branch and pull the changes -from the main (upstream) repository: - -* Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows: - - ```shell - git push origin --delete my-fix-branch - ``` - -* Check out the master branch: - - ```shell - git checkout master -f - ``` - -* Delete the local branch: - - ```shell - git branch -D my-fix-branch - ``` - -* Update your master with the latest upstream version: - - ```shell - git pull --ff upstream master - ``` - -## Git Commit Guidelines - -### Commit content - -Do your best to factor commits appropriately, i.e not too large with unrelated -things in the same commit, and not too small with the same small change applied N -times in N different commits. If there was some accidental reformatting or whitespace -changes during the course of your commits, please rebase them away before submitting -the PR. - -### Commit Message Format -Please format commit messages as follows (based on this [excellent post](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)): - -``` -Summarize change in 50 characters or less - -Provide more detail after the first line. Leave one blank line below the -summary and wrap all lines at 72 characters or less. - -If the change fixes an issue, leave another blank line after the final -paragraph and indicate which issue is fixed in the specific format -below. - -Fix #42 -``` - -Important things you should try to include in commit messages include: -* Motivation for the change -* Difference from previous behaviour -* Whether the change alters the public API, or affects existing behaviour significantly - - - -[code-of-conduct]: http://todogroup.org/opencodeofconduct/#Recastnavigation/b.hymers@gmail.com -[github]: https://github.com/recastnavigation/recastnavigation -[github-issues]: https://github.com/recastnavigation/recastnavigation/issues -[github-pulls]: https://github.com/recastnavigation/recastnavigation/pulls -[gitter]: https://gitter.im/recastnavigation/chat -[groups]: https://groups.google.com/forum/?fromgroups#!forum/recastnavigation diff --git a/extern/recastnavigation/Docs/Conceptual/license_c.txt b/extern/recastnavigation/Docs/Conceptual/license_c.txt deleted file mode 100644 index c2a3c1e85..000000000 --- a/extern/recastnavigation/Docs/Conceptual/license_c.txt +++ /dev/null @@ -1,27 +0,0 @@ - -/** -@page License License - -
-Copyright (c) 2009-2011 Mikko Mononen memon@inside.org
-
-This software is provided 'as-is', without any express or implied
-warranty.  In no event will the authors be held liable for any damages
-arising from the use of this software.
-
-Permission is granted to anyone to use this software for any purpose,
-including commercial applications, and to alter it and redistribute it
-freely, subject to the following restrictions:
-
-1. The origin of this software must not be misrepresented; you must not
-   claim that you wrote the original software. If you use this software
-   in a product, an acknowledgment in the product documentation would be
-   appreciated but is not required.
-
-2. Altered source versions must be plainly marked as such, and must not be
-   misrepresented as being the original software.
-
-3. This notice may not be removed or altered from any source distribution.
-
- -*/ \ No newline at end of file diff --git a/extern/recastnavigation/Docs/Conceptual/mainpage_c.txt b/extern/recastnavigation/Docs/Conceptual/mainpage_c.txt deleted file mode 100644 index 14dd1cf8f..000000000 --- a/extern/recastnavigation/Docs/Conceptual/mainpage_c.txt +++ /dev/null @@ -1,109 +0,0 @@ -/// @mainpage Recast Navigation -/// -/// @image html recast_intro.png -/// -///

Recast

-/// -/// _Recast_ is a state of the art navigation mesh construction toolset for -/// games. -/// -/// - It is automatic, which means that you can throw any level -/// geometry at it and you will get a robust mesh out. -/// - It is fast, which means swift turnaround times for level designers. -/// - It is open source, so it comes with full source and you can -/// customize it to your hearts content. -/// -/// The latest version can be found on -/// GitHub. -/// -/// The _Recast_ process starts with constructing a voxel mold from level -/// geometry and then casting a navigation mesh over it. The process -/// consists of three steps: building the voxel mold, partitioning the -/// mold into simple regions, and triangulating the regions as simple polygons. -/// -/// -# The voxel mold is built from the input triangle mesh by -/// rasterizing the triangles into a multi-layer heightfield. Some -/// simple filters are then applied to the mold to prune out locations -/// where the character would not be able to move. -/// -# The walkable areas described by the mold are divided into simple -/// overlayed 2D regions. The resulting regions have only one -/// non-overlapping contour, which simplifies the final step of the -/// process tremendously. -/// -# The navigation polygons are generated from the regions by first -/// tracing the boundaries and then simplifying them. The resulting -/// polygons are finally converted to convex polygons which makes them -/// perfect for pathfinding and spatial reasoning about the level. -/// -///

Detour

-/// -/// _Recast_ is accompanied by _Detour_, a path-finding and spatial reasoning -/// toolkit. You can use any navigation mesh with _Detour_, but of course -/// the data generated by _Recast_ fits perfectly. -/// -/// _Detour_ offers a simple static navigation mesh that is suitable for -/// many simple cases, as well as a tiled navigation mesh that allows you -/// to add and remove pieces of the mesh. The tiled mesh allows you to -/// create systems where you stream new navigation data in and out as -/// the player progresses the level, or regenerate tiles as the -/// world changes. -/// -///

Recast Demo

-/// -/// You can find a comprehensive demo project in the `RecastDemo` folder. It -/// is a kitchen sink demo containing all the major functionality of the library. -/// If you are new to _Recast_ & _Detour_, check out -/// -/// Sample_SoloMesh.cpp to get started with building navmeshes and -/// -/// NavMeshTesterTool.cpp to see how _Detour_ can be used to find paths. -/// -///

Building RecastDemo

-/// -/// RecastDemo uses [premake5](http://premake.github.io/) to build platform specific projects. -/// Download it and make sure it's available on your path, or specify the path to it. -/// -///

Linux

-/// -/// - Install SDl2 and its dependencies according to your distro's guidelines. -/// - run `premake5 gmake` from the `RecastDemo` folder. -/// - `cd Build/gmake` then `make` -/// - Run `RecastDemo\Bin\RecastDemo` -/// -///

OSX

-/// -/// - Grab the latest SDL2 development library dmg from [here](https://www.libsdl.org/download-2.0.php) and place `SDL2.framework` in `/Library/Frameworks/` -/// - Navigate to the `RecastDemo` folder and run `premake5 xcode4` -/// - Open `Build/xcode4/recastnavigation.xcworkspace` -/// - Select the "RecastDemo" project in the left pane, go to the "BuildPhases" tab and expand "Link Binary With Libraries" -/// - Remove the existing entry for SDL2 (it should have a white box icon) and re-add it by hitting the plus, selecting "Add Other", and selecting `/Library/Frameworks/SDL2.framework`. It should now have a suitcase icon. -/// - Set the RecastDemo project as the target and build. -/// -///

Windows

-/// -/// - Grab the latest SDL2 development library release from [here](https://www.libsdl.org/download-2.0.php) and unzip it `RecastDemo\Contrib`. Rename the SDL folder such that the path `RecastDemo\Contrib\SDL\lib\x86` is valid. -/// - Run `"premake5" vs2015` from the `RecastDemo` folder -/// - Open the solution, build, and run. -/// -///

Integrating With Your Own Project

-/// -/// It is recommended to add the source directories `DebugUtils`, `Detour`, -/// `DetourCrowd`, `DetourTileCache`, and `Recast` into your own project -/// depending on which parts of the project you need. For example your -/// level building tool could include `DebugUtils`, `Recast`, and `Detour`, -/// and your game runtime could just include `Detour`. -/// -///

Contributing

-/// All development is centralized in github. Check out the Contributing Guidelines for more information. -/// -///

Discuss

-/// -/// - Discuss _Recast_ and _Detour_: -/// -/// Recast Navigation Group -/// - Development Blog: -/// Digesting Duck -/// -///

License

-/// -/// _Recast Navigation_ is licensed under the ZLib license. -/// diff --git a/extern/recastnavigation/Docs/DoxygenLayout.xml b/extern/recastnavigation/Docs/DoxygenLayout.xml deleted file mode 100644 index b20079c91..000000000 --- a/extern/recastnavigation/Docs/DoxygenLayout.xml +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/extern/recastnavigation/Docs/Extern/Recast_api.txt b/extern/recastnavigation/Docs/Extern/Recast_api.txt deleted file mode 100644 index f6ff06444..000000000 --- a/extern/recastnavigation/Docs/Extern/Recast_api.txt +++ /dev/null @@ -1,587 +0,0 @@ -// This file contains the detail API documentation for -// elements defined in the Recast.h. - -/** - -@defgroup recast Recast - -Members in this module are used to create mesh data that is then -used to create Detour navigation meshes. - -The are a large number of possible ways to building navigation mesh data. -One of the simple piplines is as follows: - --# Prepare the input triangle mesh. --# Build a #rcHeightfield. --# Build a #rcCompactHeightfield. --# Build a #rcContourSet. --# Build a #rcPolyMesh. --# Build a #rcPolyMeshDetail. --# Use the rcPolyMesh and rcPolyMeshDetail to build a Detour navigation mesh - tile. - -The general life-cycle of the main classes is as follows: - --# Allocate the object using the Recast allocator. (E.g. #rcAllocHeightfield) --# Initialize or build the object. (E.g. #rcCreateHeightfield) --# Update the object as needed. (E.g. #rcRasterizeTriangles) --# Use the object as part of the pipeline. --# Free the object using the Recast allocator. (E.g. #rcFreeHeightField) - -@note This is a summary list of members. Use the index or search -feature to find minor members. - -@struct rcConfig -@par - -The is a convenience structure that represents an aggregation of parameters -used at different stages in the Recast build process. Some -values are derived during the build process. Not all parameters -are used for all build processes. - -Units are usually in voxels (vx) or world units (wu). The units for voxels, -grid size, and cell size are all based on the values of #cs and #ch. - -In this documentation, the term 'field' refers to heightfield and -contour data structures that define spacial information using an integer -grid. - -The upper and lower limits for the various parameters often depend on -the platform's floating point accuraccy as well as interdependencies between -the values of multiple parameters. See the individual parameter -documentation for details. - -@var rcConfig::borderSize -@par - -This value represents the the closest the walkable area of the heightfield -should come to the xz-plane AABB of the field. It does not have any -impact on the borders around internal obstructions. - -@var rcConfig::tileSize -@par - -This field is only used when building multi-tile meshes. - -@var rcConfig::cs -@par - -@p cs and #ch define voxel/grid/cell size. So their values have significant -side effects on all parameters defined in voxel units. - -The minimum value for this parameter depends on the platform's floating point -accuracy, with the practical minimum usually around 0.05. - -@var rcConfig::ch -@par - -#cs and @p ch define voxel/grid/cell size. So their values have significant -side effects on all parameters defined in voxel units. - -The minimum value for this parameter depends on the platform's floating point -accuracy, with the practical minimum usually around 0.05. - -@var rcConfig::walkableSlopeAngle -@par - -The practical upper limit for this parameter is usually around 85 degrees. - -@var rcConfig::walkableHeight -@par - -Permits detection of overhangs in the source geometry that make the geometry -below un-walkable. The value is usually set to the maximum agent height. - -@var rcConfig::walkableClimb -@par - -Allows the mesh to flow over low lying obstructions such as curbs and -up/down stairways. The value is usually set to how far up/down an agent can step. - -@var rcConfig::walkableRadius -@par - -In general, this is the closest any part of the final mesh should get to an -obstruction in the source geometry. It is usually set to the maximum -agent radius. - -While a value of zero is legal, it is not recommended and can result in -odd edge case issues. - -@var rcConfig::maxEdgeLen -@par - -Extra vertices will be inserted as needed to keep contour edges below this -length. A value of zero effectively disables this feature. - -@var rcConfig::maxSimplificationError -@par - -The effect of this parameter only applies to the xz-plane. - -@var rcConfig::minRegionArea -@par - -Any regions that are smaller than this area will be marked as unwalkable. -This is useful in removing useless regions that can sometimes form on -geometry such as table tops, box tops, etc. - -@var rcConfig::maxVertsPerPoly -@par - -If the mesh data is to be used to construct a Detour navigation mesh, then the upper limit -is limited to <= #DT_VERTS_PER_POLYGON. - - -@struct rcHeightfield -@par - -The grid of a heightfield is layed out on the xz-plane based on the -value of #cs. Spans exist within the grid columns with the span -min/max values at increments of #ch from the base of the grid. The smallest -possible span size is (#cs width) * (#cs depth) * (#ch height). (Which is a single voxel.) - -The standard process for buidling a heightfield is to allocate it using -#rcAllocHeightfield, initialize it using #rcCreateHeightfield, then -add spans using the various helper functions such as #rcRasterizeTriangle. - -Building a heightfield is one of the first steps in creating a polygon mesh -from source geometry. After it is populated, it is used to build a -rcCompactHeightfield. - -Example of iterating the spans in a heightfield: -@code -// Where hf is a reference to an heightfield object. - -const float* orig = hf.bmin; -const float cs = hf.cs; -const float ch = hf.ch; - -const int w = hf.width; -const int h = hf.height; - -for (int y = 0; y < h; ++y) -{ - for (int x = 0; x < w; ++x) - { - // Deriving the minimum corner of the grid location. - float fx = orig[0] + x*cs; - float fz = orig[2] + y*cs; - // The base span in the column. (May be null.) - const rcSpan* s = hf.spans[x + y*w]; - while (s) - { - // Detriving the minium and maximum world position of the span. - float fymin = orig[1]+s->smin*ch; - float fymax = orig[1] + s->smax*ch; - // Do other things with the span before moving up the column. - s = s->next; - } - } -} -@endcode - -@see rcAllocHeightfield, rcFreeHeightField, rcCreateHeightfield - -@struct rcCompactCell -@par - -See the rcCompactHeightfield documentation for an example of how compact cells -are used to iterate the heightfield. - -Useful instances of this type can only by obtained from a #rcCompactHeightfield object. - -@see rcCompactHeightfield - -@struct rcCompactSpan -@par - -The span represents open, unobstructed space within a compact heightfield column. -See the rcCompactHeightfield documentation for an example of iterating spans and searching -span connections. - -Useful instances of this type can only by obtained from a #rcCompactHeightfield object. - -@see rcCompactHeightfield - - -@struct rcCompactHeightfield -@par - -For this type of heightfield, the spans represent the open (unobstructed) -space above the solid surfaces of a voxel field. It is usually created from -a #rcHeightfield object. Data is stored in a compact, efficient manner, -but the structure is not condusive to adding and removing spans. - -The standard process for buidling a compact heightfield is to allocate it -using #rcAllocCompactHeightfield, build it using #rcBuildCompactHeightfield, -then run it through the various helper functions to generate neighbor -and region data. - -Connected neighbor spans form non-overlapping surfaces. When neighbor -information is generated, spans will include data that can be used to -locate axis-neighbors. Axis-neighbors are connected -spans that are offset from the current cell column as follows: -
-Direction 0 = (-1, 0)
-Direction 1 = (0, 1)
-Direction 2 = (1, 0)
-Direction 3 = (0, -1)
-
- -Example of iterating and inspecting spans, including connected neighbors: - -@code -// Where chf is an instance of a rcCompactHeightfield. - -const float cs = chf.cs; -const float ch = chf.ch; - -for (int y = 0; y < chf.height; ++y) -{ - for (int x = 0; x < chf.width; ++x) - { - // Deriving the minimum corner of the grid location. - const float fx = chf.bmin[0] + x*cs; - const float fz = chf.bmin[2] + y*cs; - - // Get the cell for the grid location then iterate - // up the column. - const rcCompactCell& c = chf.cells[x+y*chf.width]; - for (unsigned i = c.index, ni = c.index+c.count; i < ni; ++i) - { - const rcCompactSpan& s = chf.spans[i]; - - Deriving the minimum (floor) of the span. - const float fy = chf.bmin[1] + (s.y+1)*ch; - - // Testing the area assignment of the span. - if (chf.areas[i] == RC_WALKABLE_AREA) - { - // The span is in the default 'walkable area'. - } - else if (chf.areas[i] == RC_NULL_AREA) - { - // The surface is not considered walkable. - // E.g. It was filtered out during the build processes. - } - else - { - // Do something. (Only applicable for custom build - // build processes.) - } - - // Iterating the connected axis-neighbor spans. - for (int dir = 0; dir < 4; ++dir) - { - if (rcGetCon(s, dir) != RC_NOT_CONNECTED) - { - // There is a neighbor in this direction. - const int nx = x + rcGetDirOffsetX(dir); - const int ny = y + rcGetDirOffsetY(dir); - const int ni = (int)chf.cells[nx+ny*w].index + rcGetCon(s, 0); - const rcCompactSpan& ns = chf.spans[ni]; - // Do something with the neighbor span. - } - } - } - } -} -@endcode - -@see rcAllocCompactHeightfield, rcFreeCompactHeightfield, rcBuildCompactHeightfield - -@struct rcContour -@par - -A contour only exists within the context of a #rcContourSet object. - -While the height of the contour's border may vary, the contour will always -form a simple polygon when projected onto the xz-plane. - -Example of converting vertices into world space: - -@code -// Where cset is the rcContourSet object to which the contour belongs. -float worldX = cset.bmin[0] + vertX * cset.cs; -float worldY = cset.bmin[1] + vertY * cset.ch; -float worldZ = cset.bmin[2] + vertZ * cset.cs; -@endcode - -@see rcContourSet - -@var rcContour::verts -@par - -The simplified contour is a version of the raw contour with all -'unnecessary' vertices removed. Whether a vertex is -considered unnecessary depends on the contour build process. - -The data format is as follows: (x, y, z, r) * #nverts - -A contour edge is formed by the current and next vertex. The r-value -represents region and connection information for the edge. For example: - -@code -int r = verts[i*4+3]; - -int regionId = r & RC_CONTOUR_REG_MASK; - -if (r & RC_BORDER_VERTEX) -{ - // The edge represents a solid border. -} - -if (r & RC_AREA_BORDER) -{ - // The edge represents a transition between different areas. -} -@endcode - -@var rcContour::rverts -@par - -See #verts for information on element layout. - -@struct rcContourSet -@par - -All contours within the set share the minimum bounds and cell sizes of the set. - -The standard process for building a contour set is to allocate it -using #rcAllocContourSet, then initialize it using #rcBuildContours. - -@see rcAllocContourSet, rcFreeContourSet, rcBuildContours - -@struct rcPolyMesh -@par - -A mesh of potentially overlapping convex polygons of between three -and #nvp vertices. The mesh exists within the context of an axis-aligned -bounding box (AABB) with vertices laid out in an evenly spaced grid, based -on the values of #cs and #ch. - -The standard process for building a contour set is to allocate it using -#rcAllocPolyMesh, the initialize it using #rcBuildPolyMesh - -Example of iterating the polygons: - -@code -// Where mesh is a reference to a rcPolyMesh object. - -const int nvp = mesh.nvp; -const float cs = mesh.cs; -const float ch = mesh.ch; -const float* orig = mesh.bmin; - -for (int i = 0; i < mesh.npolys; ++i) -{ - const unsigned short* p = &mesh.polys[i*nvp*2]; - - // Iterate the vertices. - unsigned short vi[3]; // The vertex indices. - for (int j = 0; j < nvp; ++j) - { - if (p[j] == RC_MESH_NULL_IDX) - break; // End of vertices. - - if (p[j + nvp] == RC_MESH_NULL_IDX) - { - // The edge beginning with this vertex is a solid border. - } - else - { - // The edge beginning with this vertex connects to - // polygon p[j + nvp]. - } - - // Convert to world space. - const unsigned short* v = &mesh.verts[p[j]*3]; - const float x = orig[0] + v[0]*cs; - const float y = orig[1] + v[1]*ch; - const float z = orig[2] + v[2]*cs; - // Do something with the vertices. - } -} -@endcode - -@see rcAllocPolyMesh, rcFreePolyMesh, rcBuildPolyMesh - -@var rcPolyMesh::verts -@par - -The values of #bmin ,#cs, and #ch are used to convert vertex coordinates -to world space as follows: - -@code -float worldX = bmin[0] + verts[i*3+0] * cs -float worldY = bmin[1] + verts[i*3+1] * ch -float worldZ = bmin[2] + verts[i*3+2] * cs -@endcode - -@var rcPolyMesh::polys -@par - -Each entry is 2 * #nvp in length. The first half of the entry -contains the indices of the polygon. The first instance of #RC_MESH_NULL_IDX -indicates the end of the indices for the entry. The second half contains -indices to neighbor polygons. A value of #RC_MESH_NULL_IDX indicates no -connection for the associated edge. (I.e. The edge is a solid border.) - -For example: -
-nvp = 6
-For the entry: (1, 3, 4, 8, RC_MESH_NULL_IDX, RC_MESH_NULL_IDX, 
-                18, RC_MESH_NULL_IDX , 21, RC_MESH_NULL_IDX, RC_MESH_NULL_IDX, RC_MESH_NULL_IDX)
-
-(1, 3, 4, 8) defines a polygon with 4 vertices.
-Edge 1->3 is shared with polygon 18.
-Edge 4->8 is shared with polygon 21.
-Edges 3->4 and 4->8 are border edges not shared with any other polygon.
-
- -@var rcPolyMesh::areas -@par - -The standard build process assigns the value of #RC_WALKABLE_AREA to all walkable polygons. -This value can then be changed to meet user requirements. - -@struct rcPolyMeshDetail -@par - -The detail mesh is made up of triangle sub-meshes that provide extra -height detail for each polygon in its assoicated polygon mesh. - -The standard process for building a detail mesh is to allocate it -using #rcAllocPolyMeshDetail, then build it using #rcBuildPolyMeshDetail. - -See the individual field definitions for details realted to the structure -the mesh. - -@see rcAllocPolyMeshDetail, rcFreePolyMeshDetail, rcBuildPolyMeshDetail, rcPolyMesh - -@var rcPolyMeshDetail::meshes -@par - -[(baseVertIndex, vertCount, baseTriIndex, triCount) * #nmeshes] - -Maximum number of vertices per sub-mesh: 127
-Maximum number of triangles per sub-mesh: 255 - -The sub-meshes are stored in the same order as the polygons from the -rcPolyMesh they represent. E.g. rcPolyMeshDetail sub-mesh 5 is associated -with #rcPolyMesh polygon 5. - -Example of iterating the triangles in a sub-mesh. - -@code -// Where dmesh is a reference to a rcPolyMeshDetail object. - -// Iterate the sub-meshes. (One for each source polygon.) -for (int i = 0; i < dmesh.nmeshes; ++i) -{ - const unsigned int* meshDef = &dmesh.meshes[i*4]; - const unsigned int baseVerts = meshDef[0]; - const unsigned int baseTri = meshDef[2]; - const int ntris = (int)meshDef[3]; - - const float* verts = &dmesh.verts[baseVerts*3]; - const unsigned char* tris = &dmesh.tris[baseTri*4]; - - // Iterate the sub-mesh's triangles. - for (int j = 0; j < ntris; ++j) - { - const float x = verts[tris[j*4+0]*3]; - const float y = verts[tris[j*4+1]*3]; - const float z = verts[tris[j*4+2]*3]; - // Do something with the vertex. - } -} -@endcode - -@var rcPolyMeshDetail::verts -@par - -[(x, y, z) * #nverts] - -The vertices are grouped by sub-mesh and will contain duplicates since -each sub-mesh is independently defined. - -The first group of vertices for each sub-mesh are in the same order as -the vertices for the sub-mesh's associated PolyMesh polygon. These -vertices are followed by any additional detail vertices. So it the -associated polygon has 5 vertices, the sub-mesh will have a minimum -of 5 vertices and the first 5 vertices will be equivalent to the 5 -polygon vertices. - -@var rcPolyMeshDetail::tris -@par - -[(vertIndexA, vertIndexB, vertIndexC, flags) * #ntris] - -The triangles are grouped by sub-mesh. - -Vertex Indices - -The vertex indices in the triangle array are local to the sub-mesh, not global. -To translate into an global index in the vertices array, the values must be -offset by the sub-mesh's base vertex index. - -Example: If the baseVertexIndex for the sub-mesh is 5 and the triangle entry -is (4, 8, 7, 0), then the actual indices for the vertices are (4 + 5, 8 + 5, 7 + 5). - -@b Flags - -The flags entry indicates which edges are internal and which are external to -the sub-mesh. Internal edges connect to other triangles within the same sub-mesh. -External edges represent portals to other sub-meshes or the null region. - -Each flag is stored in a 2-bit position. Where position 0 is the lowest 2-bits -and position 4 is the highest 2-bits: - - -Position 0: Edge AB (>> 0)
-Position 1: Edge BC (>> 2)
-Position 2: Edge CA (>> 4)
-Position 4: Unused
-
- -Testing can be performed as follows: - -@code -if (((flags >> 2) & 0x3) != 0) -{ - // Edge BC is an external edge. -} -@endcode - -@fn void rcSetCon(rcCompactSpan &s, int dir, int i) -@par - -This function is used by the build process. It is rarely of use to end users. - -@see #rcCompactHeightfield, #rcCompactSpan - -@fn int rcGetCon(const rcCompactSpan &s, int dir) -@par - -Can be used to locate neighbor spans in a compact heightfield. See the -#rcCompactHeightfield documentation for details on its use. - -@see #rcCompactHeightfield, #rcCompactSpan - -@fn int rcGetDirOffsetX(int dir) -@par - -The value of @p dir will be automatically wrapped. So a value of 6 will be interpreted as 2. - -See the #rcCompactHeightfield documentation for usage details. - -@fn int rcGetDirOffsetY(int dir) -@par - -The value of @p dir will be automatically wrapped. So a value of 6 will be interpreted as 2. - -See the #rcCompactHeightfield documentation for usage details. - -*/ diff --git a/extern/recastnavigation/Docs/Images/recast_intro.png b/extern/recastnavigation/Docs/Images/recast_intro.png deleted file mode 100644 index ad18ac610..000000000 Binary files a/extern/recastnavigation/Docs/Images/recast_intro.png and /dev/null differ diff --git a/extern/recastnavigation/Docs/Readme.txt b/extern/recastnavigation/Docs/Readme.txt deleted file mode 100644 index be153b94b..000000000 --- a/extern/recastnavigation/Docs/Readme.txt +++ /dev/null @@ -1,64 +0,0 @@ -This directory contains source for the documentation. It is also the -build target for doxygen output. - -Directory Layout - -. (Docs root) - - High level content and format files. (E.g. css, header, footer.) - -./Conceptual - - Conceptual (non-api) documentation such as overviews, how-to's, etc. - The main index page content is also in this directory. - -./Extern - - API documentation that is located outside the source files. - - When the API documentation gets too big or complex for the header - and source files, it goes in this directory. - -./Images - - Images related to the documentation. - -./html - - The target for the Doxygen build. (Created during the build process.) - -Miscellany - -One of the requirements for the API documentation is that it -has the minimum possible impact on the declarations in the -header files. So, in general, the header file declarations only -contain summary documentation. The detail documentation -is placed as follows: - -1. If an element is defined in a cpp file, then place - the detail documentation in the source file. -2. If an element does not have an associated cpp file, then - place the detail documentation at the end of the header file. -3. If there is a lot of detail documentation cluttering up - the end of a header file, then the content is moved to - a separate file in the Extern directory. - -Building the Documentation - -1. Download and install the appropriate Doxygen version. (See the first - line in the Doxyfile for the current version.) -2. Run "doxygen" in the project root directory. (The location of the Doxyfile.) - No arguments are required. - -The generated html files will be located in the /Docs/html directory. - -If you want to "version" the documentation, you can set the PROJECT_NUMBER -setting in the Doxyfile. E.g. PROJECT_NUMBER = "(2014-04-23)". The project -number will be added to the header of the documentation. -E.g. "Recast Navigation (2014-04-23)" - - - - - - diff --git a/extern/recastnavigation/Docs/footer.html b/extern/recastnavigation/Docs/footer.html deleted file mode 100644 index 5bd570ad1..000000000 --- a/extern/recastnavigation/Docs/footer.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - diff --git a/extern/recastnavigation/Docs/header.html b/extern/recastnavigation/Docs/header.html deleted file mode 100644 index 7a0adcb09..000000000 --- a/extern/recastnavigation/Docs/header.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - -$projectname: $title -$title - - - -$treeview -$search -$mathjax - -$extrastylesheet - - -
- - -
- - - - - - - - - - - - - - - - - - - - - -
-
$projectname -  $projectnumber -
-
$projectbrief
-
-
$projectbrief
-
$searchbox
-
- - diff --git a/extern/recastnavigation/Doxyfile b/extern/recastnavigation/Doxyfile deleted file mode 100644 index e175a45cc..000000000 --- a/extern/recastnavigation/Doxyfile +++ /dev/null @@ -1,2427 +0,0 @@ -# Doxyfile 1.8.10 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = "Recast Navigation" - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = "Navigation-mesh Toolset for Games" - -# With the PROJECT_LOGO tag one can specify a logo or an icon that is included -# in the documentation. The maximum height of the logo should not exceed 55 -# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy -# the logo to the output directory. - -PROJECT_LOGO = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = Docs - -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = NO - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = YES - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new -# page for each member. If set to NO, the documentation of a member will be part -# of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. - -ALIASES = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = NO - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. -# -# Note: For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. - -EXTENSION_MAPPING = - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = YES - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# If one adds a struct or class to a group and this option is enabled, then also -# any nested class or struct is added to the same group. By default this option -# is disabled and one has to add nested compounds explicitly via \ingroup. -# The default value is: NO. - -GROUP_NESTED_COMPOUNDS = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = NO - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = YES - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO, -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. If set to YES, local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO, only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. -# The default value is: system dependent. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES, the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will -# append additional text to a page's title, such as Class Reference. If set to -# YES the compound reference will be hidden. -# The default value is: NO. - -HIDE_COMPOUND_REFERENCE= NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo -# list. This list is created by putting \todo commands in the documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test -# list. This list is created by putting \test commands in the documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if ... \endif and \cond -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES, the -# list will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = Docs/DoxygenLayout.xml - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. -# The default value is: NO. - -WARN_NO_PARAMDOC = NO - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING -# Note: If this tag is empty the current directory is searched. - -INPUT = . \ - DetourCrowd \ - DetourTileCache \ - Recast \ - Docs/Conceptual \ - Docs/Extern - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. -# -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, -# *.vhdl, *.ucf, *.qsf, *.as and *.js. - -FILE_PATTERNS = *.cpp \ - *.h \ - *_c.txt \ - *_api.txt - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = Doxyfile \ - License.txt \ - Readme.txt \ - TODO.txt \ - RecastDemo/Contrib \ - RecastDemo/Build \ - RecastDemo/Bin - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = CMakeLists.txt - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = Docs/Images - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# -# -# where is the value of the INPUT_FILTER tag, and is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. -# Note: The availability of this option depends on whether or not doxygen was -# compiled with the --with-libclang option. -# The default value is: NO. - -CLANG_ASSISTED_PARSING = NO - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -CLANG_OPTIONS = - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = Docs/footer.html - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated -# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = YES - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering -# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://www.mathjax.org/mathjax - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use + S -# (what the is depends on the OS and browser, but it is typically -# , /