Merge branch 'master' of https://github.com/zinnschlag/openmw into Editor-Dialog

actorid
Marek Kochanowicz 11 years ago
commit f3475f7ef5

@ -73,6 +73,8 @@ option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binarie
option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE)
option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE)
option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE)
# Apps and tools
option(BUILD_BSATOOL "build BSA extractor" OFF)
option(BUILD_ESMTOOL "build ESM inspector" ON)

@ -1,93 +0,0 @@
Copyright (c) 2010, 2011 Georg Duffner (http://www.georgduffner.at)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

@ -15,7 +15,7 @@ add_openmw_dir (mwrender
renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation
actors objects renderinginterface localmap occlusionquery water shadows
characterpreview globalmap videoplayer ripplesimulation refraction
terrainstorage renderconst effectmanager
terrainstorage renderconst effectmanager weaponanimation
)
add_openmw_dir (mwinput
@ -44,7 +44,7 @@ add_openmw_dir (mwscript
locals scriptmanagerimp compilercontext interpretercontext cellextensions miscextensions
guiextensions soundextensions skyextensions statsextensions containerextensions
aiextensions controlextensions extensions globalscripts ref dialogueextensions
animationextensions transformationextensions consoleextensions userextensions locals
animationextensions transformationextensions consoleextensions userextensions
)
add_openmw_dir (mwsound

@ -404,7 +404,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
// Create the world
mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mContentFiles,
mResDir, mCfgMgr.getCachePath(), mEncoder, mFallbackMap,
mActivationDistanceOverride));
mActivationDistanceOverride, mCellName));
MWBase::Environment::get().getWorld()->setupPlayer();
input->setPlayer(&mEnvironment.getWorld()->getPlayer());
@ -440,31 +440,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
mechanics->buildPlayer();
window->updatePlayer();
// load cell
ESM::Position pos;
MWBase::World *world = MWBase::Environment::get().getWorld();
if (!mCellName.empty())
{
if (world->findExteriorPosition(mCellName, pos)) {
world->changeToExteriorCell (pos);
}
else {
world->findInteriorPosition(mCellName, pos);
world->changeToInteriorCell (mCellName, pos);
}
}
else
{
pos.pos[0] = pos.pos[1] = pos.pos[2] = 0;
pos.rot[0] = pos.rot[1] = pos.rot[2] = 0;
world->changeToExteriorCell (pos);
}
Ogre::FrameEvent event;
event.timeSinceLastEvent = 0;
event.timeSinceLastFrame = 0;
frameRenderingQueued(event);
mOgre->getRoot()->addFrameListener (this);
// scripts
@ -502,15 +477,15 @@ void OMW::Engine::go()
// Play some good 'ol tunes
MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore"));
if (!mStartupScript.empty())
MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript);
// start in main menu
if (!mSkipMenu)
MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);
else
MWBase::Environment::get().getStateManager()->newGame (true);
if (!mStartupScript.empty())
MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript);
// Start the main rendering loop
while (!mEnvironment.get().getStateManager()->hasQuitRequest())
Ogre::Root::getSingleton().renderOneFrame();

@ -20,6 +20,9 @@ namespace MWBase
InputManager() {}
/// Clear all savegame-specific data
virtual void clear() = 0;
virtual ~InputManager() {}
virtual void update(float dt, bool loading) = 0;

@ -101,7 +101,8 @@ namespace MWBase
virtual ~World() {}
virtual void startNewGame() = 0;
virtual void startNewGame (bool bypass) = 0;
///< \param bypass Bypass regular game start.
virtual void clear() = 0;
@ -274,7 +275,7 @@ namespace MWBase
virtual void moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0;
virtual void
moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore &newCell, float x, float y, float z) = 0;
moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z) = 0;
virtual void scaleObject (const MWWorld::Ptr& ptr, float scale) = 0;
@ -282,7 +283,7 @@ namespace MWBase
virtual void localRotateObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0;
virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos) = 0;
virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos) = 0;
///< place an object in a "safe" location (ie not in the void, etc).
virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false)
@ -464,8 +465,10 @@ namespace MWBase
virtual void castSpell (const MWWorld::Ptr& actor) = 0;
virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects,
virtual void launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects,
const MWWorld::Ptr& actor, const std::string& sourceName) = 0;
virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile,
const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) = 0;
virtual const std::vector<std::string>& getContentFiles() const = 0;

@ -28,16 +28,16 @@
namespace
{
struct CustomData : public MWWorld::CustomData
struct ContainerCustomData : public MWWorld::CustomData
{
MWWorld::ContainerStore mContainerStore;
virtual MWWorld::CustomData *clone() const;
};
MWWorld::CustomData *CustomData::clone() const
MWWorld::CustomData *ContainerCustomData::clone() const
{
return new CustomData (*this);
return new ContainerCustomData (*this);
}
}
@ -47,7 +47,7 @@ namespace MWClass
{
if (!ptr.getRefData().getCustomData())
{
std::auto_ptr<CustomData> data (new CustomData);
std::auto_ptr<ContainerCustomData> data (new ContainerCustomData);
MWWorld::LiveCellRef<ESM::Container> *ref =
ptr.get<ESM::Container>();
@ -174,7 +174,7 @@ namespace MWClass
{
ensureCustomData (ptr);
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
return dynamic_cast<ContainerCustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
}
std::string Container::getScript (const MWWorld::Ptr& ptr) const
@ -267,7 +267,7 @@ namespace MWClass
ensureCustomData (ptr);
dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore.
dynamic_cast<ContainerCustomData&> (*ptr.getRefData().getCustomData()).mContainerStore.
readState (state2.mInventory);
}
@ -278,7 +278,7 @@ namespace MWClass
ensureCustomData (ptr);
dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore.
dynamic_cast<ContainerCustomData&> (*ptr.getRefData().getCustomData()).mContainerStore.
writeState (state2.mInventory);
}
}

@ -7,6 +7,7 @@
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/magiceffects.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/disease.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwbase/environment.hpp"
@ -36,7 +37,7 @@
namespace
{
struct CustomData : public MWWorld::CustomData
struct CreatureCustomData : public MWWorld::CustomData
{
MWMechanics::CreatureStats mCreatureStats;
MWWorld::ContainerStore* mContainerStore; // may be InventoryStore for some creatures
@ -44,13 +45,13 @@ namespace
virtual MWWorld::CustomData *clone() const;
CustomData() : mContainerStore(0) {}
virtual ~CustomData() { delete mContainerStore; }
CreatureCustomData() : mContainerStore(0) {}
virtual ~CreatureCustomData() { delete mContainerStore; }
};
MWWorld::CustomData *CustomData::clone() const
MWWorld::CustomData *CreatureCustomData::clone() const
{
CustomData* cloned = new CustomData (*this);
CreatureCustomData* cloned = new CreatureCustomData (*this);
cloned->mContainerStore = mContainerStore->clone();
return cloned;
}
@ -62,7 +63,7 @@ namespace MWClass
{
if (!ptr.getRefData().getCustomData())
{
std::auto_ptr<CustomData> data (new CustomData);
std::auto_ptr<CreatureCustomData> data (new CreatureCustomData);
static bool inited = false;
if(!inited)
@ -191,7 +192,7 @@ namespace MWClass
{
ensureCustomData (ptr);
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mCreatureStats;
return dynamic_cast<CreatureCustomData&> (*ptr.getRefData().getCustomData()).mCreatureStats;
}
@ -243,15 +244,7 @@ namespace MWClass
Ogre::Vector3 hitPosition = result.second;
MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim);
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
float hitchance = ref->mBase->mData.mCombat +
(stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) +
(stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
hitchance *= stats.getFatigueTerm();
hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude -
mageffects.get(ESM::MagicEffect::Blind).mMagnitude;
hitchance -= otherstats.getEvasion();
float hitchance = MWMechanics::getHitChance(ptr, victim, ref->mBase->mData.mCombat);
if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
{
@ -334,6 +327,8 @@ namespace MWClass
if (damage > 0)
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
MWMechanics::diseaseContact(victim, ptr);
victim.getClass().onHit(victim, damage, true, weapon, ptr, true);
}
@ -461,7 +456,7 @@ namespace MWClass
{
ensureCustomData (ptr);
return *dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
return *dynamic_cast<CreatureCustomData&> (*ptr.getRefData().getCustomData()).mContainerStore;
}
MWWorld::InventoryStore& Creature::getInventoryStore(const MWWorld::Ptr &ptr) const
@ -530,7 +525,7 @@ namespace MWClass
float moveSpeed;
if(normalizedEncumbrance >= 1.0f)
moveSpeed = 0.0f;
else if(isFlying(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 &&
else if(canFly(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 &&
world->isLevitationEnabled()))
{
float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() +
@ -564,7 +559,7 @@ namespace MWClass
{
ensureCustomData (ptr);
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mMovement;
return dynamic_cast<CreatureCustomData&> (*ptr.getRefData().getCustomData()).mMovement;
}
Ogre::Vector3 Creature::getMovementVector (const MWWorld::Ptr& ptr) const
@ -683,7 +678,15 @@ namespace MWClass
return MWWorld::Ptr(&cell.get<ESM::Creature>().insert(*ref), &cell);
}
bool Creature::isFlying(const MWWorld::Ptr &ptr) const
bool Creature::isBipedal(const MWWorld::Ptr &ptr) const
{
MWWorld::LiveCellRef<ESM::Creature> *ref =
ptr.get<ESM::Creature>();
return ref->mBase->mFlags & ESM::Creature::Bipedal;
}
bool Creature::canFly(const MWWorld::Ptr &ptr) const
{
MWWorld::LiveCellRef<ESM::Creature> *ref =
ptr.get<ESM::Creature>();
@ -691,6 +694,22 @@ namespace MWClass
return ref->mBase->mFlags & ESM::Creature::Flies;
}
bool Creature::canSwim(const MWWorld::Ptr &ptr) const
{
MWWorld::LiveCellRef<ESM::Creature> *ref =
ptr.get<ESM::Creature>();
return ref->mBase->mFlags & ESM::Creature::Swims;
}
bool Creature::canWalk(const MWWorld::Ptr &ptr) const
{
MWWorld::LiveCellRef<ESM::Creature> *ref =
ptr.get<ESM::Creature>();
return ref->mBase->mFlags & ESM::Creature::Walks;
}
int Creature::getSndGenTypeFromName(const MWWorld::Ptr &ptr, const std::string &name)
{
if(name == "left")
@ -767,7 +786,7 @@ namespace MWClass
ensureCustomData (ptr);
CustomData& customData = dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData());
CreatureCustomData& customData = dynamic_cast<CreatureCustomData&> (*ptr.getRefData().getCustomData());
customData.mContainerStore->readState (state2.mInventory);
customData.mCreatureStats.readState (state2.mCreatureStats);
@ -781,7 +800,7 @@ namespace MWClass
ensureCustomData (ptr);
CustomData& customData = dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData());
CreatureCustomData& customData = dynamic_cast<CreatureCustomData&> (*ptr.getRefData().getCustomData());
customData.mContainerStore->writeState (state2.mInventory);
customData.mCreatureStats.writeState (state2.mCreatureStats);

@ -124,7 +124,10 @@ namespace MWClass
return true;
}
virtual bool isFlying (const MWWorld::Ptr &ptr) const;
virtual bool isBipedal (const MWWorld::Ptr &ptr) const;
virtual bool canFly (const MWWorld::Ptr &ptr) const;
virtual bool canSwim (const MWWorld::Ptr &ptr) const;
virtual bool canWalk (const MWWorld::Ptr &ptr) const;
virtual int getSkill(const MWWorld::Ptr &ptr, int skill) const;

@ -9,15 +9,15 @@
namespace
{
struct CustomData : public MWWorld::CustomData
struct CreatureLevListCustomData : public MWWorld::CustomData
{
// TODO: save the creature we spawned here
virtual MWWorld::CustomData *clone() const;
};
MWWorld::CustomData *CustomData::clone() const
MWWorld::CustomData *CreatureLevListCustomData::clone() const
{
return new CustomData (*this);
return new CreatureLevListCustomData (*this);
}
}
@ -44,7 +44,7 @@ namespace MWClass
{
if (!ptr.getRefData().getCustomData())
{
std::auto_ptr<CustomData> data (new CustomData);
std::auto_ptr<CreatureLevListCustomData> data (new CreatureLevListCustomData);
MWWorld::LiveCellRef<ESM::CreatureLevList> *ref =
ptr.get<ESM::CreatureLevList>();
@ -57,7 +57,7 @@ namespace MWClass
MWWorld::ManualRef ref(store, id);
ref.getPtr().getCellRef().mPos = ptr.getCellRef().mPos;
// TODO: hold on to this for respawn purposes later
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), *ptr.getCell() , ptr.getCellRef().mPos);
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().mPos);
}
ptr.getRefData().setCustomData(data.release());

@ -26,12 +26,12 @@
namespace
{
struct CustomData : public MWWorld::CustomData
struct LightCustomData : public MWWorld::CustomData
{
float mTime;
///< Time remaining
CustomData(MWWorld::Ptr ptr)
LightCustomData(MWWorld::Ptr ptr)
{
MWWorld::LiveCellRef<ESM::Light> *ref = ptr.get<ESM::Light>();
mTime = ref->mBase->mData.mTime;
@ -40,7 +40,7 @@ namespace
virtual MWWorld::CustomData *clone() const
{
return new CustomData (*this);
return new LightCustomData (*this);
}
};
}
@ -210,7 +210,7 @@ namespace MWClass
{
ensureCustomData(ptr);
float &timeRemaining = dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mTime;
float &timeRemaining = dynamic_cast<LightCustomData&> (*ptr.getRefData().getCustomData()).mTime;
timeRemaining = duration;
}
@ -218,7 +218,7 @@ namespace MWClass
{
ensureCustomData(ptr);
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mTime;
return dynamic_cast<LightCustomData&> (*ptr.getRefData().getCustomData()).mTime;
}
MWWorld::Ptr
@ -233,7 +233,7 @@ namespace MWClass
void Light::ensureCustomData (const MWWorld::Ptr& ptr) const
{
if (!ptr.getRefData().getCustomData())
ptr.getRefData().setCustomData(new CustomData(ptr));
ptr.getRefData().setCustomData(new LightCustomData(ptr));
}
bool Light::canSell (const MWWorld::Ptr& item, int npcServices) const
@ -278,7 +278,7 @@ namespace MWClass
ensureCustomData (ptr);
dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mTime = state2.mTime;
dynamic_cast<LightCustomData&> (*ptr.getRefData().getCustomData()).mTime = state2.mTime;
}
void Light::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state)
@ -288,6 +288,6 @@ namespace MWClass
ensureCustomData (ptr);
state2.mTime = dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mTime;
state2.mTime = dynamic_cast<LightCustomData&> (*ptr.getRefData().getCustomData()).mTime;
}
}

