mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-07-21 11:14:04 +00:00
Add OpenMW commits up to 18 Aug 2019
# Conflicts: # CMakeLists.txt # apps/openmw/mwmechanics/character.cpp # apps/openmw/mwmechanics/spellcasting.cpp # apps/openmw/mwworld/worldimp.hpp
This commit is contained in:
commit
353e7d530a
174 changed files with 2170 additions and 36488 deletions
|
@ -22,6 +22,7 @@ Programmers
|
||||||
alexanderkjall
|
alexanderkjall
|
||||||
Alexander Nadeau (wareya)
|
Alexander Nadeau (wareya)
|
||||||
Alexander Olofsson (Ace)
|
Alexander Olofsson (Ace)
|
||||||
|
Alex Rice
|
||||||
Alex S (docwest)
|
Alex S (docwest)
|
||||||
Allofich
|
Allofich
|
||||||
Andrei Kortunov (akortunov)
|
Andrei Kortunov (akortunov)
|
||||||
|
|
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -15,6 +15,7 @@
|
||||||
Bug #3765: DisableTeleporting makes Mark/Recall/Intervention effects undetectable
|
Bug #3765: DisableTeleporting makes Mark/Recall/Intervention effects undetectable
|
||||||
Bug #3778: [Mod] Improved Thrown Weapon Projectiles - weapons have wrong transformation during throw animation
|
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 #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 #4202: Open .omwaddon files without needing toopen openmw-cs first
|
||||||
Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect
|
Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect
|
||||||
Bug #4276: Resizing character window differs from vanilla
|
Bug #4276: Resizing character window differs from vanilla
|
||||||
|
@ -24,7 +25,7 @@
|
||||||
Bug #4384: Resist Normal Weapons only checks ammunition for ranged weapons
|
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 #4411: Reloading a saved game while falling prevents damage in some cases
|
||||||
Bug #4540: Rain delay when exiting water
|
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 #4639: Black screen after completing first mages guild mission + training
|
||||||
Bug #4701: PrisonMarker record is not hardcoded like other markers
|
Bug #4701: PrisonMarker record is not hardcoded like other markers
|
||||||
Bug #4703: Editor: it's possible to preview levelled list records
|
Bug #4703: Editor: it's possible to preview levelled list records
|
||||||
|
@ -84,6 +85,7 @@
|
||||||
Bug #4945: Poor random magic magnitude distribution
|
Bug #4945: Poor random magic magnitude distribution
|
||||||
Bug #4947: Player character doesn't use lip animation
|
Bug #4947: Player character doesn't use lip animation
|
||||||
Bug #4948: Footstep sounds while levitating on ground level
|
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 #4961: Flying creature combat engagement takes z-axis into account
|
||||||
Bug #4963: Enchant skill progress is incorrect
|
Bug #4963: Enchant skill progress is incorrect
|
||||||
Bug #4964: Multiple effect spell projectile sounds play louder than vanilla
|
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 #4984: "Friendly hits" feature should be used only for player's followers
|
||||||
Bug #4989: Object dimension-dependent VFX scaling behavior is inconsistent
|
Bug #4989: Object dimension-dependent VFX scaling behavior is inconsistent
|
||||||
Bug #4990: Dead bodies prevent you from hitting
|
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 #4999: Drop instruction behaves differently from vanilla
|
||||||
Bug #5001: Possible data race in the Animation::setAlpha()
|
Bug #5001: Possible data race in the Animation::setAlpha()
|
||||||
Bug #5004: Werewolves shield their eyes during storm
|
Bug #5004: Werewolves shield their eyes during storm
|
||||||
|
@ -105,18 +108,32 @@
|
||||||
Bug #5038: Enchanting success chance calculations are blatantly wrong
|
Bug #5038: Enchanting success chance calculations are blatantly wrong
|
||||||
Bug #5047: # in cell names sets color
|
Bug #5047: # in cell names sets color
|
||||||
Bug #5050: Invalid spell effects are not handled gracefully
|
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 #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 #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 #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 #5069: Blocking creatures' attacks doesn't degrade shields
|
||||||
Bug #5074: Paralyzed actors greet the player
|
Bug #5074: Paralyzed actors greet the player
|
||||||
Bug #5075: Enchanting cast style can be changed if there's no object
|
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 #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 #5092: NPCs with enchanted weapons play sound when out of charges
|
||||||
Bug #5093: Hand to hand sound plays on knocked out enemies
|
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 #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 #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 #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 #1774: Handle AvoidNode
|
||||||
Feature #2229: Improve pathfinding AI
|
Feature #2229: Improve pathfinding AI
|
||||||
Feature #3025: Analogue gamepad movement controls
|
Feature #3025: Analogue gamepad movement controls
|
||||||
|
@ -137,6 +154,7 @@
|
||||||
Feature #4812: Support NiSwitchNode
|
Feature #4812: Support NiSwitchNode
|
||||||
Feature #4836: Daytime node switch
|
Feature #4836: Daytime node switch
|
||||||
Feature #4859: Make water reflections more configurable
|
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 #4887: Add openmw command option to set initial random seed
|
||||||
Feature #4890: Make Distant Terrain configurable
|
Feature #4890: Make Distant Terrain configurable
|
||||||
Feature #4958: Support eight blood types
|
Feature #4958: Support eight blood types
|
||||||
|
@ -152,7 +170,13 @@
|
||||||
Feature #5036: Allow scripted faction leaving
|
Feature #5036: Allow scripted faction leaving
|
||||||
Feature #5046: Gamepad thumbstick cursor speed
|
Feature #5046: Gamepad thumbstick cursor speed
|
||||||
Feature #5051: Provide a separate textures for scrollbars
|
Feature #5051: Provide a separate textures for scrollbars
|
||||||
|
Feature #5091: Human-readable light source duration
|
||||||
Feature #5094: Unix like console hotkeys
|
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 #4686: Upgrade media decoder to a more current FFmpeg API
|
||||||
Task #4695: Optimize Distant Terrain memory consumption
|
Task #4695: Optimize Distant Terrain memory consumption
|
||||||
Task #4789: Optimize cell transitions
|
Task #4789: Optimize cell transitions
|
||||||
|
|
|
@ -647,9 +647,7 @@ endif(WIN32)
|
||||||
|
|
||||||
# Extern
|
# Extern
|
||||||
IF(BUILD_OPENMW OR BUILD_OPENCS)
|
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_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/recastnavigation EXCLUDE_FROM_ALL)
|
||||||
add_subdirectory (extern/osg-ffmpeg-videoplayer)
|
add_subdirectory (extern/osg-ffmpeg-videoplayer)
|
||||||
|
|
|
@ -83,7 +83,7 @@ add_openmw_dir (mwmechanics
|
||||||
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
|
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
|
aicast aiescort aiface aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
|
||||||
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
|
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
|
add_openmw_dir (mwstate
|
||||||
|
|
|
@ -636,8 +636,17 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
controllerFileName = "gamecontrollerdb.txt";
|
controllerFileName = "gamecontrollerdb.txt";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string userdefault = mCfgMgr.getUserConfigPath().string() + "/" + controllerFileName;
|
||||||
const std::string localdefault = mCfgMgr.getLocalPath().string() + "/" + controllerFileName;
|
const std::string localdefault = mCfgMgr.getLocalPath().string() + "/" + controllerFileName;
|
||||||
const std::string globaldefault = mCfgMgr.getGlobalPath().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;
|
std::string gameControllerdb;
|
||||||
if (boost::filesystem::exists(localdefault))
|
if (boost::filesystem::exists(localdefault))
|
||||||
gameControllerdb = localdefault;
|
gameControllerdb = localdefault;
|
||||||
|
@ -646,7 +655,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||||
else
|
else
|
||||||
gameControllerdb = ""; //if it doesn't exist, pass in an empty string
|
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);
|
mEnvironment.setInputManager (input);
|
||||||
|
|
||||||
std::string myguiResources = (mResDir / "mygui").string();
|
std::string myguiResources = (mResDir / "mygui").string();
|
||||||
|
|
|
@ -559,7 +559,6 @@ namespace MWBase
|
||||||
virtual void togglePreviewMode(bool enable) = 0;
|
virtual void togglePreviewMode(bool enable) = 0;
|
||||||
virtual bool toggleVanityMode(bool enable) = 0;
|
virtual bool toggleVanityMode(bool enable) = 0;
|
||||||
virtual void allowVanityMode(bool allow) = 0;
|
virtual void allowVanityMode(bool allow) = 0;
|
||||||
virtual void togglePlayerLooking(bool enable) = 0;
|
|
||||||
virtual void changeVanityModeScale(float factor) = 0;
|
virtual void changeVanityModeScale(float factor) = 0;
|
||||||
virtual bool vanityRotateCamera(float * rot) = 0;
|
virtual bool vanityRotateCamera(float * rot) = 0;
|
||||||
virtual void setCameraDistance(float dist, bool adjust = false, bool override = true)=0;
|
virtual void setCameraDistance(float dist, bool adjust = false, bool override = true)=0;
|
||||||
|
|
|
@ -103,7 +103,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Activator> *ref = ptr.get<ESM::Activator>();
|
const MWWorld::LiveCellRef<ESM::Activator> *ref = ptr.get<ESM::Activator>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
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;
|
std::string text;
|
||||||
if (MWBase::Environment::get().getWindowManager()->getFullHelp())
|
if (MWBase::Environment::get().getWindowManager()->getFullHelp())
|
||||||
|
|
|
@ -138,7 +138,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Apparatus> *ref = ptr.get<ESM::Apparatus>();
|
const MWWorld::LiveCellRef<ESM::Apparatus> *ref = ptr.get<ESM::Apparatus>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
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;
|
info.icon = ref->mBase->mIcon;
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include "../mwrender/objects.hpp"
|
#include "../mwrender/objects.hpp"
|
||||||
#include "../mwrender/renderinginterface.hpp"
|
#include "../mwrender/renderinginterface.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
#include "../mwgui/tooltips.hpp"
|
#include "../mwgui/tooltips.hpp"
|
||||||
|
|
||||||
|
@ -244,7 +245,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Armor> *ref = ptr.get<ESM::Armor>();
|
const MWWorld::LiveCellRef<ESM::Armor> *ref = ptr.get<ESM::Armor>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
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;
|
info.icon = ref->mBase->mIcon;
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
|
@ -378,21 +379,13 @@ namespace MWClass
|
||||||
if(*slot == MWWorld::InventoryStore::Slot_CarriedLeft)
|
if(*slot == MWWorld::InventoryStore::Slot_CarriedLeft)
|
||||||
{
|
{
|
||||||
MWWorld::ConstContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
MWWorld::ConstContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
|
if(weapon != invStore.end() && weapon->getTypeName() == typeid(ESM::Weapon).name())
|
||||||
if(weapon == invStore.end())
|
|
||||||
return std::make_pair(1,"");
|
|
||||||
|
|
||||||
if(weapon->getTypeName() == typeid(ESM::Weapon).name() &&
|
|
||||||
(weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand ||
|
|
||||||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoClose ||
|
|
||||||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoWide ||
|
|
||||||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::SpearTwoWide ||
|
|
||||||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::AxeTwoHand ||
|
|
||||||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanBow ||
|
|
||||||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow))
|
|
||||||
{
|
{
|
||||||
return std::make_pair(3,"");
|
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
|
||||||
|
if (MWMechanics::getWeaponType(ref->mBase->mData.mType)->mFlags & ESM::WeaponType::TwoHanded)
|
||||||
|
return std::make_pair(3,"");
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_pair(1,"");
|
return std::make_pair(1,"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Book> *ref = ptr.get<ESM::Book>();
|
const MWWorld::LiveCellRef<ESM::Book> *ref = ptr.get<ESM::Book>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
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;
|
info.icon = ref->mBase->mIcon;
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
|
@ -202,7 +202,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Clothing> *ref = ptr.get<ESM::Clothing>();
|
const MWWorld::LiveCellRef<ESM::Clothing> *ref = ptr.get<ESM::Clothing>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
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;
|
info.icon = ref->mBase->mIcon;
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
|
@ -335,7 +335,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
|
const MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
MWGui::ToolTipInfo info;
|
||||||
info.caption = ref->mBase->mName;
|
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName);
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
int lockLevel = ptr.getCellRef().getLockLevel();
|
int lockLevel = ptr.getCellRef().getLockLevel();
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#include "../mwworld/containerstore.hpp"
|
#include "../mwworld/containerstore.hpp"
|
||||||
#include "../mwphysics/physicssystem.hpp"
|
#include "../mwphysics/physicssystem.hpp"
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
#include "../mwworld/localscripts.hpp"
|
||||||
|
|
||||||
#include "../mwrender/renderinginterface.hpp"
|
#include "../mwrender/renderinginterface.hpp"
|
||||||
#include "../mwrender/objects.hpp"
|
#include "../mwrender/objects.hpp"
|
||||||
|
@ -735,7 +736,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
MWGui::ToolTipInfo info;
|
||||||
info.caption = ref->mBase->mName;
|
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName);
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
if (MWBase::Environment::get().getWindowManager()->getFullHelp())
|
if (MWBase::Environment::get().getWindowManager()->getFullHelp())
|
||||||
|
@ -994,7 +995,12 @@ namespace MWClass
|
||||||
if (ptr.getCellRef().hasContentFile())
|
if (ptr.getCellRef().hasContentFile())
|
||||||
{
|
{
|
||||||
if (ptr.getRefData().getCount() == 0)
|
if (ptr.getRefData().getCount() == 0)
|
||||||
|
{
|
||||||
ptr.getRefData().setCount(1);
|
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);
|
MWBase::Environment::get().getWorld()->removeContainerScripts(ptr);
|
||||||
ptr.getRefData().setCustomData(nullptr);
|
ptr.getRefData().setCustomData(nullptr);
|
||||||
|
|
|
@ -357,7 +357,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
|
const MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
MWGui::ToolTipInfo info;
|
||||||
info.caption = ref->mBase->mName;
|
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName);
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Ingredient> *ref = ptr.get<ESM::Ingredient>();
|
const MWWorld::LiveCellRef<ESM::Ingredient> *ref = ptr.get<ESM::Ingredient>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
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;
|
info.icon = ref->mBase->mIcon;
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
|
@ -151,18 +151,14 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Light> *ref = ptr.get<ESM::Light>();
|
const MWWorld::LiveCellRef<ESM::Light> *ref = ptr.get<ESM::Light>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
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;
|
info.icon = ref->mBase->mIcon;
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
||||||
if (Settings::Manager::getBool("show effect duration","Game"))
|
// Don't show duration for infinite light sources.
|
||||||
{
|
if (Settings::Manager::getBool("show effect duration","Game") && ptr.getClass().getRemainingUsageTime(ptr) != -1)
|
||||||
// -1 is infinite light source, so duration makes no sense here. Other negative values are treated as 0.
|
text += MWGui::ToolTips::getDurationString(ptr.getClass().getRemainingUsageTime(ptr), "\n#{sDuration}");
|
||||||
float remainingTime = ptr.getClass().getRemainingUsageTime(ptr);
|
|
||||||
if (remainingTime != -1.0f)
|
|
||||||
text += "\n#{sDuration}: " + MWGui::ToolTips::toString(std::max(0.f, remainingTime));
|
|
||||||
}
|
|
||||||
|
|
||||||
text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}");
|
text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}");
|
||||||
text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
|
text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
|
||||||
|
|
|
@ -116,7 +116,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Lockpick> *ref = ptr.get<ESM::Lockpick>();
|
const MWWorld::LiveCellRef<ESM::Lockpick> *ref = ptr.get<ESM::Lockpick>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
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;
|
info.icon = ref->mBase->mIcon;
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
|
@ -192,7 +192,7 @@ namespace MWClass
|
||||||
else // gold displays its count also if it's 1.
|
else // gold displays its count also if it's 1.
|
||||||
countString = " (" + std::to_string(count) + ")";
|
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;
|
info.icon = ref->mBase->mIcon;
|
||||||
|
|
||||||
if (ref->mRef.getSoul() != "")
|
if (ref->mRef.getSoul() != "")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#include "npc.hpp"
|
#include "npc.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
#include "../mwmechanics/combat.hpp"
|
#include "../mwmechanics/combat.hpp"
|
||||||
#include "../mwmechanics/autocalcspell.hpp"
|
#include "../mwmechanics/autocalcspell.hpp"
|
||||||
#include "../mwmechanics/difficultyscaling.hpp"
|
#include "../mwmechanics/difficultyscaling.hpp"
|
||||||
#include "../mwmechanics/character.hpp"
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
|
@ -52,6 +52,7 @@
|
||||||
#include "../mwworld/customdata.hpp"
|
#include "../mwworld/customdata.hpp"
|
||||||
#include "../mwphysics/physicssystem.hpp"
|
#include "../mwphysics/physicssystem.hpp"
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
#include "../mwworld/localscripts.hpp"
|
||||||
|
|
||||||
#include "../mwrender/objects.hpp"
|
#include "../mwrender/objects.hpp"
|
||||||
#include "../mwrender/renderinginterface.hpp"
|
#include "../mwrender/renderinginterface.hpp"
|
||||||
|
@ -1124,10 +1125,9 @@ namespace MWClass
|
||||||
const float normalizedEncumbrance = getNormalizedEncumbrance(ptr);
|
const float normalizedEncumbrance = getNormalizedEncumbrance(ptr);
|
||||||
|
|
||||||
bool swimming = world->isSwimming(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 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));
|
running = running && (inair || MWBase::Environment::get().getMechanicsManager()->isRunning(ptr));
|
||||||
|
|
||||||
float walkSpeed = gmst.fMinWalkSpeed->mValue.getFloat() + 0.01f*npcdata->mNpcStats.getAttribute(ESM::Attribute::Speed).getModified()*
|
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();
|
bool fullHelp = MWBase::Environment::get().getWindowManager()->getFullHelp();
|
||||||
MWGui::ToolTipInfo info;
|
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())
|
if(fullHelp && ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf())
|
||||||
{
|
{
|
||||||
info.caption += " (";
|
info.caption += " (";
|
||||||
info.caption += ref->mBase->mName;
|
info.caption += MyGUI::TextIterator::toTagsString(ref->mBase->mName);
|
||||||
info.caption += ")";
|
info.caption += ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1412,9 +1412,9 @@ namespace MWClass
|
||||||
if (getNpcStats(ptr).isWerewolf()
|
if (getNpcStats(ptr).isWerewolf()
|
||||||
&& getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
|
&& getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
|
||||||
{
|
{
|
||||||
MWMechanics::WeaponType weaponType = MWMechanics::WeapType_None;
|
int weaponType = ESM::Weapon::None;
|
||||||
MWMechanics::getActiveWeapon(getCreatureStats(ptr), getInventoryStore(ptr), &weaponType);
|
MWMechanics::getActiveWeapon(ptr, &weaponType);
|
||||||
if (weaponType == MWMechanics::WeapType_None)
|
if (weaponType == ESM::Weapon::None)
|
||||||
return std::string();
|
return std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1561,7 +1561,12 @@ namespace MWClass
|
||||||
if (ptr.getCellRef().hasContentFile())
|
if (ptr.getCellRef().hasContentFile())
|
||||||
{
|
{
|
||||||
if (ptr.getRefData().getCount() == 0)
|
if (ptr.getRefData().getCount() == 0)
|
||||||
|
{
|
||||||
ptr.getRefData().setCount(1);
|
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);
|
MWBase::Environment::get().getWorld()->removeContainerScripts(ptr);
|
||||||
ptr.getRefData().setCustomData(nullptr);
|
ptr.getRefData().setCustomData(nullptr);
|
||||||
|
|
|
@ -143,7 +143,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Potion> *ref = ptr.get<ESM::Potion>();
|
const MWWorld::LiveCellRef<ESM::Potion> *ref = ptr.get<ESM::Potion>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
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;
|
info.icon = ref->mBase->mIcon;
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
|
@ -116,7 +116,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Probe> *ref = ptr.get<ESM::Probe>();
|
const MWWorld::LiveCellRef<ESM::Probe> *ref = ptr.get<ESM::Probe>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
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;
|
info.icon = ref->mBase->mIcon;
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
|
@ -150,7 +150,7 @@ namespace MWClass
|
||||||
const MWWorld::LiveCellRef<ESM::Repair> *ref = ptr.get<ESM::Repair>();
|
const MWWorld::LiveCellRef<ESM::Repair> *ref = ptr.get<ESM::Repair>();
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
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;
|
info.icon = ref->mBase->mIcon;
|
||||||
|
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
#include "../mwphysics/physicssystem.hpp"
|
#include "../mwphysics/physicssystem.hpp"
|
||||||
#include "../mwworld/nullaction.hpp"
|
#include "../mwworld/nullaction.hpp"
|
||||||
|
|
||||||
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
#include "../mwgui/tooltips.hpp"
|
#include "../mwgui/tooltips.hpp"
|
||||||
|
|
||||||
#include "../mwrender/objects.hpp"
|
#include "../mwrender/objects.hpp"
|
||||||
|
@ -98,8 +100,9 @@ namespace MWClass
|
||||||
bool Weapon::hasItemHealth (const MWWorld::ConstPtr& ptr) const
|
bool Weapon::hasItemHealth (const MWWorld::ConstPtr& ptr) const
|
||||||
{
|
{
|
||||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
||||||
|
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
|
int Weapon::getItemMaxHealth (const MWWorld::ConstPtr& ptr) const
|
||||||
|
@ -120,16 +123,17 @@ namespace MWClass
|
||||||
std::pair<std::vector<int>, bool> Weapon::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const
|
std::pair<std::vector<int>, bool> Weapon::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const
|
||||||
{
|
{
|
||||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
||||||
|
ESM::WeaponType::Class weapClass = MWMechanics::getWeaponType(ref->mBase->mData.mType)->mWeaponClass;
|
||||||
|
|
||||||
std::vector<int> slots_;
|
std::vector<int> slots_;
|
||||||
bool stack = false;
|
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));
|
slots_.push_back (int (MWWorld::InventoryStore::Slot_Ammunition));
|
||||||
stack = true;
|
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));
|
slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
|
||||||
stack = true;
|
stack = true;
|
||||||
|
@ -143,30 +147,9 @@ namespace MWClass
|
||||||
int Weapon::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const
|
int Weapon::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const
|
||||||
{
|
{
|
||||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
||||||
|
int type = ref->mBase->mData.mType;
|
||||||
|
|
||||||
const int size = 12;
|
return MWMechanics::getWeaponType(type)->mSkill;
|
||||||
|
|
||||||
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; i<size; ++i)
|
|
||||||
if (sMapping[i][0]==ref->mBase->mData.mType)
|
|
||||||
return sMapping[i][1];
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Weapon::getValue (const MWWorld::ConstPtr& ptr) const
|
int Weapon::getValue (const MWWorld::ConstPtr& ptr) const
|
||||||
|
@ -186,89 +169,17 @@ namespace MWClass
|
||||||
std::string Weapon::getUpSoundId (const MWWorld::ConstPtr& ptr) const
|
std::string Weapon::getUpSoundId (const MWWorld::ConstPtr& ptr) const
|
||||||
{
|
{
|
||||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
||||||
|
|
||||||
int type = ref->mBase->mData.mType;
|
int type = ref->mBase->mData.mType;
|
||||||
// Ammo
|
std::string soundId = MWMechanics::getWeaponType(type)->mSoundId;
|
||||||
if (type == 12 || type == 13)
|
return soundId + " Up";
|
||||||
{
|
|
||||||
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 Weapon::getDownSoundId (const MWWorld::ConstPtr& ptr) const
|
std::string Weapon::getDownSoundId (const MWWorld::ConstPtr& ptr) const
|
||||||
{
|
{
|
||||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
||||||
|
|
||||||
int type = ref->mBase->mData.mType;
|
int type = ref->mBase->mData.mType;
|
||||||
// Ammo
|
std::string soundId = MWMechanics::getWeaponType(type)->mSoundId;
|
||||||
if (type == 12 || type == 13)
|
return soundId + " Down";
|
||||||
{
|
|
||||||
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 Weapon::getInventoryIcon (const MWWorld::ConstPtr& ptr) const
|
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
|
MWGui::ToolTipInfo Weapon::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
|
||||||
{
|
{
|
||||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
||||||
|
const ESM::WeaponType* weaponType = MWMechanics::getWeaponType(ref->mBase->mData.mType);
|
||||||
|
|
||||||
MWGui::ToolTipInfo info;
|
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;
|
info.icon = ref->mBase->mIcon;
|
||||||
|
|
||||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
@ -298,37 +210,26 @@ namespace MWClass
|
||||||
std::string text;
|
std::string text;
|
||||||
|
|
||||||
// weapon type & damage
|
// 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} ";
|
text += "\n#{sType} ";
|
||||||
|
|
||||||
static std::map <int, std::pair <std::string, std::string> > mapping;
|
int skill = MWMechanics::getWeaponType(ref->mBase->mData.mType)->mSkill;
|
||||||
if (mapping.empty())
|
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");
|
if (weaponType->mFlags & ESM::WeaponType::TwoHanded)
|
||||||
mapping[ESM::Weapon::LongBladeOneHand] = std::make_pair("sSkillLongblade", "sOneHanded");
|
oneOrTwoHanded = "sTwoHanded";
|
||||||
mapping[ESM::Weapon::LongBladeTwoHand] = std::make_pair("sSkillLongblade", "sTwoHanded");
|
else
|
||||||
mapping[ESM::Weapon::BluntOneHand] = std::make_pair("sSkillBluntweapon", "sOneHanded");
|
oneOrTwoHanded = "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", "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string type = mapping[ref->mBase->mData.mType].first;
|
|
||||||
const std::string oneOrTwoHanded = mapping[ref->mBase->mData.mType].second;
|
|
||||||
|
|
||||||
text += store.get<ESM::GameSetting>().find(type)->mValue.getString() +
|
text += store.get<ESM::GameSetting>().find(type)->mValue.getString() +
|
||||||
((oneOrTwoHanded != "") ? ", " + store.get<ESM::GameSetting>().find(oneOrTwoHanded)->mValue.getString() : "");
|
((oneOrTwoHanded != "") ? ", " + store.get<ESM::GameSetting>().find(oneOrTwoHanded)->mValue.getString() : "");
|
||||||
|
|
||||||
// weapon damage
|
// weapon damage
|
||||||
if (ref->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
if (weaponType->mWeaponClass == ESM::WeaponType::Thrown)
|
||||||
{
|
{
|
||||||
// Thrown weapons have 2x real damage applied
|
// Thrown weapons have 2x real damage applied
|
||||||
// as they're both the weapon and the ammo
|
// as they're both the weapon and the ammo
|
||||||
|
@ -336,14 +237,7 @@ namespace MWClass
|
||||||
+ MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[0] * 2))
|
+ MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[0] * 2))
|
||||||
+ " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[1] * 2));
|
+ " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[1] * 2));
|
||||||
}
|
}
|
||||||
else if (ref->mBase->mData.mType >= ESM::Weapon::MarksmanBow)
|
else if (weaponType->mWeaponClass == ESM::WeaponType::Melee)
|
||||||
{
|
|
||||||
// marksman
|
|
||||||
text += "\n#{sAttack}: "
|
|
||||||
+ MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[0]))
|
|
||||||
+ " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[1]));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// Chop
|
// Chop
|
||||||
text += "\n#{sChop}: "
|
text += "\n#{sChop}: "
|
||||||
|
@ -358,6 +252,13 @@ namespace MWClass
|
||||||
+ MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mThrust[0]))
|
+ MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mThrust[0]))
|
||||||
+ " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mThrust[1]));
|
+ " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mThrust[1]));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// marksman
|
||||||
|
text += "\n#{sAttack}: "
|
||||||
|
+ MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[0]))
|
||||||
|
+ " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[1]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasItemHealth(ptr))
|
if (hasItemHealth(ptr))
|
||||||
|
@ -369,7 +270,7 @@ namespace MWClass
|
||||||
|
|
||||||
const bool verbose = Settings::Manager::getBool("show melee info", "Game");
|
const bool verbose = Settings::Manager::getBool("show melee info", "Game");
|
||||||
// add reach for melee weapon
|
// 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
|
// display value in feet
|
||||||
const float combatDistance = store.get<ESM::GameSetting>().find("fCombatDistance")->mValue.getFloat() * ref->mBase->mData.mReach;
|
const float combatDistance = store.get<ESM::GameSetting>().find("fCombatDistance")->mValue.getFloat() * ref->mBase->mData.mReach;
|
||||||
|
@ -378,7 +279,7 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
|
|
||||||
// add attack speed for any weapon excepts arrows and bolts
|
// 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}");
|
text += MWGui::ToolTips::getPercentString(ref->mBase->mData.mSpeed, "#{sAttributeSpeed}");
|
||||||
}
|
}
|
||||||
|
@ -449,13 +350,8 @@ namespace MWClass
|
||||||
if (slots_.first.empty())
|
if (slots_.first.empty())
|
||||||
return std::make_pair (0, "");
|
return std::make_pair (0, "");
|
||||||
|
|
||||||
if(ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand ||
|
int type = ptr.get<ESM::Weapon>()->mBase->mData.mType;
|
||||||
ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoClose ||
|
if(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded)
|
||||||
ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoWide ||
|
|
||||||
ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::SpearTwoWide ||
|
|
||||||
ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::AxeTwoHand ||
|
|
||||||
ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanBow ||
|
|
||||||
ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
|
||||||
{
|
{
|
||||||
return std::make_pair (2, "");
|
return std::make_pair (2, "");
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,10 +112,7 @@ struct TypesetBookImpl : TypesetBook
|
||||||
if (i->empty())
|
if (i->empty())
|
||||||
return Range (Utf8Point (nullptr), Utf8Point (nullptr));
|
return Range (Utf8Point (nullptr), Utf8Point (nullptr));
|
||||||
|
|
||||||
Utf8Point begin = &i->front ();
|
return Range (i->data(), i->data() + i->size());
|
||||||
Utf8Point end = &i->front () + i->size ();
|
|
||||||
|
|
||||||
return Range (begin, end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t pageCount () const { return mPages.size (); }
|
size_t pageCount () const { return mPages.size (); }
|
||||||
|
@ -346,8 +343,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter
|
||||||
assert (end <= mCurrentContent->size ());
|
assert (end <= mCurrentContent->size ());
|
||||||
assert (begin <= mCurrentContent->size ());
|
assert (begin <= mCurrentContent->size ());
|
||||||
|
|
||||||
Utf8Point begin_ = &mCurrentContent->front () + begin;
|
Utf8Point begin_ = mCurrentContent->data() + begin;
|
||||||
Utf8Point end_ = &mCurrentContent->front () + end ;
|
Utf8Point end_ = mCurrentContent->data() + end;
|
||||||
|
|
||||||
writeImpl (static_cast <StyleImpl*> (style), begin_, end_);
|
writeImpl (static_cast <StyleImpl*> (style), begin_, end_);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
#include <components/fallback/fallback.hpp>
|
#include <components/fallback/fallback.hpp>
|
||||||
|
#include <components/misc/rng.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/soundmanager.hpp"
|
#include "../mwbase/soundmanager.hpp"
|
||||||
|
@ -25,34 +26,52 @@
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
struct Response
|
||||||
|
{
|
||||||
|
const std::string mText;
|
||||||
|
const ESM::Class::Specialization mSpecialization;
|
||||||
|
};
|
||||||
|
|
||||||
struct Step
|
struct Step
|
||||||
{
|
{
|
||||||
const std::string mText;
|
const std::string mText;
|
||||||
const std::string mButtons[3];
|
const Response mResponses[3];
|
||||||
const std::string mSound;
|
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)
|
Step sGenerateClassSteps(int number)
|
||||||
{
|
{
|
||||||
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
|
std::string question = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_Question");
|
||||||
{
|
std::string answer0 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerOne");
|
||||||
const char *id;
|
std::string answer1 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerTwo");
|
||||||
// Specialization points to match, in order: Stealth, Combat, Magic
|
std::string answer2 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerThree");
|
||||||
// Note: Order is taken from http://www.uesp.net/wiki/Morrowind:Class_Quiz
|
std::string sound = "vo\\misc\\chargen qa" + MyGUI::utility::toString(number) + ".wav";
|
||||||
unsigned int points[3];
|
|
||||||
};
|
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()
|
void updatePlayerHealth()
|
||||||
{
|
{
|
||||||
|
@ -80,6 +99,9 @@ namespace MWGui
|
||||||
, mGenerateClassStep(0)
|
, mGenerateClassStep(0)
|
||||||
{
|
{
|
||||||
mCreationStage = CSE_NotStarted;
|
mCreationStage = CSE_NotStarted;
|
||||||
|
mGenerateClassResponses[0] = ESM::Class::Combat;
|
||||||
|
mGenerateClassResponses[1] = ESM::Class::Magic;
|
||||||
|
mGenerateClassResponses[2] = ESM::Class::Stealth;
|
||||||
mGenerateClassSpecializations[0] = 0;
|
mGenerateClassSpecializations[0] = 0;
|
||||||
mGenerateClassSpecializations[1] = 0;
|
mGenerateClassSpecializations[1] = 0;
|
||||||
mGenerateClassSpecializations[2] = 0;
|
mGenerateClassSpecializations[2] = 0;
|
||||||
|
@ -550,12 +572,12 @@ namespace MWGui
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESM::Class::Specialization specialization = mSpecializations[_index];
|
ESM::Class::Specialization specialization = mGenerateClassResponses[_index];
|
||||||
if (specialization == ESM::Class::Stealth)
|
if (specialization == ESM::Class::Combat)
|
||||||
++mGenerateClassSpecializations[0];
|
++mGenerateClassSpecializations[0];
|
||||||
else if (specialization == ESM::Class::Combat)
|
|
||||||
++mGenerateClassSpecializations[1];
|
|
||||||
else if (specialization == ESM::Class::Magic)
|
else if (specialization == ESM::Class::Magic)
|
||||||
|
++mGenerateClassSpecializations[1];
|
||||||
|
else if (specialization == ESM::Class::Stealth)
|
||||||
++mGenerateClassSpecializations[2];
|
++mGenerateClassSpecializations[2];
|
||||||
++mGenerateClassStep;
|
++mGenerateClassStep;
|
||||||
showClassQuestionDialog();
|
showClassQuestionDialog();
|
||||||
|
@ -565,57 +587,96 @@ namespace MWGui
|
||||||
{
|
{
|
||||||
if (mGenerateClassStep == 10)
|
if (mGenerateClassStep == 10)
|
||||||
{
|
{
|
||||||
static std::array<ClassPoint, 23> classes = { {
|
unsigned combat = mGenerateClassSpecializations[0];
|
||||||
{"Acrobat", {6, 2, 2}},
|
unsigned magic = mGenerateClassSpecializations[1];
|
||||||
{"Agent", {6, 1, 3}},
|
unsigned stealth = mGenerateClassSpecializations[2];
|
||||||
{"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}}
|
|
||||||
} };
|
|
||||||
|
|
||||||
int match = -1;
|
if (combat > 7)
|
||||||
for (unsigned i = 0; i < classes.size(); ++i)
|
|
||||||
{
|
{
|
||||||
if (mGenerateClassSpecializations[0] == classes[i].points[0] &&
|
mGenerateClass = "Warrior";
|
||||||
mGenerateClassSpecializations[1] == classes[i].points[1] &&
|
|
||||||
mGenerateClassSpecializations[2] == classes[i].points[2])
|
|
||||||
{
|
|
||||||
match = i;
|
|
||||||
mGenerateClass = classes[i].id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (magic > 7)
|
||||||
if (match == -1)
|
|
||||||
{
|
{
|
||||||
if (mGenerateClassSpecializations[0] >= 7)
|
mGenerateClass = "Mage";
|
||||||
mGenerateClass = "Thief";
|
}
|
||||||
else if (mGenerateClassSpecializations[1] >= 7)
|
else if (stealth > 7)
|
||||||
mGenerateClass = "Warrior";
|
{
|
||||||
else if (mGenerateClassSpecializations[2] >= 7)
|
mGenerateClass = "Thief";
|
||||||
mGenerateClass = "Mage";
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
switch (combat)
|
||||||
{
|
{
|
||||||
Log(Debug::Warning) << "Failed to deduce class from chosen answers in generate class dialog.";
|
case 4:
|
||||||
mGenerateClass = "Thief";
|
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();
|
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;
|
InfoBoxDialog::ButtonList buttons;
|
||||||
mGenerateClassQuestionDialog->setText(sGenerateClassSteps(mGenerateClassStep).mText);
|
mGenerateClassQuestionDialog->setText(step.mText);
|
||||||
buttons.push_back(sGenerateClassSteps(mGenerateClassStep).mButtons[0]);
|
buttons.push_back(step.mResponses[0].mText);
|
||||||
buttons.push_back(sGenerateClassSteps(mGenerateClassStep).mButtons[1]);
|
buttons.push_back(step.mResponses[1].mText);
|
||||||
buttons.push_back(sGenerateClassSteps(mGenerateClassStep).mButtons[2]);
|
buttons.push_back(step.mResponses[2].mText);
|
||||||
mGenerateClassQuestionDialog->setButtons(buttons);
|
mGenerateClassQuestionDialog->setButtons(buttons);
|
||||||
mGenerateClassQuestionDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassQuestionChosen);
|
mGenerateClassQuestionDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassQuestionChosen);
|
||||||
mGenerateClassQuestionDialog->setVisible(true);
|
mGenerateClassQuestionDialog->setVisible(true);
|
||||||
|
|
||||||
MWBase::Environment::get().getSoundManager()->say(sGenerateClassSteps(mGenerateClassStep).mSound);
|
MWBase::Environment::get().getSoundManager()->say(step.mSound);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterCreation::selectGeneratedClass()
|
void CharacterCreation::selectGeneratedClass()
|
||||||
|
|
|
@ -75,8 +75,9 @@ namespace MWGui
|
||||||
|
|
||||||
//Class generation vars
|
//Class generation vars
|
||||||
unsigned mGenerateClassStep; // Keeps track of current step in Generate Class dialog
|
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
|
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
|
////Dialog events
|
||||||
//Name dialog
|
//Name dialog
|
||||||
|
|
|
@ -137,27 +137,8 @@ namespace MWGui
|
||||||
MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", "") );
|
MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", "") );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (effectInfo.mRemainingTime > -1 &&
|
if (effectInfo.mRemainingTime > -1 && Settings::Manager::getBool("show effect duration","Game"))
|
||||||
Settings::Manager::getBool("show effect duration","Game")) {
|
sourcesDescription += MWGui::ToolTips::getDurationString(effectInfo.mRemainingTime, " #{sDuration}");
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewLine = true;
|
addNewLine = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ namespace MWGui
|
||||||
{
|
{
|
||||||
newSpell.mType = Spell::Type_Spell;
|
newSpell.mType = Spell::Type_Spell;
|
||||||
std::string cost = std::to_string(spell->mData.mCost);
|
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;
|
newSpell.mCostColumn = cost + "/" + chance;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -667,6 +667,60 @@ namespace MWGui
|
||||||
return ret;
|
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<int>(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()
|
bool ToolTips::toggleFullHelp()
|
||||||
{
|
{
|
||||||
mFullHelp = !mFullHelp;
|
mFullHelp = !mFullHelp;
|
||||||
|
|
|
@ -84,6 +84,9 @@ namespace MWGui
|
||||||
static std::string getCellRefString(const MWWorld::CellRef& cellref);
|
static std::string getCellRefString(const MWWorld::CellRef& cellref);
|
||||||
///< Returns a string containing debug tooltip information about the given 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
|
// 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
|
// system knows what to show in case this widget is hovered
|
||||||
static void createSkillToolTip(MyGUI::Widget* widget, int skillId);
|
static void createSkillToolTip(MyGUI::Widget* widget, int skillId);
|
||||||
|
|
|
@ -51,6 +51,7 @@ namespace MWInput
|
||||||
osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler,
|
osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler,
|
||||||
osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation,
|
osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation,
|
||||||
const std::string& userFile, bool userFileExists,
|
const std::string& userFile, bool userFileExists,
|
||||||
|
const std::string& userControllerBindingsFile,
|
||||||
const std::string& controllerBindingsFile, bool grab)
|
const std::string& controllerBindingsFile, bool grab)
|
||||||
: mWindow(window)
|
: mWindow(window)
|
||||||
, mWindowVisible(true)
|
, mWindowVisible(true)
|
||||||
|
@ -125,10 +126,14 @@ namespace MWInput
|
||||||
|
|
||||||
// Load controller mappings
|
// Load controller mappings
|
||||||
#if SDL_VERSION_ATLEAST(2,0,2)
|
#if SDL_VERSION_ATLEAST(2,0,2)
|
||||||
if(controllerBindingsFile!="")
|
if(!controllerBindingsFile.empty())
|
||||||
{
|
{
|
||||||
SDL_GameControllerAddMappingsFromFile(controllerBindingsFile.c_str());
|
SDL_GameControllerAddMappingsFromFile(controllerBindingsFile.c_str());
|
||||||
}
|
}
|
||||||
|
if(!userControllerBindingsFile.empty())
|
||||||
|
{
|
||||||
|
SDL_GameControllerAddMappingsFromFile(userControllerBindingsFile.c_str());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Open all presently connected sticks
|
// 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);
|
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
|
// 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->yaw(rot[2]);
|
||||||
mPlayer->pitch(rot[0]);
|
mPlayer->pitch(rot[0]);
|
||||||
|
@ -839,9 +844,6 @@ namespace MWInput
|
||||||
|
|
||||||
void InputManager::toggleControlSwitch (const std::string& sw, bool value)
|
void InputManager::toggleControlSwitch (const std::string& sw, bool value)
|
||||||
{
|
{
|
||||||
if (mControlSwitch[sw] == value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/// \note 7 switches at all, if-else is relevant
|
/// \note 7 switches at all, if-else is relevant
|
||||||
if (sw == "playercontrols" && !value) {
|
if (sw == "playercontrols" && !value) {
|
||||||
mPlayer->setLeftRight(0);
|
mPlayer->setLeftRight(0);
|
||||||
|
@ -853,8 +855,8 @@ namespace MWInput
|
||||||
mPlayer->setUpDown(0);
|
mPlayer->setUpDown(0);
|
||||||
} else if (sw == "vanitymode") {
|
} else if (sw == "vanitymode") {
|
||||||
MWBase::Environment::get().getWorld()->allowVanityMode(value);
|
MWBase::Environment::get().getWorld()->allowVanityMode(value);
|
||||||
} else if (sw == "playerlooking") {
|
} else if (sw == "playerlooking" && !value) {
|
||||||
MWBase::Environment::get().getWorld()->togglePlayerLooking(value);
|
MWBase::Environment::get().getWorld()->rotateObject(mPlayer->getPlayer(), 0.f, 0.f, 0.f);
|
||||||
}
|
}
|
||||||
mControlSwitch[sw] = value;
|
mControlSwitch[sw] = value;
|
||||||
}
|
}
|
||||||
|
@ -989,7 +991,7 @@ namespace MWInput
|
||||||
rot[2] = -x;
|
rot[2] = -x;
|
||||||
|
|
||||||
// Only actually turn player when we're not in vanity mode
|
// 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->yaw(x);
|
||||||
mPlayer->pitch(y);
|
mPlayer->pitch(y);
|
||||||
|
|
|
@ -76,6 +76,7 @@ namespace MWInput
|
||||||
osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler,
|
osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler,
|
||||||
osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation,
|
osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation,
|
||||||
const std::string& userFile, bool userFileExists,
|
const std::string& userFile, bool userFileExists,
|
||||||
|
const std::string& userControllerBindingsFile,
|
||||||
const std::string& controllerBindingsFile, bool grab);
|
const std::string& controllerBindingsFile, bool grab);
|
||||||
|
|
||||||
virtual ~InputManager();
|
virtual ~InputManager();
|
||||||
|
|
|
@ -2025,14 +2025,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
MWWorld::Ptr player = getPlayer();
|
MWWorld::Ptr player = getPlayer();
|
||||||
|
|
||||||
CreatureStats& stats = player.getClass().getCreatureStats(player);
|
if (!MWBase::Environment::get().getMechanicsManager()->isSneaking(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)
|
|
||||||
{
|
{
|
||||||
MWBase::Environment::get().getWindowManager()->setSneakVisibility(false);
|
MWBase::Environment::get().getWindowManager()->setSneakVisibility(false);
|
||||||
return;
|
return;
|
||||||
|
@ -2040,6 +2033,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
static float sneakSkillTimer = 0.f; // Times sneak skill progress from "avoid notice"
|
static float sneakSkillTimer = 0.f; // Times sneak skill progress from "avoid notice"
|
||||||
|
|
||||||
|
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||||
const MWWorld::Store<ESM::GameSetting>& gmst = world->getStore().get<ESM::GameSetting>();
|
const MWWorld::Store<ESM::GameSetting>& gmst = world->getStore().get<ESM::GameSetting>();
|
||||||
static const float fSneakUseDist = gmst.find("fSneakUseDist")->mValue.getFloat();
|
static const float fSneakUseDist = gmst.find("fSneakUseDist")->mValue.getFloat();
|
||||||
static const float fSneakUseDelay = gmst.find("fSneakUseDelay")->mValue.getFloat();
|
static const float fSneakUseDelay = gmst.find("fSneakUseDelay")->mValue.getFloat();
|
||||||
|
|
|
@ -511,10 +511,9 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (targetClass.hasInventoryStore(target))
|
if (targetClass.hasInventoryStore(target))
|
||||||
{
|
{
|
||||||
MWMechanics::WeaponType weapType = WeapType_None;
|
int weapType = ESM::Weapon::None;
|
||||||
MWWorld::ContainerStoreIterator weaponSlot =
|
MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(target, &weapType);
|
||||||
MWMechanics::getActiveWeapon(targetClass.getCreatureStats(target), targetClass.getInventoryStore(target), &weapType);
|
if (weapType > ESM::Weapon::None)
|
||||||
if (weapType != WeapType_PickProbe && weapType != WeapType_Spell && weapType != WeapType_None && weapType != WeapType_HandToHand)
|
|
||||||
targetWeapon = *weaponSlot;
|
targetWeapon = *weaponSlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -729,7 +728,7 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t
|
||||||
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||||
|
|
||||||
// get projectile speed (depending on weapon type)
|
// 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 fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->mValue.getFloat();
|
||||||
static float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat();
|
static float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat();
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "combat.hpp"
|
#include "combat.hpp"
|
||||||
#include "weaponpriority.hpp"
|
#include "weaponpriority.hpp"
|
||||||
#include "spellpriority.hpp"
|
#include "spellpriority.hpp"
|
||||||
|
#include "weapontype.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
@ -125,8 +126,7 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
const ESM::Weapon* weapon = mWeapon.get<ESM::Weapon>()->mBase;
|
const ESM::Weapon* weapon = mWeapon.get<ESM::Weapon>()->mBase;
|
||||||
|
if (MWMechanics::getWeaponType(weapon->mData.mType)->mWeaponClass != ESM::WeaponType::Melee)
|
||||||
if (weapon->mData.mType >= ESM::Weapon::MarksmanBow)
|
|
||||||
{
|
{
|
||||||
isRanged = true;
|
isRanged = true;
|
||||||
return fProjectileMaxSpeed;
|
return fProjectileMaxSpeed;
|
||||||
|
@ -194,11 +194,12 @@ namespace MWMechanics
|
||||||
if (rating > bestActionRating)
|
if (rating > bestActionRating)
|
||||||
{
|
{
|
||||||
const ESM::Weapon* weapon = it->get<ESM::Weapon>()->mBase;
|
const ESM::Weapon* weapon = it->get<ESM::Weapon>()->mBase;
|
||||||
|
int ammotype = getWeaponType(weapon->mData.mType)->mAmmoType;
|
||||||
|
|
||||||
MWWorld::Ptr ammo;
|
MWWorld::Ptr ammo;
|
||||||
if (weapon->mData.mType == ESM::Weapon::MarksmanBow)
|
if (ammotype == ESM::Weapon::Arrow)
|
||||||
ammo = bestArrow;
|
ammo = bestArrow;
|
||||||
else if (weapon->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
else if (ammotype == ESM::Weapon::Bolt)
|
||||||
ammo = bestBolt;
|
ammo = bestBolt;
|
||||||
|
|
||||||
bestActionRating = rating;
|
bestActionRating = rating;
|
||||||
|
@ -367,7 +368,7 @@ namespace MWMechanics
|
||||||
else if (!activeWeapon.isEmpty())
|
else if (!activeWeapon.isEmpty())
|
||||||
{
|
{
|
||||||
const ESM::Weapon* esmWeap = activeWeapon.get<ESM::Weapon>()->mBase;
|
const ESM::Weapon* esmWeap = activeWeapon.get<ESM::Weapon>()->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();
|
static const float fTargetSpellMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat();
|
||||||
dist = fTargetSpellMaxSpeed;
|
dist = fTargetSpellMaxSpeed;
|
||||||
|
|
|
@ -301,7 +301,7 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoin
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
const float actorSpeed = actor.getClass().getSpeed(actor);
|
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 distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length();
|
||||||
|
|
||||||
const float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2;
|
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
|
// get actor's shortest radius for moving in circle
|
||||||
float speed = actor.getClass().getSpeed(actor);
|
float speed = actor.getClass().getSpeed(actor);
|
||||||
speed += speed * 0.1f; // 10% real speed inaccuracy
|
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
|
// get radius direction to the center
|
||||||
const float* rot = actor.getRefData().getPosition().rot;
|
const float* rot = actor.getRefData().getPosition().rot;
|
||||||
|
|
|
@ -448,7 +448,7 @@ namespace MWMechanics
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// have not yet reached the destination
|
// have not yet reached the destination
|
||||||
evadeObstacles(actor, storage);
|
evadeObstacles(actor, duration, storage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,8 +479,17 @@ namespace MWMechanics
|
||||||
storage.setState(AiWanderStorage::Wander_IdleNow);
|
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())
|
if (mObstacleCheck.isEvading())
|
||||||
{
|
{
|
||||||
// first check if we're walking into a door
|
// first check if we're walking into a door
|
||||||
|
|
|
@ -137,7 +137,7 @@ namespace MWMechanics
|
||||||
short unsigned getRandomIdle();
|
short unsigned getRandomIdle();
|
||||||
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||||
void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
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 turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage);
|
||||||
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||||
void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||||
|
|
|
@ -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
|
std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const
|
||||||
{
|
{
|
||||||
int numAnims=0;
|
int numAnims=0;
|
||||||
|
@ -331,6 +304,8 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
|
||||||
{
|
{
|
||||||
mAnimation->disable(mCurrentWeapon);
|
mAnimation->disable(mCurrentWeapon);
|
||||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||||
|
if (mWeaponType > ESM::Weapon::None)
|
||||||
|
mAnimation->showWeapons(true);
|
||||||
}
|
}
|
||||||
else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped)
|
else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped)
|
||||||
{
|
{
|
||||||
|
@ -361,7 +336,7 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
|
||||||
idle = CharState_None;
|
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)
|
if (!force && jump == mJumpState && idle == CharState_None)
|
||||||
return;
|
return;
|
||||||
|
@ -371,22 +346,17 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState
|
||||||
if (jump != JumpState_None)
|
if (jump != JumpState_None)
|
||||||
{
|
{
|
||||||
jumpAnimName = "jump";
|
jumpAnimName = "jump";
|
||||||
if(weap != sWeaponTypeListEnd)
|
if(!weapShortGroup.empty())
|
||||||
{
|
{
|
||||||
jumpAnimName += weap->shortgroup;
|
jumpAnimName += weapShortGroup;
|
||||||
if(!mAnimation->hasAnimation(jumpAnimName))
|
if(!mAnimation->hasAnimation(jumpAnimName))
|
||||||
{
|
{
|
||||||
jumpmask = MWRender::Animation::BlendMask_LowerBody;
|
jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask);
|
||||||
jumpAnimName = "jump";
|
|
||||||
|
|
||||||
// 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.
|
// For upper body there will be idle animation.
|
||||||
if (idle == CharState_None)
|
if (jumpmask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None)
|
||||||
idle = CharState_Idle;
|
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)
|
if (movement == mMovementState && idle == mIdleState && !force)
|
||||||
return;
|
return;
|
||||||
|
@ -474,15 +502,15 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
||||||
if(movestate != sMovementListEnd)
|
if(movestate != sMovementListEnd)
|
||||||
{
|
{
|
||||||
movementAnimName = movestate->groupname;
|
movementAnimName = movestate->groupname;
|
||||||
if(weap != sWeaponTypeListEnd)
|
if(!weapShortGroup.empty())
|
||||||
{
|
{
|
||||||
std::string::size_type swimpos = movementAnimName.find("swim");
|
std::string::size_type swimpos = movementAnimName.find("swim");
|
||||||
if (swimpos == std::string::npos)
|
if (swimpos == std::string::npos)
|
||||||
{
|
{
|
||||||
if (mWeaponType == WeapType_Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case
|
if (mWeaponType == ESM::Weapon::Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case
|
||||||
movementAnimName = weap->shortgroup + movementAnimName;
|
movementAnimName = weapShortGroup + movementAnimName;
|
||||||
else
|
else
|
||||||
movementAnimName += weap->shortgroup;
|
movementAnimName += weapShortGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mAnimation->hasAnimation(movementAnimName))
|
if(!mAnimation->hasAnimation(movementAnimName))
|
||||||
|
@ -490,15 +518,12 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
||||||
movementAnimName = movestate->groupname;
|
movementAnimName = movestate->groupname;
|
||||||
if (swimpos == std::string::npos)
|
if (swimpos == std::string::npos)
|
||||||
{
|
{
|
||||||
movemask = MWRender::Animation::BlendMask_LowerBody;
|
movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask);
|
||||||
// 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;
|
|
||||||
|
|
||||||
// For crossbow animations use 1h ones as fallback
|
// If we apply movement only for lower body, do not reset idle animations.
|
||||||
if (mWeaponType == WeapType_Crossbow)
|
// For upper body there will be idle animation.
|
||||||
movementAnimName += "1h";
|
if (movemask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None)
|
||||||
|
idle = CharState_Idle;
|
||||||
}
|
}
|
||||||
else if (idle == CharState_None)
|
else if (idle == CharState_None)
|
||||||
{
|
{
|
||||||
|
@ -520,7 +545,20 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
||||||
if(!mAnimation->hasAnimation(movementAnimName))
|
if(!mAnimation->hasAnimation(movementAnimName))
|
||||||
{
|
{
|
||||||
std::string::size_type swimpos = movementAnimName.find("swim");
|
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");
|
std::string::size_type runpos = movementAnimName.find("run");
|
||||||
if (runpos != std::string::npos)
|
if (runpos != std::string::npos)
|
||||||
|
@ -532,25 +570,6 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
||||||
else
|
else
|
||||||
movementAnimName.clear();
|
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;
|
mCurrentMovement = movementAnimName;
|
||||||
if(!mCurrentMovement.empty())
|
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
|
// 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.
|
// even if we are running. This must be replicated, otherwise the observed speed would differ drastically.
|
||||||
std::string anim = mCurrentMovement;
|
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.
|
// The first person anims don't have any velocity to calculate a speed multiplier from.
|
||||||
// We use the third person velocities instead.
|
// We use the third person velocities instead.
|
||||||
// FIXME: should be pulled from the actual animation, but it is not presently loaded.
|
// 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;
|
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),
|
// 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
|
// the idle animation should be displayed
|
||||||
|
@ -644,11 +659,13 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat
|
||||||
else if(mIdleState != CharState_None)
|
else if(mIdleState != CharState_None)
|
||||||
{
|
{
|
||||||
idleGroup = "idle";
|
idleGroup = "idle";
|
||||||
if(weap != sWeaponTypeListEnd)
|
if(!weapShortGroup.empty())
|
||||||
{
|
{
|
||||||
idleGroup += weap->shortgroup;
|
idleGroup += weapShortGroup;
|
||||||
if(!mAnimation->hasAnimation(idleGroup))
|
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
|
// 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
|
// 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())
|
if (mPtr.getClass().isActor())
|
||||||
refreshHitRecoilAnims(idle);
|
refreshHitRecoilAnims(idle);
|
||||||
|
|
||||||
const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType));
|
std::string weap;
|
||||||
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
if (mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
weap = sWeaponTypeListEnd;
|
weap = getWeaponType(mWeaponType)->mShortGroup;
|
||||||
|
|
||||||
refreshJumpAnims(weap, jump, idle, force);
|
refreshJumpAnims(weap, jump, idle, force);
|
||||||
refreshMovementAnims(weap, movement, idle, force);
|
refreshMovementAnims(weap, movement, idle, force);
|
||||||
|
@ -694,77 +711,6 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
||||||
refreshIdleAnims(weap, idle, force);
|
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<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
|
|
||||||
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)
|
void CharacterController::playDeath(float startpoint, CharacterState death)
|
||||||
{
|
{
|
||||||
// Make sure the character was swimming upon death for forward-compatibility
|
// 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)
|
, mHitState(CharState_None)
|
||||||
, mUpperBodyState(UpperCharState_Nothing)
|
, mUpperBodyState(UpperCharState_Nothing)
|
||||||
, mJumpState(JumpState_None)
|
, mJumpState(JumpState_None)
|
||||||
, mWeaponType(WeapType_None)
|
, mWeaponType(ESM::Weapon::None)
|
||||||
, mAttackStrength(0.f)
|
, mAttackStrength(0.f)
|
||||||
, mSkipAnim(false)
|
, mSkipAnim(false)
|
||||||
, mSecondsOfSwimming(0)
|
, mSecondsOfSwimming(0)
|
||||||
|
@ -933,19 +879,20 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
||||||
|
|
||||||
if (cls.hasInventoryStore(mPtr))
|
if (cls.hasInventoryStore(mPtr))
|
||||||
{
|
{
|
||||||
getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType);
|
getActiveWeapon(mPtr, &mWeaponType);
|
||||||
if (mWeaponType != WeapType_None)
|
if (mWeaponType != ESM::Weapon::None)
|
||||||
{
|
{
|
||||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
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);
|
mAnimation->showWeapons(true);
|
||||||
// Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly,
|
// 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)
|
// 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);
|
mAnimation->setWeaponGroup(mCurrentWeapon, useRelativeDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1185,11 +1132,11 @@ bool CharacterController::updateCreatureState()
|
||||||
const MWWorld::Class &cls = mPtr.getClass();
|
const MWWorld::Class &cls = mPtr.getClass();
|
||||||
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
||||||
|
|
||||||
WeaponType weapType = WeapType_None;
|
int weapType = ESM::Weapon::None;
|
||||||
if(stats.getDrawState() == DrawState_Weapon)
|
if(stats.getDrawState() == DrawState_Weapon)
|
||||||
weapType = WeapType_HandToHand;
|
weapType = ESM::Weapon::HandToHand;
|
||||||
else if (stats.getDrawState() == DrawState_Spell)
|
else if (stats.getDrawState() == DrawState_Spell)
|
||||||
weapType = WeapType_Spell;
|
weapType = ESM::Weapon::Spell;
|
||||||
|
|
||||||
if (weapType != mWeaponType)
|
if (weapType != mWeaponType)
|
||||||
{
|
{
|
||||||
|
@ -1206,7 +1153,7 @@ bool CharacterController::updateCreatureState()
|
||||||
|
|
||||||
std::string startKey = "start";
|
std::string startKey = "start";
|
||||||
std::string stopKey = "stop";
|
std::string stopKey = "stop";
|
||||||
if (weapType == WeapType_Spell)
|
if (weapType == ESM::Weapon::Spell)
|
||||||
{
|
{
|
||||||
const std::string spellid = stats.getSpells().getSelectedSpell();
|
const std::string spellid = stats.getSpells().getSelectedSpell();
|
||||||
bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr);
|
bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr);
|
||||||
|
@ -1272,7 +1219,7 @@ bool CharacterController::updateCreatureState()
|
||||||
mCurrentWeapon = "";
|
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();
|
mCurrentWeapon = chooseRandomAttackAnimation();
|
||||||
}
|
}
|
||||||
|
@ -1287,7 +1234,7 @@ bool CharacterController::updateCreatureState()
|
||||||
|
|
||||||
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
|
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
|
||||||
|
|
||||||
if (weapType == WeapType_HandToHand)
|
if (weapType == ESM::Weapon::HandToHand)
|
||||||
playSwishSound(0.0f);
|
playSwishSound(0.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1301,34 +1248,23 @@ bool CharacterController::updateCreatureState()
|
||||||
return false;
|
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
|
// 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",
|
// 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.
|
// but they are also present in weapon drawing animation.
|
||||||
switch (weaptype)
|
return !(getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded);
|
||||||
{
|
|
||||||
case WeapType_Spell:
|
|
||||||
case WeapType_BowAndArrow:
|
|
||||||
case WeapType_Crossbow:
|
|
||||||
case WeapType_HandToHand:
|
|
||||||
case WeapType_TwoHand:
|
|
||||||
case WeapType_TwoWide:
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CharacterController::updateWeaponState(CharacterState& idle)
|
bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
{
|
{
|
||||||
const MWWorld::Class &cls = mPtr.getClass();
|
const MWWorld::Class &cls = mPtr.getClass();
|
||||||
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
||||||
WeaponType weaptype = WeapType_None;
|
int weaptype = ESM::Weapon::None;
|
||||||
if(stats.getDrawState() == DrawState_Weapon)
|
if(stats.getDrawState() == DrawState_Weapon)
|
||||||
weaptype = WeapType_HandToHand;
|
weaptype = ESM::Weapon::HandToHand;
|
||||||
else if (stats.getDrawState() == DrawState_Spell)
|
else if (stats.getDrawState() == DrawState_Spell)
|
||||||
weaptype = WeapType_Spell;
|
weaptype = ESM::Weapon::Spell;
|
||||||
|
|
||||||
const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf();
|
const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf();
|
||||||
|
|
||||||
|
@ -1338,18 +1274,18 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
if (mPtr.getClass().hasInventoryStore(mPtr))
|
if (mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
{
|
{
|
||||||
MWWorld::InventoryStore &inv = cls.getInventoryStore(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)
|
if(stats.getDrawState() == DrawState_Spell)
|
||||||
weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
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);
|
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);
|
downSoundId = weapon->getClass().getDownSoundId(*weapon);
|
||||||
|
|
||||||
// weapon->HtH switch: weapon is empty already, so we need to take sound from previous 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);
|
downSoundId = mWeapon.getClass().getDownSoundId(mWeapon);
|
||||||
|
|
||||||
MWWorld::Ptr newWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr();
|
MWWorld::Ptr newWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr();
|
||||||
|
@ -1370,8 +1306,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
bool forcestateupdate = false;
|
bool forcestateupdate = false;
|
||||||
|
|
||||||
// We should not play equipping animation and sound during weapon->weapon transition
|
// We should not play equipping animation and sound during weapon->weapon transition
|
||||||
bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell &&
|
bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None &&
|
||||||
mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell;
|
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),
|
// 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.
|
// 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;
|
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||||
mAttackingOrSpell = false;
|
mAttackingOrSpell = false;
|
||||||
mAnimation->disable(mCurrentWeapon);
|
mAnimation->disable(mCurrentWeapon);
|
||||||
|
mAnimation->showWeapons(true);
|
||||||
if (mPtr == getPlayer())
|
if (mPtr == getPlayer())
|
||||||
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
||||||
}
|
}
|
||||||
|
@ -1388,7 +1325,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
if(!isKnockedOut() && !isKnockedDown() && !isRecovery())
|
if(!isKnockedOut() && !isKnockedDown() && !isRecovery())
|
||||||
{
|
{
|
||||||
std::string weapgroup;
|
std::string weapgroup;
|
||||||
if ((!isWerewolf || mWeaponType != WeapType_Spell)
|
if ((!isWerewolf || mWeaponType != ESM::Weapon::Spell)
|
||||||
&& weaptype != mWeaponType
|
&& weaptype != mWeaponType
|
||||||
&& mUpperBodyState != UpperCharState_UnEquipingWeap
|
&& mUpperBodyState != UpperCharState_UnEquipingWeap
|
||||||
&& !isStillWeapon)
|
&& !isStillWeapon)
|
||||||
|
@ -1397,7 +1334,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
if (!weaponChanged)
|
if (!weaponChanged)
|
||||||
{
|
{
|
||||||
// Note: we do not disable unequipping animation automatically to avoid body desync
|
// Note: we do not disable unequipping animation automatically to avoid body desync
|
||||||
getWeaponGroup(mWeaponType, weapgroup);
|
weapgroup = getWeaponAnimation(mWeaponType);
|
||||||
mAnimation->play(weapgroup, priorityWeapon,
|
mAnimation->play(weapgroup, priorityWeapon,
|
||||||
MWRender::Animation::BlendMask_All, false,
|
MWRender::Animation::BlendMask_All, false,
|
||||||
1.0f, "unequip start", "unequip stop", 0.0f, 0);
|
1.0f, "unequip start", "unequip stop", 0.0f, 0);
|
||||||
|
@ -1425,17 +1362,18 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
{
|
{
|
||||||
forcestateupdate = true;
|
forcestateupdate = true;
|
||||||
mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype));
|
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,
|
// 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)
|
// 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);
|
mAnimation->setWeaponGroup(weapgroup, useRelativeDuration);
|
||||||
|
|
||||||
if (!isStillWeapon)
|
if (!isStillWeapon)
|
||||||
{
|
{
|
||||||
mAnimation->disable(mCurrentWeapon);
|
mAnimation->disable(mCurrentWeapon);
|
||||||
if (weaptype != WeapType_None)
|
if (weaptype != ESM::Weapon::None)
|
||||||
{
|
{
|
||||||
mAnimation->showWeapons(false);
|
mAnimation->showWeapons(false);
|
||||||
mAnimation->play(weapgroup, priorityWeapon,
|
mAnimation->play(weapgroup, priorityWeapon,
|
||||||
|
@ -1444,7 +1382,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
mUpperBodyState = UpperCharState_EquipingWeap;
|
mUpperBodyState = UpperCharState_EquipingWeap;
|
||||||
|
|
||||||
// If we do not have the "equip attach" key, show weapon manually.
|
// 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)
|
if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0)
|
||||||
mAnimation->showWeapons(true);
|
mAnimation->showWeapons(true);
|
||||||
|
@ -1464,7 +1402,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
}
|
}
|
||||||
|
|
||||||
mWeaponType = weaptype;
|
mWeaponType = weaptype;
|
||||||
getWeaponGroup(mWeaponType, mCurrentWeapon);
|
mCurrentWeapon = getWeaponAnimation(mWeaponType);
|
||||||
|
|
||||||
if(!upSoundId.empty() && !isStillWeapon)
|
if(!upSoundId.empty() && !isStillWeapon)
|
||||||
{
|
{
|
||||||
|
@ -1478,8 +1416,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
{
|
{
|
||||||
mUpperBodyState = UpperCharState_Nothing;
|
mUpperBodyState = UpperCharState_Nothing;
|
||||||
mAnimation->disable(mCurrentWeapon);
|
mAnimation->disable(mCurrentWeapon);
|
||||||
mWeaponType = WeapType_None;
|
mWeaponType = ESM::Weapon::None;
|
||||||
getWeaponGroup(mWeaponType, mCurrentWeapon);
|
mCurrentWeapon = getWeaponAnimation(mWeaponType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1490,7 +1428,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run)
|
if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run)
|
||||||
&& mHasMovedInXY
|
&& mHasMovedInXY
|
||||||
&& !MWBase::Environment::get().getWorld()->isSwimming(mPtr)
|
&& !MWBase::Environment::get().getWorld()->isSwimming(mPtr)
|
||||||
&& mWeaponType == WeapType_None)
|
&& mWeaponType == ESM::Weapon::None)
|
||||||
{
|
{
|
||||||
if(!sndMgr->getSoundPlaying(mPtr, "WolfRun"))
|
if(!sndMgr->getSoundPlaying(mPtr, "WolfRun"))
|
||||||
sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWSound::Type::Sfx,
|
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))
|
if (mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
{
|
{
|
||||||
MWWorld::InventoryStore &inv = cls.getInventoryStore(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());
|
isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name());
|
||||||
if(isWeapon)
|
if (isWeapon)
|
||||||
|
{
|
||||||
weapSpeed = weapon->get<ESM::Weapon>()->mBase->mData.mSpeed;
|
weapSpeed = weapon->get<ESM::Weapon>()->mBase->mData.mSpeed;
|
||||||
|
MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||||
|
int ammotype = getWeaponType(weapon->get<ESM::Weapon>()->mBase->mData.mType)->mAmmoType;
|
||||||
|
if (ammotype != ESM::Weapon::None && (ammo == inv.end() || ammo->get<ESM::Weapon>()->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<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt);
|
|
||||||
else if (mWeaponType == WeapType_BowAndArrow)
|
|
||||||
ammunition = (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Arrow);
|
|
||||||
if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped)
|
if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped)
|
||||||
{
|
{
|
||||||
mAnimation->disable(mCurrentWeapon);
|
mAnimation->disable(mCurrentWeapon);
|
||||||
|
@ -1530,6 +1469,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
|
|
||||||
float complete;
|
float complete;
|
||||||
bool animPlaying;
|
bool animPlaying;
|
||||||
|
ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
|
||||||
if(mAttackingOrSpell)
|
if(mAttackingOrSpell)
|
||||||
{
|
{
|
||||||
MWWorld::Ptr player = getPlayer();
|
MWWorld::Ptr player = getPlayer();
|
||||||
|
@ -1548,7 +1488,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
mCurrentWeapon = chooseRandomAttackAnimation();
|
mCurrentWeapon = chooseRandomAttackAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mWeaponType == WeapType_Spell)
|
if(mWeaponType == ESM::Weapon::Spell)
|
||||||
{
|
{
|
||||||
// Unset casting flag, otherwise pressing the mouse button down would
|
// Unset casting flag, otherwise pressing the mouse button down would
|
||||||
// continue casting every frame if there is no animation
|
// continue casting every frame if there is no animation
|
||||||
|
@ -1673,7 +1613,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
resetIdle = false;
|
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::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
MWWorld::Ptr item = *weapon;
|
MWWorld::Ptr item = *weapon;
|
||||||
|
@ -1703,7 +1643,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
{
|
{
|
||||||
std::string startKey;
|
std::string startKey;
|
||||||
std::string stopKey;
|
std::string stopKey;
|
||||||
if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown)
|
|
||||||
|
if(weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)
|
||||||
{
|
{
|
||||||
mAttackType = "shoot";
|
mAttackType = "shoot";
|
||||||
startKey = mAttackType+" start";
|
startKey = mAttackType+" start";
|
||||||
|
@ -1785,7 +1726,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
|
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();
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
|
|
||||||
|
@ -1815,15 +1756,17 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
else if (isKnockedDown())
|
else if (isKnockedDown())
|
||||||
{
|
{
|
||||||
if (mUpperBodyState > UpperCharState_WeapEquiped)
|
if (mUpperBodyState > UpperCharState_WeapEquiped)
|
||||||
|
{
|
||||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||||
|
if (mWeaponType > ESM::Weapon::None)
|
||||||
|
mAnimation->showWeapons(true);
|
||||||
|
}
|
||||||
mAnimation->disable(mCurrentWeapon);
|
mAnimation->disable(mCurrentWeapon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mAnimation->setPitchFactor(0.f);
|
mAnimation->setPitchFactor(0.f);
|
||||||
if (mWeaponType == WeapType_BowAndArrow ||
|
if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)
|
||||||
mWeaponType == WeapType_Thrown ||
|
|
||||||
mWeaponType == WeapType_Crossbow)
|
|
||||||
{
|
{
|
||||||
switch (mUpperBodyState)
|
switch (mUpperBodyState)
|
||||||
{
|
{
|
||||||
|
@ -1840,7 +1783,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
{
|
{
|
||||||
// technically we do not need a pitch for crossbow reload animation,
|
// technically we do not need a pitch for crossbow reload animation,
|
||||||
// but we should avoid abrupt repositioning
|
// 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));
|
mAnimation->setPitchFactor(std::max(0.f, 1.f-complete*10.f));
|
||||||
else
|
else
|
||||||
mAnimation->setPitchFactor(1.f-complete);
|
mAnimation->setPitchFactor(1.f-complete);
|
||||||
|
@ -1857,7 +1800,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
mUpperBodyState == UpperCharState_FollowStartToFollowStop ||
|
mUpperBodyState == UpperCharState_FollowStartToFollowStop ||
|
||||||
mUpperBodyState == UpperCharState_CastingSpell)
|
mUpperBodyState == UpperCharState_CastingSpell)
|
||||||
{
|
{
|
||||||
if (ammunition && mWeaponType == WeapType_Crossbow)
|
if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow)
|
||||||
mAnimation->attachArrow();
|
mAnimation->attachArrow();
|
||||||
|
|
||||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||||
|
@ -1934,7 +1877,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||||
if(!start.empty())
|
if(!start.empty())
|
||||||
{
|
{
|
||||||
int mask = MWRender::Animation::BlendMask_All;
|
int mask = MWRender::Animation::BlendMask_All;
|
||||||
if (mWeaponType == WeapType_Crossbow)
|
if (mWeaponType == ESM::Weapon::MarksmanCrossbow)
|
||||||
mask = MWRender::Animation::BlendMask_UpperBody;
|
mask = MWRender::Animation::BlendMask_UpperBody;
|
||||||
|
|
||||||
mAnimation->disable(mCurrentWeapon);
|
mAnimation->disable(mCurrentWeapon);
|
||||||
|
@ -2187,7 +2130,8 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
cls.getCreatureStats(mPtr).setFatigue(fatigue);
|
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;
|
vec.z() = 0.0f;
|
||||||
|
|
||||||
bool inJump = true;
|
bool inJump = true;
|
||||||
|
@ -2210,7 +2154,6 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
else if(vec.z() > 0.0f && mJumpState != JumpState_InAir)
|
else if(vec.z() > 0.0f && mJumpState != JumpState_InAir)
|
||||||
{
|
{
|
||||||
// Started a jump.
|
// Started a jump.
|
||||||
float z = cls.getJump(mPtr);
|
|
||||||
if (z > 0)
|
if (z > 0)
|
||||||
{
|
{
|
||||||
if(vec.x() == 0 && vec.y() == 0)
|
if(vec.x() == 0 && vec.y() == 0)
|
||||||
|
@ -2221,28 +2164,6 @@ void CharacterController::update(float duration, bool animationOnly)
|
||||||
lat.normalize();
|
lat.normalize();
|
||||||
vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f;
|
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)
|
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));
|
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
||||||
|
|
||||||
movement = vec;
|
movement = vec;
|
||||||
|
|
||||||
cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0;
|
cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0;
|
||||||
|
if (movement.z() == 0.f)
|
||||||
// Can't reset jump state (mPosition[2]) here; we don't know for sure whether the PhysicSystem will actually handle it in this frame
|
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.
|
// 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)
|
if (!mSkipAnim)
|
||||||
|
@ -2775,7 +2696,7 @@ void CharacterController::resurrect()
|
||||||
mAnimation->disable(mCurrentDeath);
|
mAnimation->disable(mCurrentDeath);
|
||||||
mCurrentDeath.clear();
|
mCurrentDeath.clear();
|
||||||
mDeathState = CharState_None;
|
mDeathState = CharState_None;
|
||||||
mWeaponType = WeapType_None;
|
mWeaponType = ESM::Weapon::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterController::updateContinuousVfx()
|
void CharacterController::updateContinuousVfx()
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
#include "../mwrender/animation.hpp"
|
#include "../mwrender/animation.hpp"
|
||||||
|
|
||||||
|
#include "weapontype.hpp"
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class InventoryStore;
|
class InventoryStore;
|
||||||
|
@ -113,21 +115,6 @@ enum CharacterState {
|
||||||
CharState_Block
|
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 {
|
enum UpperBodyCharacterState {
|
||||||
UpperCharState_Nothing,
|
UpperCharState_Nothing,
|
||||||
UpperCharState_EquipingWeap,
|
UpperCharState_EquipingWeap,
|
||||||
|
@ -186,7 +173,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
||||||
JumpingState mJumpState;
|
JumpingState mJumpState;
|
||||||
std::string mCurrentJump;
|
std::string mCurrentJump;
|
||||||
|
|
||||||
WeaponType mWeaponType;
|
int mWeaponType;
|
||||||
std::string mCurrentWeapon;
|
std::string mCurrentWeapon;
|
||||||
|
|
||||||
float mAttackStrength;
|
float mAttackStrength;
|
||||||
|
@ -212,9 +199,9 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
||||||
|
|
||||||
void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false);
|
void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false);
|
||||||
void refreshHitRecoilAnims(CharacterState& idle);
|
void refreshHitRecoilAnims(CharacterState& idle);
|
||||||
void refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, bool force=false);
|
void refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force=false);
|
||||||
void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, CharacterState& idle, bool force=false);
|
void refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force=false);
|
||||||
void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false);
|
void refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force=false);
|
||||||
|
|
||||||
void clearAnimQueue(bool clearPersistAnims = 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
|
/// @param num if non-nullptr, the chosen animation number will be written here
|
||||||
std::string chooseRandomGroup (const std::string& prefix, int* num = nullptr) const;
|
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:
|
public:
|
||||||
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
|
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
|
||||||
|
@ -312,8 +303,6 @@ public:
|
||||||
|
|
||||||
void playSwishSound(float attackStrength);
|
void playSwishSound(float attackStrength);
|
||||||
};
|
};
|
||||||
|
|
||||||
MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* GAME_MWMECHANICS_CHARACTER_HPP */
|
#endif /* GAME_MWMECHANICS_CHARACTER_HPP */
|
||||||
|
|
|
@ -311,23 +311,18 @@ namespace MWMechanics
|
||||||
applyWerewolfDamageMult(victim, projectile, damage);
|
applyWerewolfDamageMult(victim, projectile, damage);
|
||||||
|
|
||||||
if (attacker == getPlayer())
|
if (attacker == getPlayer())
|
||||||
{
|
|
||||||
attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0);
|
attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0);
|
||||||
const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence();
|
|
||||||
|
|
||||||
bool unaware = !sequence.isInCombat()
|
const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence();
|
||||||
&& !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim);
|
bool unaware = attacker == getPlayer() && !sequence.isInCombat()
|
||||||
|
&& !MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim);
|
||||||
if (unaware)
|
bool knockedDown = victim.getClass().getCreatureStats(victim).getKnockedDown();
|
||||||
{
|
if (knockedDown || 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())
|
|
||||||
damage *= gmst.find("fCombatKODamageMult")->mValue.getFloat();
|
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);
|
reduceWeaponCondition(damage, validVictim, weapon, attacker);
|
||||||
|
|
|
@ -490,7 +490,12 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool MechanicsManager::isSneaking(const MWWorld::Ptr& ptr)
|
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)
|
void MechanicsManager::rest(double hours, bool sleep)
|
||||||
|
@ -1027,8 +1032,7 @@ namespace MWMechanics
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// check if a player tries to pickpocket a target NPC
|
// check if a player tries to pickpocket a target NPC
|
||||||
if(ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak)
|
if (target.getClass().getCreatureStats(target).getKnockedDown() || isSneaking(ptr))
|
||||||
|| target.getClass().getCreatureStats(target).getKnockedDown())
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -1679,9 +1683,7 @@ namespace MWMechanics
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
float sneakTerm = 0;
|
float sneakTerm = 0;
|
||||||
if (ptr.getClass().getCreatureStats(ptr).getStance(CreatureStats::Stance_Sneak)
|
if (isSneaking(ptr))
|
||||||
&& !MWBase::Environment::get().getWorld()->isSwimming(ptr)
|
|
||||||
&& MWBase::Environment::get().getWorld()->isOnGround(ptr))
|
|
||||||
{
|
{
|
||||||
static float fSneakSkillMult = store.find("fSneakSkillMult")->mValue.getFloat();
|
static float fSneakSkillMult = store.find("fSneakSkillMult")->mValue.getFloat();
|
||||||
static float fSneakBootMult = store.find("fSneakBootMult")->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 agility = stats.getAttribute(ESM::Attribute::Agility).getModified();
|
||||||
int luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
int luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||||
float bootWeight = 0;
|
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);
|
const MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
|
||||||
MWWorld::ConstContainerStoreIterator it = inv.getSlot(MWWorld::InventoryStore::Slot_Boots);
|
MWWorld::ConstContainerStoreIterator it = inv.getSlot(MWWorld::InventoryStore::Slot_Boots);
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
|
||||||
#include "../mwbase/environment.hpp"
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
|
||||||
|
@ -78,10 +76,8 @@ namespace MWMechanics
|
||||||
return MWWorld::Ptr(); // none found
|
return MWWorld::Ptr(); // none found
|
||||||
}
|
}
|
||||||
|
|
||||||
ObstacleCheck::ObstacleCheck():
|
ObstacleCheck::ObstacleCheck()
|
||||||
mPrevX(0) // to see if the moved since last time
|
: mWalkState(State_Norm)
|
||||||
, mPrevY(0)
|
|
||||||
, mWalkState(State_Norm)
|
|
||||||
, mStuckDuration(0)
|
, mStuckDuration(0)
|
||||||
, mEvadeDuration(0)
|
, mEvadeDuration(0)
|
||||||
, mDistSameSpot(-1) // avoid calculating it each time
|
, mDistSameSpot(-1) // avoid calculating it each time
|
||||||
|
@ -125,21 +121,15 @@ namespace MWMechanics
|
||||||
*/
|
*/
|
||||||
void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration)
|
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)
|
if (mDistSameSpot == -1)
|
||||||
{
|
mDistSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor);
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
const float distSameSpot = mDistSameSpot * duration;
|
const float distSameSpot = mDistSameSpot * duration;
|
||||||
const float squaredMovedDistance = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2();
|
const bool samePosition = (pos - mPrev).length2() < distSameSpot * distSameSpot;
|
||||||
const bool samePosition = squaredMovedDistance < distSameSpot * distSameSpot;
|
|
||||||
|
|
||||||
// update position
|
mPrev = pos;
|
||||||
mPrevX = pos.pos[0];
|
|
||||||
mPrevY = pos.pos[1];
|
|
||||||
|
|
||||||
switch(mWalkState)
|
switch(mWalkState)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#ifndef OPENMW_MECHANICS_OBSTACLE_H
|
#ifndef OPENMW_MECHANICS_OBSTACLE_H
|
||||||
#define OPENMW_MECHANICS_OBSTACLE_H
|
#define OPENMW_MECHANICS_OBSTACLE_H
|
||||||
|
|
||||||
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class Ptr;
|
class Ptr;
|
||||||
|
@ -37,9 +39,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// for checking if we're stuck (ignoring Z axis)
|
// for checking if we're stuck
|
||||||
float mPrevX;
|
osg::Vec3f mPrev;
|
||||||
float mPrevY;
|
|
||||||
|
|
||||||
// directions to try moving in when get stuck
|
// directions to try moving in when get stuck
|
||||||
static const float evadeDirections[NUM_EVADE_DIRECTIONS][2];
|
static const float evadeDirections[NUM_EVADE_DIRECTIONS][2];
|
||||||
|
|
|
@ -73,6 +73,13 @@ namespace
|
||||||
{
|
{
|
||||||
return sqrDistance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y()));
|
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
|
namespace MWMechanics
|
||||||
|
@ -320,8 +327,7 @@ namespace MWMechanics
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const auto world = MWBase::Environment::get().getWorld();
|
const auto world = MWBase::Environment::get().getWorld();
|
||||||
const auto realHalfExtents = world->getHalfExtents(actor); // This may differ from halfExtents argument
|
const auto stepSize = getPathStepSize(actor);
|
||||||
const auto stepSize = 2 * std::max(realHalfExtents.x(), realHalfExtents.y());
|
|
||||||
const auto navigator = world->getNavigator();
|
const auto navigator = world->getNavigator();
|
||||||
navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, out);
|
navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, out);
|
||||||
}
|
}
|
||||||
|
@ -333,4 +339,39 @@ namespace MWMechanics
|
||||||
<< DetourNavigator::WriteFlags {flags} << ")";
|
<< 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<osg::Vec3f> 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} << ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,9 @@ namespace MWMechanics
|
||||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
|
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
|
||||||
const DetourNavigator::Flags flags);
|
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
|
/// Remove front point if exist and within tolerance
|
||||||
void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance);
|
void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance);
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
#include "npcstats.hpp"
|
#include "npcstats.hpp"
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
#include "aifollow.hpp"
|
#include "aifollow.hpp"
|
||||||
|
#include "weapontype.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
@ -258,7 +259,7 @@ namespace MWMechanics
|
||||||
float castChance = 100.f;
|
float castChance = 100.f;
|
||||||
if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor())
|
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)
|
if (castChance > 0)
|
||||||
x *= 50 / castChance;
|
x *= 50 / castChance;
|
||||||
|
@ -936,18 +937,23 @@ namespace MWMechanics
|
||||||
mStack = false;
|
mStack = false;
|
||||||
|
|
||||||
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
||||||
|
bool isProjectile = false;
|
||||||
|
if (item.getTypeName() == typeid(ESM::Weapon).name())
|
||||||
|
{
|
||||||
|
int type = item.get<ESM::Weapon>()->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
|
// 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<float>(enchantment->mData.mCost), mCaster);
|
int castCost = getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), mCaster);
|
||||||
|
|
||||||
if (item.getCellRef().getEnchantmentCharge() == -1)
|
if (item.getCellRef().getEnchantmentCharge() == -1)
|
||||||
item.getCellRef().setEnchantmentCharge(static_cast<float>(enchantment->mData.mCharge));
|
item.getCellRef().setEnchantmentCharge(static_cast<float>(enchantment->mData.mCharge));
|
||||||
|
|
||||||
if (godmode)
|
|
||||||
castCost = 0;
|
|
||||||
|
|
||||||
if (item.getCellRef().getEnchantmentCharge() < castCost)
|
if (item.getCellRef().getEnchantmentCharge() < castCost)
|
||||||
{
|
{
|
||||||
if (mCaster == getPlayer())
|
if (mCaster == getPlayer())
|
||||||
|
@ -975,17 +981,17 @@ namespace MWMechanics
|
||||||
item.getCellRef().setEnchantmentCharge(item.getCellRef().getEnchantmentCharge() - castCost);
|
item.getCellRef().setEnchantmentCharge(item.getCellRef().getEnchantmentCharge() - castCost);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
|
if (type == ESM::Enchantment::WhenUsed)
|
||||||
{
|
{
|
||||||
if (mCaster == getPlayer())
|
if (mCaster == getPlayer())
|
||||||
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
|
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
|
||||||
}
|
}
|
||||||
else if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
|
else if (type == ESM::Enchantment::CastOnce)
|
||||||
{
|
{
|
||||||
if (!godmode)
|
if (!godmode)
|
||||||
item.getContainerStore()->remove(item, 1, mCaster);
|
item.getContainerStore()->remove(item, 1, mCaster);
|
||||||
}
|
}
|
||||||
else if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
else if (type == ESM::Enchantment::WhenStrikes)
|
||||||
{
|
{
|
||||||
if (mCaster == getPlayer())
|
if (mCaster == getPlayer())
|
||||||
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3);
|
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3);
|
||||||
|
@ -993,13 +999,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
|
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
|
||||||
|
|
||||||
bool isProjectile = false;
|
|
||||||
if (item.getTypeName() == typeid(ESM::Weapon).name())
|
|
||||||
{
|
|
||||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = item.get<ESM::Weapon>();
|
|
||||||
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())
|
if (isProjectile || !mTarget.isEmpty())
|
||||||
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
|
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,8 @@ namespace MWMechanics
|
||||||
* @note actor can be an NPC or a creature
|
* @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.
|
* @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 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=false);
|
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 std::string& spellId, const MWWorld::Ptr& actor);
|
||||||
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
|
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
#include "creaturestats.hpp"
|
#include "creaturestats.hpp"
|
||||||
#include "spellcasting.hpp"
|
#include "spellcasting.hpp"
|
||||||
|
#include "weapontype.hpp"
|
||||||
|
#include "combat.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -122,9 +124,6 @@ namespace MWMechanics
|
||||||
return 0.f;
|
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
|
// Spells don't stack, so early out if the spell is still active on the target
|
||||||
int types = getRangeTypes(spell->mEffects);
|
int types = getRangeTypes(spell->mEffects);
|
||||||
if ((types & Self) && stats.getActiveSpells().isSpellActive(spell->mId))
|
if ((types & Self) && stats.getActiveSpells().isSpellActive(spell->mId))
|
||||||
|
@ -379,7 +378,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
case ESM::MagicEffect::BoundLongbow:
|
case ESM::MagicEffect::BoundLongbow:
|
||||||
// AI should not summon the bow if there is no suitable ammo.
|
// 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;
|
return 0.f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, f
|
||||||
if (absDiff < epsilonRadians)
|
if (absDiff < epsilonRadians)
|
||||||
return true;
|
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)
|
if (absDiff > limit)
|
||||||
diff = osg::sign(diff) * limit;
|
diff = osg::sign(diff) * limit;
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include <osg/Math>
|
#include <osg/Math>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
class Ptr;
|
class Ptr;
|
||||||
|
@ -12,7 +14,12 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
|
||||||
// Max rotating speed, radian/sec
|
// 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)
|
/// configure rotation settings for an actor to reach this target angle (eventually)
|
||||||
/// @return have we reached the target angle?
|
/// @return have we reached the target angle?
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "aicombataction.hpp"
|
#include "aicombataction.hpp"
|
||||||
#include "spellpriority.hpp"
|
#include "spellpriority.hpp"
|
||||||
#include "spellcasting.hpp"
|
#include "spellcasting.hpp"
|
||||||
|
#include "weapontype.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
@ -34,14 +35,15 @@ namespace MWMechanics
|
||||||
const MWBase::World* world = MWBase::Environment::get().getWorld();
|
const MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||||
const MWWorld::Store<ESM::GameSetting>& gmst = world->getStore().get<ESM::GameSetting>();
|
const MWWorld::Store<ESM::GameSetting>& gmst = world->getStore().get<ESM::GameSetting>();
|
||||||
|
|
||||||
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;
|
return 0.f;
|
||||||
|
|
||||||
float rating=0.f;
|
float rating=0.f;
|
||||||
static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat();
|
static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat();
|
||||||
float ratingMult = fAIMeleeWeaponMult;
|
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
|
// Underwater ranged combat is impossible
|
||||||
if (world->isUnderwater(MWWorld::ConstPtr(actor), 0.75f)
|
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;
|
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
|
// 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
|
// 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;
|
rating = chop * 2;
|
||||||
}
|
}
|
||||||
else if (weapon->mData.mType >= ESM::Weapon::MarksmanBow)
|
else if (weapclass != ESM::WeaponType::Melee)
|
||||||
{
|
{
|
||||||
rating = chop;
|
rating = chop;
|
||||||
}
|
}
|
||||||
|
@ -76,24 +78,28 @@ namespace MWMechanics
|
||||||
|
|
||||||
adjustWeaponDamage(rating, item, actor);
|
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);
|
resistNormalWeapon(enemy, actor, item, rating);
|
||||||
applyWerewolfDamageMult(enemy, item, rating);
|
applyWerewolfDamageMult(enemy, item, rating);
|
||||||
}
|
}
|
||||||
else if (weapon->mData.mType == ESM::Weapon::MarksmanBow)
|
else
|
||||||
{
|
{
|
||||||
if (arrowRating <= 0.f)
|
int ammotype = MWMechanics::getWeaponType(weapon->mData.mType)->mAmmoType;
|
||||||
rating = 0.f;
|
if (ammotype == ESM::Weapon::Arrow)
|
||||||
else
|
{
|
||||||
rating += arrowRating;
|
if (arrowRating <= 0.f)
|
||||||
}
|
rating = 0.f;
|
||||||
else if (weapon->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
else
|
||||||
{
|
rating += arrowRating;
|
||||||
if (boltRating <= 0.f)
|
}
|
||||||
rating = 0.f;
|
else if (ammotype == ESM::Weapon::Bolt)
|
||||||
else
|
{
|
||||||
rating += boltRating;
|
if (boltRating <= 0.f)
|
||||||
|
rating = 0.f;
|
||||||
|
else
|
||||||
|
rating += boltRating;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!weapon->mEnchant.empty())
|
if (!weapon->mEnchant.empty())
|
||||||
|
@ -102,8 +108,9 @@ namespace MWMechanics
|
||||||
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
||||||
{
|
{
|
||||||
int castCost = getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), actor);
|
int castCost = getEffectiveEnchantmentCastCost(static_cast<float>(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);
|
rating += rateEffects(enchantment->mEffects, actor, enemy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,13 +132,13 @@ namespace MWMechanics
|
||||||
float chance = getHitChance(actor, enemy, value) / 100.f;
|
float chance = getHitChance(actor, enemy, value) / 100.f;
|
||||||
rating *= std::min(1.f, std::max(0.01f, chance));
|
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;
|
rating *= weapon->mData.mSpeed;
|
||||||
|
|
||||||
return rating * ratingMult;
|
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;
|
float bestAmmoRating = 0.f;
|
||||||
if (!actor.getClass().hasInventoryStore(actor))
|
if (!actor.getClass().hasInventoryStore(actor))
|
||||||
|
@ -152,7 +159,7 @@ namespace MWMechanics
|
||||||
return bestAmmoRating;
|
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;
|
MWWorld::Ptr emptyPtr;
|
||||||
return rateAmmo(actor, enemy, emptyPtr, ammoType);
|
return rateAmmo(actor, enemy, emptyPtr, ammoType);
|
||||||
|
@ -174,8 +181,8 @@ namespace MWMechanics
|
||||||
float bonusDamage = 0.f;
|
float bonusDamage = 0.f;
|
||||||
|
|
||||||
const ESM::Weapon* esmWeap = weapon.get<ESM::Weapon>()->mBase;
|
const ESM::Weapon* esmWeap = weapon.get<ESM::Weapon>()->mBase;
|
||||||
|
int type = esmWeap->mData.mType;
|
||||||
if (esmWeap->mData.mType >= ESM::Weapon::MarksmanBow)
|
if (getWeaponType(type)->mWeaponClass != ESM::WeaponType::Melee)
|
||||||
{
|
{
|
||||||
if (!ammo.isEmpty() && !MWBase::Environment::get().getWorld()->isSwimming(enemy))
|
if (!ammo.isEmpty() && !MWBase::Environment::get().getWorld()->isSwimming(enemy))
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,8 +10,8 @@ namespace MWMechanics
|
||||||
float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy,
|
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);
|
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, MWWorld::Ptr &bestAmmo, int 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, int ammoType);
|
||||||
|
|
||||||
float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy);
|
float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy);
|
||||||
}
|
}
|
||||||
|
|
53
apps/openmw/mwmechanics/weapontype.cpp
Normal file
53
apps/openmw/mwmechanics/weapontype.cpp
Normal file
|
@ -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<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
|
||||||
|
*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<int, ESM::WeaponType>::const_iterator found = sWeaponTypeList.find(weaponType);
|
||||||
|
if (found == sWeaponTypeList.end())
|
||||||
|
{
|
||||||
|
// Use one-handed short blades as fallback
|
||||||
|
return &sWeaponTypeList[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return &found->second;
|
||||||
|
}
|
||||||
|
}
|
271
apps/openmw/mwmechanics/weapontype.hpp
Normal file
271
apps/openmw/mwmechanics/weapontype.hpp
Normal file
|
@ -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<int, ESM::WeaponType> 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
|
|
@ -235,7 +235,7 @@ void MechanicsHelper::resetAttack(Attack* attack)
|
||||||
|
|
||||||
bool MechanicsHelper::getSpellSuccess(std::string spellId, const MWWorld::Ptr& caster)
|
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)
|
void MechanicsHelper::processAttack(Attack attack, const MWWorld::Ptr& attacker)
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
#include "../mwworld/player.hpp"
|
||||||
|
|
||||||
#include "../mwrender/bulletdebugdraw.hpp"
|
#include "../mwrender/bulletdebugdraw.hpp"
|
||||||
|
|
||||||
|
@ -326,7 +327,30 @@ namespace MWPhysics
|
||||||
if (movement.z() > 0 && ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel)
|
if (movement.z() > 0 && ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel)
|
||||||
velocity = osg::Vec3f(0,0,1) * 25;
|
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<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||||
|
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<float> 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
|
// Now that we have the effective movement vector, apply wind forces to it
|
||||||
if (MWBase::Environment::get().getWorld()->isInStorm())
|
if (MWBase::Environment::get().getWorld()->isInStorm())
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
#include "vismask.hpp"
|
#include "vismask.hpp"
|
||||||
|
|
||||||
|
@ -51,8 +52,6 @@ ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group>
|
||||||
|
|
||||||
// Make sure we cleaned object from effects, just in cast if we re-use node
|
// Make sure we cleaned object from effects, just in cast if we re-use node
|
||||||
removeEffects();
|
removeEffects();
|
||||||
|
|
||||||
mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActorAnimation::~ActorAnimation()
|
ActorAnimation::~ActorAnimation()
|
||||||
|
@ -79,12 +78,12 @@ PartHolderPtr ActorAnimation::getWeaponPart(const std::string& model, const std:
|
||||||
return PartHolderPtr();
|
return PartHolderPtr();
|
||||||
|
|
||||||
if (enchantedGlow)
|
if (enchantedGlow)
|
||||||
addGlow(instance, *glowColor);
|
mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor);
|
||||||
|
|
||||||
return PartHolderPtr(new PartHolder(instance));
|
return PartHolderPtr(new PartHolder(instance));
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::Group* ActorAnimation::getBoneByName(std::string boneName)
|
osg::Group* ActorAnimation::getBoneByName(const std::string& boneName)
|
||||||
{
|
{
|
||||||
if (!mObjectRoot)
|
if (!mObjectRoot)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -105,93 +104,13 @@ std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr&
|
||||||
if(type == typeid(ESM::Weapon).name())
|
if(type == typeid(ESM::Weapon).name())
|
||||||
{
|
{
|
||||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon.get<ESM::Weapon>();
|
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon.get<ESM::Weapon>();
|
||||||
ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType;
|
int weaponType = ref->mBase->mData.mType;
|
||||||
return getHolsteredWeaponBoneName(weaponType);
|
return MWMechanics::getWeaponType(weaponType)->mSheathingBone;
|
||||||
}
|
}
|
||||||
|
|
||||||
return boneName;
|
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<osg::Node> 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<osg::Node> 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)
|
void ActorAnimation::resetControllers(osg::Node* node)
|
||||||
{
|
{
|
||||||
if (node == nullptr)
|
if (node == nullptr)
|
||||||
|
@ -205,7 +124,8 @@ void ActorAnimation::resetControllers(osg::Node* node)
|
||||||
|
|
||||||
void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
|
void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
|
||||||
{
|
{
|
||||||
if (!mWeaponSheathing)
|
static const bool weaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
||||||
|
if (!weaponSheathing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
|
@ -219,7 +139,8 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Since throwing weapons stack themselves, do not show such weapon itself
|
// Since throwing weapons stack themselves, do not show such weapon itself
|
||||||
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
|
||||||
|
if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown)
|
||||||
showHolsteredWeapons = false;
|
showHolsteredWeapons = false;
|
||||||
|
|
||||||
std::string mesh = weapon->getClass().getModel(*weapon);
|
std::string mesh = weapon->getClass().getModel(*weapon);
|
||||||
|
@ -238,7 +159,7 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
|
||||||
{
|
{
|
||||||
if (showHolsteredWeapons)
|
if (showHolsteredWeapons)
|
||||||
{
|
{
|
||||||
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
|
osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon);
|
||||||
mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor);
|
mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor);
|
||||||
if (mScabbard)
|
if (mScabbard)
|
||||||
resetControllers(mScabbard->getNode());
|
resetControllers(mScabbard->getNode());
|
||||||
|
@ -271,15 +192,16 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
|
||||||
|
|
||||||
if (isEnchanted)
|
if (isEnchanted)
|
||||||
{
|
{
|
||||||
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
|
osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon);
|
||||||
addGlow(weaponNode, glowColor);
|
mGlowUpdater = SceneUtil::addEnchantedGlow(weaponNode, mResourceSystem, glowColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActorAnimation::updateQuiver()
|
void ActorAnimation::updateQuiver()
|
||||||
{
|
{
|
||||||
if (!mWeaponSheathing)
|
static const bool weaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
||||||
|
if (!weaponSheathing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||||
|
@ -303,10 +225,12 @@ void ActorAnimation::updateQuiver()
|
||||||
bool suitableAmmo = false;
|
bool suitableAmmo = false;
|
||||||
MWWorld::ConstContainerStoreIterator ammo = weapon;
|
MWWorld::ConstContainerStoreIterator ammo = weapon;
|
||||||
unsigned int ammoCount = 0;
|
unsigned int ammoCount = 0;
|
||||||
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
|
||||||
|
const auto& weaponType = MWMechanics::getWeaponType(type);
|
||||||
|
if (weaponType->mWeaponClass == ESM::WeaponType::Thrown)
|
||||||
{
|
{
|
||||||
ammoCount = ammo->getRefData().getCount();
|
ammoCount = ammo->getRefData().getCount();
|
||||||
osg::Group* throwingWeaponNode = getBoneByName("Weapon Bone");
|
osg::Group* throwingWeaponNode = getBoneByName(weaponType->mAttachBone);
|
||||||
if (throwingWeaponNode && throwingWeaponNode->getNumChildren())
|
if (throwingWeaponNode && throwingWeaponNode->getNumChildren())
|
||||||
ammoCount--;
|
ammoCount--;
|
||||||
|
|
||||||
|
@ -323,10 +247,7 @@ void ActorAnimation::updateQuiver()
|
||||||
if (arrowAttached)
|
if (arrowAttached)
|
||||||
ammoCount--;
|
ammoCount--;
|
||||||
|
|
||||||
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
suitableAmmo = ammo->get<ESM::Weapon>()->mBase->mData.mType == weaponType->mAmmoType;
|
||||||
suitableAmmo = ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt;
|
|
||||||
else if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanBow)
|
|
||||||
suitableAmmo = ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Arrow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!suitableAmmo)
|
if (!suitableAmmo)
|
||||||
|
@ -347,14 +268,14 @@ void ActorAnimation::updateQuiver()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new ones
|
// Add new ones
|
||||||
osg::Vec4f glowColor = getEnchantmentColor(*ammo);
|
osg::Vec4f glowColor = ammo->getClass().getEnchantmentColor(*ammo);
|
||||||
std::string model = ammo->getClass().getModel(*ammo);
|
std::string model = ammo->getClass().getModel(*ammo);
|
||||||
for (unsigned int i=0; i<ammoCount; ++i)
|
for (unsigned int i=0; i<ammoCount; ++i)
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Group> arrowNode = ammoNode->getChild(i)->asGroup();
|
osg::ref_ptr<osg::Group> arrowNode = ammoNode->getChild(i)->asGroup();
|
||||||
osg::ref_ptr<osg::Node> arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode);
|
osg::ref_ptr<osg::Node> arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode);
|
||||||
if (!ammo->getClass().getEnchantment(*ammo).empty())
|
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;
|
return;
|
||||||
|
|
||||||
MWWorld::ConstContainerStoreIterator ammo = inv.end();
|
MWWorld::ConstContainerStoreIterator ammo = inv.end();
|
||||||
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
|
||||||
|
if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown)
|
||||||
ammo = weapon;
|
ammo = weapon;
|
||||||
else
|
else
|
||||||
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||||
|
@ -412,7 +334,8 @@ void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MWWorld::ConstContainerStoreIterator ammo = inv.end();
|
MWWorld::ConstContainerStoreIterator ammo = inv.end();
|
||||||
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
|
||||||
|
if (MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown)
|
||||||
ammo = weapon;
|
ammo = weapon;
|
||||||
else
|
else
|
||||||
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||||
|
|
|
@ -40,13 +40,10 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener
|
||||||
virtual bool isArrowAttached() const { return false; }
|
virtual bool isArrowAttached() const { return false; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool mWeaponSheathing;
|
osg::Group* getBoneByName(const std::string& boneName);
|
||||||
osg::Group* getBoneByName(std::string boneName);
|
|
||||||
virtual void updateHolsteredWeapon(bool showHolsteredWeapons);
|
virtual void updateHolsteredWeapon(bool showHolsteredWeapons);
|
||||||
virtual void injectWeaponBones();
|
|
||||||
virtual void updateQuiver();
|
virtual void updateQuiver();
|
||||||
virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon);
|
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, bool enchantedGlow, osg::Vec4f* glowColor);
|
||||||
virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename)
|
virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename)
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
#include <osg/TexGen>
|
|
||||||
#include <osg/TexEnvCombine>
|
|
||||||
#include <osg/MatrixTransform>
|
#include <osg/MatrixTransform>
|
||||||
#include <osg/BlendFunc>
|
#include <osg/BlendFunc>
|
||||||
#include <osg/Material>
|
#include <osg/Material>
|
||||||
|
@ -16,18 +14,18 @@
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
#include <components/resource/resourcesystem.hpp>
|
|
||||||
#include <components/resource/scenemanager.hpp>
|
#include <components/resource/scenemanager.hpp>
|
||||||
#include <components/resource/keyframemanager.hpp>
|
#include <components/resource/keyframemanager.hpp>
|
||||||
#include <components/resource/imagemanager.hpp>
|
|
||||||
|
|
||||||
#include <components/misc/constants.hpp>
|
#include <components/misc/constants.hpp>
|
||||||
|
#include <components/misc/resourcehelpers.hpp>
|
||||||
|
|
||||||
#include <components/nifosg/nifloader.hpp> // KeyframeHolder
|
#include <components/nifosg/nifloader.hpp> // KeyframeHolder
|
||||||
#include <components/nifosg/controller.hpp>
|
#include <components/nifosg/controller.hpp>
|
||||||
|
|
||||||
#include <components/vfs/manager.hpp>
|
#include <components/vfs/manager.hpp>
|
||||||
|
|
||||||
|
#include <components/sceneutil/actorutil.hpp>
|
||||||
#include <components/sceneutil/statesetupdater.hpp>
|
#include <components/sceneutil/statesetupdater.hpp>
|
||||||
#include <components/sceneutil/visitor.hpp>
|
#include <components/sceneutil/visitor.hpp>
|
||||||
#include <components/sceneutil/lightmanager.hpp>
|
#include <components/sceneutil/lightmanager.hpp>
|
||||||
|
@ -238,6 +236,28 @@ namespace
|
||||||
std::vector<std::pair<osg::Node*, osg::Group*> > mToRemove;
|
std::vector<std::pair<osg::Node*, osg::Group*> > 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<std::pair<osg::Node*, osg::Group*> > mFoundBones;
|
||||||
|
};
|
||||||
|
|
||||||
class RemoveFinishedCallbackVisitor : public RemoveVisitor
|
class RemoveFinishedCallbackVisitor : public RemoveVisitor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -519,135 +539,6 @@ namespace MWRender
|
||||||
float mAlpha;
|
float mAlpha;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GlowUpdater : public SceneUtil::StateSetUpdater
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector<osg::ref_ptr<osg::Texture2D> >& 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<osg::ref_ptr<osg::Texture2D> > 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
|
struct Animation::AnimSource
|
||||||
{
|
{
|
||||||
osg::ref_ptr<const NifOsg::KeyframeHolder> mKeyframes;
|
osg::ref_ptr<const NifOsg::KeyframeHolder> mKeyframes;
|
||||||
|
@ -1469,8 +1360,62 @@ namespace MWRender
|
||||||
state->second.mLoopingEnabled = enabled;
|
state->second.mLoopingEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::ref_ptr<osg::Node> getModelInstance(Resource::SceneManager* sceneMgr, const std::string& model, bool baseonly)
|
void loadBonesFromFile(osg::ref_ptr<osg::Node>& baseNode, const std::string &model, Resource::ResourceSystem* resourceSystem)
|
||||||
{
|
{
|
||||||
|
const osg::Node* node = resourceSystem->getSceneManager()->getTemplate(model).get();
|
||||||
|
osg::ref_ptr<osg::Node> sheathSkeleton (const_cast<osg::Node*>(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<osg::Node>& node, const std::string& model, Resource::ResourceSystem* resourceSystem)
|
||||||
|
{
|
||||||
|
if (model.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const std::map<std::string, VFS::File*>& 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<std::string, VFS::File*>::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<osg::Node> getModelInstance(Resource::ResourceSystem* resourceSystem, const std::string& model, bool baseonly, bool inject, const std::string& defaultSkeleton)
|
||||||
|
{
|
||||||
|
Resource::SceneManager* sceneMgr = resourceSystem->getSceneManager();
|
||||||
if (baseonly)
|
if (baseonly)
|
||||||
{
|
{
|
||||||
typedef std::map<std::string, osg::ref_ptr<osg::Node> > Cache;
|
typedef std::map<std::string, osg::ref_ptr<osg::Node> > Cache;
|
||||||
|
@ -1480,6 +1425,12 @@ namespace MWRender
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Node> created = sceneMgr->getInstance(model);
|
osg::ref_ptr<osg::Node> created = sceneMgr->getInstance(model);
|
||||||
|
|
||||||
|
if (inject)
|
||||||
|
{
|
||||||
|
injectCustomBones(created, defaultSkeleton, resourceSystem);
|
||||||
|
injectCustomBones(created, model, resourceSystem);
|
||||||
|
}
|
||||||
|
|
||||||
SceneUtil::CleanObjectRootVisitor removeDrawableVisitor;
|
SceneUtil::CleanObjectRootVisitor removeDrawableVisitor;
|
||||||
created->accept(removeDrawableVisitor);
|
created->accept(removeDrawableVisitor);
|
||||||
removeDrawableVisitor.remove();
|
removeDrawableVisitor.remove();
|
||||||
|
@ -1492,7 +1443,17 @@ namespace MWRender
|
||||||
return sceneMgr->createInstance(found->second);
|
return sceneMgr->createInstance(found->second);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return sceneMgr->getInstance(model);
|
{
|
||||||
|
osg::ref_ptr<osg::Node> 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)
|
void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature)
|
||||||
|
@ -1514,9 +1475,45 @@ namespace MWRender
|
||||||
mAccumRoot = nullptr;
|
mAccumRoot = nullptr;
|
||||||
mAccumCtrl = 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<ESM::Creature> *ref = mPtr.get<ESM::Creature>();
|
||||||
|
if(ref->mBase->mFlags & ESM::Creature::Bipedal)
|
||||||
|
{
|
||||||
|
defaultSkeleton = "meshes\\xbase_anim.nif";
|
||||||
|
inject = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inject = true;
|
||||||
|
MWWorld::LiveCellRef<ESM::NPC> *ref = mPtr.get<ESM::NPC>();
|
||||||
|
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<ESM::Race>().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)
|
if (!forceskeleton)
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Node> created = getModelInstance(mResourceSystem->getSceneManager(), model, baseonly);
|
osg::ref_ptr<osg::Node> created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton);
|
||||||
mInsert->addChild(created);
|
mInsert->addChild(created);
|
||||||
mObjectRoot = created->asGroup();
|
mObjectRoot = created->asGroup();
|
||||||
if (!mObjectRoot)
|
if (!mObjectRoot)
|
||||||
|
@ -1532,7 +1529,7 @@ namespace MWRender
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
osg::ref_ptr<osg::Node> created = getModelInstance(mResourceSystem->getSceneManager(), model, baseonly);
|
osg::ref_ptr<osg::Node> created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton);
|
||||||
osg::ref_ptr<SceneUtil::Skeleton> skel = dynamic_cast<SceneUtil::Skeleton*>(created.get());
|
osg::ref_ptr<SceneUtil::Skeleton> skel = dynamic_cast<SceneUtil::Skeleton*>(created.get());
|
||||||
if (!skel)
|
if (!skel)
|
||||||
{
|
{
|
||||||
|
@ -1574,25 +1571,6 @@ namespace MWRender
|
||||||
return mObjectRoot.get();
|
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)
|
void Animation::addSpellCastGlow(const ESM::MagicEffect *effect, float glowDuration)
|
||||||
{
|
{
|
||||||
osg::Vec4f glowColor(1,1,1,1);
|
osg::Vec4f glowColor(1,1,1,1);
|
||||||
|
@ -1611,78 +1589,10 @@ namespace MWRender
|
||||||
mGlowUpdater->setDuration(glowDuration);
|
mGlowUpdater->setDuration(glowDuration);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
addGlow(mObjectRoot, glowColor, glowDuration);
|
mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, glowColor, glowDuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::addGlow(osg::ref_ptr<osg::Node> node, osg::Vec4f glowColor, float glowDuration)
|
|
||||||
{
|
|
||||||
std::vector<osg::ref_ptr<osg::Texture2D> > 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<osg::Image> image = mResourceSystem->getImageManager()->getImage(stream.str());
|
|
||||||
osg::ref_ptr<osg::Texture2D> 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> 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<osg::StateSet> 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<ESM::Enchantment>().search(enchantmentName);
|
|
||||||
if (!enchantment)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
assert (enchantment->mEffects.mList.size());
|
|
||||||
|
|
||||||
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().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<osg::Group> parent, const ESM::Light *esmLight)
|
void Animation::addExtraLight(osg::ref_ptr<osg::Group> parent, const ESM::Light *esmLight)
|
||||||
{
|
{
|
||||||
bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior();
|
bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior();
|
||||||
|
@ -2001,7 +1911,7 @@ namespace MWRender
|
||||||
addAnimSource(model, model);
|
addAnimSource(model, model);
|
||||||
|
|
||||||
if (!ptr.getClass().getEnchantment(ptr).empty())
|
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)
|
if (ptr.getTypeName() == typeid(ESM::Light).name() && allowLight)
|
||||||
addExtraLight(getOrCreateObjectRoot(), ptr.get<ESM::Light>()->mBase);
|
addExtraLight(getOrCreateObjectRoot(), ptr.get<ESM::Light>()->mBase);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
#include <components/sceneutil/controller.hpp>
|
#include <components/sceneutil/controller.hpp>
|
||||||
|
#include <components/sceneutil/util.hpp>
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
@ -34,7 +35,6 @@ namespace MWRender
|
||||||
|
|
||||||
class ResetAccumRootCallback;
|
class ResetAccumRootCallback;
|
||||||
class RotateController;
|
class RotateController;
|
||||||
class GlowUpdater;
|
|
||||||
class TransparencyUpdater;
|
class TransparencyUpdater;
|
||||||
|
|
||||||
class EffectAnimationTime : public SceneUtil::ControllerSource
|
class EffectAnimationTime : public SceneUtil::ControllerSource
|
||||||
|
@ -266,7 +266,7 @@ protected:
|
||||||
bool mHasMagicEffects;
|
bool mHasMagicEffects;
|
||||||
|
|
||||||
osg::ref_ptr<SceneUtil::LightSource> mGlowLight;
|
osg::ref_ptr<SceneUtil::LightSource> mGlowLight;
|
||||||
osg::ref_ptr<GlowUpdater> mGlowUpdater;
|
osg::ref_ptr<SceneUtil::GlowUpdater> mGlowUpdater;
|
||||||
osg::ref_ptr<TransparencyUpdater> mTransparencyUpdater;
|
osg::ref_ptr<TransparencyUpdater> mTransparencyUpdater;
|
||||||
|
|
||||||
float mAlpha;
|
float mAlpha;
|
||||||
|
@ -330,10 +330,6 @@ protected:
|
||||||
*/
|
*/
|
||||||
virtual void addControllers();
|
virtual void addControllers();
|
||||||
|
|
||||||
osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const;
|
|
||||||
|
|
||||||
void addGlow(osg::ref_ptr<osg::Node> node, osg::Vec4f glowColor, float glowDuration = -1);
|
|
||||||
|
|
||||||
/// Set the render bin for this animation's object root. May be customized by subclasses.
|
/// Set the render bin for this animation's object root. May be customized by subclasses.
|
||||||
virtual void setRenderBin();
|
virtual void setRenderBin();
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,6 @@ namespace MWRender
|
||||||
mAnimation(nullptr),
|
mAnimation(nullptr),
|
||||||
mFirstPersonView(true),
|
mFirstPersonView(true),
|
||||||
mPreviewMode(false),
|
mPreviewMode(false),
|
||||||
mFreeLook(true),
|
|
||||||
mNearest(30.f),
|
mNearest(30.f),
|
||||||
mFurthest(800.f),
|
mFurthest(800.f),
|
||||||
mIsNearest(false),
|
mIsNearest(false),
|
||||||
|
@ -393,11 +392,6 @@ namespace MWRender
|
||||||
camera = focal + offset;
|
camera = focal + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::togglePlayerLooking(bool enable)
|
|
||||||
{
|
|
||||||
mFreeLook = enable;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Camera::isVanityOrPreviewModeEnabled()
|
bool Camera::isVanityOrPreviewModeEnabled()
|
||||||
{
|
{
|
||||||
return mPreviewMode || mVanity.enabled;
|
return mPreviewMode || mVanity.enabled;
|
||||||
|
|
|
@ -37,7 +37,6 @@ namespace MWRender
|
||||||
|
|
||||||
bool mFirstPersonView;
|
bool mFirstPersonView;
|
||||||
bool mPreviewMode;
|
bool mPreviewMode;
|
||||||
bool mFreeLook;
|
|
||||||
float mNearest;
|
float mNearest;
|
||||||
float mFurthest;
|
float mFurthest;
|
||||||
bool mIsNearest;
|
bool mIsNearest;
|
||||||
|
@ -119,8 +118,6 @@ namespace MWRender
|
||||||
/// Stores focal and camera world positions in passed arguments
|
/// Stores focal and camera world positions in passed arguments
|
||||||
void getPosition(osg::Vec3f &focal, osg::Vec3f &camera);
|
void getPosition(osg::Vec3f &focal, osg::Vec3f &camera);
|
||||||
|
|
||||||
void togglePlayerLooking(bool enable);
|
|
||||||
|
|
||||||
bool isVanityOrPreviewModeEnabled();
|
bool isVanityOrPreviewModeEnabled();
|
||||||
|
|
||||||
bool isNearest();
|
bool isNearest();
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "../mwworld/inventorystore.hpp"
|
#include "../mwworld/inventorystore.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
#include "npcanimation.hpp"
|
#include "npcanimation.hpp"
|
||||||
#include "vismask.hpp"
|
#include "vismask.hpp"
|
||||||
|
@ -290,55 +291,36 @@ namespace MWRender
|
||||||
|
|
||||||
MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter);
|
MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter);
|
||||||
MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
std::string groupname;
|
std::string groupname = "inventoryhandtohand";
|
||||||
bool showCarriedLeft = true;
|
bool showCarriedLeft = true;
|
||||||
if(iter == inv.end())
|
if(iter != inv.end())
|
||||||
groupname = "inventoryhandtohand";
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
const std::string &typeName = iter->getTypeName();
|
groupname = "inventoryweapononehand";
|
||||||
if(typeName == typeid(ESM::Lockpick).name() || typeName == typeid(ESM::Probe).name())
|
if(iter->getTypeName() == typeid(ESM::Weapon).name())
|
||||||
groupname = "inventoryweapononehand";
|
|
||||||
else if(typeName == typeid(ESM::Weapon).name())
|
|
||||||
{
|
{
|
||||||
MWWorld::LiveCellRef<ESM::Weapon> *ref = iter->get<ESM::Weapon>();
|
MWWorld::LiveCellRef<ESM::Weapon> *ref = iter->get<ESM::Weapon>();
|
||||||
|
|
||||||
int type = ref->mBase->mData.mType;
|
int type = ref->mBase->mData.mType;
|
||||||
if(type == ESM::Weapon::ShortBladeOneHand ||
|
const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(type);
|
||||||
type == ESM::Weapon::LongBladeOneHand ||
|
showCarriedLeft = !(weaponInfo->mFlags & ESM::WeaponType::TwoHanded);
|
||||||
type == ESM::Weapon::BluntOneHand ||
|
|
||||||
type == ESM::Weapon::AxeOneHand ||
|
std::string inventoryGroup = weaponInfo->mLongGroup;
|
||||||
type == ESM::Weapon::MarksmanThrown)
|
inventoryGroup = "inventory" + inventoryGroup;
|
||||||
{
|
|
||||||
groupname = "inventoryweapononehand";
|
// We still should use one-handed animation as fallback
|
||||||
}
|
if (mAnimation->hasAnimation(inventoryGroup))
|
||||||
else if(type == ESM::Weapon::MarksmanCrossbow ||
|
groupname = inventoryGroup;
|
||||||
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;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
groupname = "inventoryhandtohand";
|
static const std::string oneHandFallback = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup;
|
||||||
showCarriedLeft = false;
|
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);
|
mAnimation->showCarriedLeft(showCarriedLeft);
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
namespace MWRender
|
namespace MWRender
|
||||||
|
@ -50,9 +52,6 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const
|
||||||
|
|
||||||
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
|
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
|
||||||
{
|
{
|
||||||
if (mWeaponSheathing)
|
|
||||||
injectWeaponBones();
|
|
||||||
|
|
||||||
addAnimSource("meshes\\xbase_anim.nif", model);
|
addAnimSource("meshes\\xbase_anim.nif", model);
|
||||||
}
|
}
|
||||||
addAnimSource(model, model);
|
addAnimSource(model, model);
|
||||||
|
@ -115,7 +114,22 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
||||||
|
|
||||||
std::string bonename;
|
std::string bonename;
|
||||||
if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
|
if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
|
||||||
bonename = "Weapon Bone";
|
{
|
||||||
|
if(item.getTypeName() == typeid(ESM::Weapon).name())
|
||||||
|
{
|
||||||
|
int type = item.get<ESM::Weapon>()->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
|
else
|
||||||
bonename = "Shield Bone";
|
bonename = "Shield Bone";
|
||||||
|
|
||||||
|
@ -132,7 +146,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
||||||
scene.reset(new PartHolder(attached));
|
scene.reset(new PartHolder(attached));
|
||||||
|
|
||||||
if (!item.getClass().getEnchantment(item).empty())
|
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
|
// Crossbows start out with a bolt attached
|
||||||
// FIXME: code duplicated from NpcAnimation
|
// FIXME: code duplicated from NpcAnimation
|
||||||
|
@ -140,8 +154,9 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
||||||
item.getTypeName() == typeid(ESM::Weapon).name() &&
|
item.getTypeName() == typeid(ESM::Weapon).name() &&
|
||||||
item.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
item.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
||||||
{
|
{
|
||||||
|
const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow);
|
||||||
MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||||
if (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt)
|
if (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == weaponInfo->mAmmoType)
|
||||||
attachArrow();
|
attachArrow();
|
||||||
else
|
else
|
||||||
mAmmunition.reset();
|
mAmmunition.reset();
|
||||||
|
@ -173,6 +188,16 @@ bool CreatureWeaponAnimation::isArrowAttached() const
|
||||||
void CreatureWeaponAnimation::attachArrow()
|
void CreatureWeaponAnimation::attachArrow()
|
||||||
{
|
{
|
||||||
WeaponAnimation::attachArrow(mPtr);
|
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();
|
updateQuiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +212,19 @@ osg::Group *CreatureWeaponAnimation::getArrowBone()
|
||||||
if (!mWeapon)
|
if (!mWeapon)
|
||||||
return nullptr;
|
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<ESM::Weapon>()->mBase->mData.mType;
|
||||||
|
int ammoType = MWMechanics::getWeaponType(type)->mAmmoType;
|
||||||
|
|
||||||
|
SceneUtil::FindByNameVisitor findVisitor (MWMechanics::getWeaponType(ammoType)->mAttachBone);
|
||||||
|
|
||||||
mWeapon->getNode()->accept(findVisitor);
|
mWeapon->getNode()->accept(findVisitor);
|
||||||
|
|
||||||
return findVisitor.mFoundNode;
|
return findVisitor.mFoundNode;
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
|
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.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_LLeg, "Left Upper Leg"));
|
||||||
result.insert(std::make_pair(ESM::PRT_RPauldron, "Right Clavicle"));
|
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_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"));
|
result.insert(std::make_pair(ESM::PRT_Tail, "Tail"));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -318,12 +319,6 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
|
||||||
if(mViewMode == viewMode)
|
if(mViewMode == viewMode)
|
||||||
return;
|
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;
|
mViewMode = viewMode;
|
||||||
MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change
|
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);
|
setObjectRoot(smodel, true, true, false);
|
||||||
|
|
||||||
if (mWeaponSheathing)
|
|
||||||
injectWeaponBones();
|
|
||||||
|
|
||||||
updateParts();
|
updateParts();
|
||||||
|
|
||||||
if(!is1stPerson)
|
if(!is1stPerson)
|
||||||
|
@ -572,7 +564,7 @@ void NpcAnimation::updateParts()
|
||||||
|
|
||||||
int prio = 1;
|
int prio = 1;
|
||||||
bool enchantedGlow = !store->getClass().getEnchantment(*store).empty();
|
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())
|
if(store->getTypeName() == typeid(ESM::Clothing).name())
|
||||||
{
|
{
|
||||||
prio = ((slotlist[i].mBasePriority+1)<<1) + 0;
|
prio = ((slotlist[i].mBasePriority+1)<<1) + 0;
|
||||||
|
@ -664,7 +656,7 @@ PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const st
|
||||||
|
|
||||||
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, found->second);
|
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, found->second);
|
||||||
if (enchantedGlow)
|
if (enchantedGlow)
|
||||||
addGlow(attached, *glowColor);
|
mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor);
|
||||||
|
|
||||||
return PartHolderPtr(new PartHolder(attached));
|
return PartHolderPtr(new PartHolder(attached));
|
||||||
}
|
}
|
||||||
|
@ -745,7 +737,26 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
|
||||||
mPartPriorities[type] = priority;
|
mPartPriorities[type] = priority;
|
||||||
try
|
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<ESM::Weapon>()->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
|
// 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;
|
const std::string bonefilter = (type == ESM::PRT_Hair) ? "hair" : bonename;
|
||||||
mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor);
|
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);
|
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||||
if(weapon != inv.end())
|
if(weapon != inv.end())
|
||||||
{
|
{
|
||||||
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
|
osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon);
|
||||||
std::string mesh = weapon->getClass().getModel(*weapon);
|
std::string mesh = weapon->getClass().getModel(*weapon);
|
||||||
addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1,
|
addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1,
|
||||||
mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor);
|
mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor);
|
||||||
|
@ -906,8 +917,9 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
||||||
if (weapon->getTypeName() == typeid(ESM::Weapon).name() &&
|
if (weapon->getTypeName() == typeid(ESM::Weapon).name() &&
|
||||||
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
||||||
{
|
{
|
||||||
|
int ammotype = MWMechanics::getWeaponType(ESM::Weapon::MarksmanCrossbow)->mAmmoType;
|
||||||
MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||||
if (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt)
|
if (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ammotype)
|
||||||
attachArrow();
|
attachArrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -931,7 +943,7 @@ void NpcAnimation::showCarriedLeft(bool show)
|
||||||
MWWorld::ConstContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
MWWorld::ConstContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||||
if(show && iter != inv.end())
|
if(show && iter != inv.end())
|
||||||
{
|
{
|
||||||
osg::Vec4f glowColor = getEnchantmentColor(*iter);
|
osg::Vec4f glowColor = iter->getClass().getEnchantmentColor(*iter);
|
||||||
std::string mesh = iter->getClass().getModel(*iter);
|
std::string mesh = iter->getClass().getModel(*iter);
|
||||||
if (addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1,
|
if (addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1,
|
||||||
mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor))
|
mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor))
|
||||||
|
@ -947,6 +959,16 @@ void NpcAnimation::showCarriedLeft(bool show)
|
||||||
void NpcAnimation::attachArrow()
|
void NpcAnimation::attachArrow()
|
||||||
{
|
{
|
||||||
WeaponAnimation::attachArrow(mPtr);
|
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();
|
updateQuiver();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -962,7 +984,15 @@ osg::Group* NpcAnimation::getArrowBone()
|
||||||
if (!part)
|
if (!part)
|
||||||
return nullptr;
|
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<ESM::Weapon>()->mBase->mData.mType;
|
||||||
|
int ammoType = MWMechanics::getWeaponType(type)->mAmmoType;
|
||||||
|
|
||||||
|
SceneUtil::FindByNameVisitor findVisitor (MWMechanics::getWeaponType(ammoType)->mAttachBone);
|
||||||
part->getNode()->accept(findVisitor);
|
part->getNode()->accept(findVisitor);
|
||||||
|
|
||||||
return findVisitor.mFoundNode;
|
return findVisitor.mFoundNode;
|
||||||
|
|
|
@ -1353,11 +1353,6 @@ namespace MWRender
|
||||||
mCamera->allowVanityMode(allow);
|
mCamera->allowVanityMode(allow);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderingManager::togglePlayerLooking(bool enable)
|
|
||||||
{
|
|
||||||
mCamera->togglePlayerLooking(enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderingManager::changeVanityModeScale(float factor)
|
void RenderingManager::changeVanityModeScale(float factor)
|
||||||
{
|
{
|
||||||
if(mCamera->isVanityOrPreviewModeEnabled())
|
if(mCamera->isVanityOrPreviewModeEnabled())
|
||||||
|
|
|
@ -208,7 +208,6 @@ namespace MWRender
|
||||||
void togglePreviewMode(bool enable);
|
void togglePreviewMode(bool enable);
|
||||||
bool toggleVanityMode(bool enable);
|
bool toggleVanityMode(bool enable);
|
||||||
void allowVanityMode(bool allow);
|
void allowVanityMode(bool allow);
|
||||||
void togglePlayerLooking(bool enable);
|
|
||||||
void changeVanityModeScale(float factor);
|
void changeVanityModeScale(float factor);
|
||||||
|
|
||||||
/// temporarily override the field of view with given value.
|
/// temporarily override the field of view with given value.
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
#include "../mwmechanics/combat.hpp"
|
#include "../mwmechanics/combat.hpp"
|
||||||
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
#include "animation.hpp"
|
#include "animation.hpp"
|
||||||
#include "rotatecontroller.hpp"
|
#include "rotatecontroller.hpp"
|
||||||
|
@ -77,8 +78,10 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor)
|
||||||
return;
|
return;
|
||||||
if (weaponSlot->getTypeName() != typeid(ESM::Weapon).name())
|
if (weaponSlot->getTypeName() != typeid(ESM::Weapon).name())
|
||||||
return;
|
return;
|
||||||
int weaponType = weaponSlot->get<ESM::Weapon>()->mBase->mData.mType;
|
|
||||||
if (weaponType == ESM::Weapon::MarksmanThrown)
|
int type = weaponSlot->get<ESM::Weapon>()->mBase->mData.mType;
|
||||||
|
ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass;
|
||||||
|
if (weapclass == ESM::WeaponType::Thrown)
|
||||||
{
|
{
|
||||||
std::string soundid = weaponSlot->getClass().getUpSoundId(*weaponSlot);
|
std::string soundid = weaponSlot->getClass().getUpSoundId(*weaponSlot);
|
||||||
if(!soundid.empty())
|
if(!soundid.empty())
|
||||||
|
@ -88,7 +91,7 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor)
|
||||||
}
|
}
|
||||||
showWeapon(true);
|
showWeapon(true);
|
||||||
}
|
}
|
||||||
else if (weaponType == ESM::Weapon::MarksmanBow || weaponType == ESM::Weapon::MarksmanCrossbow)
|
else if (weapclass == ESM::WeaponType::Ranged)
|
||||||
{
|
{
|
||||||
osg::Group* parent = getArrowBone();
|
osg::Group* parent = getArrowBone();
|
||||||
if (!parent)
|
if (!parent)
|
||||||
|
@ -154,7 +157,7 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength)
|
||||||
|
|
||||||
MWMechanics::applyFatigueLoss(actor, *weapon, attackStrength);
|
MWMechanics::applyFatigueLoss(actor, *weapon, attackStrength);
|
||||||
|
|
||||||
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
if (MWMechanics::getWeaponType(weapon->get<ESM::Weapon>()->mBase->mData.mType)->mWeaponClass == ESM::WeaponType::Thrown)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
Start of tes3mp addition
|
Start of tes3mp addition
|
||||||
|
|
|
@ -186,14 +186,7 @@ namespace MWScript
|
||||||
virtual void execute (Interpreter::Runtime& runtime)
|
virtual void execute (Interpreter::Runtime& runtime)
|
||||||
{
|
{
|
||||||
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
runtime.push(MWBase::Environment::get().getMechanicsManager()->isSneaking(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));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -248,12 +248,6 @@ namespace MWScript
|
||||||
if (ptr.getTypeName() == typeid(ESM::Door).name() && !ptr.getCellRef().getTeleport())
|
if (ptr.getTypeName() == typeid(ESM::Door).name() && !ptr.getCellRef().getTeleport())
|
||||||
{
|
{
|
||||||
MWBase::Environment::get().getWorld()->activateDoor(ptr, 0);
|
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')
|
if(key < 0 || key > 32767 || *end != '\0')
|
||||||
key = ESM::MagicEffect::effectStringToId(effect);
|
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)
|
for (MWMechanics::MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it)
|
||||||
{
|
{
|
||||||
if (it->first.mId == key && it->second.getModifier() > 0)
|
if (it->first.mId == key && it->second.getModifier() > 0)
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
#include "../mwmechanics/spellcasting.hpp"
|
||||||
|
|
||||||
#include "ref.hpp"
|
#include "ref.hpp"
|
||||||
|
|
||||||
|
@ -495,6 +496,16 @@ namespace MWScript
|
||||||
{
|
{
|
||||||
// Apply looping particles immediately for constant effects
|
// Apply looping particles immediately for constant effects
|
||||||
MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -798,7 +798,7 @@ bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname
|
||||||
if(alGetError() == AL_NO_ERROR)
|
if(alGetError() == AL_NO_ERROR)
|
||||||
Log(Debug::Info) << "Standard Reverb supported";
|
Log(Debug::Info) << "Standard Reverb supported";
|
||||||
}
|
}
|
||||||
EFXEAXREVERBPROPERTIES props = EFX_REVERB_PRESET_GENERIC;
|
EFXEAXREVERBPROPERTIES props = EFX_REVERB_PRESET_LIVINGROOM;
|
||||||
props.flGain = 0.0f;
|
props.flGain = 0.0f;
|
||||||
LoadEffect(mDefaultEffect, props);
|
LoadEffect(mDefaultEffect, props);
|
||||||
}
|
}
|
||||||
|
|
|
@ -520,4 +520,28 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
throw std::runtime_error("class does not support armor ratings");
|
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<ESM::Enchantment>().search(enchantmentName);
|
||||||
|
if (!enchantment)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
assert (enchantment->mEffects.mList.size());
|
||||||
|
|
||||||
|
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <osg/Vec4f>
|
||||||
|
|
||||||
#include "ptr.hpp"
|
#include "ptr.hpp"
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
|
@ -378,6 +380,8 @@ namespace MWWorld
|
||||||
|
|
||||||
/// Get the effective armor rating, factoring in the actor's skills, for the given armor.
|
/// 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 float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const;
|
||||||
|
|
||||||
|
virtual osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
#include "../mwmechanics/spellcasting.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
#include "esmstore.hpp"
|
#include "esmstore.hpp"
|
||||||
#include "class.hpp"
|
#include "class.hpp"
|
||||||
|
@ -357,7 +357,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots
|
||||||
|
|
||||||
const ESM::Weapon* esmWeapon = iter->get<ESM::Weapon>()->mBase;
|
const ESM::Weapon* esmWeapon = iter->get<ESM::Weapon>()->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;
|
continue;
|
||||||
|
|
||||||
if (iter->getClass().getEquipmentSkill(*iter) == weaponSkills[maxWeaponSkill])
|
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<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
|
|
||||||
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)
|
if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, actor).first)
|
||||||
{
|
{
|
||||||
// Do not equip ranged weapons, if there is no suitable ammo
|
// Do not equip ranged weapons, if there is no suitable ammo
|
||||||
bool hasAmmo = true;
|
bool hasAmmo = true;
|
||||||
if (isBow == true)
|
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
|
||||||
|
int type = ref->mBase->mData.mType;
|
||||||
|
int ammotype = MWMechanics::getWeaponType(type)->mAmmoType;
|
||||||
|
if (ammotype == ESM::Weapon::Arrow)
|
||||||
{
|
{
|
||||||
if (arrow == end())
|
if (arrow == end())
|
||||||
hasAmmo = false;
|
hasAmmo = false;
|
||||||
else
|
else
|
||||||
slots_[Slot_Ammunition] = arrow;
|
slots_[Slot_Ammunition] = arrow;
|
||||||
}
|
}
|
||||||
if (isCrossbow == true)
|
else if (ammotype == ESM::Weapon::Bolt)
|
||||||
{
|
{
|
||||||
if (bolt == end())
|
if (bolt == end())
|
||||||
hasAmmo = false;
|
hasAmmo = false;
|
||||||
|
@ -431,7 +421,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots
|
||||||
int slot = itemsSlots.first.front();
|
int slot = itemsSlots.first.front();
|
||||||
slots_[slot] = weapon;
|
slots_[slot] = weapon;
|
||||||
|
|
||||||
if (!isBow && !isCrossbow)
|
if (ammotype == ESM::Weapon::None)
|
||||||
slots_[Slot_Ammunition] = end();
|
slots_[Slot_Ammunition] = end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
#include "../mwmechanics/spellcasting.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
#include "../mwmechanics/aipackage.hpp"
|
#include "../mwmechanics/aipackage.hpp"
|
||||||
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
#include "../mwrender/animation.hpp"
|
#include "../mwrender/animation.hpp"
|
||||||
#include "../mwrender/vismask.hpp"
|
#include "../mwrender/vismask.hpp"
|
||||||
|
@ -317,12 +318,16 @@ namespace MWWorld
|
||||||
state.mIdArrow = projectile.getCellRef().getRefId();
|
state.mIdArrow = projectile.getCellRef().getRefId();
|
||||||
state.mCasterHandle = actor;
|
state.mCasterHandle = actor;
|
||||||
state.mAttackStrength = attackStrength;
|
state.mAttackStrength = attackStrength;
|
||||||
state.mThrown = projectile.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown;
|
|
||||||
|
int type = projectile.get<ESM::Weapon>()->mBase->mData.mType;
|
||||||
|
state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown;
|
||||||
|
|
||||||
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId());
|
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId());
|
||||||
MWWorld::Ptr ptr = ref.getPtr();
|
MWWorld::Ptr ptr = ref.getPtr();
|
||||||
|
|
||||||
createModel(state, ptr.getClass().getModel(ptr), pos, orient, false, false, osg::Vec4(0,0,0,0));
|
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);
|
mProjectiles.push_back(state);
|
||||||
}
|
}
|
||||||
|
@ -602,7 +607,9 @@ namespace MWWorld
|
||||||
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
|
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
|
||||||
MWWorld::Ptr ptr = ref.getPtr();
|
MWWorld::Ptr ptr = ref.getPtr();
|
||||||
model = ptr.getClass().getModel(ptr);
|
model = ptr.getClass().getModel(ptr);
|
||||||
state.mThrown = ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown;
|
|
||||||
|
int weaponType = ptr.get<ESM::Weapon>()->mBase->mData.mType;
|
||||||
|
state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown;
|
||||||
}
|
}
|
||||||
catch(...)
|
catch(...)
|
||||||
{
|
{
|
||||||
|
|
|
@ -388,6 +388,30 @@ namespace MWWorld
|
||||||
iterator end() const;
|
iterator end() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
class Store<ESM::WeaponType> : public StoreBase
|
||||||
|
{
|
||||||
|
std::map<int, ESM::WeaponType> mStatic;
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef std::map<int, ESM::WeaponType>::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
|
} //end namespace
|
||||||
|
|
||||||
|
|
|
@ -1603,7 +1603,7 @@ namespace MWWorld
|
||||||
|
|
||||||
pos.z() += 20; // place slightly above. will snap down to ground with code below
|
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);
|
osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits);
|
||||||
if (traced.z() < pos.z())
|
if (traced.z() < pos.z())
|
||||||
|
@ -1660,6 +1660,9 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
mRendering->rotateObject(ptr, rotate);
|
mRendering->rotateObject(ptr, rotate);
|
||||||
mPhysics->updateRotation(ptr);
|
mPhysics->updateRotation(ptr);
|
||||||
|
|
||||||
|
if (const auto object = mPhysics->getObject(ptr))
|
||||||
|
updateNavigatorObject(object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1844,6 +1847,45 @@ namespace MWWorld
|
||||||
return result.mHit;
|
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<MWWorld::Ptr> 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)
|
void World::processDoors(float duration)
|
||||||
{
|
{
|
||||||
std::map<MWWorld::Ptr, int>::iterator it = mDoorStates.begin();
|
std::map<MWWorld::Ptr, int>::iterator it = mDoorStates.begin();
|
||||||
|
@ -1858,40 +1900,7 @@ namespace MWWorld
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const ESM::Position& objPos = it->first.getRefData().getPosition();
|
bool reached = rotateDoor(it->first, it->second, duration);
|
||||||
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<MWWorld::Ptr> 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);
|
|
||||||
|
|
||||||
if (reached)
|
if (reached)
|
||||||
{
|
{
|
||||||
|
@ -2681,11 +2690,6 @@ namespace MWWorld
|
||||||
mRendering->allowVanityMode(allow);
|
mRendering->allowVanityMode(allow);
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::togglePlayerLooking(bool enable)
|
|
||||||
{
|
|
||||||
mRendering->togglePlayerLooking(enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
void World::changeVanityModeScale(float factor)
|
void World::changeVanityModeScale(float factor)
|
||||||
{
|
{
|
||||||
mRendering->changeVanityModeScale(factor);
|
mRendering->changeVanityModeScale(factor);
|
||||||
|
@ -2853,7 +2857,10 @@ namespace MWWorld
|
||||||
door.getClass().setDoorState(door, state);
|
door.getClass().setDoorState(door, state);
|
||||||
mDoorStates[door] = state;
|
mDoorStates[door] = state;
|
||||||
if (state == 0)
|
if (state == 0)
|
||||||
|
{
|
||||||
mDoorStates.erase(door);
|
mDoorStates.erase(door);
|
||||||
|
rotateDoor(door, state, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -155,6 +155,8 @@ namespace MWWorld
|
||||||
void addContainerScripts(const Ptr& reference, CellStore* cell) override;
|
void addContainerScripts(const Ptr& reference, CellStore* cell) override;
|
||||||
void removeContainerScripts(const Ptr& reference) override;
|
void removeContainerScripts(const Ptr& reference) override;
|
||||||
private:
|
private:
|
||||||
|
bool rotateDoor(const Ptr door, int state, float duration);
|
||||||
|
|
||||||
void processDoors(float duration);
|
void processDoors(float duration);
|
||||||
///< Run physics simulation and modify \a world accordingly.
|
///< Run physics simulation and modify \a world accordingly.
|
||||||
|
|
||||||
|
@ -664,8 +666,6 @@ namespace MWWorld
|
||||||
|
|
||||||
void allowVanityMode(bool allow) override;
|
void allowVanityMode(bool allow) override;
|
||||||
|
|
||||||
void togglePlayerLooking(bool enable) override;
|
|
||||||
|
|
||||||
void changeVanityModeScale(float factor) override;
|
void changeVanityModeScale(float factor) override;
|
||||||
|
|
||||||
bool vanityRotateCamera(float * rot) override;
|
bool vanityRotateCamera(float * rot) override;
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "loadskil.hpp"
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -21,6 +23,10 @@ struct Weapon
|
||||||
|
|
||||||
enum Type
|
enum Type
|
||||||
{
|
{
|
||||||
|
PickProbe = -4,
|
||||||
|
HandToHand = -3,
|
||||||
|
Spell = -2,
|
||||||
|
None = -1,
|
||||||
ShortBladeOneHand = 0,
|
ShortBladeOneHand = 0,
|
||||||
LongBladeOneHand = 1,
|
LongBladeOneHand = 1,
|
||||||
LongBladeTwoHand = 2,
|
LongBladeTwoHand = 2,
|
||||||
|
@ -75,5 +81,34 @@ struct Weapon
|
||||||
void blank();
|
void blank();
|
||||||
///< Set record to default state (does not touch the ID).
|
///< 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
|
#endif
|
||||||
|
|
|
@ -87,7 +87,7 @@ boost::filesystem::path LinuxPath::getLocalPath() const
|
||||||
{
|
{
|
||||||
if (readlink(path, &binPath[0], binPath.size()) != -1)
|
if (readlink(path, &binPath[0], binPath.size()) != -1)
|
||||||
{
|
{
|
||||||
localPath = boost::filesystem::path(binPath).parent_path();
|
localPath = boost::filesystem::path(binPath).parent_path() / "/";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ boost::filesystem::path WindowsPath::getLocalPath() const
|
||||||
|
|
||||||
if (GetModuleFileNameW(nullptr, path, MAX_PATH + 1) > 0)
|
if (GetModuleFileNameW(nullptr, path, MAX_PATH + 1) > 0)
|
||||||
{
|
{
|
||||||
localPath = boost::filesystem::path(bconv::utf_to_utf<char>(path)).parent_path();
|
localPath = boost::filesystem::path(bconv::utf_to_utf<char>(path)).parent_path() / "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookup exe path
|
// lookup exe path
|
||||||
|
|
|
@ -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<unsigned short> lengths;
|
||||||
|
nif->getUShorts(lengths, numStrips);
|
||||||
|
|
||||||
|
for (int i = 0; i < numStrips; i++)
|
||||||
|
{
|
||||||
|
std::vector<unsigned short> strip;
|
||||||
|
nif->getUShorts(strip, lengths[i]);
|
||||||
|
strips.emplace_back(strip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NiAutoNormalParticlesData::read(NIFStream *nif)
|
void NiAutoNormalParticlesData::read(NIFStream *nif)
|
||||||
{
|
{
|
||||||
ShapeData::read(nif);
|
ShapeData::read(nif);
|
||||||
|
@ -143,22 +163,23 @@ void NiPixelData::read(NIFStream *nif)
|
||||||
{
|
{
|
||||||
fmt = (Format)nif->getUInt();
|
fmt = (Format)nif->getUInt();
|
||||||
|
|
||||||
rmask = nif->getInt(); // usually 0xff
|
rmask = nif->getUInt(); // usually 0xff
|
||||||
gmask = nif->getInt(); // usually 0xff00
|
gmask = nif->getUInt(); // usually 0xff00
|
||||||
bmask = nif->getInt(); // usually 0xff0000
|
bmask = nif->getUInt(); // usually 0xff0000
|
||||||
amask = nif->getInt(); // usually 0xff000000 or zero
|
amask = nif->getUInt(); // usually 0xff000000 or zero
|
||||||
|
|
||||||
bpp = nif->getInt();
|
bpp = nif->getUInt();
|
||||||
|
|
||||||
// Unknown
|
// 8 bytes of "Old Fast Compare". Whatever that means.
|
||||||
nif->skip(12);
|
nif->skip(8);
|
||||||
|
palette.read(nif);
|
||||||
|
|
||||||
numberOfMipmaps = nif->getInt();
|
numberOfMipmaps = nif->getUInt();
|
||||||
|
|
||||||
// Bytes per pixel, should be bpp * 8
|
// Bytes per pixel, should be bpp * 8
|
||||||
/* int bytes = */ nif->getInt();
|
/* int bytes = */ nif->getUInt();
|
||||||
|
|
||||||
for(int i=0; i<numberOfMipmaps; i++)
|
for(unsigned int i=0; i<numberOfMipmaps; i++)
|
||||||
{
|
{
|
||||||
// Image size and offset in the following data field
|
// Image size and offset in the following data field
|
||||||
Mipmap m;
|
Mipmap m;
|
||||||
|
@ -169,12 +190,17 @@ void NiPixelData::read(NIFStream *nif)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the data
|
// Read the data
|
||||||
unsigned int dataSize = nif->getInt();
|
unsigned int dataSize = nif->getUInt();
|
||||||
data.reserve(dataSize);
|
data.reserve(dataSize);
|
||||||
for (unsigned i=0; i<dataSize; ++i)
|
for (unsigned i=0; i<dataSize; ++i)
|
||||||
data.push_back((unsigned char)nif->getChar());
|
data.push_back((unsigned char)nif->getChar());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NiPixelData::post(NIFFile *nif)
|
||||||
|
{
|
||||||
|
palette.post(nif);
|
||||||
|
}
|
||||||
|
|
||||||
void NiColorData::read(NIFStream *nif)
|
void NiColorData::read(NIFStream *nif)
|
||||||
{
|
{
|
||||||
mKeyMap = std::make_shared<Vector4KeyMap>();
|
mKeyMap = std::make_shared<Vector4KeyMap>();
|
||||||
|
@ -258,4 +284,14 @@ void NiKeyframeData::read(NIFStream *nif)
|
||||||
mScales->read(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
|
} // Namespace
|
||||||
|
|
|
@ -53,6 +53,15 @@ public:
|
||||||
void read(NIFStream *nif);
|
void read(NIFStream *nif);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class NiTriStripsData : public ShapeData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Triangle strips, series of vertex indices.
|
||||||
|
std::vector<std::vector<unsigned short>> strips;
|
||||||
|
|
||||||
|
void read(NIFStream *nif);
|
||||||
|
};
|
||||||
|
|
||||||
class NiAutoNormalParticlesData : public ShapeData
|
class NiAutoNormalParticlesData : public ShapeData
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -107,6 +116,7 @@ public:
|
||||||
NIPXFMT_RGB8,
|
NIPXFMT_RGB8,
|
||||||
NIPXFMT_RGBA8,
|
NIPXFMT_RGBA8,
|
||||||
NIPXFMT_PAL8,
|
NIPXFMT_PAL8,
|
||||||
|
NIPXFMT_PALA8,
|
||||||
NIPXFMT_DXT1,
|
NIPXFMT_DXT1,
|
||||||
NIPXFMT_DXT3,
|
NIPXFMT_DXT3,
|
||||||
NIPXFMT_DXT5,
|
NIPXFMT_DXT5,
|
||||||
|
@ -114,8 +124,10 @@ public:
|
||||||
};
|
};
|
||||||
Format fmt;
|
Format fmt;
|
||||||
|
|
||||||
unsigned int rmask, gmask, bmask, amask;
|
unsigned int rmask, gmask, bmask, amask, bpp;
|
||||||
int bpp, numberOfMipmaps;
|
|
||||||
|
NiPalettePtr palette;
|
||||||
|
unsigned int numberOfMipmaps;
|
||||||
|
|
||||||
struct Mipmap
|
struct Mipmap
|
||||||
{
|
{
|
||||||
|
@ -127,6 +139,7 @@ public:
|
||||||
std::vector<unsigned char> data;
|
std::vector<unsigned char> data;
|
||||||
|
|
||||||
void read(NIFStream *nif);
|
void read(NIFStream *nif);
|
||||||
|
void post(NIFFile *nif);
|
||||||
};
|
};
|
||||||
|
|
||||||
class NiColorData : public Record
|
class NiColorData : public Record
|
||||||
|
@ -210,5 +223,14 @@ struct NiKeyframeData : public Record
|
||||||
void read(NIFStream *nif);
|
void read(NIFStream *nif);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class NiPalette : public Record
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// 32-bit RGBA colors that correspond to 8-bit indices
|
||||||
|
std::vector<unsigned int> colors;
|
||||||
|
|
||||||
|
void read(NIFStream *nif);
|
||||||
|
};
|
||||||
|
|
||||||
} // Namespace
|
} // Namespace
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -54,6 +54,7 @@ static std::map<std::string,RecordFactoryEntry> makeFactory()
|
||||||
newFactory.insert(makeEntry("NiBSAnimationNode", &construct <NiNode> , RC_NiBSAnimationNode ));
|
newFactory.insert(makeEntry("NiBSAnimationNode", &construct <NiNode> , RC_NiBSAnimationNode ));
|
||||||
newFactory.insert(makeEntry("NiBillboardNode", &construct <NiNode> , RC_NiBillboardNode ));
|
newFactory.insert(makeEntry("NiBillboardNode", &construct <NiNode> , RC_NiBillboardNode ));
|
||||||
newFactory.insert(makeEntry("NiTriShape", &construct <NiTriShape> , RC_NiTriShape ));
|
newFactory.insert(makeEntry("NiTriShape", &construct <NiTriShape> , RC_NiTriShape ));
|
||||||
|
newFactory.insert(makeEntry("NiTriStrips", &construct <NiTriStrips> , RC_NiTriStrips ));
|
||||||
newFactory.insert(makeEntry("NiRotatingParticles", &construct <NiRotatingParticles> , RC_NiRotatingParticles ));
|
newFactory.insert(makeEntry("NiRotatingParticles", &construct <NiRotatingParticles> , RC_NiRotatingParticles ));
|
||||||
newFactory.insert(makeEntry("NiAutoNormalParticles", &construct <NiAutoNormalParticles> , RC_NiAutoNormalParticles ));
|
newFactory.insert(makeEntry("NiAutoNormalParticles", &construct <NiAutoNormalParticles> , RC_NiAutoNormalParticles ));
|
||||||
newFactory.insert(makeEntry("NiCamera", &construct <NiCamera> , RC_NiCamera ));
|
newFactory.insert(makeEntry("NiCamera", &construct <NiCamera> , RC_NiCamera ));
|
||||||
|
@ -96,6 +97,7 @@ static std::map<std::string,RecordFactoryEntry> makeFactory()
|
||||||
newFactory.insert(makeEntry("NiParticleRotation", &construct <NiParticleRotation> , RC_NiParticleRotation ));
|
newFactory.insert(makeEntry("NiParticleRotation", &construct <NiParticleRotation> , RC_NiParticleRotation ));
|
||||||
newFactory.insert(makeEntry("NiFloatData", &construct <NiFloatData> , RC_NiFloatData ));
|
newFactory.insert(makeEntry("NiFloatData", &construct <NiFloatData> , RC_NiFloatData ));
|
||||||
newFactory.insert(makeEntry("NiTriShapeData", &construct <NiTriShapeData> , RC_NiTriShapeData ));
|
newFactory.insert(makeEntry("NiTriShapeData", &construct <NiTriShapeData> , RC_NiTriShapeData ));
|
||||||
|
newFactory.insert(makeEntry("NiTriStripsData", &construct <NiTriStripsData> , RC_NiTriStripsData ));
|
||||||
newFactory.insert(makeEntry("NiVisData", &construct <NiVisData> , RC_NiVisData ));
|
newFactory.insert(makeEntry("NiVisData", &construct <NiVisData> , RC_NiVisData ));
|
||||||
newFactory.insert(makeEntry("NiColorData", &construct <NiColorData> , RC_NiColorData ));
|
newFactory.insert(makeEntry("NiColorData", &construct <NiColorData> , RC_NiColorData ));
|
||||||
newFactory.insert(makeEntry("NiPixelData", &construct <NiPixelData> , RC_NiPixelData ));
|
newFactory.insert(makeEntry("NiPixelData", &construct <NiPixelData> , RC_NiPixelData ));
|
||||||
|
@ -110,6 +112,7 @@ static std::map<std::string,RecordFactoryEntry> makeFactory()
|
||||||
newFactory.insert(makeEntry("NiSourceTexture", &construct <NiSourceTexture> , RC_NiSourceTexture ));
|
newFactory.insert(makeEntry("NiSourceTexture", &construct <NiSourceTexture> , RC_NiSourceTexture ));
|
||||||
newFactory.insert(makeEntry("NiSkinInstance", &construct <NiSkinInstance> , RC_NiSkinInstance ));
|
newFactory.insert(makeEntry("NiSkinInstance", &construct <NiSkinInstance> , RC_NiSkinInstance ));
|
||||||
newFactory.insert(makeEntry("NiLookAtController", &construct <NiLookAtController> , RC_NiLookAtController ));
|
newFactory.insert(makeEntry("NiLookAtController", &construct <NiLookAtController> , RC_NiLookAtController ));
|
||||||
|
newFactory.insert(makeEntry("NiPalette", &construct <NiPalette> , RC_NiPalette ));
|
||||||
return newFactory;
|
return newFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,9 +160,9 @@ public:
|
||||||
{
|
{
|
||||||
std::vector<char> str(length + 1, 0);
|
std::vector<char> 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
|
///Read in a string of the length specified in the file
|
||||||
std::string getString()
|
std::string getString()
|
||||||
|
@ -181,34 +181,34 @@ public:
|
||||||
void getUShorts(std::vector<unsigned short> &vec, size_t size)
|
void getUShorts(std::vector<unsigned short> &vec, size_t size)
|
||||||
{
|
{
|
||||||
vec.resize(size);
|
vec.resize(size);
|
||||||
readLittleEndianDynamicBufferOfType<unsigned short,unsigned short>(inp, &vec.front(), size);
|
readLittleEndianDynamicBufferOfType<unsigned short,unsigned short>(inp, vec.data(), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getFloats(std::vector<float> &vec, size_t size)
|
void getFloats(std::vector<float> &vec, size_t size)
|
||||||
{
|
{
|
||||||
vec.resize(size);
|
vec.resize(size);
|
||||||
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, &vec.front(), size);
|
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, vec.data(), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getVector2s(std::vector<osg::Vec2f> &vec, size_t size)
|
void getVector2s(std::vector<osg::Vec2f> &vec, size_t size)
|
||||||
{
|
{
|
||||||
vec.resize(size);
|
vec.resize(size);
|
||||||
/* The packed storage of each Vec2f is 2 floats exactly */
|
/* The packed storage of each Vec2f is 2 floats exactly */
|
||||||
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp,(float*) &vec.front(), size*2);
|
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp,(float*)vec.data(), size*2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getVector3s(std::vector<osg::Vec3f> &vec, size_t size)
|
void getVector3s(std::vector<osg::Vec3f> &vec, size_t size)
|
||||||
{
|
{
|
||||||
vec.resize(size);
|
vec.resize(size);
|
||||||
/* The packed storage of each Vec3f is 3 floats exactly */
|
/* The packed storage of each Vec3f is 3 floats exactly */
|
||||||
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, (float*) &vec.front(), size*3);
|
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, (float*)vec.data(), size*3);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getVector4s(std::vector<osg::Vec4f> &vec, size_t size)
|
void getVector4s(std::vector<osg::Vec4f> &vec, size_t size)
|
||||||
{
|
{
|
||||||
vec.resize(size);
|
vec.resize(size);
|
||||||
/* The packed storage of each Vec4f is 4 floats exactly */
|
/* The packed storage of each Vec4f is 4 floats exactly */
|
||||||
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, (float*) &vec.front(), size*4);
|
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, (float*)vec.data(), size*4);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getQuaternions(std::vector<osg::Quat> &quat, size_t size)
|
void getQuaternions(std::vector<osg::Quat> &quat, size_t size)
|
||||||
|
|
|
@ -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 NiCamera : Node
|
||||||
{
|
{
|
||||||
struct Camera
|
struct Camera
|
||||||
|
|
|
@ -41,6 +41,7 @@ enum RecordType
|
||||||
RC_NiBillboardNode,
|
RC_NiBillboardNode,
|
||||||
RC_AvoidNode,
|
RC_AvoidNode,
|
||||||
RC_NiTriShape,
|
RC_NiTriShape,
|
||||||
|
RC_NiTriStrips,
|
||||||
RC_NiRotatingParticles,
|
RC_NiRotatingParticles,
|
||||||
RC_NiAutoNormalParticles,
|
RC_NiAutoNormalParticles,
|
||||||
RC_NiBSParticleNode,
|
RC_NiBSParticleNode,
|
||||||
|
@ -80,6 +81,7 @@ enum RecordType
|
||||||
RC_NiParticleRotation,
|
RC_NiParticleRotation,
|
||||||
RC_NiFloatData,
|
RC_NiFloatData,
|
||||||
RC_NiTriShapeData,
|
RC_NiTriShapeData,
|
||||||
|
RC_NiTriStripsData,
|
||||||
RC_NiVisData,
|
RC_NiVisData,
|
||||||
RC_NiColorData,
|
RC_NiColorData,
|
||||||
RC_NiPixelData,
|
RC_NiPixelData,
|
||||||
|
@ -95,7 +97,8 @@ enum RecordType
|
||||||
RC_NiSkinInstance,
|
RC_NiSkinInstance,
|
||||||
RC_RootCollisionNode,
|
RC_RootCollisionNode,
|
||||||
RC_NiSphericalCollider,
|
RC_NiSphericalCollider,
|
||||||
RC_NiLookAtController
|
RC_NiLookAtController,
|
||||||
|
RC_NiPalette
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Base class for all records
|
/// Base class for all records
|
||||||
|
|
|
@ -135,10 +135,12 @@ class NiPixelData;
|
||||||
class NiColorData;
|
class NiColorData;
|
||||||
struct NiKeyframeData;
|
struct NiKeyframeData;
|
||||||
class NiTriShapeData;
|
class NiTriShapeData;
|
||||||
|
class NiTriStripsData;
|
||||||
class NiSkinInstance;
|
class NiSkinInstance;
|
||||||
class NiSourceTexture;
|
class NiSourceTexture;
|
||||||
class NiRotatingParticlesData;
|
class NiRotatingParticlesData;
|
||||||
class NiAutoNormalParticlesData;
|
class NiAutoNormalParticlesData;
|
||||||
|
class NiPalette;
|
||||||
|
|
||||||
typedef RecordPtrT<Node> NodePtr;
|
typedef RecordPtrT<Node> NodePtr;
|
||||||
typedef RecordPtrT<Extra> ExtraPtr;
|
typedef RecordPtrT<Extra> ExtraPtr;
|
||||||
|
@ -154,10 +156,12 @@ typedef RecordPtrT<NiFloatData> NiFloatDataPtr;
|
||||||
typedef RecordPtrT<NiColorData> NiColorDataPtr;
|
typedef RecordPtrT<NiColorData> NiColorDataPtr;
|
||||||
typedef RecordPtrT<NiKeyframeData> NiKeyframeDataPtr;
|
typedef RecordPtrT<NiKeyframeData> NiKeyframeDataPtr;
|
||||||
typedef RecordPtrT<NiTriShapeData> NiTriShapeDataPtr;
|
typedef RecordPtrT<NiTriShapeData> NiTriShapeDataPtr;
|
||||||
|
typedef RecordPtrT<NiTriStripsData> NiTriStripsDataPtr;
|
||||||
typedef RecordPtrT<NiSkinInstance> NiSkinInstancePtr;
|
typedef RecordPtrT<NiSkinInstance> NiSkinInstancePtr;
|
||||||
typedef RecordPtrT<NiSourceTexture> NiSourceTexturePtr;
|
typedef RecordPtrT<NiSourceTexture> NiSourceTexturePtr;
|
||||||
typedef RecordPtrT<NiRotatingParticlesData> NiRotatingParticlesDataPtr;
|
typedef RecordPtrT<NiRotatingParticlesData> NiRotatingParticlesDataPtr;
|
||||||
typedef RecordPtrT<NiAutoNormalParticlesData> NiAutoNormalParticlesDataPtr;
|
typedef RecordPtrT<NiAutoNormalParticlesData> NiAutoNormalParticlesDataPtr;
|
||||||
|
typedef RecordPtrT<NiPalette> NiPalettePtr;
|
||||||
|
|
||||||
typedef RecordListT<Node> NodeList;
|
typedef RecordListT<Node> NodeList;
|
||||||
typedef RecordListT<Property> PropertyList;
|
typedef RecordListT<Property> PropertyList;
|
||||||
|
|
|
@ -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<osg::Vec3f> &vertices = data.vertices;
|
||||||
|
const std::vector<std::vector<unsigned short>> &strips = data.strips;
|
||||||
|
if (vertices.empty() || strips.empty())
|
||||||
|
return;
|
||||||
|
mesh.preallocateVertices(static_cast<int>(data.vertices.size()));
|
||||||
|
int numTriangles = 0;
|
||||||
|
for (const std::vector<unsigned short>& strip : strips)
|
||||||
|
{
|
||||||
|
// Each strip with N points contains information about N-2 triangles.
|
||||||
|
if (strip.size() >= 3)
|
||||||
|
numTriangles += static_cast<int>(strip.size()-2);
|
||||||
|
}
|
||||||
|
mesh.preallocateIndices(static_cast<int>(numTriangles));
|
||||||
|
|
||||||
|
// It's triangulation time. Totally not a NifSkope spell ripoff.
|
||||||
|
for (const std::vector<unsigned short>& 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<int>(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<const Nif::NiTriShape*>(nifNode)->data.get(), transform);
|
||||||
|
else // if (nifNode->recType == Nif::RC_NiTriStrips)
|
||||||
|
fillTriangleMeshWithTransform(mesh, static_cast<const Nif::NiTriStrips*>(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!
|
// NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape!
|
||||||
// It must be ignored completely.
|
// It must be ignored completely.
|
||||||
// (occurs in tr_ex_imp_wall_arch_04.nif)
|
// (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<const Nif::NiTriShape*>(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)
|
bool isAnimated, bool avoid)
|
||||||
{
|
{
|
||||||
assert(shape != nullptr);
|
assert(nifNode != nullptr);
|
||||||
|
|
||||||
// If the object was marked "NCO" earlier, it shouldn't collide with
|
// If the object was marked "NCO" earlier, it shouldn't collide with
|
||||||
// anything. So don't do anything.
|
// anything. So don't do anything.
|
||||||
if ((flags & 0x800))
|
if ((flags & 0x800))
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (nifNode->recType == Nif::RC_NiTriShape)
|
||||||
|
{
|
||||||
|
const Nif::NiTriShape* shape = static_cast<const Nif::NiTriShape*>(nifNode);
|
||||||
|
if (!shape->skin.empty())
|
||||||
|
isAnimated = false;
|
||||||
|
if (shape->data.empty() || shape->data->triangles.empty())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const Nif::NiTriStrips* shape = static_cast<const Nif::NiTriStrips*>(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)
|
if (isAnimated)
|
||||||
{
|
{
|
||||||
|
@ -290,13 +346,13 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags,
|
||||||
|
|
||||||
std::unique_ptr<btTriangleMesh> childMesh(new btTriangleMesh);
|
std::unique_ptr<btTriangleMesh> childMesh(new btTriangleMesh);
|
||||||
|
|
||||||
fillTriangleMesh(*childMesh, shape->data.get());
|
fillTriangleMesh(*childMesh, nifNode);
|
||||||
|
|
||||||
std::unique_ptr<Resource::TriangleMeshShape> childShape(new Resource::TriangleMeshShape(childMesh.get(), true));
|
std::unique_ptr<Resource::TriangleMeshShape> childShape(new Resource::TriangleMeshShape(childMesh.get(), true));
|
||||||
childMesh.release();
|
childMesh.release();
|
||||||
|
|
||||||
float scale = shape->trafo.scale;
|
float scale = nifNode->trafo.scale;
|
||||||
const Nif::Node* parent = shape;
|
const Nif::Node* parent = nifNode;
|
||||||
while (parent->parent)
|
while (parent->parent)
|
||||||
{
|
{
|
||||||
parent = 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()));
|
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());
|
mCompoundShape->addChildShape(trans, childShape.get());
|
||||||
childShape.release();
|
childShape.release();
|
||||||
|
@ -318,7 +374,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags,
|
||||||
if (!mAvoidStaticMesh)
|
if (!mAvoidStaticMesh)
|
||||||
mAvoidStaticMesh.reset(new btTriangleMesh(false));
|
mAvoidStaticMesh.reset(new btTriangleMesh(false));
|
||||||
|
|
||||||
fillTriangleMeshWithTransform(*mAvoidStaticMesh, shape->data.get(), transform);
|
fillTriangleMeshWithTransform(*mAvoidStaticMesh, nifNode, transform);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -326,7 +382,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags,
|
||||||
mStaticMesh.reset(new btTriangleMesh(false));
|
mStaticMesh.reset(new btTriangleMesh(false));
|
||||||
|
|
||||||
// Static shape, just transform all vertices into position
|
// Static shape, just transform all vertices into position
|
||||||
fillTriangleMeshWithTransform(*mStaticMesh, shape->data.get(), transform);
|
fillTriangleMeshWithTransform(*mStaticMesh, nifNode, transform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ namespace Nif
|
||||||
class Node;
|
class Node;
|
||||||
struct Transformation;
|
struct Transformation;
|
||||||
struct NiTriShape;
|
struct NiTriShape;
|
||||||
|
struct NiTriStrips;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace NifBullet
|
namespace NifBullet
|
||||||
|
@ -58,7 +59,7 @@ private:
|
||||||
|
|
||||||
bool hasAutoGeneratedCollision(const Nif::Node *rootNode);
|
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<btCompoundShape> mCompoundShape;
|
std::unique_ptr<btCompoundShape> mCompoundShape;
|
||||||
|
|
||||||
|
|
|
@ -408,8 +408,8 @@ namespace NifOsg
|
||||||
unsigned int clamp = static_cast<unsigned int>(textureEffect->clamp);
|
unsigned int clamp = static_cast<unsigned int>(textureEffect->clamp);
|
||||||
int wrapT = (clamp) & 0x1;
|
int wrapT = (clamp) & 0x1;
|
||||||
int wrapS = (clamp >> 1) & 0x1;
|
int wrapS = (clamp >> 1) & 0x1;
|
||||||
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? 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);
|
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||||
|
|
||||||
osg::ref_ptr<osg::TexEnvCombine> texEnv = new osg::TexEnvCombine;
|
osg::ref_ptr<osg::TexEnvCombine> texEnv = new osg::TexEnvCombine;
|
||||||
texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE);
|
texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE);
|
||||||
|
@ -452,6 +452,7 @@ namespace NifOsg
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Nif::RC_NiTriShape:
|
case Nif::RC_NiTriShape:
|
||||||
|
case Nif::RC_NiTriStrips:
|
||||||
case Nif::RC_NiAutoNormalParticles:
|
case Nif::RC_NiAutoNormalParticles:
|
||||||
case Nif::RC_NiRotatingParticles:
|
case Nif::RC_NiRotatingParticles:
|
||||||
// Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children.
|
// 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.
|
// Marker objects. These meshes are only visible in the editor.
|
||||||
hasMarkers = true;
|
hasMarkers = true;
|
||||||
}
|
}
|
||||||
|
else if(sd->string == "BONE")
|
||||||
|
{
|
||||||
|
node->getOrCreateUserDataContainer()->addDescription("CustomBone");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,7 +580,7 @@ namespace NifOsg
|
||||||
node->setDataVariance(osg::Object::DYNAMIC);
|
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);
|
node->setDataVariance(osg::Object::DYNAMIC);
|
||||||
}
|
}
|
||||||
|
@ -584,20 +589,25 @@ namespace NifOsg
|
||||||
|
|
||||||
applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags);
|
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<const Nif::NiTriShape*>(nifNode);
|
const std::string nodeName = Misc::StringUtils::lowerCase(nifNode->name);
|
||||||
const std::string nodeName = Misc::StringUtils::lowerCase(triShape->name);
|
|
||||||
static const std::string markerName = "tri editormarker";
|
static const std::string markerName = "tri editormarker";
|
||||||
static const std::string shadowName = "shadow";
|
static const std::string shadowName = "shadow";
|
||||||
static const std::string shadowName2 = "tri shadow";
|
static const std::string shadowName2 = "tri shadow";
|
||||||
const bool isMarker = hasMarkers && !nodeName.compare(0, markerName.size(), markerName);
|
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 (!isMarker && nodeName.compare(0, shadowName.size(), shadowName) && nodeName.compare(0, shadowName2.size(), shadowName2))
|
||||||
{
|
{
|
||||||
if (triShape->skin.empty())
|
Nif::NiSkinInstancePtr skin;
|
||||||
handleTriShape(triShape, node, composite, boundTextures, animflags);
|
if (nifNode->recType == Nif::RC_NiTriShape)
|
||||||
|
skin = static_cast<const Nif::NiTriShape*>(nifNode)->skin;
|
||||||
|
else // if (nifNode->recType == Nif::RC_NiTriStrips)
|
||||||
|
skin = static_cast<const Nif::NiTriStrips*>(nifNode)->skin;
|
||||||
|
|
||||||
|
if (skin.empty())
|
||||||
|
handleTriShape(nifNode, node, composite, boundTextures, animflags);
|
||||||
else
|
else
|
||||||
handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags);
|
handleSkinnedTriShape(nifNode, node, composite, boundTextures, animflags);
|
||||||
|
|
||||||
if (!nifNode->controller.empty())
|
if (!nifNode->controller.empty())
|
||||||
handleMeshControllers(nifNode, node, composite, boundTextures, animflags);
|
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).
|
// 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.
|
// 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<osg::MatrixTransform*>(node.get()), animflags);
|
handleNodeControllers(nifNode, static_cast<osg::MatrixTransform*>(node.get()), animflags);
|
||||||
|
|
||||||
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(nifNode);
|
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(nifNode);
|
||||||
|
@ -766,8 +777,8 @@ namespace NifOsg
|
||||||
|
|
||||||
// inherit wrap settings from the target slot
|
// inherit wrap settings from the target slot
|
||||||
osg::Texture2D* inherit = dynamic_cast<osg::Texture2D*>(stateset->getTextureAttribute(flipctrl->mTexSlot, osg::StateAttribute::TEXTURE));
|
osg::Texture2D* inherit = dynamic_cast<osg::Texture2D*>(stateset->getTextureAttribute(flipctrl->mTexSlot, osg::StateAttribute::TEXTURE));
|
||||||
osg::Texture2D::WrapMode wrapS = osg::Texture2D::CLAMP;
|
osg::Texture2D::WrapMode wrapS = osg::Texture2D::CLAMP_TO_EDGE;
|
||||||
osg::Texture2D::WrapMode wrapT = osg::Texture2D::CLAMP;
|
osg::Texture2D::WrapMode wrapT = osg::Texture2D::CLAMP_TO_EDGE;
|
||||||
if (inherit)
|
if (inherit)
|
||||||
{
|
{
|
||||||
wrapS = inherit->getWrap(osg::Texture2D::WRAP_S);
|
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<int>& boundTextures, int animflags)
|
void triCommonToGeometry(osg::Geometry *geometry, const std::vector<osg::Vec3f>& vertices, const std::vector<osg::Vec3f>& normals, const std::vector<std::vector<osg::Vec2f>>& uvlist, const std::vector<osg::Vec4f>& colors, const std::vector<int>& boundTextures, const std::string& name)
|
||||||
{
|
{
|
||||||
const Nif::NiTriShapeData* data = triShape->data.getPtr();
|
if (!vertices.empty())
|
||||||
|
geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data()));
|
||||||
{
|
if (!normals.empty())
|
||||||
geometry->setVertexArray(new osg::Vec3Array(data->vertices.size(), &data->vertices[0]));
|
geometry->setNormalArray(new osg::Vec3Array(normals.size(), normals.data()), osg::Array::BIND_PER_VERTEX);
|
||||||
if (!data->normals.empty())
|
if (!colors.empty())
|
||||||
geometry->setNormalArray(new osg::Vec3Array(data->normals.size(), &data->normals[0]), osg::Array::BIND_PER_VERTEX);
|
geometry->setColorArray(new osg::Vec4Array(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX);
|
||||||
}
|
|
||||||
|
|
||||||
int textureStage = 0;
|
int textureStage = 0;
|
||||||
for (std::vector<int>::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it,++textureStage)
|
for (const int uvSet : boundTextures)
|
||||||
{
|
{
|
||||||
int uvSet = *it;
|
if (uvSet >= (int)uvlist.size())
|
||||||
if (uvSet >= (int)data->uvlist.size())
|
|
||||||
{
|
{
|
||||||
Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename;
|
Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on shape \"" << name << "\" in " << mFilename;
|
||||||
if (!data->uvlist.empty())
|
if (!uvlist.empty())
|
||||||
geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[0].size(), &data->uvlist[0][0]), osg::Array::BIND_PER_VERTEX);
|
geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[0].size(), uvlist[0].data()), osg::Array::BIND_PER_VERTEX);
|
||||||
continue;
|
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())
|
void triShapeToGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
|
||||||
geometry->setColorArray(new osg::Vec4Array(data->colors.size(), &data->colors[0]), osg::Array::BIND_PER_VERTEX);
|
{
|
||||||
|
bool vertexColorsPresent = false;
|
||||||
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES,
|
if (nifNode->recType == Nif::RC_NiTriShape)
|
||||||
data->triangles.size(),
|
{
|
||||||
(unsigned short*)&data->triangles[0]));
|
const Nif::NiTriShape* triShape = static_cast<const Nif::NiTriShape*>(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<const Nif::NiTriStrips*>(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<unsigned short>& 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:
|
// osg::Material properties are handled here for two reasons:
|
||||||
// - if there are no vertex colors, we need to disable colorMode.
|
// - 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
|
// - there are 3 "overlapping" nif properties that all affect the osg::Material, handling them
|
||||||
// above the actual renderable would be tedious.
|
// above the actual renderable would be tedious.
|
||||||
std::vector<const Nif::Property*> drawableProps;
|
std::vector<const Nif::Property*> drawableProps;
|
||||||
collectDrawableProperties(triShape, drawableProps);
|
collectDrawableProperties(nifNode, drawableProps);
|
||||||
applyDrawableProperties(parentNode, drawableProps, composite, !data->colors.empty(), animflags, false);
|
applyDrawableProperties(parentNode, drawableProps, composite, vertexColorsPresent, animflags, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
|
void handleTriShape(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
|
||||||
{
|
{
|
||||||
|
assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips);
|
||||||
osg::ref_ptr<osg::Drawable> drawable;
|
osg::ref_ptr<osg::Drawable> drawable;
|
||||||
for (Nif::ControllerPtr ctrl = triShape->controller; !ctrl.empty(); ctrl = ctrl->next)
|
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry);
|
||||||
|
triShapeToGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags);
|
||||||
|
Nif::ControllerPtr ctrl;
|
||||||
|
if (nifNode->recType == Nif::RC_NiTriShape)
|
||||||
|
ctrl = static_cast<const Nif::NiTriShape*>(nifNode)->controller;
|
||||||
|
else
|
||||||
|
ctrl = static_cast<const Nif::NiTriStrips*>(nifNode)->controller;
|
||||||
|
for (; !ctrl.empty(); ctrl = ctrl->next)
|
||||||
{
|
{
|
||||||
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
|
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
|
||||||
continue;
|
continue;
|
||||||
if(ctrl->recType == Nif::RC_NiGeomMorpherController)
|
if(ctrl->recType == Nif::RC_NiGeomMorpherController)
|
||||||
{
|
{
|
||||||
drawable = handleMorphGeometry(static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr()), triShape, parentNode, composite, boundTextures, animflags);
|
drawable = handleMorphGeometry(static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr()), geom, parentNode, composite, boundTextures, animflags);
|
||||||
|
|
||||||
osg::ref_ptr<GeomMorpherController> morphctrl = new GeomMorpherController(
|
osg::ref_ptr<GeomMorpherController> morphctrl = new GeomMorpherController(
|
||||||
static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr())->data.getPtr());
|
static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr())->data.getPtr());
|
||||||
|
@ -1089,25 +1133,15 @@ namespace NifOsg
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!drawable.get())
|
if (!drawable.get())
|
||||||
{
|
|
||||||
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry);
|
|
||||||
drawable = geom;
|
drawable = geom;
|
||||||
triShapeToGeometry(triShape, geom, parentNode, composite, boundTextures, animflags);
|
drawable->setName(nifNode->name);
|
||||||
}
|
|
||||||
|
|
||||||
drawable->setName(triShape->name);
|
|
||||||
|
|
||||||
parentNode->addChild(drawable);
|
parentNode->addChild(drawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
osg::ref_ptr<osg::Drawable> handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, const Nif::NiTriShape *triShape, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
|
osg::ref_ptr<osg::Drawable> handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, osg::ref_ptr<osg::Geometry> sourceGeometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
|
||||||
{
|
{
|
||||||
osg::ref_ptr<SceneUtil::MorphGeometry> morphGeom = new SceneUtil::MorphGeometry;
|
osg::ref_ptr<SceneUtil::MorphGeometry> morphGeom = new SceneUtil::MorphGeometry;
|
||||||
|
|
||||||
osg::ref_ptr<osg::Geometry> sourceGeometry (new osg::Geometry);
|
|
||||||
triShapeToGeometry(triShape, sourceGeometry, parentNode, composite, boundTextures, animflags);
|
|
||||||
morphGeom->setSourceGeometry(sourceGeometry);
|
morphGeom->setSourceGeometry(sourceGeometry);
|
||||||
|
|
||||||
const std::vector<Nif::NiMorphData::MorphData>& morphs = morpher->data.getPtr()->mMorphs;
|
const std::vector<Nif::NiMorphData::MorphData>& morphs = morpher->data.getPtr()->mMorphs;
|
||||||
|
@ -1115,26 +1149,30 @@ namespace NifOsg
|
||||||
return morphGeom;
|
return morphGeom;
|
||||||
// Note we are not interested in morph 0, which just contains the original vertices
|
// Note we are not interested in morph 0, which just contains the original vertices
|
||||||
for (unsigned int i = 1; i < morphs.size(); ++i)
|
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;
|
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<int>& boundTextures, int animflags)
|
const std::vector<int>& boundTextures, int animflags)
|
||||||
{
|
{
|
||||||
|
assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips);
|
||||||
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
||||||
triShapeToGeometry(triShape, geometry, parentNode, composite, boundTextures, animflags);
|
triShapeToGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags);
|
||||||
|
|
||||||
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);
|
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);
|
||||||
rig->setSourceGeometry(geometry);
|
rig->setSourceGeometry(geometry);
|
||||||
rig->setName(triShape->name);
|
rig->setName(nifNode->name);
|
||||||
|
|
||||||
const Nif::NiSkinInstance *skin = triShape->skin.getPtr();
|
|
||||||
|
|
||||||
// Assign bone weights
|
// Assign bone weights
|
||||||
osg::ref_ptr<SceneUtil::RigGeometry::InfluenceMap> map (new SceneUtil::RigGeometry::InfluenceMap);
|
osg::ref_ptr<SceneUtil::RigGeometry::InfluenceMap> map (new SceneUtil::RigGeometry::InfluenceMap);
|
||||||
|
|
||||||
|
Nif::NiSkinInstancePtr skinPtr;
|
||||||
|
if (nifNode->recType == Nif::RC_NiTriShape)
|
||||||
|
skinPtr = static_cast<const Nif::NiTriShape*>(nifNode)->skin;
|
||||||
|
else
|
||||||
|
skinPtr = static_cast<const Nif::NiTriStrips*>(nifNode)->skin;
|
||||||
|
const Nif::NiSkinInstance *skin = skinPtr.getPtr();
|
||||||
const Nif::NiSkinData *data = skin->data.getPtr();
|
const Nif::NiSkinData *data = skin->data.getPtr();
|
||||||
const Nif::NodeList &bones = skin->bones;
|
const Nif::NodeList &bones = skin->bones;
|
||||||
for(size_t i = 0;i < bones.length();i++)
|
for(size_t i = 0;i < bones.length();i++)
|
||||||
|
@ -1238,9 +1276,11 @@ namespace NifOsg
|
||||||
switch (pixelData->fmt)
|
switch (pixelData->fmt)
|
||||||
{
|
{
|
||||||
case Nif::NiPixelData::NIPXFMT_RGB8:
|
case Nif::NiPixelData::NIPXFMT_RGB8:
|
||||||
|
case Nif::NiPixelData::NIPXFMT_PAL8:
|
||||||
pixelformat = GL_RGB;
|
pixelformat = GL_RGB;
|
||||||
break;
|
break;
|
||||||
case Nif::NiPixelData::NIPXFMT_RGBA8:
|
case Nif::NiPixelData::NIPXFMT_RGBA8:
|
||||||
|
case Nif::NiPixelData::NIPXFMT_PALA8:
|
||||||
pixelformat = GL_RGBA;
|
pixelformat = GL_RGBA;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -1255,7 +1295,7 @@ namespace NifOsg
|
||||||
int height = 0;
|
int height = 0;
|
||||||
|
|
||||||
std::vector<unsigned int> mipmapVector;
|
std::vector<unsigned int> mipmapVector;
|
||||||
for (unsigned int i=0; i<pixelData->mipmaps.size()-3; ++i)
|
for (unsigned int i=0; i<pixelData->mipmaps.size(); ++i)
|
||||||
{
|
{
|
||||||
const Nif::NiPixelData::Mipmap& mip = pixelData->mipmaps[i];
|
const Nif::NiPixelData::Mipmap& mip = pixelData->mipmaps[i];
|
||||||
|
|
||||||
|
@ -1281,10 +1321,59 @@ namespace NifOsg
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char* data = new unsigned char[pixelData->data.size()];
|
const std::vector<unsigned char>& pixels = pixelData->data;
|
||||||
memcpy(data, &pixelData->data[0], pixelData->data.size());
|
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<unsigned int>& 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->setMipmapLevels(mipmapVector);
|
||||||
image->flipVertical();
|
image->flipVertical();
|
||||||
|
|
||||||
|
@ -1354,8 +1443,8 @@ namespace NifOsg
|
||||||
int wrapT = (clamp) & 0x1;
|
int wrapT = (clamp) & 0x1;
|
||||||
int wrapS = (clamp >> 1) & 0x1;
|
int wrapS = (clamp >> 1) & 0x1;
|
||||||
|
|
||||||
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? 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);
|
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||||
|
|
||||||
int texUnit = boundTextures.size();
|
int texUnit = boundTextures.size();
|
||||||
|
|
||||||
|
|
|
@ -8,48 +8,16 @@
|
||||||
|
|
||||||
#include <components/misc/rng.hpp>
|
#include <components/misc/rng.hpp>
|
||||||
|
|
||||||
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
|
namespace SceneUtil
|
||||||
{
|
{
|
||||||
|
|
||||||
LightController::LightController()
|
LightController::LightController()
|
||||||
: mType(LT_Normal)
|
: mType(LT_Normal)
|
||||||
, mPhase((Misc::Rng::rollClosedProbability() * 2.f - 1.f) * 500.f)
|
, mPhase(0.25f + Misc::Rng::rollClosedProbability() * 0.75f)
|
||||||
, mDeltaCount(0.f)
|
, mBrightness(0.675f)
|
||||||
, mDirection(1.f)
|
, mStartTime(0.0)
|
||||||
, mLastTime(0.0)
|
, mLastTime(0.0)
|
||||||
|
, mTicksToAdvance(0.f)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,66 +29,40 @@ namespace SceneUtil
|
||||||
void LightController::operator ()(osg::Node* node, osg::NodeVisitor* nv)
|
void LightController::operator ()(osg::Node* node, osg::NodeVisitor* nv)
|
||||||
{
|
{
|
||||||
double time = nv->getFrameStamp()->getSimulationTime();
|
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
|
// disabled early out, light state needs to be set every frame regardless of change, due to the double buffering
|
||||||
//if (time == mLastTime)
|
//if (time == mLastTime)
|
||||||
// return;
|
// return;
|
||||||
|
|
||||||
float dt = static_cast<float>(time - mLastTime);
|
if (mType == LT_Normal)
|
||||||
mLastTime = time;
|
|
||||||
|
|
||||||
float brightness = 1.0f;
|
|
||||||
float cycle_time;
|
|
||||||
float time_distortion;
|
|
||||||
|
|
||||||
if(mType == LT_Pulse || mType == LT_PulseSlow)
|
|
||||||
{
|
{
|
||||||
cycle_time = 2.0f * osg::PI;
|
static_cast<SceneUtil::LightSource*>(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor);
|
||||||
time_distortion = 3.0f;
|
traverse(node, nv);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updating flickering at 15 FPS like vanilla.
|
||||||
|
constexpr float updateRate = 15.f;
|
||||||
|
mTicksToAdvance = static_cast<float>(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
|
else
|
||||||
{
|
mBrightness += mTicksToAdvance * speed;
|
||||||
static const float fa = osg::PI / 4.0f;
|
|
||||||
static const float phase_wavelength = 120.0f * osg::PI / fa;
|
|
||||||
|
|
||||||
cycle_time = 500.0f;
|
if (std::abs(mBrightness - mPhase) < speed)
|
||||||
mPhase = std::fmod(mPhase + dt, phase_wavelength);
|
{
|
||||||
time_distortion = flickerFrequency(mPhase);
|
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;
|
static_cast<SceneUtil::LightSource*>(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness);
|
||||||
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<SceneUtil::LightSource*>(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * brightness);
|
|
||||||
|
|
||||||
traverse(node, nv);
|
traverse(node, nv);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,9 +32,10 @@ namespace SceneUtil
|
||||||
LightType mType;
|
LightType mType;
|
||||||
osg::Vec4f mDiffuseColor;
|
osg::Vec4f mDiffuseColor;
|
||||||
float mPhase;
|
float mPhase;
|
||||||
float mDeltaCount;
|
float mBrightness;
|
||||||
int mDirection;
|
double mStartTime;
|
||||||
double mLastTime;
|
double mLastTime;
|
||||||
|
float mTicksToAdvance;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,29 +22,35 @@ namespace SceneUtil
|
||||||
float linearAttenuation = 0.f;
|
float linearAttenuation = 0.f;
|
||||||
float constantAttenuation = 0.f;
|
float constantAttenuation = 0.f;
|
||||||
|
|
||||||
const bool useConstant = Fallback::Map::getBool("LightAttenuation_UseConstant");
|
static const bool useConstant = Fallback::Map::getBool("LightAttenuation_UseConstant");
|
||||||
if (useConstant)
|
static const bool useLinear = Fallback::Map::getBool("LightAttenuation_UseLinear");
|
||||||
{
|
static const bool useQuadratic = Fallback::Map::getBool("LightAttenuation_UseQuadratic");
|
||||||
constantAttenuation = Fallback::Map::getFloat("LightAttenuation_ConstantValue");
|
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)
|
if (useLinear)
|
||||||
{
|
{
|
||||||
const float linearValue = Fallback::Map::getFloat("LightAttenuation_LinearValue");
|
linearAttenuation = linearMethod == 0 ? linearValue : 0.01f;
|
||||||
const float linearRadiusMult = Fallback::Map::getFloat("LightAttenuation_LinearRadiusMult");
|
|
||||||
float r = radius * linearRadiusMult;
|
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))
|
if (useQuadratic && (!outQuadInLin || isExterior))
|
||||||
{
|
{
|
||||||
const float quadraticValue = Fallback::Map::getFloat("LightAttenuation_QuadraticValue");
|
quadraticAttenuation = quadraticMethod == 0 ? quadraticValue : 0.01f;
|
||||||
const float quadraticRadiusMult = Fallback::Map::getFloat("LightAttenuation_QuadraticRadiusMult");
|
|
||||||
float r = radius * quadraticRadiusMult;
|
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);
|
light->setConstantAttenuation(constantAttenuation);
|
||||||
|
|
|
@ -1,10 +1,151 @@
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
#include <osg/Node>
|
#include <osg/Node>
|
||||||
|
#include <osg/NodeVisitor>
|
||||||
|
#include <osg/TexGen>
|
||||||
|
#include <osg/TexEnvCombine>
|
||||||
|
|
||||||
|
#include <components/resource/imagemanager.hpp>
|
||||||
|
#include <components/resource/scenemanager.hpp>
|
||||||
|
|
||||||
namespace SceneUtil
|
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<osg::ref_ptr<osg::Texture2D> >& 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)
|
void transformBoundingSphere (const osg::Matrixf& matrix, osg::BoundingSphere& bsphere)
|
||||||
{
|
{
|
||||||
osg::BoundingSphere::vec_type xdash = bsphere._center;
|
osg::BoundingSphere::vec_type xdash = bsphere._center;
|
||||||
|
@ -73,4 +214,48 @@ bool hasUserDescription(const osg::Node* node, const std::string pattern)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<GlowUpdater> addEnchantedGlow(osg::ref_ptr<osg::Node> node, Resource::ResourceSystem* resourceSystem, osg::Vec4f glowColor, float glowDuration)
|
||||||
|
{
|
||||||
|
std::vector<osg::ref_ptr<osg::Texture2D> > 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<osg::Image> image = resourceSystem->getImageManager()->getImage(stream.str());
|
||||||
|
osg::ref_ptr<osg::Texture2D> 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> 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<osg::StateSet> 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,48 @@
|
||||||
|
|
||||||
#include <osg/Matrix>
|
#include <osg/Matrix>
|
||||||
#include <osg/BoundingSphere>
|
#include <osg/BoundingSphere>
|
||||||
|
#include <osg/NodeCallback>
|
||||||
|
#include <osg/Texture2D>
|
||||||
#include <osg/Vec4f>
|
#include <osg/Vec4f>
|
||||||
|
|
||||||
|
#include <components/resource/resourcesystem.hpp>
|
||||||
|
|
||||||
|
#include "statesetupdater.hpp"
|
||||||
|
|
||||||
namespace SceneUtil
|
namespace SceneUtil
|
||||||
{
|
{
|
||||||
|
class GlowUpdater : public SceneUtil::StateSetUpdater
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector<osg::ref_ptr<osg::Texture2D> >& 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<osg::ref_ptr<osg::Texture2D> > 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
|
// Transform a bounding sphere by a matrix
|
||||||
// based off private code in osg::Transform
|
// based off private code in osg::Transform
|
||||||
|
@ -20,6 +58,8 @@ namespace SceneUtil
|
||||||
float makeOsgColorComponent (unsigned int value, unsigned int shift);
|
float makeOsgColorComponent (unsigned int value, unsigned int shift);
|
||||||
|
|
||||||
bool hasUserDescription(const osg::Node* node, const std::string pattern);
|
bool hasUserDescription(const osg::Node* node, const std::string pattern);
|
||||||
|
|
||||||
|
osg::ref_ptr<GlowUpdater> addEnchantedGlow(osg::ref_ptr<osg::Node> node, Resource::ResourceSystem* resourceSystem, osg::Vec4f glowColor, float glowDuration=-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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`_.
|
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
|
.. _`Graphic Herbalism`: https://www.nexusmods.com/morrowind/mods/46599
|
||||||
.. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232
|
.. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232
|
||||||
.. _`Glow in the Dahrk`: https://www.nexusmods.com/morrowind/mods/45886
|
.. _`Glow in the Dahrk`: https://www.nexusmods.com/morrowind/mods/45886
|
||||||
.. _`Almalexia's Cast for Beasts`: https://www.nexusmods.com/morrowind/mods/45853
|
.. _`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
|
||||||
|
|
13
extern/recastnavigation/.gitignore
vendored
13
extern/recastnavigation/.gitignore
vendored
|
@ -9,16 +9,6 @@
|
||||||
*.so
|
*.so
|
||||||
*.idb
|
*.idb
|
||||||
|
|
||||||
## Linux exes have no extension
|
|
||||||
RecastDemo/Bin/RecastDemo
|
|
||||||
RecastDemo/Bin/Tests
|
|
||||||
|
|
||||||
# Build directory
|
|
||||||
RecastDemo/Build
|
|
||||||
|
|
||||||
# Ignore meshes
|
|
||||||
RecastDemo/Bin/Meshes/*
|
|
||||||
|
|
||||||
## Logs and databases #
|
## Logs and databases #
|
||||||
*.log
|
*.log
|
||||||
*.sql
|
*.sql
|
||||||
|
@ -38,9 +28,6 @@ Thumbs.db
|
||||||
## xcode specific
|
## xcode specific
|
||||||
*xcuserdata*
|
*xcuserdata*
|
||||||
|
|
||||||
## SDL contrib
|
|
||||||
RecastDemo/Contrib/SDL/*
|
|
||||||
|
|
||||||
## Generated doc files
|
## Generated doc files
|
||||||
Docs/html
|
Docs/html
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue