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
pull/541/head
David Cernat 5 years ago
commit 353e7d530a

@ -22,6 +22,7 @@ Programmers
alexanderkjall
Alexander Nadeau (wareya)
Alexander Olofsson (Ace)
Alex Rice
Alex S (docwest)
Allofich
Andrei Kortunov (akortunov)

@ -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}}
} };
int match = -1;
for (unsigned i = 0; i < classes.size(); ++i)
{
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;
unsigned combat = mGenerateClassSpecializations[0];
unsigned magic = mGenerateClassSpecializations[1];
unsigned stealth = mGenerateClassSpecializations[2];
if (combat > 7)
{
mGenerateClass = "Warrior";
}
else if (magic > 7)
{
mGenerateClass = "Mage";
}
if (match == -1)
else if (stealth > 7)
{
if (mGenerateClassSpecializations[0] >= 7)
mGenerateClass = "Thief";
else if (mGenerateClassSpecializations[1] >= 7)
}
else
{
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";
else if (mGenerateClassSpecializations[2] >= 7)
mGenerateClass = "Mage";
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)
{
Log(Debug::Warning) << "Failed to deduce class from chosen answers in generate class dialog.";
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.
movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask);
// If we apply movement only for lower body, do not reset idle animations.
// For upper body there will be idle animation.
if (idle == CharState_None)
if (movemask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None)
idle = CharState_Idle;
// For crossbow animations use 1h ones as fallback
if (mWeaponType == WeapType_Crossbow)
movementAnimName += "1h";
}
else if (idle == CharState_None)
{
@ -520,37 +545,31 @@ 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)
{
std::string::size_type runpos = movementAnimName.find("run");
if (runpos != std::string::npos)
{
movementAnimName.replace(runpos, runpos+3, "walk");
if (!mAnimation->hasAnimation(movementAnimName))
movementAnimName.clear();
}
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)
if (!weapShortGroup.empty())
{
std::string weapMovementAnimName = movementAnimName + weap->shortgroup;
std::string weapMovementAnimName = movementAnimName + weapShortGroup;
if(mAnimation->hasAnimation(weapMovementAnimName))
movementAnimName = weapMovementAnimName;
else
movemask = MWRender::Animation::BlendMask_LowerBody;
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)
{
movementAnimName.replace(runpos, runpos+3, "walk");
if (!mAnimation->hasAnimation(movementAnimName))
movementAnimName.clear();
}
else
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)
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()
const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence();
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);
}

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

@ -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);
}
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;
int weaponType = ref->mBase->mData.mType;
return MWMechanics::getWeaponType(weaponType)->mSheathingBone;
}
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 = oneHandFallback;
}
}
else
groupname = "inventoryhandtohand";
}
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,34 +1847,22 @@ namespace MWWorld
return result.mHit;
}
void World::processDoors(float duration)
{
std::map<MWWorld::Ptr, int>::iterator it = mDoorStates.begin();
while (it != mDoorStates.end())
bool World::rotateDoor(const Ptr door, int state, float duration)
{
if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode())
{
// The door is no longer in an active cell, or it was disabled.
// Erase from mDoorStates, since we no longer need to move it.
// Once we load the door's cell again (or re-enable the door), Door::insertObject will reinsert to mDoorStates.
mDoorStates.erase(it++);
}
else
{
const ESM::Position& objPos = it->first.getRefData().getPosition();
const ESM::Position& objPos = door.getRefData().getPosition();
float oldRot = objPos.rot[2];
float minRot = it->first.getCellRef().getPosition().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 * (it->second == 1 ? 1 : -1)), maxRot);
rotateObject(it->first, objPos.rot[0], objPos.rot[1], targetRot);
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 && it->second) || targetRot == minRot;
bool reached = (targetRot == maxRot && state) || targetRot == minRot;
/// \todo should use convexSweepTest here
std::vector<MWWorld::Ptr> collisions = mPhysics->getCollisions(it->first, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor);
std::vector<MWWorld::Ptr> collisions = mPhysics->getCollisions(door, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor);
for (MWWorld::Ptr& ptr : collisions)
{
if (ptr.getClass().isActor())
@ -1881,17 +1872,35 @@ namespace MWWorld
{
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);
seq.stack(MWMechanics::AiAvoidDoor(door),ptr);
}
// we need to undo the rotation
rotateObject(it->first, objPos.rot[0], objPos.rot[1], oldRot);
rotateObject(door, objPos.rot[0], objPos.rot[1], oldRot);
reached = false;
}
}
// the rotation order we want to use
mWorldScene->updateObjectRotation(it->first, false);
mWorldScene->updateObjectRotation(door, false);
return reached;
}
void World::processDoors(float duration)
{
std::map<MWWorld::Ptr, int>::iterator it = mDoorStates.begin();
while (it != mDoorStates.end())
{
if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode())
{
// The door is no longer in an active cell, or it was disabled.
// Erase from mDoorStates, since we no longer need to move it.
// Once we load the door's cell again (or re-enable the door), Door::insertObject will reinsert to mDoorStates.
mDoorStates.erase(it++);
}
else
{
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())
if (shape->data.empty() || shape->data->triangles.empty())
return;
if (shape->data->triangles.empty())
}
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 (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)
if (mType == LT_Normal)
{
cycle_time = 2.0f * osg::PI;
time_distortion = 3.0f;
static_cast<SceneUtil::LightSource*>(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor);
traverse(node, nv);
return;
}
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);
}
// 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;
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;
float speed = (mType == LT_Flicker || mType == LT_Pulse) ? 0.1f : 0.05f;
if (mBrightness >= mPhase)
mBrightness -= mTicksToAdvance * speed;
else
mBrightness += mTicksToAdvance * speed;
// These formulas are just guesswork, but they work pretty well
if(mType == LT_Normal)
if (std::abs(mBrightness - mPhase) < speed)
{
// Less than 1/255 light modifier for a constant light:
brightness = 1.0f + flickerAmplitude(mDeltaCount*slow)/255.0f;
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;
}
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);
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");
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 = Fallback::Map::getFloat("LightAttenuation_ConstantValue");
}
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

@ -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…
Cancel
Save