@ -39,7 +39,7 @@
namespace
{
struct CustomData : public MWWorld::CustomData
struct NpcCustomData : public MWWorld::CustomData
{
MWMechanics::NpcStats mNpcStats;
MWMechanics::Movement mMovement;
@ -48,9 +48,9 @@ namespace
virtual MWWorld::CustomData *clone() const;
};
MWWorld::CustomData *CustomData::clone() const
MWWorld::CustomData *NpcCustomData::clone() const
{
return new CustomData (*this);
return new NpcCustomData (*this);
}
void autoCalculateAttributes (const ESM::NPC* npc, MWMechanics::CreatureStats& creatureStats)
@ -262,7 +262,7 @@ namespace MWClass
}
if (!ptr.getRefData().getCustomData())
{
std::auto_ptr<CustomData> data(new CustomData);
std::auto_ptr<NpcCustomData> data(new NpcCustomData);
MWWorld::LiveCellRef<ESM::NPC> *ref = ptr.get<ESM::NPC>();
@ -436,14 +436,14 @@ namespace MWClass
{
ensureCustomData (ptr);
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
}
MWMechanics::NpcStats& Npc::getNpcStats (const MWWorld::Ptr& ptr) const
{
ensureCustomData (ptr);
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mNpcStats;
}
@ -498,15 +498,7 @@ namespace MWClass
if(!weapon.isEmpty())
weapskill = get(weapon).getEquipmentSkill(weapon);
MWMechanics::NpcStats &stats = getNpcStats(ptr);
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
float hitchance = stats.getSkill(weapskill).getModified() +
(stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) +
(stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
hitchance *= stats.getFatigueTerm();
hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude -
mageffects.get(ESM::MagicEffect::Blind).mMagnitude;
hitchance -= otherstats.getEvasion();
float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill));
if((::rand()/(RAND_MAX+1.0)) > hitchance/100.0f)
{
@ -516,6 +508,7 @@ namespace MWClass
bool healthdmg;
float damage = 0.0f;
MWMechanics::NpcStats &stats = getNpcStats(ptr);
if(!weapon.isEmpty())
{
const bool weaphashealth = get(weapon).hasItemHealth(weapon);
@ -615,6 +608,8 @@ namespace MWClass
if (healthdmg && damage > 0)
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
MWMechanics::diseaseContact(victim, ptr);
othercls.onHit(victim, damage, healthdmg, weapon, ptr, true);
}
@ -648,9 +643,6 @@ namespace MWClass
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
}
if (!attacker.isEmpty())
MWMechanics::diseaseContact(ptr, attacker);
if (damage > 0.0f && !object.isEmpty())
MWMechanics::resistNormalWeapon(ptr, attacker, object, damage);
@ -681,12 +673,7 @@ namespace MWClass
else
getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur?
if(object.isEmpty())
{
if(ishealth)
damage /= std::min(1.0f + getArmorRating(ptr)/std::max(1.0f, damage), 4.0f);
}
else if(ishealth)
if(ishealth)
{
// Hit percentages:
// cuirass = 30%
@ -832,7 +819,7 @@ namespace MWClass
{
ensureCustomData (ptr);
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mInventoryStore;
return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mInventoryStore;
}
MWWorld::InventoryStore& Npc::getInventoryStore (const MWWorld::Ptr& ptr)
@ -840,7 +827,7 @@ namespace MWClass
{
ensureCustomData (ptr);
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mInventoryStore;
return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mInventoryStore;
}
std::string Npc::getScript (const MWWorld::Ptr& ptr) const
@ -854,7 +841,7 @@ namespace MWClass
float Npc::getSpeed(const MWWorld::Ptr& ptr) const
{
const MWBase::World *world = MWBase::Environment::get().getWorld();
const CustomData *npcdata = static_cast<const CustomData*>(ptr.getRefData().getCustomData());
const NpcCustomData *npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData());
const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects();
const float normalizedEncumbrance = Npc::getEncumbrance(ptr) / Npc::getCapacity(ptr);
@ -909,7 +896,7 @@ namespace MWClass
float Npc::getJump(const MWWorld::Ptr &ptr) const
{
const CustomData *npcdata = static_cast<const CustomData*>(ptr.getRefData().getCustomData());
const NpcCustomData *npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData());
const MWMechanics::MagicEffects &mageffects = npcdata->mNpcStats.getMagicEffects();
const float encumbranceTerm = fJumpEncumbranceBase->getFloat() +
fJumpEncumbranceMultiplier->getFloat() *
@ -948,7 +935,7 @@ namespace MWClass
if (fallHeight >= fallDistanceMin)
{
const float acrobaticsSkill = MWWorld::Class::get(ptr).getNpcStats (ptr).getSkill(ESM::Skill::Acrobatics).getModified();
const CustomData *npcdata = static_cast<const CustomData*>(ptr.getRefData().getCustomData());
const NpcCustomData *npcdata = static_cast<const NpcCustomData*>(ptr.getRefData().getCustomData());
const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(ESM::MagicEffect::Jump).mMagnitude;
const float fallAcroBase = gmst.find("fFallAcroBase")->getFloat();
const float fallAcroMult = gmst.find("fFallAcroMult")->getFloat();
@ -973,7 +960,7 @@ namespace MWClass
{
ensureCustomData (ptr);
return dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData()).mMovement;
return dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData()).mMovement;
}
Ogre::Vector3 Npc::getMovementVector (const MWWorld::Ptr& ptr) const
@ -1279,7 +1266,7 @@ namespace MWClass
ensureCustomData (ptr);
CustomData& customData = dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData());
NpcCustomData& customData = dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData());
customData.mInventoryStore.readState (state2.mInventory);
customData.mNpcStats.readState (state2.mNpcStats);
@ -1293,7 +1280,7 @@ namespace MWClass
ensureCustomData (ptr);
CustomData& customData = dynamic_cast<CustomData&> (*ptr.getRefData().getCustomData());
NpcCustomData& customData = dynamic_cast<NpcCustomData&> (*ptr.getRefData().getCustomData());
customData.mInventoryStore.writeState (state2.mInventory);
customData.mNpcStats.writeState (state2.mNpcStats);

@ -26,16 +26,6 @@ namespace
return path;
}
std::string getCountString(const int count)
{
if (count == 1)
return "";
if (count > 9999)
return boost::lexical_cast<std::string>(int(count/1000.f)) + "k";
else
return boost::lexical_cast<std::string>(count);
}
}
namespace MWGui
@ -226,7 +216,7 @@ namespace MWGui
text->setNeedMouseFocus(false);
text->setTextShadow(true);
text->setTextShadowColour(MyGUI::Colour(0,0,0));
text->setCaption(getCountString(ingredient->getUserData<MWWorld::Ptr>()->getRefData().getCount()));
text->setCaption(ItemView::getCountString(ingredient->getUserData<MWWorld::Ptr>()->getRefData().getCount()));
}
mItemView->update();

@ -23,19 +23,6 @@
#include "sortfilteritemmodel.hpp"
#include "pickpocketitemmodel.hpp"
namespace
{
std::string getCountString(const int count)
{
if (count == 1)
return "";
if (count > 9999)
return boost::lexical_cast<std::string>(int(count/1000.f)) + "k";
else
return boost::lexical_cast<std::string>(count);
}
}
namespace MWGui
{
@ -79,7 +66,7 @@ namespace MWGui
text->setNeedMouseFocus(false);
text->setTextShadow(true);
text->setTextShadowColour(MyGUI::Colour(0,0,0));
text->setCaption(getCountString(count));
text->setCaption(ItemView::getCountString(count));
sourceView->update();

@ -23,18 +23,7 @@
#include "travelwindow.hpp"
#include "bookpage.hpp"
namespace
{
MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text)
{
typedef MWGui::BookTypesetter::Utf8Point point;
point begin = reinterpret_cast <point> (text);
return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text));
}
}
#include "journalbooks.hpp" // to_utf8_span
namespace MWGui
{

@ -604,15 +604,22 @@ namespace MWGui
mEffectBox->setPosition((viewSize.width - mEffectBoxBaseRight) - mEffectBox->getWidth() + effectsDx, mEffectBox->getTop());
}
void HUD::updateEnemyHealthBar()
{
MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy);
mEnemyHealth->setProgressRange(100);
// Health is usually cast to int before displaying. Actors die whenever they are < 1 health.
// Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :)
mEnemyHealth->setProgressPosition(int(stats.getHealth().getCurrent()) / stats.getHealth().getModified() * 100);
}
void HUD::update()
{
mSpellIcons->updateWidgets(mEffectBox, true);
if (!mEnemy.isEmpty() && mEnemyHealth->getVisible())
{
MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy);
mEnemyHealth->setProgressRange(100);
mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100);
updateEnemyHealthBar();
}
if (mIsDrowning)
@ -629,9 +636,7 @@ namespace MWGui
if (!mEnemyHealth->getVisible())
mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0,20));
mEnemyHealth->setVisible(true);
MWMechanics::CreatureStats& stats = MWWorld::Class::get(mEnemy).getCreatureStats(mEnemy);
mEnemyHealth->setProgressRange(100);
mEnemyHealth->setProgressPosition(stats.getHealth().getCurrent() / stats.getHealth().getModified() * 100);
updateEnemyHealthBar();
}
}

@ -1,3 +1,6 @@
#ifndef OPENMW_GAME_MWGUI_HUD_H
#define OPENMW_GAME_MWGUI_HUD_H
#include "mapwindow.hpp"
#include "../mwmechanics/stat.hpp"
@ -112,6 +115,10 @@ namespace MWGui
void onMagicClicked(MyGUI::Widget* _sender);
void onMapClicked(MyGUI::Widget* _sender);
void updateEnemyHealthBar();
void updatePositions();
};
}
#endif

@ -514,6 +514,9 @@ namespace MWGui
void InventoryWindow::pickUpObject (MWWorld::Ptr object)
{
// If the inventory is not yet enabled, don't pick anything up
if (!MWBase::Environment::get().getWindowManager()->isAllowed(GW_Inventory))
return;
// make sure the object is of a type that can be picked up
std::string type = object.getTypeName();
if ( (type != typeid(ESM::Apparatus).name())

@ -1,3 +1,6 @@
#ifndef OPENMW_GAME_MWGUI_ITEMSELECTION_H
#define OPENMW_GAME_MWGUI_ITEMSELECTION_H
#include "container.hpp"
namespace MWGui
@ -32,3 +35,5 @@ namespace MWGui
};
}
#endif

@ -13,22 +13,18 @@
#include "itemmodel.hpp"
namespace
namespace MWGui
{
std::string getCountString(const int count)
{
if (count == 1)
return "";
if (count > 9999)
return boost::lexical_cast<std::string>(int(count/1000.f)) + "k";
else
return boost::lexical_cast<std::string>(count);
}
}
namespace MWGui
std::string ItemView::getCountString(int count)
{
if (count == 1)
return "";
if (count > 9999)
return boost::lexical_cast<std::string>(int(count/1000.f)) + "k";
else
return boost::lexical_cast<std::string>(count);
}
ItemView::ItemView()
: mModel(NULL)

@ -30,6 +30,8 @@ namespace MWGui
void update();
static std::string getCountString(int count);
private:
virtual void initialiseOverride();

@ -2,15 +2,6 @@
namespace
{
MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text)
{
typedef MWGui::BookTypesetter::Utf8Point point;
point begin = reinterpret_cast <point> (text);
return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text));
}
const MyGUI::Colour linkHot (0.40f, 0.40f, 0.80f);
const MyGUI::Colour linkNormal (0.20f, 0.20f, 0.60f);
const MyGUI::Colour linkActive (0.50f, 0.50f, 1.00f);
@ -178,6 +169,15 @@ namespace
namespace MWGui
{
MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text)
{
typedef MWGui::BookTypesetter::Utf8Point point;
point begin = reinterpret_cast <point> (text);
return MWGui::BookTypesetter::Utf8Span (begin, begin + strlen (text));
}
typedef TypesetBook::Ptr book;
JournalBooks::JournalBooks (JournalViewModel::Ptr model) :

@ -6,6 +6,8 @@
namespace MWGui
{
MWGui::BookTypesetter::Utf8Span to_utf8_span (char const * text);
struct JournalBooks
{
typedef TypesetBook::Ptr Book;

@ -1,5 +1,7 @@
#include "mainmenu.hpp"
#include <components/version/version.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
@ -20,6 +22,22 @@ namespace MWGui
, mButtonBox(0), mWidth (w), mHeight (h)
, mSaveGameDialog(NULL)
{
getWidget(mVersionText, "VersionText");
std::stringstream sstream;
sstream << "OpenMW version: " << OPENMW_VERSION;
// adding info about git hash if availible
std::string rev = OPENMW_VERSION_COMMITHASH;
std::string tag = OPENMW_VERSION_TAGHASH;
if (!rev.empty() && !tag.empty())
{
rev = rev.substr(0,10);
sstream << "\nrevision: " << rev;
}
std::string output = sstream.str();
mVersionText->setCaption(output);
updateMenu();
}

@ -1,3 +1,6 @@
#ifndef OPENMW_GAME_MWGUI_MAINMENU_H
#define OPENMW_GAME_MWGUI_MAINMENU_H
#include <openengine/gui/layout.hpp>
#include "imagebutton.hpp"
@ -24,6 +27,7 @@ namespace MWGui
private:
MyGUI::Widget* mButtonBox;
MyGUI::TextBox* mVersionText;
std::map<std::string, MWGui::ImageButton*> mButtons;
@ -35,3 +39,5 @@ namespace MWGui
};
}
#endif

@ -19,28 +19,8 @@
#include "windowmanagerimp.hpp"
#include "itemselection.hpp"
#include "spellwindow.hpp"
namespace
{
bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right)
{
int cmp = left.getClass().getName(left).compare(
right.getClass().getName(right));
return cmp < 0;
}
bool sortSpells(const std::string& left, const std::string& right)
{
const MWWorld::Store<ESM::Spell> &spells =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>();
const ESM::Spell* a = spells.find(left);
const ESM::Spell* b = spells.find(right);
int cmp = a->mName.compare(b->mName);
return cmp < 0;
}
}
namespace MWGui
{

@ -41,9 +41,13 @@ namespace MWGui
getWidget(mPreviewImage, "PreviewImage");
getWidget(mHeadRotate, "HeadRotate");
mHeadRotate->setScrollRange(50);
mHeadRotate->setScrollPosition(25);
mHeadRotate->setScrollViewPage(10);
// Mouse wheel step is hardcoded to 50 in MyGUI 3.2 ("FIXME").
// Give other steps the same value to accomodate.
mHeadRotate->setScrollRange(1000);
mHeadRotate->setScrollPosition(500);
mHeadRotate->setScrollViewPage(50);
mHeadRotate->setScrollPage(50);
mHeadRotate->eventScrollChangePosition += MyGUI::newDelegate(this, &RaceDialog::onHeadRotate);
// Set up next/previous buttons
@ -171,9 +175,9 @@ namespace MWGui
eventBack();
}
void RaceDialog::onHeadRotate(MyGUI::ScrollBar*, size_t _position)
void RaceDialog::onHeadRotate(MyGUI::ScrollBar* scroll, size_t _position)
{
float angle = (float(_position) / 49.f - 0.5) * 3.14 * 2;
float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5) * 3.14 * 2;
float diff = angle - mCurrentAngle;
mPreview->update (diff);
mPreviewDirty = true;

@ -18,8 +18,16 @@
#include "inventorywindow.hpp"
#include "confirmationdialog.hpp"
namespace
namespace MWGui
{
bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right)
{
int cmp = left.getClass().getName(left).compare(
right.getClass().getName(right));
return cmp < 0;
}
bool sortSpells(const std::string& left, const std::string& right)
{
const MWWorld::Store<ESM::Spell> &spells =
@ -32,16 +40,6 @@ namespace
return cmp < 0;
}
bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right)
{
int cmp = MWWorld::Class::get(left).getName(left).compare(
MWWorld::Class::get(right).getName(right));
return cmp < 0;
}
}
namespace MWGui
{
SpellWindow::SpellWindow(DragAndDrop* drag)
: WindowPinnableBase("openmw_spell_window.layout")
, NoDrop(drag, mMainWidget)

@ -2,11 +2,16 @@
#define MWGUI_SPELLWINDOW_H
#include "windowpinnablebase.hpp"
#include "../mwworld/ptr.hpp"
namespace MWGui
{
class SpellIcons;
bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right);
bool sortSpells(const std::string& left, const std::string& right);
class SpellWindow : public WindowPinnableBase, public NoDrop
{
public:

@ -143,6 +143,13 @@ namespace MWInput
mControlSwitch["vanitymode"] = true;
}
void InputManager::clear()
{
// Enable all controls
for (std::map<std::string, bool>::iterator it = mControlSwitch.begin(); it != mControlSwitch.end(); ++it)
it->second = true;
}
InputManager::~InputManager()
{
mInputBinder->save (mUserFile);
@ -578,13 +585,13 @@ namespace MWInput
float rot[3];
rot[0] = -y;
rot[1] = 0.0f;
rot[2] = x;
rot[2] = -x;
// Only actually turn player when we're not in vanity mode
if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot))
{
mPlayer->yaw(x);
mPlayer->pitch(-y);
mPlayer->pitch(y);
}
if (arg.zrel && mControlSwitch["playerviewswitch"]) //Check to make sure you are allowed to zoomout and there is a change

@ -65,6 +65,9 @@ namespace MWInput
virtual ~InputManager();
/// Clear all savegame-specific data
virtual void clear();
virtual void update(float dt, bool loading);
void setPlayer (MWWorld::Player* player) { mPlayer = player; }

@ -210,7 +210,7 @@ namespace MWMechanics
&& LOS
)
{
creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()));
creatureStats.getAiSequence().stack(AiCombat(MWBase::Environment::get().getWorld()->getPlayerPtr()));
creatureStats.setHostile(true);
}
}
@ -541,7 +541,7 @@ namespace MWMechanics
// TODO: Add AI to follow player and fight for him
// TODO: VFX_SummonStart, VFX_SummonEnd
creatureStats.mSummonedCreatures.insert(std::make_pair(it->first,
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle()));
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos).getRefData().getHandle()));
}
}
else

@ -10,16 +10,6 @@
#include "steering.hpp"
#include "movement.hpp"
namespace
{
float sgn(float a)
{
if(a > 0)
return 1.0;
return -1.0;
}
}
MWMechanics::AiActivate::AiActivate(const std::string &objectId)
: mObjectId(objectId)
{
@ -38,7 +28,7 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration)
MWWorld::Ptr player = world->getPlayerPtr();
if(cell->mData.mX != player.getCell()->getCell()->mData.mX)
{
int sideX = sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX);
int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) >
sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
@ -49,7 +39,7 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration)
}
if(cell->mData.mY != player.getCell()->getCell()->mData.mY)
{
int sideY = sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY);
int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) >
sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f))

@ -11,16 +11,6 @@
#include "steering.hpp"
#include "movement.hpp"
namespace
{
float sgn(float a)
{
if(a > 0)
return 1.0;
return -1.0;
}
}
/*
TODO: Test vanilla behavior on passing x0, y0, and z0 with duration of anything including 0.
TODO: Different behavior for AIEscort a d x y z and AIEscortCell a c d x y z.
@ -91,7 +81,7 @@ namespace MWMechanics
if(actor.getCell()->getCell()->mData.mX != player.getCell()->getCell()->mData.mX)
{
int sideX = sgn(actor.getCell()->getCell()->mData.mX - player.getCell()->getCell()->mData.mX);
int sideX = PathFinder::sgn(actor.getCell()->getCell()->mData.mX - player.getCell()->getCell()->mData.mX);
// Check if actor is near the border of an inactive cell. If so, pause walking.
if(sideX * (pos.pos[0] - actor.getCell()->getCell()->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE /
2.0 - 200))
@ -102,7 +92,7 @@ namespace MWMechanics
}
if(actor.getCell()->getCell()->mData.mY != player.getCell()->getCell()->mData.mY)
{
int sideY = sgn(actor.getCell()->getCell()->mData.mY - player.getCell()->getCell()->mData.mY);
int sideY = PathFinder::sgn(actor.getCell()->getCell()->mData.mY - player.getCell()->getCell()->mData.mY);
// Check if actor is near the border of an inactive cell. If so, pause walking.
if(sideY*(pos.pos[1] - actor.getCell()->getCell()->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE /
2.0 - 200))

@ -9,16 +9,6 @@
#include "steering.hpp"
#include "movement.hpp"
namespace
{
float sgn(float a)
{
if(a > 0)
return 1.0;
return -1.0;
}
}
namespace MWMechanics
{
AiTravel::AiTravel(float x, float y, float z)
@ -43,7 +33,7 @@ namespace MWMechanics
MWWorld::Ptr player = world->getPlayerPtr();
if(cell->mData.mX != player.getCell()->getCell()->mData.mX)
{
int sideX = sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX);
int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) >
sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
@ -54,7 +44,7 @@ namespace MWMechanics
}
if(cell->mData.mY != player.getCell()->getCell()->mData.mY)
{
int sideY = sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY);
int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) >
sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f))

@ -15,18 +15,13 @@
#include "steering.hpp"
#include "movement.hpp"
namespace
{
float sgn(float a)
{
if(a > 0)
return 1.0;
return -1.0;
}
}
namespace MWMechanics
{
// NOTE: determined empirically but probably need further tweaking
static const int COUNT_BEFORE_STUCK = 20;
static const int COUNT_BEFORE_RESET = 200;
static const int COUNT_EVADE = 7;
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<int>& idle, bool repeat):
mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat)
, mCellX(std::numeric_limits<int>::max())
@ -36,6 +31,11 @@ namespace MWMechanics
, mX(0)
, mY(0)
, mZ(0)
, mPrevX(0)
, mPrevY(0)
, mWalkState(State_Norm)
, mStuckCount(0)
, mEvadeCount(0)
, mSaidGreeting(false)
{
for(unsigned short counter = 0; counter < mIdle.size(); counter++)
@ -298,9 +298,91 @@ namespace MWMechanics
}
else
{
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
/* 1 n
* State_Norm <---> State_CheckStuck --> State_Evade
* ^ ^ | ^ | ^ | |
* | | | | | | | |
* | +---+ +---+ +---+ | m
* | any < n < m |
* +--------------------------------------------+
*/
bool samePosition = (abs(pos.pos[0] - mPrevX) < 1) && (abs(pos.pos[1] - mPrevY) < 1);
switch(mWalkState)
{
case State_Norm:
{
if(!samePosition)
break;
else
mWalkState = State_CheckStuck;
}
/* FALL THROUGH */
case State_CheckStuck:
{
if(!samePosition)
{
mWalkState = State_Norm;
// to do this properly need yet another variable, simply don't clear for now
//mStuckCount = 0;
break;
}
else
{
// consider stuck only if position unchanges consecutively
if((mStuckCount++ % COUNT_BEFORE_STUCK) == 0)
mWalkState = State_Evade;
// NOTE: mStuckCount is purposely not cleared here
else
break; // still in the same state, but counter got incremented
}
}
/* FALL THROUGH */
case State_Evade:
{
if(mEvadeCount++ < COUNT_EVADE)
break;
else
{
mWalkState = State_Norm; // tried to evade, assume all is ok and start again
// NOTE: mStuckCount is purposely not cleared here
mEvadeCount = 0;
}
}
/* NO DEFAULT CASE */
}
if(mWalkState == State_Evade)
{
//std::cout << "Stuck \""<<actor.getClass().getName(actor)<<"\"" << std::endl;
// diagonal should have same animation as walk forward
actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
actor.getClass().getMovementSettings(actor).mPosition[1] = 0.01f;
// change the angle a bit, too
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1])));
}
else
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
}
if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset
{
//std::cout << "Reset \""<<actor.getClass().getName(actor)<<"\"" << std::endl;
mWalkState = State_Norm;
mStuckCount = 0;
stopWalking(actor);
mMoveNow = false;
mWalking = false;
mChooseAction = true;
}
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
// update position
ESM::Position updatedPos = actor.getRefData().getPosition();
mPrevX = updatedPos.pos[0];
mPrevY = updatedPos.pos[1];
}
}

@ -43,6 +43,21 @@ namespace MWMechanics
float mXCell;
float mYCell;
// for checking if we're stuck (but don't check Z axis)
float mPrevX;
float mPrevY;
enum WalkState
{
State_Norm,
State_CheckStuck,
State_Evade
};
WalkState mWalkState;
int mStuckCount;
int mEvadeCount;
bool mStoredAvailableNodes;
bool mChooseAction;
bool mIdleNow;

@ -313,6 +313,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
mAnimation->disable(mCurrentMovement);
mCurrentMovement = movement;
mMovementAnimVelocity = 0.0f;
if(!mCurrentMovement.empty())
{
float vel, speedmult = 1.0f;
@ -320,7 +321,10 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run);
if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f)
{
mMovementAnimVelocity = vel;
speedmult = mMovementSpeed / vel;
}
else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight)
speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed
else if (mMovementSpeed > 0.0f)
@ -330,10 +334,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f);
mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false,
speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
mMovementAnimVelocity = vel;
}
else mMovementAnimVelocity = 0.0f;
}
}
@ -1194,7 +1195,10 @@ void CharacterController::update(float duration)
: (sneak ? CharState_SneakBack
: (isrunning ? CharState_RunBack : CharState_WalkBack)));
}
else if(rot.z != 0.0f && !inwater && !sneak)
// Don't play turning animations during attack. It would break positioning of the arrow bone when releasing a shot.
// Actually, in vanilla the turning animation is not even played when merely having equipped the weapon,
// but I don't think we need to go as far as that.
else if(rot.z != 0.0f && !inwater && !sneak && mUpperBodyState < UpperCharState_StartToMinAttack)
{
if(rot.z > 0.0f)
movestate = CharState_TurnRight;

@ -4,9 +4,12 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
@ -17,14 +20,30 @@
namespace
{
Ogre::Radian signedAngle(Ogre::Vector3 v1, Ogre::Vector3 v2, Ogre::Vector3 n)
Ogre::Radian signedAngle(Ogre::Vector3 v1, Ogre::Vector3 v2, Ogre::Vector3 normal)
{
return Ogre::Math::ATan2(
n.dotProduct( v1.crossProduct(v2) ),
normal.dotProduct( v1.crossProduct(v2) ),
v1.dotProduct(v2)
);
}
void applyEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const Ogre::Vector3& hitPosition)
{
std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : "";
if (!enchantmentName.empty())
{
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(
enchantmentName);
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
{
MWMechanics::CastSpell cast(attacker, victim);
cast.mHitPosition = hitPosition;
cast.cast(object);
}
}
}
}
namespace MWMechanics
@ -135,4 +154,94 @@ namespace MWMechanics
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResistsWeapons}");
}
void projectileHit(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, MWWorld::Ptr weapon, const MWWorld::Ptr &projectile,
const Ogre::Vector3& hitPosition)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
const MWWorld::Store<ESM::GameSetting> &gmst = world->getStore().get<ESM::GameSetting>();
MWMechanics::CreatureStats& attackerStats = attacker.getClass().getCreatureStats(attacker);
const MWWorld::Class &othercls = victim.getClass();
if(!othercls.isActor()) // Can't hit non-actors
return;
MWMechanics::CreatureStats &otherstats = victim.getClass().getCreatureStats(victim);
if(otherstats.isDead()) // Can't hit dead actors
return;
if(attacker.getRefData().getHandle() == "player")
MWBase::Environment::get().getWindowManager()->setEnemy(victim);
int weapskill = ESM::Skill::Marksman;
if(!weapon.isEmpty())
weapskill = weapon.getClass().getEquipmentSkill(weapon);
float skillValue = attacker.getClass().getSkill(attacker,
weapon.getClass().getEquipmentSkill(weapon));
if((::rand()/(RAND_MAX+1.0)) > getHitChance(attacker, victim, skillValue)/100.0f)
{
victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false);
return;
}
float damage = 0.0f;
float fDamageStrengthBase = gmst.find("fDamageStrengthBase")->getFloat();
float fDamageStrengthMult = gmst.find("fDamageStrengthMult")->getFloat();
const unsigned char* attack = weapon.get<ESM::Weapon>()->mBase->mData.mChop;
damage = attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength()); // Bow/crossbow damage
if (weapon != projectile)
{
// Arrow/bolt damage
attack = projectile.get<ESM::Weapon>()->mBase->mData.mChop;
damage += attack[0] + ((attack[1]-attack[0])*attackerStats.getAttackStrength());
}
damage *= fDamageStrengthBase +
(attackerStats.getAttribute(ESM::Attribute::Strength).getModified() * fDamageStrengthMult * 0.1);
if(attacker.getRefData().getHandle() == "player")
attacker.getClass().skillUsageSucceeded(attacker, weapskill, 0);
bool detected = MWBase::Environment::get().getMechanicsManager()->awarenessCheck(attacker, victim);
if(!detected)
{
damage *= gmst.find("fCombatCriticalStrikeMult")->getFloat();
MWBase::Environment::get().getWindowManager()->messageBox("#{sTargetCriticalStrike}");
MWBase::Environment::get().getSoundManager()->playSound3D(victim, "critical damage", 1.0f, 1.0f);
}
if (victim.getClass().getCreatureStats(victim).getKnockedDown())
damage *= gmst.find("fCombatKODamageMult")->getFloat();
// Apply "On hit" effect of the weapon
applyEnchantment(attacker, victim, weapon, hitPosition);
if (weapon != projectile)
applyEnchantment(attacker, victim, projectile, hitPosition);
if (damage > 0)
MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition);
float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat();
if ((::rand()/(RAND_MAX+1.0)) < fProjectileThrownStoreChance/100.f)
victim.getClass().getContainerStore(victim).add(projectile, 1, victim);
victim.getClass().onHit(victim, damage, true, projectile, attacker, true);
}
float getHitChance(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, int skillValue)
{
MWMechanics::CreatureStats &stats = attacker.getClass().getCreatureStats(attacker);
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
float hitchance = skillValue +
(stats.getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) +
(stats.getAttribute(ESM::Attribute::Luck).getModified() / 10.0f);
hitchance *= stats.getFatigueTerm();
hitchance += mageffects.get(ESM::MagicEffect::FortifyAttack).mMagnitude -
mageffects.get(ESM::MagicEffect::Blind).mMagnitude;
hitchance -= victim.getClass().getCreatureStats(victim).getEvasion();
return hitchance;
}
}

@ -2,6 +2,7 @@
#define OPENMW_MECHANICS_COMBAT_H
#include "../mwworld/ptr.hpp"
#include <OgreVector3.h>
namespace MWMechanics
{
@ -11,6 +12,13 @@ bool blockMeleeAttack (const MWWorld::Ptr& attacker, const MWWorld::Ptr& blocker
void resistNormalWeapon (const MWWorld::Ptr& actor, const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float& damage);
/// @note for a thrown weapon, \a weapon == \a projectile, for bows/crossbows, \a projectile is the arrow/bolt
void projectileHit (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWWorld::Ptr weapon, const MWWorld::Ptr& projectile,
const Ogre::Vector3& hitPosition);
/// Get the chance (in percent) for \a attacker to successfully hit \a victim with a given weapon skill value
float getHitChance (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, int skillValue);
}
#endif

@ -37,13 +37,6 @@ namespace
return sqrt(x * x + y * y + z * z);
}
static float sgn(Ogre::Radian a)
{
if(a.valueRadians() > 0)
return 1.0;
return -1.0;
}
int getClosestPoint(const ESM::Pathgrid* grid, float x, float y, float z)
{
if(!grid || grid->mPoints.empty())

@ -4,6 +4,8 @@
#include <components/esm/loadpgrd.hpp>
#include <list>
#include <OgreMath.h>
namespace MWWorld
{
class CellStore;
@ -16,6 +18,20 @@ namespace MWMechanics
public:
PathFinder();
static float sgn(Ogre::Radian a)
{
if(a.valueRadians() > 0)
return 1.0;
return -1.0;
}
static float sgn(float a)
{
if(a > 0)
return 1.0;
return -1.0;
}
void clearPath();
void buildPathgridGraph(const ESM::Pathgrid* pathGrid);

@ -587,7 +587,7 @@ namespace MWMechanics
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
}
MWBase::Environment::get().getWorld()->launchProjectile(mId, false, enchantment->mEffects, mCaster, mSourceName);
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, enchantment->mEffects, mCaster, mSourceName);
return true;
}
@ -666,7 +666,7 @@ namespace MWMechanics
}
}
MWBase::Environment::get().getWorld()->launchProjectile(mId, false, spell->mEffects, mCaster, mSourceName);
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, spell->mEffects, mCaster, mSourceName);
return true;
}

@ -70,7 +70,7 @@ namespace MWRender
if (!mVanity.enabled && !mPreviewMode) {
mCamera->getParentNode()->setOrientation(xr);
} else {
Ogre::Quaternion zr(Ogre::Radian(getYaw()), Ogre::Vector3::NEGATIVE_UNIT_Z);
Ogre::Quaternion zr(Ogre::Radian(getYaw()), Ogre::Vector3::UNIT_Z);
mCamera->getParentNode()->setOrientation(zr * xr);
}
}

@ -55,6 +55,8 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr)
updateParts();
}
mWeaponAnimationTime = Ogre::SharedPtr<WeaponAnimationTime>(new WeaponAnimationTime(this));
}
void CreatureWeaponAnimation::showWeapons(bool showWeapon)
@ -110,6 +112,20 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo
setRenderProperties(scene, RV_Actors, RQG_Main, RQG_Alpha, 0,
!item.getClass().getEnchantment(item).empty(), &glowColor);
// Crossbows start out with a bolt attached
if (slot == MWWorld::InventoryStore::Slot_CarriedRight &&
item.getTypeName() == typeid(ESM::Weapon).name() &&
item.get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
{
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if (ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt)
attachArrow();
else
mAmmunition.setNull();
}
else
mAmmunition.setNull();
if(scene->mSkelBase)
{
Ogre::SkeletonInstance *skel = scene->mSkelBase->getSkeleton();
@ -133,15 +149,42 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo
updateSkeletonInstance(mSkelBase->getSkeleton(), skel);
}
// TODO:
// type == ESM::PRT_Weapon should get an animation source based on the current offset
// of the weapon attack animation (from its beginning, or start marker?)
std::vector<Ogre::Controller<Ogre::Real> >::iterator ctrl(scene->mControllers.begin());
for(;ctrl != scene->mControllers.end();ctrl++)
{
if(ctrl->getSource().isNull())
ctrl->setSource(Ogre::SharedPtr<NullAnimationTime>(new NullAnimationTime()));
{
if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
ctrl->setSource(mWeaponAnimationTime);
else
ctrl->setSource(Ogre::SharedPtr<NullAnimationTime>(new NullAnimationTime()));
}
}
}
void CreatureWeaponAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot)
{
Ogre::Vector3 glowColor = getEnchantmentColor(ptr);
setRenderProperties(object, RV_Actors, RQG_Main, RQG_Alpha, 0,
!ptr.getClass().getEnchantment(ptr).empty(), &glowColor);
}
void CreatureWeaponAnimation::attachArrow()
{
WeaponAnimation::attachArrow(mPtr);
}
void CreatureWeaponAnimation::releaseArrow()
{
WeaponAnimation::releaseArrow(mPtr);
}
Ogre::Vector3 CreatureWeaponAnimation::runAnimation(float duration)
{
Ogre::Vector3 ret = Animation::runAnimation(duration);
pitchSkeleton(mPtr.getRefData().getPosition().rot[0], mSkelBase->getSkeleton());
return ret;
}
}

@ -2,6 +2,7 @@
#define GAME_RENDER_CREATUREANIMATION_H
#include "animation.hpp"
#include "weaponanimation.hpp"
#include "../mwworld/inventorystore.hpp"
namespace MWWorld
@ -21,7 +22,7 @@ namespace MWRender
// For creatures with weapons and shields
// Animation is already virtual anyway, so might as well make a separate class.
// Most creatures don't need weapons/shields, so this will save some memory.
class CreatureWeaponAnimation : public Animation, public MWWorld::InventoryStoreListener
class CreatureWeaponAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener
{
public:
CreatureWeaponAnimation(const MWWorld::Ptr& ptr);
@ -36,11 +37,29 @@ namespace MWRender
void updatePart(NifOgre::ObjectScenePtr& scene, int slot);
virtual void attachArrow();
virtual void releaseArrow();
virtual Ogre::Vector3 runAnimation(float duration);
/// A relative factor (0-1) that decides if and how much the skeleton should be pitched
/// to indicate the facing orientation of the character.
virtual void setPitchFactor(float factor) { mPitchFactor = factor; }
virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); }
// WeaponAnimation
virtual NifOgre::ObjectScenePtr getWeapon() { return mWeapon; }
virtual void showWeapon(bool show) { showWeapons(show); }
virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot);
private:
NifOgre::ObjectScenePtr mWeapon;
NifOgre::ObjectScenePtr mShield;
bool mShowWeapons;
bool mShowCarriedLeft;
Ogre::SharedPtr<WeaponAnimationTime> mWeaponAnimationTime;
};
}

@ -76,27 +76,6 @@ float HeadAnimationTime::getValue() const
return 1;
}
float WeaponAnimationTime::getValue() const
{
if (mWeaponGroup.empty())
return 0;
float current = mAnimation->getCurrentTime(mWeaponGroup);
if (current == -1)
return 0;
return current - mStartTime;
}
void WeaponAnimationTime::setGroup(const std::string &group)
{
mWeaponGroup = group;
mStartTime = mAnimation->getStartTime(mWeaponGroup);
}
void WeaponAnimationTime::updateStartTime()
{
setGroup(mWeaponGroup);
}
static NpcAnimation::PartBoneMap createPartListMap()
{
NpcAnimation::PartBoneMap result;
@ -147,8 +126,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v
mShowCarriedLeft(true),
mFirstPersonOffset(0.f, 0.f, 0.f),
mAlpha(1.f),
mNpcType(Type_Normal),
mPitchFactor(0)
mNpcType(Type_Normal)
{
mNpc = mPtr.get<ESM::NPC>()->mBase;
@ -532,20 +510,16 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed)
{
float pitch = mPtr.getRefData().getPosition().rot[0];
Ogre::Node *node = baseinst->getBone("Bip01 Neck");
node->pitch(Ogre::Radian(pitch), Ogre::Node::TS_WORLD);
node->pitch(Ogre::Radian(-pitch), Ogre::Node::TS_WORLD);
// This has to be done before this function ends;
// updateSkeletonInstance, below, touches the hands.
node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD);
}
else if (mPitchFactor > 0)
else
{
// In third person mode we may still need pitch for ranged weapon targeting
float pitch = mPtr.getRefData().getPosition().rot[0] * mPitchFactor;
Ogre::Node *node = baseinst->getBone("Bip01 Spine2");
node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD);
node = baseinst->getBone("Bip01 Spine1");
node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD);
pitchSkeleton(mPtr.getRefData().getPosition().rot[0], baseinst);
}
mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame.
@ -695,13 +669,14 @@ void NpcAnimation::showWeapons(bool showWeapon)
{
MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr);
MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if(weapon != inv.end()) // special case for weapons
if(weapon != inv.end())
{
Ogre::Vector3 glowColor = getEnchantmentColor(*weapon);
std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon);
addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1,
mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor);
// Crossbows start out with a bolt attached
if (weapon->getTypeName() == typeid(ESM::Weapon).name() &&
weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
{
@ -743,50 +718,24 @@ void NpcAnimation::showCarriedLeft(bool show)
removeIndividualPart(ESM::PRT_Shield);
}
void NpcAnimation::attachArrow()
void NpcAnimation::configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot)
{
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weaponSlot != inv.end() && weaponSlot->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
showWeapons(true);
else
{
NifOgre::ObjectScenePtr weapon = mObjectParts[ESM::PRT_Weapon];
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if (ammo == inv.end())
return;
std::string model = ammo->getClass().getModel(*ammo);
Ogre::Vector3 glowColor = getEnchantmentColor(ptr);
setRenderProperties(object, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0,
!ptr.getClass().getEnchantment(ptr).empty(), &glowColor);
mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", mInsert, model);
Ogre::Vector3 glowColor = getEnchantmentColor(*ammo);
setRenderProperties(mAmmunition, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0,
!ammo->getClass().getEnchantment(*ammo).empty(), &glowColor);
std::for_each(object->mEntities.begin(), object->mEntities.end(), SetObjectGroup(slot));
std::for_each(object->mParticles.begin(), object->mParticles.end(), SetObjectGroup(slot));
}
std::for_each(mAmmunition->mEntities.begin(), mAmmunition->mEntities.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition));
std::for_each(mAmmunition->mParticles.begin(), mAmmunition->mParticles.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition));
}
void NpcAnimation::attachArrow()
{
WeaponAnimation::attachArrow(mPtr);
}
void NpcAnimation::releaseArrow()
{
// Thrown weapons get detached now
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weapon != inv.end() && weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
{
showWeapons(false);
inv.remove(*weapon, 1, mPtr);
}
else
{
// With bows and crossbows only the used arrow/bolt gets detached
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if (ammo == inv.end())
return;
inv.remove(*ammo, 1, mPtr);
mAmmunition.setNull();
}
WeaponAnimation::releaseArrow(mPtr);
}
void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound)

@ -5,6 +5,8 @@
#include "../mwworld/inventorystore.hpp"
#include "weaponanimation.hpp"
namespace ESM
{
struct NPC;
@ -25,24 +27,7 @@ public:
{ }
};
class WeaponAnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
private:
Animation* mAnimation;
std::string mWeaponGroup;
float mStartTime;
public:
WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {}
void setGroup(const std::string& group);
void updateStartTime();
virtual Ogre::Real getValue() const;
virtual void setValue(Ogre::Real value)
{ }
};
class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener
class NpcAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener
{
public:
virtual void equipmentChanged() { updateParts(); }
@ -91,7 +76,6 @@ private:
Ogre::SharedPtr<WeaponAnimationTime> mWeaponAnimationTime;
float mAlpha;
float mPitchFactor;
void updateNpcBase();
@ -138,7 +122,10 @@ public:
virtual void attachArrow();
virtual void releaseArrow();
NifOgre::ObjectScenePtr mAmmunition;
// WeaponAnimation
virtual NifOgre::ObjectScenePtr getWeapon() { return mObjectParts[ESM::PRT_Weapon]; }
virtual void showWeapon(bool show) { showWeapons(show); }
virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot);
void setViewMode(ViewMode viewMode);

@ -280,13 +280,12 @@ void RenderingManager::rotateObject(const MWWorld::Ptr &ptr)
if(ptr.getRefData().getHandle() == mCamera->getHandle() &&
!mCamera->isVanityOrPreviewModeEnabled())
mCamera->rotateCamera(rot, false);
mCamera->rotateCamera(-rot, false);
Ogre::Quaternion newo = Ogre::Quaternion(Ogre::Radian(-rot.z), Ogre::Vector3::UNIT_Z);
Ogre::Quaternion newo = Ogre::Quaternion(Ogre::Radian(rot.z), Ogre::Vector3::NEGATIVE_UNIT_Z);
if(!MWWorld::Class::get(ptr).isActor())
newo = Ogre::Quaternion(Ogre::Radian(-rot.x), Ogre::Vector3::UNIT_X) *
Ogre::Quaternion(Ogre::Radian(-rot.y), Ogre::Vector3::UNIT_Y) * newo;
newo = Ogre::Quaternion(Ogre::Radian(rot.x), Ogre::Vector3::NEGATIVE_UNIT_X) *
Ogre::Quaternion(Ogre::Radian(rot.y), Ogre::Vector3::NEGATIVE_UNIT_Y) * newo;
ptr.getRefData().getBaseNode()->setOrientation(newo);
}
@ -1074,7 +1073,7 @@ float RenderingManager::getCameraDistance() const
void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const Vector3 &worldPosition, float scale)
{
mEffectManager->addEffect(model, "", worldPosition, scale);
mEffectManager->addEffect(model, texture, worldPosition, scale);
}
} // namespace

@ -0,0 +1,159 @@
#include "weaponanimation.hpp"
#include <OgreEntity.h>
#include <OgreBone.h>
#include <OgreSceneNode.h>
#include <OgreSkeletonInstance.h>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "animation.hpp"
namespace MWRender
{
float WeaponAnimationTime::getValue() const
{
if (mWeaponGroup.empty())
return 0;
float current = mAnimation->getCurrentTime(mWeaponGroup);
if (current == -1)
return 0;
return current - mStartTime;
}
void WeaponAnimationTime::setGroup(const std::string &group)
{
mWeaponGroup = group;
mStartTime = mAnimation->getStartTime(mWeaponGroup);
}
void WeaponAnimationTime::updateStartTime()
{
setGroup(mWeaponGroup);
}
void WeaponAnimation::attachArrow(MWWorld::Ptr actor)
{
MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor);
MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weaponSlot != inv.end() && weaponSlot->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
showWeapon(true);
else
{
NifOgre::ObjectScenePtr weapon = getWeapon();
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if (ammo == inv.end())
return;
std::string model = ammo->getClass().getModel(*ammo);
mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", weapon->mSkelBase->getParentSceneNode(), model);
configureAddedObject(mAmmunition, *ammo, MWWorld::InventoryStore::Slot_Ammunition);
}
}
void WeaponAnimation::releaseArrow(MWWorld::Ptr actor)
{
MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor);
MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (weapon == inv.end())
return;
// The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise.
Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) *
Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X);
const MWWorld::Store<ESM::GameSetting> &gmst =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
// Reduce fatigue
// somewhat of a guess, but using the weapon weight makes sense
const float fFatigueAttackBase = gmst.find("fFatigueAttackBase")->getFloat();
const float fFatigueAttackMult = gmst.find("fFatigueAttackMult")->getFloat();
const float fWeaponFatigueMult = gmst.find("fWeaponFatigueMult")->getFloat();
MWMechanics::CreatureStats& attackerStats = actor.getClass().getCreatureStats(actor);
MWMechanics::DynamicStat<float> fatigue = attackerStats.getFatigue();
const float normalizedEncumbrance = actor.getClass().getEncumbrance(actor) / actor.getClass().getCapacity(actor);
float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult;
if (!weapon->isEmpty())
fatigueLoss += weapon->getClass().getWeight(*weapon) * attackerStats.getAttackStrength() * fWeaponFatigueMult;
fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
attackerStats.setFatigue(fatigue);
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
{
// Thrown weapons get detached now
NifOgre::ObjectScenePtr objects = getWeapon();
Ogre::Vector3 launchPos(0,0,0);
if (objects->mSkelBase)
{
launchPos = objects->mSkelBase->getParentNode()->_getDerivedPosition();
}
else if (objects->mEntities.size())
{
objects->mEntities[0]->getParentNode()->needUpdate(true);
launchPos = objects->mEntities[0]->getParentNode()->_getDerivedPosition();
}
float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->getFloat();
float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->getFloat();
float speed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) *
actor.getClass().getCreatureStats(actor).getAttackStrength();
MWBase::Environment::get().getWorld()->launchProjectile(actor, *weapon, launchPos, orient, *weapon, speed);
showWeapon(false);
inv.remove(*weapon, 1, actor);
}
else
{
// With bows and crossbows only the used arrow/bolt gets detached
MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
if (ammo == inv.end())
return;
Ogre::Vector3 launchPos(0,0,0);
if (mAmmunition->mSkelBase)
{
launchPos = mAmmunition->mSkelBase->getParentNode()->_getDerivedPosition();
}
else if (mAmmunition->mEntities.size())
{
mAmmunition->mEntities[0]->getParentNode()->needUpdate(true);
launchPos = mAmmunition->mEntities[0]->getParentNode()->_getDerivedPosition();
}
float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->getFloat();
float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->getFloat();
float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * actor.getClass().getCreatureStats(actor).getAttackStrength();
MWBase::Environment::get().getWorld()->launchProjectile(actor, *ammo, launchPos, orient, *weapon, speed);
inv.remove(*ammo, 1, actor);
mAmmunition.setNull();
}
}
void WeaponAnimation::pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel)
{
if (mPitchFactor == 0)
return;
float pitch = xrot * mPitchFactor;
Ogre::Node *node = skel->getBone("Bip01 Spine2");
node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD);
node = skel->getBone("Bip01 Spine1");
node->pitch(Ogre::Radian(-pitch/2), Ogre::Node::TS_WORLD);
}
}

@ -0,0 +1,56 @@
#ifndef OPENMW_MWRENDER_WEAPONANIMATION_H
#define OPENMW_MWRENDER_WEAPONANIMATION_H
#include <OgreController.h>
#include <components/nifogre/ogrenifloader.hpp>
#include "../mwworld/ptr.hpp"
namespace MWRender
{
class Animation;
class WeaponAnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
private:
Animation* mAnimation;
std::string mWeaponGroup;
float mStartTime;
public:
WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {}
void setGroup(const std::string& group);
void updateStartTime();
virtual Ogre::Real getValue() const;
virtual void setValue(Ogre::Real value)
{ }
};
/// Handles attach & release of projectiles for ranged weapons
class WeaponAnimation
{
public:
WeaponAnimation() : mPitchFactor(0) {}
virtual void attachArrow(MWWorld::Ptr actor);
virtual void releaseArrow(MWWorld::Ptr actor);
protected:
NifOgre::ObjectScenePtr mAmmunition;
virtual NifOgre::ObjectScenePtr getWeapon() = 0;
virtual void showWeapon(bool show) = 0;
virtual void configureAddedObject(NifOgre::ObjectScenePtr object, MWWorld::Ptr ptr, int slot) = 0;
/// A relative factor (0-1) that decides if and how much the skeleton should be pitched
/// to indicate the facing orientation of the character, for ranged weapon aiming.
float mPitchFactor;
void pitchSkeleton(float xrot, Ogre::SkeletonInstance* skel);
};
}
#endif

@ -121,7 +121,7 @@ namespace MWScript
std::string itemName;
for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter)
if (Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item))
if (::Misc::StringUtils::ciEqual(iter->getCellRef().mRefID, item))
itemName = iter->getClass().getName(*iter);
int numRemoved = store.remove(item, count, ptr);
@ -165,7 +165,7 @@ namespace MWScript
MWWorld::ContainerStoreIterator it = invStore.begin();
for (; it != invStore.end(); ++it)
{
if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item))
if (::Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item))
break;
}
if (it == invStore.end())
@ -268,7 +268,7 @@ namespace MWScript
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
{
MWWorld::ContainerStoreIterator it = invStore.getSlot (slot);
if (it != invStore.end() && Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item))
if (it != invStore.end() && ::Misc::StringUtils::ciEqual(it->getCellRef().mRefID, item))
{
runtime.push(1);
return;
@ -295,7 +295,7 @@ namespace MWScript
it != invStore.end(); ++it)
{
if (Misc::StringUtils::ciEqual(it->getCellRef().mSoul, name))
if (::Misc::StringUtils::ciEqual(it->getCellRef().mSoul, name))
{
runtime.push(1);
return;

@ -24,7 +24,7 @@ namespace MWScript
void GlobalScripts::addScript (const std::string& name)
{
std::map<std::string, std::pair<bool, Locals> >::iterator iter =
mScripts.find (Misc::StringUtils::lowerCase (name));
mScripts.find (::Misc::StringUtils::lowerCase (name));
if (iter==mScripts.end())
{
@ -44,7 +44,7 @@ namespace MWScript
void GlobalScripts::removeScript (const std::string& name)
{
std::map<std::string, std::pair<bool, Locals> >::iterator iter =
mScripts.find (Misc::StringUtils::lowerCase (name));
mScripts.find (::Misc::StringUtils::lowerCase (name));
if (iter!=mScripts.end())
iter->second.first = false;
@ -53,7 +53,7 @@ namespace MWScript
bool GlobalScripts::isRunning (const std::string& name) const
{
std::map<std::string, std::pair<bool, Locals> >::const_iterator iter =
mScripts.find (Misc::StringUtils::lowerCase (name));
mScripts.find (::Misc::StringUtils::lowerCase (name));
if (iter==mScripts.end())
return false;
@ -151,7 +151,7 @@ namespace MWScript
Locals& GlobalScripts::getLocals (const std::string& name)
{
std::string name2 = Misc::StringUtils::lowerCase (name);
std::string name2 = ::Misc::StringUtils::lowerCase (name);
std::map<std::string, std::pair<bool, Locals> >::iterator iter =
mScripts.find (name2);

@ -113,7 +113,7 @@ namespace MWScript
virtual void execute (Interpreter::Runtime& runtime)
{
std::string cell = (runtime.getStringLiteral (runtime[0].mInteger));
Misc::StringUtils::toLower(cell);
::Misc::StringUtils::toLower(cell);
runtime.pop();
// "Will match complete or partial cells, so ShowMap, "Vivec" will show cells Vivec and Vivec, Fred's House as well."
@ -126,7 +126,7 @@ namespace MWScript
for (; it != cells.extEnd(); ++it)
{
std::string name = it->mName;
Misc::StringUtils::toLower(name);
::Misc::StringUtils::toLower(name);
if (name.find(cell) != std::string::npos)
MWBase::Environment::get().getWindowManager()->addVisitedLocation (
it->mName,

@ -540,7 +540,7 @@ namespace MWScript
factionID = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
}
Misc::StringUtils::toLower(factionID);
::Misc::StringUtils::toLower(factionID);
if(factionID != "")
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
@ -569,7 +569,7 @@ namespace MWScript
factionID = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
}
Misc::StringUtils::toLower(factionID);
::Misc::StringUtils::toLower(factionID);
if(factionID != "")
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
@ -602,7 +602,7 @@ namespace MWScript
factionID = runtime.getStringLiteral (runtime[0].mInteger);
runtime.pop();
}
Misc::StringUtils::toLower(factionID);
::Misc::StringUtils::toLower(factionID);
if(factionID != "")
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
@ -640,7 +640,7 @@ namespace MWScript
factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first;
}
}
Misc::StringUtils::toLower(factionID);
::Misc::StringUtils::toLower(factionID);
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if(factionID!="")
{
@ -742,7 +742,7 @@ namespace MWScript
if (factionId.empty())
throw std::runtime_error ("failed to determine faction");
Misc::StringUtils::toLower (factionId);
::Misc::StringUtils::toLower (factionId);
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
runtime.push (
@ -778,7 +778,7 @@ namespace MWScript
if (factionId.empty())
throw std::runtime_error ("failed to determine faction");
Misc::StringUtils::toLower (factionId);
::Misc::StringUtils::toLower (factionId);
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId, value);
@ -813,7 +813,7 @@ namespace MWScript
if (factionId.empty())
throw std::runtime_error ("failed to determine faction");
Misc::StringUtils::toLower (factionId);
::Misc::StringUtils::toLower (factionId);
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
MWWorld::Class::get (player).getNpcStats (player).setFactionReputation (factionId,
@ -858,11 +858,11 @@ namespace MWScript
MWWorld::Ptr ptr = R()(runtime);
std::string race = runtime.getStringLiteral(runtime[0].mInteger);
Misc::StringUtils::toLower(race);
::Misc::StringUtils::toLower(race);
runtime.pop();
std::string npcRace = ptr.get<ESM::NPC>()->mBase->mRace;
Misc::StringUtils::toLower(npcRace);
::Misc::StringUtils::toLower(npcRace);
runtime.push (npcRace == race);
}
@ -906,7 +906,7 @@ namespace MWScript
factionID = MWWorld::Class::get(ptr).getNpcStats(ptr).getFactionRanks().begin()->first;
}
}
Misc::StringUtils::toLower(factionID);
::Misc::StringUtils::toLower(factionID);
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
if(factionID!="")
{

@ -82,30 +82,24 @@ namespace MWScript
Interpreter::Type_Float angle = runtime[0].mFloat;
runtime.pop();
float ax = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees();
float ay = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees();
float az = Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees();
float *objRot = ptr.getRefData().getPosition().rot;
float lx = Ogre::Radian(objRot[0]).valueDegrees();
float ly = Ogre::Radian(objRot[1]).valueDegrees();
float lz = Ogre::Radian(objRot[2]).valueDegrees();
float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees();
float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees();
float az = Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees();
if (axis == "x")
{
MWBase::Environment::get().getWorld()->localRotateObject(ptr,angle-lx,ay,az);
MWBase::Environment::get().getWorld()->rotateObject(ptr,angle,ay,az);
}
else if (axis == "y")
{
MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,angle-ly,az);
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,angle,az);
}
else if (axis == "z")
{
MWBase::Environment::get().getWorld()->localRotateObject(ptr,ax,ay,angle-lz);
MWBase::Environment::get().getWorld()->rotateObject(ptr,ax,ay,angle);
}
else
throw std::runtime_error ("invalid ration axis: " + axis);
throw std::runtime_error ("invalid rotation axis: " + axis);
}
};
@ -134,7 +128,7 @@ namespace MWScript
runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[2]).valueDegrees());
}
else
throw std::runtime_error ("invalid ration axis: " + axis);
throw std::runtime_error ("invalid rotation axis: " + axis);
}
};
@ -152,18 +146,18 @@ namespace MWScript
if (axis=="x")
{
runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[0]).valueDegrees()+Ogre::Radian(ptr.getRefData().getLocalRotation().rot[0]).valueDegrees());
runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees());
}
else if (axis=="y")
{
runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[1]).valueDegrees()+Ogre::Radian(ptr.getRefData().getLocalRotation().rot[1]).valueDegrees());
runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees());
}
else if (axis=="z")
{
runtime.push(Ogre::Radian(ptr.getCellRef().mPos.rot[2]).valueDegrees()+Ogre::Radian(ptr.getRefData().getLocalRotation().rot[2]).valueDegrees());
runtime.push(Ogre::Radian(ptr.getRefData().getPosition().rot[2]).valueDegrees());
}
else
throw std::runtime_error ("invalid ration axis: " + axis);
throw std::runtime_error ("invalid rotation axis: " + axis);
}
};
@ -192,7 +186,7 @@ namespace MWScript
runtime.push(ptr.getRefData().getPosition().pos[2]);
}
else
throw std::runtime_error ("invalid rotation axis: " + axis);
throw std::runtime_error ("invalid axis: " + axis);
}
};
@ -313,7 +307,7 @@ namespace MWScript
}
if(store)
{
MWBase::Environment::get().getWorld()->moveObject(ptr,*store,x,y,z);
MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z);
float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees();
float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees();
if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity
@ -361,7 +355,7 @@ namespace MWScript
int cx,cy;
MWBase::Environment::get().getWorld()->positionToIndex(x,y,cx,cy);
MWBase::Environment::get().getWorld()->moveObject(ptr,
*MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z);
MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z);
float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees();
float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees();
if(ptr.getTypeName() == typeid(ESM::NPC).name())//some morrowind oddity
@ -421,7 +415,7 @@ namespace MWScript
pos.rot[2] = zRot;
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID);
ref.getPtr().getCellRef().mPos = pos;
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,pos);
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos);
}
else
{
@ -462,7 +456,7 @@ namespace MWScript
pos.rot[2] = zRot;
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID);
ref.getPtr().getCellRef().mPos = pos;
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,pos);
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos);
}
else
{
@ -530,7 +524,7 @@ namespace MWScript
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), itemID, count);
ref.getPtr().getCellRef().mPos = ipos;
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos);
MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos);
}
};

@ -11,9 +11,10 @@
namespace MWSound
{
static void fail(const std::string &msg)
{ throw std::runtime_error("FFmpeg exception: "+msg); }
void FFmpeg_Decoder::fail(const std::string &msg)
{
throw std::runtime_error("FFmpeg exception: "+msg);
}
int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size)
{

@ -61,6 +61,8 @@ namespace MWSound
virtual void rewind();
virtual size_t getSampleOffset();
void fail(const std::string &msg);
FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs);
FFmpeg_Decoder(const FFmpeg_Decoder &rhs);

@ -20,6 +20,7 @@
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/inputmanager.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
@ -39,6 +40,7 @@ void MWState::StateManager::cleanup (bool force)
MWBase::Environment::get().getScriptManager()->getGlobalScripts().clear();
MWBase::Environment::get().getWorld()->clear();
MWBase::Environment::get().getWindowManager()->clear();
MWBase::Environment::get().getInputManager()->clear();
mState = State_NoGame;
mCharacterManager.clearCurrentCharacter();
@ -123,11 +125,10 @@ void MWState::StateManager::newGame (bool bypass)
{
cleanup();
MWBase::Environment::get().getWorld()->startNewGame (bypass);
if (!bypass)
{
MWBase::Environment::get().getWorld()->startNewGame();
MWBase::Environment::get().getWindowManager()->setNewGame (true);
}
else
MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1);
@ -148,7 +149,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
MWBase::World& world = *MWBase::Environment::get().getWorld();
MWWorld::Ptr player = world.getPlayer().getPlayer();
MWWorld::Ptr player = world.getPlayerPtr();
profile.mContentFiles = world.getContentFiles();
@ -300,7 +301,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl
MWBase::Environment::get().getWindowManager()->updatePlayer();
MWBase::Environment::get().getMechanicsManager()->playerLoaded();
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr();
ESM::CellId cellId = ptr.getCell()->getCell()->getCellId();

@ -41,11 +41,11 @@ namespace MWWorld
int cellX;
int cellY;
world->positionToIndex(mPosition.pos[0],mPosition.pos[1],cellX,cellY);
world->moveObject(actor,*world->getExterior(cellX,cellY),
world->moveObject(actor,world->getExterior(cellX,cellY),
mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]);
}
else
world->moveObject(actor,*world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]);
world->moveObject(actor,world->getInterior(mCellName),mPosition.pos[0],mPosition.pos[1],mPosition.pos[2]);
}
}
}

@ -363,7 +363,22 @@ namespace MWWorld
return newPtr;
}
bool Class::isFlying(const Ptr &ptr) const
bool Class::isBipedal(const Ptr &ptr) const
{
return false;
}
bool Class::canFly(const Ptr &ptr) const
{
return false;
}
bool Class::canSwim(const Ptr &ptr) const
{
return false;
}
bool Class::canWalk(const Ptr &ptr) const
{
return false;
}

@ -307,7 +307,10 @@ namespace MWWorld
return false;
}
virtual bool isFlying(const MWWorld::Ptr& ptr) const;
virtual bool isBipedal(const MWWorld::Ptr& ptr) const;
virtual bool canFly(const MWWorld::Ptr& ptr) const;
virtual bool canSwim(const MWWorld::Ptr& ptr) const;
virtual bool canWalk(const MWWorld::Ptr& ptr) const;
virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const;

@ -120,11 +120,9 @@ namespace MWWorld
OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle());
if(!physicActor || !physicActor->getCollisionMode())
{
// FIXME: This works, but it's inconcsistent with how the rotations are applied elsewhere. Why?
return position + (Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)*
Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)*
Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) *
movement * time;
return position + (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) *
Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X))
* movement * time;
}
btCollisionObject *colobj = physicActor->getCollisionBody();
@ -132,33 +130,52 @@ namespace MWWorld
position.z += halfExtents.z;
waterlevel -= halfExtents.z * 0.5;
/*
* A 3/4 submerged example
*
* +---+
* | |
* | | <- (original waterlevel)
* | |
* | | <- position <- waterlevel
* | |
* | |
* | |
* +---+ <- (original position)
*/
OEngine::Physic::ActorTracer tracer;
bool wasOnGround = false;
bool isOnGround = false;
Ogre::Vector3 inertia(0.0f);
Ogre::Vector3 velocity;
if(position.z < waterlevel || isFlying)
bool canWalk = ptr.getClass().canWalk(ptr);
bool isBipedal = ptr.getClass().isBipedal(ptr);
bool isNpc = ptr.getClass().isNpc();
if(position.z < waterlevel || isFlying) // under water by 3/4 or can fly
{
velocity = (Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)*
Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)*
Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) *
movement;
// TODO: Shouldn't water have higher drag in calculating velocity?
velocity = (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z)*
Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)) * movement;
}
else
{
velocity = Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z) * movement;
velocity = Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * movement;
// not in water nor can fly, so need to deal with gravity
if(!physicActor->getOnGround())
{
// If falling, add part of the incoming velocity with the current inertia
velocity = velocity*time + physicActor->getInertialForce();
velocity = velocity * time + physicActor->getInertialForce();
}
inertia = velocity;
inertia = velocity; // REM velocity is for z axis only in this code block
if(!(movement.z > 0.0f))
{
wasOnGround = physicActor->getOnGround();
tracer.doTrace(colobj, position, position-Ogre::Vector3(0,0,2), engine);
// TODO: Find out if there is a significance with the value 2 used here
tracer.doTrace(colobj, position, position - Ogre::Vector3(0,0,2), engine);
if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope)
isOnGround = true;
}
@ -167,24 +184,38 @@ namespace MWWorld
if(isOnGround)
{
// if we're on the ground, don't try to fall
velocity.z = std::max(0.0f, velocity.z);
velocity.z = std::max(0.0f, velocity.z); // NOTE: two different velocity assignments above
}
Ogre::Vector3 newPosition = position;
/*
* A loop to find newPosition using tracer, if successful different from the starting position.
* nextpos is the local variable used to find potential newPosition, using velocity and remainingTime
* The initial velocity was set earlier (see above).
*/
float remainingTime = time;
for(int iterations = 0;iterations < sMaxIterations && remainingTime > 0.01f;++iterations)
for(int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations)
{
Ogre::Vector3 nextpos = newPosition + velocity*remainingTime;
if(newPosition.z < waterlevel && !isFlying &&
nextpos.z > waterlevel && newPosition.z <= waterlevel)
Ogre::Vector3 nextpos = newPosition + velocity * remainingTime;
// If not able to fly, walk or bipedal don't allow to move out of water
// TODO: this if condition may not work for large creatures or situations
// where the creature gets above the waterline for some reason
if(newPosition.z < waterlevel && // started 3/4 under water
!isFlying && // can't fly
!canWalk && // can't walk
!isBipedal && // not bipedal (assume bipedals can walk)
!isNpc && // FIXME: shouldn't really need this
nextpos.z > waterlevel && // but about to go above water
newPosition.z <= waterlevel)
{
const Ogre::Vector3 down(0,0,-1);
Ogre::Real movelen = velocity.normalise();
Ogre::Vector3 reflectdir = velocity.reflect(down);
reflectdir.normalise();
velocity = slide(reflectdir, down)*movelen;
continue;
// NOTE: remainingTime is unchanged before the loop continues
continue; // velocity updated, calculate nextpos again
}
// trace to where character would go if there were no obstructions
@ -193,13 +224,14 @@ namespace MWWorld
// check for obstructions
if(tracer.mFraction >= 1.0f)
{
newPosition = tracer.mEndPos;
remainingTime *= (1.0f-tracer.mFraction);
newPosition = tracer.mEndPos; // ok to move, so set newPosition
remainingTime *= (1.0f-tracer.mFraction); // FIXME: remainingTime is no longer used so don't set it?
break;
}
// We hit something. Try to step up onto it.
if(stepMove(colobj, newPosition, velocity, remainingTime, engine))
// NOTE: May need to stop slaughterfish step out of the water.
if((canWalk || isBipedal || isNpc) && stepMove(colobj, newPosition, velocity, remainingTime, engine))
isOnGround = !(newPosition.z < waterlevel || isFlying); // Only on the ground if there's gravity
else
{
@ -218,7 +250,7 @@ namespace MWWorld
if(isOnGround || wasOnGround)
{
tracer.doTrace(colobj, newPosition, newPosition-Ogre::Vector3(0,0,sStepSize+2.0f), engine);
tracer.doTrace(colobj, newPosition, newPosition - Ogre::Vector3(0,0,sStepSize+2.0f), engine);
if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope)
{
newPosition.z = tracer.mEndPos.z + 1.0f;
@ -240,7 +272,7 @@ namespace MWWorld
}
physicActor->setOnGround(isOnGround);
newPosition.z -= halfExtents.z;
newPosition.z -= halfExtents.z; // remove what was added at the beggining
return newPosition;
}
};

