mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 19:19:56 +00:00
Add OpenMW commits up to 18 Aug 2019
# Conflicts: # CMakeLists.txt # apps/openmw/mwmechanics/character.cpp # apps/openmw/mwmechanics/spellcasting.cpp # apps/openmw/mwworld/worldimp.hpp
This commit is contained in:
commit
353e7d530a
174 changed files with 2170 additions and 36488 deletions
|
@ -22,6 +22,7 @@ Programmers
|
|||
alexanderkjall
|
||||
Alexander Nadeau (wareya)
|
||||
Alexander Olofsson (Ace)
|
||||
Alex Rice
|
||||
Alex S (docwest)
|
||||
Allofich
|
||||
Andrei Kortunov (akortunov)
|
||||
|
|
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -15,6 +15,7 @@
|
|||
Bug #3765: DisableTeleporting makes Mark/Recall/Intervention effects undetectable
|
||||
Bug #3778: [Mod] Improved Thrown Weapon Projectiles - weapons have wrong transformation during throw animation
|
||||
Bug #3812: Wrong multiline tooltips width when word-wrapping is enabled
|
||||
Bug #3894: Hostile spell effects not detected/present on first frame of OnPCHitMe
|
||||
Bug #4202: Open .omwaddon files without needing toopen openmw-cs first
|
||||
Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect
|
||||
Bug #4276: Resizing character window differs from vanilla
|
||||
|
@ -84,6 +85,7 @@
|
|||
Bug #4945: Poor random magic magnitude distribution
|
||||
Bug #4947: Player character doesn't use lip animation
|
||||
Bug #4948: Footstep sounds while levitating on ground level
|
||||
Bug #4952: Torches held by NPCs flicker too quickly
|
||||
Bug #4961: Flying creature combat engagement takes z-axis into account
|
||||
Bug #4963: Enchant skill progress is incorrect
|
||||
Bug #4964: Multiple effect spell projectile sounds play louder than vanilla
|
||||
|
@ -96,6 +98,7 @@
|
|||
Bug #4984: "Friendly hits" feature should be used only for player's followers
|
||||
Bug #4989: Object dimension-dependent VFX scaling behavior is inconsistent
|
||||
Bug #4990: Dead bodies prevent you from hitting
|
||||
Bug #4991: Jumping occasionally takes too much fatigue
|
||||
Bug #4999: Drop instruction behaves differently from vanilla
|
||||
Bug #5001: Possible data race in the Animation::setAlpha()
|
||||
Bug #5004: Werewolves shield their eyes during storm
|
||||
|
@ -105,18 +108,32 @@
|
|||
Bug #5038: Enchanting success chance calculations are blatantly wrong
|
||||
Bug #5047: # in cell names sets color
|
||||
Bug #5050: Invalid spell effects are not handled gracefully
|
||||
Bug #5055: Mark, Recall, Intervention magic effect abilities have no effect when added and removed in the same frame
|
||||
Bug #5056: Calling Cast function on player doesn't equip the spell but casts it
|
||||
Bug #5060: Magic effect visuals stop when death animation begins instead of when it ends
|
||||
Bug #5063: Shape named "Tri Shadow" in creature mesh is visible if it isn't hidden
|
||||
Bug #5067: Ranged attacks on unaware opponents ("critical hits") differ from the vanilla engine
|
||||
Bug #5069: Blocking creatures' attacks doesn't degrade shields
|
||||
Bug #5074: Paralyzed actors greet the player
|
||||
Bug #5075: Enchanting cast style can be changed if there's no object
|
||||
Bug #5078: DisablePlayerLooking is broken
|
||||
Bug #5082: Scrolling with controller in GUI mode is broken
|
||||
Bug #5089: Swimming/Underwater creatures only swim around ground level
|
||||
Bug #5092: NPCs with enchanted weapons play sound when out of charges
|
||||
Bug #5093: Hand to hand sound plays on knocked out enemies
|
||||
Bug #5099: Non-swimming enemies will enter water if player is water walking
|
||||
Bug #5103: Sneaking state behavior is still inconsistent
|
||||
Bug #5104: Black Dart's enchantment doesn't trigger at low Enchant levels
|
||||
Bug #5105: NPCs start combat with werewolves from any distance
|
||||
Bug #5106: Still can jump even when encumbered
|
||||
Bug #5110: ModRegion with a redundant numerical argument breaks script execution
|
||||
Bug #5112: Insufficient magicka for current spell not reflected on HUD icon
|
||||
Bug #5123: Script won't run on respawn
|
||||
Bug #5124: Arrow remains attached to actor if pulling animation was cancelled
|
||||
Bug #5126: Swimming creatures without RunForward animations are motionless during combat
|
||||
Bug #5134: Doors rotation by "Lock" console command is inconsistent
|
||||
Bug #5126: Swimming creatures without RunForward animations are motionless during combat
|
||||
Bug #5137: Textures with Clamp Mode set to Clamp instead of Wrap are too dark outside the boundaries
|
||||
Feature #1774: Handle AvoidNode
|
||||
Feature #2229: Improve pathfinding AI
|
||||
Feature #3025: Analogue gamepad movement controls
|
||||
|
@ -137,6 +154,7 @@
|
|||
Feature #4812: Support NiSwitchNode
|
||||
Feature #4836: Daytime node switch
|
||||
Feature #4859: Make water reflections more configurable
|
||||
Feature #4882: Support for NiPalette node
|
||||
Feature #4887: Add openmw command option to set initial random seed
|
||||
Feature #4890: Make Distant Terrain configurable
|
||||
Feature #4958: Support eight blood types
|
||||
|
@ -152,7 +170,13 @@
|
|||
Feature #5036: Allow scripted faction leaving
|
||||
Feature #5046: Gamepad thumbstick cursor speed
|
||||
Feature #5051: Provide a separate textures for scrollbars
|
||||
Feature #5091: Human-readable light source duration
|
||||
Feature #5094: Unix like console hotkeys
|
||||
Feature #5098: Allow user controller bindings
|
||||
Feature #5121: Handle NiTriStrips and NiTriStripsData
|
||||
Feature #5122: Use magic glow for enchanted arrows
|
||||
Feature #5131: Custom skeleton bones
|
||||
Feature #5132: Unique animations for different weapon types
|
||||
Task #4686: Upgrade media decoder to a more current FFmpeg API
|
||||
Task #4695: Optimize Distant Terrain memory consumption
|
||||
Task #4789: Optimize cell transitions
|
||||
|
|
|
@ -647,9 +647,7 @@ endif(WIN32)
|
|||
|
||||
# Extern
|
||||
IF(BUILD_OPENMW OR BUILD_OPENCS)
|
||||
set(RECASTNAVIGATION_DEMO OFF CACHE BOOL "Do not build RecastDemo")
|
||||
set(RECASTNAVIGATION_STATIC ON CACHE BOOL "Build recastnavigation static libraries")
|
||||
set(RECASTNAVIGATION_TESTS OFF CACHE BOOL "Do not build recastnavigation tests")
|
||||
|
||||
add_subdirectory (extern/recastnavigation EXCLUDE_FROM_ALL)
|
||||
add_subdirectory (extern/osg-ffmpeg-videoplayer)
|
||||
|
|
|
@ -83,7 +83,7 @@ add_openmw_dir (mwmechanics
|
|||
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
|
||||
aicast aiescort aiface aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting
|
||||
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
|
||||
character actors objects aistate coordinateconverter trading weaponpriority spellpriority
|
||||
character actors objects aistate coordinateconverter trading weaponpriority spellpriority weapontype
|
||||
)
|
||||
|
||||
add_openmw_dir (mwstate
|
||||
|
|
|
@ -636,8 +636,17 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
|||
controllerFileName = "gamecontrollerdb.txt";
|
||||
}
|
||||
|
||||
const std::string userdefault = mCfgMgr.getUserConfigPath().string() + "/" + controllerFileName;
|
||||
const std::string localdefault = mCfgMgr.getLocalPath().string() + "/" + controllerFileName;
|
||||
const std::string globaldefault = mCfgMgr.getGlobalPath().string() + "/" + controllerFileName;
|
||||
|
||||
std::string userGameControllerdb;
|
||||
if (boost::filesystem::exists(userdefault)){
|
||||
userGameControllerdb = userdefault;
|
||||
}
|
||||
else
|
||||
userGameControllerdb = "";
|
||||
|
||||
std::string gameControllerdb;
|
||||
if (boost::filesystem::exists(localdefault))
|
||||
gameControllerdb = localdefault;
|
||||
|
@ -646,7 +655,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
|||
else
|
||||
gameControllerdb = ""; //if it doesn't exist, pass in an empty string
|
||||
|
||||
MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, gameControllerdb, mGrab);
|
||||
MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab);
|
||||
mEnvironment.setInputManager (input);
|
||||
|
||||
std::string myguiResources = (mResDir / "mygui").string();
|
||||
|
|
|
@ -559,7 +559,6 @@ namespace MWBase
|
|||
virtual void togglePreviewMode(bool enable) = 0;
|
||||
virtual bool toggleVanityMode(bool enable) = 0;
|
||||
virtual void allowVanityMode(bool allow) = 0;
|
||||
virtual void togglePlayerLooking(bool enable) = 0;
|
||||
virtual void changeVanityModeScale(float factor) = 0;
|
||||
virtual bool vanityRotateCamera(float * rot) = 0;
|
||||
virtual void setCameraDistance(float dist, bool adjust = false, bool override = true)=0;
|
||||
|
|
|
@ -103,7 +103,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Activator> *ref = ptr.get<ESM::Activator>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count);
|
||||
|
||||
std::string text;
|
||||
if (MWBase::Environment::get().getWindowManager()->getFullHelp())
|
||||
|
|
|
@ -138,7 +138,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Apparatus> *ref = ptr.get<ESM::Apparatus>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count);
|
||||
info.icon = ref->mBase->mIcon;
|
||||
|
||||
std::string text;
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "../mwrender/objects.hpp"
|
||||
#include "../mwrender/renderinginterface.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
#include "../mwgui/tooltips.hpp"
|
||||
|
||||
|
@ -244,7 +245,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Armor> *ref = ptr.get<ESM::Armor>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count);
|
||||
info.icon = ref->mBase->mIcon;
|
||||
|
||||
std::string text;
|
||||
|
@ -378,21 +379,13 @@ namespace MWClass
|
|||
if(*slot == MWWorld::InventoryStore::Slot_CarriedLeft)
|
||||
{
|
||||
MWWorld::ConstContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
|
||||
if(weapon == invStore.end())
|
||||
return std::make_pair(1,"");
|
||||
|
||||
if(weapon->getTypeName() == typeid(ESM::Weapon).name() &&
|
||||
(weapon->get<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))
|
||||
if(weapon != invStore.end() && weapon->getTypeName() == typeid(ESM::Weapon).name())
|
||||
{
|
||||
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,"");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,7 +154,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Book> *ref = ptr.get<ESM::Book>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count);
|
||||
info.icon = ref->mBase->mIcon;
|
||||
|
||||
std::string text;
|
||||
|
|
|
@ -202,7 +202,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Clothing> *ref = ptr.get<ESM::Clothing>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count);
|
||||
info.icon = ref->mBase->mIcon;
|
||||
|
||||
std::string text;
|
||||
|
|
|
@ -335,7 +335,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Container> *ref = ptr.get<ESM::Container>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName;
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName);
|
||||
|
||||
std::string text;
|
||||
int lockLevel = ptr.getCellRef().getLockLevel();
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwphysics/physicssystem.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/localscripts.hpp"
|
||||
|
||||
#include "../mwrender/renderinginterface.hpp"
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
@ -735,7 +736,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Creature> *ref = ptr.get<ESM::Creature>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName;
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName);
|
||||
|
||||
std::string text;
|
||||
if (MWBase::Environment::get().getWindowManager()->getFullHelp())
|
||||
|
@ -994,7 +995,12 @@ namespace MWClass
|
|||
if (ptr.getCellRef().hasContentFile())
|
||||
{
|
||||
if (ptr.getRefData().getCount() == 0)
|
||||
{
|
||||
ptr.getRefData().setCount(1);
|
||||
const std::string& script = getScript(ptr);
|
||||
if(!script.empty())
|
||||
MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr);
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getWorld()->removeContainerScripts(ptr);
|
||||
ptr.getRefData().setCustomData(nullptr);
|
||||
|
|
|
@ -357,7 +357,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Door> *ref = ptr.get<ESM::Door>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName;
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName);
|
||||
|
||||
std::string text;
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Ingredient> *ref = ptr.get<ESM::Ingredient>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count);
|
||||
info.icon = ref->mBase->mIcon;
|
||||
|
||||
std::string text;
|
||||
|
|
|
@ -151,18 +151,14 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Light> *ref = ptr.get<ESM::Light>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count);
|
||||
info.icon = ref->mBase->mIcon;
|
||||
|
||||
std::string text;
|
||||
|
||||
if (Settings::Manager::getBool("show effect duration","Game"))
|
||||
{
|
||||
// -1 is infinite light source, so duration makes no sense here. Other negative values are treated as 0.
|
||||
float remainingTime = ptr.getClass().getRemainingUsageTime(ptr);
|
||||
if (remainingTime != -1.0f)
|
||||
text += "\n#{sDuration}: " + MWGui::ToolTips::toString(std::max(0.f, remainingTime));
|
||||
}
|
||||
// Don't show duration for infinite light sources.
|
||||
if (Settings::Manager::getBool("show effect duration","Game") && ptr.getClass().getRemainingUsageTime(ptr) != -1)
|
||||
text += MWGui::ToolTips::getDurationString(ptr.getClass().getRemainingUsageTime(ptr), "\n#{sDuration}");
|
||||
|
||||
text += MWGui::ToolTips::getWeightString(ref->mBase->mData.mWeight, "#{sWeight}");
|
||||
text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}");
|
||||
|
|
|
@ -116,7 +116,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Lockpick> *ref = ptr.get<ESM::Lockpick>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count);
|
||||
info.icon = ref->mBase->mIcon;
|
||||
|
||||
std::string text;
|
||||
|
|
|
@ -192,7 +192,7 @@ namespace MWClass
|
|||
else // gold displays its count also if it's 1.
|
||||
countString = " (" + std::to_string(count) + ")";
|
||||
|
||||
info.caption = ref->mBase->mName + countString;
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + countString;
|
||||
info.icon = ref->mBase->mIcon;
|
||||
|
||||
if (ref->mRef.getSoul() != "")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "npc.hpp"
|
||||
#include "npc.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
@ -41,7 +41,7 @@
|
|||
#include "../mwmechanics/combat.hpp"
|
||||
#include "../mwmechanics/autocalcspell.hpp"
|
||||
#include "../mwmechanics/difficultyscaling.hpp"
|
||||
#include "../mwmechanics/character.hpp"
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
@ -52,6 +52,7 @@
|
|||
#include "../mwworld/customdata.hpp"
|
||||
#include "../mwphysics/physicssystem.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/localscripts.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
#include "../mwrender/renderinginterface.hpp"
|
||||
|
@ -1124,10 +1125,9 @@ namespace MWClass
|
|||
const float normalizedEncumbrance = getNormalizedEncumbrance(ptr);
|
||||
|
||||
bool swimming = world->isSwimming(ptr);
|
||||
bool 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 sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr);
|
||||
bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run);
|
||||
bool inair = !world->isOnGround(ptr) && !swimming && !world->isFlying(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()*
|
||||
|
@ -1255,11 +1255,11 @@ namespace MWClass
|
|||
bool fullHelp = MWBase::Environment::get().getWindowManager()->getFullHelp();
|
||||
MWGui::ToolTipInfo info;
|
||||
|
||||
info.caption = getName(ptr);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(getName(ptr));
|
||||
if(fullHelp && ptr.getRefData().getCustomData() && ptr.getRefData().getCustomData()->asNpcCustomData().mNpcStats.isWerewolf())
|
||||
{
|
||||
info.caption += " (";
|
||||
info.caption += ref->mBase->mName;
|
||||
info.caption += MyGUI::TextIterator::toTagsString(ref->mBase->mName);
|
||||
info.caption += ")";
|
||||
}
|
||||
|
||||
|
@ -1412,9 +1412,9 @@ namespace MWClass
|
|||
if (getNpcStats(ptr).isWerewolf()
|
||||
&& getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
|
||||
{
|
||||
MWMechanics::WeaponType weaponType = MWMechanics::WeapType_None;
|
||||
MWMechanics::getActiveWeapon(getCreatureStats(ptr), getInventoryStore(ptr), &weaponType);
|
||||
if (weaponType == MWMechanics::WeapType_None)
|
||||
int weaponType = ESM::Weapon::None;
|
||||
MWMechanics::getActiveWeapon(ptr, &weaponType);
|
||||
if (weaponType == ESM::Weapon::None)
|
||||
return std::string();
|
||||
}
|
||||
|
||||
|
@ -1561,7 +1561,12 @@ namespace MWClass
|
|||
if (ptr.getCellRef().hasContentFile())
|
||||
{
|
||||
if (ptr.getRefData().getCount() == 0)
|
||||
{
|
||||
ptr.getRefData().setCount(1);
|
||||
const std::string& script = getScript(ptr);
|
||||
if (!script.empty())
|
||||
MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr);
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getWorld()->removeContainerScripts(ptr);
|
||||
ptr.getRefData().setCustomData(nullptr);
|
||||
|
|
|
@ -143,7 +143,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Potion> *ref = ptr.get<ESM::Potion>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count);
|
||||
info.icon = ref->mBase->mIcon;
|
||||
|
||||
std::string text;
|
||||
|
|
|
@ -116,7 +116,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Probe> *ref = ptr.get<ESM::Probe>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count);
|
||||
info.icon = ref->mBase->mIcon;
|
||||
|
||||
std::string text;
|
||||
|
|
|
@ -150,7 +150,7 @@ namespace MWClass
|
|||
const MWWorld::LiveCellRef<ESM::Repair> *ref = ptr.get<ESM::Repair>();
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count);
|
||||
info.icon = ref->mBase->mIcon;
|
||||
|
||||
std::string text;
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
#include "../mwphysics/physicssystem.hpp"
|
||||
#include "../mwworld/nullaction.hpp"
|
||||
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
#include "../mwgui/tooltips.hpp"
|
||||
|
||||
#include "../mwrender/objects.hpp"
|
||||
|
@ -98,8 +100,9 @@ namespace MWClass
|
|||
bool Weapon::hasItemHealth (const MWWorld::ConstPtr& ptr) const
|
||||
{
|
||||
const MWWorld::LiveCellRef<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
|
||||
|
@ -120,16 +123,17 @@ namespace MWClass
|
|||
std::pair<std::vector<int>, bool> Weapon::getEquipmentSlots (const MWWorld::ConstPtr& ptr) const
|
||||
{
|
||||
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_;
|
||||
bool stack = false;
|
||||
|
||||
if (ref->mBase->mData.mType==ESM::Weapon::Arrow || ref->mBase->mData.mType==ESM::Weapon::Bolt)
|
||||
if (weapClass == ESM::WeaponType::Ammo)
|
||||
{
|
||||
slots_.push_back (int (MWWorld::InventoryStore::Slot_Ammunition));
|
||||
stack = true;
|
||||
}
|
||||
else if (ref->mBase->mData.mType==ESM::Weapon::MarksmanThrown)
|
||||
else if (weapClass == ESM::WeaponType::Thrown)
|
||||
{
|
||||
slots_.push_back (int (MWWorld::InventoryStore::Slot_CarriedRight));
|
||||
stack = true;
|
||||
|
@ -143,30 +147,9 @@ namespace MWClass
|
|||
int Weapon::getEquipmentSkill (const MWWorld::ConstPtr& ptr) const
|
||||
{
|
||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
||||
int type = ref->mBase->mData.mType;
|
||||
|
||||
const int size = 12;
|
||||
|
||||
static const int sMapping[size][2] =
|
||||
{
|
||||
{ ESM::Weapon::ShortBladeOneHand, ESM::Skill::ShortBlade },
|
||||
{ ESM::Weapon::LongBladeOneHand, ESM::Skill::LongBlade },
|
||||
{ ESM::Weapon::LongBladeTwoHand, ESM::Skill::LongBlade },
|
||||
{ ESM::Weapon::BluntOneHand, ESM::Skill::BluntWeapon },
|
||||
{ ESM::Weapon::BluntTwoClose, ESM::Skill::BluntWeapon },
|
||||
{ ESM::Weapon::BluntTwoWide, ESM::Skill::BluntWeapon },
|
||||
{ ESM::Weapon::SpearTwoWide, ESM::Skill::Spear },
|
||||
{ ESM::Weapon::AxeOneHand, ESM::Skill::Axe },
|
||||
{ ESM::Weapon::AxeTwoHand, ESM::Skill::Axe },
|
||||
{ ESM::Weapon::MarksmanBow, ESM::Skill::Marksman },
|
||||
{ ESM::Weapon::MarksmanCrossbow, ESM::Skill::Marksman },
|
||||
{ ESM::Weapon::MarksmanThrown, ESM::Skill::Marksman }
|
||||
};
|
||||
|
||||
for (int i=0; i<size; ++i)
|
||||
if (sMapping[i][0]==ref->mBase->mData.mType)
|
||||
return sMapping[i][1];
|
||||
|
||||
return -1;
|
||||
return MWMechanics::getWeaponType(type)->mSkill;
|
||||
}
|
||||
|
||||
int Weapon::getValue (const MWWorld::ConstPtr& ptr) const
|
||||
|
@ -186,89 +169,17 @@ namespace MWClass
|
|||
std::string Weapon::getUpSoundId (const MWWorld::ConstPtr& ptr) const
|
||||
{
|
||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
||||
|
||||
int type = ref->mBase->mData.mType;
|
||||
// Ammo
|
||||
if (type == 12 || type == 13)
|
||||
{
|
||||
return std::string("Item Ammo Up");
|
||||
}
|
||||
// Bow
|
||||
if (type == 9)
|
||||
{
|
||||
return std::string("Item Weapon Bow Up");
|
||||
}
|
||||
// Crossbow
|
||||
if (type == 10)
|
||||
{
|
||||
return std::string("Item Weapon Crossbow Up");
|
||||
}
|
||||
// Longblades, One hand and Two
|
||||
if (type == 1 || type == 2)
|
||||
{
|
||||
return std::string("Item Weapon Longblade Up");
|
||||
}
|
||||
// Shortblade
|
||||
if (type == 0)
|
||||
{
|
||||
return std::string("Item Weapon Shortblade Up");
|
||||
}
|
||||
// Spear
|
||||
if (type == 6)
|
||||
{
|
||||
return std::string("Item Weapon Spear Up");
|
||||
}
|
||||
// Blunts, Axes and Thrown weapons
|
||||
if (type == 3 || type == 4 || type == 5 || type == 7 || type == 8 || type == 11)
|
||||
{
|
||||
return std::string("Item Weapon Blunt Up");
|
||||
}
|
||||
|
||||
return std::string("Item Misc Up");
|
||||
std::string soundId = MWMechanics::getWeaponType(type)->mSoundId;
|
||||
return soundId + " Up";
|
||||
}
|
||||
|
||||
std::string Weapon::getDownSoundId (const MWWorld::ConstPtr& ptr) const
|
||||
{
|
||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
||||
|
||||
int type = ref->mBase->mData.mType;
|
||||
// Ammo
|
||||
if (type == 12 || type == 13)
|
||||
{
|
||||
return std::string("Item Ammo Down");
|
||||
}
|
||||
// Bow
|
||||
if (type == 9)
|
||||
{
|
||||
return std::string("Item Weapon Bow Down");
|
||||
}
|
||||
// Crossbow
|
||||
if (type == 10)
|
||||
{
|
||||
return std::string("Item Weapon Crossbow Down");
|
||||
}
|
||||
// Longblades, One hand and Two
|
||||
if (type == 1 || type == 2)
|
||||
{
|
||||
return std::string("Item Weapon Longblade Down");
|
||||
}
|
||||
// Shortblade
|
||||
if (type == 0)
|
||||
{
|
||||
return std::string("Item Weapon Shortblade Down");
|
||||
}
|
||||
// Spear
|
||||
if (type == 6)
|
||||
{
|
||||
return std::string("Item Weapon Spear Down");
|
||||
}
|
||||
// Blunts, Axes and Thrown weapons
|
||||
if (type == 3 || type == 4 || type == 5 || type == 7 || type == 8 || type == 11)
|
||||
{
|
||||
return std::string("Item Weapon Blunt Down");
|
||||
}
|
||||
|
||||
return std::string("Item Misc Down");
|
||||
std::string soundId = MWMechanics::getWeaponType(type)->mSoundId;
|
||||
return soundId + " Down";
|
||||
}
|
||||
|
||||
std::string Weapon::getInventoryIcon (const MWWorld::ConstPtr& ptr) const
|
||||
|
@ -288,9 +199,10 @@ namespace MWClass
|
|||
MWGui::ToolTipInfo Weapon::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
|
||||
{
|
||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = ptr.get<ESM::Weapon>();
|
||||
const ESM::WeaponType* weaponType = MWMechanics::getWeaponType(ref->mBase->mData.mType);
|
||||
|
||||
MWGui::ToolTipInfo info;
|
||||
info.caption = ref->mBase->mName + MWGui::ToolTips::getCountString(count);
|
||||
info.caption = MyGUI::TextIterator::toTagsString(ref->mBase->mName) + MWGui::ToolTips::getCountString(count);
|
||||
info.icon = ref->mBase->mIcon;
|
||||
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
|
@ -298,37 +210,26 @@ namespace MWClass
|
|||
std::string text;
|
||||
|
||||
// weapon type & damage
|
||||
if ((ref->mBase->mData.mType < ESM::Weapon::Arrow || Settings::Manager::getBool("show projectile damage", "Game")) && ref->mBase->mData.mType <= ESM::Weapon::Bolt)
|
||||
if (weaponType->mWeaponClass != ESM::WeaponType::Ammo || Settings::Manager::getBool("show projectile damage", "Game"))
|
||||
{
|
||||
text += "\n#{sType} ";
|
||||
|
||||
static std::map <int, std::pair <std::string, std::string> > mapping;
|
||||
if (mapping.empty())
|
||||
int skill = MWMechanics::getWeaponType(ref->mBase->mData.mType)->mSkill;
|
||||
const std::string type = ESM::Skill::sSkillNameIds[skill];
|
||||
std::string oneOrTwoHanded;
|
||||
if (weaponType->mWeaponClass == ESM::WeaponType::Melee)
|
||||
{
|
||||
mapping[ESM::Weapon::ShortBladeOneHand] = std::make_pair("sSkillShortblade", "sOneHanded");
|
||||
mapping[ESM::Weapon::LongBladeOneHand] = std::make_pair("sSkillLongblade", "sOneHanded");
|
||||
mapping[ESM::Weapon::LongBladeTwoHand] = std::make_pair("sSkillLongblade", "sTwoHanded");
|
||||
mapping[ESM::Weapon::BluntOneHand] = std::make_pair("sSkillBluntweapon", "sOneHanded");
|
||||
mapping[ESM::Weapon::BluntTwoClose] = std::make_pair("sSkillBluntweapon", "sTwoHanded");
|
||||
mapping[ESM::Weapon::BluntTwoWide] = std::make_pair("sSkillBluntweapon", "sTwoHanded");
|
||||
mapping[ESM::Weapon::SpearTwoWide] = std::make_pair("sSkillSpear", "sTwoHanded");
|
||||
mapping[ESM::Weapon::AxeOneHand] = std::make_pair("sSkillAxe", "sOneHanded");
|
||||
mapping[ESM::Weapon::AxeTwoHand] = std::make_pair("sSkillAxe", "sTwoHanded");
|
||||
mapping[ESM::Weapon::MarksmanBow] = std::make_pair("sSkillMarksman", "");
|
||||
mapping[ESM::Weapon::MarksmanCrossbow] = std::make_pair("sSkillMarksman", "");
|
||||
mapping[ESM::Weapon::MarksmanThrown] = std::make_pair("sSkillMarksman", "");
|
||||
mapping[ESM::Weapon::Arrow] = std::make_pair("sSkillMarksman", "");
|
||||
mapping[ESM::Weapon::Bolt] = std::make_pair("sSkillMarksman", "");
|
||||
if (weaponType->mFlags & ESM::WeaponType::TwoHanded)
|
||||
oneOrTwoHanded = "sTwoHanded";
|
||||
else
|
||||
oneOrTwoHanded = "sOneHanded";
|
||||
}
|
||||
|
||||
const std::string type = mapping[ref->mBase->mData.mType].first;
|
||||
const std::string oneOrTwoHanded = mapping[ref->mBase->mData.mType].second;
|
||||
|
||||
text += store.get<ESM::GameSetting>().find(type)->mValue.getString() +
|
||||
((oneOrTwoHanded != "") ? ", " + store.get<ESM::GameSetting>().find(oneOrTwoHanded)->mValue.getString() : "");
|
||||
|
||||
// weapon damage
|
||||
if (ref->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||
if (weaponType->mWeaponClass == ESM::WeaponType::Thrown)
|
||||
{
|
||||
// Thrown weapons have 2x real damage applied
|
||||
// as they're both the weapon and the ammo
|
||||
|
@ -336,14 +237,7 @@ namespace MWClass
|
|||
+ MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[0] * 2))
|
||||
+ " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[1] * 2));
|
||||
}
|
||||
else if (ref->mBase->mData.mType >= ESM::Weapon::MarksmanBow)
|
||||
{
|
||||
// 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
|
||||
else if (weaponType->mWeaponClass == ESM::WeaponType::Melee)
|
||||
{
|
||||
// Chop
|
||||
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[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))
|
||||
|
@ -369,7 +270,7 @@ namespace MWClass
|
|||
|
||||
const bool verbose = Settings::Manager::getBool("show melee info", "Game");
|
||||
// add reach for melee weapon
|
||||
if (ref->mBase->mData.mType < ESM::Weapon::MarksmanBow && verbose)
|
||||
if (weaponType->mWeaponClass == ESM::WeaponType::Melee && verbose)
|
||||
{
|
||||
// display value in feet
|
||||
const float combatDistance = store.get<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
|
||||
if (ref->mBase->mData.mType < ESM::Weapon::Arrow && verbose)
|
||||
if (weaponType->mWeaponClass != ESM::WeaponType::Ammo && verbose)
|
||||
{
|
||||
text += MWGui::ToolTips::getPercentString(ref->mBase->mData.mSpeed, "#{sAttributeSpeed}");
|
||||
}
|
||||
|
@ -449,13 +350,8 @@ namespace MWClass
|
|||
if (slots_.first.empty())
|
||||
return std::make_pair (0, "");
|
||||
|
||||
if(ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand ||
|
||||
ptr.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::BluntTwoClose ||
|
||||
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)
|
||||
int type = ptr.get<ESM::Weapon>()->mBase->mData.mType;
|
||||
if(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded)
|
||||
{
|
||||
return std::make_pair (2, "");
|
||||
}
|
||||
|
|
|
@ -112,10 +112,7 @@ struct TypesetBookImpl : TypesetBook
|
|||
if (i->empty())
|
||||
return Range (Utf8Point (nullptr), Utf8Point (nullptr));
|
||||
|
||||
Utf8Point begin = &i->front ();
|
||||
Utf8Point end = &i->front () + i->size ();
|
||||
|
||||
return Range (begin, end);
|
||||
return Range (i->data(), i->data() + i->size());
|
||||
}
|
||||
|
||||
size_t pageCount () const { return mPages.size (); }
|
||||
|
@ -346,8 +343,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter
|
|||
assert (end <= mCurrentContent->size ());
|
||||
assert (begin <= mCurrentContent->size ());
|
||||
|
||||
Utf8Point begin_ = &mCurrentContent->front () + begin;
|
||||
Utf8Point end_ = &mCurrentContent->front () + end ;
|
||||
Utf8Point begin_ = mCurrentContent->data() + begin;
|
||||
Utf8Point end_ = mCurrentContent->data() + end;
|
||||
|
||||
writeImpl (static_cast <StyleImpl*> (style), begin_, end_);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/fallback/fallback.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
|
@ -25,34 +26,52 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
struct Response
|
||||
{
|
||||
const std::string mText;
|
||||
const ESM::Class::Specialization mSpecialization;
|
||||
};
|
||||
|
||||
struct Step
|
||||
{
|
||||
const std::string mText;
|
||||
const std::string mButtons[3];
|
||||
const Response mResponses[3];
|
||||
const std::string mSound;
|
||||
};
|
||||
|
||||
const ESM::Class::Specialization mSpecializations[3]={ESM::Class::Combat, ESM::Class::Magic, ESM::Class::Stealth}; // The specialization for each answer
|
||||
Step sGenerateClassSteps(int number)
|
||||
{
|
||||
number++;
|
||||
Step step = {
|
||||
Fallback::Map::getString("Question_"+MyGUI::utility::toString(number)+"_Question"),
|
||||
{Fallback::Map::getString("Question_"+MyGUI::utility::toString(number)+"_AnswerOne"),
|
||||
Fallback::Map::getString("Question_"+MyGUI::utility::toString(number)+"_AnswerTwo"),
|
||||
Fallback::Map::getString("Question_"+MyGUI::utility::toString(number)+"_AnswerThree")},
|
||||
"vo\\misc\\chargen qa"+MyGUI::utility::toString(number)+".wav"
|
||||
};
|
||||
return step;
|
||||
}
|
||||
|
||||
struct ClassPoint
|
||||
std::string question = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_Question");
|
||||
std::string answer0 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerOne");
|
||||
std::string answer1 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerTwo");
|
||||
std::string answer2 = Fallback::Map::getString("Question_" + MyGUI::utility::toString(number) + "_AnswerThree");
|
||||
std::string sound = "vo\\misc\\chargen qa" + MyGUI::utility::toString(number) + ".wav";
|
||||
|
||||
Response r0 = {answer0, ESM::Class::Combat};
|
||||
Response r1 = {answer1, ESM::Class::Magic};
|
||||
Response r2 = {answer2, ESM::Class::Stealth};
|
||||
|
||||
// randomize order in which responses are displayed
|
||||
int order = Misc::Rng::rollDice(6);
|
||||
|
||||
switch (order)
|
||||
{
|
||||
const char *id;
|
||||
// Specialization points to match, in order: Stealth, Combat, Magic
|
||||
// Note: Order is taken from http://www.uesp.net/wiki/Morrowind:Class_Quiz
|
||||
unsigned int points[3];
|
||||
};
|
||||
case 0:
|
||||
return {question, {r0, r1, r2}, sound};
|
||||
case 1:
|
||||
return {question, {r0, r2, r1}, sound};
|
||||
case 2:
|
||||
return {question, {r1, r0, r2}, sound};
|
||||
case 3:
|
||||
return {question, {r1, r2, r0}, sound};
|
||||
case 4:
|
||||
return {question, {r2, r0, r1}, sound};
|
||||
default:
|
||||
return {question, {r2, r1, r0}, sound};
|
||||
}
|
||||
}
|
||||
|
||||
void updatePlayerHealth()
|
||||
{
|
||||
|
@ -80,6 +99,9 @@ namespace MWGui
|
|||
, mGenerateClassStep(0)
|
||||
{
|
||||
mCreationStage = CSE_NotStarted;
|
||||
mGenerateClassResponses[0] = ESM::Class::Combat;
|
||||
mGenerateClassResponses[1] = ESM::Class::Magic;
|
||||
mGenerateClassResponses[2] = ESM::Class::Stealth;
|
||||
mGenerateClassSpecializations[0] = 0;
|
||||
mGenerateClassSpecializations[1] = 0;
|
||||
mGenerateClassSpecializations[2] = 0;
|
||||
|
@ -550,12 +572,12 @@ namespace MWGui
|
|||
return;
|
||||
}
|
||||
|
||||
ESM::Class::Specialization specialization = mSpecializations[_index];
|
||||
if (specialization == ESM::Class::Stealth)
|
||||
ESM::Class::Specialization specialization = mGenerateClassResponses[_index];
|
||||
if (specialization == ESM::Class::Combat)
|
||||
++mGenerateClassSpecializations[0];
|
||||
else if (specialization == ESM::Class::Combat)
|
||||
++mGenerateClassSpecializations[1];
|
||||
else if (specialization == ESM::Class::Magic)
|
||||
++mGenerateClassSpecializations[1];
|
||||
else if (specialization == ESM::Class::Stealth)
|
||||
++mGenerateClassSpecializations[2];
|
||||
++mGenerateClassStep;
|
||||
showClassQuestionDialog();
|
||||
|
@ -565,57 +587,96 @@ namespace MWGui
|
|||
{
|
||||
if (mGenerateClassStep == 10)
|
||||
{
|
||||
static std::array<ClassPoint, 23> classes = { {
|
||||
{"Acrobat", {6, 2, 2}},
|
||||
{"Agent", {6, 1, 3}},
|
||||
{"Archer", {3, 5, 2}},
|
||||
{"Archer", {5, 5, 0}},
|
||||
{"Assassin", {6, 3, 1}},
|
||||
{"Barbarian", {3, 6, 1}},
|
||||
{"Bard", {3, 3, 3}},
|
||||
{"Battlemage", {1, 3, 6}},
|
||||
{"Crusader", {1, 6, 3}},
|
||||
{"Healer", {3, 1, 6}},
|
||||
{"Knight", {2, 6, 2}},
|
||||
{"Monk", {5, 3, 2}},
|
||||
{"Nightblade", {4, 2, 4}},
|
||||
{"Pilgrim", {5, 2, 3}},
|
||||
{"Rogue", {3, 4, 3}},
|
||||
{"Rogue", {4, 4, 2}},
|
||||
{"Rogue", {5, 4, 1}},
|
||||
{"Scout", {2, 5, 3}},
|
||||
{"Sorcerer", {2, 2, 6}},
|
||||
{"Spellsword", {2, 4, 4}},
|
||||
{"Spellsword", {5, 1, 4}},
|
||||
{"Witchhunter", {2, 3, 5}},
|
||||
{"Witchhunter", {5, 0, 5}}
|
||||
} };
|
||||
unsigned combat = mGenerateClassSpecializations[0];
|
||||
unsigned magic = mGenerateClassSpecializations[1];
|
||||
unsigned stealth = mGenerateClassSpecializations[2];
|
||||
|
||||
int match = -1;
|
||||
for (unsigned i = 0; i < classes.size(); ++i)
|
||||
if (combat > 7)
|
||||
{
|
||||
if (mGenerateClassSpecializations[0] == classes[i].points[0] &&
|
||||
mGenerateClassSpecializations[1] == classes[i].points[1] &&
|
||||
mGenerateClassSpecializations[2] == classes[i].points[2])
|
||||
{
|
||||
match = i;
|
||||
mGenerateClass = classes[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match == -1)
|
||||
{
|
||||
if (mGenerateClassSpecializations[0] >= 7)
|
||||
mGenerateClass = "Thief";
|
||||
else if (mGenerateClassSpecializations[1] >= 7)
|
||||
mGenerateClass = "Warrior";
|
||||
else if (mGenerateClassSpecializations[2] >= 7)
|
||||
}
|
||||
else if (magic > 7)
|
||||
{
|
||||
mGenerateClass = "Mage";
|
||||
}
|
||||
else if (stealth > 7)
|
||||
{
|
||||
mGenerateClass = "Thief";
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(Debug::Warning) << "Failed to deduce class from chosen answers in generate class dialog.";
|
||||
switch (combat)
|
||||
{
|
||||
case 4:
|
||||
mGenerateClass = "Rogue";
|
||||
break;
|
||||
case 5:
|
||||
if (stealth == 3)
|
||||
mGenerateClass = "Scout";
|
||||
else
|
||||
mGenerateClass = "Archer";
|
||||
break;
|
||||
case 6:
|
||||
if (stealth == 1)
|
||||
mGenerateClass = "Barbarian";
|
||||
else if (stealth == 3)
|
||||
mGenerateClass = "Crusader";
|
||||
else
|
||||
mGenerateClass = "Knight";
|
||||
break;
|
||||
case 7:
|
||||
mGenerateClass = "Warrior";
|
||||
break;
|
||||
default:
|
||||
switch (magic)
|
||||
{
|
||||
case 4:
|
||||
mGenerateClass = "Spellsword";
|
||||
break;
|
||||
case 5:
|
||||
mGenerateClass = "Witchhunter";
|
||||
break;
|
||||
case 6:
|
||||
if (combat == 2)
|
||||
mGenerateClass = "Sorcerer";
|
||||
else if (combat == 3)
|
||||
mGenerateClass = "Healer";
|
||||
else
|
||||
mGenerateClass = "Battlemage";
|
||||
break;
|
||||
case 7:
|
||||
mGenerateClass = "Mage";
|
||||
break;
|
||||
default:
|
||||
switch (stealth)
|
||||
{
|
||||
case 3:
|
||||
if (magic == 3)
|
||||
mGenerateClass = "Bard"; // unreachable
|
||||
else
|
||||
mGenerateClass = "Warrior";
|
||||
break;
|
||||
case 5:
|
||||
if (magic == 3)
|
||||
mGenerateClass = "Monk";
|
||||
else
|
||||
mGenerateClass = "Pilgrim";
|
||||
break;
|
||||
case 6:
|
||||
if (magic == 1)
|
||||
mGenerateClass = "Agent";
|
||||
else if (magic == 3)
|
||||
mGenerateClass = "Assassin";
|
||||
else
|
||||
mGenerateClass = "Acrobat";
|
||||
break;
|
||||
case 7:
|
||||
mGenerateClass = "Thief";
|
||||
break;
|
||||
default:
|
||||
mGenerateClass = "Warrior";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -642,16 +703,21 @@ namespace MWGui
|
|||
|
||||
mGenerateClassQuestionDialog = new InfoBoxDialog();
|
||||
|
||||
Step step = sGenerateClassSteps(mGenerateClassStep);
|
||||
mGenerateClassResponses[0] = step.mResponses[0].mSpecialization;
|
||||
mGenerateClassResponses[1] = step.mResponses[1].mSpecialization;
|
||||
mGenerateClassResponses[2] = step.mResponses[2].mSpecialization;
|
||||
|
||||
InfoBoxDialog::ButtonList buttons;
|
||||
mGenerateClassQuestionDialog->setText(sGenerateClassSteps(mGenerateClassStep).mText);
|
||||
buttons.push_back(sGenerateClassSteps(mGenerateClassStep).mButtons[0]);
|
||||
buttons.push_back(sGenerateClassSteps(mGenerateClassStep).mButtons[1]);
|
||||
buttons.push_back(sGenerateClassSteps(mGenerateClassStep).mButtons[2]);
|
||||
mGenerateClassQuestionDialog->setText(step.mText);
|
||||
buttons.push_back(step.mResponses[0].mText);
|
||||
buttons.push_back(step.mResponses[1].mText);
|
||||
buttons.push_back(step.mResponses[2].mText);
|
||||
mGenerateClassQuestionDialog->setButtons(buttons);
|
||||
mGenerateClassQuestionDialog->eventButtonSelected += MyGUI::newDelegate(this, &CharacterCreation::onClassQuestionChosen);
|
||||
mGenerateClassQuestionDialog->setVisible(true);
|
||||
|
||||
MWBase::Environment::get().getSoundManager()->say(sGenerateClassSteps(mGenerateClassStep).mSound);
|
||||
MWBase::Environment::get().getSoundManager()->say(step.mSound);
|
||||
}
|
||||
|
||||
void CharacterCreation::selectGeneratedClass()
|
||||
|
|
|
@ -75,8 +75,9 @@ namespace MWGui
|
|||
|
||||
//Class generation vars
|
||||
unsigned mGenerateClassStep; // Keeps track of current step in Generate Class dialog
|
||||
ESM::Class::Specialization mGenerateClassResponses[3];
|
||||
unsigned mGenerateClassSpecializations[3]; // A counter for each specialization which is increased when an answer is chosen
|
||||
std::string mGenerateClass; // In order: Stealth, Combat, Magic
|
||||
std::string mGenerateClass; // In order: Combat, Magic, Stealth
|
||||
|
||||
////Dialog events
|
||||
//Name dialog
|
||||
|
|
|
@ -137,27 +137,8 @@ namespace MWGui
|
|||
MWBase::Environment::get().getWindowManager()->getGameSettingString("spoint", "") );
|
||||
}
|
||||
}
|
||||
if (effectInfo.mRemainingTime > -1 &&
|
||||
Settings::Manager::getBool("show effect duration","Game")) {
|
||||
sourcesDescription += " #{sDuration}: ";
|
||||
float duration = effectInfo.mRemainingTime;
|
||||
if (duration > 3600)
|
||||
{
|
||||
int hour = duration / 3600;
|
||||
duration -= hour*3600;
|
||||
sourcesDescription += MWGui::ToolTips::toString(hour) + "h";
|
||||
}
|
||||
if (duration > 60)
|
||||
{
|
||||
int minute = duration / 60;
|
||||
duration -= minute*60;
|
||||
sourcesDescription += MWGui::ToolTips::toString(minute) + "m";
|
||||
}
|
||||
if (duration > 0.1)
|
||||
{
|
||||
sourcesDescription += MWGui::ToolTips::toString(duration) + "s";
|
||||
}
|
||||
}
|
||||
if (effectInfo.mRemainingTime > -1 && Settings::Manager::getBool("show effect duration","Game"))
|
||||
sourcesDescription += MWGui::ToolTips::getDurationString(effectInfo.mRemainingTime, " #{sDuration}");
|
||||
|
||||
addNewLine = true;
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ namespace MWGui
|
|||
{
|
||||
newSpell.mType = Spell::Type_Spell;
|
||||
std::string cost = std::to_string(spell->mData.mCost);
|
||||
std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor, nullptr, true, true)));
|
||||
std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor)));
|
||||
newSpell.mCostColumn = cost + "/" + chance;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -667,6 +667,60 @@ namespace MWGui
|
|||
return ret;
|
||||
}
|
||||
|
||||
std::string ToolTips::getDurationString(float duration, const std::string& prefix)
|
||||
{
|
||||
std::string ret;
|
||||
ret = prefix + ": ";
|
||||
|
||||
if (duration < 1.f)
|
||||
{
|
||||
ret += "0 s";
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr int secondsPerMinute = 60; // 60 seconds
|
||||
constexpr int secondsPerHour = secondsPerMinute * 60; // 60 minutes
|
||||
constexpr int secondsPerDay = secondsPerHour * 24; // 24 hours
|
||||
constexpr int secondsPerMonth = secondsPerDay * 30; // 30 days
|
||||
constexpr int secondsPerYear = secondsPerDay * 365;
|
||||
int fullDuration = static_cast<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()
|
||||
{
|
||||
mFullHelp = !mFullHelp;
|
||||
|
|
|
@ -84,6 +84,9 @@ namespace MWGui
|
|||
static std::string getCellRefString(const MWWorld::CellRef& cellref);
|
||||
///< Returns a string containing debug tooltip information about the given cellref.
|
||||
|
||||
static std::string getDurationString (float duration, const std::string& prefix);
|
||||
///< Returns duration as two largest time units, rounded down. Note: not localized; no line break.
|
||||
|
||||
// these do not create an actual tooltip, but they fill in the data that is required so the tooltip
|
||||
// system knows what to show in case this widget is hovered
|
||||
static void createSkillToolTip(MyGUI::Widget* widget, int skillId);
|
||||
|
|
|
@ -51,6 +51,7 @@ namespace MWInput
|
|||
osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler,
|
||||
osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation,
|
||||
const std::string& userFile, bool userFileExists,
|
||||
const std::string& userControllerBindingsFile,
|
||||
const std::string& controllerBindingsFile, bool grab)
|
||||
: mWindow(window)
|
||||
, mWindowVisible(true)
|
||||
|
@ -125,10 +126,14 @@ namespace MWInput
|
|||
|
||||
// Load controller mappings
|
||||
#if SDL_VERSION_ATLEAST(2,0,2)
|
||||
if(controllerBindingsFile!="")
|
||||
if(!controllerBindingsFile.empty())
|
||||
{
|
||||
SDL_GameControllerAddMappingsFromFile(controllerBindingsFile.c_str());
|
||||
}
|
||||
if(!userControllerBindingsFile.empty())
|
||||
{
|
||||
SDL_GameControllerAddMappingsFromFile(userControllerBindingsFile.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Open all presently connected sticks
|
||||
|
@ -601,7 +606,7 @@ namespace MWInput
|
|||
rot[2] = xAxis * (dt * 100.0f) * 10.0f * mCameraSensitivity * (1.0f/256.f) * (mInvertX ? -1 : 1);
|
||||
|
||||
// Only actually turn player when we're not in vanity mode
|
||||
if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot))
|
||||
if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && mControlSwitch["playerlooking"])
|
||||
{
|
||||
mPlayer->yaw(rot[2]);
|
||||
mPlayer->pitch(rot[0]);
|
||||
|
@ -839,9 +844,6 @@ namespace MWInput
|
|||
|
||||
void InputManager::toggleControlSwitch (const std::string& sw, bool value)
|
||||
{
|
||||
if (mControlSwitch[sw] == value) {
|
||||
return;
|
||||
}
|
||||
/// \note 7 switches at all, if-else is relevant
|
||||
if (sw == "playercontrols" && !value) {
|
||||
mPlayer->setLeftRight(0);
|
||||
|
@ -853,8 +855,8 @@ namespace MWInput
|
|||
mPlayer->setUpDown(0);
|
||||
} else if (sw == "vanitymode") {
|
||||
MWBase::Environment::get().getWorld()->allowVanityMode(value);
|
||||
} else if (sw == "playerlooking") {
|
||||
MWBase::Environment::get().getWorld()->togglePlayerLooking(value);
|
||||
} else if (sw == "playerlooking" && !value) {
|
||||
MWBase::Environment::get().getWorld()->rotateObject(mPlayer->getPlayer(), 0.f, 0.f, 0.f);
|
||||
}
|
||||
mControlSwitch[sw] = value;
|
||||
}
|
||||
|
@ -989,7 +991,7 @@ namespace MWInput
|
|||
rot[2] = -x;
|
||||
|
||||
// Only actually turn player when we're not in vanity mode
|
||||
if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot))
|
||||
if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && mControlSwitch["playerlooking"])
|
||||
{
|
||||
mPlayer->yaw(x);
|
||||
mPlayer->pitch(y);
|
||||
|
|
|
@ -76,6 +76,7 @@ namespace MWInput
|
|||
osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler,
|
||||
osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation,
|
||||
const std::string& userFile, bool userFileExists,
|
||||
const std::string& userControllerBindingsFile,
|
||||
const std::string& controllerBindingsFile, bool grab);
|
||||
|
||||
virtual ~InputManager();
|
||||
|
|
|
@ -2025,14 +2025,7 @@ namespace MWMechanics
|
|||
|
||||
MWWorld::Ptr player = getPlayer();
|
||||
|
||||
CreatureStats& stats = player.getClass().getCreatureStats(player);
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
|
||||
bool sneaking = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak);
|
||||
bool inair = !world->isOnGround(player) && !world->isSwimming(player) && !world->isFlying(player);
|
||||
sneaking = sneaking && (ctrl->isSneaking() || inair);
|
||||
|
||||
if (!sneaking)
|
||||
if (!MWBase::Environment::get().getMechanicsManager()->isSneaking(player))
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->setSneakVisibility(false);
|
||||
return;
|
||||
|
@ -2040,6 +2033,7 @@ namespace MWMechanics
|
|||
|
||||
static float sneakSkillTimer = 0.f; // Times sneak skill progress from "avoid notice"
|
||||
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
const MWWorld::Store<ESM::GameSetting>& gmst = world->getStore().get<ESM::GameSetting>();
|
||||
static const float fSneakUseDist = gmst.find("fSneakUseDist")->mValue.getFloat();
|
||||
static const float fSneakUseDelay = gmst.find("fSneakUseDelay")->mValue.getFloat();
|
||||
|
|
|
@ -511,10 +511,9 @@ namespace MWMechanics
|
|||
|
||||
if (targetClass.hasInventoryStore(target))
|
||||
{
|
||||
MWMechanics::WeaponType weapType = WeapType_None;
|
||||
MWWorld::ContainerStoreIterator weaponSlot =
|
||||
MWMechanics::getActiveWeapon(targetClass.getCreatureStats(target), targetClass.getInventoryStore(target), &weapType);
|
||||
if (weapType != WeapType_PickProbe && weapType != WeapType_Spell && weapType != WeapType_None && weapType != WeapType_HandToHand)
|
||||
int weapType = ESM::Weapon::None;
|
||||
MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(target, &weapType);
|
||||
if (weapType > ESM::Weapon::None)
|
||||
targetWeapon = *weaponSlot;
|
||||
}
|
||||
|
||||
|
@ -729,7 +728,7 @@ osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& t
|
|||
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||
|
||||
// get projectile speed (depending on weapon type)
|
||||
if (weapType == ESM::Weapon::MarksmanThrown)
|
||||
if (MWMechanics::getWeaponType(weapType)->mWeaponClass == ESM::WeaponType::Thrown)
|
||||
{
|
||||
static float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->mValue.getFloat();
|
||||
static float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat();
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "combat.hpp"
|
||||
#include "weaponpriority.hpp"
|
||||
#include "spellpriority.hpp"
|
||||
#include "weapontype.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
@ -125,8 +126,7 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
const ESM::Weapon* weapon = mWeapon.get<ESM::Weapon>()->mBase;
|
||||
|
||||
if (weapon->mData.mType >= ESM::Weapon::MarksmanBow)
|
||||
if (MWMechanics::getWeaponType(weapon->mData.mType)->mWeaponClass != ESM::WeaponType::Melee)
|
||||
{
|
||||
isRanged = true;
|
||||
return fProjectileMaxSpeed;
|
||||
|
@ -194,11 +194,12 @@ namespace MWMechanics
|
|||
if (rating > bestActionRating)
|
||||
{
|
||||
const ESM::Weapon* weapon = it->get<ESM::Weapon>()->mBase;
|
||||
int ammotype = getWeaponType(weapon->mData.mType)->mAmmoType;
|
||||
|
||||
MWWorld::Ptr ammo;
|
||||
if (weapon->mData.mType == ESM::Weapon::MarksmanBow)
|
||||
if (ammotype == ESM::Weapon::Arrow)
|
||||
ammo = bestArrow;
|
||||
else if (weapon->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
||||
else if (ammotype == ESM::Weapon::Bolt)
|
||||
ammo = bestBolt;
|
||||
|
||||
bestActionRating = rating;
|
||||
|
@ -367,7 +368,7 @@ namespace MWMechanics
|
|||
else if (!activeWeapon.isEmpty())
|
||||
{
|
||||
const ESM::Weapon* esmWeap = activeWeapon.get<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();
|
||||
dist = fTargetSpellMaxSpeed;
|
||||
|
|
|
@ -301,7 +301,7 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoin
|
|||
return true;
|
||||
|
||||
const float actorSpeed = actor.getClass().getSpeed(actor);
|
||||
const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability
|
||||
const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / getAngularVelocity(actorSpeed) * 2; // *2 - for reliability
|
||||
const float distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length();
|
||||
|
||||
const float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2;
|
||||
|
@ -363,7 +363,7 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act
|
|||
// get actor's shortest radius for moving in circle
|
||||
float speed = actor.getClass().getSpeed(actor);
|
||||
speed += speed * 0.1f; // 10% real speed inaccuracy
|
||||
float radius = speed / MAX_VEL_ANGULAR_RADIANS;
|
||||
float radius = speed / getAngularVelocity(speed);
|
||||
|
||||
// get radius direction to the center
|
||||
const float* rot = actor.getRefData().getPosition().rot;
|
||||
|
|
|
@ -448,7 +448,7 @@ namespace MWMechanics
|
|||
else
|
||||
{
|
||||
// have not yet reached the destination
|
||||
evadeObstacles(actor, storage);
|
||||
evadeObstacles(actor, duration, storage);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -479,8 +479,17 @@ namespace MWMechanics
|
|||
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||
}
|
||||
|
||||
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||
{
|
||||
if (mUsePathgrid)
|
||||
{
|
||||
const auto halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
|
||||
const float actorTolerance = 2 * actor.getClass().getSpeed(actor) * duration
|
||||
+ 1.2 * std::max(halfExtents.x(), halfExtents.y());
|
||||
const float pointTolerance = std::max(MIN_TOLERANCE, actorTolerance);
|
||||
mPathFinder.buildPathByNavMeshToNextPoint(actor, halfExtents, getNavigatorFlags(actor), pointTolerance);
|
||||
}
|
||||
|
||||
if (mObstacleCheck.isEvading())
|
||||
{
|
||||
// first check if we're walking into a door
|
||||
|
|
|
@ -137,7 +137,7 @@ namespace MWMechanics
|
|||
short unsigned getRandomIdle();
|
||||
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||
void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
void evadeObstacles(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||
void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage);
|
||||
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||
void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||
|
|
|
@ -214,33 +214,6 @@ public:
|
|||
};
|
||||
|
||||
|
||||
static const struct WeaponInfo {
|
||||
WeaponType type;
|
||||
const char shortgroup[16];
|
||||
const char longgroup[16];
|
||||
} sWeaponTypeList[] = {
|
||||
{ WeapType_HandToHand, "hh", "handtohand" },
|
||||
{ WeapType_OneHand, "1h", "weapononehand" },
|
||||
{ WeapType_TwoHand, "2c", "weapontwohand" },
|
||||
{ WeapType_TwoWide, "2w", "weapontwowide" },
|
||||
{ WeapType_BowAndArrow, "1h", "bowandarrow" },
|
||||
{ WeapType_Crossbow, "crossbow", "crossbow" },
|
||||
{ WeapType_Thrown, "1h", "throwweapon" },
|
||||
{ WeapType_PickProbe, "1h", "pickprobe" },
|
||||
{ WeapType_Spell, "spell", "spellcast" },
|
||||
};
|
||||
static const WeaponInfo *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])];
|
||||
|
||||
class FindWeaponType {
|
||||
WeaponType type;
|
||||
|
||||
public:
|
||||
FindWeaponType(WeaponType _type) : type(_type) { }
|
||||
|
||||
bool operator()(const WeaponInfo &weap) const
|
||||
{ return weap.type == type; }
|
||||
};
|
||||
|
||||
std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const
|
||||
{
|
||||
int numAnims=0;
|
||||
|
@ -331,6 +304,8 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
|
|||
{
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
if (mWeaponType > ESM::Weapon::None)
|
||||
mAnimation->showWeapons(true);
|
||||
}
|
||||
else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped)
|
||||
{
|
||||
|
@ -361,7 +336,7 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
|
|||
idle = CharState_None;
|
||||
}
|
||||
|
||||
void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, bool force)
|
||||
void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force)
|
||||
{
|
||||
if (!force && jump == mJumpState && idle == CharState_None)
|
||||
return;
|
||||
|
@ -371,22 +346,17 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState
|
|||
if (jump != JumpState_None)
|
||||
{
|
||||
jumpAnimName = "jump";
|
||||
if(weap != sWeaponTypeListEnd)
|
||||
if(!weapShortGroup.empty())
|
||||
{
|
||||
jumpAnimName += weap->shortgroup;
|
||||
jumpAnimName += weapShortGroup;
|
||||
if(!mAnimation->hasAnimation(jumpAnimName))
|
||||
{
|
||||
jumpmask = MWRender::Animation::BlendMask_LowerBody;
|
||||
jumpAnimName = "jump";
|
||||
jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask);
|
||||
|
||||
// Since we apply movement only for lower body, do not reset idle animations.
|
||||
// If we apply jump only for lower body, do not reset idle animations.
|
||||
// For upper body there will be idle animation.
|
||||
if (idle == CharState_None)
|
||||
if (jumpmask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None)
|
||||
idle = CharState_Idle;
|
||||
|
||||
// For crossbow animations use 1h ones as fallback
|
||||
if (mWeaponType == WeapType_Crossbow)
|
||||
jumpAnimName += "1h";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -460,7 +430,65 @@ void CharacterController::onClose()
|
|||
}
|
||||
}
|
||||
|
||||
void CharacterController::refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, CharacterState& idle, bool force)
|
||||
std::string CharacterController::getWeaponAnimation(int weaponType) const
|
||||
{
|
||||
std::string weaponGroup = getWeaponType(weaponType)->mLongGroup;
|
||||
bool isRealWeapon = weaponType != ESM::Weapon::HandToHand && weaponType != ESM::Weapon::Spell && weaponType != ESM::Weapon::None;
|
||||
if (isRealWeapon && !mAnimation->hasAnimation(weaponGroup))
|
||||
{
|
||||
static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup;
|
||||
static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup;
|
||||
|
||||
const ESM::WeaponType* weapInfo = getWeaponType(weaponType);
|
||||
|
||||
// For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones
|
||||
if (weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee)
|
||||
weaponGroup = twoHandFallback;
|
||||
else if (isRealWeapon)
|
||||
weaponGroup = oneHandFallback;
|
||||
}
|
||||
|
||||
return weaponGroup;
|
||||
}
|
||||
|
||||
std::string CharacterController::fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask)
|
||||
{
|
||||
bool isRealWeapon = mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None;
|
||||
if (!isRealWeapon)
|
||||
{
|
||||
if (blendMask != nullptr)
|
||||
*blendMask = MWRender::Animation::BlendMask_LowerBody;
|
||||
|
||||
return baseGroupName;
|
||||
}
|
||||
|
||||
static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mShortGroup;
|
||||
static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mShortGroup;
|
||||
|
||||
std::string groupName = baseGroupName;
|
||||
const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType);
|
||||
|
||||
// For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones
|
||||
if (isRealWeapon && weapInfo->mFlags & ESM::WeaponType::TwoHanded && weapInfo->mWeaponClass == ESM::WeaponType::Melee)
|
||||
groupName += twoHandFallback;
|
||||
else if (isRealWeapon)
|
||||
groupName += oneHandFallback;
|
||||
|
||||
// Special case for crossbows - we shouls apply 1h animations a fallback only for lower body
|
||||
if (mWeaponType == ESM::Weapon::MarksmanCrossbow && blendMask != nullptr)
|
||||
*blendMask = MWRender::Animation::BlendMask_LowerBody;
|
||||
|
||||
if (!mAnimation->hasAnimation(groupName))
|
||||
{
|
||||
groupName = baseGroupName;
|
||||
if (blendMask != nullptr)
|
||||
*blendMask = MWRender::Animation::BlendMask_LowerBody;
|
||||
}
|
||||
|
||||
return groupName;
|
||||
}
|
||||
|
||||
void CharacterController::refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force)
|
||||
{
|
||||
if (movement == mMovementState && idle == mIdleState && !force)
|
||||
return;
|
||||
|
@ -474,15 +502,15 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
|||
if(movestate != sMovementListEnd)
|
||||
{
|
||||
movementAnimName = movestate->groupname;
|
||||
if(weap != sWeaponTypeListEnd)
|
||||
if(!weapShortGroup.empty())
|
||||
{
|
||||
std::string::size_type swimpos = movementAnimName.find("swim");
|
||||
if (swimpos == std::string::npos)
|
||||
{
|
||||
if (mWeaponType == WeapType_Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case
|
||||
movementAnimName = weap->shortgroup + movementAnimName;
|
||||
if (mWeaponType == ESM::Weapon::Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case
|
||||
movementAnimName = weapShortGroup + movementAnimName;
|
||||
else
|
||||
movementAnimName += weap->shortgroup;
|
||||
movementAnimName += weapShortGroup;
|
||||
}
|
||||
|
||||
if(!mAnimation->hasAnimation(movementAnimName))
|
||||
|
@ -490,15 +518,12 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
|||
movementAnimName = movestate->groupname;
|
||||
if (swimpos == std::string::npos)
|
||||
{
|
||||
movemask = MWRender::Animation::BlendMask_LowerBody;
|
||||
// Since we apply movement only for lower body, do not reset idle animations.
|
||||
// For upper body there will be idle animation.
|
||||
if (idle == CharState_None)
|
||||
idle = CharState_Idle;
|
||||
movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask);
|
||||
|
||||
// For crossbow animations use 1h ones as fallback
|
||||
if (mWeaponType == WeapType_Crossbow)
|
||||
movementAnimName += "1h";
|
||||
// If we apply movement only for lower body, do not reset idle animations.
|
||||
// For upper body there will be idle animation.
|
||||
if (movemask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None)
|
||||
idle = CharState_Idle;
|
||||
}
|
||||
else if (idle == CharState_None)
|
||||
{
|
||||
|
@ -520,7 +545,20 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
|||
if(!mAnimation->hasAnimation(movementAnimName))
|
||||
{
|
||||
std::string::size_type swimpos = movementAnimName.find("swim");
|
||||
if(swimpos == std::string::npos)
|
||||
if (swimpos != std::string::npos)
|
||||
{
|
||||
movementAnimName.erase(swimpos, 4);
|
||||
if (!weapShortGroup.empty())
|
||||
{
|
||||
std::string weapMovementAnimName = movementAnimName + weapShortGroup;
|
||||
if(mAnimation->hasAnimation(weapMovementAnimName))
|
||||
movementAnimName = weapMovementAnimName;
|
||||
else
|
||||
movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask);
|
||||
}
|
||||
}
|
||||
|
||||
if (swimpos == std::string::npos || !mAnimation->hasAnimation(movementAnimName))
|
||||
{
|
||||
std::string::size_type runpos = movementAnimName.find("run");
|
||||
if (runpos != std::string::npos)
|
||||
|
@ -532,25 +570,6 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
|||
else
|
||||
movementAnimName.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// For crossbow animations use 1h ones as fallback
|
||||
if (mWeaponType == WeapType_Crossbow)
|
||||
movementAnimName += "1h";
|
||||
|
||||
movementAnimName.erase(swimpos, 4);
|
||||
if (weap != sWeaponTypeListEnd)
|
||||
{
|
||||
std::string weapMovementAnimName = movementAnimName + weap->shortgroup;
|
||||
if(mAnimation->hasAnimation(weapMovementAnimName))
|
||||
movementAnimName = weapMovementAnimName;
|
||||
else
|
||||
movemask = MWRender::Animation::BlendMask_LowerBody;
|
||||
}
|
||||
|
||||
if (!mAnimation->hasAnimation(movementAnimName))
|
||||
movementAnimName.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -565,10 +584,6 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
|||
mCurrentMovement = movementAnimName;
|
||||
if(!mCurrentMovement.empty())
|
||||
{
|
||||
bool isflying = MWBase::Environment::get().getWorld()->isFlying(mPtr);
|
||||
bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !isflying;
|
||||
bool issneaking = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak) && !isflying;
|
||||
|
||||
// For non-flying creatures, MW uses the Walk animation to calculate the animation velocity
|
||||
// even if we are running. This must be replicated, otherwise the observed speed would differ drastically.
|
||||
std::string anim = mCurrentMovement;
|
||||
|
@ -601,7 +616,7 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
|||
// The first person anims don't have any velocity to calculate a speed multiplier from.
|
||||
// We use the third person velocities instead.
|
||||
// FIXME: should be pulled from the actual animation, but it is not presently loaded.
|
||||
mMovementAnimSpeed = (issneaking ? 33.5452f : (isrunning ? 222.857f : 154.064f));
|
||||
mMovementAnimSpeed = (isSneaking() ? 33.5452f : (isRunning() ? 222.857f : 154.064f));
|
||||
mMovementAnimationControlled = false;
|
||||
}
|
||||
}
|
||||
|
@ -612,7 +627,7 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
|||
}
|
||||
}
|
||||
|
||||
void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force)
|
||||
void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force)
|
||||
{
|
||||
// FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update),
|
||||
// the idle animation should be displayed
|
||||
|
@ -644,11 +659,13 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat
|
|||
else if(mIdleState != CharState_None)
|
||||
{
|
||||
idleGroup = "idle";
|
||||
if(weap != sWeaponTypeListEnd)
|
||||
if(!weapShortGroup.empty())
|
||||
{
|
||||
idleGroup += weap->shortgroup;
|
||||
idleGroup += weapShortGroup;
|
||||
if(!mAnimation->hasAnimation(idleGroup))
|
||||
idleGroup = "idle";
|
||||
{
|
||||
idleGroup = fallbackShortWeaponGroup("idle");
|
||||
}
|
||||
|
||||
// play until the Loop Stop key 2 to 5 times, then play until the Stop key
|
||||
// this replicates original engine behavior for the "Idle1h" 1st-person animation
|
||||
|
@ -683,9 +700,9 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|||
if (mPtr.getClass().isActor())
|
||||
refreshHitRecoilAnims(idle);
|
||||
|
||||
const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType));
|
||||
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||
weap = sWeaponTypeListEnd;
|
||||
std::string weap;
|
||||
if (mPtr.getClass().hasInventoryStore(mPtr))
|
||||
weap = getWeaponType(mWeaponType)->mShortGroup;
|
||||
|
||||
refreshJumpAnims(weap, jump, idle, force);
|
||||
refreshMovementAnims(weap, movement, idle, force);
|
||||
|
@ -694,77 +711,6 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
|
|||
refreshIdleAnims(weap, idle, force);
|
||||
}
|
||||
|
||||
|
||||
void getWeaponGroup(WeaponType weaptype, std::string &group)
|
||||
{
|
||||
const WeaponInfo *info = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(weaptype));
|
||||
if(info != sWeaponTypeListEnd)
|
||||
group = info->longgroup;
|
||||
else
|
||||
group.clear();
|
||||
}
|
||||
|
||||
|
||||
MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype)
|
||||
{
|
||||
if(stats.getDrawState() == DrawState_Spell)
|
||||
{
|
||||
*weaptype = WeapType_Spell;
|
||||
return inv.end();
|
||||
}
|
||||
|
||||
if(stats.getDrawState() == MWMechanics::DrawState_Weapon)
|
||||
{
|
||||
MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if(weapon == inv.end())
|
||||
*weaptype = WeapType_HandToHand;
|
||||
else
|
||||
{
|
||||
const std::string &type = weapon->getTypeName();
|
||||
if(type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name())
|
||||
*weaptype = WeapType_PickProbe;
|
||||
else if(type == typeid(ESM::Weapon).name())
|
||||
{
|
||||
MWWorld::LiveCellRef<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)
|
||||
{
|
||||
// Make sure the character was swimming upon death for forward-compatibility
|
||||
|
@ -909,7 +855,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
|||
, mHitState(CharState_None)
|
||||
, mUpperBodyState(UpperCharState_Nothing)
|
||||
, mJumpState(JumpState_None)
|
||||
, mWeaponType(WeapType_None)
|
||||
, mWeaponType(ESM::Weapon::None)
|
||||
, mAttackStrength(0.f)
|
||||
, mSkipAnim(false)
|
||||
, mSecondsOfSwimming(0)
|
||||
|
@ -933,19 +879,20 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
|||
|
||||
if (cls.hasInventoryStore(mPtr))
|
||||
{
|
||||
getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType);
|
||||
if (mWeaponType != WeapType_None)
|
||||
getActiveWeapon(mPtr, &mWeaponType);
|
||||
if (mWeaponType != ESM::Weapon::None)
|
||||
{
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
getWeaponGroup(mWeaponType, mCurrentWeapon);
|
||||
mCurrentWeapon = getWeaponAnimation(mWeaponType);
|
||||
}
|
||||
|
||||
if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand)
|
||||
if(mWeaponType != ESM::Weapon::None && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::HandToHand)
|
||||
{
|
||||
mAnimation->showWeapons(true);
|
||||
// Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly,
|
||||
// for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example)
|
||||
bool useRelativeDuration = mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Crossbow;
|
||||
ESM::WeaponType::Class weaponClass = getWeaponType(mWeaponType)->mWeaponClass;
|
||||
bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged;
|
||||
mAnimation->setWeaponGroup(mCurrentWeapon, useRelativeDuration);
|
||||
}
|
||||
|
||||
|
@ -1185,11 +1132,11 @@ bool CharacterController::updateCreatureState()
|
|||
const MWWorld::Class &cls = mPtr.getClass();
|
||||
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
||||
|
||||
WeaponType weapType = WeapType_None;
|
||||
int weapType = ESM::Weapon::None;
|
||||
if(stats.getDrawState() == DrawState_Weapon)
|
||||
weapType = WeapType_HandToHand;
|
||||
weapType = ESM::Weapon::HandToHand;
|
||||
else if (stats.getDrawState() == DrawState_Spell)
|
||||
weapType = WeapType_Spell;
|
||||
weapType = ESM::Weapon::Spell;
|
||||
|
||||
if (weapType != mWeaponType)
|
||||
{
|
||||
|
@ -1206,7 +1153,7 @@ bool CharacterController::updateCreatureState()
|
|||
|
||||
std::string startKey = "start";
|
||||
std::string stopKey = "stop";
|
||||
if (weapType == WeapType_Spell)
|
||||
if (weapType == ESM::Weapon::Spell)
|
||||
{
|
||||
const std::string spellid = stats.getSpells().getSelectedSpell();
|
||||
bool canCast = mCastingManualSpell || MWBase::Environment::get().getWorld()->startSpellCast(mPtr);
|
||||
|
@ -1272,7 +1219,7 @@ bool CharacterController::updateCreatureState()
|
|||
mCurrentWeapon = "";
|
||||
}
|
||||
|
||||
if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation
|
||||
if (weapType != ESM::Weapon::Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation
|
||||
{
|
||||
mCurrentWeapon = chooseRandomAttackAnimation();
|
||||
}
|
||||
|
@ -1287,7 +1234,7 @@ bool CharacterController::updateCreatureState()
|
|||
|
||||
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
|
||||
|
||||
if (weapType == WeapType_HandToHand)
|
||||
if (weapType == ESM::Weapon::HandToHand)
|
||||
playSwishSound(0.0f);
|
||||
}
|
||||
}
|
||||
|
@ -1301,34 +1248,23 @@ bool CharacterController::updateCreatureState()
|
|||
return false;
|
||||
}
|
||||
|
||||
bool CharacterController::updateCarriedLeftVisible(WeaponType weaptype) const
|
||||
bool CharacterController::updateCarriedLeftVisible(const int weaptype) const
|
||||
{
|
||||
// Shields/torches shouldn't be visible during any operation involving two hands
|
||||
// There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop",
|
||||
// but they are also present in weapon drawing animation.
|
||||
switch (weaptype)
|
||||
{
|
||||
case WeapType_Spell:
|
||||
case WeapType_BowAndArrow:
|
||||
case WeapType_Crossbow:
|
||||
case WeapType_HandToHand:
|
||||
case WeapType_TwoHand:
|
||||
case WeapType_TwoWide:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
return !(getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded);
|
||||
}
|
||||
|
||||
bool CharacterController::updateWeaponState(CharacterState& idle)
|
||||
{
|
||||
const MWWorld::Class &cls = mPtr.getClass();
|
||||
CreatureStats &stats = cls.getCreatureStats(mPtr);
|
||||
WeaponType weaptype = WeapType_None;
|
||||
int weaptype = ESM::Weapon::None;
|
||||
if(stats.getDrawState() == DrawState_Weapon)
|
||||
weaptype = WeapType_HandToHand;
|
||||
weaptype = ESM::Weapon::HandToHand;
|
||||
else if (stats.getDrawState() == DrawState_Spell)
|
||||
weaptype = WeapType_Spell;
|
||||
weaptype = ESM::Weapon::Spell;
|
||||
|
||||
const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf();
|
||||
|
||||
|
@ -1338,18 +1274,18 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
if (mPtr.getClass().hasInventoryStore(mPtr))
|
||||
{
|
||||
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
|
||||
MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype);
|
||||
MWWorld::ContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype);
|
||||
if(stats.getDrawState() == DrawState_Spell)
|
||||
weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
|
||||
if(weapon != inv.end() && mWeaponType != WeapType_HandToHand && weaptype > WeapType_HandToHand && weaptype < WeapType_Spell)
|
||||
if(weapon != inv.end() && mWeaponType != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None)
|
||||
upSoundId = weapon->getClass().getUpSoundId(*weapon);
|
||||
|
||||
if(weapon != inv.end() && mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell)
|
||||
if(weapon != inv.end() && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None)
|
||||
downSoundId = weapon->getClass().getDownSoundId(*weapon);
|
||||
|
||||
// weapon->HtH switch: weapon is empty already, so we need to take sound from previous weapon
|
||||
if(weapon == inv.end() && !mWeapon.isEmpty() && weaptype == WeapType_HandToHand && mWeaponType != WeapType_Spell)
|
||||
if(weapon == inv.end() && !mWeapon.isEmpty() && weaptype == ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell)
|
||||
downSoundId = mWeapon.getClass().getDownSoundId(mWeapon);
|
||||
|
||||
MWWorld::Ptr newWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr();
|
||||
|
@ -1370,8 +1306,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
bool forcestateupdate = false;
|
||||
|
||||
// We should not play equipping animation and sound during weapon->weapon transition
|
||||
bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell &&
|
||||
mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell;
|
||||
bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None &&
|
||||
mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None;
|
||||
|
||||
// If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires),
|
||||
// we should force actor to the "weapon equipped" state, interrupt attack and update animations.
|
||||
|
@ -1381,6 +1317,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
mAttackingOrSpell = false;
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
mAnimation->showWeapons(true);
|
||||
if (mPtr == getPlayer())
|
||||
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
||||
}
|
||||
|
@ -1388,7 +1325,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
if(!isKnockedOut() && !isKnockedDown() && !isRecovery())
|
||||
{
|
||||
std::string weapgroup;
|
||||
if ((!isWerewolf || mWeaponType != WeapType_Spell)
|
||||
if ((!isWerewolf || mWeaponType != ESM::Weapon::Spell)
|
||||
&& weaptype != mWeaponType
|
||||
&& mUpperBodyState != UpperCharState_UnEquipingWeap
|
||||
&& !isStillWeapon)
|
||||
|
@ -1397,7 +1334,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
if (!weaponChanged)
|
||||
{
|
||||
// Note: we do not disable unequipping animation automatically to avoid body desync
|
||||
getWeaponGroup(mWeaponType, weapgroup);
|
||||
weapgroup = getWeaponAnimation(mWeaponType);
|
||||
mAnimation->play(weapgroup, priorityWeapon,
|
||||
MWRender::Animation::BlendMask_All, false,
|
||||
1.0f, "unequip start", "unequip stop", 0.0f, 0);
|
||||
|
@ -1425,17 +1362,18 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
{
|
||||
forcestateupdate = true;
|
||||
mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype));
|
||||
weapgroup = getWeaponAnimation(weaptype);
|
||||
|
||||
getWeaponGroup(weaptype, weapgroup);
|
||||
// Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly,
|
||||
// for other weapons they should use absolute time. Some mods rely on this behaviour (to rotate throwing projectiles, for example)
|
||||
bool useRelativeDuration = weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow;
|
||||
ESM::WeaponType::Class weaponClass = getWeaponType(weaptype)->mWeaponClass;
|
||||
bool useRelativeDuration = weaponClass == ESM::WeaponType::Ranged;
|
||||
mAnimation->setWeaponGroup(weapgroup, useRelativeDuration);
|
||||
|
||||
if (!isStillWeapon)
|
||||
{
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
if (weaptype != WeapType_None)
|
||||
if (weaptype != ESM::Weapon::None)
|
||||
{
|
||||
mAnimation->showWeapons(false);
|
||||
mAnimation->play(weapgroup, priorityWeapon,
|
||||
|
@ -1444,7 +1382,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
mUpperBodyState = UpperCharState_EquipingWeap;
|
||||
|
||||
// If we do not have the "equip attach" key, show weapon manually.
|
||||
if (weaptype != WeapType_Spell)
|
||||
if (weaptype != ESM::Weapon::Spell)
|
||||
{
|
||||
if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0)
|
||||
mAnimation->showWeapons(true);
|
||||
|
@ -1464,7 +1402,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
}
|
||||
|
||||
mWeaponType = weaptype;
|
||||
getWeaponGroup(mWeaponType, mCurrentWeapon);
|
||||
mCurrentWeapon = getWeaponAnimation(mWeaponType);
|
||||
|
||||
if(!upSoundId.empty() && !isStillWeapon)
|
||||
{
|
||||
|
@ -1478,8 +1416,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
{
|
||||
mUpperBodyState = UpperCharState_Nothing;
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
mWeaponType = WeapType_None;
|
||||
getWeaponGroup(mWeaponType, mCurrentWeapon);
|
||||
mWeaponType = ESM::Weapon::None;
|
||||
mCurrentWeapon = getWeaponAnimation(mWeaponType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1490,7 +1428,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run)
|
||||
&& mHasMovedInXY
|
||||
&& !MWBase::Environment::get().getWorld()->isSwimming(mPtr)
|
||||
&& mWeaponType == WeapType_None)
|
||||
&& mWeaponType == ESM::Weapon::None)
|
||||
{
|
||||
if(!sndMgr->getSoundPlaying(mPtr, "WolfRun"))
|
||||
sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWSound::Type::Sfx,
|
||||
|
@ -1507,16 +1445,17 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
if (mPtr.getClass().hasInventoryStore(mPtr))
|
||||
{
|
||||
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
|
||||
MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype);
|
||||
MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype);
|
||||
isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name());
|
||||
if (isWeapon)
|
||||
{
|
||||
weapSpeed = weapon->get<ESM::Weapon>()->mBase->mData.mSpeed;
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped)
|
||||
{
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
|
@ -1530,6 +1469,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
|
||||
float complete;
|
||||
bool animPlaying;
|
||||
ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
|
||||
if(mAttackingOrSpell)
|
||||
{
|
||||
MWWorld::Ptr player = getPlayer();
|
||||
|
@ -1548,7 +1488,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
mCurrentWeapon = chooseRandomAttackAnimation();
|
||||
}
|
||||
|
||||
if(mWeaponType == WeapType_Spell)
|
||||
if(mWeaponType == ESM::Weapon::Spell)
|
||||
{
|
||||
// Unset casting flag, otherwise pressing the mouse button down would
|
||||
// continue casting every frame if there is no animation
|
||||
|
@ -1673,7 +1613,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
resetIdle = false;
|
||||
}
|
||||
}
|
||||
else if(mWeaponType == WeapType_PickProbe)
|
||||
else if(mWeaponType == ESM::Weapon::PickProbe)
|
||||
{
|
||||
MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
MWWorld::Ptr item = *weapon;
|
||||
|
@ -1703,7 +1643,8 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
{
|
||||
std::string startKey;
|
||||
std::string stopKey;
|
||||
if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown)
|
||||
|
||||
if(weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)
|
||||
{
|
||||
mAttackType = "shoot";
|
||||
startKey = mAttackType+" start";
|
||||
|
@ -1785,7 +1726,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability());
|
||||
}
|
||||
|
||||
if(mWeaponType != WeapType_Crossbow && mWeaponType != WeapType_BowAndArrow)
|
||||
if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown)
|
||||
{
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
|
||||
|
@ -1815,15 +1756,17 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
else if (isKnockedDown())
|
||||
{
|
||||
if (mUpperBodyState > UpperCharState_WeapEquiped)
|
||||
{
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
if (mWeaponType > ESM::Weapon::None)
|
||||
mAnimation->showWeapons(true);
|
||||
}
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
}
|
||||
}
|
||||
|
||||
mAnimation->setPitchFactor(0.f);
|
||||
if (mWeaponType == WeapType_BowAndArrow ||
|
||||
mWeaponType == WeapType_Thrown ||
|
||||
mWeaponType == WeapType_Crossbow)
|
||||
if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)
|
||||
{
|
||||
switch (mUpperBodyState)
|
||||
{
|
||||
|
@ -1840,7 +1783,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
{
|
||||
// technically we do not need a pitch for crossbow reload animation,
|
||||
// but we should avoid abrupt repositioning
|
||||
if (mWeaponType == WeapType_Crossbow)
|
||||
if (mWeaponType == ESM::Weapon::MarksmanCrossbow)
|
||||
mAnimation->setPitchFactor(std::max(0.f, 1.f-complete*10.f));
|
||||
else
|
||||
mAnimation->setPitchFactor(1.f-complete);
|
||||
|
@ -1857,7 +1800,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
mUpperBodyState == UpperCharState_FollowStartToFollowStop ||
|
||||
mUpperBodyState == UpperCharState_CastingSpell)
|
||||
{
|
||||
if (ammunition && mWeaponType == WeapType_Crossbow)
|
||||
if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow)
|
||||
mAnimation->attachArrow();
|
||||
|
||||
mUpperBodyState = UpperCharState_WeapEquiped;
|
||||
|
@ -1934,7 +1877,7 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
|
|||
if(!start.empty())
|
||||
{
|
||||
int mask = MWRender::Animation::BlendMask_All;
|
||||
if (mWeaponType == WeapType_Crossbow)
|
||||
if (mWeaponType == ESM::Weapon::MarksmanCrossbow)
|
||||
mask = MWRender::Animation::BlendMask_UpperBody;
|
||||
|
||||
mAnimation->disable(mCurrentWeapon);
|
||||
|
@ -2187,7 +2130,8 @@ void CharacterController::update(float duration, bool animationOnly)
|
|||
cls.getCreatureStats(mPtr).setFatigue(fatigue);
|
||||
}
|
||||
|
||||
if(sneak || inwater || flying || incapacitated || !solid)
|
||||
float z = cls.getJump(mPtr);
|
||||
if(sneak || inwater || flying || incapacitated || !solid || z <= 0)
|
||||
vec.z() = 0.0f;
|
||||
|
||||
bool inJump = true;
|
||||
|
@ -2210,7 +2154,6 @@ void CharacterController::update(float duration, bool animationOnly)
|
|||
else if(vec.z() > 0.0f && mJumpState != JumpState_InAir)
|
||||
{
|
||||
// Started a jump.
|
||||
float z = cls.getJump(mPtr);
|
||||
if (z > 0)
|
||||
{
|
||||
if(vec.x() == 0 && vec.y() == 0)
|
||||
|
@ -2221,28 +2164,6 @@ void CharacterController::update(float duration, bool animationOnly)
|
|||
lat.normalize();
|
||||
vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f;
|
||||
}
|
||||
|
||||
// advance acrobatics
|
||||
// also set jumping flag to allow GetPCJumping works
|
||||
if (isPlayer)
|
||||
{
|
||||
cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0);
|
||||
MWBase::Environment::get().getWorld()->getPlayer().setJumping(true);
|
||||
}
|
||||
|
||||
// decrease fatigue
|
||||
const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat();
|
||||
const float fatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat();
|
||||
float normalizedEncumbrance = mPtr.getClass().getNormalizedEncumbrance(mPtr);
|
||||
if (normalizedEncumbrance > 1)
|
||||
normalizedEncumbrance = 1;
|
||||
const float fatigueDecrease = fatigueJumpBase + normalizedEncumbrance * fatigueJumpMult;
|
||||
|
||||
if (!godmode)
|
||||
{
|
||||
fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
|
||||
cls.getCreatureStats(mPtr).setFatigue(fatigue);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(mJumpState == JumpState_InAir && !inwater && !flying && solid)
|
||||
|
@ -2455,10 +2376,10 @@ void CharacterController::update(float duration, bool animationOnly)
|
|||
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
||||
|
||||
movement = vec;
|
||||
|
||||
cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0;
|
||||
|
||||
// Can't reset jump state (mPosition[2]) here; we don't know for sure whether the PhysicSystem will actually handle it in this frame
|
||||
if (movement.z() == 0.f)
|
||||
cls.getMovementSettings(mPtr).mPosition[2] = 0;
|
||||
// Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicSystem will actually handle it in this frame
|
||||
// due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled.
|
||||
|
||||
if (!mSkipAnim)
|
||||
|
@ -2775,7 +2696,7 @@ void CharacterController::resurrect()
|
|||
mAnimation->disable(mCurrentDeath);
|
||||
mCurrentDeath.clear();
|
||||
mDeathState = CharState_None;
|
||||
mWeaponType = WeapType_None;
|
||||
mWeaponType = ESM::Weapon::None;
|
||||
}
|
||||
|
||||
void CharacterController::updateContinuousVfx()
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "weapontype.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class InventoryStore;
|
||||
|
@ -113,21 +115,6 @@ enum CharacterState {
|
|||
CharState_Block
|
||||
};
|
||||
|
||||
enum WeaponType {
|
||||
WeapType_None,
|
||||
|
||||
WeapType_HandToHand,
|
||||
WeapType_OneHand,
|
||||
WeapType_TwoHand,
|
||||
WeapType_TwoWide,
|
||||
WeapType_BowAndArrow,
|
||||
WeapType_Crossbow,
|
||||
WeapType_Thrown,
|
||||
WeapType_PickProbe,
|
||||
|
||||
WeapType_Spell
|
||||
};
|
||||
|
||||
enum UpperBodyCharacterState {
|
||||
UpperCharState_Nothing,
|
||||
UpperCharState_EquipingWeap,
|
||||
|
@ -186,7 +173,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
|||
JumpingState mJumpState;
|
||||
std::string mCurrentJump;
|
||||
|
||||
WeaponType mWeaponType;
|
||||
int mWeaponType;
|
||||
std::string mCurrentWeapon;
|
||||
|
||||
float mAttackStrength;
|
||||
|
@ -212,9 +199,9 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
|||
|
||||
void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false);
|
||||
void refreshHitRecoilAnims(CharacterState& idle);
|
||||
void refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, bool force=false);
|
||||
void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, CharacterState& idle, bool force=false);
|
||||
void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false);
|
||||
void refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force=false);
|
||||
void refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force=false);
|
||||
void refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force=false);
|
||||
|
||||
void clearAnimQueue(bool clearPersistAnims = false);
|
||||
|
||||
|
@ -241,7 +228,11 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
|||
/// @param num if non-nullptr, the chosen animation number will be written here
|
||||
std::string chooseRandomGroup (const std::string& prefix, int* num = nullptr) const;
|
||||
|
||||
bool updateCarriedLeftVisible(WeaponType weaptype) const;
|
||||
bool updateCarriedLeftVisible(int weaptype) const;
|
||||
|
||||
std::string fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask = nullptr);
|
||||
|
||||
std::string getWeaponAnimation(int weaponType) const;
|
||||
|
||||
public:
|
||||
CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
|
||||
|
@ -312,8 +303,6 @@ public:
|
|||
|
||||
void playSwishSound(float attackStrength);
|
||||
};
|
||||
|
||||
MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype);
|
||||
}
|
||||
|
||||
#endif /* GAME_MWMECHANICS_CHARACTER_HPP */
|
||||
|
|
|
@ -311,25 +311,20 @@ namespace MWMechanics
|
|||
applyWerewolfDamageMult(victim, projectile, damage);
|
||||
|
||||
if (attacker == getPlayer())
|
||||
{
|
||||
attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0);
|
||||
|
||||
const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence();
|
||||
|
||||
bool unaware = !sequence.isInCombat()
|
||||
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}");
|
||||
damage *= gmst.find("fCombatKODamageMult")->mValue.getFloat();
|
||||
if (!knockedDown)
|
||||
MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (victim.getClass().getCreatureStats(victim).getKnockedDown())
|
||||
damage *= gmst.find("fCombatKODamageMult")->mValue.getFloat();
|
||||
}
|
||||
|
||||
reduceWeaponCondition(damage, validVictim, weapon, attacker);
|
||||
|
||||
// Apply "On hit" effect of the weapon & projectile
|
||||
|
|
|
@ -490,7 +490,12 @@ namespace MWMechanics
|
|||
|
||||
bool MechanicsManager::isSneaking(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
return mActors.isSneaking(ptr);
|
||||
CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
bool animActive = mActors.isSneaking(ptr);
|
||||
bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak);
|
||||
bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr);
|
||||
return stanceOn && (animActive || inair);
|
||||
}
|
||||
|
||||
void MechanicsManager::rest(double hours, bool sleep)
|
||||
|
@ -1027,8 +1032,7 @@ namespace MWMechanics
|
|||
return true;
|
||||
|
||||
// check if a player tries to pickpocket a target NPC
|
||||
if(ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Sneak)
|
||||
|| target.getClass().getCreatureStats(target).getKnockedDown())
|
||||
if (target.getClass().getCreatureStats(target).getKnockedDown() || isSneaking(ptr))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
@ -1679,9 +1683,7 @@ namespace MWMechanics
|
|||
return false;
|
||||
|
||||
float sneakTerm = 0;
|
||||
if (ptr.getClass().getCreatureStats(ptr).getStance(CreatureStats::Stance_Sneak)
|
||||
&& !MWBase::Environment::get().getWorld()->isSwimming(ptr)
|
||||
&& MWBase::Environment::get().getWorld()->isOnGround(ptr))
|
||||
if (isSneaking(ptr))
|
||||
{
|
||||
static float fSneakSkillMult = store.find("fSneakSkillMult")->mValue.getFloat();
|
||||
static float fSneakBootMult = store.find("fSneakBootMult")->mValue.getFloat();
|
||||
|
@ -1689,7 +1691,7 @@ namespace MWMechanics
|
|||
int agility = stats.getAttribute(ESM::Attribute::Agility).getModified();
|
||||
int luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||
float bootWeight = 0;
|
||||
if (ptr.getClass().isNpc())
|
||||
if (ptr.getClass().isNpc() && MWBase::Environment::get().getWorld()->isOnGround(ptr))
|
||||
{
|
||||
const MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
|
||||
MWWorld::ConstContainerStoreIterator it = inv.getSlot(MWWorld::InventoryStore::Slot_Boots);
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
|
@ -78,10 +76,8 @@ namespace MWMechanics
|
|||
return MWWorld::Ptr(); // none found
|
||||
}
|
||||
|
||||
ObstacleCheck::ObstacleCheck():
|
||||
mPrevX(0) // to see if the moved since last time
|
||||
, mPrevY(0)
|
||||
, mWalkState(State_Norm)
|
||||
ObstacleCheck::ObstacleCheck()
|
||||
: mWalkState(State_Norm)
|
||||
, mStuckDuration(0)
|
||||
, mEvadeDuration(0)
|
||||
, mDistSameSpot(-1) // avoid calculating it each time
|
||||
|
@ -125,21 +121,15 @@ namespace MWMechanics
|
|||
*/
|
||||
void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration)
|
||||
{
|
||||
const ESM::Position pos = actor.getRefData().getPosition();
|
||||
const osg::Vec3f pos = actor.getRefData().getPosition().asVec3();
|
||||
|
||||
if (mDistSameSpot == -1)
|
||||
{
|
||||
const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
|
||||
mDistSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor) + 1.2 * std::max(halfExtents.x(), halfExtents.y());
|
||||
}
|
||||
mDistSameSpot = DIST_SAME_SPOT * actor.getClass().getSpeed(actor);
|
||||
|
||||
const float distSameSpot = mDistSameSpot * duration;
|
||||
const float squaredMovedDistance = (osg::Vec2f(pos.pos[0], pos.pos[1]) - osg::Vec2f(mPrevX, mPrevY)).length2();
|
||||
const bool samePosition = squaredMovedDistance < distSameSpot * distSameSpot;
|
||||
const bool samePosition = (pos - mPrev).length2() < distSameSpot * distSameSpot;
|
||||
|
||||
// update position
|
||||
mPrevX = pos.pos[0];
|
||||
mPrevY = pos.pos[1];
|
||||
mPrev = pos;
|
||||
|
||||
switch(mWalkState)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef OPENMW_MECHANICS_OBSTACLE_H
|
||||
#define OPENMW_MECHANICS_OBSTACLE_H
|
||||
|
||||
#include <osg/Vec3f>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
|
@ -37,9 +39,8 @@ namespace MWMechanics
|
|||
|
||||
private:
|
||||
|
||||
// for checking if we're stuck (ignoring Z axis)
|
||||
float mPrevX;
|
||||
float mPrevY;
|
||||
// for checking if we're stuck
|
||||
osg::Vec3f mPrev;
|
||||
|
||||
// directions to try moving in when get stuck
|
||||
static const float evadeDirections[NUM_EVADE_DIRECTIONS][2];
|
||||
|
|
|
@ -73,6 +73,13 @@ namespace
|
|||
{
|
||||
return sqrDistance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y()));
|
||||
}
|
||||
|
||||
float getPathStepSize(const MWWorld::ConstPtr& actor)
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const auto realHalfExtents = world->getHalfExtents(actor);
|
||||
return 2 * std::max(realHalfExtents.x(), realHalfExtents.y());
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
|
@ -320,8 +327,7 @@ namespace MWMechanics
|
|||
try
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const auto realHalfExtents = world->getHalfExtents(actor); // This may differ from halfExtents argument
|
||||
const auto stepSize = 2 * std::max(realHalfExtents.x(), realHalfExtents.y());
|
||||
const auto stepSize = getPathStepSize(actor);
|
||||
const auto navigator = world->getNavigator();
|
||||
navigator->findPath(halfExtents, stepSize, startPoint, endPoint, flags, out);
|
||||
}
|
||||
|
@ -333,4 +339,39 @@ namespace MWMechanics
|
|||
<< DetourNavigator::WriteFlags {flags} << ")";
|
||||
}
|
||||
}
|
||||
|
||||
void PathFinder::buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents,
|
||||
const DetourNavigator::Flags flags, const float pointTolerance)
|
||||
{
|
||||
if (mPath.empty())
|
||||
return;
|
||||
|
||||
const auto stepSize = getPathStepSize(actor);
|
||||
const auto startPoint = actor.getRefData().getPosition().asVec3();
|
||||
|
||||
if (sqrDistanceIgnoreZ(mPath.front(), startPoint) <= 4 * stepSize * stepSize)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||
std::deque<osg::Vec3f> prePath;
|
||||
navigator->findPath(halfExtents, stepSize, startPoint, mPath.front(), flags, std::back_inserter(prePath));
|
||||
|
||||
while (!prePath.empty() && sqrDistanceIgnoreZ(prePath.front(), startPoint) < stepSize * stepSize)
|
||||
prePath.pop_front();
|
||||
|
||||
while (!prePath.empty() && sqrDistanceIgnoreZ(prePath.back(), mPath.front()) < stepSize * stepSize)
|
||||
prePath.pop_back();
|
||||
|
||||
std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath));
|
||||
}
|
||||
catch (const DetourNavigator::NavigatorException& exception)
|
||||
{
|
||||
Log(Debug::Debug) << "Build path by navigator exception: \"" << exception.what()
|
||||
<< "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase()
|
||||
<< ") from " << startPoint << " to " << mPath.front() << " with flags ("
|
||||
<< DetourNavigator::WriteFlags {flags} << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,6 +84,9 @@ namespace MWMechanics
|
|||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
|
||||
const DetourNavigator::Flags flags);
|
||||
|
||||
void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents,
|
||||
const DetourNavigator::Flags flags, const float pointTolerance);
|
||||
|
||||
/// Remove front point if exist and within tolerance
|
||||
void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance);
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include "npcstats.hpp"
|
||||
#include "actorutil.hpp"
|
||||
#include "aifollow.hpp"
|
||||
#include "weapontype.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
@ -258,7 +259,7 @@ namespace MWMechanics
|
|||
float castChance = 100.f;
|
||||
if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor())
|
||||
{
|
||||
castChance = getSpellSuccessChance(spell, caster, nullptr, false); // Uncapped casting chance
|
||||
castChance = getSpellSuccessChance(spell, caster, nullptr, false, false); // Uncapped casting chance
|
||||
}
|
||||
if (castChance > 0)
|
||||
x *= 50 / castChance;
|
||||
|
@ -936,18 +937,23 @@ namespace MWMechanics
|
|||
mStack = false;
|
||||
|
||||
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
||||
bool isProjectile = false;
|
||||
if (item.getTypeName() == typeid(ESM::Weapon).name())
|
||||
{
|
||||
int type = item.get<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
|
||||
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);
|
||||
|
||||
if (item.getCellRef().getEnchantmentCharge() == -1)
|
||||
item.getCellRef().setEnchantmentCharge(static_cast<float>(enchantment->mData.mCharge));
|
||||
|
||||
if (godmode)
|
||||
castCost = 0;
|
||||
|
||||
if (item.getCellRef().getEnchantmentCharge() < castCost)
|
||||
{
|
||||
if (mCaster == getPlayer())
|
||||
|
@ -975,17 +981,17 @@ namespace MWMechanics
|
|||
item.getCellRef().setEnchantmentCharge(item.getCellRef().getEnchantmentCharge() - castCost);
|
||||
}
|
||||
|
||||
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
|
||||
if (type == ESM::Enchantment::WhenUsed)
|
||||
{
|
||||
if (mCaster == getPlayer())
|
||||
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
|
||||
}
|
||||
else if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
|
||||
else if (type == ESM::Enchantment::CastOnce)
|
||||
{
|
||||
if (!godmode)
|
||||
item.getContainerStore()->remove(item, 1, mCaster);
|
||||
}
|
||||
else if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
||||
else if (type == ESM::Enchantment::WhenStrikes)
|
||||
{
|
||||
if (mCaster == getPlayer())
|
||||
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3);
|
||||
|
@ -993,13 +999,6 @@ namespace MWMechanics
|
|||
|
||||
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
|
||||
|
||||
bool isProjectile = false;
|
||||
if (item.getTypeName() == typeid(ESM::Weapon).name())
|
||||
{
|
||||
const MWWorld::LiveCellRef<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())
|
||||
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
|
||||
|
||||
|
|
|
@ -39,8 +39,8 @@ namespace MWMechanics
|
|||
* @note actor can be an NPC or a creature
|
||||
* @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned.
|
||||
*/
|
||||
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=false);
|
||||
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=false);
|
||||
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
|
||||
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
|
||||
|
||||
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor);
|
||||
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
#include "creaturestats.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "weapontype.hpp"
|
||||
#include "combat.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -122,9 +124,6 @@ namespace MWMechanics
|
|||
return 0.f;
|
||||
}
|
||||
|
||||
if (spell->mData.mCost > stats.getMagicka().getCurrent())
|
||||
return 0.f;
|
||||
|
||||
// Spells don't stack, so early out if the spell is still active on the target
|
||||
int types = getRangeTypes(spell->mEffects);
|
||||
if ((types & Self) && stats.getActiveSpells().isSpellActive(spell->mId))
|
||||
|
@ -379,7 +378,7 @@ namespace MWMechanics
|
|||
|
||||
case ESM::MagicEffect::BoundLongbow:
|
||||
// AI should not summon the bow if there is no suitable ammo.
|
||||
if (rateAmmo(actor, enemy, ESM::Weapon::Arrow) <= 0.f)
|
||||
if (rateAmmo(actor, enemy, getWeaponType(ESM::Weapon::MarksmanBow)->mAmmoType) <= 0.f)
|
||||
return 0.f;
|
||||
break;
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ bool smoothTurn(const MWWorld::Ptr& actor, float targetAngleRadians, int axis, f
|
|||
if (absDiff < epsilonRadians)
|
||||
return true;
|
||||
|
||||
float limit = MAX_VEL_ANGULAR_RADIANS * MWBase::Environment::get().getFrameDuration();
|
||||
float limit = getAngularVelocity(actor.getClass().getSpeed(actor)) * MWBase::Environment::get().getFrameDuration();
|
||||
if (absDiff > limit)
|
||||
diff = osg::sign(diff) * limit;
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <osg/Math>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
|
@ -12,7 +14,12 @@ namespace MWMechanics
|
|||
{
|
||||
|
||||
// Max rotating speed, radian/sec
|
||||
const float MAX_VEL_ANGULAR_RADIANS(10);
|
||||
inline float getAngularVelocity(const float actorSpeed)
|
||||
{
|
||||
const float baseAngluarVelocity = 10;
|
||||
const float baseSpeed = 200;
|
||||
return baseAngluarVelocity * std::max(actorSpeed / baseSpeed, 1.0f);
|
||||
}
|
||||
|
||||
/// configure rotation settings for an actor to reach this target angle (eventually)
|
||||
/// @return have we reached the target angle?
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "aicombataction.hpp"
|
||||
#include "spellpriority.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "weapontype.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
@ -34,14 +35,15 @@ namespace MWMechanics
|
|||
const MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
const MWWorld::Store<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;
|
||||
|
||||
float rating=0.f;
|
||||
static const float fAIMeleeWeaponMult = gmst.find("fAIMeleeWeaponMult")->mValue.getFloat();
|
||||
float ratingMult = fAIMeleeWeaponMult;
|
||||
|
||||
if (weapon->mData.mType >= ESM::Weapon::MarksmanBow && weapon->mData.mType <= ESM::Weapon::MarksmanThrown)
|
||||
if (weapclass != ESM::WeaponType::Melee)
|
||||
{
|
||||
// Underwater ranged combat is impossible
|
||||
if (world->isUnderwater(MWWorld::ConstPtr(actor), 0.75f)
|
||||
|
@ -59,11 +61,11 @@ namespace MWMechanics
|
|||
const float chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f;
|
||||
// We need to account for the fact that thrown weapons have 2x real damage applied to the target
|
||||
// as they're both the weapon and the ammo of the hit
|
||||
if (weapon->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||
if (weapclass == ESM::WeaponType::Thrown)
|
||||
{
|
||||
rating = chop * 2;
|
||||
}
|
||||
else if (weapon->mData.mType >= ESM::Weapon::MarksmanBow)
|
||||
else if (weapclass != ESM::WeaponType::Melee)
|
||||
{
|
||||
rating = chop;
|
||||
}
|
||||
|
@ -76,25 +78,29 @@ namespace MWMechanics
|
|||
|
||||
adjustWeaponDamage(rating, item, actor);
|
||||
|
||||
if (weapon->mData.mType != ESM::Weapon::MarksmanBow && weapon->mData.mType != ESM::Weapon::MarksmanCrossbow)
|
||||
if (weapclass != ESM::WeaponType::Ranged)
|
||||
{
|
||||
resistNormalWeapon(enemy, actor, item, rating);
|
||||
applyWerewolfDamageMult(enemy, item, rating);
|
||||
}
|
||||
else if (weapon->mData.mType == ESM::Weapon::MarksmanBow)
|
||||
else
|
||||
{
|
||||
int ammotype = MWMechanics::getWeaponType(weapon->mData.mType)->mAmmoType;
|
||||
if (ammotype == ESM::Weapon::Arrow)
|
||||
{
|
||||
if (arrowRating <= 0.f)
|
||||
rating = 0.f;
|
||||
else
|
||||
rating += arrowRating;
|
||||
}
|
||||
else if (weapon->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
||||
else if (ammotype == ESM::Weapon::Bolt)
|
||||
{
|
||||
if (boltRating <= 0.f)
|
||||
rating = 0.f;
|
||||
else
|
||||
rating += boltRating;
|
||||
}
|
||||
}
|
||||
|
||||
if (!weapon->mEnchant.empty())
|
||||
{
|
||||
|
@ -102,8 +108,9 @@ namespace MWMechanics
|
|||
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
|
||||
{
|
||||
int castCost = getEffectiveEnchantmentCastCost(static_cast<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);
|
||||
}
|
||||
}
|
||||
|
@ -125,13 +132,13 @@ namespace MWMechanics
|
|||
float chance = getHitChance(actor, enemy, value) / 100.f;
|
||||
rating *= std::min(1.f, std::max(0.01f, chance));
|
||||
|
||||
if (weapon->mData.mType < ESM::Weapon::Arrow)
|
||||
if (weapclass != ESM::WeaponType::Ammo)
|
||||
rating *= weapon->mData.mSpeed;
|
||||
|
||||
return rating * ratingMult;
|
||||
}
|
||||
|
||||
float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType)
|
||||
float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, int ammoType)
|
||||
{
|
||||
float bestAmmoRating = 0.f;
|
||||
if (!actor.getClass().hasInventoryStore(actor))
|
||||
|
@ -152,7 +159,7 @@ namespace MWMechanics
|
|||
return bestAmmoRating;
|
||||
}
|
||||
|
||||
float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, ESM::Weapon::Type ammoType)
|
||||
float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, int ammoType)
|
||||
{
|
||||
MWWorld::Ptr emptyPtr;
|
||||
return rateAmmo(actor, enemy, emptyPtr, ammoType);
|
||||
|
@ -174,8 +181,8 @@ namespace MWMechanics
|
|||
float bonusDamage = 0.f;
|
||||
|
||||
const ESM::Weapon* esmWeap = weapon.get<ESM::Weapon>()->mBase;
|
||||
|
||||
if (esmWeap->mData.mType >= ESM::Weapon::MarksmanBow)
|
||||
int type = esmWeap->mData.mType;
|
||||
if (getWeaponType(type)->mWeaponClass != ESM::WeaponType::Melee)
|
||||
{
|
||||
if (!ammo.isEmpty() && !MWBase::Environment::get().getWorld()->isSwimming(enemy))
|
||||
{
|
||||
|
|
|
@ -10,8 +10,8 @@ namespace MWMechanics
|
|||
float rateWeapon (const MWWorld::Ptr& item, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy,
|
||||
int type=-1, float arrowRating=0.f, float boltRating=0.f);
|
||||
|
||||
float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, ESM::Weapon::Type ammoType);
|
||||
float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, ESM::Weapon::Type ammoType);
|
||||
float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, MWWorld::Ptr &bestAmmo, int ammoType);
|
||||
float rateAmmo(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy, int ammoType);
|
||||
|
||||
float vanillaRateWeaponAndAmmo(const MWWorld::Ptr& weapon, const MWWorld::Ptr& ammo, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy);
|
||||
}
|
||||
|
|
53
apps/openmw/mwmechanics/weapontype.cpp
Normal file
53
apps/openmw/mwmechanics/weapontype.cpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#include "weapontype.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
static const ESM::WeaponType *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])];
|
||||
|
||||
MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype)
|
||||
{
|
||||
MWWorld::InventoryStore &inv = actor.getClass().getInventoryStore(actor);
|
||||
CreatureStats &stats = actor.getClass().getCreatureStats(actor);
|
||||
if(stats.getDrawState() == MWMechanics::DrawState_Spell)
|
||||
{
|
||||
*weaptype = ESM::Weapon::Spell;
|
||||
return inv.end();
|
||||
}
|
||||
|
||||
if(stats.getDrawState() == MWMechanics::DrawState_Weapon)
|
||||
{
|
||||
MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if(weapon == inv.end())
|
||||
*weaptype = ESM::Weapon::HandToHand;
|
||||
else
|
||||
{
|
||||
const std::string &type = weapon->getTypeName();
|
||||
if(type == typeid(ESM::Weapon).name())
|
||||
{
|
||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
|
||||
*weaptype = ref->mBase->mData.mType;
|
||||
}
|
||||
else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name())
|
||||
*weaptype = ESM::Weapon::PickProbe;
|
||||
}
|
||||
|
||||
return weapon;
|
||||
}
|
||||
|
||||
return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
}
|
||||
|
||||
const ESM::WeaponType* getWeaponType(const int weaponType)
|
||||
{
|
||||
std::map<int, ESM::WeaponType>::const_iterator found = sWeaponTypeList.find(weaponType);
|
||||
if (found == sWeaponTypeList.end())
|
||||
{
|
||||
// Use one-handed short blades as fallback
|
||||
return &sWeaponTypeList[0];
|
||||
}
|
||||
|
||||
return &found->second;
|
||||
}
|
||||
}
|
271
apps/openmw/mwmechanics/weapontype.hpp
Normal file
271
apps/openmw/mwmechanics/weapontype.hpp
Normal file
|
@ -0,0 +1,271 @@
|
|||
#ifndef GAME_MWMECHANICS_WEAPONTYPE_H
|
||||
#define GAME_MWMECHANICS_WEAPONTYPE_H
|
||||
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
static std::map<int, ESM::WeaponType> sWeaponTypeList =
|
||||
{
|
||||
{
|
||||
ESM::Weapon::None,
|
||||
{
|
||||
/* short group */ "",
|
||||
/* long group */ "",
|
||||
/* sound ID */ "",
|
||||
/* attach bone */ "",
|
||||
/* sheath bone */ "",
|
||||
/* usage skill */ ESM::Skill::HandToHand,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ 0
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::PickProbe,
|
||||
{
|
||||
/* short group */ "1h",
|
||||
/* long group */ "pickprobe",
|
||||
/* sound ID */ "",
|
||||
/* attach bone */ "",
|
||||
/* sheath bone */ "",
|
||||
/* usage skill */ ESM::Skill::Security,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ 0
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::Spell,
|
||||
{
|
||||
/* short group */ "spell",
|
||||
/* long group */ "spellcast",
|
||||
/* sound ID */ "",
|
||||
/* attach bone */ "",
|
||||
/* sheath bone */ "",
|
||||
/* usage skill */ ESM::Skill::HandToHand,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ ESM::WeaponType::TwoHanded
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::HandToHand,
|
||||
{
|
||||
/* short group */ "hh",
|
||||
/* long group */ "handtohand",
|
||||
/* sound ID */ "",
|
||||
/* attach bone */ "",
|
||||
/* sheath bone */ "",
|
||||
/* usage skill */ ESM::Skill::HandToHand,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ ESM::WeaponType::TwoHanded
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::ShortBladeOneHand,
|
||||
{
|
||||
/* short group */ "1s",
|
||||
/* long group */ "shortbladeonehand",
|
||||
/* sound ID */ "Item Weapon Shortblade",
|
||||
/* attach bone */ "Weapon Bone",
|
||||
/* sheath bone */ "Bip01 ShortBladeOneHand",
|
||||
/* usage skill */ ESM::Skill::ShortBlade,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ ESM::WeaponType::HasHealth
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::LongBladeOneHand,
|
||||
{
|
||||
/* short group */ "1h",
|
||||
/* long group */ "weapononehand",
|
||||
/* sound ID */ "Item Weapon Longblade",
|
||||
/* attach bone */ "Weapon Bone",
|
||||
/* sheath bone */ "Bip01 LongBladeOneHand",
|
||||
/* usage skill */ ESM::Skill::LongBlade,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ ESM::WeaponType::HasHealth
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::BluntOneHand,
|
||||
{
|
||||
/* short group */ "1b",
|
||||
/* long group */ "bluntonehand",
|
||||
/* sound ID */ "Item Weapon Blunt",
|
||||
/* attach bone */ "Weapon Bone",
|
||||
/* sheath bone */ "Bip01 BluntOneHand",
|
||||
/* usage skill */ ESM::Skill::BluntWeapon,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ ESM::WeaponType::HasHealth
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::AxeOneHand,
|
||||
{
|
||||
/* short group */ "1b",
|
||||
/* long group */ "bluntonehand",
|
||||
/* sound ID */ "Item Weapon Blunt",
|
||||
/* attach bone */ "Weapon Bone",
|
||||
/* sheath bone */ "Bip01 LongBladeOneHand",
|
||||
/* usage skill */ ESM::Skill::Axe,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ ESM::WeaponType::HasHealth
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::LongBladeTwoHand,
|
||||
{
|
||||
/* short group */ "2c",
|
||||
/* long group */ "weapontwohand",
|
||||
/* sound ID */ "Item Weapon Longblade",
|
||||
/* attach bone */ "Weapon Bone",
|
||||
/* sheath bone */ "Bip01 LongBladeTwoClose",
|
||||
/* usage skill */ ESM::Skill::LongBlade,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::AxeTwoHand,
|
||||
{
|
||||
/* short group */ "2b",
|
||||
/* long group */ "blunttwohand",
|
||||
/* sound ID */ "Item Weapon Blunt",
|
||||
/* attach bone */ "Weapon Bone",
|
||||
/* sheath bone */ "Bip01 AxeTwoClose",
|
||||
/* usage skill */ ESM::Skill::Axe,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::BluntTwoClose,
|
||||
{
|
||||
/* short group */ "2b",
|
||||
/* long group */ "blunttwohand",
|
||||
/* sound ID */ "Item Weapon Blunt",
|
||||
/* attach bone */ "Weapon Bone",
|
||||
/* sheath bone */ "Bip01 BluntTwoClose",
|
||||
/* usage skill */ ESM::Skill::BluntWeapon,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::BluntTwoWide,
|
||||
{
|
||||
/* short group */ "2w",
|
||||
/* long group */ "weapontwowide",
|
||||
/* sound ID */ "Item Weapon Blunt",
|
||||
/* attach bone */ "Weapon Bone",
|
||||
/* sheath bone */ "Bip01 BluntTwoWide",
|
||||
/* usage skill */ ESM::Skill::BluntWeapon,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::SpearTwoWide,
|
||||
{
|
||||
/* short group */ "2w",
|
||||
/* long group */ "weapontwowide",
|
||||
/* sound ID */ "Item Weapon Spear",
|
||||
/* attach bone */ "Weapon Bone",
|
||||
/* sheath bone */ "Bip01 SpearTwoWide",
|
||||
/* usage skill */ ESM::Skill::Spear,
|
||||
/* weapon class*/ ESM::WeaponType::Melee,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::MarksmanBow,
|
||||
{
|
||||
/* short group */ "bow",
|
||||
/* long group */ "bowandarrow",
|
||||
/* sound ID */ "Item Weapon Bow",
|
||||
/* attach bone */ "Weapon Bone Left",
|
||||
/* sheath bone */ "Bip01 MarksmanBow",
|
||||
/* usage skill */ ESM::Skill::Marksman,
|
||||
/* weapon class*/ ESM::WeaponType::Ranged,
|
||||
/* ammo type */ ESM::Weapon::Arrow,
|
||||
/* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::MarksmanCrossbow,
|
||||
{
|
||||
/* short group */ "crossbow",
|
||||
/* long group */ "crossbow",
|
||||
/* sound ID */ "Item Weapon Crossbow",
|
||||
/* attach bone */ "Weapon Bone",
|
||||
/* sheath bone */ "Bip01 MarksmanCrossbow",
|
||||
/* usage skill */ ESM::Skill::Marksman,
|
||||
/* weapon class*/ ESM::WeaponType::Ranged,
|
||||
/* ammo type */ ESM::Weapon::Bolt,
|
||||
/* flags */ ESM::WeaponType::HasHealth|ESM::WeaponType::TwoHanded
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::MarksmanThrown,
|
||||
{
|
||||
/* short group */ "1t",
|
||||
/* long group */ "throwweapon",
|
||||
/* sound ID */ "Item Weapon Blunt",
|
||||
/* attach bone */ "Weapon Bone",
|
||||
/* sheath bone */ "Bip01 MarksmanThrown",
|
||||
/* usage skill */ ESM::Skill::Marksman,
|
||||
/* weapon class*/ ESM::WeaponType::Thrown,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ 0
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::Arrow,
|
||||
{
|
||||
/* short group */ "",
|
||||
/* long group */ "",
|
||||
/* sound ID */ "Item Ammo",
|
||||
/* attach bone */ "ArrowBone",
|
||||
/* sheath bone */ "",
|
||||
/* usage skill */ ESM::Skill::Marksman,
|
||||
/* weapon class*/ ESM::WeaponType::Ammo,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ 0
|
||||
}
|
||||
},
|
||||
{
|
||||
ESM::Weapon::Bolt,
|
||||
{
|
||||
/* short group */ "",
|
||||
/* long group */ "",
|
||||
/* sound ID */ "Item Ammo",
|
||||
/* attach bone */ "ArrowBone",
|
||||
/* sheath bone */ "",
|
||||
/* usage skill */ ESM::Skill::Marksman,
|
||||
/* weapon class*/ ESM::WeaponType::Ammo,
|
||||
/* ammo type */ ESM::Weapon::None,
|
||||
/* flags */ 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MWWorld::ContainerStoreIterator getActiveWeapon(MWWorld::Ptr actor, int *weaptype);
|
||||
|
||||
const ESM::WeaponType* getWeaponType(const int weaponType);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -235,7 +235,7 @@ void MechanicsHelper::resetAttack(Attack* attack)
|
|||
|
||||
bool MechanicsHelper::getSpellSuccess(std::string spellId, const MWWorld::Ptr& caster)
|
||||
{
|
||||
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)
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
|
||||
#include "../mwrender/bulletdebugdraw.hpp"
|
||||
|
||||
|
@ -326,7 +327,30 @@ namespace MWPhysics
|
|||
if (movement.z() > 0 && ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel)
|
||||
velocity = osg::Vec3f(0,0,1) * 25;
|
||||
|
||||
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
|
||||
if (MWBase::Environment::get().getWorld()->isInStorm())
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
#include "vismask.hpp"
|
||||
|
||||
|
@ -51,8 +52,6 @@ ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group>
|
|||
|
||||
// Make sure we cleaned object from effects, just in cast if we re-use node
|
||||
removeEffects();
|
||||
|
||||
mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
||||
}
|
||||
|
||||
ActorAnimation::~ActorAnimation()
|
||||
|
@ -79,12 +78,12 @@ PartHolderPtr ActorAnimation::getWeaponPart(const std::string& model, const std:
|
|||
return PartHolderPtr();
|
||||
|
||||
if (enchantedGlow)
|
||||
addGlow(instance, *glowColor);
|
||||
mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor);
|
||||
|
||||
return PartHolderPtr(new PartHolder(instance));
|
||||
}
|
||||
|
||||
osg::Group* ActorAnimation::getBoneByName(std::string boneName)
|
||||
osg::Group* ActorAnimation::getBoneByName(const std::string& boneName)
|
||||
{
|
||||
if (!mObjectRoot)
|
||||
return nullptr;
|
||||
|
@ -105,93 +104,13 @@ std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr&
|
|||
if(type == typeid(ESM::Weapon).name())
|
||||
{
|
||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon.get<ESM::Weapon>();
|
||||
ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType;
|
||||
return getHolsteredWeaponBoneName(weaponType);
|
||||
int weaponType = ref->mBase->mData.mType;
|
||||
return MWMechanics::getWeaponType(weaponType)->mSheathingBone;
|
||||
}
|
||||
|
||||
return boneName;
|
||||
}
|
||||
|
||||
std::string ActorAnimation::getHolsteredWeaponBoneName(const unsigned int weaponType)
|
||||
{
|
||||
std::string boneName;
|
||||
|
||||
switch(weaponType)
|
||||
{
|
||||
case ESM::Weapon::ShortBladeOneHand:
|
||||
boneName = "Bip01 ShortBladeOneHand";
|
||||
break;
|
||||
case ESM::Weapon::LongBladeOneHand:
|
||||
boneName = "Bip01 LongBladeOneHand";
|
||||
break;
|
||||
case ESM::Weapon::BluntOneHand:
|
||||
boneName = "Bip01 BluntOneHand";
|
||||
break;
|
||||
case ESM::Weapon::AxeOneHand:
|
||||
boneName = "Bip01 LongBladeOneHand";
|
||||
break;
|
||||
case ESM::Weapon::LongBladeTwoHand:
|
||||
boneName = "Bip01 LongBladeTwoClose";
|
||||
break;
|
||||
case ESM::Weapon::BluntTwoClose:
|
||||
boneName = "Bip01 BluntTwoClose";
|
||||
break;
|
||||
case ESM::Weapon::AxeTwoHand:
|
||||
boneName = "Bip01 AxeTwoClose";
|
||||
break;
|
||||
case ESM::Weapon::BluntTwoWide:
|
||||
boneName = "Bip01 BluntTwoWide";
|
||||
break;
|
||||
case ESM::Weapon::SpearTwoWide:
|
||||
boneName = "Bip01 SpearTwoWide";
|
||||
break;
|
||||
case ESM::Weapon::MarksmanBow:
|
||||
boneName = "Bip01 MarksmanBow";
|
||||
break;
|
||||
case ESM::Weapon::MarksmanCrossbow:
|
||||
boneName = "Bip01 MarksmanCrossbow";
|
||||
break;
|
||||
case ESM::Weapon::MarksmanThrown:
|
||||
boneName = "Bip01 MarksmanThrown";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return boneName;
|
||||
}
|
||||
|
||||
void ActorAnimation::injectWeaponBones()
|
||||
{
|
||||
if (!mResourceSystem->getVFS()->exists("meshes\\xbase_anim_sh.nif"))
|
||||
{
|
||||
mWeaponSheathing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
osg::ref_ptr<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)
|
||||
{
|
||||
if (node == nullptr)
|
||||
|
@ -205,7 +124,8 @@ void ActorAnimation::resetControllers(osg::Node* node)
|
|||
|
||||
void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
|
||||
{
|
||||
if (!mWeaponSheathing)
|
||||
static const bool weaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
||||
if (!weaponSheathing)
|
||||
return;
|
||||
|
||||
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||
|
@ -219,7 +139,8 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
|
|||
return;
|
||||
|
||||
// Since throwing weapons stack themselves, do not show such weapon itself
|
||||
if (weapon->get<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;
|
||||
|
||||
std::string mesh = weapon->getClass().getModel(*weapon);
|
||||
|
@ -238,7 +159,7 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
|
|||
{
|
||||
if (showHolsteredWeapons)
|
||||
{
|
||||
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
|
||||
osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon);
|
||||
mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor);
|
||||
if (mScabbard)
|
||||
resetControllers(mScabbard->getNode());
|
||||
|
@ -271,15 +192,16 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
|
|||
|
||||
if (isEnchanted)
|
||||
{
|
||||
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
|
||||
addGlow(weaponNode, glowColor);
|
||||
osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon);
|
||||
mGlowUpdater = SceneUtil::addEnchantedGlow(weaponNode, mResourceSystem, glowColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActorAnimation::updateQuiver()
|
||||
{
|
||||
if (!mWeaponSheathing)
|
||||
static const bool weaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
||||
if (!weaponSheathing)
|
||||
return;
|
||||
|
||||
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||
|
@ -303,10 +225,12 @@ void ActorAnimation::updateQuiver()
|
|||
bool suitableAmmo = false;
|
||||
MWWorld::ConstContainerStoreIterator ammo = weapon;
|
||||
unsigned int ammoCount = 0;
|
||||
if (weapon->get<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();
|
||||
osg::Group* throwingWeaponNode = getBoneByName("Weapon Bone");
|
||||
osg::Group* throwingWeaponNode = getBoneByName(weaponType->mAttachBone);
|
||||
if (throwingWeaponNode && throwingWeaponNode->getNumChildren())
|
||||
ammoCount--;
|
||||
|
||||
|
@ -323,10 +247,7 @@ void ActorAnimation::updateQuiver()
|
|||
if (arrowAttached)
|
||||
ammoCount--;
|
||||
|
||||
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
||||
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;
|
||||
suitableAmmo = ammo->get<ESM::Weapon>()->mBase->mData.mType == weaponType->mAmmoType;
|
||||
}
|
||||
|
||||
if (!suitableAmmo)
|
||||
|
@ -347,14 +268,14 @@ void ActorAnimation::updateQuiver()
|
|||
}
|
||||
|
||||
// Add new ones
|
||||
osg::Vec4f glowColor = getEnchantmentColor(*ammo);
|
||||
osg::Vec4f glowColor = ammo->getClass().getEnchantmentColor(*ammo);
|
||||
std::string model = ammo->getClass().getModel(*ammo);
|
||||
for (unsigned int i=0; i<ammoCount; ++i)
|
||||
{
|
||||
osg::ref_ptr<osg::Group> arrowNode = ammoNode->getChild(i)->asGroup();
|
||||
osg::ref_ptr<osg::Node> arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode);
|
||||
if (!ammo->getClass().getEnchantment(*ammo).empty())
|
||||
addGlow(arrow, glowColor);
|
||||
mGlowUpdater = SceneUtil::addEnchantedGlow(arrow, mResourceSystem, glowColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -379,7 +300,8 @@ void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/)
|
|||
return;
|
||||
|
||||
MWWorld::ConstContainerStoreIterator ammo = inv.end();
|
||||
if (weapon->get<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;
|
||||
else
|
||||
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||
|
@ -412,7 +334,8 @@ void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/)
|
|||
return;
|
||||
|
||||
MWWorld::ConstContainerStoreIterator ammo = inv.end();
|
||||
if (weapon->get<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;
|
||||
else
|
||||
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||
|
|
|
@ -40,13 +40,10 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener
|
|||
virtual bool isArrowAttached() const { return false; }
|
||||
|
||||
protected:
|
||||
bool mWeaponSheathing;
|
||||
osg::Group* getBoneByName(std::string boneName);
|
||||
osg::Group* getBoneByName(const std::string& boneName);
|
||||
virtual void updateHolsteredWeapon(bool showHolsteredWeapons);
|
||||
virtual void injectWeaponBones();
|
||||
virtual void updateQuiver();
|
||||
virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon);
|
||||
virtual std::string getHolsteredWeaponBoneName(const unsigned int weaponType);
|
||||
virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor);
|
||||
virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename)
|
||||
{
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
#include <iomanip>
|
||||
#include <limits>
|
||||
|
||||
#include <osg/TexGen>
|
||||
#include <osg/TexEnvCombine>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/BlendFunc>
|
||||
#include <osg/Material>
|
||||
|
@ -16,18 +14,18 @@
|
|||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/resource/keyframemanager.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
|
||||
#include <components/misc/constants.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
|
||||
#include <components/nifosg/nifloader.hpp> // KeyframeHolder
|
||||
#include <components/nifosg/controller.hpp>
|
||||
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#include <components/sceneutil/actorutil.hpp>
|
||||
#include <components/sceneutil/statesetupdater.hpp>
|
||||
#include <components/sceneutil/visitor.hpp>
|
||||
#include <components/sceneutil/lightmanager.hpp>
|
||||
|
@ -238,6 +236,28 @@ namespace
|
|||
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
|
||||
{
|
||||
public:
|
||||
|
@ -519,135 +539,6 @@ namespace MWRender
|
|||
float mAlpha;
|
||||
};
|
||||
|
||||
class GlowUpdater : public SceneUtil::StateSetUpdater
|
||||
{
|
||||
public:
|
||||
GlowUpdater(int texUnit, const osg::Vec4f& color, const std::vector<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
|
||||
{
|
||||
osg::ref_ptr<const NifOsg::KeyframeHolder> mKeyframes;
|
||||
|
@ -1469,8 +1360,62 @@ namespace MWRender
|
|||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
if (inject)
|
||||
{
|
||||
injectCustomBones(created, defaultSkeleton, resourceSystem);
|
||||
injectCustomBones(created, model, resourceSystem);
|
||||
}
|
||||
|
||||
SceneUtil::CleanObjectRootVisitor removeDrawableVisitor;
|
||||
created->accept(removeDrawableVisitor);
|
||||
removeDrawableVisitor.remove();
|
||||
|
@ -1492,7 +1443,17 @@ namespace MWRender
|
|||
return sceneMgr->createInstance(found->second);
|
||||
}
|
||||
else
|
||||
return sceneMgr->getInstance(model);
|
||||
{
|
||||
osg::ref_ptr<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)
|
||||
|
@ -1514,9 +1475,45 @@ namespace MWRender
|
|||
mAccumRoot = nullptr;
|
||||
mAccumCtrl = nullptr;
|
||||
|
||||
static const bool useAdditionalSources = Settings::Manager::getBool ("use additional anim sources", "Game");
|
||||
std::string defaultSkeleton;
|
||||
bool inject = false;
|
||||
|
||||
if (useAdditionalSources && mPtr.getClass().isActor())
|
||||
{
|
||||
if (isCreature)
|
||||
{
|
||||
MWWorld::LiveCellRef<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)
|
||||
{
|
||||
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);
|
||||
mObjectRoot = created->asGroup();
|
||||
if (!mObjectRoot)
|
||||
|
@ -1532,7 +1529,7 @@ namespace MWRender
|
|||
}
|
||||
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());
|
||||
if (!skel)
|
||||
{
|
||||
|
@ -1574,25 +1571,6 @@ namespace MWRender
|
|||
return mObjectRoot.get();
|
||||
}
|
||||
|
||||
class FindLowestUnusedTexUnitVisitor : public osg::NodeVisitor
|
||||
{
|
||||
public:
|
||||
FindLowestUnusedTexUnitVisitor()
|
||||
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
||||
, mLowestUnusedTexUnit(0)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void apply(osg::Node& node)
|
||||
{
|
||||
if (osg::StateSet* stateset = node.getStateSet())
|
||||
mLowestUnusedTexUnit = std::max(mLowestUnusedTexUnit, int(stateset->getTextureAttributeList().size()));
|
||||
|
||||
traverse(node);
|
||||
}
|
||||
int mLowestUnusedTexUnit;
|
||||
};
|
||||
|
||||
void Animation::addSpellCastGlow(const ESM::MagicEffect *effect, float glowDuration)
|
||||
{
|
||||
osg::Vec4f glowColor(1,1,1,1);
|
||||
|
@ -1611,78 +1589,10 @@ namespace MWRender
|
|||
mGlowUpdater->setDuration(glowDuration);
|
||||
}
|
||||
else
|
||||
addGlow(mObjectRoot, glowColor, glowDuration);
|
||||
mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, glowColor, glowDuration);
|
||||
}
|
||||
}
|
||||
|
||||
void Animation::addGlow(osg::ref_ptr<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)
|
||||
{
|
||||
bool exterior = mPtr.isInCell() && mPtr.getCell()->getCell()->isExterior();
|
||||
|
@ -2001,7 +1911,7 @@ namespace MWRender
|
|||
addAnimSource(model, model);
|
||||
|
||||
if (!ptr.getClass().getEnchantment(ptr).empty())
|
||||
addGlow(mObjectRoot, getEnchantmentColor(ptr));
|
||||
mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr));
|
||||
}
|
||||
if (ptr.getTypeName() == typeid(ESM::Light).name() && allowLight)
|
||||
addExtraLight(getOrCreateObjectRoot(), ptr.get<ESM::Light>()->mBase);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
#include <components/sceneutil/controller.hpp>
|
||||
#include <components/sceneutil/util.hpp>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
@ -34,7 +35,6 @@ namespace MWRender
|
|||
|
||||
class ResetAccumRootCallback;
|
||||
class RotateController;
|
||||
class GlowUpdater;
|
||||
class TransparencyUpdater;
|
||||
|
||||
class EffectAnimationTime : public SceneUtil::ControllerSource
|
||||
|
@ -266,7 +266,7 @@ protected:
|
|||
bool mHasMagicEffects;
|
||||
|
||||
osg::ref_ptr<SceneUtil::LightSource> mGlowLight;
|
||||
osg::ref_ptr<GlowUpdater> mGlowUpdater;
|
||||
osg::ref_ptr<SceneUtil::GlowUpdater> mGlowUpdater;
|
||||
osg::ref_ptr<TransparencyUpdater> mTransparencyUpdater;
|
||||
|
||||
float mAlpha;
|
||||
|
@ -330,10 +330,6 @@ protected:
|
|||
*/
|
||||
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.
|
||||
virtual void setRenderBin();
|
||||
|
||||
|
|
|
@ -48,7 +48,6 @@ namespace MWRender
|
|||
mAnimation(nullptr),
|
||||
mFirstPersonView(true),
|
||||
mPreviewMode(false),
|
||||
mFreeLook(true),
|
||||
mNearest(30.f),
|
||||
mFurthest(800.f),
|
||||
mIsNearest(false),
|
||||
|
@ -393,11 +392,6 @@ namespace MWRender
|
|||
camera = focal + offset;
|
||||
}
|
||||
|
||||
void Camera::togglePlayerLooking(bool enable)
|
||||
{
|
||||
mFreeLook = enable;
|
||||
}
|
||||
|
||||
bool Camera::isVanityOrPreviewModeEnabled()
|
||||
{
|
||||
return mPreviewMode || mVanity.enabled;
|
||||
|
|
|
@ -37,7 +37,6 @@ namespace MWRender
|
|||
|
||||
bool mFirstPersonView;
|
||||
bool mPreviewMode;
|
||||
bool mFreeLook;
|
||||
float mNearest;
|
||||
float mFurthest;
|
||||
bool mIsNearest;
|
||||
|
@ -119,8 +118,6 @@ namespace MWRender
|
|||
/// Stores focal and camera world positions in passed arguments
|
||||
void getPosition(osg::Vec3f &focal, osg::Vec3f &camera);
|
||||
|
||||
void togglePlayerLooking(bool enable);
|
||||
|
||||
bool isVanityOrPreviewModeEnabled();
|
||||
|
||||
bool isNearest();
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
#include "npcanimation.hpp"
|
||||
#include "vismask.hpp"
|
||||
|
@ -290,55 +291,36 @@ namespace MWRender
|
|||
|
||||
MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter);
|
||||
MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
std::string groupname;
|
||||
std::string groupname = "inventoryhandtohand";
|
||||
bool showCarriedLeft = true;
|
||||
if(iter == inv.end())
|
||||
groupname = "inventoryhandtohand";
|
||||
else
|
||||
if(iter != inv.end())
|
||||
{
|
||||
const std::string &typeName = iter->getTypeName();
|
||||
if(typeName == typeid(ESM::Lockpick).name() || typeName == typeid(ESM::Probe).name())
|
||||
groupname = "inventoryweapononehand";
|
||||
else if(typeName == typeid(ESM::Weapon).name())
|
||||
if(iter->getTypeName() == typeid(ESM::Weapon).name())
|
||||
{
|
||||
MWWorld::LiveCellRef<ESM::Weapon> *ref = iter->get<ESM::Weapon>();
|
||||
|
||||
int type = ref->mBase->mData.mType;
|
||||
if(type == ESM::Weapon::ShortBladeOneHand ||
|
||||
type == ESM::Weapon::LongBladeOneHand ||
|
||||
type == ESM::Weapon::BluntOneHand ||
|
||||
type == ESM::Weapon::AxeOneHand ||
|
||||
type == ESM::Weapon::MarksmanThrown)
|
||||
{
|
||||
groupname = "inventoryweapononehand";
|
||||
}
|
||||
else if(type == ESM::Weapon::MarksmanCrossbow ||
|
||||
type == ESM::Weapon::MarksmanBow)
|
||||
{
|
||||
groupname = "inventoryweapononehand";
|
||||
showCarriedLeft = false;
|
||||
}
|
||||
else if(type == ESM::Weapon::LongBladeTwoHand ||
|
||||
type == ESM::Weapon::BluntTwoClose ||
|
||||
type == ESM::Weapon::AxeTwoHand)
|
||||
{
|
||||
groupname = "inventoryweapontwohand";
|
||||
showCarriedLeft = false;
|
||||
}
|
||||
else if(type == ESM::Weapon::BluntTwoWide ||
|
||||
type == ESM::Weapon::SpearTwoWide)
|
||||
{
|
||||
groupname = "inventoryweapontwowide";
|
||||
showCarriedLeft = false;
|
||||
}
|
||||
const ESM::WeaponType* weaponInfo = MWMechanics::getWeaponType(type);
|
||||
showCarriedLeft = !(weaponInfo->mFlags & ESM::WeaponType::TwoHanded);
|
||||
|
||||
std::string inventoryGroup = weaponInfo->mLongGroup;
|
||||
inventoryGroup = "inventory" + inventoryGroup;
|
||||
|
||||
// We still should use one-handed animation as fallback
|
||||
if (mAnimation->hasAnimation(inventoryGroup))
|
||||
groupname = inventoryGroup;
|
||||
else
|
||||
{
|
||||
groupname = "inventoryhandtohand";
|
||||
showCarriedLeft = false;
|
||||
}
|
||||
}
|
||||
static const std::string oneHandFallback = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeOneHand)->mLongGroup;
|
||||
static const std::string twoHandFallback = "inventory" + MWMechanics::getWeaponType(ESM::Weapon::LongBladeTwoHand)->mLongGroup;
|
||||
|
||||
// For real two-handed melee weapons use 2h swords animations as fallback, otherwise use the 1h ones
|
||||
if (weaponInfo->mFlags & ESM::WeaponType::TwoHanded && weaponInfo->mWeaponClass == ESM::WeaponType::Melee)
|
||||
groupname = twoHandFallback;
|
||||
else
|
||||
groupname = "inventoryhandtohand";
|
||||
groupname = oneHandFallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mAnimation->showCarriedLeft(showCarriedLeft);
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
namespace MWRender
|
||||
|
@ -50,9 +52,6 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const
|
|||
|
||||
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
|
||||
{
|
||||
if (mWeaponSheathing)
|
||||
injectWeaponBones();
|
||||
|
||||
addAnimSource("meshes\\xbase_anim.nif", model);
|
||||
}
|
||||
addAnimSource(model, model);
|
||||
|
@ -115,7 +114,22 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
|||
|
||||
std::string bonename;
|
||||
if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
|
||||
{
|
||||
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
|
||||
bonename = "Shield Bone";
|
||||
|
||||
|
@ -132,7 +146,7 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
|||
scene.reset(new PartHolder(attached));
|
||||
|
||||
if (!item.getClass().getEnchantment(item).empty())
|
||||
addGlow(attached, getEnchantmentColor(item));
|
||||
mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, item.getClass().getEnchantmentColor(item));
|
||||
|
||||
// Crossbows start out with a bolt attached
|
||||
// FIXME: code duplicated from NpcAnimation
|
||||
|
@ -140,8 +154,9 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
|||
item.getTypeName() == typeid(ESM::Weapon).name() &&
|
||||
item.get<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);
|
||||
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();
|
||||
else
|
||||
mAmmunition.reset();
|
||||
|
@ -173,6 +188,16 @@ bool CreatureWeaponAnimation::isArrowAttached() const
|
|||
void CreatureWeaponAnimation::attachArrow()
|
||||
{
|
||||
WeaponAnimation::attachArrow(mPtr);
|
||||
|
||||
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||
if (ammo != inv.end() && !ammo->getClass().getEnchantment(*ammo).empty())
|
||||
{
|
||||
osg::Group* bone = getArrowBone();
|
||||
if (bone != nullptr && bone->getNumChildren())
|
||||
SceneUtil::addEnchantedGlow(bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo));
|
||||
}
|
||||
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
|
@ -187,7 +212,19 @@ osg::Group *CreatureWeaponAnimation::getArrowBone()
|
|||
if (!mWeapon)
|
||||
return nullptr;
|
||||
|
||||
SceneUtil::FindByNameVisitor findVisitor ("ArrowBone");
|
||||
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||
return nullptr;
|
||||
|
||||
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
|
||||
return nullptr;
|
||||
|
||||
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
|
||||
int ammoType = MWMechanics::getWeaponType(type)->mAmmoType;
|
||||
|
||||
SceneUtil::FindByNameVisitor findVisitor (MWMechanics::getWeaponType(ammoType)->mAttachBone);
|
||||
|
||||
mWeapon->getNode()->accept(findVisitor);
|
||||
|
||||
return findVisitor.mFoundNode;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
@ -275,7 +276,7 @@ static NpcAnimation::PartBoneMap createPartListMap()
|
|||
result.insert(std::make_pair(ESM::PRT_LLeg, "Left Upper Leg"));
|
||||
result.insert(std::make_pair(ESM::PRT_RPauldron, "Right Clavicle"));
|
||||
result.insert(std::make_pair(ESM::PRT_LPauldron, "Left Clavicle"));
|
||||
result.insert(std::make_pair(ESM::PRT_Weapon, "Weapon Bone"));
|
||||
result.insert(std::make_pair(ESM::PRT_Weapon, "Weapon Bone")); // Fallback. The real node name depends on the current weapon type.
|
||||
result.insert(std::make_pair(ESM::PRT_Tail, "Tail"));
|
||||
return result;
|
||||
}
|
||||
|
@ -318,12 +319,6 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
|
|||
if(mViewMode == viewMode)
|
||||
return;
|
||||
|
||||
// Disable weapon sheathing in the 1st-person mode
|
||||
if (viewMode == VM_FirstPerson)
|
||||
mWeaponSheathing = false;
|
||||
else
|
||||
mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
||||
|
||||
mViewMode = viewMode;
|
||||
MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale()); // apply race height after view change
|
||||
|
||||
|
@ -485,9 +480,6 @@ void NpcAnimation::updateNpcBase()
|
|||
|
||||
setObjectRoot(smodel, true, true, false);
|
||||
|
||||
if (mWeaponSheathing)
|
||||
injectWeaponBones();
|
||||
|
||||
updateParts();
|
||||
|
||||
if(!is1stPerson)
|
||||
|
@ -572,7 +564,7 @@ void NpcAnimation::updateParts()
|
|||
|
||||
int prio = 1;
|
||||
bool enchantedGlow = !store->getClass().getEnchantment(*store).empty();
|
||||
osg::Vec4f glowColor = getEnchantmentColor(*store);
|
||||
osg::Vec4f glowColor = store->getClass().getEnchantmentColor(*store);
|
||||
if(store->getTypeName() == typeid(ESM::Clothing).name())
|
||||
{
|
||||
prio = ((slotlist[i].mBasePriority+1)<<1) + 0;
|
||||
|
@ -664,7 +656,7 @@ PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const st
|
|||
|
||||
osg::ref_ptr<osg::Node> attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, found->second);
|
||||
if (enchantedGlow)
|
||||
addGlow(attached, *glowColor);
|
||||
mGlowUpdater = SceneUtil::addEnchantedGlow(attached, mResourceSystem, *glowColor);
|
||||
|
||||
return PartHolderPtr(new PartHolder(attached));
|
||||
}
|
||||
|
@ -745,7 +737,26 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g
|
|||
mPartPriorities[type] = priority;
|
||||
try
|
||||
{
|
||||
const std::string& bonename = sPartList.at(type);
|
||||
std::string bonename = sPartList.at(type);
|
||||
if (type == ESM::PRT_Weapon)
|
||||
{
|
||||
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if(weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name())
|
||||
{
|
||||
int weaponType = weapon->get<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
|
||||
const std::string bonefilter = (type == ESM::PRT_Hair) ? "hair" : bonename;
|
||||
mObjectParts[type] = insertBoundedPart(mesh, bonename, bonefilter, enchantedGlow, glowColor);
|
||||
|
@ -897,7 +908,7 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
|||
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if(weapon != inv.end())
|
||||
{
|
||||
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
|
||||
osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon);
|
||||
std::string mesh = weapon->getClass().getModel(*weapon);
|
||||
addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1,
|
||||
mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor);
|
||||
|
@ -906,8 +917,9 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
|||
if (weapon->getTypeName() == typeid(ESM::Weapon).name() &&
|
||||
weapon->get<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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -931,7 +943,7 @@ void NpcAnimation::showCarriedLeft(bool show)
|
|||
MWWorld::ConstContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
|
||||
if(show && iter != inv.end())
|
||||
{
|
||||
osg::Vec4f glowColor = getEnchantmentColor(*iter);
|
||||
osg::Vec4f glowColor = iter->getClass().getEnchantmentColor(*iter);
|
||||
std::string mesh = iter->getClass().getModel(*iter);
|
||||
if (addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1,
|
||||
mesh, !iter->getClass().getEnchantment(*iter).empty(), &glowColor))
|
||||
|
@ -947,6 +959,16 @@ void NpcAnimation::showCarriedLeft(bool show)
|
|||
void NpcAnimation::attachArrow()
|
||||
{
|
||||
WeaponAnimation::attachArrow(mPtr);
|
||||
|
||||
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||
if (ammo != inv.end() && !ammo->getClass().getEnchantment(*ammo).empty())
|
||||
{
|
||||
osg::Group* bone = getArrowBone();
|
||||
if (bone != nullptr && bone->getNumChildren())
|
||||
SceneUtil::addEnchantedGlow(bone->getChild(0), mResourceSystem, ammo->getClass().getEnchantmentColor(*ammo));
|
||||
}
|
||||
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
|
@ -962,7 +984,15 @@ osg::Group* NpcAnimation::getArrowBone()
|
|||
if (!part)
|
||||
return nullptr;
|
||||
|
||||
SceneUtil::FindByNameVisitor findVisitor ("ArrowBone");
|
||||
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
|
||||
return nullptr;
|
||||
|
||||
int type = weapon->get<ESM::Weapon>()->mBase->mData.mType;
|
||||
int ammoType = MWMechanics::getWeaponType(type)->mAmmoType;
|
||||
|
||||
SceneUtil::FindByNameVisitor findVisitor (MWMechanics::getWeaponType(ammoType)->mAttachBone);
|
||||
part->getNode()->accept(findVisitor);
|
||||
|
||||
return findVisitor.mFoundNode;
|
||||
|
|
|
@ -1353,11 +1353,6 @@ namespace MWRender
|
|||
mCamera->allowVanityMode(allow);
|
||||
}
|
||||
|
||||
void RenderingManager::togglePlayerLooking(bool enable)
|
||||
{
|
||||
mCamera->togglePlayerLooking(enable);
|
||||
}
|
||||
|
||||
void RenderingManager::changeVanityModeScale(float factor)
|
||||
{
|
||||
if(mCamera->isVanityOrPreviewModeEnabled())
|
||||
|
|
|
@ -208,7 +208,6 @@ namespace MWRender
|
|||
void togglePreviewMode(bool enable);
|
||||
bool toggleVanityMode(bool enable);
|
||||
void allowVanityMode(bool allow);
|
||||
void togglePlayerLooking(bool enable);
|
||||
void changeVanityModeScale(float factor);
|
||||
|
||||
/// temporarily override the field of view with given value.
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/combat.hpp"
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
#include "animation.hpp"
|
||||
#include "rotatecontroller.hpp"
|
||||
|
@ -77,8 +78,10 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor)
|
|||
return;
|
||||
if (weaponSlot->getTypeName() != typeid(ESM::Weapon).name())
|
||||
return;
|
||||
int weaponType = weaponSlot->get<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);
|
||||
if(!soundid.empty())
|
||||
|
@ -88,7 +91,7 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor)
|
|||
}
|
||||
showWeapon(true);
|
||||
}
|
||||
else if (weaponType == ESM::Weapon::MarksmanBow || weaponType == ESM::Weapon::MarksmanCrossbow)
|
||||
else if (weapclass == ESM::WeaponType::Ranged)
|
||||
{
|
||||
osg::Group* parent = getArrowBone();
|
||||
if (!parent)
|
||||
|
@ -154,7 +157,7 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength)
|
|||
|
||||
MWMechanics::applyFatigueLoss(actor, *weapon, attackStrength);
|
||||
|
||||
if (weapon->get<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
|
||||
|
|
|
@ -186,14 +186,7 @@ namespace MWScript
|
|||
virtual void execute (Interpreter::Runtime& runtime)
|
||||
{
|
||||
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
|
||||
bool stanceOn = stats.getStance(MWMechanics::CreatureStats::Stance_Sneak);
|
||||
bool sneaking = MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr);
|
||||
bool inair = !world->isOnGround(ptr) && !world->isSwimming(ptr) && !world->isFlying(ptr);
|
||||
|
||||
runtime.push(stanceOn && (sneaking || inair));
|
||||
runtime.push(MWBase::Environment::get().getMechanicsManager()->isSneaking(ptr));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -248,12 +248,6 @@ namespace MWScript
|
|||
if (ptr.getTypeName() == typeid(ESM::Door).name() && !ptr.getCellRef().getTeleport())
|
||||
{
|
||||
MWBase::Environment::get().getWorld()->activateDoor(ptr, 0);
|
||||
|
||||
float xr = ptr.getCellRef().getPosition().rot[0];
|
||||
float yr = ptr.getCellRef().getPosition().rot[1];
|
||||
float zr = ptr.getCellRef().getPosition().rot[2];
|
||||
|
||||
MWBase::Environment::get().getWorld()->rotateObject(ptr, xr, yr, zr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -529,7 +523,16 @@ namespace MWScript
|
|||
if(key < 0 || key > 32767 || *end != '\0')
|
||||
key = ESM::MagicEffect::effectStringToId(effect);
|
||||
|
||||
const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects();
|
||||
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
|
||||
MWMechanics::MagicEffects effects = stats.getSpells().getMagicEffects();
|
||||
effects += stats.getActiveSpells().getMagicEffects();
|
||||
if (ptr.getClass().isNpc())
|
||||
{
|
||||
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
|
||||
effects += store.getMagicEffects();
|
||||
}
|
||||
|
||||
for (MWMechanics::MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it)
|
||||
{
|
||||
if (it->first.mId == key && it->second.getModifier() > 0)
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
|
||||
#include "ref.hpp"
|
||||
|
||||
|
@ -495,6 +496,16 @@ namespace MWScript
|
|||
{
|
||||
// Apply looping particles immediately for constant effects
|
||||
MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr);
|
||||
|
||||
// The spell may have an instant effect which must be handled immediately.
|
||||
for (const auto& effect : creatureStats.getSpells().getMagicEffects())
|
||||
{
|
||||
if (effect.second.getMagnitude() <= 0)
|
||||
continue;
|
||||
MWMechanics::CastSpell cast(ptr, ptr);
|
||||
if (cast.applyInstantEffect(ptr, ptr, effect.first, effect.second.getMagnitude()))
|
||||
creatureStats.getSpells().purgeEffect(effect.first.mId);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -798,7 +798,7 @@ bool OpenAL_Output::init(const std::string &devname, const std::string &hrtfname
|
|||
if(alGetError() == AL_NO_ERROR)
|
||||
Log(Debug::Info) << "Standard Reverb supported";
|
||||
}
|
||||
EFXEAXREVERBPROPERTIES props = EFX_REVERB_PRESET_GENERIC;
|
||||
EFXEAXREVERBPROPERTIES props = EFX_REVERB_PRESET_LIVINGROOM;
|
||||
props.flGain = 0.0f;
|
||||
LoadEffect(mDefaultEffect, props);
|
||||
}
|
||||
|
|
|
@ -520,4 +520,28 @@ namespace MWWorld
|
|||
{
|
||||
throw std::runtime_error("class does not support armor ratings");
|
||||
}
|
||||
|
||||
osg::Vec4f Class::getEnchantmentColor(const MWWorld::ConstPtr& item) const
|
||||
{
|
||||
osg::Vec4f result(1,1,1,1);
|
||||
std::string enchantmentName = item.getClass().getEnchantment(item);
|
||||
if (enchantmentName.empty())
|
||||
return result;
|
||||
|
||||
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().search(enchantmentName);
|
||||
if (!enchantment)
|
||||
return result;
|
||||
|
||||
assert (enchantment->mEffects.mList.size());
|
||||
|
||||
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().search(
|
||||
enchantment->mEffects.mList.front().mEffectID);
|
||||
if (!magicEffect)
|
||||
return result;
|
||||
|
||||
result.x() = magicEffect->mData.mRed / 255.f;
|
||||
result.y() = magicEffect->mData.mGreen / 255.f;
|
||||
result.z() = magicEffect->mData.mBlue / 255.f;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <osg/Vec4f>
|
||||
|
||||
#include "ptr.hpp"
|
||||
|
||||
namespace ESM
|
||||
|
@ -378,6 +380,8 @@ namespace MWWorld
|
|||
|
||||
/// Get the effective armor rating, factoring in the actor's skills, for the given armor.
|
||||
virtual float getEffectiveArmorRating(const MWWorld::ConstPtr& armor, const MWWorld::Ptr& actor) const;
|
||||
|
||||
virtual osg::Vec4f getEnchantmentColor(const MWWorld::ConstPtr& item) const;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
#include "esmstore.hpp"
|
||||
#include "class.hpp"
|
||||
|
@ -357,7 +357,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots
|
|||
|
||||
const ESM::Weapon* esmWeapon = iter->get<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;
|
||||
|
||||
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)
|
||||
{
|
||||
// Do not equip ranged weapons, if there is no suitable ammo
|
||||
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())
|
||||
hasAmmo = false;
|
||||
else
|
||||
slots_[Slot_Ammunition] = arrow;
|
||||
}
|
||||
if (isCrossbow == true)
|
||||
else if (ammotype == ESM::Weapon::Bolt)
|
||||
{
|
||||
if (bolt == end())
|
||||
hasAmmo = false;
|
||||
|
@ -431,7 +421,7 @@ void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots
|
|||
int slot = itemsSlots.first.front();
|
||||
slots_[slot] = weapon;
|
||||
|
||||
if (!isBow && !isCrossbow)
|
||||
if (ammotype == ESM::Weapon::None)
|
||||
slots_[Slot_Ammunition] = end();
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/aipackage.hpp"
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
#include "../mwrender/vismask.hpp"
|
||||
|
@ -317,12 +318,16 @@ namespace MWWorld
|
|||
state.mIdArrow = projectile.getCellRef().getRefId();
|
||||
state.mCasterHandle = actor;
|
||||
state.mAttackStrength = attackStrength;
|
||||
state.mThrown = projectile.get<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::Ptr ptr = ref.getPtr();
|
||||
|
||||
createModel(state, ptr.getClass().getModel(ptr), pos, orient, false, false, osg::Vec4(0,0,0,0));
|
||||
if (!ptr.getClass().getEnchantment(ptr).empty())
|
||||
SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr));
|
||||
|
||||
mProjectiles.push_back(state);
|
||||
}
|
||||
|
@ -602,7 +607,9 @@ namespace MWWorld
|
|||
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
|
||||
MWWorld::Ptr ptr = ref.getPtr();
|
||||
model = ptr.getClass().getModel(ptr);
|
||||
state.mThrown = ptr.get<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(...)
|
||||
{
|
||||
|
|
|
@ -388,6 +388,30 @@ namespace MWWorld
|
|||
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
|
||||
|
||||
|
|
|
@ -1603,7 +1603,7 @@ namespace MWWorld
|
|||
|
||||
pos.z() += 20; // place slightly above. will snap down to ground with code below
|
||||
|
||||
if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && isActorCollisionEnabled(ptr)))
|
||||
if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !isSwimming(ptr) && isActorCollisionEnabled(ptr)))
|
||||
{
|
||||
osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits);
|
||||
if (traced.z() < pos.z())
|
||||
|
@ -1660,6 +1660,9 @@ namespace MWWorld
|
|||
{
|
||||
mRendering->rotateObject(ptr, rotate);
|
||||
mPhysics->updateRotation(ptr);
|
||||
|
||||
if (const auto object = mPhysics->getObject(ptr))
|
||||
updateNavigatorObject(object);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1844,6 +1847,45 @@ namespace MWWorld
|
|||
return result.mHit;
|
||||
}
|
||||
|
||||
bool World::rotateDoor(const Ptr door, int state, float duration)
|
||||
{
|
||||
const ESM::Position& objPos = door.getRefData().getPosition();
|
||||
float oldRot = objPos.rot[2];
|
||||
|
||||
float minRot = door.getCellRef().getPosition().rot[2];
|
||||
float maxRot = minRot + osg::DegreesToRadians(90.f);
|
||||
|
||||
float diff = duration * osg::DegreesToRadians(90.f);
|
||||
float targetRot = std::min(std::max(minRot, oldRot + diff * (state == 1 ? 1 : -1)), maxRot);
|
||||
rotateObject(door, objPos.rot[0], objPos.rot[1], targetRot);
|
||||
|
||||
bool reached = (targetRot == maxRot && state) || targetRot == minRot;
|
||||
|
||||
/// \todo should use convexSweepTest here
|
||||
std::vector<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)
|
||||
{
|
||||
std::map<MWWorld::Ptr, int>::iterator it = mDoorStates.begin();
|
||||
|
@ -1858,40 +1900,7 @@ namespace MWWorld
|
|||
}
|
||||
else
|
||||
{
|
||||
const ESM::Position& objPos = it->first.getRefData().getPosition();
|
||||
float oldRot = objPos.rot[2];
|
||||
|
||||
float minRot = it->first.getCellRef().getPosition().rot[2];
|
||||
float maxRot = minRot + osg::DegreesToRadians(90.f);
|
||||
|
||||
float diff = duration * osg::DegreesToRadians(90.f);
|
||||
float targetRot = std::min(std::max(minRot, oldRot + diff * (it->second == 1 ? 1 : -1)), maxRot);
|
||||
rotateObject(it->first, objPos.rot[0], objPos.rot[1], targetRot);
|
||||
|
||||
bool reached = (targetRot == maxRot && it->second) || targetRot == minRot;
|
||||
|
||||
/// \todo should use convexSweepTest here
|
||||
std::vector<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);
|
||||
bool reached = rotateDoor(it->first, it->second, duration);
|
||||
|
||||
if (reached)
|
||||
{
|
||||
|
@ -2681,11 +2690,6 @@ namespace MWWorld
|
|||
mRendering->allowVanityMode(allow);
|
||||
}
|
||||
|
||||
void World::togglePlayerLooking(bool enable)
|
||||
{
|
||||
mRendering->togglePlayerLooking(enable);
|
||||
}
|
||||
|
||||
void World::changeVanityModeScale(float factor)
|
||||
{
|
||||
mRendering->changeVanityModeScale(factor);
|
||||
|
@ -2853,7 +2857,10 @@ namespace MWWorld
|
|||
door.getClass().setDoorState(door, state);
|
||||
mDoorStates[door] = state;
|
||||
if (state == 0)
|
||||
{
|
||||
mDoorStates.erase(door);
|
||||
rotateDoor(door, state, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -155,6 +155,8 @@ namespace MWWorld
|
|||
void addContainerScripts(const Ptr& reference, CellStore* cell) override;
|
||||
void removeContainerScripts(const Ptr& reference) override;
|
||||
private:
|
||||
bool rotateDoor(const Ptr door, int state, float duration);
|
||||
|
||||
void processDoors(float duration);
|
||||
///< Run physics simulation and modify \a world accordingly.
|
||||
|
||||
|
@ -664,8 +666,6 @@ namespace MWWorld
|
|||
|
||||
void allowVanityMode(bool allow) override;
|
||||
|
||||
void togglePlayerLooking(bool enable) override;
|
||||
|
||||
void changeVanityModeScale(float factor) override;
|
||||
|
||||
bool vanityRotateCamera(float * rot) override;
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#include "loadskil.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
|
||||
|
@ -21,6 +23,10 @@ struct Weapon
|
|||
|
||||
enum Type
|
||||
{
|
||||
PickProbe = -4,
|
||||
HandToHand = -3,
|
||||
Spell = -2,
|
||||
None = -1,
|
||||
ShortBladeOneHand = 0,
|
||||
LongBladeOneHand = 1,
|
||||
LongBladeTwoHand = 2,
|
||||
|
@ -75,5 +81,34 @@ struct Weapon
|
|||
void blank();
|
||||
///< Set record to default state (does not touch the ID).
|
||||
};
|
||||
|
||||
struct WeaponType
|
||||
{
|
||||
enum Flags
|
||||
{
|
||||
TwoHanded = 0x01,
|
||||
HasHealth = 0x02
|
||||
};
|
||||
|
||||
enum Class
|
||||
{
|
||||
Melee = 0,
|
||||
Ranged = 1,
|
||||
Thrown = 2,
|
||||
Ammo = 3
|
||||
};
|
||||
|
||||
//std::string mDisplayName; // TODO: will be needed later for editor
|
||||
std::string mShortGroup;
|
||||
std::string mLongGroup;
|
||||
std::string mSoundId;
|
||||
std::string mAttachBone;
|
||||
std::string mSheathingBone;
|
||||
ESM::Skill::SkillEnum mSkill;
|
||||
Class mWeaponClass;
|
||||
int mAmmoType;
|
||||
int mFlags;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -87,7 +87,7 @@ boost::filesystem::path LinuxPath::getLocalPath() const
|
|||
{
|
||||
if (readlink(path, &binPath[0], binPath.size()) != -1)
|
||||
{
|
||||
localPath = boost::filesystem::path(binPath).parent_path();
|
||||
localPath = boost::filesystem::path(binPath).parent_path() / "/";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ boost::filesystem::path WindowsPath::getLocalPath() const
|
|||
|
||||
if (GetModuleFileNameW(nullptr, path, MAX_PATH + 1) > 0)
|
||||
{
|
||||
localPath = boost::filesystem::path(bconv::utf_to_utf<char>(path)).parent_path();
|
||||
localPath = boost::filesystem::path(bconv::utf_to_utf<char>(path)).parent_path() / "/";
|
||||
}
|
||||
|
||||
// lookup exe path
|
||||
|
|
|
@ -90,6 +90,26 @@ void NiTriShapeData::read(NIFStream *nif)
|
|||
}
|
||||
}
|
||||
|
||||
void NiTriStripsData::read(NIFStream *nif)
|
||||
{
|
||||
ShapeData::read(nif);
|
||||
|
||||
// Every strip with n points defines n-2 triangles, so this should be unnecessary.
|
||||
/*int tris =*/ nif->getUShort();
|
||||
// Number of triangle strips
|
||||
int numStrips = nif->getUShort();
|
||||
|
||||
std::vector<unsigned short> lengths;
|
||||
nif->getUShorts(lengths, numStrips);
|
||||
|
||||
for (int i = 0; i < numStrips; i++)
|
||||
{
|
||||
std::vector<unsigned short> strip;
|
||||
nif->getUShorts(strip, lengths[i]);
|
||||
strips.emplace_back(strip);
|
||||
}
|
||||
}
|
||||
|
||||
void NiAutoNormalParticlesData::read(NIFStream *nif)
|
||||
{
|
||||
ShapeData::read(nif);
|
||||
|
@ -143,22 +163,23 @@ void NiPixelData::read(NIFStream *nif)
|
|||
{
|
||||
fmt = (Format)nif->getUInt();
|
||||
|
||||
rmask = nif->getInt(); // usually 0xff
|
||||
gmask = nif->getInt(); // usually 0xff00
|
||||
bmask = nif->getInt(); // usually 0xff0000
|
||||
amask = nif->getInt(); // usually 0xff000000 or zero
|
||||
rmask = nif->getUInt(); // usually 0xff
|
||||
gmask = nif->getUInt(); // usually 0xff00
|
||||
bmask = nif->getUInt(); // usually 0xff0000
|
||||
amask = nif->getUInt(); // usually 0xff000000 or zero
|
||||
|
||||
bpp = nif->getInt();
|
||||
bpp = nif->getUInt();
|
||||
|
||||
// Unknown
|
||||
nif->skip(12);
|
||||
// 8 bytes of "Old Fast Compare". Whatever that means.
|
||||
nif->skip(8);
|
||||
palette.read(nif);
|
||||
|
||||
numberOfMipmaps = nif->getInt();
|
||||
numberOfMipmaps = nif->getUInt();
|
||||
|
||||
// Bytes per pixel, should be bpp * 8
|
||||
/* int bytes = */ nif->getInt();
|
||||
/* int bytes = */ nif->getUInt();
|
||||
|
||||
for(int i=0; i<numberOfMipmaps; i++)
|
||||
for(unsigned int i=0; i<numberOfMipmaps; i++)
|
||||
{
|
||||
// Image size and offset in the following data field
|
||||
Mipmap m;
|
||||
|
@ -169,12 +190,17 @@ void NiPixelData::read(NIFStream *nif)
|
|||
}
|
||||
|
||||
// Read the data
|
||||
unsigned int dataSize = nif->getInt();
|
||||
unsigned int dataSize = nif->getUInt();
|
||||
data.reserve(dataSize);
|
||||
for (unsigned i=0; i<dataSize; ++i)
|
||||
data.push_back((unsigned char)nif->getChar());
|
||||
}
|
||||
|
||||
void NiPixelData::post(NIFFile *nif)
|
||||
{
|
||||
palette.post(nif);
|
||||
}
|
||||
|
||||
void NiColorData::read(NIFStream *nif)
|
||||
{
|
||||
mKeyMap = std::make_shared<Vector4KeyMap>();
|
||||
|
@ -258,4 +284,14 @@ void NiKeyframeData::read(NIFStream *nif)
|
|||
mScales->read(nif);
|
||||
}
|
||||
|
||||
void NiPalette::read(NIFStream *nif)
|
||||
{
|
||||
unsigned int alphaMask = !nif->getChar() ? 0xFF000000 : 0;
|
||||
// Fill the entire palette with black even if there isn't enough entries.
|
||||
colors.resize(256);
|
||||
unsigned int numEntries = nif->getUInt();
|
||||
for (unsigned int i = 0; i < numEntries; i++)
|
||||
colors[i] = nif->getUInt() | alphaMask;
|
||||
}
|
||||
|
||||
} // Namespace
|
||||
|
|
|
@ -53,6 +53,15 @@ public:
|
|||
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
|
||||
{
|
||||
public:
|
||||
|
@ -107,6 +116,7 @@ public:
|
|||
NIPXFMT_RGB8,
|
||||
NIPXFMT_RGBA8,
|
||||
NIPXFMT_PAL8,
|
||||
NIPXFMT_PALA8,
|
||||
NIPXFMT_DXT1,
|
||||
NIPXFMT_DXT3,
|
||||
NIPXFMT_DXT5,
|
||||
|
@ -114,8 +124,10 @@ public:
|
|||
};
|
||||
Format fmt;
|
||||
|
||||
unsigned int rmask, gmask, bmask, amask;
|
||||
int bpp, numberOfMipmaps;
|
||||
unsigned int rmask, gmask, bmask, amask, bpp;
|
||||
|
||||
NiPalettePtr palette;
|
||||
unsigned int numberOfMipmaps;
|
||||
|
||||
struct Mipmap
|
||||
{
|
||||
|
@ -127,6 +139,7 @@ public:
|
|||
std::vector<unsigned char> data;
|
||||
|
||||
void read(NIFStream *nif);
|
||||
void post(NIFFile *nif);
|
||||
};
|
||||
|
||||
class NiColorData : public Record
|
||||
|
@ -210,5 +223,14 @@ struct NiKeyframeData : public Record
|
|||
void read(NIFStream *nif);
|
||||
};
|
||||
|
||||
class NiPalette : public Record
|
||||
{
|
||||
public:
|
||||
// 32-bit RGBA colors that correspond to 8-bit indices
|
||||
std::vector<unsigned int> colors;
|
||||
|
||||
void read(NIFStream *nif);
|
||||
};
|
||||
|
||||
} // Namespace
|
||||
#endif
|
||||
|
|
|
@ -54,6 +54,7 @@ static std::map<std::string,RecordFactoryEntry> makeFactory()
|
|||
newFactory.insert(makeEntry("NiBSAnimationNode", &construct <NiNode> , RC_NiBSAnimationNode ));
|
||||
newFactory.insert(makeEntry("NiBillboardNode", &construct <NiNode> , RC_NiBillboardNode ));
|
||||
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("NiAutoNormalParticles", &construct <NiAutoNormalParticles> , RC_NiAutoNormalParticles ));
|
||||
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("NiFloatData", &construct <NiFloatData> , RC_NiFloatData ));
|
||||
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("NiColorData", &construct <NiColorData> , RC_NiColorData ));
|
||||
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("NiSkinInstance", &construct <NiSkinInstance> , RC_NiSkinInstance ));
|
||||
newFactory.insert(makeEntry("NiLookAtController", &construct <NiLookAtController> , RC_NiLookAtController ));
|
||||
newFactory.insert(makeEntry("NiPalette", &construct <NiPalette> , RC_NiPalette ));
|
||||
return newFactory;
|
||||
}
|
||||
|
||||
|
|
|
@ -160,9 +160,9 @@ public:
|
|||
{
|
||||
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
|
||||
std::string getString()
|
||||
|
@ -181,34 +181,34 @@ public:
|
|||
void getUShorts(std::vector<unsigned short> &vec, size_t 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
vec.resize(size);
|
||||
/* 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)
|
||||
{
|
||||
vec.resize(size);
|
||||
/* 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)
|
||||
{
|
||||
vec.resize(size);
|
||||
/* 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)
|
||||
|
|
|
@ -156,6 +156,29 @@ struct NiTriShape : Node
|
|||
}
|
||||
};
|
||||
|
||||
struct NiTriStrips : Node
|
||||
{
|
||||
NiTriStripsDataPtr data;
|
||||
NiSkinInstancePtr skin;
|
||||
|
||||
void read(NIFStream *nif)
|
||||
{
|
||||
Node::read(nif);
|
||||
data.read(nif);
|
||||
skin.read(nif);
|
||||
}
|
||||
|
||||
void post(NIFFile *nif)
|
||||
{
|
||||
Node::post(nif);
|
||||
data.post(nif);
|
||||
skin.post(nif);
|
||||
if (!skin.empty())
|
||||
nif->setUseSkinning(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct NiCamera : Node
|
||||
{
|
||||
struct Camera
|
||||
|
|
|
@ -41,6 +41,7 @@ enum RecordType
|
|||
RC_NiBillboardNode,
|
||||
RC_AvoidNode,
|
||||
RC_NiTriShape,
|
||||
RC_NiTriStrips,
|
||||
RC_NiRotatingParticles,
|
||||
RC_NiAutoNormalParticles,
|
||||
RC_NiBSParticleNode,
|
||||
|
@ -80,6 +81,7 @@ enum RecordType
|
|||
RC_NiParticleRotation,
|
||||
RC_NiFloatData,
|
||||
RC_NiTriShapeData,
|
||||
RC_NiTriStripsData,
|
||||
RC_NiVisData,
|
||||
RC_NiColorData,
|
||||
RC_NiPixelData,
|
||||
|
@ -95,7 +97,8 @@ enum RecordType
|
|||
RC_NiSkinInstance,
|
||||
RC_RootCollisionNode,
|
||||
RC_NiSphericalCollider,
|
||||
RC_NiLookAtController
|
||||
RC_NiLookAtController,
|
||||
RC_NiPalette
|
||||
};
|
||||
|
||||
/// Base class for all records
|
||||
|
|
|
@ -135,10 +135,12 @@ class NiPixelData;
|
|||
class NiColorData;
|
||||
struct NiKeyframeData;
|
||||
class NiTriShapeData;
|
||||
class NiTriStripsData;
|
||||
class NiSkinInstance;
|
||||
class NiSourceTexture;
|
||||
class NiRotatingParticlesData;
|
||||
class NiAutoNormalParticlesData;
|
||||
class NiPalette;
|
||||
|
||||
typedef RecordPtrT<Node> NodePtr;
|
||||
typedef RecordPtrT<Extra> ExtraPtr;
|
||||
|
@ -154,10 +156,12 @@ typedef RecordPtrT<NiFloatData> NiFloatDataPtr;
|
|||
typedef RecordPtrT<NiColorData> NiColorDataPtr;
|
||||
typedef RecordPtrT<NiKeyframeData> NiKeyframeDataPtr;
|
||||
typedef RecordPtrT<NiTriShapeData> NiTriShapeDataPtr;
|
||||
typedef RecordPtrT<NiTriStripsData> NiTriStripsDataPtr;
|
||||
typedef RecordPtrT<NiSkinInstance> NiSkinInstancePtr;
|
||||
typedef RecordPtrT<NiSourceTexture> NiSourceTexturePtr;
|
||||
typedef RecordPtrT<NiRotatingParticlesData> NiRotatingParticlesDataPtr;
|
||||
typedef RecordPtrT<NiAutoNormalParticlesData> NiAutoNormalParticlesDataPtr;
|
||||
typedef RecordPtrT<NiPalette> NiPalettePtr;
|
||||
|
||||
typedef RecordListT<Node> NodeList;
|
||||
typedef RecordListT<Property> PropertyList;
|
||||
|
|
|
@ -54,9 +54,57 @@ void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriShapeDa
|
|||
}
|
||||
}
|
||||
|
||||
void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data)
|
||||
void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, const osg::Matrixf &transform)
|
||||
{
|
||||
fillTriangleMeshWithTransform(mesh, data, osg::Matrixf());
|
||||
const std::vector<osg::Vec3f> &vertices = data.vertices;
|
||||
const std::vector<std::vector<unsigned short>> &strips = data.strips;
|
||||
if (vertices.empty() || strips.empty())
|
||||
return;
|
||||
mesh.preallocateVertices(static_cast<int>(data.vertices.size()));
|
||||
int numTriangles = 0;
|
||||
for (const std::vector<unsigned short>& strip : strips)
|
||||
{
|
||||
// Each strip with N points contains information about N-2 triangles.
|
||||
if (strip.size() >= 3)
|
||||
numTriangles += static_cast<int>(strip.size()-2);
|
||||
}
|
||||
mesh.preallocateIndices(static_cast<int>(numTriangles));
|
||||
|
||||
// It's triangulation time. Totally not a NifSkope spell ripoff.
|
||||
for (const std::vector<unsigned short>& strip : strips)
|
||||
{
|
||||
// Can't make a triangle from less than 3 points.
|
||||
if (strip.size() < 3)
|
||||
continue;
|
||||
|
||||
unsigned short a = strip[0], b = strip[0], c = strip[1];
|
||||
for (int i = 2; i < static_cast<int>(strip.size()); i++)
|
||||
{
|
||||
a = b;
|
||||
b = c;
|
||||
c = strip[i];
|
||||
if (a != b && b != c && a != c)
|
||||
{
|
||||
if (i%2==0)
|
||||
mesh.addTriangle(getbtVector(vertices[a]), getbtVector(vertices[b]), getbtVector(vertices[c]));
|
||||
else
|
||||
mesh.addTriangle(getbtVector(vertices[a]), getbtVector(vertices[c]), getbtVector(vertices[b]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::Node* nifNode, const osg::Matrixf &transform)
|
||||
{
|
||||
if (nifNode->recType == Nif::RC_NiTriShape)
|
||||
fillTriangleMeshWithTransform(mesh, static_cast<const Nif::NiTriShape*>(nifNode)->data.get(), transform);
|
||||
else // if (nifNode->recType == Nif::RC_NiTriStrips)
|
||||
fillTriangleMeshWithTransform(mesh, static_cast<const Nif::NiTriStrips*>(nifNode)->data.get(), transform);
|
||||
}
|
||||
|
||||
void fillTriangleMesh(btTriangleMesh& mesh, const Nif::Node* node)
|
||||
{
|
||||
fillTriangleMeshWithTransform(mesh, node, osg::Matrixf());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -244,9 +292,9 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n
|
|||
// NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape!
|
||||
// It must be ignored completely.
|
||||
// (occurs in tr_ex_imp_wall_arch_04.nif)
|
||||
if(!node->hasBounds && node->recType == Nif::RC_NiTriShape)
|
||||
if(!node->hasBounds && (node->recType == Nif::RC_NiTriShape || node->recType == Nif::RC_NiTriStrips))
|
||||
{
|
||||
handleNiTriShape(static_cast<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)
|
||||
{
|
||||
assert(shape != nullptr);
|
||||
assert(nifNode != nullptr);
|
||||
|
||||
// If the object was marked "NCO" earlier, it shouldn't collide with
|
||||
// anything. So don't do anything.
|
||||
if ((flags & 0x800))
|
||||
return;
|
||||
|
||||
if (nifNode->recType == Nif::RC_NiTriShape)
|
||||
{
|
||||
const Nif::NiTriShape* shape = static_cast<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)
|
||||
{
|
||||
|
@ -290,13 +346,13 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags,
|
|||
|
||||
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));
|
||||
childMesh.release();
|
||||
|
||||
float scale = shape->trafo.scale;
|
||||
const Nif::Node* parent = shape;
|
||||
float scale = nifNode->trafo.scale;
|
||||
const Nif::Node* parent = nifNode;
|
||||
while (parent->parent)
|
||||
{
|
||||
parent = parent->parent;
|
||||
|
@ -308,7 +364,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags,
|
|||
|
||||
btTransform trans(btQuaternion(q.x(), q.y(), q.z(), q.w()), btVector3(v.x(), v.y(), v.z()));
|
||||
|
||||
mShape->mAnimatedShapes.insert(std::make_pair(shape->recIndex, mCompoundShape->getNumChildShapes()));
|
||||
mShape->mAnimatedShapes.emplace(nifNode->recIndex, mCompoundShape->getNumChildShapes());
|
||||
|
||||
mCompoundShape->addChildShape(trans, childShape.get());
|
||||
childShape.release();
|
||||
|
@ -318,7 +374,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags,
|
|||
if (!mAvoidStaticMesh)
|
||||
mAvoidStaticMesh.reset(new btTriangleMesh(false));
|
||||
|
||||
fillTriangleMeshWithTransform(*mAvoidStaticMesh, shape->data.get(), transform);
|
||||
fillTriangleMeshWithTransform(*mAvoidStaticMesh, nifNode, transform);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -326,7 +382,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags,
|
|||
mStaticMesh.reset(new btTriangleMesh(false));
|
||||
|
||||
// Static shape, just transform all vertices into position
|
||||
fillTriangleMeshWithTransform(*mStaticMesh, shape->data.get(), transform);
|
||||
fillTriangleMeshWithTransform(*mStaticMesh, nifNode, transform);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace Nif
|
|||
class Node;
|
||||
struct Transformation;
|
||||
struct NiTriShape;
|
||||
struct NiTriStrips;
|
||||
}
|
||||
|
||||
namespace NifBullet
|
||||
|
@ -58,7 +59,7 @@ private:
|
|||
|
||||
bool hasAutoGeneratedCollision(const Nif::Node *rootNode);
|
||||
|
||||
void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid);
|
||||
void handleNiTriShape(const Nif::Node *nifNode, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid);
|
||||
|
||||
std::unique_ptr<btCompoundShape> mCompoundShape;
|
||||
|
||||
|
|
|
@ -408,8 +408,8 @@ namespace NifOsg
|
|||
unsigned int clamp = static_cast<unsigned int>(textureEffect->clamp);
|
||||
int wrapT = (clamp) & 0x1;
|
||||
int wrapS = (clamp >> 1) & 0x1;
|
||||
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP);
|
||||
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP);
|
||||
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
|
||||
osg::ref_ptr<osg::TexEnvCombine> texEnv = new osg::TexEnvCombine;
|
||||
texEnv->setCombine_Alpha(osg::TexEnvCombine::REPLACE);
|
||||
|
@ -452,6 +452,7 @@ namespace NifOsg
|
|||
break;
|
||||
}
|
||||
case Nif::RC_NiTriShape:
|
||||
case Nif::RC_NiTriStrips:
|
||||
case Nif::RC_NiAutoNormalParticles:
|
||||
case Nif::RC_NiRotatingParticles:
|
||||
// Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children.
|
||||
|
@ -540,6 +541,10 @@ namespace NifOsg
|
|||
// Marker objects. These meshes are only visible in the editor.
|
||||
hasMarkers = true;
|
||||
}
|
||||
else if(sd->string == "BONE")
|
||||
{
|
||||
node->getOrCreateUserDataContainer()->addDescription("CustomBone");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -575,7 +580,7 @@ namespace NifOsg
|
|||
node->setDataVariance(osg::Object::DYNAMIC);
|
||||
}
|
||||
|
||||
if (nifNode->recType == Nif::RC_NiTriShape && isAnimated) // the same thing for animated NiTriShapes
|
||||
if ((nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips) && isAnimated) // Same thing for animated shapes
|
||||
{
|
||||
node->setDataVariance(osg::Object::DYNAMIC);
|
||||
}
|
||||
|
@ -584,20 +589,25 @@ namespace NifOsg
|
|||
|
||||
applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags);
|
||||
|
||||
if (nifNode->recType == Nif::RC_NiTriShape && !skipMeshes)
|
||||
if ((nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips) && !skipMeshes)
|
||||
{
|
||||
const Nif::NiTriShape* triShape = static_cast<const Nif::NiTriShape*>(nifNode);
|
||||
const std::string nodeName = Misc::StringUtils::lowerCase(triShape->name);
|
||||
const std::string nodeName = Misc::StringUtils::lowerCase(nifNode->name);
|
||||
static const std::string markerName = "tri editormarker";
|
||||
static const std::string shadowName = "shadow";
|
||||
static const std::string shadowName2 = "tri shadow";
|
||||
const bool isMarker = hasMarkers && !nodeName.compare(0, markerName.size(), markerName);
|
||||
if (!isMarker && nodeName.compare(0, shadowName.size(), shadowName) && nodeName.compare(0, shadowName2.size(), shadowName2))
|
||||
{
|
||||
if (triShape->skin.empty())
|
||||
handleTriShape(triShape, node, composite, boundTextures, animflags);
|
||||
Nif::NiSkinInstancePtr skin;
|
||||
if (nifNode->recType == Nif::RC_NiTriShape)
|
||||
skin = static_cast<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
|
||||
handleSkinnedTriShape(triShape, node, composite, boundTextures, animflags);
|
||||
handleSkinnedTriShape(nifNode, node, composite, boundTextures, animflags);
|
||||
|
||||
if (!nifNode->controller.empty())
|
||||
handleMeshControllers(nifNode, node, composite, boundTextures, animflags);
|
||||
|
@ -612,7 +622,8 @@ namespace NifOsg
|
|||
|
||||
// Note: NiTriShapes are not allowed to have KeyframeControllers (the vanilla engine just crashes when there is one).
|
||||
// We can take advantage of this constraint for optimizations later.
|
||||
if (nifNode->recType != Nif::RC_NiTriShape && !nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC)
|
||||
if (nifNode->recType != Nif::RC_NiTriShape && nifNode->recType != Nif::RC_NiTriStrips
|
||||
&& !nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC)
|
||||
handleNodeControllers(nifNode, static_cast<osg::MatrixTransform*>(node.get()), animflags);
|
||||
|
||||
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(nifNode);
|
||||
|
@ -766,8 +777,8 @@ namespace NifOsg
|
|||
|
||||
// inherit wrap settings from the target slot
|
||||
osg::Texture2D* inherit = dynamic_cast<osg::Texture2D*>(stateset->getTextureAttribute(flipctrl->mTexSlot, osg::StateAttribute::TEXTURE));
|
||||
osg::Texture2D::WrapMode wrapS = osg::Texture2D::CLAMP;
|
||||
osg::Texture2D::WrapMode wrapT = osg::Texture2D::CLAMP;
|
||||
osg::Texture2D::WrapMode wrapS = osg::Texture2D::CLAMP_TO_EDGE;
|
||||
osg::Texture2D::WrapMode wrapT = osg::Texture2D::CLAMP_TO_EDGE;
|
||||
if (inherit)
|
||||
{
|
||||
wrapS = inherit->getWrap(osg::Texture2D::WRAP_S);
|
||||
|
@ -1030,57 +1041,90 @@ namespace NifOsg
|
|||
}
|
||||
}
|
||||
|
||||
void triShapeToGeometry(const Nif::NiTriShape *triShape, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<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();
|
||||
|
||||
{
|
||||
geometry->setVertexArray(new osg::Vec3Array(data->vertices.size(), &data->vertices[0]));
|
||||
if (!data->normals.empty())
|
||||
geometry->setNormalArray(new osg::Vec3Array(data->normals.size(), &data->normals[0]), osg::Array::BIND_PER_VERTEX);
|
||||
}
|
||||
if (!vertices.empty())
|
||||
geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data()));
|
||||
if (!normals.empty())
|
||||
geometry->setNormalArray(new osg::Vec3Array(normals.size(), normals.data()), osg::Array::BIND_PER_VERTEX);
|
||||
if (!colors.empty())
|
||||
geometry->setColorArray(new osg::Vec4Array(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX);
|
||||
|
||||
int textureStage = 0;
|
||||
for (std::vector<int>::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it,++textureStage)
|
||||
for (const int uvSet : boundTextures)
|
||||
{
|
||||
int uvSet = *it;
|
||||
if (uvSet >= (int)data->uvlist.size())
|
||||
if (uvSet >= (int)uvlist.size())
|
||||
{
|
||||
Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on TriShape \"" << triShape->name << "\" in " << mFilename;
|
||||
if (!data->uvlist.empty())
|
||||
geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[0].size(), &data->uvlist[0][0]), osg::Array::BIND_PER_VERTEX);
|
||||
Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on shape \"" << name << "\" in " << mFilename;
|
||||
if (!uvlist.empty())
|
||||
geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[0].size(), uvlist[0].data()), osg::Array::BIND_PER_VERTEX);
|
||||
continue;
|
||||
}
|
||||
|
||||
geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[uvSet].size(), &data->uvlist[uvSet][0]), osg::Array::BIND_PER_VERTEX);
|
||||
geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[uvSet].size(), uvlist[uvSet].data()), osg::Array::BIND_PER_VERTEX);
|
||||
textureStage++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!data->colors.empty())
|
||||
geometry->setColorArray(new osg::Vec4Array(data->colors.size(), &data->colors[0]), osg::Array::BIND_PER_VERTEX);
|
||||
|
||||
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES,
|
||||
data->triangles.size(),
|
||||
(unsigned short*)&data->triangles[0]));
|
||||
void triShapeToGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<int>& boundTextures, int animflags)
|
||||
{
|
||||
bool vertexColorsPresent = false;
|
||||
if (nifNode->recType == Nif::RC_NiTriShape)
|
||||
{
|
||||
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:
|
||||
// - if there are no vertex colors, we need to disable colorMode.
|
||||
// - there are 3 "overlapping" nif properties that all affect the osg::Material, handling them
|
||||
// above the actual renderable would be tedious.
|
||||
std::vector<const Nif::Property*> drawableProps;
|
||||
collectDrawableProperties(triShape, drawableProps);
|
||||
applyDrawableProperties(parentNode, drawableProps, composite, !data->colors.empty(), animflags, false);
|
||||
collectDrawableProperties(nifNode, drawableProps);
|
||||
applyDrawableProperties(parentNode, drawableProps, composite, vertexColorsPresent, animflags, false);
|
||||
}
|
||||
|
||||
void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<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;
|
||||
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))
|
||||
continue;
|
||||
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(
|
||||
static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr())->data.getPtr());
|
||||
|
@ -1089,25 +1133,15 @@ namespace NifOsg
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!drawable.get())
|
||||
{
|
||||
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry);
|
||||
drawable = geom;
|
||||
triShapeToGeometry(triShape, geom, parentNode, composite, boundTextures, animflags);
|
||||
}
|
||||
|
||||
drawable->setName(triShape->name);
|
||||
|
||||
drawable->setName(nifNode->name);
|
||||
parentNode->addChild(drawable);
|
||||
}
|
||||
|
||||
osg::ref_ptr<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<osg::Geometry> sourceGeometry (new osg::Geometry);
|
||||
triShapeToGeometry(triShape, sourceGeometry, parentNode, composite, boundTextures, animflags);
|
||||
morphGeom->setSourceGeometry(sourceGeometry);
|
||||
|
||||
const std::vector<Nif::NiMorphData::MorphData>& morphs = morpher->data.getPtr()->mMorphs;
|
||||
|
@ -1115,26 +1149,30 @@ namespace NifOsg
|
|||
return morphGeom;
|
||||
// Note we are not interested in morph 0, which just contains the original vertices
|
||||
for (unsigned int i = 1; i < morphs.size(); ++i)
|
||||
morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0]), 0.f);
|
||||
morphGeom->addMorphTarget(new osg::Vec3Array(morphs[i].mVertices.size(), morphs[i].mVertices.data()), 0.f);
|
||||
|
||||
return morphGeom;
|
||||
}
|
||||
|
||||
void handleSkinnedTriShape(const Nif::NiTriShape *triShape, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite,
|
||||
void handleSkinnedTriShape(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite,
|
||||
const std::vector<int>& boundTextures, int animflags)
|
||||
{
|
||||
assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips);
|
||||
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);
|
||||
rig->setSourceGeometry(geometry);
|
||||
rig->setName(triShape->name);
|
||||
|
||||
const Nif::NiSkinInstance *skin = triShape->skin.getPtr();
|
||||
rig->setName(nifNode->name);
|
||||
|
||||
// Assign bone weights
|
||||
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::NodeList &bones = skin->bones;
|
||||
for(size_t i = 0;i < bones.length();i++)
|
||||
|
@ -1238,9 +1276,11 @@ namespace NifOsg
|
|||
switch (pixelData->fmt)
|
||||
{
|
||||
case Nif::NiPixelData::NIPXFMT_RGB8:
|
||||
case Nif::NiPixelData::NIPXFMT_PAL8:
|
||||
pixelformat = GL_RGB;
|
||||
break;
|
||||
case Nif::NiPixelData::NIPXFMT_RGBA8:
|
||||
case Nif::NiPixelData::NIPXFMT_PALA8:
|
||||
pixelformat = GL_RGBA;
|
||||
break;
|
||||
default:
|
||||
|
@ -1255,7 +1295,7 @@ namespace NifOsg
|
|||
int height = 0;
|
||||
|
||||
std::vector<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];
|
||||
|
||||
|
@ -1281,10 +1321,59 @@ namespace NifOsg
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
unsigned char* data = new unsigned char[pixelData->data.size()];
|
||||
memcpy(data, &pixelData->data[0], pixelData->data.size());
|
||||
|
||||
const std::vector<unsigned char>& pixels = pixelData->data;
|
||||
switch (pixelData->fmt)
|
||||
{
|
||||
case Nif::NiPixelData::NIPXFMT_RGB8:
|
||||
case Nif::NiPixelData::NIPXFMT_RGBA8:
|
||||
{
|
||||
unsigned char* data = new unsigned char[pixels.size()];
|
||||
memcpy(data, pixels.data(), pixels.size());
|
||||
image->setImage(width, height, 1, pixelformat, pixelformat, GL_UNSIGNED_BYTE, data, osg::Image::USE_NEW_DELETE);
|
||||
break;
|
||||
}
|
||||
case Nif::NiPixelData::NIPXFMT_PAL8:
|
||||
case Nif::NiPixelData::NIPXFMT_PALA8:
|
||||
{
|
||||
if (pixelData->palette.empty() || pixelData->bpp != 8)
|
||||
{
|
||||
Log(Debug::Info) << "Palettized texture in " << mFilename << " is invalid, ignoring";
|
||||
return nullptr;
|
||||
}
|
||||
// We're going to convert the indices that pixel data contains
|
||||
// into real colors using the palette.
|
||||
const std::vector<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->setMipmapLevels(mipmapVector);
|
||||
image->flipVertical();
|
||||
|
||||
|
@ -1354,8 +1443,8 @@ namespace NifOsg
|
|||
int wrapT = (clamp) & 0x1;
|
||||
int wrapS = (clamp >> 1) & 0x1;
|
||||
|
||||
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP);
|
||||
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP);
|
||||
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
|
||||
|
||||
int texUnit = boundTextures.size();
|
||||
|
||||
|
|
|
@ -8,48 +8,16 @@
|
|||
|
||||
#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
|
||||
{
|
||||
|
||||
LightController::LightController()
|
||||
: mType(LT_Normal)
|
||||
, mPhase((Misc::Rng::rollClosedProbability() * 2.f - 1.f) * 500.f)
|
||||
, mDeltaCount(0.f)
|
||||
, mDirection(1.f)
|
||||
, mPhase(0.25f + Misc::Rng::rollClosedProbability() * 0.75f)
|
||||
, mBrightness(0.675f)
|
||||
, mStartTime(0.0)
|
||||
, mLastTime(0.0)
|
||||
, mTicksToAdvance(0.f)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -61,66 +29,40 @@ namespace SceneUtil
|
|||
void LightController::operator ()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
double time = nv->getFrameStamp()->getSimulationTime();
|
||||
if (mStartTime == 0)
|
||||
mStartTime = time;
|
||||
|
||||
// disabled early out, light state needs to be set every frame regardless of change, due to the double buffering
|
||||
//if (time == mLastTime)
|
||||
// return;
|
||||
|
||||
float dt = static_cast<float>(time - mLastTime);
|
||||
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;
|
||||
time_distortion = 3.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
static const float fa = osg::PI / 4.0f;
|
||||
static const float phase_wavelength = 120.0f * osg::PI / fa;
|
||||
|
||||
cycle_time = 500.0f;
|
||||
mPhase = std::fmod(mPhase + dt, phase_wavelength);
|
||||
time_distortion = flickerFrequency(mPhase);
|
||||
}
|
||||
|
||||
mDeltaCount += mDirection*dt*time_distortion;
|
||||
if(mDirection > 0 && mDeltaCount > +cycle_time)
|
||||
{
|
||||
mDirection = -1.0f;
|
||||
float extra = mDeltaCount - cycle_time;
|
||||
mDeltaCount -= 2*extra;
|
||||
}
|
||||
if(mDirection < 0 && mDeltaCount < -cycle_time)
|
||||
{
|
||||
mDirection = +1.0f;
|
||||
float extra = cycle_time - mDeltaCount;
|
||||
mDeltaCount += 2*extra;
|
||||
}
|
||||
|
||||
static const float fast = 4.0f/1.0f;
|
||||
static const float slow = 1.0f/1.0f;
|
||||
|
||||
// These formulas are just guesswork, but they work pretty well
|
||||
if (mType == LT_Normal)
|
||||
{
|
||||
// Less than 1/255 light modifier for a constant light:
|
||||
brightness = 1.0f + flickerAmplitude(mDeltaCount*slow)/255.0f;
|
||||
static_cast<SceneUtil::LightSource*>(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor);
|
||||
traverse(node, nv);
|
||||
return;
|
||||
}
|
||||
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);
|
||||
// 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
|
||||
mBrightness += mTicksToAdvance * speed;
|
||||
|
||||
if (std::abs(mBrightness - mPhase) < speed)
|
||||
{
|
||||
if (mType == LT_Flicker || mType == LT_FlickerSlow)
|
||||
mPhase = 0.25f + Misc::Rng::rollClosedProbability() * 0.75f;
|
||||
else // if (mType == LT_Pulse || mType == LT_PulseSlow)
|
||||
mPhase = mPhase <= 0.5f ? 1.f : 0.25f;
|
||||
}
|
||||
|
||||
static_cast<SceneUtil::LightSource*>(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness);
|
||||
|
||||
traverse(node, nv);
|
||||
}
|
||||
|
|
|
@ -32,9 +32,10 @@ namespace SceneUtil
|
|||
LightType mType;
|
||||
osg::Vec4f mDiffuseColor;
|
||||
float mPhase;
|
||||
float mDeltaCount;
|
||||
int mDirection;
|
||||
float mBrightness;
|
||||
double mStartTime;
|
||||
double mLastTime;
|
||||
float mTicksToAdvance;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -22,29 +22,35 @@ namespace SceneUtil
|
|||
float linearAttenuation = 0.f;
|
||||
float constantAttenuation = 0.f;
|
||||
|
||||
const bool useConstant = Fallback::Map::getBool("LightAttenuation_UseConstant");
|
||||
if (useConstant)
|
||||
{
|
||||
constantAttenuation = Fallback::Map::getFloat("LightAttenuation_ConstantValue");
|
||||
}
|
||||
static const bool useConstant = Fallback::Map::getBool("LightAttenuation_UseConstant");
|
||||
static const bool useLinear = Fallback::Map::getBool("LightAttenuation_UseLinear");
|
||||
static const bool useQuadratic = Fallback::Map::getBool("LightAttenuation_UseQuadratic");
|
||||
static const float constantValue = Fallback::Map::getFloat("LightAttenuation_ConstantValue");
|
||||
static const float linearValue = Fallback::Map::getFloat("LightAttenuation_LinearValue");
|
||||
static const float quadraticValue = Fallback::Map::getFloat("LightAttenuation_QuadraticValue");
|
||||
static const float linearRadiusMult = Fallback::Map::getFloat("LightAttenuation_LinearRadiusMult");
|
||||
static const float quadraticRadiusMult = Fallback::Map::getFloat("LightAttenuation_QuadraticRadiusMult");
|
||||
static const int linearMethod = Fallback::Map::getInt("LightAttenuation_LinearMethod");
|
||||
static const int quadraticMethod = Fallback::Map::getInt("LightAttenuation_QuadraticMethod");
|
||||
static const bool outQuadInLin = Fallback::Map::getBool("LightAttenuation_OutQuadInLin");
|
||||
|
||||
if (useConstant)
|
||||
constantAttenuation = constantValue;
|
||||
|
||||
const bool useLinear = Fallback::Map::getBool("LightAttenuation_UseLinear");
|
||||
if (useLinear)
|
||||
{
|
||||
const float linearValue = Fallback::Map::getFloat("LightAttenuation_LinearValue");
|
||||
const float linearRadiusMult = Fallback::Map::getFloat("LightAttenuation_LinearRadiusMult");
|
||||
linearAttenuation = linearMethod == 0 ? linearValue : 0.01f;
|
||||
float r = radius * linearRadiusMult;
|
||||
if (r) linearAttenuation = linearValue / r;
|
||||
if (r && (linearMethod == 1 || linearMethod == 2))
|
||||
linearAttenuation = linearValue / std::pow(r, linearMethod);
|
||||
}
|
||||
|
||||
const bool useQuadratic = Fallback::Map::getBool("LightAttenuation_UseQuadratic");
|
||||
const bool outQuadInLin = Fallback::Map::getBool("LightAttenuation_OutQuadInLin");
|
||||
if (useQuadratic && (!outQuadInLin || isExterior))
|
||||
{
|
||||
const float quadraticValue = Fallback::Map::getFloat("LightAttenuation_QuadraticValue");
|
||||
const float quadraticRadiusMult = Fallback::Map::getFloat("LightAttenuation_QuadraticRadiusMult");
|
||||
quadraticAttenuation = quadraticMethod == 0 ? quadraticValue : 0.01f;
|
||||
float r = radius * quadraticRadiusMult;
|
||||
if (r) quadraticAttenuation = quadraticValue / std::pow(r, 2);
|
||||
if (r && (quadraticMethod == 1 || quadraticMethod == 2))
|
||||
quadraticAttenuation = quadraticValue / std::pow(r, quadraticMethod);
|
||||
}
|
||||
|
||||
light->setConstantAttenuation(constantAttenuation);
|
||||
|
|
|
@ -1,10 +1,151 @@
|
|||
#include "util.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <osg/Node>
|
||||
#include <osg/NodeVisitor>
|
||||
#include <osg/TexGen>
|
||||
#include <osg/TexEnvCombine>
|
||||
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
|
||||
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)
|
||||
{
|
||||
osg::BoundingSphere::vec_type xdash = bsphere._center;
|
||||
|
@ -73,4 +214,48 @@ bool hasUserDescription(const osg::Node* node, const std::string pattern)
|
|||
return false;
|
||||
}
|
||||
|
||||
osg::ref_ptr<GlowUpdater> addEnchantedGlow(osg::ref_ptr<osg::Node> node, Resource::ResourceSystem* resourceSystem, osg::Vec4f glowColor, float glowDuration)
|
||||
{
|
||||
std::vector<osg::ref_ptr<osg::Texture2D> > textures;
|
||||
for (int i=0; i<32; ++i)
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << "textures/magicitem/caust";
|
||||
stream << std::setw(2);
|
||||
stream << std::setfill('0');
|
||||
stream << i;
|
||||
stream << ".dds";
|
||||
|
||||
osg::ref_ptr<osg::Image> image = resourceSystem->getImageManager()->getImage(stream.str());
|
||||
osg::ref_ptr<osg::Texture2D> tex (new osg::Texture2D(image));
|
||||
tex->setName("envMap");
|
||||
tex->setWrap(osg::Texture::WRAP_S, osg::Texture2D::REPEAT);
|
||||
tex->setWrap(osg::Texture::WRAP_T, osg::Texture2D::REPEAT);
|
||||
resourceSystem->getSceneManager()->applyFilterSettings(tex);
|
||||
textures.push_back(tex);
|
||||
}
|
||||
|
||||
FindLowestUnusedTexUnitVisitor findLowestUnusedTexUnitVisitor;
|
||||
node->accept(findLowestUnusedTexUnitVisitor);
|
||||
int texUnit = findLowestUnusedTexUnitVisitor.mLowestUnusedTexUnit;
|
||||
|
||||
osg::ref_ptr<GlowUpdater> glowUpdater = new GlowUpdater(texUnit, glowColor, textures, node, glowDuration, resourceSystem);
|
||||
node->addUpdateCallback(glowUpdater);
|
||||
|
||||
// set a texture now so that the ShaderVisitor can find it
|
||||
osg::ref_ptr<osg::StateSet> writableStateSet = nullptr;
|
||||
if (!node->getStateSet())
|
||||
writableStateSet = node->getOrCreateStateSet();
|
||||
else
|
||||
{
|
||||
writableStateSet = new osg::StateSet(*node->getStateSet(), osg::CopyOp::SHALLOW_COPY);
|
||||
node->setStateSet(writableStateSet);
|
||||
}
|
||||
writableStateSet->setTextureAttributeAndModes(texUnit, textures.front(), osg::StateAttribute::ON);
|
||||
writableStateSet->addUniform(new osg::Uniform("envMapColor", glowColor));
|
||||
resourceSystem->getSceneManager()->recreateShaders(node);
|
||||
|
||||
return glowUpdater;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,10 +3,48 @@
|
|||
|
||||
#include <osg/Matrix>
|
||||
#include <osg/BoundingSphere>
|
||||
#include <osg/NodeCallback>
|
||||
#include <osg/Texture2D>
|
||||
#include <osg/Vec4f>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
|
||||
#include "statesetupdater.hpp"
|
||||
|
||||
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
|
||||
// based off private code in osg::Transform
|
||||
|
@ -20,6 +58,8 @@ namespace SceneUtil
|
|||
float makeOsgColorComponent (unsigned int value, unsigned int shift);
|
||||
|
||||
bool hasUserDescription(const osg::Node* node, const std::string pattern);
|
||||
|
||||
osg::ref_ptr<GlowUpdater> addEnchantedGlow(osg::ref_ptr<osg::Node> node, Resource::ResourceSystem* resourceSystem, osg::Vec4f glowColor, float glowDuration=-1);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -203,8 +203,79 @@ Shield holstering is not supported at the moment since it conflicts with any mod
|
|||
|
||||
An example of a mod which uses this feature is `Weapon Sheathing`_.
|
||||
|
||||
|
||||
Skeleton extensions
|
||||
-------------------
|
||||
|
||||
It is possible to inject custom bones into actor skeletons:
|
||||
|
||||
::
|
||||
|
||||
[Game]
|
||||
use additional anim sources = true
|
||||
|
||||
If this setting is enabled, OpenMW will seek for modified skeletons in the ``Animations/[skeleton name]`` folder in your ``Data Files``.
|
||||
For example, the biped creature skeleton folder is ``Animations/xbase_anim``, the female NPCs skeleton folder is ``Animations/xbase_anim_female``,
|
||||
the beast race skeleton folder is ``Animations/xbase_anim_kna``.
|
||||
Note that these are the third person view skeletons, and the first person view skeleton will have a different name.
|
||||
|
||||
OpenMW scans every NIF file in such a folder for nodes which have "BONE" NiStringExtraData.
|
||||
It is recommended to give such nodes names that start with "Bip01 " so that the mesh optimizer doesn't try to optimize them out.
|
||||
Then OpenMW copies all found nodes to related skeleton. To determine the bone to which the new node should be attached,
|
||||
OpenMW checks the name of the parent node of the new node in the original NIF file.
|
||||
For example, to attach a custom weapon bone, you'll need to follow this NIF record hierarchy:
|
||||
|
||||
::
|
||||
|
||||
NiNode "root"
|
||||
NiNode "Bip01 L Hand"
|
||||
NiNode "Weapon Bone Left"
|
||||
NiStringExtraData "BONE"
|
||||
|
||||
OpenMW will detect ``Weapon Bone Left`` node and attach it to ``Bip01 L Hand`` bone of the target skeleton.
|
||||
|
||||
An example of a mod which uses this feature is `Weapon Sheathing`_.
|
||||
|
||||
|
||||
Extended weapon animations
|
||||
--------------------------
|
||||
|
||||
It is possible to use unique animation groups for different weapon types.
|
||||
They are not mandatory, and the currently hardcoded weapon types will fall back to existing generic animations.
|
||||
Every weapon type has an attack animation group and a suffix for the movement animation groups.
|
||||
For example, long blades use ``weapononehand`` attack animation group, ``idle1h`` idle animation group, ``jump1h`` jumping animation group, etc.
|
||||
This is the full table of supported animation groups:
|
||||
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
| Weapon type | Animation group | Movement suffix | Attack (fallback) | Suffix (fallback) |
|
||||
+===============+===================+==================+======================+=======================+
|
||||
| Short blade | shortbladeonehand | 1s | weapononehand | 1h |
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
| Long blade 1H | weapononehand | 1h | | |
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
| Long blade 2H | weapontwohand | 2c | | |
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
| Blunt 1H | bluntonehand | 1b | weapononehand | 1h |
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
| Blunt 2H | blunttwohand | 2b | weapontwohand | 2c |
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
| Axe 1H | bluntonehand | 1b | weapononehand | 1h |
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
| Axe 2H | blunttwohand | 2b | weapontwohand | 2c |
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
| Blunt 2H wide | weapontwowide | 2w | weapontwohand | 2c |
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
| Spear | weapontwowide | 2w | weapontwohand | 2c |
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
| Bow | bowandarrow | bow | | 1h |
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
| Crossbow | crossbow | crossbow | | 1h |
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
| Thrown | throwweapon | 1t | | 1h |
|
||||
+---------------+-------------------+------------------+----------------------+-----------------------+
|
||||
|
||||
.. _`Graphic Herbalism`: https://www.nexusmods.com/morrowind/mods/46599
|
||||
.. _`OpenMW Containers Animated`: https://www.nexusmods.com/morrowind/mods/46232
|
||||
.. _`Glow in the Dahrk`: https://www.nexusmods.com/morrowind/mods/45886
|
||||
.. _`Almalexia's Cast for Beasts`: https://www.nexusmods.com/morrowind/mods/45853
|
||||
.. _`Weapon sheathing`: https://www.nexusmods.com/morrowind/mods/46069`
|
||||
.. _`Weapon sheathing`: https://www.nexusmods.com/morrowind/mods/46069
|
||||
|
|
13
extern/recastnavigation/.gitignore
vendored
13
extern/recastnavigation/.gitignore
vendored
|
@ -9,16 +9,6 @@
|
|||
*.so
|
||||
*.idb
|
||||
|
||||
## Linux exes have no extension
|
||||
RecastDemo/Bin/RecastDemo
|
||||
RecastDemo/Bin/Tests
|
||||
|
||||
# Build directory
|
||||
RecastDemo/Build
|
||||
|
||||
# Ignore meshes
|
||||
RecastDemo/Bin/Meshes/*
|
||||
|
||||
## Logs and databases #
|
||||
*.log
|
||||
*.sql
|
||||
|
@ -38,9 +28,6 @@ Thumbs.db
|
|||
## xcode specific
|
||||
*xcuserdata*
|
||||
|
||||
## SDL contrib
|
||||
RecastDemo/Contrib/SDL/*
|
||||
|
||||
## Generated doc files
|
||||
Docs/html
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue