1
0
Fork 1
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:
David Cernat 2019-08-22 22:44:00 +03:00
commit 353e7d530a
174 changed files with 2170 additions and 36488 deletions

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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();

View file

@ -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;

View file

@ -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())

View file

@ -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;

View file

@ -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,"");
} }
} }

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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}");

View file

@ -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;

View file

@ -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() != "")

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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, "");
} }

View file

@ -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_);
} }

View file

@ -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()

View file

@ -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

View file

@ -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;
} }

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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);

View file

@ -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()

View file

@ -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 */

View file

@ -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);

View file

@ -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);

View file

@ -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)
{ {

View file

@ -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];

View file

@ -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} << ")";
}
}
} }

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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?

View file

@ -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))
{ {

View file

@ -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);
} }

View 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;
}
}

View 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

View file

@ -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)

View file

@ -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())

View file

@ -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);

View file

@ -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)
{ {

View file

@ -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);

View file

@ -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();

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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())

View file

@ -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.

View file

@ -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

View file

@ -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));
} }
}; };

View file

@ -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)

View file

@ -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);
}
} }
} }
}; };

View file

@ -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);
} }

View file

@ -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;
}
} }

View file

@ -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;
}; };
} }

View file

@ -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();
} }

View file

@ -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(...)
{ {

View file

@ -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

View file

@ -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);
}
} }
/* /*

View file

@ -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;

View file

@ -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

View file

@ -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;
} }
} }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;
} }

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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);
} }
} }

View file

@ -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;

View file

@ -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();

View file

@ -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);
} }

View file

@ -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;
}; };
} }

View file

@ -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);

View file

@ -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;
}
} }

View file

@ -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

View file

@ -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

View file

@ -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