@ -28,7 +28,7 @@
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/levelledlist.hpp"
#include "../mwmechanics/combat.hpp"
#include "../mwrender/sky.hpp"
#include "../mwrender/animation.hpp"
@ -121,13 +121,15 @@ namespace MWWorld
const Files::Collections& fileCollections,
const std::vector<std::string>& contentFiles,
const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir,
ToUTF8::Utf8Encoder* encoder, const std::map<std::string,std::string>& fallbackMap, int mActivationDistanceOverride)
ToUTF8::Utf8Encoder* encoder, const std::map<std::string,std::string>& fallbackMap,
int activationDistanceOverride, const std::string& startCell)
: mPlayer (0), mLocalScripts (mStore),
mSky (true), mCells (mStore, mEsm),
mActivationDistanceOverride (mActivationDistanceOverride),
mActivationDistanceOverride (activationDistanceOverride),
mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(true),
mFacedDistance(FLT_MAX), mGodMode(false), mContentFiles (contentFiles),
mGoToJail(false)
mGoToJail(false),
mStartCell (startCell)
{
mPhysics = new PhysicsSystem(renderer);
mPhysEngine = mPhysics->getEngine();
@ -168,7 +170,7 @@ namespace MWWorld
mWorldScene = new Scene(*mRendering, mPhysics);
}
void World::startNewGame()
void World::startNewGame (bool bypass)
{
mGoToJail = false;
mLevitationEnabled = true;
@ -181,23 +183,44 @@ namespace MWWorld
MWBase::Environment::get().getWindowManager()->updatePlayer();
// FIXME: this will add cell 0,0 as visible on the global map
ESM::Position pos;
const int cellSize = 8192;
pos.pos[0] = cellSize/2;
pos.pos[1] = cellSize/2;
pos.pos[2] = 0;
pos.rot[0] = 0;
pos.rot[1] = 0;
pos.rot[2] = 0;
mWorldScene->changeToExteriorCell(pos);
if (bypass && !mStartCell.empty())
{
ESM::Position pos;
if (findExteriorPosition (mStartCell, pos))
{
changeToExteriorCell (pos);
}
else
{
findInteriorPosition (mStartCell, pos);
changeToInteriorCell (mStartCell, pos);
}
}
else
{
/// \todo if !bypass, do not add player location to global map for the duration of one
/// frame
ESM::Position pos;
const int cellSize = 8192;
pos.pos[0] = cellSize/2;
pos.pos[1] = cellSize/2;
pos.pos[2] = 0;
pos.rot[0] = 0;
pos.rot[1] = 0;
pos.rot[2] = 0;
mWorldScene->changeToExteriorCell(pos);
}
// FIXME: should be set to 1, but the sound manager won't pause newly started sounds
mPlayIntro = 2;
if (!bypass)
{
// FIXME: should be set to 1, but the sound manager won't pause newly started sounds
mPlayIntro = 2;
// set new game mark
mGlobalVariables["chargenstate"].setInteger (1);
mGlobalVariables["pcrace"].setInteger (3);
// set new game mark
mGlobalVariables["chargenstate"].setInteger (1);
mGlobalVariables["pcrace"].setInteger (3);
}
// we don't want old weather to persist on a new game
delete mWeatherManager;
@ -228,6 +251,7 @@ namespace MWWorld
mCells.clear();
mMagicBolts.clear();
mProjectiles.clear();
mDoorStates.clear();
@ -821,7 +845,7 @@ namespace MWWorld
const ESM::Position &posdata = ptr.getRefData().getPosition();
Ogre::Vector3 pos(posdata.pos);
Ogre::Quaternion rot = Ogre::Quaternion(Ogre::Radian(posdata.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) *
Ogre::Quaternion(Ogre::Radian(posdata.rot[0]), Ogre::Vector3::UNIT_X);
Ogre::Quaternion(Ogre::Radian(posdata.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X);
MWRender::Animation *anim = mRendering->getAnimation(ptr);
if(anim != NULL)
@ -858,7 +882,7 @@ namespace MWWorld
}
}
void World::moveObject(const Ptr &ptr, CellStore &newCell, float x, float y, float z)
void World::moveObject(const Ptr &ptr, CellStore* newCell, float x, float y, float z)
{
ESM::Position &pos = ptr.getRefData().getPosition();
@ -872,27 +896,27 @@ namespace MWWorld
bool isPlayer = ptr == mPlayer->getPlayer();
bool haveToMove = isPlayer || mWorldScene->isCellActive(*currCell);
if (*currCell != newCell)
if (currCell != newCell)
{
removeContainerScripts(ptr);
if (isPlayer)
{
if (!newCell.isExterior())
changeToInteriorCell(Misc::StringUtils::lowerCase(newCell.getCell()->mName), pos);
if (!newCell->isExterior())
changeToInteriorCell(Misc::StringUtils::lowerCase(newCell->getCell()->mName), pos);
else
{
int cellX = newCell.getCell()->getGridX();
int cellY = newCell.getCell()->getGridY();
int cellX = newCell->getCell()->getGridX();
int cellY = newCell->getCell()->getGridY();
mWorldScene->changeCell(cellX, cellY, pos, false);
}
addContainerScripts (getPlayerPtr(), &newCell);
addContainerScripts (getPlayerPtr(), newCell);
}
else
{
if (!mWorldScene->isCellActive(*currCell))
copyObjectToCell(ptr, newCell, pos);
else if (!mWorldScene->isCellActive(newCell))
ptr.getClass().copyToCell(ptr, *newCell, pos);
else if (!mWorldScene->isCellActive(*newCell))
{
mWorldScene->removeObjectFromScene(ptr);
mLocalScripts.remove(ptr);
@ -900,7 +924,7 @@ namespace MWWorld
haveToMove = false;
MWWorld::Ptr newPtr = MWWorld::Class::get(ptr)
.copyToCell(ptr, newCell);
.copyToCell(ptr, *newCell);
newPtr.getRefData().setBaseNode(0);
objectLeftActiveCell(ptr, newPtr);
@ -908,7 +932,7 @@ namespace MWWorld
else
{
MWWorld::Ptr copy =
MWWorld::Class::get(ptr).copyToCell(ptr, newCell, pos);
MWWorld::Class::get(ptr).copyToCell(ptr, *newCell, pos);
mRendering->updateObjectCell(ptr, copy);
MWBase::Environment::get().getSoundManager()->updatePtr (ptr, copy);
@ -923,7 +947,7 @@ namespace MWWorld
mLocalScripts.remove(ptr);
removeContainerScripts (ptr);
mLocalScripts.add(script, copy);
addContainerScripts (copy, &newCell);
addContainerScripts (copy, newCell);
}
}
ptr.getRefData().setCount(0);
@ -947,7 +971,7 @@ namespace MWWorld
cell = getExterior(cellX, cellY);
}
moveObject(ptr, *cell, x, y, z);
moveObject(ptr, cell, x, y, z);
return cell != ptr.getCell();
}
@ -1041,15 +1065,13 @@ namespace MWWorld
while(ptr.getRefData().getLocalRotation().rot[2]<=-fullRotateRad)
ptr.getRefData().getLocalRotation().rot[2]+=fullRotateRad;
float *worldRot = ptr.getRefData().getPosition().rot;
Ogre::Quaternion worldRotQuat(Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)*
Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y)*
Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z));
Ogre::Quaternion worldRotQuat(Ogre::Quaternion(Ogre::Radian(-worldRot[0]), Ogre::Vector3::UNIT_X)*
Ogre::Quaternion(Ogre::Radian(-worldRot[1]), Ogre::Vector3::UNIT_Y)*
Ogre::Quaternion(Ogre::Radian(-worldRot[2]), Ogre::Vector3::UNIT_Z));
Ogre::Quaternion rot(Ogre::Quaternion(Ogre::Radian(Ogre::Degree(-x).valueRadians()), Ogre::Vector3::UNIT_X)*
Ogre::Quaternion(Ogre::Radian(Ogre::Degree(-y).valueRadians()), Ogre::Vector3::UNIT_Y)*
Ogre::Quaternion(Ogre::Radian(Ogre::Degree(-z).valueRadians()), Ogre::Vector3::UNIT_Z));
Ogre::Quaternion rot(Ogre::Quaternion(Ogre::Degree(x), Ogre::Vector3::NEGATIVE_UNIT_X)*
Ogre::Quaternion(Ogre::Degree(y), Ogre::Vector3::NEGATIVE_UNIT_Y)*
Ogre::Quaternion(Ogre::Degree(z), Ogre::Vector3::NEGATIVE_UNIT_Z));
ptr.getRefData().getBaseNode()->setOrientation(worldRotQuat*rot);
mPhysics->rotateObject(ptr);
@ -1080,7 +1102,7 @@ namespace MWWorld
pos.z = traced.z;
}
moveObject(ptr, *ptr.getCell(), pos.x, pos.y, pos.z);
moveObject(ptr, ptr.getCell(), pos.x, pos.y, pos.z);
}
void World::rotateObject (const Ptr& ptr,float x,float y,float z, bool adjust)
@ -1091,9 +1113,9 @@ namespace MWWorld
adjust);
}
MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos)
MWWorld::Ptr World::safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos)
{
return copyObjectToCell(ptr,Cell,pos,false);
return copyObjectToCell(ptr,cell,pos,false);
}
void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const
@ -1127,6 +1149,7 @@ namespace MWWorld
{
processDoors(duration);
moveMagicBolts(duration);
moveProjectiles(duration);
const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration);
@ -1506,15 +1529,7 @@ namespace MWWorld
if (!result.first)
return false;
CellStore* cell;
if (isCellExterior())
{
int cellX, cellY;
positionToIndex(result.second[0], result.second[1], cellX, cellY);
cell = mCells.getExterior(cellX, cellY);
}
else
cell = getPlayerPtr().getCell();
CellStore* cell = getPlayerPtr().getCell();
ESM::Position pos = getPlayerPtr().getRefData().getPosition();
pos.pos[0] = result.second[0];
@ -1527,7 +1542,7 @@ namespace MWWorld
// copy the object and set its count
int origCount = object.getRefData().getCount();
object.getRefData().setCount(amount);
Ptr dropped = copyObjectToCell(object, *cell, pos, true);
Ptr dropped = copyObjectToCell(object, cell, pos, true);
object.getRefData().setCount(origCount);
// only the player place items in the world, so no need to check actor
@ -1548,7 +1563,7 @@ namespace MWWorld
}
Ptr World::copyObjectToCell(const Ptr &object, CellStore &cell, ESM::Position pos, bool adjustPos)
Ptr World::copyObjectToCell(const Ptr &object, CellStore* cell, ESM::Position pos, bool adjustPos)
{
if (object.getClass().isActor() || adjustPos)
{
@ -1560,17 +1575,17 @@ namespace MWWorld
}
}
if (cell.isExterior())
if (cell->isExterior())
{
int cellX, cellY;
positionToIndex(pos.pos[0], pos.pos[1], cellX, cellY);
cell = *mCells.getExterior(cellX, cellY);
cell = mCells.getExterior(cellX, cellY);
}
MWWorld::Ptr dropped =
MWWorld::Class::get(object).copyToCell(object, cell, pos);
MWWorld::Class::get(object).copyToCell(object, *cell, pos);
if (mWorldScene->isCellActive(cell)) {
if (mWorldScene->isCellActive(*cell)) {
if (dropped.getRefData().isEnabled()) {
mWorldScene->addObjectToScene(dropped);
}
@ -1578,7 +1593,7 @@ namespace MWWorld
if (!script.empty()) {
mLocalScripts.add(script, dropped);
}
addContainerScripts(dropped, &cell);
addContainerScripts(dropped, cell);
}
return dropped;
@ -1608,7 +1623,7 @@ namespace MWWorld
// copy the object and set its count
int origCount = object.getRefData().getCount();
object.getRefData().setCount(amount);
Ptr dropped = copyObjectToCell(object, *cell, pos);
Ptr dropped = copyObjectToCell(object, cell, pos);
object.getRefData().setCount(origCount);
if(actor == mPlayer->getPlayer()) // Only call if dropped by player
@ -1634,7 +1649,7 @@ namespace MWWorld
if (ptr.getClass().getCreatureStats(ptr).isDead())
return false;
if (ptr.getClass().isFlying(ptr))
if (ptr.getClass().canFly(ptr))
return true;
const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr);
@ -2143,7 +2158,37 @@ namespace MWWorld
}
}
void World::launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects,
void World::launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile,
const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed)
{
ProjectileState state;
state.mActorHandle = actor.getRefData().getHandle();
state.mBow = bow;
state.mVelocity = orient.yAxis() * speed;
MWWorld::ManualRef ref(getStore(), projectile.getCellRef().mRefID);
ESM::Position pos;
pos.pos[0] = worldPos.x;
pos.pos[1] = worldPos.y;
pos.pos[2] = worldPos.z;
// Do NOT copy actor rotation! actors use a different rotation order, and this will not produce the same facing direction.
Ogre::Matrix3 mat;
orient.ToRotationMatrix(mat);
Ogre::Radian xr,yr,zr;
mat.ToEulerAnglesXYZ(xr, yr, zr);
pos.rot[0] = -xr.valueRadians();
pos.rot[1] = -yr.valueRadians();
pos.rot[2] = -zr.valueRadians();
MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), actor.getCell(), pos, false);
ptr.getRefData().setCount(1);
mProjectiles[ptr] = state;
}
void World::launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects,
const MWWorld::Ptr& actor, const std::string& sourceName)
{
std::string projectileModel;
@ -2185,13 +2230,23 @@ namespace MWWorld
pos.pos[0] = actor.getRefData().getPosition().pos[0];
pos.pos[1] = actor.getRefData().getPosition().pos[1];
pos.pos[2] = actor.getRefData().getPosition().pos[2] + height;
pos.rot[0] = actor.getRefData().getPosition().rot[0];
pos.rot[1] = actor.getRefData().getPosition().rot[1];
pos.rot[2] = actor.getRefData().getPosition().rot[2];
// Do NOT copy rotation directly! actors use a different rotation order, and this will not produce the same facing direction.
Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) *
Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X);
Ogre::Matrix3 mat;
orient.ToRotationMatrix(mat);
Ogre::Radian xr,yr,zr;
mat.ToEulerAnglesXYZ(xr, yr, zr);
pos.rot[0] = -xr.valueRadians();
pos.rot[1] = -yr.valueRadians();
pos.rot[2] = -zr.valueRadians();
ref.getPtr().getCellRef().mPos = pos;
MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), *actor.getCell(), pos);
CellStore* cell = actor.getCell();
MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), cell, pos);
ProjectileState state;
MagicBoltState state;
state.mSourceName = sourceName;
state.mId = id;
state.mActorHandle = actor.getRefData().getHandle();
@ -2209,7 +2264,7 @@ namespace MWWorld
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
mProjectiles[ptr] = state;
mMagicBolts[ptr] = state;
}
void World::moveProjectiles(float duration)
@ -2226,13 +2281,105 @@ namespace MWWorld
MWWorld::Ptr ptr = it->first;
Ogre::Vector3 rot(ptr.getRefData().getPosition().rot);
// gravity constant - must be way lower than the gravity affecting actors, since we're not
// simulating aerodynamics at all
it->second.mVelocity -= Ogre::Vector3(0, 0, 627.2f * 0.1f) * duration;
// TODO: Why -rot.z, but not -rot.x? (note: same issue in MovementSolver::move)
Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(-rot.z), Ogre::Vector3::UNIT_Z);
orient = orient * Ogre::Quaternion(Ogre::Radian(rot.x), Ogre::Vector3::UNIT_X);
Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
Ogre::Vector3 newPos = pos + it->second.mVelocity * duration;
Ogre::Quaternion orient = Ogre::Vector3::UNIT_Y.getRotationTo(it->second.mVelocity);
Ogre::Matrix3 mat;
orient.ToRotationMatrix(mat);
Ogre::Radian xr,yr,zr;
mat.ToEulerAnglesXYZ(xr, yr, zr);
rotateObject(ptr, -xr.valueDegrees(), -yr.valueDegrees(), -zr.valueDegrees());
// Check for impact
btVector3 from(pos.x, pos.y, pos.z);
btVector3 to(newPos.x, newPos.y, newPos.z);
std::vector<std::pair<float, std::string> > collisions = mPhysEngine->rayTest2(from, to);
bool hit=false;
// HACK: query against the shape as well, since the ray does not take the volume into account
// really, this should be a convex cast, but the whole physics system needs a rewrite
std::vector<std::string> col2 = mPhysEngine->getCollisions(ptr.getRefData().getHandle());
for (std::vector<std::string>::iterator ci = col2.begin(); ci != col2.end(); ++ci)
collisions.push_back(std::make_pair(0.f,*ci));
for (std::vector<std::pair<float, std::string> >::iterator cIt = collisions.begin(); cIt != collisions.end() && !hit; ++cIt)
{
MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second);
if (obstacle == ptr)
continue;
MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle);
// Arrow intersects with player immediately after shooting :/
if (obstacle == caster)
continue;
if (caster.isEmpty())
caster = obstacle;
if (obstacle.isEmpty())
{
// Terrain
}
else if (obstacle.getClass().isActor())
{
MWMechanics::projectileHit(caster, obstacle, it->second.mBow, ptr, pos + (newPos - pos) * cIt->first);
}
hit = true;
}
if (hit)
{
deleteObject(ptr);
mProjectiles.erase(it++);
continue;
}
std::string handle = ptr.getRefData().getHandle();
moveObject(ptr, newPos.x, newPos.y, newPos.z);
// HACK: Re-fetch Ptrs if necessary, since the cell might have changed
if (!ptr.getRefData().getCount())
{
moved[handle] = it->second;
mProjectiles.erase(it++);
}
else
++it;
}
// HACK: Re-fetch Ptrs if necessary, since the cell might have changed
for (std::map<std::string, ProjectileState>::iterator it = moved.begin(); it != moved.end(); ++it)
{
MWWorld::Ptr newPtr = searchPtrViaHandle(it->first);
if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted
continue;
mProjectiles[getPtrViaHandle(it->first)] = it->second;
}
}
void World::moveMagicBolts(float duration)
{
std::map<std::string, MagicBoltState> moved;
for (std::map<MWWorld::Ptr, MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();)
{
if (!mWorldScene->isCellActive(*it->first.getCell()))
{
deleteObject(it->first);
mMagicBolts.erase(it++);
continue;
}
MWWorld::Ptr ptr = it->first;
Ogre::Vector3 rot(ptr.getRefData().getPosition().rot);
Ogre::Quaternion orient = ptr.getRefData().getBaseNode()->getOrientation();
static float fTargetSpellMaxSpeed = getStore().get<ESM::GameSetting>().find("fTargetSpellMaxSpeed")->getFloat();
float speed = fTargetSpellMaxSpeed * it->second.mSpeed;
@ -2279,7 +2426,7 @@ namespace MWWorld
explodeSpell(Ogre::Vector3(ptr.getRefData().getPosition().pos), ptr, it->second.mEffects, caster, it->second.mId, it->second.mSourceName);
deleteObject(ptr);
mProjectiles.erase(it++);
mMagicBolts.erase(it++);
continue;
}
@ -2291,29 +2438,29 @@ namespace MWWorld
if (!ptr.getRefData().getCount())
{
moved[handle] = it->second;
mProjectiles.erase(it++);
mMagicBolts.erase(it++);
}
else
++it;
}
// HACK: Re-fetch Ptrs if necessary, since the cell might have changed
for (std::map<std::string, ProjectileState>::iterator it = moved.begin(); it != moved.end(); ++it)
for (std::map<std::string, MagicBoltState>::iterator it = moved.begin(); it != moved.end(); ++it)
{
MWWorld::Ptr newPtr = searchPtrViaHandle(it->first);
if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted
continue;
mProjectiles[getPtrViaHandle(it->first)] = it->second;
mMagicBolts[getPtrViaHandle(it->first)] = it->second;
}
}
void World::objectLeftActiveCell(Ptr object, Ptr movedPtr)
{
// For now, projectiles moved to an inactive cell are just deleted, because there's no reliable way to hold on to the meta information
if (mMagicBolts.find(object) != mMagicBolts.end())
deleteObject(movedPtr);
if (mProjectiles.find(object) != mProjectiles.end())
{
deleteObject(movedPtr);
}
}
const std::vector<std::string>& World::getContentFiles() const
@ -2650,7 +2797,7 @@ namespace MWWorld
MWWorld::ManualRef ref(getStore(), selectedCreature, 1);
ref.getPtr().getCellRef().mPos = ipos;
safePlaceObject(ref.getPtr(),*cell,ipos);
safePlaceObject(ref.getPtr(), cell, ipos);
}
}

@ -90,7 +90,7 @@ namespace MWWorld
std::map<MWWorld::Ptr, int> mDoorStates;
///< only holds doors that are currently moving. 0 means closing, 1 opening
struct ProjectileState
struct MagicBoltState
{
// Id of spell or enchantment to apply when it hits
std::string mId;
@ -108,7 +108,21 @@ namespace MWWorld
bool mStack;
};
struct ProjectileState
{
// Actor who shot this projectile
std::string mActorHandle;
MWWorld::Ptr mBow; // bow or crossbow the projectile was fired from
Ogre::Vector3 mVelocity;
};
std::map<MWWorld::Ptr, MagicBoltState> mMagicBolts;
std::map<MWWorld::Ptr, ProjectileState> mProjectiles;
std::string mStartCell;
void updateWeather(float duration);
int getDaysPerMonth (int month) const;
@ -117,7 +131,7 @@ namespace MWWorld
bool moveObjectImp (const Ptr& ptr, float x, float y, float z);
///< @return true if the active cell (cell player is in) changed
Ptr copyObjectToCell(const Ptr &ptr, CellStore &cell, ESM::Position pos, bool adjustPos=true);
Ptr copyObjectToCell(const Ptr &ptr, CellStore* cell, ESM::Position pos, bool adjustPos=true);
void updateWindowManager ();
void performUpdateSceneQueries ();
@ -134,6 +148,7 @@ namespace MWWorld
void processDoors(float duration);
///< Run physics simulation and modify \a world accordingly.
void moveMagicBolts(float duration);
void moveProjectiles(float duration);
void doPhysics(float duration);
@ -167,11 +182,13 @@ namespace MWWorld
const Files::Collections& fileCollections,
const std::vector<std::string>& contentFiles,
const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir,
ToUTF8::Utf8Encoder* encoder, const std::map<std::string,std::string>& fallbackMap, int mActivationDistanceOverride);
ToUTF8::Utf8Encoder* encoder, const std::map<std::string,std::string>& fallbackMap,
int activationDistanceOverride, const std::string& startCell);
virtual ~World();
virtual void startNewGame();
virtual void startNewGame (bool bypass);
///< \param bypass Bypass regular game start.
virtual void clear();
@ -341,7 +358,7 @@ namespace MWWorld
virtual void deleteObject (const Ptr& ptr);
virtual void moveObject (const Ptr& ptr, float x, float y, float z);
virtual void moveObject (const Ptr& ptr, CellStore &newCell, float x, float y, float z);
virtual void moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z);
virtual void scaleObject (const Ptr& ptr, float scale);
@ -351,7 +368,7 @@ namespace MWWorld
virtual void localRotateObject (const Ptr& ptr, float x, float y, float z);
virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr,MWWorld::CellStore &Cell,ESM::Position pos);
virtual MWWorld::Ptr safePlaceObject(const MWWorld::Ptr& ptr, MWWorld::CellStore* cell, ESM::Position pos);
///< place an object in a "safe" location (ie not in the void, etc). Makes a copy of the Ptr.
virtual void indexToPosition (int cellX, int cellY, float &x, float &y, bool centre = false)
@ -552,8 +569,10 @@ namespace MWWorld
*/
virtual void castSpell (const MWWorld::Ptr& actor);
virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects,
virtual void launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects,
const MWWorld::Ptr& actor, const std::string& sourceName);
virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile,
const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed);
virtual const std::vector<std::string>& getContentFiles() const;

@ -1,26 +1,75 @@
function(enable_unity_build UB_SUFFIX SOURCE_VARIABLE_NAME)
set(files ${SOURCE_VARIABLE_NAME})
# Generate a unique filename for the unity build translation unit
set(unit_build_file ${CMAKE_CURRENT_BINARY_DIR}/ub_${UB_SUFFIX}.cpp)
# Exclude all translation units from compilation
set_source_files_properties(${files} PROPERTIES HEADER_FILE_ONLY true)
# Open the ub file
FILE(WRITE ${unit_build_file} "// Unity Build generated by CMake\n")
# Add include statement for each translation unit
foreach(source_file ${files} )
FILE( APPEND ${unit_build_file} "#include <${source_file}>\n")
endforeach(source_file)
# Complement list of translation units with the name of ub
set(${SOURCE_VARIABLE_NAME} ${${SOURCE_VARIABLE_NAME}} ${unit_build_file} PARENT_SCOPE)
endfunction(enable_unity_build)
macro (add_openmw_dir dir)
set (files)
foreach (u ${ARGN})
file (GLOB ALL "${dir}/${u}.[ch]pp")
foreach (f ${ALL})
list (APPEND files "${f}")
list (APPEND OPENMW_FILES "${f}")
endforeach (f)
endforeach (u)
source_group ("apps\\openmw\\${dir}" FILES ${files})
set (files)
set (cppfiles)
foreach (u ${ARGN})
# Add cpp and hpp to OPENMW_FILES
file (GLOB ALL "${dir}/${u}.[ch]pp")
foreach (f ${ALL})
list (APPEND files "${f}")
list (APPEND OPENMW_FILES "${f}")
endforeach (f)
# Add cpp to unity build
file (GLOB ALL "${dir}/${u}.cpp")
foreach (f ${ALL})
list (APPEND cppfiles "${f}")
endforeach (f)
endforeach (u)
if (OPENMW_UNITY_BUILD)
enable_unity_build(${dir} "${cppfiles}")
list (APPEND OPENMW_FILES ${CMAKE_CURRENT_BINARY_DIR}/ub_${dir}.cpp)
endif()
source_group ("apps\\openmw\\${dir}" FILES ${files})
endmacro (add_openmw_dir)
macro (add_component_dir dir)
set (files)
foreach (u ${ARGN})
file (GLOB ALL "${dir}/${u}.[ch]pp")
foreach (f ${ALL})
list (APPEND files "${f}")
list (APPEND COMPONENT_FILES "${f}")
endforeach (f)
endforeach (u)
source_group ("components\\${dir}" FILES ${files})
set (files)
set (cppfiles)
foreach (u ${ARGN})
file (GLOB ALL "${dir}/${u}.[ch]pp")
foreach (f ${ALL})
list (APPEND files "${f}")
list (APPEND COMPONENT_FILES "${f}")
endforeach (f)
# Add cpp to unity build
file (GLOB ALL "${dir}/${u}.cpp")
foreach (f ${ALL})
list (APPEND cppfiles "${f}")
endforeach (f)
endforeach (u)
if (OPENMW_UNITY_BUILD)
enable_unity_build(${dir} "${cppfiles}")
list (APPEND COMPONENT_FILES ${CMAKE_CURRENT_BINARY_DIR}/ub_${dir}.cpp)
endif()
source_group ("components\\${dir}" FILES ${files})
endmacro (add_component_dir)
macro (add_component_qt_dir dir)

@ -111,7 +111,7 @@ void BSAFile::readHeader()
fail("Directory information larger than entire archive");
// Read the offset info into a temporary buffer
vector<uint32_t> offsets(3*filenum);
std::vector<uint32_t> offsets(3*filenum);
input.read(reinterpret_cast<char*>(&offsets[0]), 12*filenum);
// Read the string table

@ -18,7 +18,7 @@ bool Compiler::DeclarationParser::parseName (const std::string& name, const Toke
{
if (mState==State_Name)
{
std::string name2 = Misc::StringUtils::lowerCase (name);
std::string name2 = ::Misc::StringUtils::lowerCase (name);
char type = mLocals.getType (name2);
@ -80,4 +80,4 @@ bool Compiler::DeclarationParser::parseSpecial (int code, const TokenLoc& loc, S
void Compiler::DeclarationParser::reset()
{
mState = State_Begin;
}
}

@ -50,7 +50,7 @@ namespace Interpreter
return literalBlock+offset;
}
void Runtime::configure (const Interpreter::Type_Code *code, int codeSize, Context& context)
void Runtime::configure (const Type_Code *code, int codeSize, Context& context)
{
clear();

@ -282,9 +282,6 @@ bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
size_t wantedLod = 0;
float cellWorldSize = mTerrain->getStorage()->getCellWorldSize();
if (!mTerrain->getDistantLandEnabled() && dist > cellWorldSize)
return true;
if (dist > cellWorldSize*64)
wantedLod = 6;
else if (dist > cellWorldSize*32)
@ -357,6 +354,7 @@ bool QuadTreeNode::update(const Ogre::Vector3 &cameraPos)
if (!childrenLoaded)
{
mChunk->setVisible(true);
// Make sure child scene nodes are detached until all children are loaded
mSceneNode->removeAllChildren();
}
@ -391,6 +389,8 @@ void QuadTreeNode::load(const LoadResponseData &data)
mChunk = new Chunk(mTerrain->getBufferCache().getUVBuffer(), mBounds, data);
mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags());
mChunk->setCastShadows(true);
if (!mTerrain->getDistantLandEnabled())
mChunk->setRenderingDistance(8192);
mSceneNode->attachObject(mChunk);
mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled());
@ -437,7 +437,7 @@ void QuadTreeNode::unload(bool recursive)
if (recursive && hasChildren())
{
for (int i=0; i<4; ++i)
mChildren[i]->unload();
mChildren[i]->unload(true);
}
}

@ -82,12 +82,14 @@ Sandy Carter (bwrsandman) - Arch Linux
Public Relations and Translations:
Alex McKibben (WeirdSexy) - Podcaster
Artem Kotsynyak (greye) - Russian News Writer
Jim Clauwaert (Zedd) - Public Outreach
Julien Voisin (jvoisin/ap0) - French News Writer
Lukasz Gromanowski (lgro) - English News Writer
Mickey Lyle (raevol) - Release Manager
Pithorn - Chinese News Writer
sir_herrbatka - English/Polish News Writer
Alex McKibben (WeirdSexy) - Podcaster
sir_herrbatka - Polish News Writer
Website:

@ -2,5 +2,11 @@
<MyGUI type="Layout">
<!-- The entire screen -->
<Widget type="Widget" layer="Windows" position="0 0 300 300" name="_Main" />
<Widget type="Widget" layer="Windows" position="0 0 300 300" name="_Main" >
<Widget type="TextBox" skin="SandText" position="0 250 300 50" align="Bottom" name="VersionText">
<Property key="TextAlign" value="Center"/>
<Property key="TextShadow" value="true"/>
<Property key="TextShadowColour" value="0 0 0"/>
</Widget>
</Widget>
</MyGUI>

@ -597,6 +597,8 @@ namespace Physic
std::vector<std::string> PhysicEngine::getCollisions(const std::string& name)
{
RigidBody* body = getRigidBody(name);
if (!body) // fall back to raycasting body if there is no collision body
body = getRigidBody(name, true);
ContactTestResultCallback callback;
dynamicsWorld->contactTest(body, callback);
return callback.mResult;

@ -8,7 +8,6 @@ License: GPL (see GPL3.txt for more information)
Website: http://www.openmw.org
Font Licenses:
EBGaramond-Regular.ttf: OFL (see OFL.txt for more information)
DejaVuLGCSansMono.ttf: custom (see DejaVu Font License.txt for more information)

Loading…
Cancel
Save