From 46a734852b5b6a36ddb554cb86d297c2bd19e71a Mon Sep 17 00:00:00 2001 From: gus Date: Tue, 10 Sep 2013 16:16:13 +0200 Subject: [PATCH 001/142] adding script instruction getLOS + some test about AI --- apps/openmw/mwbase/world.hpp | 3 ++ apps/openmw/mwmechanics/aisequence.cpp | 51 ++++++++++++++++++++++++++ apps/openmw/mwscript/aiextensions.cpp | 28 ++++++++++++++ apps/openmw/mwscript/docs/vmformat.txt | 2 + apps/openmw/mwworld/worldimp.cpp | 5 +++ apps/openmw/mwworld/worldimp.hpp | 3 ++ components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 2 + 8 files changed, 95 insertions(+) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 6ca900a4d3..071d2a16c4 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -356,6 +356,9 @@ namespace MWBase virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out) = 0; ///< get all items in active cells owned by this Npc + virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) = 0; + ///< get Line of Sight (morrowind stupid implementation) + virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; virtual void setupExternalRendering (MWRender::ExternalRendering& rendering) = 0; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 2f06b849a3..79ecf1586c 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -9,6 +9,13 @@ #include "aifollow.hpp" #include "aiactivate.hpp" +#include "..\mwworld\class.hpp" +#include "creaturestats.hpp" +#include "npcstats.hpp" +#include "..\mwbase\environment.hpp" +#include "..\mwbase\world.hpp" +#include "..\mwworld\player.hpp" + void MWMechanics::AiSequence::copy (const AiSequence& sequence) { for (std::list::const_iterator iter (sequence.mPackages.begin()); @@ -54,6 +61,50 @@ bool MWMechanics::AiSequence::isPackageDone() const void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) { + /*if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) + { + MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); + if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) + MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + ESM::Position pos = actor.getRefData().getPosition(); + const ESM::Pathgrid *pathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + + int cellX = actor.getCell()->mCell->mData.mX; + int cellY = actor.getCell()->mCell->mData.mY; + float xCell = 0; + float yCell = 0; + + if (actor.getCell()->mCell->isExterior()) + { + xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; + yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + } + + ESM::Pathgrid::Point dest; + dest.mX = player.getRefData().getPosition().pos[0]; + dest.mY = player.getRefData().getPosition().pos[1]; + dest.mZ = player.getRefData().getPosition().pos[2]; + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + PathFinder mPathFinder; + mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + + if(dest.mX - start.mX < 100) + { + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); + } + }*/ if (!mPackages.empty()) { if (mPackages.front()->execute (actor)) diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index fac44c08f3..39ed36ac7c 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -17,6 +17,9 @@ #include "../mwmechanics/aitravel.hpp" #include "../mwmechanics/aiwander.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + #include "interpretercontext.hpp" #include "ref.hpp" @@ -364,6 +367,28 @@ namespace MWScript } }; + template + class OpGetLineOfSight : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + + MWWorld::Ptr source = R()(runtime); + + std::string actorID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWWorld::Ptr dest = MWBase::Environment::get().getWorld()->getPtr(actorID,true); + bool value = false; + if(dest != MWWorld::Ptr() ) + { + value = MWBase::Environment::get().getWorld()->getLOS(source,dest); + } + runtime.push (value); + } + }; void installOpcodes (Interpreter::Interpreter& interpreter) { @@ -389,6 +414,9 @@ namespace MWScript interpreter.installSegment5 (Compiler::Ai::opcodeGetCurrentAiPackageExplicit, new OpGetCurrentAIPackage); interpreter.installSegment3 (Compiler::Ai::opcodeGetDetected, new OpGetDetected); interpreter.installSegment3 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected); + interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight); + interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight); + interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting(0)); interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting(0)); interpreter.installSegment5 (Compiler::Ai::opcodeSetFight, new OpSetAiSetting(1)); diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 08b4991753..053964effd 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -351,5 +351,7 @@ op 0x200021b: SetWerewolfAcrobatics op 0x200021c: SetWerewolfAcrobaticsExplicit op 0x200021d: ShowVars op 0x200021e: ShowVarsExplicit +op 0x200021f: GetLineOfSight +op 0x200022a: GetLineOfSightExplicit opcodes 0x200021f-0x3ffffff unused diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 09dc0493e8..5e293a2922 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1779,6 +1779,11 @@ namespace MWWorld } } + bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) + { + return false; + } + void World::enableActorCollision(const MWWorld::Ptr& actor, bool enable) { OEngine::Physic::PhysicActor *physicActor = mPhysEngine->getCharacter(actor.getRefData().getHandle()); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 71391239b3..ac1e0b3833 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -403,6 +403,9 @@ namespace MWWorld virtual void getItemsOwnedBy (const MWWorld::Ptr& npc, std::vector& out); ///< get all items in active cells owned by this Npc + virtual bool getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc); + ///< get Line of Sight (morrowind stupid implementation) + virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable); virtual void setupExternalRendering (MWRender::ExternalRendering& rendering); diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 9e0c36825a..a671420d43 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -63,6 +63,7 @@ namespace Compiler extensions.registerFunction ("getfight", 'l', "", opcodeGetFight, opcodeGetFightExplicit); extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit); extensions.registerFunction ("getalarm", 'l', "", opcodeGetAlarm, opcodeGetAlarmExplicit); + extensions.registerFunction ("getlineofsight", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); } } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index c4e2c1bc6b..185eb3c219 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -49,6 +49,8 @@ namespace Compiler const int opcodeGetFleeExplicit = 0x20001c4; const int opcodeGetAlarm = 0x20001c5; const int opcodeGetAlarmExplicit = 0x20001c6; + const int opcodeGetLineOfSight = 0x200021f; + const int opcodeGetLineOfSightExplicit = 0x200022a; } namespace Animation From 9353f4d14f9b96fffe23b92eaa75a974fb061fb6 Mon Sep 17 00:00:00 2001 From: gus Date: Wed, 25 Sep 2013 18:01:36 +0200 Subject: [PATCH 002/142] WIP AI combat --- apps/openmw/mwmechanics/aicombat.cpp | 87 ++++++++++++++++++++++++++ apps/openmw/mwmechanics/aicombat.hpp | 39 ++++++++++++ apps/openmw/mwmechanics/aisequence.cpp | 71 +++++++++++++-------- apps/openmw/mwmechanics/aisequence.hpp | 3 + 4 files changed, 173 insertions(+), 27 deletions(-) create mode 100644 apps/openmw/mwmechanics/aicombat.cpp create mode 100644 apps/openmw/mwmechanics/aicombat.hpp diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp new file mode 100644 index 0000000000..071aa50e39 --- /dev/null +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -0,0 +1,87 @@ +#include "aicombat.hpp" + +#include "movement.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/timestamp.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" + + +namespace MWMechanics +{ + + AiCombat::AiCombat(const std::string &targetId) + :mTargetId(targetId) + { + } + + bool AiCombat::execute (const MWWorld::Ptr& actor) + { + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();//MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); + + MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); + if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) + MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + + ESM::Position pos = actor.getRefData().getPosition(); + const ESM::Pathgrid *pathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + + int cellX = actor.getCell()->mCell->mData.mX; + int cellY = actor.getCell()->mCell->mData.mY; + float xCell = 0; + float yCell = 0; + + if (actor.getCell()->mCell->isExterior()) + { + xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; + yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + } + + ESM::Pathgrid::Point dest; + dest.mX = target.getRefData().getPosition().pos[0]; + dest.mY = target.getRefData().getPosition().pos[1]; + dest.mZ = target.getRefData().getPosition().pos[2]; + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + std::cout << start.mX << " " << dest.mX << "\n"; + + mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + + if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) + { + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + } + + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + std::cout << zAngle; + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + + if(dest.mX - start.mX < 100) + { + //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); + } + + return false; + } + + int AiCombat::getTypeId() const + { + return 2; + } + + AiCombat *MWMechanics::AiCombat::clone() const + { + return new AiCombat(*this); + } +} + diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp new file mode 100644 index 0000000000..3b2ae6fc0b --- /dev/null +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -0,0 +1,39 @@ +#ifndef GAME_MWMECHANICS_AICOMBAT_H +#define GAME_MWMECHANICS_AICOMBAT_H + +#include "aipackage.hpp" + +#include "pathfinding.hpp" + + +#include "..\mwworld\class.hpp" +#include "creaturestats.hpp" +#include "npcstats.hpp" +#include "..\mwbase\environment.hpp" +#include "..\mwbase\world.hpp" +#include "..\mwworld\player.hpp" + +#include "movement.hpp" + +namespace MWMechanics +{ + class AiCombat : public AiPackage + { + public: + AiCombat(const std::string &targetId); + + virtual AiCombat *clone() const; + + virtual bool execute (const MWWorld::Ptr& actor); + ///< \return Package completed? + + virtual int getTypeId() const; + + private: + std::string mTargetId; + + PathFinder mPathFinder; + }; +} + +#endif \ No newline at end of file diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 79ecf1586c..d02f03e18d 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -8,6 +8,7 @@ #include "aitravel.hpp" #include "aifollow.hpp" #include "aiactivate.hpp" +#include "aicombat.hpp" #include "..\mwworld\class.hpp" #include "creaturestats.hpp" @@ -21,9 +22,11 @@ void MWMechanics::AiSequence::copy (const AiSequence& sequence) for (std::list::const_iterator iter (sequence.mPackages.begin()); iter!=sequence.mPackages.end(); ++iter) mPackages.push_back ((*iter)->clone()); + mCombat = sequence.mCombat; + mCombatPackage = sequence.mCombatPackage; } -MWMechanics::AiSequence::AiSequence() : mDone (false) {} +MWMechanics::AiSequence::AiSequence() : mDone (false), mCombat (false), mCombatPackage (0) {} MWMechanics::AiSequence::AiSequence (const AiSequence& sequence) : mDone (false) { @@ -61,16 +64,27 @@ bool MWMechanics::AiSequence::isPackageDone() const void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) { - /*if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) + if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) { - MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); - if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + if(mCombat) + { + //mCombatPackage->execute(actor); + } + else + { + //mCombat = true; + //mCombatPackage = new AiCombat("player"); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - ESM::Position pos = actor.getRefData().getPosition(); - const ESM::Pathgrid *pathgrid = + /*if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) + { + MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); + if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) + MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + ESM::Position pos = actor.getRefData().getPosition(); + const ESM::Pathgrid *pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); int cellX = actor.getCell()->mCell->mData.mX; @@ -80,8 +94,8 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) if (actor.getCell()->mCell->isExterior()) { - xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; - yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; + yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; } ESM::Pathgrid::Point dest; @@ -96,24 +110,26 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) PathFinder mPathFinder; mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - if(dest.mX - start.mX < 100) - { + if(dest.mX - start.mX < 100) + { MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); + } + }*/ + if (!mPackages.empty()) + { + if (mPackages.front()->execute (actor)) + { + mPackages.erase (mPackages.begin()); + mDone = true; + } + else + mDone = false; + } } - }*/ - if (!mPackages.empty()) - { - if (mPackages.front()->execute (actor)) - { - mPackages.erase (mPackages.begin()); - mDone = true; - } - else - mDone = false; } } @@ -121,7 +137,8 @@ void MWMechanics::AiSequence::clear() { for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) delete *iter; - + + if(mCombatPackage) delete mCombatPackage; mPackages.clear(); } diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 9f70daeb83..37084a1e5b 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -18,6 +18,9 @@ namespace MWMechanics class AiSequence { std::list mPackages; + AiPackage* mCombatPackage; + bool mCombat; + bool mDone; void copy (const AiSequence& sequence); From 2537384c502c8cc74769bcd7d968542eff82ce2a Mon Sep 17 00:00:00 2001 From: gus Date: Sat, 28 Sep 2013 12:25:37 +0200 Subject: [PATCH 003/142] bug fix --- apps/openmw/mwmechanics/aicombat.cpp | 98 ++++++++++++++------------ apps/openmw/mwmechanics/aisequence.cpp | 51 +------------- 2 files changed, 54 insertions(+), 95 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 071aa50e39..4988dc9e31 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -22,55 +22,59 @@ namespace MWMechanics { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();//MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); - MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); - if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); - - ESM::Position pos = actor.getRefData().getPosition(); - const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); - - int cellX = actor.getCell()->mCell->mData.mX; - int cellY = actor.getCell()->mCell->mData.mY; - float xCell = 0; - float yCell = 0; - - if (actor.getCell()->mCell->isExterior()) + if(actor.getTypeName() == typeid(ESM::NPC).name()) { - xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; - yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + + MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); + if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) + MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + + ESM::Position pos = actor.getRefData().getPosition(); + const ESM::Pathgrid *pathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + + int cellX = actor.getCell()->mCell->mData.mX; + int cellY = actor.getCell()->mCell->mData.mY; + float xCell = 0; + float yCell = 0; + + if (actor.getCell()->mCell->isExterior()) + { + xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; + yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + } + + ESM::Pathgrid::Point dest; + dest.mX = target.getRefData().getPosition().pos[0]; + dest.mY = target.getRefData().getPosition().pos[1]; + dest.mZ = target.getRefData().getPosition().pos[2]; + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + std::cout << start.mX << " " << dest.mX << "\n"; + + mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + + mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]) + + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + std::cout << zAngle; + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + + float range = 100; + + if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) + < range*range) + { + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell()); + } } - - ESM::Pathgrid::Point dest; - dest.mX = target.getRefData().getPosition().pos[0]; - dest.mY = target.getRefData().getPosition().pos[1]; - dest.mZ = target.getRefData().getPosition().pos[2]; - - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; - start.mZ = pos.pos[2]; - - std::cout << start.mX << " " << dest.mX << "\n"; - - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); - - if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) - { - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - } - - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - std::cout << zAngle; - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - - if(dest.mX - start.mX < 100) - { - //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); - } - return false; } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index d02f03e18d..f3aa877e24 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -68,57 +68,12 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) { if(mCombat) { - //mCombatPackage->execute(actor); + mCombatPackage->execute(actor); } else { - //mCombat = true; - //mCombatPackage = new AiCombat("player"); - - /*if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) - { - MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); - if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) - MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - ESM::Position pos = actor.getRefData().getPosition(); - const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); - - int cellX = actor.getCell()->mCell->mData.mX; - int cellY = actor.getCell()->mCell->mData.mY; - float xCell = 0; - float yCell = 0; - - if (actor.getCell()->mCell->isExterior()) - { - xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; - yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; - } - - ESM::Pathgrid::Point dest; - dest.mX = player.getRefData().getPosition().pos[0]; - dest.mY = player.getRefData().getPosition().pos[1]; - dest.mZ = player.getRefData().getPosition().pos[2]; - - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; - start.mZ = pos.pos[2]; - - PathFinder mPathFinder; - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - - if(dest.mX - start.mX < 100) - { - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); - } - }*/ + mCombat = true; + mCombatPackage = new AiCombat("player"); if (!mPackages.empty()) { if (mPackages.front()->execute (actor)) From 83a375b55d6f6a0b93f476c92fedf385dadd216b Mon Sep 17 00:00:00 2001 From: gus Date: Sat, 28 Sep 2013 21:17:06 +0200 Subject: [PATCH 004/142] AI now continue to hit you. TODO: change the way time is handled --- apps/openmw/mwmechanics/aicombat.cpp | 17 ++++++++++++++--- apps/openmw/mwmechanics/aicombat.hpp | 1 + 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 4988dc9e31..bf5968e8c7 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -14,7 +14,7 @@ namespace MWMechanics { AiCombat::AiCombat(const std::string &targetId) - :mTargetId(targetId) + :mTargetId(targetId),mStartingSecond(0) { } @@ -28,7 +28,7 @@ namespace MWMechanics MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); ESM::Position pos = actor.getRefData().getPosition(); const ESM::Pathgrid *pathgrid = @@ -59,7 +59,7 @@ namespace MWMechanics mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); - mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]) + mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); std::cout << zAngle; @@ -71,6 +71,17 @@ namespace MWMechanics if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) < range*range) { + MWWorld::TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); + if(mStartingSecond == 0) + { + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); + mStartingSecond = ((time.getHour() - int(time.getHour())) * 100); + } + else if( ((time.getHour() - int(time.getHour())) * 100) - mStartingSecond > 1) + { + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + mStartingSecond = 0; + } MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell()); } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 3b2ae6fc0b..bde66a46bc 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -33,6 +33,7 @@ namespace MWMechanics std::string mTargetId; PathFinder mPathFinder; + unsigned int mStartingSecond; }; } From 1ac3d99c78adc9bbef46256ba33c0a109e2a41b0 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 7 Oct 2013 10:20:02 +0200 Subject: [PATCH 005/142] pathfinding now works in AICombat. --- apps/openmw/mwmechanics/aicombat.cpp | 20 ++++++++++++++------ apps/openmw/mwmechanics/aicombat.hpp | 1 + apps/openmw/mwmechanics/pathfinding.cpp | 17 ++++++++++++++++- apps/openmw/mwmechanics/pathfinding.hpp | 12 ++++++++++++ 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index bf5968e8c7..529855b2ff 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -55,14 +55,20 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; - std::cout << start.mX << " " << dest.mX << "\n"; - - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); - + if(!mPathFinder.isPathConstructed()) + mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + else + { + mPathFinder2.buildPath(start, dest, pathgrid, xCell, yCell, true); + ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); + if(mPathFinder2.getPathSize() < mPathFinder.getPathSize() || + (dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200) + mPathFinder = mPathFinder2; + } + mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - std::cout << zAngle; MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; @@ -71,7 +77,9 @@ namespace MWMechanics if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) < range*range) { - MWWorld::TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); + mPathFinder.clearPath(); + + MWWorld::TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); if(mStartingSecond == 0) { MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index bde66a46bc..f61582adc8 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -33,6 +33,7 @@ namespace MWMechanics std::string mTargetId; PathFinder mPathFinder; + PathFinder mPathFinder2; unsigned int mStartingSecond; }; } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 8ef0edab83..ff266f9ae7 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -189,7 +189,7 @@ namespace MWMechanics // This should never happen (programmers should have an if statement checking mIsPathConstructed that prevents this call // if otherwise). if(mPath.empty()) - return 0; + return 0.; const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); float directionX = nextPoint.mX - x; @@ -199,6 +199,21 @@ namespace MWMechanics return Ogre::Radian(acos(directionY / directionResult) * sgn(asin(directionX / directionResult))).valueDegrees(); } + bool PathFinder::checkWaypoint(float x, float y, float z) + { + if(mPath.empty()) + return true; + + ESM::Pathgrid::Point nextPoint = *mPath.begin(); + if(distanceZCorrected(nextPoint, x, y, z) < 64) + { + mPath.pop_front(); + if(mPath.empty()) mIsPathConstructed = false; + return true; + } + return false; + } + bool PathFinder::checkPathCompleted(float x, float y, float z) { if(mPath.empty()) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 35e0fa9087..916df850b5 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -18,6 +18,8 @@ namespace MWMechanics bool checkPathCompleted(float x, float y, float z); ///< \Returns true if the last point of the path has been reached. + bool checkWaypoint(float x, float y, float z); + ///< \Returns true if a way point was reached float getZAngleToNext(float x, float y) const; bool isPathConstructed() const @@ -25,6 +27,16 @@ namespace MWMechanics return mIsPathConstructed; } + int getPathSize() const + { + return mPath.size(); + } + + std::list getPath() const + { + return mPath; + } + private: std::list mPath; bool mIsPathConstructed; From 94069e3a7e35225f9264fc9970e80257e4903011 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 7 Oct 2013 20:30:12 +0200 Subject: [PATCH 006/142] bugfix --- apps/openmw/mwmechanics/aisequence.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index f3aa877e24..ecd21d04c3 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -23,7 +23,7 @@ void MWMechanics::AiSequence::copy (const AiSequence& sequence) iter!=sequence.mPackages.end(); ++iter) mPackages.push_back ((*iter)->clone()); mCombat = sequence.mCombat; - mCombatPackage = sequence.mCombatPackage; + mCombatPackage = sequence.mCombatPackage->clone(); } MWMechanics::AiSequence::AiSequence() : mDone (false), mCombat (false), mCombatPackage (0) {} @@ -93,7 +93,11 @@ void MWMechanics::AiSequence::clear() for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) delete *iter; - if(mCombatPackage) delete mCombatPackage; + if(mCombatPackage) + { + delete mCombatPackage; + mCombatPackage = 0; + } mPackages.clear(); } From 882b136b35736eb78fbcce63c7e2e16ccdb85f9a Mon Sep 17 00:00:00 2001 From: gus Date: Sun, 27 Oct 2013 13:21:16 +0100 Subject: [PATCH 007/142] bugfix of bugfix ^^ --- apps/openmw/mwmechanics/aisequence.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index ecd21d04c3..5e1bc17910 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -23,7 +23,8 @@ void MWMechanics::AiSequence::copy (const AiSequence& sequence) iter!=sequence.mPackages.end(); ++iter) mPackages.push_back ((*iter)->clone()); mCombat = sequence.mCombat; - mCombatPackage = sequence.mCombatPackage->clone(); + mCombatPackage = 0; + if(sequence.mCombat) mCombatPackage = sequence.mCombatPackage->clone(); } MWMechanics::AiSequence::AiSequence() : mDone (false), mCombat (false), mCombatPackage (0) {} From a0edb55f600844b7f5474df1395f7476983d7e99 Mon Sep 17 00:00:00 2001 From: gus Date: Sun, 27 Oct 2013 13:50:29 +0100 Subject: [PATCH 008/142] only NPC with fight over 80 will attack you now --- apps/openmw/mwmechanics/aisequence.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 5e1bc17910..86c07947f6 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -73,8 +73,11 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) } else { - mCombat = true; - mCombatPackage = new AiCombat("player"); + if(actor.getClass().getCreatureStats(actor).getAiSetting(1)> 80) + { + mCombat = true; + mCombatPackage = new AiCombat("player"); + } if (!mPackages.empty()) { if (mPackages.front()->execute (actor)) From 968968502e623857ae730a2a386cbc64108c1066 Mon Sep 17 00:00:00 2001 From: gus Date: Sun, 27 Oct 2013 14:03:58 +0100 Subject: [PATCH 009/142] NPC always face you when fighting --- apps/openmw/mwmechanics/aicombat.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 529855b2ff..8f51c462fa 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -9,6 +9,17 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "OgreMath.h" + +namespace +{ + static float sgn(float a) + { + if(a > 0) + return 1.0; + return -1.0; + } +} namespace MWMechanics { @@ -71,12 +82,20 @@ namespace MWMechanics float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + float range = 100; - + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) < range*range) { + float directionX = dest.mX - start.mX; + float directionY = dest.mY - start.mY; + float directionResult = sqrt(directionX * directionX + directionY * directionY); + + zAngle = Ogre::Radian( acos(directionY / directionResult) * sgn(asin(directionX / directionResult)) ).valueDegrees(); + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + mPathFinder.clearPath(); MWWorld::TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); From 2515a1239e67b169f0e1d3a189f6eb25f18a18cd Mon Sep 17 00:00:00 2001 From: gus Date: Sun, 27 Oct 2013 14:22:51 +0100 Subject: [PATCH 010/142] improved combat conditions --- apps/openmw/mwmechanics/aisequence.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 86c07947f6..a798040fe6 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -17,6 +17,8 @@ #include "..\mwbase\world.hpp" #include "..\mwworld\player.hpp" +#include "..\mwbase\mechanicsmanager.hpp" + void MWMechanics::AiSequence::copy (const AiSequence& sequence) { for (std::list::const_iterator iter (sequence.mPackages.begin()); @@ -73,7 +75,23 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) } else { - if(actor.getClass().getCreatureStats(actor).getAiSetting(1)> 80) + ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); + ESM::Position actorpos = actor.getRefData().getPosition(); + float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) + +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) + +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); + float fight = actor.getClass().getCreatureStats(actor).getAiSetting(1); + float disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(actor); + if(fight == 100 + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000) + || (fight >= 80 && disp <= 40) + || (fight >= 70 && disp <= 35 && d <= 1000) + || (fight >= 60 && disp <= 30 && d <= 1000) + || (fight >= 50 && disp == 0) + || (fight >= 40 && disp <= 10 && d <= 500) + ) { mCombat = true; mCombatPackage = new AiCombat("player"); From d7584f9df7f6d39d7191aa7d8fe4448ba04b11f1 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 18:01:40 +0100 Subject: [PATCH 011/142] getLineOfSight, no script instruction yet --- apps/openmw/mwmechanics/aisequence.cpp | 6 ++++-- apps/openmw/mwworld/worldimp.cpp | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index a798040fe6..940002a573 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -82,7 +82,8 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); float fight = actor.getClass().getCreatureStats(actor).getAiSetting(1); float disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(actor); - if(fight == 100 + bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + if( ( (fight == 100 ) || (fight >= 95 && d <= 3000) || (fight >= 90 && d <= 2000) || (fight >= 80 && d <= 1000) @@ -90,7 +91,8 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) || (fight >= 70 && disp <= 35 && d <= 1000) || (fight >= 60 && disp <= 30 && d <= 1000) || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) + || (fight >= 40 && disp <= 10 && d <= 500) ) + && LOS ) { mCombat = true; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 724972600a..b6d6cc2d8a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1786,6 +1786,16 @@ namespace MWWorld bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc) { + Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(npc.getRefData().getHandle())->getHalfExtents(); + float* pos1 = npc.getRefData().getPosition().pos; + Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetNpc.getRefData().getHandle())->getHalfExtents(); + float* pos2 = targetNpc.getRefData().getPosition().pos; + + btVector3 from(pos1[0],pos1[1],pos1[2]+halfExt1.z); + btVector3 to(pos2[0],pos2[1],pos2[2]+halfExt2.z); + + std::pair result = mPhysEngine->rayTest(from, to,false); + if(result.first == "") return true; return false; } From 2d84f7d33dd2d65d5f53acf8439737d6563f6058 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 18:10:00 +0100 Subject: [PATCH 012/142] NPC run --- apps/openmw/mwmechanics/aicombat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 8f51c462fa..c3ac8cb551 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -35,7 +35,7 @@ namespace MWMechanics if(actor.getTypeName() == typeid(ESM::NPC).name()) { - + MWWorld::Class::get(actor).setStance(actor, MWWorld::Class::Run,true); MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); From 12d8b4e0f888ef50f8704db9f4ad9bd79a78ab5e Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 18:13:07 +0100 Subject: [PATCH 013/142] bugfix --- apps/openmw/mwmechanics/aicombat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index c3ac8cb551..b01889f89b 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -31,7 +31,7 @@ namespace MWMechanics bool AiCombat::execute (const MWWorld::Ptr& actor) { - const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();//MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); if(actor.getTypeName() == typeid(ESM::NPC).name()) { From b7a92431733f729f89196713a9b19fdbd22138d1 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 18:22:36 +0100 Subject: [PATCH 014/142] fighting should stop when the target is dead --- apps/openmw/mwmechanics/aicombat.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index b01889f89b..80ad241ef8 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -33,6 +33,8 @@ namespace MWMechanics { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); + if(MWWorld::Class::get(actor).getCreatureStats(actor).getHealth().getCurrent() <= 0) return true; + if(actor.getTypeName() == typeid(ESM::NPC).name()) { MWWorld::Class::get(actor).setStance(actor, MWWorld::Class::Run,true); @@ -45,8 +47,6 @@ namespace MWMechanics const ESM::Pathgrid *pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); - int cellX = actor.getCell()->mCell->mData.mX; - int cellY = actor.getCell()->mCell->mData.mY; float xCell = 0; float yCell = 0; From 0e862092241c3f1d22bd153165b20a5df9fd3fcf Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 18:33:10 +0100 Subject: [PATCH 015/142] linux fix :p --- apps/openmw/mwmechanics/aicombat.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 80ad241ef8..66b93e8c9e 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -2,12 +2,12 @@ #include "movement.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/player.hpp" -#include "../mwworld/timestamp.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" -#include "../mwbase/mechanicsmanager.hpp" +#include "..\mwworld\class.hpp" +#include "..\mwworld\player.hpp" +#include "..\mwworld\timestamp.hpp" +#include "..\mwbase\world.hpp" +#include "..\mwbase\environment.hpp" +#include "..\mwbase\mechanicsmanager.hpp" #include "OgreMath.h" From 5b20cce849d21d8ddb54bbee1c2c2cbfe1389324 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 18:36:09 +0100 Subject: [PATCH 016/142] CMake fix --- apps/openmw/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a44fd4b343..a35fc91974 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -68,7 +68,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow - aiescort aiactivate repair enchanting pathfinding security + aiescort aiactivate aicombat repair enchanting pathfinding security ) add_openmw_dir (mwbase From a6e7c6c1049d8b7a4a97fc76bdcc285d51caa7b8 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 28 Oct 2013 23:04:46 +0100 Subject: [PATCH 017/142] disable AI combat for creatures --- apps/openmw/mwmechanics/aisequence.cpp | 45 ++++++++++++++------------ 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 940002a573..f94bb4b5f8 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -75,28 +75,31 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) } else { - ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); - ESM::Position actorpos = actor.getRefData().getPosition(); - float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) - +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) - +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); - float fight = actor.getClass().getCreatureStats(actor).getAiSetting(1); - float disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(actor); - bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - if( ( (fight == 100 ) - || (fight >= 95 && d <= 3000) - || (fight >= 90 && d <= 2000) - || (fight >= 80 && d <= 1000) - || (fight >= 80 && disp <= 40) - || (fight >= 70 && disp <= 35 && d <= 1000) - || (fight >= 60 && disp <= 30 && d <= 1000) - || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) ) - && LOS - ) + if(actor.getTypeName() == typeid(ESM::NPC).name()) { - mCombat = true; - mCombatPackage = new AiCombat("player"); + ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); + ESM::Position actorpos = actor.getRefData().getPosition(); + float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) + +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) + +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); + float fight = actor.getClass().getCreatureStats(actor).getAiSetting(1); + float disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(actor); + bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + if( ( (fight == 100 ) + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000) + || (fight >= 80 && disp <= 40) + || (fight >= 70 && disp <= 35 && d <= 1000) + || (fight >= 60 && disp <= 30 && d <= 1000) + || (fight >= 50 && disp == 0) + || (fight >= 40 && disp <= 10 && d <= 500) ) + && LOS + ) + { + mCombat = true; + mCombatPackage = new AiCombat("player"); + } } if (!mPackages.empty()) { From 650a112e2e5168ae3ab26d5ebf8a9c98d5ed46b5 Mon Sep 17 00:00:00 2001 From: gus Date: Wed, 30 Oct 2013 20:42:50 +0100 Subject: [PATCH 018/142] better timer --- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/aiactivate.cpp | 2 +- apps/openmw/mwmechanics/aiactivate.hpp | 2 +- apps/openmw/mwmechanics/aicombat.cpp | 20 ++++++++++++-------- apps/openmw/mwmechanics/aicombat.hpp | 4 ++-- apps/openmw/mwmechanics/aiescort.cpp | 2 +- apps/openmw/mwmechanics/aiescort.hpp | 2 +- apps/openmw/mwmechanics/aifollow.cpp | 2 +- apps/openmw/mwmechanics/aifollow.hpp | 2 +- apps/openmw/mwmechanics/aipackage.hpp | 2 +- apps/openmw/mwmechanics/aisequence.cpp | 6 +++--- apps/openmw/mwmechanics/aisequence.hpp | 2 +- apps/openmw/mwmechanics/aitravel.cpp | 2 +- apps/openmw/mwmechanics/aitravel.hpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 2 +- apps/openmw/mwmechanics/aiwander.hpp | 2 +- 16 files changed, 30 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 566d4bc505..52c1685a03 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -35,7 +35,7 @@ namespace MWMechanics if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) { CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - creatureStats.getAiSequence().execute (ptr); + creatureStats.getAiSequence().execute (ptr,duration); } } diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index b94c8c2599..ee0dcf96e5 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -9,7 +9,7 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const { return new AiActivate(*this); } -bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor) +bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) { std::cout << "AiActivate completed.\n"; return true; diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 7f3d4016dc..f922e238c2 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -12,7 +12,7 @@ namespace MWMechanics public: AiActivate(const std::string &objectId); virtual AiActivate *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 66b93e8c9e..bab183f119 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -25,11 +25,11 @@ namespace MWMechanics { AiCombat::AiCombat(const std::string &targetId) - :mTargetId(targetId),mStartingSecond(0) + :mTargetId(targetId),mTimer(0) { } - bool AiCombat::execute (const MWWorld::Ptr& actor) + bool AiCombat::execute (const MWWorld::Ptr& actor,float duration) { const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); @@ -97,18 +97,22 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); mPathFinder.clearPath(); - - MWWorld::TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); - if(mStartingSecond == 0) + + if(mTimer == 0) { MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); - mStartingSecond = ((time.getHour() - int(time.getHour())) * 100); + //mTimer = mTimer + duration; } - else if( ((time.getHour() - int(time.getHour())) * 100) - mStartingSecond > 1) + if( mTimer > 1) { MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); - mStartingSecond = 0; + mTimer = 0; } + else + { + mTimer = mTimer + duration; + } + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell()); } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index f61582adc8..8e2c3cb94f 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -24,7 +24,7 @@ namespace MWMechanics virtual AiCombat *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; @@ -34,7 +34,7 @@ namespace MWMechanics PathFinder mPathFinder; PathFinder mPathFinder2; - unsigned int mStartingSecond; + float mTimer; }; } diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 556e0b1267..3615c8546e 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -71,7 +71,7 @@ namespace MWMechanics return new AiEscort(*this); } - bool AiEscort::execute (const MWWorld::Ptr& actor) + bool AiEscort::execute (const MWWorld::Ptr& actor,float duration) { // If AiEscort has ran for as long or longer then the duration specified // and the duration is not infinite, the package is complete. diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 3ae604035a..f3f6d2bd9b 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -18,7 +18,7 @@ namespace MWMechanics virtual AiEscort *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index dab9e02839..73bf9259af 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -15,7 +15,7 @@ MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const return new AiFollow(*this); } - bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor) + bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { std::cout << "AiFollow completed.\n"; return true; diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 0b37b0a2d9..39df024e4b 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -13,7 +13,7 @@ namespace MWMechanics AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z); AiFollow(const std::string &ActorId,const std::string &CellId,float duration, float X, float Y, float Z); virtual AiFollow *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index d286fbba89..794708f227 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -17,7 +17,7 @@ namespace MWMechanics virtual AiPackage *clone() const = 0; - virtual bool execute (const MWWorld::Ptr& actor) = 0; + virtual bool execute (const MWWorld::Ptr& actor,float duration) = 0; ///< \return Package completed? virtual int getTypeId() const = 0; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index f94bb4b5f8..bdde23e0d3 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -65,13 +65,13 @@ bool MWMechanics::AiSequence::isPackageDone() const return mDone; } -void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) +void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration) { if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) { if(mCombat) { - mCombatPackage->execute(actor); + mCombatPackage->execute(actor,duration); } else { @@ -103,7 +103,7 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor) } if (!mPackages.empty()) { - if (mPackages.front()->execute (actor)) + if (mPackages.front()->execute (actor,duration)) { mPackages.erase (mPackages.begin()); mDone = true; diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 37084a1e5b..ea2a048f16 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -41,7 +41,7 @@ namespace MWMechanics bool isPackageDone() const; ///< Has a package been completed during the last update? - void execute (const MWWorld::Ptr& actor); + void execute (const MWWorld::Ptr& actor,float duration); ///< Execute package. void clear(); diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index d47a49c702..08d7586388 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -31,7 +31,7 @@ namespace MWMechanics return new AiTravel(*this); } - bool AiTravel::execute (const MWWorld::Ptr& actor) + bool AiTravel::execute (const MWWorld::Ptr& actor,float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); ESM::Position pos = actor.getRefData().getPosition(); diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 6eb9af8cec..b479dfd431 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -13,7 +13,7 @@ namespace MWMechanics AiTravel(float x, float y, float z); virtual AiTravel *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 96a41883b9..ecc41489a5 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -63,7 +63,7 @@ namespace MWMechanics return new AiWander(*this); } - bool AiWander::execute (const MWWorld::Ptr& actor) + bool AiWander::execute (const MWWorld::Ptr& actor,float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index c82ccc2155..48bc62c625 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -16,7 +16,7 @@ namespace MWMechanics AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); virtual AiPackage *clone() const; - virtual bool execute (const MWWorld::Ptr& actor); + virtual bool execute (const MWWorld::Ptr& actor,float duration); ///< \return Package completed? virtual int getTypeId() const; ///< 0: Wander From 2a927f65d396e8c9d249e55a42a90f8376e18195 Mon Sep 17 00:00:00 2001 From: gus Date: Wed, 30 Oct 2013 20:52:23 +0100 Subject: [PATCH 019/142] perfomances optimisations (pathfinding is computed less often, max 4 times per sec) --- apps/openmw/mwmechanics/aicombat.cpp | 11 ++++++++--- apps/openmw/mwmechanics/aicombat.hpp | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index bab183f119..0a64abec32 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -25,7 +25,7 @@ namespace MWMechanics { AiCombat::AiCombat(const std::string &targetId) - :mTargetId(targetId),mTimer(0) + :mTargetId(targetId),mTimer(0),mTimer2(0) { } @@ -66,15 +66,20 @@ namespace MWMechanics start.mY = pos.pos[1]; start.mZ = pos.pos[2]; + mTimer2 = mTimer2 + duration; + if(!mPathFinder.isPathConstructed()) mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); else { mPathFinder2.buildPath(start, dest, pathgrid, xCell, yCell, true); ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); - if(mPathFinder2.getPathSize() < mPathFinder.getPathSize() || - (dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200) + if((mTimer2 > 0.25)&&(mPathFinder2.getPathSize() < mPathFinder.getPathSize() || + (dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200)) + { + mTimer2 = 0; mPathFinder = mPathFinder2; + } } mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 8e2c3cb94f..7321b2cde6 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -35,6 +35,7 @@ namespace MWMechanics PathFinder mPathFinder; PathFinder mPathFinder2; float mTimer; + float mTimer2; }; } From 02ded18fc55d313fbd39e6e683a7e8726c702020 Mon Sep 17 00:00:00 2001 From: gus Date: Thu, 31 Oct 2013 09:43:12 +0100 Subject: [PATCH 020/142] Linux backslash fix --- apps/openmw/mwmechanics/aicombat.cpp | 12 ++++++------ apps/openmw/mwmechanics/aisequence.cpp | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 0a64abec32..3054a9456c 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -2,12 +2,12 @@ #include "movement.hpp" -#include "..\mwworld\class.hpp" -#include "..\mwworld\player.hpp" -#include "..\mwworld\timestamp.hpp" -#include "..\mwbase\world.hpp" -#include "..\mwbase\environment.hpp" -#include "..\mwbase\mechanicsmanager.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/timestamp.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "OgreMath.h" diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index bdde23e0d3..994a7a9323 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -10,14 +10,14 @@ #include "aiactivate.hpp" #include "aicombat.hpp" -#include "..\mwworld\class.hpp" +#include "../mwworld/class.hpp" #include "creaturestats.hpp" #include "npcstats.hpp" -#include "..\mwbase\environment.hpp" -#include "..\mwbase\world.hpp" -#include "..\mwworld\player.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/player.hpp" -#include "..\mwbase\mechanicsmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" void MWMechanics::AiSequence::copy (const AiSequence& sequence) { From 683b8d77dd3bb7f83b31f870f6e07e732291a818 Mon Sep 17 00:00:00 2001 From: gus Date: Thu, 31 Oct 2013 11:02:26 +0100 Subject: [PATCH 021/142] build fix (thanks travis) --- apps/openmw/mwmechanics/aicombat.cpp | 3 +++ apps/openmw/mwmechanics/aicombat.hpp | 8 -------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 3054a9456c..aeddb4781e 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -9,6 +9,9 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "creaturestats.hpp" +#include "npcstats.hpp" + #include "OgreMath.h" namespace diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 7321b2cde6..eed92ef3a7 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -5,14 +5,6 @@ #include "pathfinding.hpp" - -#include "..\mwworld\class.hpp" -#include "creaturestats.hpp" -#include "npcstats.hpp" -#include "..\mwbase\environment.hpp" -#include "..\mwbase\world.hpp" -#include "..\mwworld\player.hpp" - #include "movement.hpp" namespace MWMechanics From e885c502a1c7742d4fff43afa94121dad91d4418 Mon Sep 17 00:00:00 2001 From: eroen Date: Fri, 8 Nov 2013 04:24:36 +0100 Subject: [PATCH 022/142] Stop installing "Daedric Font License.txt" It was removed in 3a827d9c12 --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f31a5e533..fcc63e1b9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -383,7 +383,6 @@ IF(NOT WIN32 AND NOT APPLE) # Install licenses INSTALL(FILES "DejaVu Font License.txt" DESTINATION "${LICDIR}" ) - INSTALL(FILES "Daedric Font License.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "OFL.txt" DESTINATION "${LICDIR}" ) INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) ENDIF (DPKG_PROGRAM) @@ -458,7 +457,6 @@ if(WIN32) "${OpenMW_SOURCE_DIR}/GPL3.txt" "${OpenMW_SOURCE_DIR}/OFL.txt" "${OpenMW_SOURCE_DIR}/DejaVu Font License.txt" - "${OpenMW_SOURCE_DIR}/Daedric Font License.txt" "${OpenMW_BINARY_DIR}/settings-default.cfg" "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" "${OpenMW_BINARY_DIR}/Release/openmw.exe" From 5a071b0f81515b43fdc26ccb230168bfca2554ed Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 7 Nov 2013 23:05:45 +0100 Subject: [PATCH 023/142] necessary dpkg rules to get opencs building and packaged on dpkg systems --- CMakeLists.txt | 2 +- apps/opencs/CMakeLists.txt | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f31a5e533..b90d64666d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -431,7 +431,7 @@ IF(NOT WIN32 AND NOT APPLE) Data files from the original game is required to run it.") SET(CPACK_DEBIAN_PACKAGE_NAME "openmw") SET(CPACK_DEBIAN_PACKAGE_VERSION "${VERSION_STRING}") - SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW bsatool;Bsatool esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") + SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW opencs;OpenCS bsatool;Bsatool esmtool;Esmtool omwlauncher;OMWLauncher mwiniimporter;MWiniImporter") SET(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.11.2), libfreetype6 (>= 2.2.1), libgcc1 (>= 1:4.1.1), libmpg123-0 (>= 1.12.1), libopenal1 (>= 1:1.12.854), libsndfile1 (>= 1.0.23), libstdc++6 (>= 4.4.5), libuuid1 (>= 2.17.2), libqtgui4 (>= 4.7.0)") SET(CPACK_DEBIAN_PACKAGE_SECTION "Games") diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index d317331e51..7498044ab5 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -160,3 +160,8 @@ target_link_libraries(opencs ${QT_LIBRARIES} components ) + +if(DPKG_PROGRAM) + INSTALL(TARGETS opencs RUNTIME DESTINATION games COMPONENT opencs) +endif() + From 04edd25add7ed4cc43e7f705a8d04e17cb9e6514 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 19:09:05 +0400 Subject: [PATCH 024/142] OpenCS as a separate app bundle --- apps/opencs/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 7498044ab5..1fb5737a3f 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -149,12 +149,17 @@ qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_executable(opencs + MACOSX_BUNDLE ${OPENCS_SRC} ${OPENCS_UI_HDR} ${OPENCS_MOC_SRC} ${OPENCS_RES_SRC} ) +if(APPLE) + set_target_properties(opencs PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") +endif(APPLE) + target_link_libraries(opencs ${Boost_LIBRARIES} ${QT_LIBRARIES} From df5b52b45b4601857f09cd4833e34b285dcbdec0 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 20:11:53 +0400 Subject: [PATCH 025/142] OpenCS.app icon & bundle properties --- apps/opencs/CMakeLists.txt | 20 +++++++++++++++++++- files/mac/opencs.icns | Bin 0 -> 50537 bytes 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 files/mac/opencs.icns diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 1fb5737a3f..bd8a852b92 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -148,16 +148,34 @@ qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) +if(APPLE) + set (OPENCS_MAC_ICON ${CMAKE_SOURCE_DIR}/files/mac/opencs.icns) +else() + set (OPENCS_MAC_ICON "") +endif(APPLE) + add_executable(opencs MACOSX_BUNDLE ${OPENCS_SRC} ${OPENCS_UI_HDR} ${OPENCS_MOC_SRC} ${OPENCS_RES_SRC} + ${OPENCS_MAC_ICON} ) if(APPLE) - set_target_properties(opencs PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}") + set_target_properties(opencs PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}" + OUTPUT_NAME "OpenCS" + MACOSX_BUNDLE_ICON_FILE "opencs.icns" + MACOSX_BUNDLE_BUNDLE_NAME "OpenCS" + MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs" + MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION} + MACOSX_BUNDLE_BUNDLE_VERSION ${OPENMW_VERSION} + ) + + set_source_files_properties(${OPENCS_MAC_ICON} PROPERTIES + MACOSX_PACKAGE_LOCATION Resources) endif(APPLE) target_link_libraries(opencs diff --git a/files/mac/opencs.icns b/files/mac/opencs.icns new file mode 100644 index 0000000000000000000000000000000000000000..98812f871d9a9a8bc7bfb1ab86d22cb33ab45bd5 GIT binary patch literal 50537 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 Date: Sun, 10 Nov 2013 20:39:35 +0400 Subject: [PATCH 026/142] OpenCS: set current directory to bundle location on OS X, like in OpenMW binary --- apps/opencs/main.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index e5e7514ce0..76869d0afa 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -7,6 +7,11 @@ #include #include +// for Ogre::macBundlePath +#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE +#include +#endif + class Application : public QApplication { private: @@ -32,6 +37,12 @@ class Application : public QApplication int main(int argc, char *argv[]) { +#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE + // set current dir to bundle path + boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); + boost::filesystem::current_path(bundlePath); +#endif + Q_INIT_RESOURCE (resources); Application mApplication (argc, argv); From dfa900e4e3928f3a997202abc1b11da2ddad6b97 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 20:58:57 +0400 Subject: [PATCH 027/142] =?UTF-8?q?OS=20X:=20Fixed=20=E2=80=9Cmacro=20rede?= =?UTF-8?q?fined=E2=80=9D=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/launcher/graphicspage.cpp | 6 ++++-- apps/launcher/main.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 52fa192d55..516c3d8233 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -4,10 +4,12 @@ #include #include -#ifdef __APPLE__ +#ifdef MAC_OS_X_VERSION_MIN_REQUIRED +#undef MAC_OS_X_VERSION_MIN_REQUIRED // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ -#endif +#endif // MAC_OS_X_VERSION_MIN_REQUIRED + #include #include diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 0b5e62a66f..9f89f28102 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -3,10 +3,12 @@ #include #include -#ifdef __APPLE__ +#ifdef MAC_OS_X_VERSION_MIN_REQUIRED +#undef MAC_OS_X_VERSION_MIN_REQUIRED // We need to do this because of Qt: https://bugreports.qt-project.org/browse/QTBUG-22154 #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ -#endif +#endif // MAC_OS_X_VERSION_MIN_REQUIRED + #include #include "maindialog.hpp" From fa138183607c2b02787abd9c9b955dcca77f50b9 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 22:04:13 +0400 Subject: [PATCH 028/142] OS X: OpenCS packaging --- CMakeLists.txt | 8 ++++++-- apps/opencs/CMakeLists.txt | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 578c6cfd63..01f02ddb96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -685,7 +685,10 @@ if (APPLE) set(CPACK_PACKAGE_VERSION_MINOR ${OPENMW_VERSION_MINO}) set(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) - set(APPS "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") + set(OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") + + set(OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/OpenCS.app") + set(PLUGINS "") set(ABSOLUTE_PLUGINS "") @@ -746,7 +749,8 @@ if (APPLE) cmake_policy(SET CMP0009 OLD) set(BU_CHMOD_BUNDLE_ITEMS ON) include(BundleUtilities) - fixup_bundle(\"${APPS}\" \"${PLUGINS}\" \"${DIRS}\") + fixup_bundle(\"${OPENMW_APP}\" \"${PLUGINS}\" \"${DIRS}\") + fixup_bundle(\"${OPENCS_APP}\" \"\" \"${DIRS}\") " COMPONENT Runtime) include(CPack) endif (APPLE) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index bd8a852b92..f3b93ab1b0 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -188,3 +188,6 @@ if(DPKG_PROGRAM) INSTALL(TARGETS opencs RUNTIME DESTINATION games COMPONENT opencs) endif() +if(APPLE) + INSTALL(TARGETS opencs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) +endif() \ No newline at end of file From 96714fc2c136da990d9da1b4d6416f5a23e0e98a Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 22:19:32 +0400 Subject: [PATCH 029/142] OpenCS: proper working dir & library path on OS X --- apps/opencs/main.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index 76869d0afa..1a3f63c4bb 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -8,8 +8,8 @@ #include // for Ogre::macBundlePath -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE -#include +#ifdef Q_OS_MAC +#include #endif class Application : public QApplication @@ -37,15 +37,28 @@ class Application : public QApplication int main(int argc, char *argv[]) { -#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE - // set current dir to bundle path - boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); - boost::filesystem::current_path(bundlePath); -#endif - Q_INIT_RESOURCE (resources); Application mApplication (argc, argv); +#ifdef Q_OS_MAC + QDir dir(QCoreApplication::applicationDirPath()); + if (dir.dirName() == "MacOS") { + dir.cdUp(); + dir.cdUp(); + dir.cdUp(); + } + QDir::setCurrent(dir.absolutePath()); + + // force Qt to load only LOCAL plugins, don't touch system Qt installation + QDir pluginsPath(QCoreApplication::applicationDirPath()); + pluginsPath.cdUp(); + pluginsPath.cd("Plugins"); + + QStringList libraryPaths; + libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); + mApplication.setLibraryPaths(libraryPaths); +#endif + mApplication.setWindowIcon (QIcon (":./opencs.png")); CS::Editor editor; From dafe80874ab096f018f2642e809bf620915c769c Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 22:24:31 +0400 Subject: [PATCH 030/142] Added empty line --- apps/opencs/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index f3b93ab1b0..f87650b331 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -190,4 +190,4 @@ endif() if(APPLE) INSTALL(TARGETS opencs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) -endif() \ No newline at end of file +endif() From 05e19b37e3f0c0836e18c60010ab9dc32b0d27db Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Sun, 10 Nov 2013 22:25:29 +0400 Subject: [PATCH 031/142] Removed obsolete comment --- apps/opencs/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index 1a3f63c4bb..344a9360f6 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -7,7 +7,6 @@ #include #include -// for Ogre::macBundlePath #ifdef Q_OS_MAC #include #endif From 821d0c5c2d7e48b72259eefcebdb5da4712c35fa Mon Sep 17 00:00:00 2001 From: Marek Kochanowicz Date: Wed, 13 Nov 2013 11:38:29 +0100 Subject: [PATCH 032/142] Corrected filter template loading. --- apps/opencs/model/doc/document.cpp | 32 +++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 590a19439c..4ee3c461fe 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2245,29 +2245,33 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co mData.setDescription (""); mData.setAuthor (""); } -/// \todo un-outcomment the else, once loading an existing content file works properly again. + if (boost::filesystem::exists (mProjectPath)) + { + std::cout<<"Loading file."< Date: Wed, 13 Nov 2013 14:56:04 +0100 Subject: [PATCH 033/142] fixed handling of user directory defaultfilters file; some cleanup --- apps/opencs/model/doc/document.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 4ee3c461fe..27f4f498a4 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -2245,33 +2245,39 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, co mData.setDescription (""); mData.setAuthor (""); } + + bool filtersFound = false; + if (boost::filesystem::exists (mProjectPath)) { - std::cout<<"Loading file."< Date: Fri, 15 Nov 2013 15:59:38 +0100 Subject: [PATCH 034/142] Introduce -DBUILD_WITH_DPKG to toggle dpkg based install The current system automagically chooses between the dpkg-based install method (for debian-derived distributions) and a "traditional" install and sets install paths based on whether cmake can find a 'dpkg' executable. This is not ideal, since dpkg is occasionally installed on linux distributions unrelated to debian for purposes other than package management. In particular, Arch and Gentoo carry it in their repositories. --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b85fabf522..b4d1a9a808 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,10 @@ option(USE_MPG123 "use mpg123 + libsndfile for sound" ON) # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) -find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") +option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF) +if(BUILD_WITH_DPKG) + find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") +endif(BUILD_WITH_DPKG) # Location of morrowind data files if (APPLE) From d4b8ac5b499b2a109503937b34861a2e77c44fdd Mon Sep 17 00:00:00 2001 From: eroen Date: Fri, 15 Nov 2013 16:53:05 +0100 Subject: [PATCH 035/142] don't dpkg on windows and macos No need to clutter windows and macos configuration with dpkg options, as it is unlikely to ever be useful. --- CMakeLists.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b4d1a9a808..d36898400a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,10 +50,12 @@ option(USE_MPG123 "use mpg123 + libsndfile for sound" ON) # OS X deployment option(OPENMW_OSX_DEPLOYMENT OFF) -option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF) -if(BUILD_WITH_DPKG) - find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") -endif(BUILD_WITH_DPKG) +if(UNIX AND NOT APPLE) + option(BUILD_WITH_DPKG "enable dpkg-based install for debian and debian derivatives" OFF) + if(BUILD_WITH_DPKG) + find_program(DPKG_PROGRAM dpkg DOC "dpkg program of Debian-based systems") + endif(BUILD_WITH_DPKG) +endif(UNIX AND NOT APPLE) # Location of morrowind data files if (APPLE) From c5f1bbcc5fb41151dd7ea8117b519ad0e849a449 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Nov 2013 19:43:25 +0100 Subject: [PATCH 036/142] Add functions to get the effect affecting resistance and weakness for another effect --- components/esm/loadmgef.cpp | 94 +++++++++++++++++++++++++++++++++++++ components/esm/loadmgef.hpp | 6 +++ 2 files changed, 100 insertions(+) diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 1a90f5b09c..46e2a8555d 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -81,6 +81,100 @@ void MagicEffect::save(ESMWriter &esm) const esm.writeHNOString("DESC", mDescription); } +short MagicEffect::getResistanceEffect(short effect) +{ + // Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute + + // + std::map effects; + effects[DisintegrateArmor] = Sanctuary; + effects[DisintegrateWeapon] = Sanctuary; + + for (int i=0; i<5; ++i) + effects[DrainAttribute+i] = ResistMagicka; + for (int i=0; i<5; ++i) + effects[DamageAttribute+i] = ResistMagicka; + for (int i=0; i<5; ++i) + effects[AbsorbAttribute+i] = ResistMagicka; + for (int i=0; i<10; ++i) + effects[WeaknessToFire+i] = ResistMagicka; + + effects[Burden] = ResistMagicka; + effects[Charm] = ResistMagicka; + effects[Silence] = ResistMagicka; + effects[Blind] = ResistMagicka; + effects[Sound] = ResistMagicka; + + for (int i=0; i<2; ++i) + { + effects[CalmHumanoid] = ResistMagicka; + effects[FrenzyHumanoid] = ResistMagicka; + effects[DemoralizeHumanoid] = ResistMagicka; + effects[RallyHumanoid] = ResistMagicka; + } + + effects[TurnUndead] = ResistMagicka; + + effects[FireDamage] = ResistFire; + effects[FrostDamage] = ResistFrost; + effects[ShockDamage] = ResistShock; + effects[Vampirism] = ResistCommonDisease; + effects[Corprus] = ResistCorprusDisease; + effects[Poison] = ResistPoison; + effects[Paralyze] = ResistParalysis; + + if (effects.find(effect) != effects.end()) + return effects[effect]; + else + return -1; +} + +short MagicEffect::getWeaknessEffect(short effect) +{ + std::map effects; + effects[DisintegrateArmor] = Sanctuary; + effects[DisintegrateWeapon] = Sanctuary; + + for (int i=0; i<5; ++i) + effects[DrainAttribute+i] = WeaknessToMagicka; + for (int i=0; i<5; ++i) + effects[DamageAttribute+i] = WeaknessToMagicka; + for (int i=0; i<5; ++i) + effects[AbsorbAttribute+i] = WeaknessToMagicka; + for (int i=0; i<10; ++i) + effects[WeaknessToFire+i] = WeaknessToMagicka; + + effects[Burden] = WeaknessToMagicka; + effects[Charm] = WeaknessToMagicka; + effects[Silence] = WeaknessToMagicka; + effects[Blind] = WeaknessToMagicka; + effects[Sound] = WeaknessToMagicka; + + for (int i=0; i<2; ++i) + { + effects[CalmHumanoid] = WeaknessToMagicka; + effects[FrenzyHumanoid] = WeaknessToMagicka; + effects[DemoralizeHumanoid] = WeaknessToMagicka; + effects[RallyHumanoid] = WeaknessToMagicka; + } + + effects[TurnUndead] = WeaknessToMagicka; + + effects[FireDamage] = WeaknessToFire; + effects[FrostDamage] = WeaknessToFrost; + effects[ShockDamage] = WeaknessToShock; + effects[Vampirism] = WeaknessToCommonDisease; + effects[Corprus] = WeaknessToCorprusDisease; + effects[Poison] = WeaknessToPoison; + + // Weakness to magicka or -1 ? + effects[Paralyze] = WeaknessToMagicka; + + if (effects.find(effect) != effects.end()) + return effects[effect]; + else + return -1; +} static std::map genNameMap() { diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index b1047e94af..cc9cc180ee 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -58,6 +58,12 @@ struct MagicEffect static const std::string &effectIdToString(short effectID); static short effectStringToId(const std::string &effect); + + /// Returns the effect that provides resistance against \a effect (or -1 if there's none) + static short getResistanceEffect(short effect); + /// Returns the effect that induces weakness against \a effect (or -1 if there's none) + static short getWeaknessEffect(short effect); + MagnitudeDisplayType getMagnitudeDisplayType() const; From c73217627e1a7533bf181a2731673ae77ea24247 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Nov 2013 20:29:47 +0100 Subject: [PATCH 037/142] Move code for listing effect sources to the spell management classes --- apps/openmw/mwgui/spellicons.cpp | 194 +++-------------------- apps/openmw/mwgui/spellicons.hpp | 14 +- apps/openmw/mwmechanics/activespells.cpp | 85 +++++++++- apps/openmw/mwmechanics/activespells.hpp | 28 ++-- apps/openmw/mwmechanics/magiceffects.hpp | 13 +- apps/openmw/mwmechanics/spells.cpp | 23 +++ apps/openmw/mwmechanics/spells.hpp | 4 + apps/openmw/mwmechanics/spellsuccess.hpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 4 +- apps/openmw/mwworld/inventorystore.cpp | 98 +++++++++--- apps/openmw/mwworld/inventorystore.hpp | 18 ++- 11 files changed, 273 insertions(+), 210 deletions(-) diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index da51f7e8f1..a18e88f5fd 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -21,6 +21,20 @@ namespace MWGui { + void EffectSourceVisitor::visit (const ESM::ENAMstruct& enam, + const std::string& sourceName, float magnitude, float remainingTime) + { + MagicEffectInfo newEffectSource; + newEffectSource.mKey = MWMechanics::EffectKey(enam); + newEffectSource.mMagnitude = magnitude; + newEffectSource.mPermanent = mIsPermanent; + newEffectSource.mRemainingTime = remainingTime; + newEffectSource.mSource = sourceName; + + mEffectSources[enam.mEffectID].push_back(newEffectSource); + } + + void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize) { // TODO: Tracking add/remove/expire would be better than force updating every frame @@ -28,125 +42,20 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); - std::map > effects; - // add permanent item enchantments + EffectSourceVisitor visitor; + + // permanent item enchantments & permanent spells + visitor.mIsPermanent = true; MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - MWWorld::ContainerStoreIterator it = store.getSlot(slot); - if (it == store.end()) - continue; - std::string enchantment = MWWorld::Class::get(*it).getEnchantment(*it); - if (enchantment.empty()) - continue; - const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().get().find(enchantment); - if (enchant->mData.mType != ESM::Enchantment::ConstantEffect) - continue; + store.visitEffectSources(visitor); + stats.getSpells().visitEffectSources(visitor); - const ESM::EffectList& list = enchant->mEffects; - for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt) - { - const ESM::MagicEffect* magicEffect = - MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); + // now add lasting effects + visitor.mIsPermanent = false; + stats.getActiveSpells().visitEffectSources(visitor); - MagicEffectInfo effectInfo; - effectInfo.mSource = MWWorld::Class::get(*it).getName(*it); - effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) - effectInfo.mKey.mArg = effectIt->mSkill; - else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - effectInfo.mKey.mArg = effectIt->mAttribute; - // just using the min magnitude here, permanent enchantments with a random magnitude just wouldn't make any sense - effectInfo.mMagnitude = effectIt->mMagnMin; - effectInfo.mPermanent = true; - effects[effectIt->mEffectID].push_back (effectInfo); - } - } - - // add permanent spells - const MWMechanics::Spells& spells = stats.getSpells(); - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) - { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); - - // these are the spell types that are permanently in effect - if (!(spell->mData.mType == ESM::Spell::ST_Ability) - && !(spell->mData.mType == ESM::Spell::ST_Disease) - && !(spell->mData.mType == ESM::Spell::ST_Curse) - && !(spell->mData.mType == ESM::Spell::ST_Blight)) - continue; - const ESM::EffectList& list = spell->mEffects; - for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt) - { - const ESM::MagicEffect* magicEffect = - MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); - MagicEffectInfo effectInfo; - effectInfo.mSource = getSpellDisplayName (it->first); - effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) - effectInfo.mKey.mArg = effectIt->mSkill; - else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - effectInfo.mKey.mArg = effectIt->mAttribute; - // just using the min magnitude here, permanent spells with a random magnitude just wouldn't make any sense - effectInfo.mMagnitude = effectIt->mMagnMin; - effectInfo.mPermanent = true; - - effects[effectIt->mEffectID].push_back (effectInfo); - } - } - - // add lasting effect spells/potions etc - - // TODO: Move this to ActiveSpells - const MWMechanics::ActiveSpells::TContainer& activeSpells = stats.getActiveSpells().getActiveSpells(); - for (MWMechanics::ActiveSpells::TContainer::const_iterator it = activeSpells.begin(); - it != activeSpells.end(); ++it) - { - const ESM::EffectList& list = getSpellEffectList(it->first); - - float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); - - int i=0; - for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt, ++i) - { - if (effectIt->mRange != it->second.mRange) - continue; - - float randomFactor = it->second.mRandom[i]; - - const ESM::MagicEffect* magicEffect = - MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); - - MagicEffectInfo effectInfo; - if (!it->second.mName.empty()) - effectInfo.mSource = it->second.mName; - else - effectInfo.mSource = getSpellDisplayName(it->first); - effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) - effectInfo.mKey.mArg = effectIt->mSkill; - else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - effectInfo.mKey.mArg = effectIt->mAttribute; - effectInfo.mMagnitude = effectIt->mMagnMin + (effectIt->mMagnMax-effectIt->mMagnMin) * randomFactor; - effectInfo.mRemainingTime = effectIt->mDuration + - (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; - - // ingredients need special casing for their magnitude / duration - if (MWBase::Environment::get().getWorld()->getStore().get().search (it->first)) - { - effectInfo.mRemainingTime = effectIt->mDuration * randomFactor + - (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; - - effectInfo.mMagnitude = static_cast (0.05*randomFactor / (0.1 * magicEffect->mData.mBaseCost)); - } - - effects[effectIt->mEffectID].push_back (effectInfo); - } - } + std::map >& effects = visitor.mEffectSources; int w=2; @@ -280,59 +189,4 @@ namespace MWGui } } - - std::string SpellIcons::getSpellDisplayName (const std::string& id) - { - if (const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return spell->mName; - - if (const ESM::Potion *potion = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return potion->mName; - - if (const ESM::Ingredient *ingredient = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return ingredient->mName; - - throw std::runtime_error ("ID " + id + " has no display name"); - } - - ESM::EffectList SpellIcons::getSpellEffectList (const std::string& id) - { - if (const ESM::Enchantment* enchantment = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return enchantment->mEffects; - - if (const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return spell->mEffects; - - if (const ESM::Potion *potion = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return potion->mEffects; - - if (const ESM::Ingredient *ingredient = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - ingredient->mData.mEffectID[0]); - - ESM::ENAMstruct effect; - effect.mEffectID = ingredient->mData.mEffectID[0]; - effect.mSkill = ingredient->mData.mSkills[0]; - effect.mAttribute = ingredient->mData.mAttributes[0]; - effect.mRange = 0; - effect.mArea = 0; - effect.mDuration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ? 0 : 1; - effect.mMagnMin = 1; - effect.mMagnMax = 1; - ESM::EffectList result; - result.mList.push_back (effect); - return result; - } - throw std::runtime_error("ID " + id + " does not have effects"); - } - } diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index 818d67b5bb..bae108a1da 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -2,6 +2,7 @@ #define MWGUI_SPELLICONS_H #include +#include #include "../mwmechanics/magiceffects.hpp" @@ -34,14 +35,23 @@ namespace MWGui bool mPermanent; // the effect is permanent }; + class EffectSourceVisitor : public MWMechanics::EffectSourceVisitor + { + public: + bool mIsPermanent; + + std::map > mEffectSources; + + virtual void visit (const ESM::ENAMstruct& enam, + const std::string& sourceName, float magnitude, float remainingTime = -1); + }; + class SpellIcons { public: void updateWidgets(MyGUI::Widget* parent, bool adjustSize); private: - std::string getSpellDisplayName (const std::string& id); - ESM::EffectList getSpellEffectList (const std::string& id); std::map mWidgetMap; }; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 88fa57a8f6..d96a5f3510 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -164,12 +164,31 @@ namespace MWMechanics throw std::runtime_error ("ID " + id + " can not produce lasting effects"); } + std::string ActiveSpells::getSpellDisplayName (const std::string& id) const + { + if (const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return spell->mName; + + if (const ESM::Potion *potion = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return potion->mName; + + if (const ESM::Ingredient *ingredient = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return ingredient->mName; + + throw std::runtime_error ("ID " + id + " has no display name"); + } + ActiveSpells::ActiveSpells() : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) {} - bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range, const std::string& name) + bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range, const std::string& name, int effectIndex) { + const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); + std::pair > effects = getEffectList (id); bool stacks = effects.second.second; @@ -195,7 +214,6 @@ namespace MWMechanics if (effects.second.first) { // ingredient -> special treatment required. - const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); float x = @@ -220,6 +238,26 @@ namespace MWMechanics else iter->second = params; + + + /* + for (int i=0; imRange != range) + { + params.mDisabled.push_back(true); + continue; + } + + bool disabled = false; + + int reflect = creatureStats.getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < reflect) + disabled = true; + } + */ + // Play sounds & particles bool first=true; for (std::vector::const_iterator iter (effects.first.mList.begin()); @@ -338,4 +376,47 @@ namespace MWMechanics { return mSpells; } + + void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const + { + for (TContainer::const_iterator it = begin(); it != end(); ++it) + { + const ESM::EffectList& list = getEffectList(it->first).first; + + float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + + int i=0; + for (std::vector::const_iterator effectIt = list.mList.begin(); + effectIt != list.mList.end(); ++effectIt, ++i) + { + if (effectIt->mRange != it->second.mRange) + continue; + + std::string name; + if (it->second.mName.empty()) + name = getSpellDisplayName(it->first); + else + name = it->second.mName; + + float remainingTime = effectIt->mDuration + + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second.mRandom[i]; + + // hack for ingredients + if (MWBase::Environment::get().getWorld()->getStore().get().search (it->first)) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); + + remainingTime = effectIt->mDuration * it->second.mRandom[i] + + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; + + magnitude = static_cast (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost)); + } + + visitor.visit(*effectIt, name, magnitude, remainingTime); + } + } + } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index e3f882b9aa..2cf30da94b 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -35,6 +35,10 @@ namespace MWMechanics // Random factor for each effect std::vector mRandom; + // Effect magnitude multiplier. Use 0 to completely disable the effect + // (if it was resisted, reflected or absorbed). Use (0,1) for partially resisted. + std::vector mMultiplier; + // Display name, we need this for enchantments, which don't have a name - so you need to supply the // name of the item with the enchantment to addSpell std::string mName; @@ -65,17 +69,30 @@ namespace MWMechanics std::pair > getEffectList (const std::string& id) const; ///< @return (EffectList, (isIngredient, stacks)) + double timeToExpire (const TIterator& iterator) const; + ///< Returns time (in in-game hours) until the spell pointed to by \a iterator + /// expires. + + const TContainer& getActiveSpells() const; + + TIterator begin() const; + + TIterator end() const; + + std::string getSpellDisplayName (const std::string& id) const; + public: ActiveSpells(); - bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = ""); + bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1); ///< Overwrites an existing spell with the same ID. If the spell does not have any /// non-instant effects, it is ignored. /// @param id /// @param actor /// @param range Only effects with range type \a range will be applied /// @param name Display name for enchantments, since they don't have a name in their record + /// @param effectIndex Only apply one specific effect - useful for reflecting spells, since each effect is reflected individually /// /// \return Has the spell been added? @@ -86,15 +103,8 @@ namespace MWMechanics const MagicEffects& getMagicEffects() const; - const TContainer& getActiveSpells() const; + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; - TIterator begin() const; - - TIterator end() const; - - double timeToExpire (const TIterator& iterator) const; - ///< Returns time (in in-game hours) until the spell pointed to by \a iterator - /// expires. }; } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 212ef312dd..9cdc27514e 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -2,6 +2,7 @@ #define GAME_MWMECHANICS_MAGICEFFECTS_H #include +#include namespace ESM { @@ -11,6 +12,13 @@ namespace ESM namespace MWMechanics { + // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display + struct EffectSourceVisitor + { + virtual void visit (const ESM::ENAMstruct& enam, + const std::string& sourceName, float magnitude, float remainingTime = -1) = 0; + }; + struct EffectKey { int mId; @@ -29,11 +37,12 @@ namespace MWMechanics struct EffectParam { - int mMagnitude; + // Note usually this would be int, but applying partial resistance might introduce decimal point. + float mMagnitude; EffectParam(); - EffectParam(int magnitude) : mMagnitude(magnitude) {} + EffectParam(float magnitude) : mMagnitude(magnitude) {} EffectParam& operator+= (const EffectParam& param); diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index df1f6a3182..a5a5677d12 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -117,4 +117,27 @@ namespace MWMechanics return false; } + + void Spells::visitEffectSources(EffectSourceVisitor &visitor) const + { + for (TIterator it = begin(); it != end(); ++it) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); + + // these are the spell types that are permanently in effect + if (!(spell->mData.mType == ESM::Spell::ST_Ability) + && !(spell->mData.mType == ESM::Spell::ST_Disease) + && !(spell->mData.mType == ESM::Spell::ST_Curse) + && !(spell->mData.mType == ESM::Spell::ST_Blight)) + continue; + const ESM::EffectList& list = spell->mEffects; + int i=0; + for (std::vector::const_iterator effectIt = list.mList.begin(); + effectIt != list.mList.end(); ++effectIt, ++i) + { + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i]; + visitor.visit(*effectIt, spell->mName, magnitude); + } + } + } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index ccac96190b..79b7a782d0 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -6,6 +6,8 @@ #include "../mwworld/ptr.hpp" +#include "magiceffects.hpp" + namespace ESM { struct Spell; @@ -59,6 +61,8 @@ namespace MWMechanics bool hasCommonDisease() const; bool hasBlightDisease() const; + + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; }; } diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp index 68b89752f8..fc6af3c552 100644 --- a/apps/openmw/mwmechanics/spellsuccess.hpp +++ b/apps/openmw/mwmechanics/spellsuccess.hpp @@ -63,7 +63,7 @@ namespace MWMechanics x *= 0.1 * magicEffect->mData.mBaseCost; x *= 0.5 * (it->mMagnMin + it->mMagnMax); x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost; - if (it->mRange == ESM::RT_Target) + if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) x *= 1.5; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( "fEffectCostMult")->getFloat(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 6bc2bfc12a..bc7e48af1b 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -60,7 +60,7 @@ const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); NpcAnimation::~NpcAnimation() { if (!mListenerDisabled) - mPtr.getClass().getInventoryStore(mPtr).setListener(NULL); + mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr); Ogre::SceneManager *sceneMgr = mInsert->getCreator(); for(size_t i = 0;i < ESM::PRT_Count;i++) @@ -85,7 +85,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v } if (!disableListener) - mPtr.getClass().getInventoryStore(mPtr).setListener(this); + mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr); updateNpcBase(); } diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index c2b1f084d5..bec0593891 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -123,7 +123,7 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite flagAsModified(); fireEquipmentChangedEvent(); - updateMagicEffects(); + updateMagicEffects(actor); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) @@ -150,10 +150,10 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) return mSlots[slot]; } -void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) +void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { - const MWMechanics::NpcStats& stats = MWWorld::Class::get(npc).getNpcStats(npc); - MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc); + const MWMechanics::NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); + MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor); TSlots slots_; initSlots (slots_); @@ -211,15 +211,15 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) } } - switch(MWWorld::Class::get (test).canBeEquipped (test, npc).first) + switch(MWWorld::Class::get (test).canBeEquipped (test, actor).first) { case 0: continue; case 2: - invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, npc); + invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, actor); break; case 3: - invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, npc); + invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor); break; } @@ -255,7 +255,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) { mSlots.swap (slots_); fireEquipmentChangedEvent(); - updateMagicEffects(); + updateMagicEffects(actor); flagAsModified(); } } @@ -265,7 +265,7 @@ const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() cons return mMagicEffects; } -void MWWorld::InventoryStore::updateMagicEffects() +void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) { // To avoid excessive updates during auto-equip if (!mUpdatesEnabled) @@ -293,19 +293,41 @@ void MWWorld::InventoryStore::updateMagicEffects() continue; // Roll some dice, one for each effect - std::vector random; - random.resize(enchantment.mEffects.mList.size()); - for (unsigned int i=0; i (std::rand()) / RAND_MAX; + std::vector params; + params.resize(enchantment.mEffects.mList.size()); + for (unsigned int i=0; i (std::rand()) / RAND_MAX; bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) != mPermanentMagicEffectMagnitudes.end()); if (!existed) { + // Try resisting each effect + int i=0; + for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); + effectIt!=enchantment.mEffects.mList.end(); ++effectIt) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); + + //const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + float resisted = 0; + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) + { + + } + params[i].mMultiplier = (100.f - resisted) / 100.f; + + ++i; + } + + // Note that using the RefID as a key here is not entirely correct. // Consider equipping the same item twice (e.g. a ring) // However, permanent enchantments with a random magnitude are kind of an exploit anyway, // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. - mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = random; + mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = params; } int i=0; @@ -316,6 +338,10 @@ void MWWorld::InventoryStore::updateMagicEffects() MWBase::Environment::get().getWorld()->getStore().get().find ( effectIt->mEffectID); + // Fully resisted? + if (params[i].mMultiplier == 0) + continue; + if (!existed) { // During first auto equip, we don't play any sounds. @@ -325,7 +351,10 @@ void MWWorld::InventoryStore::updateMagicEffects() !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin()); } - mMagicEffects.add (*effectIt, random[i]); + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; + magnitude *= params[i].mMultiplier; + if (magnitude) + mMagicEffects.add (*effectIt, magnitude); ++i; } } @@ -467,7 +496,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c } fireEquipmentChangedEvent(); - updateMagicEffects(); + updateMagicEffects(actor); return retval; } @@ -487,10 +516,10 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWor throw std::runtime_error ("attempt to unequip an item that is not currently equipped"); } -void MWWorld::InventoryStore::setListener(InventoryStoreListener *listener) +void MWWorld::InventoryStore::setListener(InventoryStoreListener *listener, const Ptr& actor) { mListener = listener; - updateMagicEffects(); + updateMagicEffects(actor); } void MWWorld::InventoryStore::fireEquipmentChangedEvent() @@ -500,3 +529,36 @@ void MWWorld::InventoryStore::fireEquipmentChangedEvent() if (mListener) mListener->equipmentChanged(); } + +void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisitor &visitor) +{ + for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) + { + if (*iter==end()) + continue; + + std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter); + if (enchantmentId.empty()) + continue; + + const ESM::Enchantment& enchantment = + *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); + + if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) + continue; + + if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) == mPermanentMagicEffectMagnitudes.end()) + continue; + + int i=0; + for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); + effectIt!=enchantment.mEffects.mList.end(); ++effectIt) + { + float random = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i].mRandom; + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; + visitor.visit(*effectIt, (**iter).getClass().getName(**iter), magnitude); + + ++i; + } + } +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 099523a9c8..f53ce8efb9 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -74,7 +74,15 @@ namespace MWWorld // Vanilla allows permanent effects with a random magnitude, so it needs to be stored here. // We also need this to only play sounds and particle effects when the item is equipped, rather than on every update. - typedef std::map > TEffectMagnitudes; + struct EffectParams + { + // Modifier to scale between min and max magnitude + float mRandom; + // Multiplier for when an effect was fully or partially resisted + float mMultiplier; + }; + + typedef std::map > TEffectMagnitudes; TEffectMagnitudes mPermanentMagicEffectMagnitudes; typedef std::vector TSlots; @@ -88,7 +96,7 @@ namespace MWWorld void initSlots (TSlots& slots_); - void updateMagicEffects(); + void updateMagicEffects(const Ptr& actor); void fireEquipmentChangedEvent(); @@ -127,7 +135,7 @@ namespace MWWorld void unequipAll(const MWWorld::Ptr& actor); ///< Unequip all currently equipped items. - void autoEquip (const MWWorld::Ptr& npc); + void autoEquip (const MWWorld::Ptr& actor); ///< Auto equip items according to stats and item value. const MWMechanics::MagicEffects& getMagicEffects() const; @@ -160,8 +168,10 @@ namespace MWWorld /// (it can be re-stacked so its count may be different than when it /// was equipped). - void setListener (InventoryStoreListener* listener); + void setListener (InventoryStoreListener* listener, const Ptr& actor); ///< Set a listener for various events, see \a InventoryStoreListener + + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); }; } From aa4b2d9504f642d691e26b63a696d199edf0059d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 01:19:39 +0100 Subject: [PATCH 038/142] Fix uninitialized mSkill/mAttribute for spellmaker spells --- apps/openmw/mwgui/spellcreationdialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index dc86fd825f..e0b808b283 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -9,7 +9,7 @@ #include "../mwworld/player.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "tooltips.hpp" #include "class.hpp" @@ -89,6 +89,8 @@ namespace MWGui mEffect.mMagnMax = 1; mEffect.mDuration = 1; mEffect.mArea = 0; + mEffect.mSkill = -1; + mEffect.mAttribute = -1; eventEffectAdded(mEffect); onRangeButtonClicked(mRangeButton); From d49b6f19ffef8bd9ed6fe118233285458592b8dc Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 02:11:11 +0100 Subject: [PATCH 039/142] Don't advance acrobatics skill for NPCs --- apps/openmw/mwmechanics/character.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 76bbafb22b..240695a1cb 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -782,7 +782,7 @@ void CharacterController::update(float duration) if(!onground && !flying && !inwater) { - // The player is in the air (either getting up —ascending part of jump— or falling). + // In the air (either getting up —ascending part of jump— or falling). if (world->isSlowFalling(mPtr)) { @@ -817,8 +817,7 @@ void CharacterController::update(float duration) } else if(vec.z > 0.0f && mJumpState == JumpState_None) { - // The player has started a jump. - + // Started a jump. float z = cls.getJump(mPtr); if(vec.x == 0 && vec.y == 0) vec = Ogre::Vector3(0.0f, 0.0f, z); @@ -829,7 +828,8 @@ void CharacterController::update(float duration) } // advance acrobatics - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); + if (mPtr.getRefData().getHandle() == "player") + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); // decrease fatigue const MWWorld::Store &gmst = world->getStore().get(); @@ -843,8 +843,6 @@ void CharacterController::update(float duration) } else if(mJumpState == JumpState_Falling) { - // The player is landing. - forcestateupdate = true; mJumpState = JumpState_Landing; vec.z = 0.0f; @@ -861,7 +859,8 @@ void CharacterController::update(float duration) cls.getCreatureStats(mPtr).setHealth(health); // report acrobatics progression - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); + if (mPtr.getRefData().getHandle() == "player") + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); const float acrobaticsSkill = cls.getNpcStats(mPtr).getSkill(ESM::Skill::Acrobatics).getModified(); if (healthLost > (acrobaticsSkill * fatigueTerm)) From b1a29eb27eafdc2db1bf7ffb416ac42aea25357f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 02:34:43 +0100 Subject: [PATCH 040/142] Implement Resist & Weakness effects --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwclass/npc.cpp | 6 +- apps/openmw/mwgui/quickkeysmenu.cpp | 2 +- apps/openmw/mwgui/spellwindow.cpp | 2 +- apps/openmw/mwmechanics/activespells.cpp | 104 +++++++++++------- apps/openmw/mwmechanics/activespells.hpp | 7 +- .../{spellsuccess.hpp => spellcasting.hpp} | 66 +++++++++++ apps/openmw/mwworld/actiontrap.cpp | 2 +- apps/openmw/mwworld/inventorystore.cpp | 36 +++--- apps/openmw/mwworld/worldimp.cpp | 6 +- 10 files changed, 159 insertions(+), 74 deletions(-) rename apps/openmw/mwmechanics/{spellsuccess.hpp => spellcasting.hpp} (64%) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 04bd89f959..cc4a9d6de3 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -69,7 +69,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow - aiescort aiactivate repair enchanting pathfinding security spellsuccess + aiescort aiactivate repair enchanting pathfinding security spellsuccess spellcasting ) add_openmw_dir (mwbase diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 35a6d17a3b..fd24dd93fc 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -468,9 +468,9 @@ namespace MWClass { weapon.getCellRef().mEnchantmentCharge -= castCost; // Touch - othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ESM::RT_Touch, weapon.getClass().getName(weapon)); + othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ptr, ESM::RT_Touch, weapon.getClass().getName(weapon)); // Self - getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ESM::RT_Self, weapon.getClass().getName(weapon)); + getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ptr, ESM::RT_Self, weapon.getClass().getName(weapon)); // Target MWBase::Environment::get().getWorld()->launchProjectile(enchantmentName, enchantment->mEffects, ptr, weapon.getClass().getName(weapon)); } @@ -936,7 +936,7 @@ namespace MWClass /// \todo consider instant effects - return stats.getActiveSpells().addSpell (id, actor); + return stats.getActiveSpells().addSpell (id, actor, actor); } void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 5f749d3d3d..503bf7c114 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -5,7 +5,7 @@ #include "../mwworld/player.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "../mwgui/inventorywindow.hpp" #include "../mwgui/bookwindow.hpp" #include "../mwgui/scrollwindow.hpp" diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 61ac2c7b29..03bb106310 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -9,7 +9,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "spellicons.hpp" #include "inventorywindow.hpp" diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index d96a5f3510..3a9321f398 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -16,11 +16,14 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwrender/animation.hpp" #include "../mwworld/class.hpp" +#include "../mwmechanics/spellcasting.hpp" + #include "creaturestats.hpp" #include "npcstats.hpp" @@ -74,7 +77,7 @@ namespace MWMechanics for (std::vector::const_iterator effectIter (effects.first.mList.begin()); effectIter!=effects.first.mList.end(); ++effectIter, ++i) { - float magnitude = iter->second.mRandom[i]; + float random = iter->second.mRandom[i]; if (effectIter->mRange != iter->second.mRange) continue; @@ -83,7 +86,7 @@ namespace MWMechanics int duration = effectIter->mDuration; if (effects.second.first) - duration *= magnitude; + duration *= random; MWWorld::TimeStamp end = start; end += static_cast (duration)* @@ -102,19 +105,21 @@ namespace MWMechanics if (effectIter->mDuration==0) { param.mMagnitude = - static_cast (magnitude / (0.1 * magicEffect->mData.mBaseCost)); + static_cast (random / (0.1 * magicEffect->mData.mBaseCost)); } else { param.mMagnitude = - static_cast (0.05*magnitude / (0.1 * magicEffect->mData.mBaseCost)); + static_cast (0.05*random / (0.1 * magicEffect->mData.mBaseCost)); } } else param.mMagnitude = static_cast ( - (effectIter->mMagnMax-effectIter->mMagnMin)*magnitude + effectIter->mMagnMin); - - mEffects.add (*effectIter, param); + (effectIter->mMagnMax-effectIter->mMagnMin)*random + effectIter->mMagnMin); + param.mMagnitude *= iter->second.mMultiplier[i]; + + if (param.mMagnitude) + mEffects.add (*effectIter, param); } } } @@ -185,7 +190,7 @@ namespace MWMechanics : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) {} - bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range, const std::string& name, int effectIndex) + bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range, const std::string& name, int effectIndex) { const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); @@ -197,6 +202,8 @@ namespace MWMechanics for (std::vector::const_iterator iter (effects.first.mList.begin()); iter!=effects.first.mList.end(); ++iter) { + if (iter->mRange != range) + continue; if (iter->mDuration) { found = true; @@ -204,41 +211,37 @@ namespace MWMechanics } } + // If none of the effects need to apply, no need to add the spell if (!found) return false; TContainer::iterator iter = mSpells.find (id); - float random = static_cast (std::rand()) / RAND_MAX; - - if (effects.second.first) - { - // ingredient -> special treatment required. - const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); - - float x = - (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + - 0.2 * creatureStats.getAttribute (1).getModified() - + 0.1 * creatureStats.getAttribute (7).getModified()) - * creatureStats.getFatigueTerm(); - random *= 100; - random = random / std::min (x, 100.0f); - random *= 0.25 * x; - } - ActiveSpellParams params; for (unsigned int i=0; i (std::rand()) / RAND_MAX); + { + float random = static_cast (std::rand()) / RAND_MAX; + if (effects.second.first) + { + // ingredient -> special treatment required. + const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); + + float x = + (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + + 0.2 * creatureStats.getAttribute (1).getModified() + + 0.1 * creatureStats.getAttribute (7).getModified()) + * creatureStats.getFatigueTerm(); + random *= 100; + random = random / std::min (x, 100.0f); + random *= 0.25 * x; + } + + params.mRandom.push_back(random); + } params.mRange = range; params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); params.mName = name; - - if (iter==mSpells.end() || stacks) - mSpells.insert (std::make_pair (id, params)); - else - iter->second = params; - - + params.mMultiplier.resize(effects.first.mList.size(), 1); /* for (int i=0; i::const_iterator iter (effects.first.mList.begin()); - iter!=effects.first.mList.end(); ++iter) + int i = 0; + for (std::vector::const_iterator effectIt (effects.first.mList.begin()); + effectIt!=effects.first.mList.end(); ++effectIt, ++i) { - if (iter->mRange != range) + if (effectIt->mRange != range) + continue; + + // Try resisting effect in case its harmful + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().search (id); + params.mMultiplier[i] = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, caster, spell); + if (params.mMultiplier[i] == 0) + { + if (actor.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + } + + // If fully resisted, don't play sounds or particles + if (params.mMultiplier[i] == 0) continue; // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World. const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - iter->mEffectID); + effectIt->mEffectID); // Only the sound of the first effect plays if (first) @@ -296,6 +315,11 @@ namespace MWMechanics first = false; } + if (iter==mSpells.end() || stacks) + mSpells.insert (std::make_pair (id, params)); + else + iter->second = params; + mSpellsChanged = true; return true; @@ -415,7 +439,9 @@ namespace MWMechanics magnitude = static_cast (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost)); } - visitor.visit(*effectIt, name, magnitude, remainingTime); + magnitude *= it->second.mMultiplier[i]; + if (magnitude) + visitor.visit(*effectIt, name, magnitude, remainingTime); } } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 2cf30da94b..b5c302afe1 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -37,7 +37,7 @@ namespace MWMechanics // Effect magnitude multiplier. Use 0 to completely disable the effect // (if it was resisted, reflected or absorbed). Use (0,1) for partially resisted. - std::vector mMultiplier; + std::vector mMultiplier; // Display name, we need this for enchantments, which don't have a name - so you need to supply the // name of the item with the enchantment to addSpell @@ -85,11 +85,12 @@ namespace MWMechanics ActiveSpells(); - bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1); + bool addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1); ///< Overwrites an existing spell with the same ID. If the spell does not have any /// non-instant effects, it is ignored. /// @param id - /// @param actor + /// @param actor actor to add the spell to + /// @param caster actor who casted the spell /// @param range Only effects with range type \a range will be applied /// @param name Display name for enchantments, since they don't have a name in their record /// @param effectIndex Only apply one specific effect - useful for reflecting spells, since each effect is reflected individually diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellcasting.hpp similarity index 64% rename from apps/openmw/mwmechanics/spellsuccess.hpp rename to apps/openmw/mwmechanics/spellcasting.hpp index fc6af3c552..3bbb4d0c53 100644 --- a/apps/openmw/mwmechanics/spellsuccess.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -114,6 +114,72 @@ namespace MWMechanics return school; } + /// @return >=100 for fully resisted. can also return negative value for damage amplification. + inline float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectId); + + const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + float resisted = 0; + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) + { + + short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); + short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); + + float resistance = 0; + if (resistanceEffect != -1) + resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude; + if (weaknessEffect != -1) + resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude; + + + float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); + float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + float x = (willpower + 0.1 * luck) * stats.getFatigueTerm(); + + // This makes spells that are easy to cast harder to resist and vice versa + if (spell != NULL) + { + float castChance = getSpellSuccessChance(spell, caster); + if (castChance > 0) + x *= 50 / castChance; + } + + float roll = static_cast(std::rand()) / RAND_MAX * 100; + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + roll -= resistance; + + if (x <= roll) + x = 0; + else + { + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + x = 100; + else + x = roll / std::min(x, 100.f); + } + + x = std::min(x + resistance, 100.f); + + resisted = x; + } + + return resisted; + } + + inline float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL) + { + float resistance = getEffectResistance(effectId, actor, caster, spell); + if (resistance >= 0) + return 1 - resistance / 100.f; + else + return -(resistance-100) / 100.f; + } + } #endif diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp index 13b2fd2694..80da290727 100644 --- a/apps/openmw/mwworld/actiontrap.cpp +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -16,7 +16,7 @@ namespace MWWorld // TODO: Apply RT_Self effects on the door / container that triggered the trap. Not terribly useful, but you could // make it lock itself when activated for example. - actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, ESM::RT_Touch); + actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, actor, ESM::RT_Touch); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index bec0593891..9e18691254 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -11,6 +11,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellcasting.hpp" + #include "esmstore.hpp" #include "class.hpp" @@ -292,47 +294,37 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) continue; - // Roll some dice, one for each effect std::vector params; - params.resize(enchantment.mEffects.mList.size()); - for (unsigned int i=0; i (std::rand()) / RAND_MAX; bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) != mPermanentMagicEffectMagnitudes.end()); if (!existed) { + // Roll some dice, one for each effect + params.resize(enchantment.mEffects.mList.size()); + for (unsigned int i=0; i (std::rand()) / RAND_MAX; + // Try resisting each effect int i=0; for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); effectIt!=enchantment.mEffects.mList.end(); ++effectIt) { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIt->mEffectID); - - //const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - - float resisted = 0; - if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) - { - - } - params[i].mMultiplier = (100.f - resisted) / 100.f; - + params[i].mMultiplier = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, actor); ++i; } - // Note that using the RefID as a key here is not entirely correct. // Consider equipping the same item twice (e.g. a ring) // However, permanent enchantments with a random magnitude are kind of an exploit anyway, // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = params; } + else + params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID]; int i=0; for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); - effectIt!=enchantment.mEffects.mList.end(); ++effectIt) + effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( @@ -355,7 +347,6 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) magnitude *= params[i].mMultiplier; if (magnitude) mMagicEffects.add (*effectIt, magnitude); - ++i; } } } @@ -554,8 +545,9 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); effectIt!=enchantment.mEffects.mList.end(); ++effectIt) { - float random = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i].mRandom; - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; + const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i]; + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; + magnitude *= params.mMultiplier; visitor.visit(*effectIt, (**iter).getClass().getName(**iter), magnitude); ++i; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index eca2ebb79f..e6c4c1ef08 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -26,7 +26,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "../mwrender/sky.hpp" @@ -2158,7 +2158,7 @@ namespace MWWorld // Now apply the spell! // Apply Self portion - actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, ESM::RT_Self, sourceName); + actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, actor, ESM::RT_Self, sourceName); // Apply Touch portion // TODO: Distance is probably incorrect, and should it be hardcoded? @@ -2166,7 +2166,7 @@ namespace MWWorld if (!contact.first.isEmpty()) { if (contact.first.getClass().isActor()) - contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, ESM::RT_Touch, sourceName); + contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, actor, ESM::RT_Touch, sourceName); else { // We hit a non-actor, e.g. a door. Only instant effects are relevant. From bb43ec9b35b3f45d55de492834b5d45f550890f5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 03:16:21 +0100 Subject: [PATCH 041/142] Implement damage tick effects --- apps/openmw/mwmechanics/activespells.cpp | 12 ++++++++---- apps/openmw/mwmechanics/actors.cpp | 18 ++++++++++++++++-- apps/openmw/mwmechanics/actors.hpp | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 3a9321f398..89543476b0 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -269,6 +269,14 @@ namespace MWMechanics if (effectIt->mRange != range) continue; + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); + + if (caster.getRefData().getHandle() == "player" && actor != caster + && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) + MWBase::Environment::get().getWindowManager()->setEnemy(actor); + // Try resisting effect in case its harmful const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().search (id); @@ -287,10 +295,6 @@ namespace MWMechanics // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World. - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIt->mEffectID); - // Only the sound of the first effect plays if (first) { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3d52ce8e69..576c830da7 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -29,7 +29,7 @@ namespace MWMechanics // magic effects adjustMagicEffects (ptr); calculateDynamicStats (ptr); - calculateCreatureStatModifiers (ptr); + calculateCreatureStatModifiers (ptr, duration); if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) { @@ -145,7 +145,7 @@ namespace MWMechanics stats.setFatigue (fatigue); } - void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr) + void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) { CreatureStats &creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); const MagicEffects &effects = creatureStats.getMagicEffects(); @@ -167,10 +167,24 @@ namespace MWMechanics stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude - effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude); + float damage = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth)).mMagnitude; + stat.setCurrent(stat.getCurrent() - damage * duration); + creatureStats.setDynamic(i, stat); } + // Apply damage ticks + int damageEffects[] = { + ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison + }; + for (unsigned int i=0; i health = creatureStats.getHealth(); + health.setCurrent(health.getCurrent() - magnitude * duration); + creatureStats.setHealth(health); + } } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index eeef22635c..251c38ec0e 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -40,7 +40,7 @@ namespace MWMechanics void calculateDynamicStats (const MWWorld::Ptr& ptr); - void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr); + void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration); void calculateNpcStatModifiers (const MWWorld::Ptr& ptr); void calculateRestoration (const MWWorld::Ptr& ptr, float duration); From 7474e87edce448361d6f48eb4013bac85c7a0c02 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 05:06:54 +0100 Subject: [PATCH 042/142] Implement RestoreHealth/Magicka/Fatigue --- apps/openmw/mwmechanics/activespells.hpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index b5c302afe1..56d9413c39 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -57,7 +57,7 @@ namespace MWMechanics private: - mutable TContainer mSpells; // spellId, (time of casting, relative magnitude) + mutable TContainer mSpells; mutable MagicEffects mEffects; mutable bool mSpellsChanged; mutable MWWorld::TimeStamp mLastUpdate; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 576c830da7..2873816f73 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -167,8 +167,9 @@ namespace MWMechanics stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude - effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude); - float damage = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth)).mMagnitude; - stat.setCurrent(stat.getCurrent() - damage * duration); + float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude + - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude; + stat.setCurrent(stat.getCurrent() + currentDiff * duration); creatureStats.setDynamic(i, stat); } From aa84ce3f0dfacca1b85d7efa93b20a33b76d97e9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 14:44:31 +0100 Subject: [PATCH 043/142] Copy paste mistake (oops) --- apps/openmw/mwmechanics/magiceffects.hpp | 2 -- components/esm/loadmgef.cpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 9cdc27514e..58f023ebac 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -24,8 +24,6 @@ namespace MWMechanics int mId; int mArg; // skill or ability - // TODO: Add caster here for Absorb effects? - EffectKey(); EffectKey (int id, int arg = -1) : mId (id), mArg (arg) {} diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 46e2a8555d..f601915395 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -132,8 +132,6 @@ short MagicEffect::getResistanceEffect(short effect) short MagicEffect::getWeaknessEffect(short effect) { std::map effects; - effects[DisintegrateArmor] = Sanctuary; - effects[DisintegrateWeapon] = Sanctuary; for (int i=0; i<5; ++i) effects[DrainAttribute+i] = WeaknessToMagicka; From b9899696e31cd1f4f969effcbb5819b4d14469d5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 15:56:15 +0100 Subject: [PATCH 044/142] Add a crash catcher for unix. When encountering a fatal signal, attach gdb and log backtrace. --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/crashcatcher.cpp | 462 +++++++++++++++++++++++++++++++++++ apps/openmw/main.cpp | 18 ++ 3 files changed, 481 insertions(+) create mode 100644 apps/openmw/crashcatcher.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 04bd89f959..a3f3745a23 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -6,6 +6,7 @@ configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.cmake" "${CMAKE_CURRENT_ set(GAME main.cpp engine.cpp + crashcatcher.cpp ) set(GAME_HEADER engine.hpp diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp new file mode 100644 index 0000000000..2a1abdcec7 --- /dev/null +++ b/apps/openmw/crashcatcher.cpp @@ -0,0 +1,462 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include + +#include + +#include + +#ifdef __linux__ +#include +#ifndef PR_SET_PTRACER +#define PR_SET_PTRACER 0x59616d61 +#endif +#elif defined (__APPLE__) +#include +#endif + + +static const char crash_switch[] = "--cc-handle-crash"; + +static const char fatal_err[] = "\n\n*** Fatal Error ***\n"; +static const char pipe_err[] = "!!! Failed to create pipe\n"; +static const char fork_err[] = "!!! Failed to fork debug process\n"; +static const char exec_err[] = "!!! Failed to exec debug process\n"; + +static char argv0[PATH_MAX]; + +static char altstack[SIGSTKSZ]; + + +static struct { + int signum; + pid_t pid; + int has_siginfo; + siginfo_t siginfo; + char buf[1024]; +} crash_info; + + +static const struct { + const char *name; + int signum; +} signals[] = { + { "Segmentation fault", SIGSEGV }, + { "Illegal instruction", SIGILL }, + { "FPU exception", SIGFPE }, + { "System BUS error", SIGBUS }, + { NULL, 0 } +}; + +static const struct { + int code; + const char *name; +} sigill_codes[] = { +#ifndef __FreeBSD__ + { ILL_ILLOPC, "Illegal opcode" }, + { ILL_ILLOPN, "Illegal operand" }, + { ILL_ILLADR, "Illegal addressing mode" }, + { ILL_ILLTRP, "Illegal trap" }, + { ILL_PRVOPC, "Privileged opcode" }, + { ILL_PRVREG, "Privileged register" }, + { ILL_COPROC, "Coprocessor error" }, + { ILL_BADSTK, "Internal stack error" }, +#endif + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigfpe_codes[] = { + { FPE_INTDIV, "Integer divide by zero" }, + { FPE_INTOVF, "Integer overflow" }, + { FPE_FLTDIV, "Floating point divide by zero" }, + { FPE_FLTOVF, "Floating point overflow" }, + { FPE_FLTUND, "Floating point underflow" }, + { FPE_FLTRES, "Floating point inexact result" }, + { FPE_FLTINV, "Floating point invalid operation" }, + { FPE_FLTSUB, "Subscript out of range" }, + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigsegv_codes[] = { +#ifndef __FreeBSD__ + { SEGV_MAPERR, "Address not mapped to object" }, + { SEGV_ACCERR, "Invalid permissions for mapped object" }, +#endif + { 0, NULL } +}; + +static const struct { + int code; + const char *name; +} sigbus_codes[] = { +#ifndef __FreeBSD__ + { BUS_ADRALN, "Invalid address alignment" }, + { BUS_ADRERR, "Non-existent physical address" }, + { BUS_OBJERR, "Object specific hardware error" }, +#endif + { 0, NULL } +}; + +static int (*cc_user_info)(char*, char*); + + +static void gdb_info(pid_t pid) +{ + char respfile[64]; + char cmd_buf[128]; + FILE *f; + int fd; + + /* Create a temp file to put gdb commands into */ + strcpy(respfile, "gdb-respfile-XXXXXX"); + if((fd=mkstemp(respfile)) >= 0 && (f=fdopen(fd, "w")) != NULL) + { + fprintf(f, "attach %d\n" + "shell echo \"\"\n" + "shell echo \"* Loaded Libraries\"\n" + "info sharedlibrary\n" + "shell echo \"\"\n" + "shell echo \"* Threads\"\n" + "info threads\n" + "shell echo \"\"\n" + "shell echo \"* FPU Status\"\n" + "info float\n" + "shell echo \"\"\n" + "shell echo \"* Registers\"\n" + "info registers\n" + "shell echo \"\"\n" + "shell echo \"* Backtrace\"\n" + "thread apply all backtrace full\n" + "detach\n" + "quit\n", pid); + fclose(f); + + /* Run gdb and print process info. */ + snprintf(cmd_buf, sizeof(cmd_buf), "gdb --quiet --batch --command=%s", respfile); + printf("Executing: %s\n", cmd_buf); + fflush(stdout); + + system(cmd_buf); + /* Clean up */ + remove(respfile); + } + else + { + /* Error creating temp file */ + if(fd >= 0) + { + close(fd); + remove(respfile); + } + printf("!!! Could not create gdb command file\n"); + } + fflush(stdout); +} + +static void sys_info(void) +{ +#ifdef __unix__ + system("echo \"System: `uname -a`\""); + putchar('\n'); + fflush(stdout); +#endif +} + + +static size_t safe_write(int fd, const void *buf, size_t len) +{ + size_t ret = 0; + while(ret < len) + { + ssize_t rem; + if((rem=write(fd, (const char*)buf+ret, len-ret)) == -1) + { + if(errno == EINTR) + continue; + break; + } + ret += rem; + } + return ret; +} + +static void crash_catcher(int signum, siginfo_t *siginfo, void *context) +{ + //ucontext_t *ucontext = (ucontext_t*)context; + pid_t dbg_pid; + int fd[2]; + + /* Make sure the effective uid is the real uid */ + if(getuid() != geteuid()) + { + raise(signum); + return; + } + + safe_write(STDERR_FILENO, fatal_err, sizeof(fatal_err)-1); + if(pipe(fd) == -1) + { + safe_write(STDERR_FILENO, pipe_err, sizeof(pipe_err)-1); + raise(signum); + return; + } + + crash_info.signum = signum; + crash_info.pid = getpid(); + crash_info.has_siginfo = !!siginfo; + if(siginfo) + crash_info.siginfo = *siginfo; + if(cc_user_info) + cc_user_info(crash_info.buf, crash_info.buf+sizeof(crash_info.buf)); + + /* Fork off to start a crash handler */ + switch((dbg_pid=fork())) + { + /* Error */ + case -1: + safe_write(STDERR_FILENO, fork_err, sizeof(fork_err)-1); + raise(signum); + return; + + case 0: + dup2(fd[0], STDIN_FILENO); + close(fd[0]); + close(fd[1]); + + execl(argv0, argv0, crash_switch, NULL); + + safe_write(STDERR_FILENO, exec_err, sizeof(exec_err)-1); + _exit(1); + + default: +#ifdef __linux__ + prctl(PR_SET_PTRACER, dbg_pid, 0, 0, 0); +#endif + safe_write(fd[1], &crash_info, sizeof(crash_info)); + close(fd[0]); + close(fd[1]); + + /* Wait; we'll be killed when gdb is done */ + do { + int status; + if(waitpid(dbg_pid, &status, 0) == dbg_pid && + (WIFEXITED(status) || WIFSIGNALED(status))) + { + /* The debug process died before it could kill us */ + raise(signum); + break; + } + } while(1); + } +} + +static void crash_handler(const char *logfile) +{ + const char *sigdesc = ""; + int i; + + if(fread(&crash_info, sizeof(crash_info), 1, stdin) != 1) + { + fprintf(stderr, "!!! Failed to retrieve info from crashed process\n"); + exit(1); + } + + /* Get the signal description */ + for(i = 0;signals[i].name;++i) + { + if(signals[i].signum == crash_info.signum) + { + sigdesc = signals[i].name; + break; + } + } + + if(crash_info.has_siginfo) + { + switch(crash_info.signum) + { + case SIGSEGV: + for(i = 0;sigsegv_codes[i].name;++i) + { + if(sigsegv_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigsegv_codes[i].name; + break; + } + } + break; + + case SIGFPE: + for(i = 0;sigfpe_codes[i].name;++i) + { + if(sigfpe_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigfpe_codes[i].name; + break; + } + } + break; + + case SIGILL: + for(i = 0;sigill_codes[i].name;++i) + { + if(sigill_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigill_codes[i].name; + break; + } + } + break; + + case SIGBUS: + for(i = 0;sigbus_codes[i].name;++i) + { + if(sigbus_codes[i].code == crash_info.siginfo.si_code) + { + sigdesc = sigbus_codes[i].name; + break; + } + } + break; + } + } + fprintf(stderr, "%s (signal %i)\n", sigdesc, crash_info.signum); + if(crash_info.has_siginfo) + fprintf(stderr, "Address: %p\n", crash_info.siginfo.si_addr); + fputc('\n', stderr); + + if(logfile) + { + /* Create crash log file and redirect shell output to it */ + if(freopen(logfile, "wa", stdout) != stdout) + { + fprintf(stderr, "!!! Could not create %s following signal\n", logfile); + exit(1); + } + fprintf(stderr, "Generating %s and killing process %d, please wait... ", logfile, crash_info.pid); + + printf("*** Fatal Error ***\n" + "%s (signal %i)\n", sigdesc, crash_info.signum); + if(crash_info.has_siginfo) + printf("Address: %p\n", crash_info.siginfo.si_addr); + fputc('\n', stdout); + fflush(stdout); + } + + sys_info(); + + crash_info.buf[sizeof(crash_info.buf)-1] = '\0'; + printf("%s\n", crash_info.buf); + fflush(stdout); + + if(crash_info.pid > 0) + { + gdb_info(crash_info.pid); + kill(crash_info.pid, SIGKILL); + } + + if(logfile) + { + char cwd[MAXPATHLEN]; + getcwd(cwd, MAXPATHLEN); + + std::string message = "OpenMW has encountered a fatal error.\nCrash log saved to '" + std::string(cwd) + "/" + std::string(logfile) + "'.\n Please report this to https://bugs.openmw.org !"; + SDL_ShowSimpleMessageBox(0, "Fatal Error", message.c_str(), NULL); + } + exit(0); +} + +int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, const char *logfile, int (*user_info)(char*, char*)) +{ + struct sigaction sa; + stack_t altss; + int retval; + + if(argc == 2 && strcmp(argv[1], crash_switch) == 0) + crash_handler(logfile); + + cc_user_info = user_info; + + if(argv[0][0] == '/') + snprintf(argv0, sizeof(argv0), "%s", argv[0]); + else + { + getcwd(argv0, sizeof(argv0)); + retval = strlen(argv0); + snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]); + } + + /* Set an alternate signal stack so SIGSEGVs caused by stack overflows + * still run */ + altss.ss_sp = altstack; + altss.ss_flags = 0; + altss.ss_size = sizeof(altstack); + sigaltstack(&altss, NULL); + + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = crash_catcher; + sa.sa_flags = SA_RESETHAND | SA_NODEFER | SA_SIGINFO | SA_ONSTACK; + sigemptyset(&sa.sa_mask); + + retval = 0; + while(num_signals--) + { + if((*signals != SIGSEGV && *signals != SIGILL && *signals != SIGFPE && *signals != SIGABRT && + *signals != SIGBUS) || sigaction(*signals, &sa, NULL) == -1) + { + *signals = 0; + retval = -1; + } + ++signals; + } + return retval; +} + + + + +static void* +test_trace(void* ignored) +{ + return (void*)ptrace(PTRACE_TRACEME, 0, NULL, NULL); +} + +bool +is_debugger_attached(void) +{ + pthread_attr_t attr; + void* result; + pthread_t thread; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + if (pthread_create(&thread, &attr, test_trace, NULL) != 0) { + pthread_attr_destroy(&attr); + return false; + } + pthread_attr_destroy(&attr); + if (pthread_join(thread, &result) != 0) { + return false; + } + + return result != NULL; +} diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 33f740b311..672a65d37f 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -16,6 +16,12 @@ #endif + +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE +extern int cc_install_handlers(int argc, char **argv, int num_signals, int *sigs, const char *logfile, int (*user_info)(char*, char*)); +extern int is_debugger_attached(void); +#endif + // for Ogre::macBundlePath #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE #include @@ -239,6 +245,18 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat int main(int argc, char**argv) { +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE + // Unix crash catcher + if (!is_debugger_attached()) + { + int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; + cc_install_handlers(argc, argv, 5, s, "crash.log", NULL); + std::cout << "Installing crash catcher" << std::endl; + } + else + std::cout << "Running in a debugger, not installing crash catcher" << std::endl; +#endif + #if OGRE_PLATFORM == OGRE_PLATFORM_APPLE // set current dir to bundle path boost::filesystem::path bundlePath = boost::filesystem::path(Ogre::macBundlePath()).parent_path(); From 883140babfb48b4bf670a667d02163cee85645b8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 17:00:26 +0100 Subject: [PATCH 045/142] Add missing include for signals to make travis happy --- apps/openmw/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 672a65d37f..4f77c1b1ad 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -18,6 +18,7 @@ #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE +#include extern int cc_install_handlers(int argc, char **argv, int num_signals, int *sigs, const char *logfile, int (*user_info)(char*, char*)); extern int is_debugger_attached(void); #endif From 20d806b40ca38ea6c52d7cee1efdb0a26f953076 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 18:15:54 +0100 Subject: [PATCH 046/142] Change is_debugger_attached to a different hack --- apps/openmw/crashcatcher.cpp | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index 2a1abdcec7..6663306663 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -432,31 +432,18 @@ int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, co } - - -static void* -test_trace(void* ignored) -{ - return (void*)ptrace(PTRACE_TRACEME, 0, NULL, NULL); -} - +// gdb apparently opens FD(s) 3,4,5 (whereas a typical prog uses only stdin=0, stdout=1,stderr=2) bool is_debugger_attached(void) { - pthread_attr_t attr; - void* result; - pthread_t thread; + bool rc = false; + FILE *fd = fopen("/tmp", "r"); - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); - if (pthread_create(&thread, &attr, test_trace, NULL) != 0) { - pthread_attr_destroy(&attr); - return false; - } - pthread_attr_destroy(&attr); - if (pthread_join(thread, &result) != 0) { - return false; + if (fileno(fd) > 5) + { + rc = true; } - return result != NULL; + fclose(fd); + return rc; } From b2d2a3dd178a9ebffeec9d1120d3f3aa3638b2a1 Mon Sep 17 00:00:00 2001 From: vorenon Date: Sat, 16 Nov 2013 19:15:47 +0000 Subject: [PATCH 047/142] Closing message boxes with numpad enter --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 35487e3391..1c4ff161f1 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -503,7 +503,7 @@ namespace MWInput mInputBinder->keyPressed (arg); - if(arg.keysym.sym == SDLK_RETURN + if(arg.keysym.sym == SDLK_RETURN || arg.keysym.sym == SDLK_KP_ENTER && MWBase::Environment::get().getWindowManager()->isGuiMode()) { // Pressing enter when a messagebox is prompting for "ok" will activate the ok button From a19242b4ec0d718e019bc2f8a66a326379091053 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Sat, 16 Nov 2013 19:31:35 +0100 Subject: [PATCH 048/142] fixed expression in if statement --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 1c4ff161f1..9b50be7cab 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -503,7 +503,7 @@ namespace MWInput mInputBinder->keyPressed (arg); - if(arg.keysym.sym == SDLK_RETURN || arg.keysym.sym == SDLK_KP_ENTER + if((arg.keysym.sym == SDLK_RETURN || arg.keysym.sym == SDLK_KP_ENTER) && MWBase::Environment::get().getWindowManager()->isGuiMode()) { // Pressing enter when a messagebox is prompting for "ok" will activate the ok button From f990ba09f01edd2cfe4936838d6787e580b74d8c Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 20:55:41 +0100 Subject: [PATCH 049/142] gdb detection doesn't seem to work for the forked process --- apps/openmw/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 4f77c1b1ad..a36b6e12f5 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -248,7 +248,7 @@ int main(int argc, char**argv) { #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE // Unix crash catcher - if (!is_debugger_attached()) + if ((argc == 2 && strcmp(argv[1], "--cc-handle-crash") == 0) || !is_debugger_attached()) { int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; cc_install_handlers(argc, argv, 5, s, "crash.log", NULL); From 7eb1dcb682ab5aaf96542f5c81e29e0c8f99a955 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 22:29:40 +0100 Subject: [PATCH 050/142] Fix trade windows crashing after a new game --- apps/openmw/mwgui/container.cpp | 6 ++++-- apps/openmw/mwgui/tradewindow.cpp | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 5d864752fc..19ed4dbc00 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -220,11 +220,13 @@ namespace MWGui mDisposeCorpseButton->setVisible(loot); - setTitle(MWWorld::Class::get(container).getName(container)); - mSortModel = new SortFilterItemModel(mModel); mItemView->setModel (mSortModel); + + // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last + // or we end up using a possibly invalid model. + setTitle(MWWorld::Class::get(container).getName(container)); } void ContainerWindow::onCloseButtonClicked(MyGUI::Widget* _sender) diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c179236087..65e3917ed5 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -82,7 +82,6 @@ namespace MWGui void TradeWindow::startTrade(const MWWorld::Ptr& actor) { mPtr = actor; - setTitle(MWWorld::Class::get(actor).getName(actor)); mCurrentBalance = 0; mCurrentMerchantOffer = 0; @@ -99,6 +98,10 @@ namespace MWGui mItemView->setModel (mSortModel); updateLabels(); + + // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last + // or we end up using a possibly invalid model. + setTitle(MWWorld::Class::get(actor).getName(actor)); } void TradeWindow::onFilterChanged(MyGUI::Widget* _sender) From 4a816b6c179e8a63237abbbd773c83bb596cd7a7 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Sat, 16 Nov 2013 23:08:03 +0100 Subject: [PATCH 051/142] fix context menu --- apps/opencs/view/world/table.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index d4ccba4a7b..71bdb9000e 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -49,10 +49,10 @@ void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) { int row =selectedRows.begin()->row(); - int column = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Topic); + int column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Topic); if (column==-1) - column = mModel->findColumnIndex (CSMWorld::Columns::ColumnId_Journal); + column = mModel->searchColumnIndex (CSMWorld::Columns::ColumnId_Journal); if (column!=-1) { @@ -410,4 +410,4 @@ void CSVWorld::Table::requestFocus (const std::string& id) void CSVWorld::Table::recordFilterChanged (boost::shared_ptr filter) { mProxyModel->setFilter (filter); -} \ No newline at end of file +} From 35434dc05375feede36d8debd449a30b32bbeda4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20S=C3=B6derberg?= Date: Sat, 16 Nov 2013 23:15:51 +0100 Subject: [PATCH 052/142] German fixes Fixes for German language. --- files/mygui/openmw_chargen_birth.layout | 10 ++--- files/mygui/openmw_chargen_class.layout | 44 +++++++++--------- .../mygui/openmw_chargen_create_class.layout | 45 ++++++++++--------- files/mygui/openmw_chargen_review.layout | 42 ++++++++--------- 4 files changed, 71 insertions(+), 70 deletions(-) diff --git a/files/mygui/openmw_chargen_birth.layout b/files/mygui/openmw_chargen_birth.layout index b368a6407c..d13a5a02de 100644 --- a/files/mygui/openmw_chargen_birth.layout +++ b/files/mygui/openmw_chargen_birth.layout @@ -1,20 +1,20 @@ - + - + - + - + - + diff --git a/files/mygui/openmw_chargen_class.layout b/files/mygui/openmw_chargen_class.layout index 3c0348b663..aae3c70354 100644 --- a/files/mygui/openmw_chargen_class.layout +++ b/files/mygui/openmw_chargen_class.layout @@ -1,20 +1,20 @@ - + - + - + - + - + @@ -22,12 +22,12 @@ - + - + @@ -35,37 +35,37 @@ - - + + - + - - - - - + + + + + - + - - - - - + + + + + - + diff --git a/files/mygui/openmw_chargen_create_class.layout b/files/mygui/openmw_chargen_create_class.layout index 92382640b9..890e2aac17 100644 --- a/files/mygui/openmw_chargen_create_class.layout +++ b/files/mygui/openmw_chargen_create_class.layout @@ -1,13 +1,14 @@ - + - + + - + @@ -22,12 +23,12 @@ - + - + @@ -35,37 +36,37 @@ - - + + - - + + - - - - - + + + + + - - + + - - - - - + + + + + - + diff --git a/files/mygui/openmw_chargen_review.layout b/files/mygui/openmw_chargen_review.layout index 5d18f4bff8..db55e87546 100644 --- a/files/mygui/openmw_chargen_review.layout +++ b/files/mygui/openmw_chargen_review.layout @@ -1,10 +1,10 @@ - + - + @@ -17,27 +17,27 @@ - - - - + + + + - - + + - + - + @@ -46,57 +46,57 @@ - - + + - + - + - + - + - + - + - + @@ -106,12 +106,12 @@ - + - + From b8f5cd6cb759644ed949e41da924c62fcd3cbb27 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 23:55:54 +0100 Subject: [PATCH 053/142] Forgot to apply the same fix to companion window --- apps/openmw/mwgui/companionwindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index 9698608d69..a0a34108eb 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -86,12 +86,13 @@ void CompanionWindow::onBackgroundSelected() void CompanionWindow::open(const MWWorld::Ptr& npc) { mPtr = npc; - setTitle(MWWorld::Class::get(npc).getName(npc)); updateEncumbranceBar(); mModel = new CompanionItemModel(npc); mSortModel = new SortFilterItemModel(mModel); mItemView->setModel(mSortModel); + + setTitle(MWWorld::Class::get(npc).getName(npc)); } void CompanionWindow::onFrame() From 07251f0fa4319f694802837c04cc11790906f00a Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 04:32:56 +0100 Subject: [PATCH 054/142] Don't apply spells to dead actors --- apps/openmw/mwworld/worldimp.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e6c4c1ef08..2dd0a9b930 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2166,7 +2166,10 @@ namespace MWWorld if (!contact.first.isEmpty()) { if (contact.first.getClass().isActor()) - contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, actor, ESM::RT_Touch, sourceName); + { + if (!contact.first.getClass().getCreatureStats(contact.first).isDead()) + contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, actor, ESM::RT_Touch, sourceName); + } else { // We hit a non-actor, e.g. a door. Only instant effects are relevant. From e7993ced69af272b9eafb6605f2ada9c2ffbc675 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 04:33:04 +0100 Subject: [PATCH 055/142] Fix invalid casts --- apps/openmw/mwrender/animation.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index cc92e62489..10c925b36a 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1045,7 +1045,9 @@ void Animation::updateEffects(float duration) NifOgre::ObjectList& objects = it->mObjects; for(size_t i = 0; i < objects.mControllers.size() ;i++) { - static_cast (objects.mControllers[i].getSource().get())->addTime(duration); + EffectAnimationValue* value = dynamic_cast(objects.mControllers[i].getSource().get()); + if (value) + value->addTime(duration); objects.mControllers[i].update(); } @@ -1058,7 +1060,9 @@ void Animation::updateEffects(float duration) float remainder = objects.mControllers[0].getSource()->getValue() - objects.mMaxControllerLength; for(size_t i = 0; i < objects.mControllers.size() ;i++) { - static_cast (objects.mControllers[i].getSource().get())->resetTime(remainder); + EffectAnimationValue* value = dynamic_cast(objects.mControllers[i].getSource().get()); + if (value) + value->resetTime(remainder); } } else From b8c358df63a079b29096cdc811dc991665fd8498 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 13:37:19 +0100 Subject: [PATCH 056/142] Fix a bug with equipping using the quick keys: when item is already equipped, equipping it again would cause the slot to get unequipped first, possibly restacking and destroying the Ptr that was supposed to be equipped in the first place --- apps/openmw/mwworld/actionequip.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 2a50b8a600..0d091e7425 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -60,6 +60,9 @@ namespace MWWorld for (std::vector::const_iterator slot=slots_.first.begin(); slot!=slots_.first.end(); ++slot) { + // if the item is equipped already, nothing to do + if (invStore.getSlot(*slot) == it) + return; // if all slots are occupied, replace the last slot if (slot == --slots_.first.end()) From 2a11618ee7b4b6370f2ebe3d3898df43a7721006 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 13:38:54 +0100 Subject: [PATCH 057/142] Make sure the equipped weapon HUD icon is updated properly --- apps/openmw/mwgui/inventorywindow.cpp | 8 -------- apps/openmw/mwgui/quickkeysmenu.cpp | 3 ++- apps/openmw/mwmechanics/character.cpp | 6 ++---- .../openmw/mwmechanics/mechanicsmanagerimp.cpp | 2 +- apps/openmw/mwworld/inventorystore.cpp | 18 ++++++++++++------ 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 8f8744917f..022e0a47d8 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -455,14 +455,6 @@ namespace MWGui if (MWBase::Environment::get().getWindowManager()->getSpellWindow()) MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells(); - // update selected weapon icon - MWWorld::InventoryStore& invStore = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weaponSlot = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weaponSlot == invStore.end()) - MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon(); - else - MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*weaponSlot); - mPreviewDirty = true; mArmorRating->setCaptionWithReplacing ("#{sArmor}: " diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 503bf7c114..3956766499 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -284,8 +284,9 @@ namespace MWGui MWWorld::Ptr item = *button->getChildAt (0)->getUserData(); // make sure the item is available - if (item.getRefData ().getCount() == 0) + if (item.getRefData ().getCount() < 1) { + // TODO: Try to find a replacement with the same ID? MWBase::Environment::get().getWindowManager ()->messageBox ( "#{sQuickMenu5} " + MWWorld::Class::get(item).getName(item)); return; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 240695a1cb..88c9f0ff6f 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -563,10 +563,8 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun if(!resultSound.empty()) MWBase::Environment::get().getSoundManager()->playSound(resultSound, 1.0f, 1.0f); - // tool used up? - if(!item.getRefData().getCount()) - MWBase::Environment::get().getWindowManager()->unsetSelectedWeapon(); - else + // Set again, just to update the charge bar + if(item.getRefData().getCount()) MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item); } else diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index bbebcd6939..29b12b0f3a 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -159,7 +159,7 @@ namespace MWMechanics // auto-equip again. we need this for when the race is changed to a beast race MWWorld::InventoryStore& invStore = MWWorld::Class::get(ptr).getInventoryStore(ptr); for (int i=0; i=static_cast (mSlots.size())) throw std::runtime_error ("slot number out of range"); @@ -99,13 +102,11 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite throw std::runtime_error ("attempt to equip an item that is not in the inventory"); std::pair, bool> slots_; - if (iterator!=end()) - { - slots_ = Class::get (*iterator).getEquipmentSlots (*iterator); - if (std::find (slots_.first.begin(), slots_.first.end(), slot)==slots_.first.end()) - throw std::runtime_error ("invalid slot"); - } + slots_ = Class::get (*iterator).getEquipmentSlots (*iterator); + + if (std::find (slots_.first.begin(), slots_.first.end(), slot)==slots_.first.end()) + throw std::runtime_error ("invalid slot"); if (mSlots[slot] != end()) unequipSlot(slot, actor); @@ -126,6 +127,10 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite fireEquipmentChangedEvent(); updateMagicEffects(actor); + + // Update HUD icon for player weapon + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + MWBase::Environment::get().getWindowManager()->setSelectedWeapon(*getSlot(slot)); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) @@ -482,6 +487,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c if ((mSelectedEnchantItem != end()) && (mSelectedEnchantItem == it)) { // enchanted item + mSelectedEnchantItem = end(); MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); } } From 47fc3f81f520758dc618449b726ce765833dbd72 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 14:09:42 +0100 Subject: [PATCH 058/142] Don't update actors while paused --- apps/openmw/mwmechanics/actors.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 2873816f73..6d31a810aa 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -336,6 +336,7 @@ namespace MWMechanics mDuration += duration; //if (mDuration>=0.25) + if (!paused) { float totalDuration = mDuration; mDuration = 0; From fc268bf3028e2815577ec9079c87cb0743a43088 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 14:17:59 +0100 Subject: [PATCH 059/142] Initialize mFallHeight --- apps/openmw/mwmechanics/character.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 88c9f0ff6f..7df6ec566e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -350,6 +350,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mSkipAnim(false) , mSecondsOfRunning(0) , mSecondsOfSwimming(0) + , mFallHeight(0) { if(!mAnimation) return; From 7fd5f1df83b934ea291c524cd8e84aa9e8d7851f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 22:33:37 +0100 Subject: [PATCH 060/142] Change setKeepParticlesInLocalSpace to false. Not correct for all particles, but the opposite isn't either. Plus it breaks pretty much all magic VFX. --- components/nifogre/ogrenifloader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 4b9cb5ad5b..bb98501f47 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -602,7 +602,7 @@ class NIFObjectLoader partsys->setParticleQuota(particledata->numParticles); // TODO: There is probably a field or flag to specify this, as some // particle effects have it and some don't. - partsys->setKeepParticlesInLocalSpace(true); + partsys->setKeepParticlesInLocalSpace(false); Nif::ControllerPtr ctrl = partnode->controller; while(!ctrl.empty()) From 0dc2e829dd45b66c2d02ba69e3de887a4472b8d6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 23:15:57 +0100 Subject: [PATCH 061/142] Lots of cleanup. Implemented Absorb and Resist. Implemented several instant effects. Added hand VFX. --- apps/openmw/mwbase/mechanicsmanager.hpp | 5 + apps/openmw/mwclass/npc.cpp | 17 +- apps/openmw/mwgui/spellicons.cpp | 6 +- apps/openmw/mwgui/spellicons.hpp | 2 +- apps/openmw/mwmechanics/activespells.cpp | 388 +++----------- apps/openmw/mwmechanics/activespells.hpp | 77 ++- apps/openmw/mwmechanics/actors.hpp | 4 + apps/openmw/mwmechanics/character.cpp | 6 + apps/openmw/mwmechanics/magiceffects.hpp | 14 +- .../mwmechanics/mechanicsmanagerimp.cpp | 5 + .../mwmechanics/mechanicsmanagerimp.hpp | 5 + apps/openmw/mwmechanics/spellcasting.cpp | 473 ++++++++++++++++++ apps/openmw/mwmechanics/spellcasting.hpp | 42 +- apps/openmw/mwmechanics/spells.cpp | 58 ++- apps/openmw/mwmechanics/spells.hpp | 5 + apps/openmw/mwrender/animation.cpp | 44 +- apps/openmw/mwrender/animation.hpp | 3 +- apps/openmw/mwrender/objects.cpp | 8 + apps/openmw/mwrender/objects.hpp | 2 + apps/openmw/mwrender/renderingmanager.cpp | 5 + apps/openmw/mwworld/actioneat.cpp | 28 +- apps/openmw/mwworld/actiontrap.cpp | 18 +- apps/openmw/mwworld/inventorystore.cpp | 23 +- apps/openmw/mwworld/worldimp.cpp | 150 +----- 24 files changed, 801 insertions(+), 587 deletions(-) create mode 100644 apps/openmw/mwmechanics/spellcasting.cpp diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 7e09f9b4d7..24dc569d8c 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -114,6 +114,11 @@ namespace MWBase /// references that are currently not in the scene should be ignored. virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; + + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0; + }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index fd24dd93fc..5c67f17afb 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -20,6 +20,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/movement.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontalk.hpp" @@ -467,12 +468,9 @@ namespace MWClass else { weapon.getCellRef().mEnchantmentCharge -= castCost; - // Touch - othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ptr, ESM::RT_Touch, weapon.getClass().getName(weapon)); - // Self - getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ptr, ESM::RT_Self, weapon.getClass().getName(weapon)); - // Target - MWBase::Environment::get().getWorld()->launchProjectile(enchantmentName, enchantment->mEffects, ptr, weapon.getClass().getName(weapon)); + + MWMechanics::CastSpell cast(ptr, victim); + cast.cast(weapon); } } } @@ -932,11 +930,8 @@ namespace MWClass bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id, const MWWorld::Ptr& actor) const { - MWMechanics::CreatureStats& stats = getCreatureStats (ptr); - - /// \todo consider instant effects - - return stats.getActiveSpells().addSpell (id, actor, actor); + MWMechanics::CastSpell cast(ptr, ptr); + return cast.cast(id); } void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index a18e88f5fd..e93e96c4b2 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -21,17 +21,17 @@ namespace MWGui { - void EffectSourceVisitor::visit (const ESM::ENAMstruct& enam, + void EffectSourceVisitor::visit (MWMechanics::EffectKey key, const std::string& sourceName, float magnitude, float remainingTime) { MagicEffectInfo newEffectSource; - newEffectSource.mKey = MWMechanics::EffectKey(enam); + newEffectSource.mKey = key; newEffectSource.mMagnitude = magnitude; newEffectSource.mPermanent = mIsPermanent; newEffectSource.mRemainingTime = remainingTime; newEffectSource.mSource = sourceName; - mEffectSources[enam.mEffectID].push_back(newEffectSource); + mEffectSources[key.mId].push_back(newEffectSource); } diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index bae108a1da..a29e2a00ab 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -42,7 +42,7 @@ namespace MWGui std::map > mEffectSources; - virtual void visit (const ESM::ENAMstruct& enam, + virtual void visit (MWMechanics::EffectKey key, const std::string& sourceName, float magnitude, float remainingTime = -1); }; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 89543476b0..dc79901b0e 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -1,31 +1,7 @@ - #include "activespells.hpp" -#include - -#include - -#include -#include -#include -#include -#include - -#include "../mwworld/esmstore.hpp" - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwbase/soundmanager.hpp" -#include "../mwbase/windowmanager.hpp" - -#include "../mwrender/animation.hpp" - -#include "../mwworld/class.hpp" - -#include "../mwmechanics/spellcasting.hpp" - -#include "creaturestats.hpp" -#include "npcstats.hpp" namespace MWMechanics { @@ -35,6 +11,7 @@ namespace MWMechanics MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp(); + // Erase no longer active spells if (mLastUpdate!=now) { TContainer::iterator iter (mSpells.begin()); @@ -42,7 +19,6 @@ namespace MWMechanics if (!timeToExpire (iter)) { mSpells.erase (iter++); - //onSpellExpired rebuild = true; } else @@ -69,277 +45,28 @@ namespace MWMechanics for (TIterator iter (begin()); iter!=end(); ++iter) { - std::pair > effects = getEffectList (iter->first); - const MWWorld::TimeStamp& start = iter->second.mTimeStamp; - int i = 0; - for (std::vector::const_iterator effectIter (effects.first.mList.begin()); - effectIter!=effects.first.mList.end(); ++effectIter, ++i) + const std::vector& effects = iter->second.mEffects; + + for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) { - float random = iter->second.mRandom[i]; - if (effectIter->mRange != iter->second.mRange) - continue; + int duration = effectIt->mDuration; + MWWorld::TimeStamp end = start; + end += static_cast (duration)* + MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); - if (effectIter->mDuration) - { - int duration = effectIter->mDuration; - - if (effects.second.first) - duration *= random; - - MWWorld::TimeStamp end = start; - end += static_cast (duration)* - MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); - - if (end>now) - { - EffectParam param; - - if (effects.second.first) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIter->mEffectID); - - if (effectIter->mDuration==0) - { - param.mMagnitude = - static_cast (random / (0.1 * magicEffect->mData.mBaseCost)); - } - else - { - param.mMagnitude = - static_cast (0.05*random / (0.1 * magicEffect->mData.mBaseCost)); - } - } - else - param.mMagnitude = static_cast ( - (effectIter->mMagnMax-effectIter->mMagnMin)*random + effectIter->mMagnMin); - param.mMagnitude *= iter->second.mMultiplier[i]; - - if (param.mMagnitude) - mEffects.add (*effectIter, param); - } - } + if (end>now) + mEffects.add(effectIt->mKey, MWMechanics::EffectParam(effectIt->mMagnitude)); } } } - std::pair > ActiveSpells::getEffectList (const std::string& id) const - { - if (const ESM::Enchantment* enchantment = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return std::make_pair (enchantment->mEffects, std::make_pair(false, false)); - - if (const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return std::make_pair (spell->mEffects, std::make_pair(false, false)); - - if (const ESM::Potion *potion = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return std::make_pair (potion->mEffects, std::make_pair(false, true)); - - if (const ESM::Ingredient *ingredient = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - ingredient->mData.mEffectID[0]); - - ESM::ENAMstruct effect; - effect.mEffectID = ingredient->mData.mEffectID[0]; - effect.mSkill = ingredient->mData.mSkills[0]; - effect.mAttribute = ingredient->mData.mAttributes[0]; - effect.mRange = 0; - effect.mArea = 0; - effect.mDuration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ? 0 : 1; - effect.mMagnMin = 1; - effect.mMagnMax = 1; - - std::pair > result; - result.second.second = true; - result.second.first = true; - - result.first.mList.push_back (effect); - - return result; - } - - throw std::runtime_error ("ID " + id + " can not produce lasting effects"); - } - - std::string ActiveSpells::getSpellDisplayName (const std::string& id) const - { - if (const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return spell->mName; - - if (const ESM::Potion *potion = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return potion->mName; - - if (const ESM::Ingredient *ingredient = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return ingredient->mName; - - throw std::runtime_error ("ID " + id + " has no display name"); - } - ActiveSpells::ActiveSpells() - : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) + : mSpellsChanged (false) + , mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) {} - bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range, const std::string& name, int effectIndex) - { - const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); - - std::pair > effects = getEffectList (id); - bool stacks = effects.second.second; - - bool found = false; - - for (std::vector::const_iterator iter (effects.first.mList.begin()); - iter!=effects.first.mList.end(); ++iter) - { - if (iter->mRange != range) - continue; - if (iter->mDuration) - { - found = true; - break; - } - } - - // If none of the effects need to apply, no need to add the spell - if (!found) - return false; - - TContainer::iterator iter = mSpells.find (id); - - ActiveSpellParams params; - for (unsigned int i=0; i (std::rand()) / RAND_MAX; - if (effects.second.first) - { - // ingredient -> special treatment required. - const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); - - float x = - (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + - 0.2 * creatureStats.getAttribute (1).getModified() - + 0.1 * creatureStats.getAttribute (7).getModified()) - * creatureStats.getFatigueTerm(); - random *= 100; - random = random / std::min (x, 100.0f); - random *= 0.25 * x; - } - - params.mRandom.push_back(random); - } - params.mRange = range; - params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); - params.mName = name; - params.mMultiplier.resize(effects.first.mList.size(), 1); - - /* - for (int i=0; imRange != range) - { - params.mDisabled.push_back(true); - continue; - } - - bool disabled = false; - - int reflect = creatureStats.getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (roll < reflect) - disabled = true; - } - */ - - bool first=true; - int i = 0; - for (std::vector::const_iterator effectIt (effects.first.mList.begin()); - effectIt!=effects.first.mList.end(); ++effectIt, ++i) - { - if (effectIt->mRange != range) - continue; - - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIt->mEffectID); - - if (caster.getRefData().getHandle() == "player" && actor != caster - && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) - MWBase::Environment::get().getWindowManager()->setEnemy(actor); - - // Try resisting effect in case its harmful - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id); - params.mMultiplier[i] = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, caster, spell); - if (params.mMultiplier[i] == 0) - { - if (actor.getRefData().getHandle() == "player") - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - else - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); - } - - // If fully resisted, don't play sounds or particles - if (params.mMultiplier[i] == 0) - continue; - - // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World. - - // Only the sound of the first effect plays - if (first) - { - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!magicEffect->mHitSound.empty()) - sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); - } - - if (!magicEffect->mHit.empty()) - { - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; - MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); - } - - first = false; - } - - if (iter==mSpells.end() || stacks) - mSpells.insert (std::make_pair (id, params)); - else - iter->second = params; - - mSpellsChanged = true; - - return true; - } - - void ActiveSpells::removeSpell (const std::string& id) - { - TContainer::iterator iter = mSpells.find (id); - - if (iter!=mSpells.end()) - { - mSpells.erase (iter); - mSpellsChanged = true; - } - } - const MagicEffects& ActiveSpells::getMagicEffects() const { update(); @@ -348,37 +75,31 @@ namespace MWMechanics ActiveSpells::TIterator ActiveSpells::begin() const { - update(); return mSpells.begin(); } ActiveSpells::TIterator ActiveSpells::end() const { - update(); return mSpells.end(); } double ActiveSpells::timeToExpire (const TIterator& iterator) const { - std::pair > effects = getEffectList (iterator->first); + const std::vector& effects = iterator->second.mEffects; int duration = 0; - for (std::vector::const_iterator iter (effects.first.mList.begin()); - iter!=effects.first.mList.end(); ++iter) + for (std::vector::const_iterator iter (effects.begin()); + iter!=effects.end(); ++iter) { if (iter->mDuration > duration) duration = iter->mDuration; } - // Scale duration by magnitude if needed - if (effects.second.first && iterator->second.mRandom.size()) - duration *= iterator->second.mRandom.front(); - double scaledDuration = duration * MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); - double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp()-iterator->second.mTimeStamp; + double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp() - iterator->second.mTimeStamp; if (usedUp>=scaledDuration) return 0; @@ -405,48 +126,67 @@ namespace MWMechanics return mSpells; } + void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector effects, const std::string &displayName) + { + bool exists = false; + for (TContainer::const_iterator it = begin(); it != end(); ++it) + { + if (id == it->first) + exists = true; + } + + ActiveSpellParams params; + params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); + params.mEffects = effects; + params.mDisplayName = displayName; + + if (!exists || stack) + mSpells.insert (std::make_pair(id, params)); + else + mSpells.find(id)->second = params; + + mSpellsChanged = true; + } + void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const { for (TContainer::const_iterator it = begin(); it != end(); ++it) { - const ESM::EffectList& list = getEffectList(it->first).first; - float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); - int i=0; - for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt, ++i) + for (std::vector::const_iterator effectIt = it->second.mEffects.begin(); + effectIt != it->second.mEffects.end(); ++effectIt) { - if (effectIt->mRange != it->second.mRange) - continue; - - std::string name; - if (it->second.mName.empty()) - name = getSpellDisplayName(it->first); - else - name = it->second.mName; + std::string name = it->second.mDisplayName; float remainingTime = effectIt->mDuration + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second.mRandom[i]; + float magnitude = effectIt->mMagnitude; - // hack for ingredients - if (MWBase::Environment::get().getWorld()->getStore().get().search (it->first)) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIt->mEffectID); - - remainingTime = effectIt->mDuration * it->second.mRandom[i] + - (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; - - magnitude = static_cast (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost)); - } - - magnitude *= it->second.mMultiplier[i]; if (magnitude) - visitor.visit(*effectIt, name, magnitude, remainingTime); + visitor.visit(effectIt->mKey, name, magnitude, remainingTime); } } } + + void ActiveSpells::purgeAll() + { + mSpells.clear(); + } + + void ActiveSpells::purgeEffect(short effectId) + { + for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + for (std::vector::iterator effectIt = it->second.mEffects.begin(); + effectIt != it->second.mEffects.end();) + { + if (effectIt->mKey.mId == effectId) + effectIt = it->second.mEffects.erase(effectIt); + else + effectIt++; + } + } + + } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 56d9413c39..001402337c 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -11,39 +11,8 @@ #include -namespace ESM -{ - struct Spell; - struct EffectList; -} - -namespace MWWorld -{ - class Ptr; -} - namespace MWMechanics { - struct ActiveSpellParams - { - // Only apply effects of this range type - ESM::RangeType mRange; - - // When the spell was added - MWWorld::TimeStamp mTimeStamp; - - // Random factor for each effect - std::vector mRandom; - - // Effect magnitude multiplier. Use 0 to completely disable the effect - // (if it was resisted, reflected or absorbed). Use (0,1) for partially resisted. - std::vector mMultiplier; - - // Display name, we need this for enchantments, which don't have a name - so you need to supply the - // name of the item with the enchantment to addSpell - std::string mName; - }; - /// \brief Lasting spell effects /// /// \note The name of this class is slightly misleading, since it also handels lasting potion @@ -52,6 +21,23 @@ namespace MWMechanics { public: + // Parameters of an effect concerning lasting effects. + // Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc. + // It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster. + struct Effect + { + float mMagnitude; + EffectKey mKey; + float mDuration; + }; + + struct ActiveSpellParams + { + std::vector mEffects; + MWWorld::TimeStamp mTimeStamp; + std::string mDisplayName; + }; + typedef std::multimap TContainer; typedef TContainer::const_iterator TIterator; @@ -66,9 +52,6 @@ namespace MWMechanics void rebuildEffects() const; - std::pair > getEffectList (const std::string& id) const; - ///< @return (EffectList, (isIngredient, stacks)) - double timeToExpire (const TIterator& iterator) const; ///< Returns time (in in-game hours) until the spell pointed to by \a iterator /// expires. @@ -79,25 +62,25 @@ namespace MWMechanics TIterator end() const; - std::string getSpellDisplayName (const std::string& id) const; - public: ActiveSpells(); - bool addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1); - ///< Overwrites an existing spell with the same ID. If the spell does not have any - /// non-instant effects, it is ignored. - /// @param id - /// @param actor actor to add the spell to - /// @param caster actor who casted the spell - /// @param range Only effects with range type \a range will be applied - /// @param name Display name for enchantments, since they don't have a name in their record - /// @param effectIndex Only apply one specific effect - useful for reflecting spells, since each effect is reflected individually + /// Add lasting effects /// - /// \return Has the spell been added? + /// \brief addSpell + /// \param id ID for stacking purposes. + /// \param stack If false, the spell is not added if one with the same ID exists already. + /// \param effects + /// \param displayName Name for display in magic menu. + /// + void addSpell (const std::string& id, bool stack, std::vector effects, const std::string& displayName); - void removeSpell (const std::string& id); + /// Remove all active effects with this id + void purgeEffect (short effectId); + + /// Remove all active effects + void purgeAll (); bool isSpellActive (std::string id) const; ///< case insensitive diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 251c38ec0e..01a96f1993 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -53,6 +53,10 @@ namespace MWMechanics Actors(); + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + void updateMagicEffects (const MWWorld::Ptr& ptr) { adjustMagicEffects(ptr); } + void addActor (const MWWorld::Ptr& ptr); ///< Register an actor for stats management /// diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 7df6ec566e..c7a6b38751 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -516,6 +516,12 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun const ESM::Static* castStatic = store.get().find (effect->mCasting); mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); + castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); + //mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); + //mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle); + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Right Hand", effect->mParticle); + switch(effectentry.mRange) { case 0: mAttackType = "self"; break; diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 58f023ebac..2c1b363b7d 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -12,13 +12,6 @@ namespace ESM namespace MWMechanics { - // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display - struct EffectSourceVisitor - { - virtual void visit (const ESM::ENAMstruct& enam, - const std::string& sourceName, float magnitude, float remainingTime = -1) = 0; - }; - struct EffectKey { int mId; @@ -59,6 +52,13 @@ namespace MWMechanics return param -= right; } + // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display + struct EffectSourceVisitor + { + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, float magnitude, float remainingTime = -1) = 0; + }; + /// \brief Effects currently affecting a NPC or creature class MagicEffects { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 29b12b0f3a..e1a7ac1231 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -679,4 +679,9 @@ namespace MWMechanics return false; } + void MechanicsManager::updateMagicEffects(const MWWorld::Ptr &ptr) + { + mActors.updateMagicEffects(ptr); + } + } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index aedb84b29c..42656d5abc 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -100,6 +100,11 @@ namespace MWMechanics virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); virtual void skipAnimation(const MWWorld::Ptr& ptr); virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); + + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + virtual void updateMagicEffects (const MWWorld::Ptr& ptr); + }; } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp new file mode 100644 index 0000000000..00ca82c5af --- /dev/null +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -0,0 +1,473 @@ +#include "spellcasting.hpp" + +#include + +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/soundmanager.hpp" + + +#include "../mwworld/containerstore.hpp" + +#include "../mwrender/animation.hpp" + +namespace MWMechanics +{ + + CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target) + : mCaster(caster) + , mTarget(target) + , mStack(false) + { + } + + void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, + const ESM::EffectList &effects, ESM::RangeType range, bool reflected) + { + // If none of the effects need to apply, we can early-out + bool found = false; + for (std::vector::const_iterator iter (effects.mList.begin()); + iter!=effects.mList.end(); ++iter) + { + if (iter->mRange != range) + continue; + found = true; + } + if (!found) + return; + + ESM::EffectList reflectedEffects; + std::vector appliedLastingEffects; + bool firstAppliedEffect = true; + + for (std::vector::const_iterator effectIt (effects.mList.begin()); + effectIt!=effects.mList.end(); ++effectIt) + { + if (effectIt->mRange != range) + continue; + + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); + + float magnitudeMult = 1; + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor()) + { + // If player is attempting to cast a harmful spell, show the target's HP bar + if (caster.getRefData().getHandle() == "player" && target != caster) + MWBase::Environment::get().getWindowManager()->setEnemy(target); + + // Try absorbing if it's a spell + // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure + // if that is worth replicating. + if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId)) + { + int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + bool isAbsorbed = (roll < absorb); + if (isAbsorbed) + { + const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); + MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( + "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::Reflect, false, ""); + // Magicka is increased by cost of spell + DynamicStat magicka = target.getClass().getCreatureStats(target).getMagicka(); + magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); + target.getClass().getCreatureStats(target).setMagicka(magicka); + magnitudeMult = 0; + } + } + + // Try reflecting + if (!reflected && magnitudeMult > 0) + { + int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + bool isReflected = (roll < reflect); + if (isReflected) + { + const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); + MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( + "meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, ""); + reflectedEffects.mList.push_back(*effectIt); + magnitudeMult = 0; + } + } + + // Try resisting + if (magnitudeMult > 0 && caster.getClass().isActor()) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().search (mId); + magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); + if (magnitudeMult == 0) + { + // Fully resisted, show message + if (target.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + } + } + } + + + if (magnitudeMult > 0) + { + float random = std::rand() / static_cast(RAND_MAX); + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; + magnitude *= magnitudeMult; + + if (target.getClass().isActor() && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) + { + ActiveSpells::Effect effect; + effect.mKey = MWMechanics::EffectKey(*effectIt); + effect.mDuration = effectIt->mDuration; + effect.mMagnitude = magnitude; + + appliedLastingEffects.push_back(effect); + } + else + applyInstantEffect(mTarget, effectIt->mEffectID, magnitude); + + if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + { + // Play sound, only for the first effect + if (firstAppliedEffect) + { + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!magicEffect->mHitSound.empty()) + sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); + firstAppliedEffect = false; + } + + // Add VFX + if (!magicEffect->mHit.empty()) + { + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); + bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; + // Note: in case of non actor, a free effect should be fine as well + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); + if (anim) + anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); + } + } + + // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World. + } + } + + if (reflectedEffects.mList.size()) + inflict(caster, target, reflectedEffects, range, true); + + if (appliedLastingEffects.size()) + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, mSourceName); + } + + void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, short effectId, float magnitude) + { + if (!target.getClass().isActor()) + { + if (effectId == ESM::MagicEffect::Lock) + { + if (target.getCellRef().mLockLevel < magnitude) + target.getCellRef().mLockLevel = magnitude; + } + else if (effectId == ESM::MagicEffect::Open) + { + // TODO: This is a crime + if (target.getCellRef().mLockLevel <= magnitude) + { + if (target.getCellRef().mLockLevel > 0) + MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); + target.getCellRef().mLockLevel = 0; + } + else + MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); + } + } + else + { + if (effectId == ESM::MagicEffect::CurePoison) + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); + else if (effectId == ESM::MagicEffect::CureParalyzation) + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); + else if (effectId == ESM::MagicEffect::CureCommonDisease) + target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease(); + else if (effectId == ESM::MagicEffect::CureBlightDisease) + target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease(); + else if (effectId == ESM::MagicEffect::CureCorprusDisease) + target.getClass().getCreatureStats(target).getSpells().purgeCorprusDisease(); + else if (effectId == ESM::MagicEffect::Dispel) + target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(); + else if (effectId == ESM::MagicEffect::RemoveCurse) + target.getClass().getCreatureStats(target).getSpells().purgeCurses(); + + else if (effectId == ESM::MagicEffect::DivineIntervention) + { + // We need to be able to get the world location of an interior cell before implementing this + // or alternatively, the last known exterior location of the player, which is how vanilla does it. + } + else if (effectId == ESM::MagicEffect::AlmsiviIntervention) + { + // Same as above + } + + else if (effectId == ESM::MagicEffect::Mark) + { + // TODO + } + else if (effectId == ESM::MagicEffect::Recall) + { + // TODO + } + } + } + + bool CastSpell::cast(const std::string &id) + { + if (const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return cast(spell); + + if (const ESM::Potion *potion = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return cast(potion); + + if (const ESM::Ingredient *ingredient = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return cast(ingredient); + + throw std::runtime_error("ID type cannot be casted"); + } + + bool CastSpell::cast(const MWWorld::Ptr &item) + { + std::string enchantmentName = item.getClass().getEnchantment(item); + if (enchantmentName.empty()) + throw std::runtime_error("can't cast an item without an enchantment"); + + mSourceName = item.getClass().getName(item); + mId = item.getCellRef().mRefID; + + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); + + mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce); + + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) + { + // Check if there's enough charge left + const float enchantCost = enchantment->mData.mCost; + MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster); + int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); + const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); + + if (item.getCellRef().mEnchantmentCharge == -1) + item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; + + if (mCaster.getRefData().getHandle() == "player" && item.getCellRef().mEnchantmentCharge < castCost) + { + // TODO: Should there be a sound here? + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); + return false; + } + + // Reduce charge + item.getCellRef().mEnchantmentCharge -= castCost; + } + if (enchantment->mData.mType == ESM::Enchantment::CastOnce) + item.getContainerStore()->remove(item, 1, mCaster); + else + { + if (mCaster.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge + } + + inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); + + if (!mTarget.isEmpty()) + { + if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead()) + inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); + } + + MWBase::Environment::get().getWorld()->launchProjectile(mId, enchantment->mEffects, mCaster, mSourceName); + + return true; + } + + bool CastSpell::cast(const ESM::Potion* potion) + { + mSourceName = potion->mName; + mId = potion->mId; + mStack = true; + + inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self); + + return true; + } + + bool CastSpell::cast(const ESM::Spell* spell) + { + mSourceName = spell->mName; + mId = spell->mId; + mStack = false; + + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + + int school = 0; + + if (mCaster.getClass().isActor()) + { + school = getSpellSchool(spell, mCaster); + + CreatureStats& stats = mCaster.getClass().getCreatureStats(mCaster); + + // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss) + static const float fFatigueSpellBase = store.get().find("fFatigueSpellBase")->getFloat(); + static const float fFatigueSpellMult = store.get().find("fFatigueSpellMult")->getFloat(); + DynamicStat fatigue = stats.getFatigue(); + const float normalizedEncumbrance = mCaster.getClass().getEncumbrance(mCaster) / mCaster.getClass().getCapacity(mCaster); + float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); + fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss)); + stats.setFatigue(fatigue); + + // Check mana + bool fail = false; + DynamicStat magicka = stats.getMagicka(); + if (magicka.getCurrent() < spell->mData.mCost) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}"); + fail = true; + } + + // Reduce mana + if (!fail) + { + magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); + stats.setMagicka(magicka); + } + + // If this is a power, check if it was already used in last 24h + if (!fail && spell->mData.mType & ESM::Spell::ST_Power) + { + if (stats.canUsePower(spell->mId)) + stats.usePower(spell->mId); + else + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sPowerAlreadyUsed}"); + fail = true; + } + } + + // Check success + int successChance = getSpellSuccessChance(spell, mCaster); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (!fail && roll >= successChance) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); + fail = true; + } + + if (fail) + { + // Failure sound + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f); + return false; + } + } + + if (mCaster.getRefData().getHandle() == "player" && spell->mData.mType == ESM::Spell::ST_Spell) + mCaster.getClass().skillUsageSucceeded(mCaster, + spellSchoolToSkill(school), 0); + + inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self); + + if (!mTarget.isEmpty()) + { + if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead()) + { + inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); + } + } + + MWBase::Environment::get().getWorld()->launchProjectile(mId, spell->mEffects, mCaster, mSourceName); + return true; + } + + bool CastSpell::cast (const ESM::Ingredient* ingredient) + { + mId = ingredient->mId; + mStack = true; + mSourceName = ingredient->mName; + + ESM::ENAMstruct effect; + effect.mEffectID = ingredient->mData.mEffectID[0]; + effect.mSkill = ingredient->mData.mSkills[0]; + effect.mAttribute = ingredient->mData.mAttributes[0]; + effect.mRange = ESM::RT_Self; + effect.mArea = 0; + + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effect.mEffectID); + + const MWMechanics::NpcStats& npcStats = mCaster.getClass().getNpcStats(mCaster); + const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); + + float x = (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + + 0.2 * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified() + + 0.1 * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()) + * creatureStats.getFatigueTerm(); + + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll > x) + { + // "X has no effect on you" + std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("#{sNotifyMessage50}")->getString(); + message = boost::str(boost::format(message) % ingredient->mName); + + MWBase::Environment::get().getWindowManager()->messageBox(message); + return false; + } + + float magnitude = 0; + float y = roll / std::min(x, 100.f); + y *= 0.25 * x; + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + effect.mDuration = int(y); + else + effect.mDuration = 1; + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + { + if (!magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + magnitude = int((0.05 * y) / (0.1 * magicEffect->mData.mBaseCost)); + else + magnitude = int(y / (0.1 * magicEffect->mData.mBaseCost)); + magnitude = std::max(1.f, magnitude); + } + else + magnitude = 1; + + effect.mMagnMax = magnitude; + effect.mMagnMin = magnitude; + + ESM::EffectList effects; + effects.mList.push_back(effect); + + inflict(mCaster, mCaster, effects, ESM::RT_Self); + + return true; + } + +} diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 3bbb4d0c53..5e87e19933 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -44,12 +44,6 @@ namespace MWMechanics if (creatureStats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude) return 0; - if (spell->mData.mType != ESM::Spell::ST_Spell) - return 100; - - if (spell->mData.mFlags & ESM::Spell::F_Always) - return 100; - float y = FLT_MAX; float lowestSkill = 0; @@ -79,8 +73,14 @@ namespace MWMechanics } } + if (spell->mData.mType != ESM::Spell::ST_Spell) + return 100; + + if (spell->mData.mFlags & ESM::Spell::F_Always) + return 100; int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude; + int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); @@ -98,7 +98,6 @@ namespace MWMechanics return getSpellSuccessChance(spell, actor, effectiveSchool); } - /// @note this only works for ST_Spell inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) { int school = 0; @@ -106,7 +105,6 @@ namespace MWMechanics return school; } - /// @note this only works for ST_Spell inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) { int school = 0; @@ -180,6 +178,34 @@ namespace MWMechanics return -(resistance-100) / 100.f; } + + class CastSpell + { + private: + MWWorld::Ptr mCaster; + MWWorld::Ptr mTarget; + + bool mStack; + std::string mId; // ID of spell, potion, item etc + std::string mSourceName; // Display name for spell, potion, etc + + public: + CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); + + bool cast (const ESM::Spell* spell); + bool cast (const MWWorld::Ptr& item); + bool cast (const ESM::Ingredient* ingredient); + bool cast (const ESM::Potion* potion); + + /// @note Auto detects if spell, ingredient or potion + bool cast (const std::string& id); + + void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, + const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false); + + void applyInstantEffect (const MWWorld::Ptr& target, short effectId, float magnitude); + }; + } #endif diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index a5a5677d12..5b18e2a3c5 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -118,6 +118,62 @@ namespace MWMechanics return false; } + void Spells::purgeCommonDisease() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + + if (spell->mData.mType & ESM::Spell::ST_Disease) + mSpells.erase(iter++); + else + iter++; + } + } + + void Spells::purgeBlightDisease() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + + if (spell->mData.mType & ESM::Spell::ST_Blight) + mSpells.erase(iter++); + else + iter++; + } + } + + void Spells::purgeCorprusDisease() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + + if (Misc::StringUtils::ciEqual(spell->mId, "corprus")) + mSpells.erase(iter++); + else + iter++; + } + } + + void Spells::purgeCurses() + { + for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + { + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().find (iter->first); + + if (spell->mData.mType == ESM::Spell::ST_Curse) + mSpells.erase(iter++); + else + iter++; + } + } + void Spells::visitEffectSources(EffectSourceVisitor &visitor) const { for (TIterator it = begin(); it != end(); ++it) @@ -136,7 +192,7 @@ namespace MWMechanics effectIt != list.mList.end(); ++effectIt, ++i) { float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i]; - visitor.visit(*effectIt, spell->mName, magnitude); + visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, magnitude); } } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 79b7a782d0..cf9b660915 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -35,6 +35,11 @@ namespace MWMechanics public: + void purgeCommonDisease(); + void purgeBlightDisease(); + void purgeCorprusDisease(); + void purgeCurses(); + TIterator begin() const; TIterator end() const; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 10c925b36a..a29beb35c8 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -994,16 +994,27 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj) mSkelBase->detachObjectFromBone(obj); } -void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename) +void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename, std::string texture) { // Early out if we already have this effect for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) return; + // fix texture extension to .dds + if (texture.size() > 4) + { + texture[texture.size()-3] = 'd'; + texture[texture.size()-2] = 'd'; + texture[texture.size()-1] = 's'; + } + EffectParams params; params.mModelName = model; - params.mObjects = NifOgre::Loader::createObjects(mInsert, model); + if (bonename.empty()) + params.mObjects = NifOgre::Loader::createObjects(mInsert, model); + else + params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); params.mLoop = loop; params.mEffectId = effectId; params.mBoneName = bonename; @@ -1013,6 +1024,35 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con if(params.mObjects.mControllers[i].getSource().isNull()) params.mObjects.mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationValue())); } + + if (!texture.empty()) + { + for(size_t i = 0;i < params.mObjects.mParticles.size(); ++i) + { + Ogre::ParticleSystem* partSys = params.mObjects.mParticles[i]; + Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName()); + static int count = 0; + Ogre::String materialName = "openmw/" + Ogre::StringConverter::toString(count++); + // TODO: destroy when effect is removed + Ogre::MaterialPtr newMat = mat->clone(materialName); + partSys->setMaterialName(materialName); + + for (int t=0; tgetNumTechniques(); ++t) + { + Ogre::Technique* tech = newMat->getTechnique(t); + for (int p=0; pgetNumPasses(); ++p) + { + Ogre::Pass* pass = tech->getPass(p); + for (int tex=0; texgetNumTextureUnitStates(); ++tex) + { + Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); + tus->setTextureName("textures\\" + texture); + } + } + } + } + } + mEffects.push_back(params); } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index b1572b6a16..7d1fd010ca 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -206,9 +206,10 @@ public: * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, * you need to remove it manually using removeEffect when the effect should end. * @param bonename Bone to attach to, or empty string to use the scene node instead + * @param texture override the texture specified in the model's materials * @note Will not add an effect twice. */ - void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = ""); + void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", std::string texture = ""); void removeEffect (int effectId); void getLoopingEffects (std::vector& out); private: diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index fd81baf6ed..4d5f6872df 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -281,3 +281,11 @@ void Objects::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) node->addChild(cur.getRefData().getBaseNode()); } +ObjectAnimation* Objects::getAnimation(const MWWorld::Ptr &ptr) +{ + PtrAnimationMap::const_iterator iter = mObjects.find(ptr); + if(iter != mObjects.end()) + return iter->second; + return NULL; +} + diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 22dd1e4f5d..165f6551d3 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -41,6 +41,8 @@ public: ~Objects(){} void insertModel(const MWWorld::Ptr& ptr, const std::string &model); + ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr); + void enableLights(); void disableLights(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 57e00d76c7..1b891368ff 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -975,8 +975,13 @@ void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rend Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) { Animation *anim = mActors.getAnimation(ptr); + if(!anim && ptr.getRefData().getHandle() == "player") anim = mPlayerAnimation; + + if (!anim) + anim = mObjects.getAnimation(ptr); + return anim; } diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index 470eeda2b3..f5d7e26361 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -3,17 +3,11 @@ #include -#include - #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" -#include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/npcstats.hpp" - #include "../mwworld/containerstore.hpp" -#include "esmstore.hpp" #include "class.hpp" namespace MWWorld @@ -23,27 +17,11 @@ namespace MWWorld // remove used item (assume the item is present in inventory) getTarget().getContainerStore()->remove(getTarget(), 1, actor); - // check for success - const MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); - MWMechanics::NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); - - float x = - (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + - 0.2 * creatureStats.getAttribute (1).getModified() - + 0.1 * creatureStats.getAttribute (7).getModified()) - * creatureStats.getFatigueTerm(); - - if (x>=100*static_cast (std::rand()) / RAND_MAX) - { - // apply to actor - std::string id = Class::get (getTarget()).getId (getTarget()); + // apply to actor + std::string id = Class::get (getTarget()).getId (getTarget()); - Class::get (actor).apply (actor, id, actor); - // we ignore the result here. Skill increases no matter if the ingredient did something or not. - - // increase skill + if (Class::get (actor).apply (actor, id, actor)) Class::get (actor).skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); - } } ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {} diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp index 80da290727..d723b98239 100644 --- a/apps/openmw/mwworld/actiontrap.cpp +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -1,26 +1,14 @@ #include "actiontrap.hpp" -#include "../mwworld/class.hpp" - -#include "../mwmechanics/activespells.hpp" -#include "../mwmechanics/creaturestats.hpp" - -#include "../mwbase/world.hpp" -#include "../mwbase/environment.hpp" +#include "../mwmechanics/spellcasting.hpp" namespace MWWorld { void ActionTrap::executeImp(const Ptr &actor) { - // TODO: Apply RT_Self effects on the door / container that triggered the trap. Not terribly useful, but you could - // make it lock itself when activated for example. - - actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, actor, ESM::RT_Touch); - - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); - - MWBase::Environment::get().getWorld()->launchProjectile(mSpellId, spell->mEffects, mTrapSource, spell->mName); + MWMechanics::CastSpell cast(mTrapSource, actor); + cast.cast(mSpellId); mTrapSource.getCellRef().mTrap = ""; } diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 72523a69ce..6483b32b2e 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -9,6 +9,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" @@ -339,6 +340,9 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) if (params[i].mMultiplier == 0) continue; + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; + magnitude *= params[i].mMultiplier; + if (!existed) { // During first auto equip, we don't play any sounds. @@ -346,10 +350,13 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) // the items should appear as if they'd always been equipped. mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip, !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin()); + + // Apply instant effects + MWMechanics::CastSpell cast(actor, actor); + if (magnitude) + cast.applyInstantEffect(actor, effectIt->mEffectID, magnitude); } - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; - magnitude *= params[i].mMultiplier; if (magnitude) mMagicEffects.add (*effectIt, magnitude); } @@ -376,6 +383,9 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) ++it; } + // Magic effects are normally not updated when paused, but we need this to make resistances work immediately after equipping + MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); + mFirstAutoEquip = false; } @@ -442,6 +452,13 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor autoEquip(actor); } + if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() + && *mSelectedEnchantItem == item && actor.getRefData().getHandle() == "player") + { + mSelectedEnchantItem = end(); + MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); + } + return retCount; } @@ -554,7 +571,7 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i]; float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; magnitude *= params.mMultiplier; - visitor.visit(*effectIt, (**iter).getClass().getName(**iter), magnitude); + visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), magnitude); ++i; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 2dd0a9b930..7dec848ad3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2029,156 +2029,28 @@ namespace MWWorld void World::castSpell(const Ptr &actor) { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + InventoryStore& inv = actor.getClass().getInventoryStore(actor); + + // Unset casting flag, otherwise pressing the mouse button down would continue casting every frame if using an enchantment + // (which casts instantly without an animation) stats.setAttackingOrSpell(false); - ESM::EffectList effects; + MWWorld::Ptr target = getFacedObject(); std::string selectedSpell = stats.getSpells().getSelectedSpell(); - std::string sourceName; + + MWMechanics::CastSpell cast(actor, target); + if (!selectedSpell.empty()) { const ESM::Spell* spell = getStore().get().search(selectedSpell); - // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss) - static const float fFatigueSpellBase = getStore().get().find("fFatigueSpellBase")->getFloat(); - static const float fFatigueSpellMult = getStore().get().find("fFatigueSpellMult")->getFloat(); - MWMechanics::DynamicStat fatigue = stats.getFatigue(); - const float normalizedEncumbrance = actor.getClass().getEncumbrance(actor) / actor.getClass().getCapacity(actor); - float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); - fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss)); - stats.setFatigue(fatigue); - - // Check mana - bool fail = false; - MWMechanics::DynamicStat magicka = stats.getMagicka(); - if (magicka.getCurrent() < spell->mData.mCost) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}"); - fail = true; - } - - // Reduce mana - if (!fail) - { - magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); - stats.setMagicka(magicka); - } - - // If this is a power, check if it was already used in last 24h - if (!fail && spell->mData.mType & ESM::Spell::ST_Power) - { - if (stats.canUsePower(selectedSpell)) - stats.usePower(selectedSpell); - else - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sPowerAlreadyUsed}"); - fail = true; - } - } - - // Check success - int successChance = MWMechanics::getSpellSuccessChance(selectedSpell, actor); - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (!fail && roll >= successChance) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); - fail = true; - } - - if (fail) - { - // Failure sound - for (std::vector::const_iterator iter (spell->mEffects.mList.begin()); - iter!=spell->mEffects.mList.end(); ++iter) - { - const ESM::MagicEffect *magicEffect = getStore().get().find ( - iter->mEffectID); - - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(actor, "Spell Failure " + schools[magicEffect->mData.mSchool], 1.0f, 1.0f); - break; - } - return; - } - - if (actor == getPlayer().getPlayer() && spell->mData.mType == ESM::Spell::ST_Spell) - actor.getClass().skillUsageSucceeded(actor, - MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(selectedSpell, actor)), 0); - - effects = spell->mEffects; + cast.cast(spell); } - InventoryStore& inv = actor.getClass().getInventoryStore(actor); - if (selectedSpell.empty() && inv.getSelectedEnchantItem() != inv.end()) + else if (inv.getSelectedEnchantItem() != inv.end()) { - MWWorld::Ptr item = *inv.getSelectedEnchantItem(); - selectedSpell = item.getClass().getEnchantment(item); - const ESM::Enchantment* enchantment = getStore().get().search (selectedSpell); - - if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) - { - // Check if there's enough charge left - const float enchantCost = enchantment->mData.mCost; - MWMechanics::NpcStats &stats = MWWorld::Class::get(actor).getNpcStats(actor); - int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); - const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); - - if (item.getCellRef().mEnchantmentCharge == -1) - item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; - - if (item.getCellRef().mEnchantmentCharge < castCost) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); - return; - } - - // Reduce charge - item.getCellRef().mEnchantmentCharge -= castCost; - } - if (enchantment->mData.mType == ESM::Enchantment::CastOnce) - { - if (!item.getContainerStore()->remove(item, 1, actor)) - { - // Item was used up - MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); - inv.setSelectedEnchantItem(inv.end()); - } - } - else - MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge - - sourceName = item.getClass().getName(item); - - effects = enchantment->mEffects; + cast.cast(*inv.getSelectedEnchantItem()); } - - // Now apply the spell! - - // Apply Self portion - actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, actor, ESM::RT_Self, sourceName); - - // Apply Touch portion - // TODO: Distance is probably incorrect, and should it be hardcoded? - std::pair contact = getHitContact(actor, 100); - if (!contact.first.isEmpty()) - { - if (contact.first.getClass().isActor()) - { - if (!contact.first.getClass().getCreatureStats(contact.first).isDead()) - contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, actor, ESM::RT_Touch, sourceName); - } - else - { - // We hit a non-actor, e.g. a door. Only instant effects are relevant. - // inflictSpellOnNonActor(contact.first, selectedSpell, ESM::RT_Touch); - } - } - - launchProjectile(selectedSpell, effects, actor, sourceName); - } void World::launchProjectile (const std::string& id, const ESM::EffectList& effects, From a42069823734b4ddd162e6e6740cccb539826a25 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 17 Nov 2013 23:34:52 +0100 Subject: [PATCH 062/142] Don't try to absorb or reflect for self casted spells --- apps/openmw/mwmechanics/spellcasting.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 00ca82c5af..1498a34aac 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -59,7 +59,8 @@ namespace MWMechanics // Try absorbing if it's a spell // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure // if that is worth replicating. - if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId)) + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); + if (spell && caster != target) { int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] @@ -78,7 +79,7 @@ namespace MWMechanics } // Try reflecting - if (!reflected && magnitudeMult > 0) + if (!reflected && magnitudeMult > 0 && caster != target) { int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] @@ -434,9 +435,8 @@ namespace MWMechanics if (roll > x) { // "X has no effect on you" - std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("#{sNotifyMessage50}")->getString(); + std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage50")->getString(); message = boost::str(boost::format(message) % ingredient->mName); - MWBase::Environment::get().getWindowManager()->messageBox(message); return false; } From 6902569d035bf87afd8b0336f9c2486b3e9ff9d4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 18 Nov 2013 04:57:54 +0100 Subject: [PATCH 063/142] Implement Absorb effects (AbsorbHealth, etc) --- apps/openmw/mwmechanics/actors.cpp | 9 ++++++--- apps/openmw/mwmechanics/spellcasting.cpp | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6d31a810aa..6c559f0946 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -155,7 +155,8 @@ namespace MWMechanics { Stat stat = creatureStats.getAttribute(i); stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).mMagnitude - - effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude); + effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).mMagnitude - + effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).mMagnitude); creatureStats.setAttribute(i, stat); } @@ -168,7 +169,8 @@ namespace MWMechanics effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude); float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude - - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude; + - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude + - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::AbsorbHealth)).mMagnitude; stat.setCurrent(stat.getCurrent() + currentDiff * duration); creatureStats.setDynamic(i, stat); @@ -198,7 +200,8 @@ namespace MWMechanics { Stat& skill = npcStats.getSkill(i); skill.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).mMagnitude - - effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).mMagnitude); + effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).mMagnitude - + effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).mMagnitude); } } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 1498a34aac..5763549cf5 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -116,7 +116,7 @@ namespace MWMechanics { float random = std::rand() / static_cast(RAND_MAX); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; - magnitude *= magnitudeMult; + magnitude *= magnitudeMult; if (target.getClass().isActor() && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) { @@ -126,6 +126,20 @@ namespace MWMechanics effect.mMagnitude = magnitude; appliedLastingEffects.push_back(effect); + + // For absorb effects, also apply the effect to the caster - but with a negative + // magnitude, since we're transfering stats from the target to the caster + for (int i=0; i<5; ++i) + { + if (effectIt->mEffectID == ESM::MagicEffect::AbsorbAttribute+i) + { + std::vector effects; + ActiveSpells::Effect effect_ = effect; + effect_.mMagnitude *= -1; + effects.push_back(effect_); + caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, effects, mSourceName); + } + } } else applyInstantEffect(mTarget, effectIt->mEffectID, magnitude); From 4559e932ae0b9d1319b6d0fc99396c819a51dfb7 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 18 Nov 2013 12:33:09 +0100 Subject: [PATCH 064/142] AI packages have priority and combat is triggered in actor.cpp using the Hostile setting in CreaturesState --- apps/openmw/mwmechanics/actors.cpp | 38 +++++++ apps/openmw/mwmechanics/aicombat.cpp | 141 +++++++++++++------------ apps/openmw/mwmechanics/aicombat.hpp | 2 + apps/openmw/mwmechanics/aipackage.hpp | 3 + apps/openmw/mwmechanics/aisequence.cpp | 68 +++--------- apps/openmw/mwmechanics/aisequence.hpp | 6 +- 6 files changed, 135 insertions(+), 123 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 03a161e86a..cbd369a814 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -22,6 +22,11 @@ #include "creaturestats.hpp" #include "movement.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" + +#include "aicombat.hpp" + namespace MWMechanics { void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) @@ -35,6 +40,39 @@ namespace MWMechanics { // AI CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + + //engage combat or not? + if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile()) + { + ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); + ESM::Position actorpos = ptr.getRefData().getPosition(); + float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) + +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) + +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); + float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1); + float disp = 100; //creatures don't have disposition, so set it to 100 by default + if(ptr.getTypeName() == typeid(ESM::NPC).name()) + { + disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); + } + bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + if( ( (fight == 100 ) + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000) + || (fight >= 80 && disp <= 40) + || (fight >= 70 && disp <= 35 && d <= 1000) + || (fight >= 60 && disp <= 30 && d <= 1000) + || (fight >= 50 && disp == 0) + || (fight >= 40 && disp <= 10 && d <= 500) ) + && LOS + ) + { + creatureStats.getAiSequence().stack(AiCombat("player")); + creatureStats.setHostile(true); + } + } + creatureStats.getAiSequence().execute (ptr,duration); // fatigue restoration diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index aeddb4781e..39a97df6c1 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -34,103 +34,110 @@ namespace MWMechanics bool AiCombat::execute (const MWWorld::Ptr& actor,float duration) { + if(!MWWorld::Class::get(actor).getCreatureStats(actor).isHostile()) return true; + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mTargetId, false); if(MWWorld::Class::get(actor).getCreatureStats(actor).getHealth().getCurrent() <= 0) return true; if(actor.getTypeName() == typeid(ESM::NPC).name()) { + MWWorld::Class::get(actor). MWWorld::Class::get(actor).setStance(actor, MWWorld::Class::Run,true); MWMechanics::DrawState_ state = MWWorld::Class::get(actor).getNpcStats(actor).getDrawState(); if (state == MWMechanics::DrawState_Spell || state == MWMechanics::DrawState_Nothing) MWWorld::Class::get(actor).getNpcStats(actor).setDrawState(MWMechanics::DrawState_Weapon); //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + } + ESM::Position pos = actor.getRefData().getPosition(); + const ESM::Pathgrid *pathgrid = + MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); - ESM::Position pos = actor.getRefData().getPosition(); - const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*actor.getCell()->mCell); + float xCell = 0; + float yCell = 0; - float xCell = 0; - float yCell = 0; + if (actor.getCell()->mCell->isExterior()) + { + xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; + yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + } - if (actor.getCell()->mCell->isExterior()) + ESM::Pathgrid::Point dest; + dest.mX = target.getRefData().getPosition().pos[0]; + dest.mY = target.getRefData().getPosition().pos[1]; + dest.mZ = target.getRefData().getPosition().pos[2]; + + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; + start.mZ = pos.pos[2]; + + mTimer2 = mTimer2 + duration; + + if(!mPathFinder.isPathConstructed()) + mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + else + { + mPathFinder2.buildPath(start, dest, pathgrid, xCell, yCell, true); + ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); + if((mTimer2 > 0.25)&&(mPathFinder2.getPathSize() < mPathFinder.getPathSize() || + (dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200)) { - xCell = actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE; - yCell = actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE; + mTimer2 = 0; + mPathFinder = mPathFinder2; } + } - ESM::Pathgrid::Point dest; - dest.mX = target.getRefData().getPosition().pos[0]; - dest.mY = target.getRefData().getPosition().pos[1]; - dest.mZ = target.getRefData().getPosition().pos[2]; + mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; - start.mZ = pos.pos[2]; + float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - mTimer2 = mTimer2 + duration; - if(!mPathFinder.isPathConstructed()) - mPathFinder.buildPath(start, dest, pathgrid, xCell, yCell, true); + float range = 100; + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); + if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) + < range*range) + { + float directionX = dest.mX - start.mX; + float directionY = dest.mY - start.mY; + float directionResult = sqrt(directionX * directionX + directionY * directionY); + + zAngle = Ogre::Radian( acos(directionY / directionResult) * sgn(asin(directionX / directionResult)) ).valueDegrees(); + MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + + mPathFinder.clearPath(); + + if(mTimer == 0) + { + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); + //mTimer = mTimer + duration; + } + if( mTimer > 1) + { + MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); + mTimer = 0; + } else { - mPathFinder2.buildPath(start, dest, pathgrid, xCell, yCell, true); - ESM::Pathgrid::Point lastPt = mPathFinder.getPath().back(); - if((mTimer2 > 0.25)&&(mPathFinder2.getPathSize() < mPathFinder.getPathSize() || - (dest.mX - lastPt.mX)*(dest.mX - lastPt.mX)+(dest.mY - lastPt.mY)*(dest.mY - lastPt.mY)+(dest.mZ - lastPt.mZ)*(dest.mZ - lastPt.mZ) > 200*200)) - { - mTimer2 = 0; - mPathFinder = mPathFinder2; - } + mTimer = mTimer + duration; } - - mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - - - float range = 100; - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); - if((dest.mX - start.mX)*(dest.mX - start.mX)+(dest.mY - start.mY)*(dest.mY - start.mY)+(dest.mZ - start.mZ)*(dest.mZ - start.mZ) - < range*range) - { - float directionX = dest.mX - start.mX; - float directionY = dest.mY - start.mY; - float directionResult = sqrt(directionX * directionX + directionY * directionY); - - zAngle = Ogre::Radian( acos(directionY / directionResult) * sgn(asin(directionX / directionResult)) ).valueDegrees(); - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - - mPathFinder.clearPath(); - - if(mTimer == 0) - { - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); - //mTimer = mTimer + duration; - } - if( mTimer > 1) - { - MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(true); - mTimer = 0; - } - else - { - mTimer = mTimer + duration; - } - - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell()); - } + MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(!MWWorld::Class::get(actor).getCreatureStats(actor).getAttackingOrSpell()); } return false; } int AiCombat::getTypeId() const { - return 2; + return 5; + } + + unsigned int AiCombat::getPriority() const + { + return 1; } AiCombat *MWMechanics::AiCombat::clone() const diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index eed92ef3a7..fa71e261fc 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -21,6 +21,8 @@ namespace MWMechanics virtual int getTypeId() const; + virtual unsigned int getPriority() const; + private: std::string mTargetId; diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 794708f227..5832198dad 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -22,6 +22,9 @@ namespace MWMechanics virtual int getTypeId() const = 0; ///< 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate + + virtual unsigned int getPriority() const {return 0;} + ///< higher number is higher priority (0 beeing the lowest) }; } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 994a7a9323..d5fb76eded 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -17,19 +17,14 @@ #include "../mwbase/world.hpp" #include "../mwworld/player.hpp" -#include "../mwbase/mechanicsmanager.hpp" - void MWMechanics::AiSequence::copy (const AiSequence& sequence) { for (std::list::const_iterator iter (sequence.mPackages.begin()); iter!=sequence.mPackages.end(); ++iter) mPackages.push_back ((*iter)->clone()); - mCombat = sequence.mCombat; - mCombatPackage = 0; - if(sequence.mCombat) mCombatPackage = sequence.mCombatPackage->clone(); } -MWMechanics::AiSequence::AiSequence() : mDone (false), mCombat (false), mCombatPackage (0) {} +MWMechanics::AiSequence::AiSequence() : mDone (false) {} MWMechanics::AiSequence::AiSequence (const AiSequence& sequence) : mDone (false) { @@ -69,48 +64,15 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration) { if(actor != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) { - if(mCombat) + if (!mPackages.empty()) { - mCombatPackage->execute(actor,duration); - } - else - { - if(actor.getTypeName() == typeid(ESM::NPC).name()) + if (mPackages.front()->execute (actor,duration)) { - ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); - ESM::Position actorpos = actor.getRefData().getPosition(); - float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) - +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) - +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); - float fight = actor.getClass().getCreatureStats(actor).getAiSetting(1); - float disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(actor); - bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - if( ( (fight == 100 ) - || (fight >= 95 && d <= 3000) - || (fight >= 90 && d <= 2000) - || (fight >= 80 && d <= 1000) - || (fight >= 80 && disp <= 40) - || (fight >= 70 && disp <= 35 && d <= 1000) - || (fight >= 60 && disp <= 30 && d <= 1000) - || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) ) - && LOS - ) - { - mCombat = true; - mCombatPackage = new AiCombat("player"); - } - } - if (!mPackages.empty()) - { - if (mPackages.front()->execute (actor,duration)) - { - mPackages.erase (mPackages.begin()); - mDone = true; - } - else - mDone = false; + mPackages.erase (mPackages.begin()); + mDone = true; } + else + mDone = false; } } } @@ -119,18 +81,20 @@ void MWMechanics::AiSequence::clear() { for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) delete *iter; - - if(mCombatPackage) - { - delete mCombatPackage; - mCombatPackage = 0; - } + mPackages.clear(); } void MWMechanics::AiSequence::stack (const AiPackage& package) { - mPackages.push_front (package.clone()); + for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); it++) + { + if(mPackages.front()->getPriority() <= package.getPriority()) + mPackages.insert(it,package.clone()); + } + + if(mPackages.empty()) + mPackages.push_front (package.clone()); } void MWMechanics::AiSequence::queue (const AiPackage& package) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index ea2a048f16..0976ef0995 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -18,8 +18,6 @@ namespace MWMechanics class AiSequence { std::list mPackages; - AiPackage* mCombatPackage; - bool mCombat; bool mDone; @@ -36,7 +34,7 @@ namespace MWMechanics virtual ~AiSequence(); int getTypeId() const; - ///< -1: None, 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate + ///< -1: None, 0: Wanter, 1 Travel, 2 Escort, 3 Follow, 4 Activate, 5 Combat bool isPackageDone() const; ///< Has a package been completed during the last update? @@ -46,7 +44,7 @@ namespace MWMechanics void clear(); ///< Remove all packages. - + void stack (const AiPackage& package); ///< Add \a package to the front of the sequence (suspends current package) From 4baaf9463e70d55213af5b82baaad3f1279118df Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Mon, 18 Nov 2013 12:34:25 +0100 Subject: [PATCH 065/142] Remove crashcatcher.cpp from windows builds, it can't be built or used there. --- apps/openmw/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a36c179968..4469de85ec 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -6,8 +6,10 @@ configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.cmake" "${CMAKE_CURRENT_ set(GAME main.cpp engine.cpp - crashcatcher.cpp ) +if(NOT WIN32) + set(GAME ${GAME} crashcatcher.cpp) +endif() set(GAME_HEADER engine.hpp config.hpp From dff3cf162dc415ee7d065b8b5f695918b9aad841 Mon Sep 17 00:00:00 2001 From: gus Date: Mon, 18 Nov 2013 23:03:44 +0100 Subject: [PATCH 066/142] ToggleAI script instruction --- apps/openmw/mwbase/mechanicsmanager.hpp | 3 + apps/openmw/mwmechanics/actors.cpp | 68 ++++++++++--------- .../mwmechanics/mechanicsmanagerimp.cpp | 12 +++- .../mwmechanics/mechanicsmanagerimp.hpp | 4 ++ apps/openmw/mwscript/aiextensions.cpp | 15 ++++ apps/openmw/mwscript/docs/vmformat.txt | 4 +- components/compiler/extensions0.cpp | 1 + components/compiler/opcodes.hpp | 2 + 8 files changed, 76 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 7e09f9b4d7..a3a60d96ec 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -114,6 +114,9 @@ namespace MWBase /// references that are currently not in the scene should be ignored. virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; + + virtual void toggleAI() = 0; + virtual bool isAIActive() = 0; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index cbd369a814..bfb0379308 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -39,42 +39,48 @@ namespace MWMechanics if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) { // AI - CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - - //engage combat or not? - if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile()) + if(MWBase::Environment::get().getMechanicsManager())//check MechanismsManager is already created { - ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); - ESM::Position actorpos = ptr.getRefData().getPosition(); - float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) - +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) - +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); - float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1); - float disp = 100; //creatures don't have disposition, so set it to 100 by default - if(ptr.getTypeName() == typeid(ESM::NPC).name()) + if(MWBase::Environment::get().getMechanicsManager()->isAIActive())//MWBase::Environment::get().getMechanicsManager()->isAIActive()) { - disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); - } - bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - if( ( (fight == 100 ) - || (fight >= 95 && d <= 3000) - || (fight >= 90 && d <= 2000) - || (fight >= 80 && d <= 1000) - || (fight >= 80 && disp <= 40) - || (fight >= 70 && disp <= 35 && d <= 1000) - || (fight >= 60 && disp <= 30 && d <= 1000) - || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) ) - && LOS - ) - { - creatureStats.getAiSequence().stack(AiCombat("player")); - creatureStats.setHostile(true); + CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(); + //engage combat or not? + if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile()) + { + ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); + ESM::Position actorpos = ptr.getRefData().getPosition(); + float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) + +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) + +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); + float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1); + float disp = 100; //creatures don't have disposition, so set it to 100 by default + if(ptr.getTypeName() == typeid(ESM::NPC).name()) + { + disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); + } + bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + if( ( (fight == 100 ) + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000) + || (fight >= 80 && disp <= 40) + || (fight >= 70 && disp <= 35 && d <= 1000) + || (fight >= 60 && disp <= 30 && d <= 1000) + || (fight >= 50 && disp == 0) + || (fight >= 40 && disp <= 10 && d <= 500) ) + && LOS + ) + { + creatureStats.getAiSequence().stack(AiCombat("player")); + creatureStats.setHostile(true); + } + } + + creatureStats.getAiSequence().execute (ptr,duration); } } - creatureStats.getAiSequence().execute (ptr,duration); - // fatigue restoration calculateRestoration(ptr, duration); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 8c13db7900..cfac6541b7 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -165,7 +165,7 @@ namespace MWMechanics MechanicsManager::MechanicsManager() : mUpdatePlayer (true), mClassSelected (false), - mRaceSelected (false) + mRaceSelected (false), mAI(true) { buildPlayer(); } @@ -679,4 +679,14 @@ namespace MWMechanics return false; } + void MechanicsManager::toggleAI() + { + mAI = !mAI; + } + + bool MechanicsManager::isAIActive() + { + return mAI; + } + } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index ad07562c7b..2cb84bd655 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -29,6 +29,7 @@ namespace MWMechanics bool mUpdatePlayer; bool mClassSelected; bool mRaceSelected; + bool mAI;///< is AI active? Objects mObjects; Actors mActors; @@ -100,6 +101,9 @@ namespace MWMechanics virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); virtual void skipAnimation(const MWWorld::Ptr& ptr); virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); + + virtual void toggleAI(); + virtual bool isAIActive(); }; } diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 39ed36ac7c..1cdbaa008d 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -25,6 +25,8 @@ #include +#include "../mwbase/mechanicsmanager.hpp" + namespace MWScript { namespace Ai @@ -390,6 +392,17 @@ namespace MWScript } }; + template + class OpToggleAI : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWBase::Environment::get().getMechanicsManager()->toggleAI(); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment3 (Compiler::Ai::opcodeAIActivate, new OpAiActivate); @@ -416,6 +429,8 @@ namespace MWScript interpreter.installSegment3 (Compiler::Ai::opcodeGetDetectedExplicit, new OpGetDetected); interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSight, new OpGetLineOfSight); interpreter.installSegment5 (Compiler::Ai::opcodeGetLineOfSightExplicit, new OpGetLineOfSight); + interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI); + interpreter.installSegment5 (Compiler::Ai::opcodeToggleAIExplicit, new OpToggleAI); interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting(0)); interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting(0)); diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 685741db6f..9e024f8723 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -356,5 +356,7 @@ op 0x2000220: DisableLevitation op 0x2000221: EnableLevitation op 0x2000222: GetLineOfSight op 0x2000223: GetLineOfSightExplicit +op 0x2000224: ToggleAI +op 0x2000225: ToggleAIExplicit -opcodes 0x2000224-0x3ffffff unused +opcodes 0x2000226-0x3ffffff unused diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 2799c90db7..01792def95 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -59,6 +59,7 @@ namespace Compiler extensions.registerInstruction ("modfight", "l", opcodeModFight, opcodeModFightExplicit); extensions.registerInstruction ("modflee", "l", opcodeModFlee, opcodeModFleeExplicit); extensions.registerInstruction ("modalarm", "l", opcodeModAlarm, opcodeModAlarmExplicit); + extensions.registerInstruction ("toggleai", "", opcodeToggleAI, opcodeToggleAI); extensions.registerFunction ("gethello", 'l', "", opcodeGetHello, opcodeGetHelloExplicit); extensions.registerFunction ("getfight", 'l', "", opcodeGetFight, opcodeGetFightExplicit); extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit); diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 582edcd33b..a885a373de 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -51,6 +51,8 @@ namespace Compiler const int opcodeGetAlarmExplicit = 0x20001c6; const int opcodeGetLineOfSight = 0x2000222; const int opcodeGetLineOfSightExplicit = 0x2000223; + const int opcodeToggleAI = 0x2000224; + const int opcodeToggleAIExplicit = 0x2000225; } namespace Animation From 16e5477c60b9e724fc758432eea915136caf511a Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 02:17:44 +0100 Subject: [PATCH 067/142] Fix an uninitalized member --- apps/openmw/mwgui/mapwindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 5ed002d7b3..93e1d11a3d 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -261,6 +261,7 @@ namespace MWGui : MWGui::WindowPinnableBase("openmw_map_window.layout") , mGlobal(false) , mGlobalMap(0) + , mGlobalMapRender(0) { setCoord(500,0,320,300); From cab535dd6918c08d60ab3350e769865d88a25585 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 06:48:47 +0100 Subject: [PATCH 068/142] Implement magic item recharging via soulgem use --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/mwbase/windowmanager.hpp | 1 + apps/openmw/mwgui/mode.hpp | 1 + apps/openmw/mwgui/recharge.cpp | 194 ++++++++++++++++++++++ apps/openmw/mwgui/recharge.hpp | 42 +++++ apps/openmw/mwgui/soulgemdialog.cpp | 3 +- apps/openmw/mwgui/windowmanagerimp.cpp | 13 ++ apps/openmw/mwgui/windowmanagerimp.hpp | 3 + files/mygui/CMakeLists.txt | 1 + files/mygui/openmw_recharge_dialog.layout | 29 ++++ 10 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 apps/openmw/mwgui/recharge.cpp create mode 100644 apps/openmw/mwgui/recharge.hpp create mode 100644 files/mygui/openmw_recharge_dialog.layout diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index a36c179968..576b28ea1a 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -36,6 +36,7 @@ add_openmw_dir (mwgui merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks keywordsearch itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel fontloader controllers savegamedialog + recharge ) add_openmw_dir (mwdialogue diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 1cd8672230..c6864de369 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -265,6 +265,7 @@ namespace MWBase virtual void showCompanionWindow(MWWorld::Ptr actor) = 0; virtual void startSpellMaking(MWWorld::Ptr actor) = 0; virtual void startEnchanting(MWWorld::Ptr actor) = 0; + virtual void startRecharge(MWWorld::Ptr soulgem) = 0; virtual void startSelfEnchanting(MWWorld::Ptr soulgem) = 0; virtual void startTraining(MWWorld::Ptr actor) = 0; virtual void startRepair(MWWorld::Ptr actor) = 0; diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index 879fcb483f..50d53abacd 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -28,6 +28,7 @@ namespace MWGui GM_Travel, GM_SpellCreation, GM_Enchanting, + GM_Recharge, GM_Training, GM_MerchantRepair, diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp new file mode 100644 index 0000000000..7af9fbcfb9 --- /dev/null +++ b/apps/openmw/mwgui/recharge.cpp @@ -0,0 +1,194 @@ +#include "recharge.hpp" + +#include +#include + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwworld/player.hpp" +#include "../mwworld/containerstore.hpp" +#include "../mwworld/class.hpp" + +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" + +#include "widgets.hpp" + +namespace MWGui +{ + +Recharge::Recharge() + : WindowBase("openmw_recharge_dialog.layout") +{ + getWidget(mBox, "Box"); + getWidget(mView, "View"); + getWidget(mGemBox, "GemBox"); + getWidget(mGemIcon, "GemIcon"); + getWidget(mChargeLabel, "ChargeLabel"); + getWidget(mCancelButton, "CancelButton"); + + mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onCancel); + + setVisible(false); +} + +void Recharge::open() +{ + center(); +} + +void Recharge::start (const MWWorld::Ptr &item) +{ + std::string path = std::string("icons\\"); + path += MWWorld::Class::get(item).getInventoryIcon(item); + int pos = path.rfind("."); + path.erase(pos); + path.append(".dds"); + mGemIcon->setImageTexture (path); + mGemIcon->setUserString("ToolTipType", "ItemPtr"); + mGemIcon->setUserData(item); + + updateView(); +} + +void Recharge::updateView() +{ + MWWorld::Ptr gem = *mGemIcon->getUserData(); + + std::string soul = gem.getCellRef().mSoul; + const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); + + mChargeLabel->setCaptionWithReplacing("#{sCharges} " + boost::lexical_cast(creature->mData.mSoul)); + + bool toolBoxVisible = (gem.getRefData().getCount() != 0); + mGemBox->setVisible(toolBoxVisible); + + bool toolBoxWasVisible = (mBox->getPosition().top != mGemBox->getPosition().top); + + if (toolBoxVisible && !toolBoxWasVisible) + { + // shrink + mBox->setPosition(mBox->getPosition() + MyGUI::IntPoint(0, mGemBox->getSize().height)); + mBox->setSize(mBox->getSize() - MyGUI::IntSize(0,mGemBox->getSize().height)); + } + else if (!toolBoxVisible && toolBoxWasVisible) + { + // expand + mBox->setPosition(MyGUI::IntPoint (mBox->getPosition().left, mGemBox->getPosition().top)); + mBox->setSize(mBox->getSize() + MyGUI::IntSize(0,mGemBox->getSize().height)); + } + + while (mView->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mView->getChildAt(0)); + + int currentY = 0; + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWWorld::ContainerStore& store = MWWorld::Class::get(player).getContainerStore(player); + for (MWWorld::ContainerStoreIterator iter (store.begin()); + iter!=store.end(); ++iter) + { + std::string enchantmentName = iter->getClass().getEnchantment(*iter); + if (enchantmentName.empty()) + continue; + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); + if (iter->getCellRef().mEnchantmentCharge >= enchantment->mData.mCharge + || iter->getCellRef().mEnchantmentCharge == -1) + continue; + + MyGUI::TextBox* text = mView->createWidget ( + "SandText", MyGUI::IntCoord(8, currentY, mView->getWidth()-8, 18), MyGUI::Align::Default); + text->setCaption(MWWorld::Class::get(*iter).getName(*iter)); + text->setNeedMouseFocus(false); + currentY += 19; + + MyGUI::ImageBox* icon = mView->createWidget ( + "ImageBox", MyGUI::IntCoord(16, currentY, 32, 32), MyGUI::Align::Default); + std::string path = std::string("icons\\"); + path += MWWorld::Class::get(*iter).getInventoryIcon(*iter); + int pos = path.rfind("."); + path.erase(pos); + path.append(".dds"); + icon->setImageTexture (path); + icon->setUserString("ToolTipType", "ItemPtr"); + icon->setUserData(*iter); + icon->eventMouseButtonClick += MyGUI::newDelegate(this, &Recharge::onItemClicked); + icon->eventMouseWheel += MyGUI::newDelegate(this, &Recharge::onMouseWheel); + + Widgets::MWDynamicStatPtr chargeWidget = mView->createWidget + ("MW_ChargeBar", MyGUI::IntCoord(72, currentY+2, 199, 20), MyGUI::Align::Default); + chargeWidget->setValue(iter->getCellRef().mEnchantmentCharge, enchantment->mData.mCharge); + chargeWidget->setNeedMouseFocus(false); + + currentY += 32 + 4; + } + mView->setCanvasSize (MyGUI::IntSize(mView->getWidth(), std::max(mView->getHeight(), currentY))); +} + +void Recharge::onCancel(MyGUI::Widget *sender) +{ + MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Recharge); +} + +void Recharge::onItemClicked(MyGUI::Widget *sender) +{ + MWWorld::Ptr gem = *mGemIcon->getUserData(); + + if (!gem.getRefData().getCount()) + return; + + MWWorld::Ptr item = *sender->getUserData(); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); + MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); + + float luckTerm = 0.1 * stats.getAttribute(ESM::Attribute::Luck).getModified(); + if (luckTerm < 1|| luckTerm > 10) + luckTerm = 1; + + float intelligenceTerm = 0.2 * stats.getAttribute(ESM::Attribute::Intelligence).getModified(); + + if (intelligenceTerm > 20) + intelligenceTerm = 20; + if (intelligenceTerm < 1) + intelligenceTerm = 1; + + float x = (npcStats.getSkill(ESM::Skill::Enchant).getModified() + intelligenceTerm + luckTerm) * stats.getFatigueTerm(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < x) + { + std::string soul = gem.getCellRef().mSoul; + const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); + + float restored = creature->mData.mSoul * (roll / x); + + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + item.getClass().getEnchantment(item)); + item.getCellRef().mEnchantmentCharge = + std::min(item.getCellRef().mEnchantmentCharge + restored, static_cast(enchantment->mData.mCharge)); + } + + gem.getContainerStore()->remove(gem, 1, player); + + if (gem.getRefData().getCount() == 0) + { + std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage51")->getString(); + message = boost::str(boost::format(message) % gem.getClass().getName(gem)); + MWBase::Environment::get().getWindowManager()->messageBox(message); + } + + updateView(); +} + +void Recharge::onMouseWheel(MyGUI::Widget* _sender, int _rel) +{ + if (mView->getViewOffset().top + _rel*0.3 > 0) + mView->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mView->setViewOffset(MyGUI::IntPoint(0, mView->getViewOffset().top + _rel*0.3)); +} + +} diff --git a/apps/openmw/mwgui/recharge.hpp b/apps/openmw/mwgui/recharge.hpp new file mode 100644 index 0000000000..2ffc5e10f8 --- /dev/null +++ b/apps/openmw/mwgui/recharge.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_MWGUI_RECHARGE_H +#define OPENMW_MWGUI_RECHARGE_H + +#include "windowbase.hpp" + +#include "../mwworld/ptr.hpp" + +namespace MWGui +{ + +class Recharge : public WindowBase +{ +public: + Recharge(); + + virtual void open(); + + void start (const MWWorld::Ptr& gem); + +protected: + MyGUI::Widget* mBox; + MyGUI::ScrollView* mView; + + MyGUI::Widget* mGemBox; + + MyGUI::ImageBox* mGemIcon; + + MyGUI::TextBox* mChargeLabel; + + MyGUI::Button* mCancelButton; + + void updateView(); + + void onItemClicked (MyGUI::Widget* sender); + void onCancel (MyGUI::Widget* sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + +}; + +} + +#endif diff --git a/apps/openmw/mwgui/soulgemdialog.cpp b/apps/openmw/mwgui/soulgemdialog.cpp index b95eec0b67..6d70c85d9d 100644 --- a/apps/openmw/mwgui/soulgemdialog.cpp +++ b/apps/openmw/mwgui/soulgemdialog.cpp @@ -21,7 +21,8 @@ namespace MWGui { if (button == 0) { - /// \todo show recharge enchanted item dialog here + MWBase::Environment::get().getWindowManager()->pushGuiMode(GM_Recharge); + MWBase::Environment::get().getWindowManager()->startRecharge(mSoulgem); } else { diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 83325de23a..b77d1a885b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -43,6 +43,7 @@ #include "waitdialog.hpp" #include "enchantingdialog.hpp" #include "trainingwindow.hpp" +#include "recharge.hpp" #include "exposedwindow.hpp" #include "cursor.hpp" #include "merchantrepair.hpp" @@ -95,6 +96,7 @@ namespace MWGui , mTrainingWindow(NULL) , mMerchantRepair(NULL) , mSoulgemDialog(NULL) + , mRecharge(NULL) , mRepair(NULL) , mCompanionWindow(NULL) , mTranslationDataStorage (translationDataStorage) @@ -197,6 +199,7 @@ namespace MWGui mDragAndDrop->mDraggedWidget = 0; mDragAndDrop->mDragAndDropWidget = dragAndDropWidget; + mRecharge = new Recharge(); mMenu = new MainMenu(w,h); mMap = new MapWindow(""); mStatsWindow = new StatsWindow(); @@ -312,6 +315,7 @@ namespace MWGui delete mSoulgemDialog; delete mSoftwareCursor; delete mCursorManager; + delete mRecharge; cleanupGarbage(); @@ -375,6 +379,7 @@ namespace MWGui mRepair->setVisible(false); mCompanionWindow->setVisible(false); mInventoryWindow->setTrading(false); + mRecharge->setVisible(false); mHud->setVisible(mHudEnabled); @@ -492,6 +497,9 @@ namespace MWGui case GM_SpellCreation: mSpellCreationDialog->setVisible(true); break; + case GM_Recharge: + mRecharge->setVisible(true); + break; case GM_Enchanting: mEnchantingDialog->setVisible(true); break; @@ -1363,4 +1371,9 @@ namespace MWGui return mLoadingScreen; } + void WindowManager::startRecharge(MWWorld::Ptr soulgem) + { + mRecharge->start(soulgem); + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index badb333a7f..965c7d6382 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -77,6 +77,7 @@ namespace MWGui class MerchantRepair; class Repair; class SoulgemDialog; + class Recharge; class CompanionWindow; class WindowManager : public MWBase::WindowManager @@ -263,6 +264,7 @@ namespace MWGui virtual void startTraining(MWWorld::Ptr actor); virtual void startRepair(MWWorld::Ptr actor); virtual void startRepairItem(MWWorld::Ptr item); + virtual void startRecharge(MWWorld::Ptr soulgem); virtual void frameStarted(float dt); @@ -313,6 +315,7 @@ namespace MWGui MerchantRepair* mMerchantRepair; SoulgemDialog* mSoulgemDialog; Repair* mRepair; + Recharge* mRecharge; CompanionWindow* mCompanionWindow; Translation::Storage& mTranslationDataStorage; diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index f79f2c3ed6..70fdf42489 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -81,6 +81,7 @@ set(MYGUI_FILES openmw_repair.layout openmw_companion_window.layout openmw_savegame_dialog.layout + openmw_recharge_dialog.layout smallbars.png DejaVuLGCSansMono.ttf markers.png diff --git a/files/mygui/openmw_recharge_dialog.layout b/files/mygui/openmw_recharge_dialog.layout new file mode 100644 index 0000000000..49e7357645 --- /dev/null +++ b/files/mygui/openmw_recharge_dialog.layout @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 74e42a2d02d1a1e37df1a2aeccacae7c77ac70de Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 06:55:53 +0100 Subject: [PATCH 069/142] Add missing skill increases for Enchant skill --- apps/openmw/mwclass/npc.cpp | 3 +++ apps/openmw/mwgui/recharge.cpp | 2 ++ apps/openmw/mwmechanics/enchanting.cpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 5c67f17afb..e9182d0941 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -471,6 +471,9 @@ namespace MWClass MWMechanics::CastSpell cast(ptr, victim); cast.cast(weapon); + + if (ptr.getRefData().getHandle() == "player") + skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3); } } } diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index 7af9fbcfb9..b700360bac 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -169,6 +169,8 @@ void Recharge::onItemClicked(MyGUI::Widget *sender) item.getClass().getEnchantment(item)); item.getCellRef().mEnchantmentCharge = std::min(item.getCellRef().mEnchantmentCharge + restored, static_cast(enchantment->mData.mCharge)); + + player.getClass().skillUsageSucceeded (player, ESM::Skill::Enchant, 0); } gem.getContainerStore()->remove(gem, 1, player); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 5d148d1106..ba53a1a725 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -72,7 +72,7 @@ namespace MWMechanics if(getEnchantChance() (RAND_MAX)*100) return false; - MWWorld::Class::get (mEnchanter).skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 1); + MWWorld::Class::get (mEnchanter).skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); } if(mCastStyle==ESM::Enchantment::ConstantEffect) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 5763549cf5..f733472063 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -303,6 +303,9 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge } + if (mCaster.getRefData().getHandle() == "player") + mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1); + inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); if (!mTarget.isEmpty()) From e8dcd747416eacd5e098609b2bf6f9c598ec9584 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 16:42:24 +0100 Subject: [PATCH 070/142] Recharge enchanted items in player's inventory over time --- apps/openmw/mwbase/mechanicsmanager.hpp | 2 ++ .../mwmechanics/mechanicsmanagerimp.cpp | 8 +++++ .../mwmechanics/mechanicsmanagerimp.hpp | 2 ++ apps/openmw/mwworld/inventorystore.cpp | 36 +++++++++++++++++++ apps/openmw/mwworld/inventorystore.hpp | 8 +++++ apps/openmw/mwworld/worldimp.cpp | 2 ++ 6 files changed, 58 insertions(+) diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 24dc569d8c..c8db203252 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -59,6 +59,8 @@ namespace MWBase /// \param paused In game type does not currently advance (this usually means some GUI /// component is up). + virtual void advanceTime (float duration) = 0; + virtual void setPlayerName (const std::string& name) = 0; ///< Set player name. diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index e1a7ac1231..8ae413b0a5 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -213,6 +213,14 @@ namespace MWMechanics mWatched = ptr; } + void MechanicsManager::advanceTime (float duration) + { + // Uses ingame time, but scaled to real time + duration /= MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + player.getClass().getInventoryStore(player).rechargeItems(duration); + } + void MechanicsManager::update(float duration, bool paused) { if(!mWatched.isEmpty()) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 42656d5abc..57309a6f09 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -63,6 +63,8 @@ namespace MWMechanics /// \param paused In game type does not currently advance (this usually means some GUI /// component is up). + virtual void advanceTime (float duration); + virtual void setPlayerName (const std::string& name); ///< Set player name. diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 6483b32b2e..349c664c3f 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -88,6 +88,8 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, autoEquip(actorPtr); } + updateRechargingItems(); + return retVal; } @@ -459,6 +461,8 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); } + updateRechargingItems(); + return retCount; } @@ -577,3 +581,35 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito } } } + +void MWWorld::InventoryStore::updateRechargingItems() +{ + mRechargingItems.clear(); + for (ContainerStoreIterator it = begin(); it != end(); ++it) + { + if (it->getClass().getEnchantment(*it) != "") + { + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find( + it->getClass().getEnchantment(*it)); + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed + || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) + mRechargingItems.push_back(std::make_pair(it, enchantment->mData.mCharge)); + } + } +} + +void MWWorld::InventoryStore::rechargeItems(float duration) +{ + for (TRechargingItems::iterator it = mRechargingItems.begin(); it != mRechargingItems.end(); ++it) + { + if (it->first->getCellRef().mEnchantmentCharge == -1 + || it->first->getCellRef().mEnchantmentCharge == it->second) + continue; + + static float fMagicItemRechargePerSecond = MWBase::Environment::get().getWorld()->getStore().get().find( + "fMagicItemRechargePerSecond")->getFloat(); + + it->first->getCellRef().mEnchantmentCharge = std::min (it->first->getCellRef().mEnchantmentCharge + fMagicItemRechargePerSecond * duration, + it->second); + } +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index f53ce8efb9..58ff50ead0 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -92,11 +92,16 @@ namespace MWWorld // selected magic item (for using enchantments of type "Cast once" or "Cast when used") ContainerStoreIterator mSelectedEnchantItem; + // (item, max charge) + typedef std::vector > TRechargingItems; + TRechargingItems mRechargingItems; + void copySlots (const InventoryStore& store); void initSlots (TSlots& slots_); void updateMagicEffects(const Ptr& actor); + void updateRechargingItems(); void fireEquipmentChangedEvent(); @@ -172,6 +177,9 @@ namespace MWWorld ///< Set a listener for various events, see \a InventoryStoreListener void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); + + void rechargeItems (float duration); + /// Restore charge on enchanted items. Note this should only be done for the player. }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 7dec848ad3..7702ea2502 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -607,6 +607,8 @@ namespace MWWorld void World::advanceTime (double hours) { + MWBase::Environment::get().getMechanicsManager()->advanceTime(hours*3600); + mWeatherManager->advanceTime (hours); hours += mGlobalVariables->getFloat ("gamehour"); From 654b7d9ba5e61bd5522383d75d8f373bb42a89a7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 16:52:26 +0100 Subject: [PATCH 071/142] Apply disease resistance manually as according to wiki --- apps/openmw/mwmechanics/spellcasting.cpp | 35 ++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index f733472063..ac5be48918 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -95,18 +95,37 @@ namespace MWMechanics } // Try resisting - if (magnitudeMult > 0 && caster.getClass().isActor()) + if (magnitudeMult > 0 && target.getClass().isActor()) { const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); - magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); - if (magnitudeMult == 0) + + if (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight) { - // Fully resisted, show message - if (target.getRefData().getHandle() == "player") - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - else - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + float x = (spell->mData.mType == ESM::Spell::ST_Disease) ? + target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).mMagnitude + : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).mMagnitude; + + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll <= x) + { + // Fully resisted, show message + if (target.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + magnitudeMult = 0; + } + } + else + { + magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); + if (magnitudeMult == 0) + { + // Fully resisted, show message + if (target.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + } } } } From c03c82c78a18aa4d38da4efc3526849e95f4883b Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 16:52:26 +0100 Subject: [PATCH 072/142] Apply disease resistance manually as according to wiki --- apps/openmw/mwmechanics/spellcasting.cpp | 52 +++++++++++------------- apps/openmw/mwmechanics/spellcasting.hpp | 2 +- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index ac5be48918..af149b3bab 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -35,6 +35,23 @@ namespace MWMechanics if (!found) return; + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); + if (spell && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight)) + { + float x = (spell->mData.mType == ESM::Spell::ST_Disease) ? + target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).mMagnitude + : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).mMagnitude; + + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll <= x) + { + // Fully resisted, show message + if (target.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + return; + } + } + ESM::EffectList reflectedEffects; std::vector appliedLastingEffects; bool firstAppliedEffect = true; @@ -59,7 +76,6 @@ namespace MWMechanics // Try absorbing if it's a spell // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure // if that is worth replicating. - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search (mId); if (spell && caster != target) { int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; @@ -97,35 +113,15 @@ namespace MWMechanics // Try resisting if (magnitudeMult > 0 && target.getClass().isActor()) { - const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (mId); - if (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight) + magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); + if (magnitudeMult == 0) { - float x = (spell->mData.mType == ESM::Spell::ST_Disease) ? - target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistCommonDisease).mMagnitude - : target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::ResistBlightDisease).mMagnitude; - - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (roll <= x) - { - // Fully resisted, show message - if (target.getRefData().getHandle() == "player") - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - magnitudeMult = 0; - } - } - else - { - magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); - if (magnitudeMult == 0) - { - // Fully resisted, show message - if (target.getRefData().getHandle() == "player") - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - else - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); - } + // Fully resisted, show message + if (target.getRefData().getHandle() == "player") + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); } } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 5e87e19933..c40567fcfa 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -140,7 +140,7 @@ namespace MWMechanics float x = (willpower + 0.1 * luck) * stats.getFatigueTerm(); // This makes spells that are easy to cast harder to resist and vice versa - if (spell != NULL) + if (spell != NULL && caster.getClass().isActor()) { float castChance = getSpellSuccessChance(spell, caster); if (castChance > 0) From f3ff2e4260d39c1b0e7a678b586e5a7f87ae3d48 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 17:33:02 +0100 Subject: [PATCH 073/142] Handle Unreflectable flag --- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index af149b3bab..859d8d6991 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -95,7 +95,7 @@ namespace MWMechanics } // Try reflecting - if (!reflected && magnitudeMult > 0 && caster != target) + if (!reflected && magnitudeMult > 0 && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) { int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] From 8b095982e930912e04e46d93803d8430d252fd2a Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 18:42:43 +0100 Subject: [PATCH 074/142] Don't auto equip for dead actors --- apps/openmw/mwworld/inventorystore.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 349c664c3f..69e06378a6 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -81,7 +81,8 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, // Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves if ((actorPtr.getRefData().getHandle() != "player") - && !(MWWorld::Class::get(actorPtr).getNpcStats(actorPtr).isWerewolf())) + && !(MWWorld::Class::get(actorPtr).getNpcStats(actorPtr).isWerewolf()) + && !actorPtr.getClass().getCreatureStats(actorPtr).isDead()) { std::string type = itemPtr.getTypeName(); if ((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name())) @@ -450,7 +451,8 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor && !(MWWorld::Class::get(actor).getNpcStats(actor).isWerewolf())) { std::string type = item.getTypeName(); - if ((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name())) + if (((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name())) + && !actor.getClass().getCreatureStats(actor).isDead()) autoEquip(actor); } From 38a82c4b0bce8530a8e692bb047b3c06c995cf99 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 18:43:21 +0100 Subject: [PATCH 075/142] Add a todo comment --- apps/openmw/mwmechanics/activespells.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 001402337c..b3f499c6b2 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -36,6 +36,9 @@ namespace MWMechanics std::vector mEffects; MWWorld::TimeStamp mTimeStamp; std::string mDisplayName; + + // TODO: To handle CASTER_LINKED flag (spell is purged when caster dies), + // we should probably store a handle to the caster here. }; typedef std::multimap TContainer; From 0b9676aaa3d0085ae12c4a30b8348013b0e225ae Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 19 Nov 2013 18:43:32 +0100 Subject: [PATCH 076/142] Fix an issue with the AI code --- apps/openmw/mwmechanics/actors.cpp | 67 ++++++++++++++---------------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 5623dd36fb..ec22c8d7f5 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -39,46 +39,42 @@ namespace MWMechanics if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) { // AI - if(MWBase::Environment::get().getMechanicsManager())//check MechanismsManager is already created + if(MWBase::Environment::get().getMechanicsManager()->isAIActive()) { - if(MWBase::Environment::get().getMechanicsManager()->isAIActive())//MWBase::Environment::get().getMechanicsManager()->isAIActive()) + CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); + //engage combat or not? + if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile()) { - CreatureStats& creatureStats = MWWorld::Class::get (ptr).getCreatureStats (ptr); - if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer()) MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(); - //engage combat or not? - if(ptr != MWBase::Environment::get().getWorld()->getPlayer().getPlayer() && !creatureStats.isHostile()) + ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); + ESM::Position actorpos = ptr.getRefData().getPosition(); + float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) + +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) + +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); + float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1); + float disp = 100; //creatures don't have disposition, so set it to 100 by default + if(ptr.getTypeName() == typeid(ESM::NPC).name()) { - ESM::Position playerpos = MWBase::Environment::get().getWorld()->getPlayer().getPlayer().getRefData().getPosition(); - ESM::Position actorpos = ptr.getRefData().getPosition(); - float d = sqrt((actorpos.pos[0] - playerpos.pos[0])*(actorpos.pos[0] - playerpos.pos[0]) - +(actorpos.pos[1] - playerpos.pos[1])*(actorpos.pos[1] - playerpos.pos[1]) - +(actorpos.pos[2] - playerpos.pos[2])*(actorpos.pos[2] - playerpos.pos[2])); - float fight = ptr.getClass().getCreatureStats(ptr).getAiSetting(1); - float disp = 100; //creatures don't have disposition, so set it to 100 by default - if(ptr.getTypeName() == typeid(ESM::NPC).name()) - { - disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); - } - bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - if( ( (fight == 100 ) - || (fight >= 95 && d <= 3000) - || (fight >= 90 && d <= 2000) - || (fight >= 80 && d <= 1000) - || (fight >= 80 && disp <= 40) - || (fight >= 70 && disp <= 35 && d <= 1000) - || (fight >= 60 && disp <= 30 && d <= 1000) - || (fight >= 50 && disp == 0) - || (fight >= 40 && disp <= 10 && d <= 500) ) - && LOS - ) - { - creatureStats.getAiSequence().stack(AiCombat("player")); - creatureStats.setHostile(true); - } + disp = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(ptr); + } + bool LOS = MWBase::Environment::get().getWorld()->getLOS(ptr,MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); + if( ( (fight == 100 ) + || (fight >= 95 && d <= 3000) + || (fight >= 90 && d <= 2000) + || (fight >= 80 && d <= 1000) + || (fight >= 80 && disp <= 40) + || (fight >= 70 && disp <= 35 && d <= 1000) + || (fight >= 60 && disp <= 30 && d <= 1000) + || (fight >= 50 && disp == 0) + || (fight >= 40 && disp <= 10 && d <= 500) ) + && LOS + ) + { + creatureStats.getAiSequence().stack(AiCombat("player")); + creatureStats.setHostile(true); } - - creatureStats.getAiSequence().execute (ptr,duration); } + + creatureStats.getAiSequence().execute (ptr,duration); } // fatigue restoration @@ -212,6 +208,7 @@ namespace MWMechanics stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude - effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude); + float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::AbsorbHealth)).mMagnitude; From 3452bd2e0b59903673509dac6eb3bfc2b8c7cceb Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 20 Nov 2013 00:07:26 +0100 Subject: [PATCH 077/142] Add glow effect for enchanted items --- apps/openmw/mwrender/animation.cpp | 60 +++++++++++++++++- apps/openmw/mwrender/animation.hpp | 7 ++- apps/openmw/mwrender/npcanimation.cpp | 23 ++++--- apps/openmw/mwrender/npcanimation.hpp | 9 ++- apps/openmw/mwrender/objects.hpp | 2 + components/esm/loadmgef.hpp | 5 +- extern/shiny/Main/Factory.hpp | 3 +- .../Platforms/Ogre/OgreTextureUnitState.cpp | 29 +++++++++ files/materials/objects.mat | 12 ++++ files/materials/objects.shader | 63 ++++++++++++++----- 10 files changed, 180 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index a29beb35c8..15df907b2f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -17,6 +17,8 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" +#include + #include "../mwmechanics/character.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwworld/class.hpp" @@ -24,6 +26,7 @@ #include "renderconst.hpp" + namespace MWRender { @@ -166,8 +169,37 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) } } +struct AddGlow +{ + Ogre::Vector3* mColor; + AddGlow(Ogre::Vector3* col) : mColor(col) {} -class VisQueueSet { + // TODO: integrate this with material controllers? + void operator()(Ogre::Entity* entity) const + { + unsigned int numsubs = entity->getNumSubEntities(); + for(unsigned int i = 0;i < numsubs;++i) + { + unsigned int numsubs = entity->getNumSubEntities(); + for(unsigned int i = 0;i < numsubs;++i) + { + Ogre::SubEntity* subEnt = entity->getSubEntity(i); + std::string newName = subEnt->getMaterialName() + "@fx"; + if (sh::Factory::getInstance().searchInstance(newName) == NULL) + { + sh::MaterialInstance* instance = + sh::Factory::getInstance().createMaterialInstance(newName, subEnt->getMaterialName()); + instance->setProperty("env_map", sh::makeProperty(new sh::BooleanValue(true))); + instance->setProperty("env_map_color", sh::makeProperty(new sh::Vector3(mColor->x, mColor->y, mColor->z))); + } + subEnt->setMaterialName(newName); + } + } + } +}; + +class VisQueueSet +{ Ogre::uint32 mVisFlags; Ogre::uint8 mSolidQueue, mTransQueue; Ogre::Real mDist; @@ -201,12 +233,16 @@ public: } }; -void Animation::setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist) +void Animation::setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist, bool enchantedGlow, Ogre::Vector3* glowColor) { std::for_each(objlist.mEntities.begin(), objlist.mEntities.end(), VisQueueSet(visflags, solidqueue, transqueue, dist)); std::for_each(objlist.mParticles.begin(), objlist.mParticles.end(), VisQueueSet(visflags, solidqueue, transqueue, dist)); + + if (enchantedGlow) + std::for_each(objlist.mEntities.begin(), objlist.mEntities.end(), + AddGlow(glowColor)); } @@ -1116,6 +1152,23 @@ void Animation::updateEffects(float duration) } } +// TODO: Should not be here +Ogre::Vector3 Animation::getEnchantmentColor(MWWorld::Ptr item) +{ + Ogre::Vector3 result(1,1,1); + std::string enchantmentName = item.getClass().getEnchantment(item); + if (enchantmentName.empty()) + return result; + const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); + assert (enchantment->mEffects.mList.size()); + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( + enchantment->mEffects.mList.front().mEffectID); + result.x = magicEffect->mData.mRed / 255.f; + result.y = magicEffect->mData.mGreen / 255.f; + result.z = magicEffect->mData.mBlue / 255.f; + return result; +} + ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model) : Animation(ptr, ptr.getRefData().getBaseNode()) @@ -1132,9 +1185,10 @@ ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &mod small = false; float dist = small ? Settings::Manager::getInt("small object distance", "Viewing distance") : 0.0f; + Ogre::Vector3 col = getEnchantmentColor(ptr); setRenderProperties(mObjectRoot, (mPtr.getTypeName() == typeid(ESM::Static).name()) ? (small ? RV_StaticsSmall : RV_Statics) : RV_Misc, - RQG_Main, RQG_Alpha, dist); + RQG_Main, RQG_Alpha, dist, !ptr.getClass().getEnchantment(ptr).empty(), &col); } void ObjectAnimation::addLight(const ESM::Light *light) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 7d1fd010ca..38e7742ef1 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -191,10 +191,15 @@ protected: static void destroyObjectList(Ogre::SceneManager *sceneMgr, NifOgre::ObjectList &objects); - static void setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, Ogre::uint8 transqueue, Ogre::Real dist=0.0f); + static void setRenderProperties(const NifOgre::ObjectList &objlist, Ogre::uint32 visflags, Ogre::uint8 solidqueue, + Ogre::uint8 transqueue, Ogre::Real dist=0.0f, + bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); void clearAnimSources(); + // TODO: Should not be here + Ogre::Vector3 getEnchantmentColor(MWWorld::Ptr item); + public: Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node); virtual ~Animation(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index bc7e48af1b..c714289416 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -208,17 +208,19 @@ void NpcAnimation::updateParts() removeIndividualPart(ESM::PRT_Hair); int prio = 1; + bool enchantedGlow = !store->getClass().getEnchantment(*store).empty(); + Ogre::Vector3 glowColor = getEnchantmentColor(*store); if(store->getTypeName() == typeid(ESM::Clothing).name()) { prio = ((slotlist[i].mBasePriority+1)<<1) + 0; const ESM::Clothing *clothes = store->get()->mBase; - addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts); + addPartGroup(slotlist[i].mSlot, prio, clothes->mParts.mParts, enchantedGlow, &glowColor); } else if(store->getTypeName() == typeid(ESM::Armor).name()) { prio = ((slotlist[i].mBasePriority+1)<<1) + 1; const ESM::Armor *armor = store->get()->mBase; - addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts); + addPartGroup(slotlist[i].mSlot, prio, armor->mParts.mParts, enchantedGlow, &glowColor); } if(slotlist[i].mSlot == MWWorld::InventoryStore::Slot_Robe) @@ -389,10 +391,11 @@ public: } }; -NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename) +NifOgre::ObjectList NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename, bool enchantedGlow, Ogre::Vector3* glowColor) { NifOgre::ObjectList objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); - setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha); + setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, + enchantedGlow, glowColor); std::for_each(objects.mEntities.begin(), objects.mEntities.end(), SetObjectGroup(group)); std::for_each(objects.mParticles.begin(), objects.mParticles.end(), SetObjectGroup(group)); @@ -475,7 +478,7 @@ void NpcAnimation::removePartGroup(int group) } } -bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh) +bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, bool enchantedGlow, Ogre::Vector3* glowColor) { if(priority <= mPartPriorities[type]) return false; @@ -484,7 +487,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g mPartslots[type] = group; mPartPriorities[type] = priority; - mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type)); + mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type), enchantedGlow, glowColor); if(mObjectParts[type].mSkelBase) { Ogre::SkeletonInstance *skel = mObjectParts[type].mSkelBase->getSkeleton(); @@ -521,7 +524,7 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g return true; } -void NpcAnimation::addPartGroup(int group, int priority, const std::vector &parts) +void NpcAnimation::addPartGroup(int group, int priority, const std::vector &parts, bool enchantedGlow, Ogre::Vector3* glowColor) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::Store &partStore = store.get(); @@ -559,7 +562,7 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectormPart, group, priority, "meshes\\"+bodypart->mModel); + addOrReplaceIndividualPart((ESM::PartReferenceType)part->mPart, group, priority, "meshes\\"+bodypart->mModel, enchantedGlow, glowColor); else reserveIndividualPart((ESM::PartReferenceType)part->mPart, group, priority); } @@ -574,8 +577,10 @@ void NpcAnimation::showWeapons(bool showWeapon) MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if(weapon != inv.end()) // special case for weapons { + Ogre::Vector3 glowColor = getEnchantmentColor(*weapon); std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon); - addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh); + addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, + mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); } } else diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index becd014370..7116b6ef1c 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -51,14 +51,17 @@ private: void updateNpcBase(); - NifOgre::ObjectList insertBoundedPart(const std::string &model, int group, const std::string &bonename); + NifOgre::ObjectList insertBoundedPart(const std::string &model, int group, const std::string &bonename, + bool enchantedGlow, Ogre::Vector3* glowColor=NULL); void removeIndividualPart(ESM::PartReferenceType type); void reserveIndividualPart(ESM::PartReferenceType type, int group, int priority); - bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh); + bool addOrReplaceIndividualPart(ESM::PartReferenceType type, int group, int priority, const std::string &mesh, + bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); void removePartGroup(int group); - void addPartGroup(int group, int priority, const std::vector &parts); + void addPartGroup(int group, int priority, const std::vector &parts, + bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); public: /** diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 165f6551d3..022752fae9 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -33,6 +33,8 @@ class Objects{ void insertBegin(const MWWorld::Ptr& ptr); + + public: Objects(OEngine::Render::OgreRenderer &renderer) : mRenderer(renderer) diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index cc9cc180ee..77056b9ec6 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -49,8 +49,9 @@ struct MagicEffect int mSchool; // SpellSchool, see defs.hpp float mBaseCost; int mFlags; - // Properties of the fired magic 'ball' I think - int mRed, mBlue, mGreen; + // Glow color for enchanted items with this effect + int mRed, mGreen, mBlue; + // Properties of the fired magic 'ball' float mSpeed, mSize, mSizeCap; }; // 36 bytes diff --git a/extern/shiny/Main/Factory.hpp b/extern/shiny/Main/Factory.hpp index 97984609ea..7d52266b55 100644 --- a/extern/shiny/Main/Factory.hpp +++ b/extern/shiny/Main/Factory.hpp @@ -259,8 +259,9 @@ namespace sh Platform* mPlatform; MaterialInstance* findInstance (const std::string& name); + public: MaterialInstance* searchInstance (const std::string& name); - + private: /// @return was anything removed? bool removeCache (const std::string& pattern); diff --git a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp index 54dda3523f..f215f4ab7d 100644 --- a/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp +++ b/extern/shiny/Platforms/Ogre/OgreTextureUnitState.cpp @@ -1,5 +1,8 @@ #include "OgreTextureUnitState.hpp" +#include +#include + #include "OgrePass.hpp" #include "OgrePlatform.hpp" #include "OgreMaterialSerializer.hpp" @@ -28,6 +31,32 @@ namespace sh setTextureName (retrieveValue(value, context).get()); return true; } + else if (name == "anim_texture2") + { + std::string val = retrieveValue(value, context).get(); + std::vector tokens; + boost::split(tokens, val, boost::is_any_of(" ")); + assert(tokens.size() == 3); + std::string texture = tokens[0]; + int frames = boost::lexical_cast(tokens[1]); + float duration = boost::lexical_cast(tokens[2]); + + std::vector frameTextures; + for (int i=0; isetAnimatedTextureName(&frameTextures[0], frames, duration); + return true; + } else if (name == "create_in_ffp") return true; // handled elsewhere diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 8f8734d629..10fb72740f 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -19,6 +19,8 @@ material openmw_objects_base alpha_rejection default transparent_sorting default polygon_mode default + env_map false + env_map_color 1 1 1 pass { @@ -33,6 +35,8 @@ material openmw_objects_base detailMapUVSet $detailMapUVSet emissiveMap $emissiveMap detailMap $detailMap + env_map $env_map + env_map_color $env_map_color } diffuse $diffuse @@ -75,6 +79,14 @@ material openmw_objects_base direct_texture $detailMap tex_coord_set $detailMapUVSet } + + texture_unit envMap + { + create_in_ffp $env_map + env_map spherical + anim_texture2 textures\magicitem\caust.dds 32 2 + colour_op add + } texture_unit shadowMap0 { diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 36f92bfd91..9fc9fceaf0 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -30,6 +30,8 @@ #define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix) +#define ENV_MAP @shPropertyBool(env_map) + #ifdef SH_VERTEX_SHADER // ------------------------------------- VERTEX --------------------------------------- @@ -61,7 +63,7 @@ shOutput(float3, tangentPassthrough) #endif -#if !VERTEX_LIGHTING +#if !VERTEX_LIGHTING || ENV_MAP shOutput(float3, normalPassthrough) #endif @@ -79,12 +81,15 @@ shOutput(float4, colourPassthrough) #endif +#if ENV_MAP || VERTEX_LIGHTING + shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix) +#endif + #if VERTEX_LIGHTING shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) - shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix) #if VERTEXCOLOR_MODE != 2 shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour) #endif @@ -125,10 +130,23 @@ UV.zw = uv1; #endif +#if ENV_MAP || VERTEX_LIGHTING + float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); +#endif + +#if ENV_MAP + float3 viewVec = normalize( shMatrixMult(worldView, shInputPosition).xyz); + + float3 r = reflect( viewVec, viewNormal ); + float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) ); + UV.z = r.x/m + 0.5; + UV.w = r.y/m + 0.5; +#endif + #if NORMAL_MAP tangentPassthrough = tangent.xyz; #endif -#if !VERTEX_LIGHTING +#if !VERTEX_LIGHTING || ENV_MAP normalPassthrough = normal.xyz; #endif #if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING @@ -173,7 +191,6 @@ #if VERTEX_LIGHTING float3 viewPos = shMatrixMult(worldView, shInputPosition).xyz; - float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); float3 lightDir; float d; @@ -242,12 +259,18 @@ shSampler2D(detailMap) #endif +#if ENV_MAP + shSampler2D(envMap) + shUniform(float3, env_map_color) @shUniformProperty3f(env_map_color, env_map_color) + shUniform(float3, cameraPosObjSpace) @shAutoConstant(cameraPosObjSpace, camera_position_object_space) +#endif + shInput(float4, UV) #if NORMAL_MAP shInput(float3, tangentPassthrough) #endif -#if !VERTEX_LIGHTING +#if !VERTEX_LIGHTING || ENV_MAP shInput(float3, normalPassthrough) #endif @@ -327,8 +350,11 @@ #endif #endif -#if NORMAL_MAP +#if !VERTEX_LIGHTING || ENV_MAP float3 normal = normalPassthrough; +#endif + +#if NORMAL_MAP float3 binormal = cross(tangentPassthrough.xyz, normal.xyz); float3x3 tbn = float3x3(tangentPassthrough.xyz, binormal, normal.xyz); @@ -420,6 +446,23 @@ shOutputColour(0) *= lightResult; #endif +#if EMISSIVE_MAP + #if @shPropertyString(emissiveMapUVSet) + shOutputColour(0).xyz += shSample(emissiveMap, UV.zw).xyz; + #else + shOutputColour(0).xyz += shSample(emissiveMap, UV.xy).xyz; + #endif +#endif + +#if ENV_MAP + // Everything looks better with fresnel + float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz); + float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0); + float envFactor = shSaturate(0.25 + 0.75 * pow(facing, 1)); + + shOutputColour(0).xyz += shSample(envMap, UV.zw).xyz * envFactor * env_map_color; +#endif + #if FOG float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); @@ -430,14 +473,6 @@ shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColour, fogValue); #endif -#endif - -#if EMISSIVE_MAP - #if @shPropertyString(emissiveMapUVSet) - shOutputColour(0).xyz += shSample(emissiveMap, UV.zw).xyz; - #else - shOutputColour(0).xyz += shSample(emissiveMap, UV.xy).xyz; - #endif #endif // prevent negative colour output (for example with negative lights) From fa639248848eb0d6a831c2c052f7cabb352f0355 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 20 Nov 2013 05:49:05 +0100 Subject: [PATCH 078/142] Simplify message box manager, should fix a random bug with boxes not disappearing --- apps/openmw/mwgui/messagebox.cpp | 71 +++++++++----------------------- apps/openmw/mwgui/messagebox.hpp | 12 +----- 2 files changed, 22 insertions(+), 61 deletions(-) diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 48d7ec1717..0a9ee01888 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -10,7 +10,6 @@ namespace MWGui MessageBoxManager::MessageBoxManager () { - // defines mMessageBoxSpeed = 0.1; mInterMessageBoxe = NULL; mStaticMessageBox = NULL; @@ -19,47 +18,26 @@ namespace MWGui void MessageBoxManager::onFrame (float frameDuration) { - std::vector::iterator it; - for(it = mTimers.begin(); it != mTimers.end();) + std::vector::iterator it; + for(it = mMessageBoxes.begin(); it != mMessageBoxes.end();) { - // if this messagebox is already deleted, remove the timer and move on - if (std::find(mMessageBoxes.begin(), mMessageBoxes.end(), it->messageBox) == mMessageBoxes.end()) + (*it)->mCurrentTime += frameDuration; + if((*it)->mCurrentTime >= (*it)->mMaxTime && *it != mStaticMessageBox) { - it = mTimers.erase(it); - continue; - } - - it->current += frameDuration; - if(it->current >= it->max) - { - it->messageBox->mMarkedToDelete = true; - - if(*mMessageBoxes.begin() == it->messageBox) // if this box is the last one - { - // collect all with mMarkedToDelete and delete them. - // and place the other messageboxes on the right position - int height = 0; - std::vector::iterator it2 = mMessageBoxes.begin(); - while(it2 != mMessageBoxes.end()) - { - if((*it2)->mMarkedToDelete) - { - delete (*it2); - it2 = mMessageBoxes.erase(it2); - } - else { - (*it2)->update(height); - height += (*it2)->getHeight(); - ++it2; - } - } - } - it = mTimers.erase(it); + delete *it; + it = mMessageBoxes.erase(it); } else - { ++it; - } + } + + float height = 0; + it = mMessageBoxes.begin(); + while(it != mMessageBoxes.end()) + { + (*it)->update(height); + height += (*it)->getHeight(); + ++it; } if(mInterMessageBoxe != NULL && mInterMessageBoxe->mMarkedToDelete) { @@ -74,14 +52,13 @@ namespace MWGui void MessageBoxManager::createMessageBox (const std::string& message, bool stat) { MessageBox *box = new MessageBox(*this, message); + box->mCurrentTime = 0; + box->mMaxTime = message.length()*mMessageBoxSpeed; if(stat) mStaticMessageBox = box; - else - removeMessageBox(message.length()*mMessageBoxSpeed, box); mMessageBoxes.push_back(box); - std::vector::iterator it; if(mMessageBoxes.size() > 3) { delete *mMessageBoxes.begin(); @@ -89,7 +66,7 @@ namespace MWGui } int height = 0; - for(it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it) + for(std::vector::iterator it = mMessageBoxes.begin(); it != mMessageBoxes.end(); ++it) { (*it)->update(height); height += (*it)->getHeight(); @@ -119,15 +96,6 @@ namespace MWGui return mInterMessageBoxe != NULL; } - void MessageBoxManager::removeMessageBox (float time, MessageBox *msgbox) - { - MessageBoxManagerTimer timer; - timer.current = 0; - timer.max = time; - timer.messageBox = msgbox; - - mTimers.insert(mTimers.end(), timer); - } bool MessageBoxManager::removeMessageBox (MessageBox *msgbox) { @@ -169,12 +137,13 @@ namespace MWGui : Layout("openmw_messagebox.layout") , mMessageBoxManager(parMessageBoxManager) , mMessage(message) + , mCurrentTime(0) + , mMaxTime(0) { // defines mFixedWidth = 300; mBottomPadding = 20; mNextBoxPadding = 20; - mMarkedToDelete = false; getWidget(mMessageWidget, "message"); diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 63840cfe2c..ce3a85ab3e 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -19,13 +19,6 @@ namespace MWGui class InteractiveMessageBox; class MessageBoxManager; class MessageBox; - - struct MessageBoxManagerTimer { - float current; - float max; - MessageBox *messageBox; - }; - class MessageBoxManager { public: @@ -36,7 +29,6 @@ namespace MWGui bool createInteractiveMessageBox (const std::string& message, const std::vector& buttons); bool isInteractiveMessageBox (); - void removeMessageBox (float time, MessageBox *msgbox); bool removeMessageBox (MessageBox *msgbox); void setMessageBoxSpeed (int speed); @@ -54,7 +46,6 @@ namespace MWGui std::vector mMessageBoxes; InteractiveMessageBox* mInterMessageBoxe; MessageBox* mStaticMessageBox; - std::vector mTimers; float mMessageBoxSpeed; int mLastButtonPressed; }; @@ -67,7 +58,8 @@ namespace MWGui int getHeight (); void update (int height); - bool mMarkedToDelete; + float mCurrentTime; + float mMaxTime; protected: MessageBoxManager& mMessageBoxManager; From 5a4bd9b202824baba15b9498aee2bfef11adfb7b Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 20 Nov 2013 06:20:16 +0100 Subject: [PATCH 079/142] Remove software cursor option, hw cursors seem to be working well enough --- apps/openmw/mwgui/cursor.cpp | 53 -------------------------- apps/openmw/mwgui/cursor.hpp | 17 --------- apps/openmw/mwgui/windowmanagerimp.cpp | 24 +++--------- apps/openmw/mwgui/windowmanagerimp.hpp | 3 -- 4 files changed, 5 insertions(+), 92 deletions(-) diff --git a/apps/openmw/mwgui/cursor.cpp b/apps/openmw/mwgui/cursor.cpp index c069eca15a..9c64f94caa 100644 --- a/apps/openmw/mwgui/cursor.cpp +++ b/apps/openmw/mwgui/cursor.cpp @@ -73,57 +73,4 @@ namespace MWGui return mSize; } - // ---------------------------------------------------------------------------------------- - - Cursor::Cursor() - { - // hide mygui's pointer since we're rendering it ourselves (because mygui's pointer doesn't support rotation) - MyGUI::PointerManager::getInstance().setVisible(false); - - MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &Cursor::onCursorChange); - - mWidget = MyGUI::Gui::getInstance().createWidget("RotatingSkin",0,0,0,0,MyGUI::Align::Default,"Pointer",""); - - onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); - } - - Cursor::~Cursor() - { - } - - void Cursor::onCursorChange(const std::string &name) - { - ResourceImageSetPointerFix* imgSetPtr = dynamic_cast( - MyGUI::PointerManager::getInstance().getByName(name)); - assert(imgSetPtr != NULL); - - MyGUI::ResourceImageSet* imgSet = imgSetPtr->getImageSet(); - - std::string texture = imgSet->getIndexInfo(0,0).texture; - - mSize = imgSetPtr->getSize(); - mHotSpot = imgSetPtr->getHotSpot(); - - int rotation = imgSetPtr->getRotation(); - - mWidget->setImageTexture(texture); - MyGUI::ISubWidget* main = mWidget->getSubWidgetMain(); - MyGUI::RotatingSkin* rotatingSubskin = main->castType(); - rotatingSubskin->setCenter(MyGUI::IntPoint(mSize.width/2,mSize.height/2)); - rotatingSubskin->setAngle(Ogre::Degree(rotation).valueRadians()); - } - - void Cursor::update() - { - MyGUI::IntPoint position = MyGUI::InputManager::getInstance().getMousePosition(); - - mWidget->setPosition(position - mHotSpot); - mWidget->setSize(mSize); - } - - void Cursor::setVisible(bool visible) - { - mWidget->setVisible(visible); - } - } diff --git a/apps/openmw/mwgui/cursor.hpp b/apps/openmw/mwgui/cursor.hpp index badf82262b..4e3eb90972 100644 --- a/apps/openmw/mwgui/cursor.hpp +++ b/apps/openmw/mwgui/cursor.hpp @@ -39,23 +39,6 @@ namespace MWGui int mRotation; // rotation in degrees }; - class Cursor - { - public: - Cursor(); - ~Cursor(); - void update (); - - void setVisible (bool visible); - - void onCursorChange (const std::string& name); - - private: - MyGUI::ImageBox* mWidget; - - MyGUI::IntSize mSize; - MyGUI::IntPoint mHotSpot; - }; } #endif diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index b77d1a885b..b5447eaa85 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -100,7 +100,6 @@ namespace MWGui , mRepair(NULL) , mCompanionWindow(NULL) , mTranslationDataStorage (translationDataStorage) - , mSoftwareCursor(NULL) , mCharGen(NULL) , mInputBlocker(NULL) , mCrosshairEnabled(Settings::Manager::getBool ("crosshair", "HUD")) @@ -128,7 +127,6 @@ namespace MWGui , mFPS(0.0f) , mTriangleCount(0) , mBatchCount(0) - , mUseHardwareCursors(Settings::Manager::getBool("hardware cursors", "GUI")) { // Set up the GUI system mGuiManager = new OEngine::GUI::MyGUIManager(mRendering->getWindow(), mRendering->getScene(), false, logpath); @@ -173,16 +171,19 @@ namespace MWGui mLoadingScreen->onResChange (w,h); //set up the hardware cursor manager - mSoftwareCursor = new Cursor(); mCursorManager = new SFO::SDLCursorManager(); MyGUI::PointerManager::getInstance().eventChangeMousePointer += MyGUI::newDelegate(this, &WindowManager::onCursorChange); MyGUI::InputManager::getInstance().eventChangeKeyFocus += MyGUI::newDelegate(this, &WindowManager::onKeyFocusChanged); - setUseHardwareCursors(mUseHardwareCursors); + mCursorManager->setEnabled(true); + onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); mCursorManager->cursorVisibilityChange(false); + + // hide mygui's pointer + MyGUI::PointerManager::getInstance().setVisible(false); } void WindowManager::initUI() @@ -313,7 +314,6 @@ namespace MWGui delete mMerchantRepair; delete mRepair; delete mSoulgemDialog; - delete mSoftwareCursor; delete mCursorManager; delete mRecharge; @@ -344,8 +344,6 @@ namespace MWGui mHud->setBatchCount(mBatchCount); mHud->update(); - - mSoftwareCursor->update(); } void WindowManager::updateVisible() @@ -879,12 +877,6 @@ namespace MWGui MWBase::Environment::get().getInputManager()->setDragDrop(dragDrop); } - void WindowManager::setUseHardwareCursors(bool use) - { - mCursorManager->setEnabled(use); - mSoftwareCursor->setVisible(!use && mCursorVisible); - } - void WindowManager::setCursorVisible(bool visible) { if(mCursorVisible == visible) @@ -892,8 +884,6 @@ namespace MWGui mCursorVisible = visible; mCursorManager->cursorVisibilityChange(visible); - - mSoftwareCursor->setVisible(!mUseHardwareCursors && visible); } void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) @@ -924,8 +914,6 @@ namespace MWGui mHud->setFpsLevel(Settings::Manager::getInt("fps", "HUD")); mToolTips->setDelay(Settings::Manager::getFloat("tooltip delay", "GUI")); - setUseHardwareCursors(Settings::Manager::getBool("hardware cursors", "GUI")); - for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it) { @@ -977,8 +965,6 @@ namespace MWGui void WindowManager::onCursorChange(const std::string &name) { - mSoftwareCursor->onCursorChange(name); - if(!mCursorManager->cursorChanged(name)) return; //the cursor manager doesn't want any more info about this cursor //See if we can get the information we need out of the cursor resource diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 965c7d6382..5231397cb6 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -369,9 +369,6 @@ namespace MWGui unsigned int mTriangleCount; unsigned int mBatchCount; - bool mUseHardwareCursors; - void setUseHardwareCursors(bool use); - /** * Called when MyGUI tries to retrieve a tag. This usually corresponds to a GMST string, * so this method will retrieve the GMST with the name \a _tag and place the result in \a _result From 862f2f0883a0083abae305e11e319e35ffcb8219 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 20 Nov 2013 10:16:51 +0100 Subject: [PATCH 080/142] added keywords for debian and switch to dyn libs in travis --- .travis.yml | 6 +++--- cmake/FindOGRE.cmake | 6 ++++++ files/opencs.desktop | 1 + files/openmw.desktop | 1 + 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index caf2e33899..0fb8bf71ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,13 +9,13 @@ before_install: - pwd - git submodule update --init --recursive - echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" - - echo "yes" | sudo apt-add-repository ppa:openmw/deps + - echo "yes" | sudo apt-add-repository ppa:openmw/openmw - sudo apt-get update -qq - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev - sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev - sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev - sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev - - sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev libunshield-dev + - sudo apt-get install -qq libbullet-dev libogre-1.8-dev libmygui-dev libsdl2-dev libunshield-dev - sudo mkdir /usr/src/gtest/build - cd /usr/src/gtest/build - sudo cmake .. -DBUILD_SHARED_LIBS=1 @@ -26,7 +26,7 @@ before_script: - cd - - mkdir build - cd build - - cmake .. -DOGRE_STATIC=1 -DMYGUI_STATIC=1 -DBOOST_STATIC=1 -DSDL2_STATIC=1 -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 + - cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 DBUILD_WITH_DPKG=1 script: - make -j4 after_script: diff --git a/cmake/FindOGRE.cmake b/cmake/FindOGRE.cmake index fb4a090c6e..6a3ffd6eda 100644 --- a/cmake/FindOGRE.cmake +++ b/cmake/FindOGRE.cmake @@ -552,3 +552,9 @@ clear_if_changed(OGRE_PREFIX_WATCH OGRE_MEDIA_DIR) find_path(OGRE_MEDIA_DIR NAMES packs/cubemapsJS.zip HINTS ${OGRE_MEDIA_SEARCH_PATH} PATHS ${OGRE_PREFIX_PATH} PATH_SUFFIXES ${OGRE_MEDIA_SEARCH_SUFFIX}) +if (OGRE_RenderSystem_GL_FOUND) +MESSAGE("-- OpenGL -- Found!") +else() +MESSAGE("-- OpenGL -- Not Found!") +endif() +MESSAGE("-- ${OGRE_RenderSystem_GL_LIBRARIES} ${OPENGL_LIBRARIES}") diff --git a/files/opencs.desktop b/files/opencs.desktop index f6aad5b097..80afa26bce 100644 --- a/files/opencs.desktop +++ b/files/opencs.desktop @@ -3,6 +3,7 @@ Type=Application Name=OpenMW Content Editor GenericName=Content Editor Comment=A replacement for the Morrowind Construction Set. +Keywords=Morrowind;Construction Set;Creation Kit Editor;Set;Kit TryExec=opencs Exec=opencs Icon=opencs diff --git a/files/openmw.desktop b/files/openmw.desktop index 118cd3bbe6..3e26018d0c 100644 --- a/files/openmw.desktop +++ b/files/openmw.desktop @@ -3,6 +3,7 @@ Type=Application Name=OpenMW Launcher GenericName=Role Playing Game Comment=An engine replacement for The Elder Scrolls III: Morrowind +Keywords=Morrowind;Reimplementation Mods;esm;bsa TryExec=omwlauncher Exec=omwlauncher Icon=openmw From 729ca1bfddfadb33921d40063c3a63d8cfd30e4a Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 20 Nov 2013 10:18:49 +0100 Subject: [PATCH 081/142] remove debug code --- cmake/FindOGRE.cmake | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cmake/FindOGRE.cmake b/cmake/FindOGRE.cmake index 6a3ffd6eda..96f93cf34a 100644 --- a/cmake/FindOGRE.cmake +++ b/cmake/FindOGRE.cmake @@ -551,10 +551,3 @@ set(OGRE_MEDIA_SEARCH_SUFFIX clear_if_changed(OGRE_PREFIX_WATCH OGRE_MEDIA_DIR) find_path(OGRE_MEDIA_DIR NAMES packs/cubemapsJS.zip HINTS ${OGRE_MEDIA_SEARCH_PATH} PATHS ${OGRE_PREFIX_PATH} PATH_SUFFIXES ${OGRE_MEDIA_SEARCH_SUFFIX}) - -if (OGRE_RenderSystem_GL_FOUND) -MESSAGE("-- OpenGL -- Found!") -else() -MESSAGE("-- OpenGL -- Not Found!") -endif() -MESSAGE("-- ${OGRE_RenderSystem_GL_LIBRARIES} ${OPENGL_LIBRARIES}") From ac38274165c78fe73193aefaa3773f25529a3798 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 20 Nov 2013 10:23:23 +0100 Subject: [PATCH 082/142] typo in travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0fb8bf71ae..eaf83f8bc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ before_script: - cd - - mkdir build - cd build - - cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 DBUILD_WITH_DPKG=1 + - cmake .. -DBUILD_WITH_CODE_COVERAGE=1 -DBUILD_UNITTESTS=1 -DBUILD_WITH_DPKG=1 script: - make -j4 after_script: From 995c01d7ba7982dace8aa37c9adbe34aa482ff24 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 20 Nov 2013 10:29:22 +0100 Subject: [PATCH 083/142] added uuid-dev which is now required during building --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index eaf83f8bc7..112cf94f16 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ before_install: - echo "yes" | sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu `lsb_release -sc` main universe restricted multiverse" - echo "yes" | sudo apt-add-repository ppa:openmw/openmw - sudo apt-get update -qq - - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev + - sudo apt-get install -qq libboost-all-dev libgtest-dev google-mock libzzip-dev uuid-dev - sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev - sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev - sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev From 7f735c2c4c099da209c677c27ef4d25cf5d94422 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 20 Nov 2013 16:05:24 +0100 Subject: [PATCH 084/142] Release and show the cursor when focus lost --- apps/openmw/mwbase/windowmanager.hpp | 3 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 11 ++--- apps/openmw/mwgui/windowmanagerimp.hpp | 2 + apps/openmw/mwinput/inputmanagerimp.cpp | 2 + extern/sdl4ogre/cursormanager.hpp | 3 -- extern/sdl4ogre/sdlcursormanager.cpp | 23 +--------- extern/sdl4ogre/sdlcursormanager.hpp | 3 -- extern/sdl4ogre/sdlinputwrapper.cpp | 56 +++++++++++++++++++------ extern/sdl4ogre/sdlinputwrapper.hpp | 10 ++++- 9 files changed, 66 insertions(+), 47 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index c6864de369..c47ad066b5 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -284,6 +284,9 @@ namespace MWBase virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0; virtual Loading::Listener* getLoadingScreen() = 0; + + /// Should the cursor be visible? + virtual bool getCursorVisible() = 0; }; } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index b5447eaa85..dc753a8fa7 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -180,7 +180,7 @@ namespace MWGui mCursorManager->setEnabled(true); onCursorChange(MyGUI::PointerManager::getInstance().getDefaultPointer()); - mCursorManager->cursorVisibilityChange(false); + SDL_ShowCursor(false); // hide mygui's pointer MyGUI::PointerManager::getInstance().setVisible(false); @@ -879,11 +879,7 @@ namespace MWGui void WindowManager::setCursorVisible(bool visible) { - if(mCursorVisible == visible) - return; - mCursorVisible = visible; - mCursorManager->cursorVisibilityChange(visible); } void WindowManager::onRetrieveTag(const MyGUI::UString& _tag, MyGUI::UString& _result) @@ -1362,4 +1358,9 @@ namespace MWGui mRecharge->start(soulgem); } + bool WindowManager::getCursorVisible() + { + return mCursorVisible; + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 5231397cb6..c5b6ff2beb 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -278,6 +278,8 @@ namespace MWGui void onSoulgemDialogButtonPressed (int button); + virtual bool getCursorVisible(); + private: bool mConsoleOnlyScripts; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 9b50be7cab..6901bcdb92 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -264,6 +264,8 @@ namespace MWInput void InputManager::update(float dt, bool loading) { + mInputManager->setMouseVisible(MWBase::Environment::get().getWindowManager()->getCursorVisible()); + mInputManager->capture(loading); // inject some fake mouse movement to force updating MyGUI's widget states // this shouldn't do any harm since we're moving back to the original position afterwards diff --git a/extern/sdl4ogre/cursormanager.hpp b/extern/sdl4ogre/cursormanager.hpp index f45c5cdc24..35ec92a706 100644 --- a/extern/sdl4ogre/cursormanager.hpp +++ b/extern/sdl4ogre/cursormanager.hpp @@ -22,9 +22,6 @@ public: /// \brief Follow up a cursorChanged() call with enough info to create an cursor. virtual void receiveCursorInfo(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) = 0; - /// \brief Tell the manager when the cursor visibility changed - virtual void cursorVisibilityChange(bool visible) = 0; - /// \brief sets whether to actively manage cursors or not virtual void setEnabled(bool enabled) = 0; }; diff --git a/extern/sdl4ogre/sdlcursormanager.cpp b/extern/sdl4ogre/sdlcursormanager.cpp index d14a9ffa09..65fb7f98bc 100644 --- a/extern/sdl4ogre/sdlcursormanager.cpp +++ b/extern/sdl4ogre/sdlcursormanager.cpp @@ -10,7 +10,6 @@ namespace SFO SDLCursorManager::SDLCursorManager() : mEnabled(false), - mCursorVisible(false), mInitialized(false) { } @@ -70,27 +69,7 @@ namespace SFO void SDLCursorManager::_setGUICursor(const std::string &name) { - if(mEnabled && mCursorVisible) - { - SDL_SetCursor(mCursorMap.find(name)->second); - _setCursorVisible(mCursorVisible); - } - } - - void SDLCursorManager::_setCursorVisible(bool visible) - { - if(!mEnabled) - return; - - SDL_ShowCursor(visible ? SDL_TRUE : SDL_FALSE); - } - - void SDLCursorManager::cursorVisibilityChange(bool visible) - { - mCursorVisible = visible; - - _setGUICursor(mCurrentCursor); - _setCursorVisible(visible); + SDL_SetCursor(mCursorMap.find(name)->second); } void SDLCursorManager::receiveCursorInfo(const std::string& name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y) diff --git a/extern/sdl4ogre/sdlcursormanager.hpp b/extern/sdl4ogre/sdlcursormanager.hpp index 8940220d41..7ba69f013e 100644 --- a/extern/sdl4ogre/sdlcursormanager.hpp +++ b/extern/sdl4ogre/sdlcursormanager.hpp @@ -19,14 +19,12 @@ namespace SFO virtual bool cursorChanged(const std::string &name); virtual void receiveCursorInfo(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); - virtual void cursorVisibilityChange(bool visible); private: void _createCursorFromResource(const std::string &name, int rotDegrees, Ogre::TexturePtr tex, Uint8 size_x, Uint8 size_y, Uint8 hotspot_x, Uint8 hotspot_y); void _putPixel(SDL_Surface *surface, int x, int y, Uint32 pixel); void _setGUICursor(const std::string& name); - void _setCursorVisible(bool visible); typedef std::map CursorMap; CursorMap mCursorMap; @@ -34,7 +32,6 @@ namespace SFO std::string mCurrentCursor; bool mEnabled; bool mInitialized; - bool mCursorVisible; }; } diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index 5c0eff1c0f..9990e828aa 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -23,7 +23,11 @@ namespace SFO mJoyListener(NULL), mKeyboardListener(NULL), mMouseListener(NULL), - mWindowListener(NULL) + mWindowListener(NULL), + mWindowHasFocus(true), + mWantGrab(false), + mWantRelative(false), + mWantMouseVisible(false) { _setupOISKeys(); } @@ -51,13 +55,16 @@ namespace SFO switch(evt.type) { case SDL_MOUSEMOTION: - //ignore this if it happened due to a warp + // Ignore this if it happened due to a warp if(!_handleWarpMotion(evt.motion)) { - mMouseListener->mouseMoved(_packageMouseMotion(evt)); + // If in relative mode, don't trigger events unless window has focus + if (!mWantRelative || mWindowHasFocus) + mMouseListener->mouseMoved(_packageMouseMotion(evt)); - //try to keep the mouse inside the window - _wrapMousePointer(evt.motion); + // Try to keep the mouse inside the window + if (mWindowHasFocus) + _wrapMousePointer(evt.motion); } break; case SDL_MOUSEWHEEL: @@ -118,11 +125,11 @@ namespace SFO switch (evt.window.event) { case SDL_WINDOWEVENT_ENTER: mMouseInWindow = true; + updateMouseSettings(); break; case SDL_WINDOWEVENT_LEAVE: mMouseInWindow = false; - SDL_SetWindowGrab(mSDLWindow, SDL_FALSE); - SDL_SetRelativeMouseMode(SDL_FALSE); + updateMouseSettings(); break; case SDL_WINDOWEVENT_SIZE_CHANGED: int w,h; @@ -149,10 +156,15 @@ namespace SFO break; case SDL_WINDOWEVENT_FOCUS_GAINED: + mWindowHasFocus = true; + updateMouseSettings(); if (mWindowListener) mWindowListener->windowFocusChange(true); + break; case SDL_WINDOWEVENT_FOCUS_LOST: + mWindowHasFocus = false; + updateMouseSettings(); if (mWindowListener) mWindowListener->windowFocusChange(false); break; @@ -193,25 +205,43 @@ namespace SFO /// \brief Locks the pointer to the window void InputWrapper::setGrabPointer(bool grab) { - mGrabPointer = grab && mMouseInWindow; - SDL_SetWindowGrab(mSDLWindow, grab && mMouseInWindow ? SDL_TRUE : SDL_FALSE); + mWantGrab = grab; + updateMouseSettings(); } /// \brief Set the mouse to relative positioning. Doesn't move the cursor /// and disables mouse acceleration. void InputWrapper::setMouseRelative(bool relative) { - if(mMouseRelative == relative && mMouseInWindow) + mWantRelative = relative; + updateMouseSettings(); + } + + void InputWrapper::setMouseVisible(bool visible) + { + mWantMouseVisible = visible; + updateMouseSettings(); + } + + void InputWrapper::updateMouseSettings() + { + mGrabPointer = mWantGrab && mMouseInWindow && mWindowHasFocus; + SDL_SetWindowGrab(mSDLWindow, mGrabPointer ? SDL_TRUE : SDL_FALSE); + + SDL_ShowCursor(mWantMouseVisible || !mWindowHasFocus); + + bool relative = mWantRelative && mMouseInWindow && mWindowHasFocus; + if(mMouseRelative == relative) return; - mMouseRelative = relative && mMouseInWindow; + mMouseRelative = relative; mWrapPointer = false; //eep, wrap the pointer manually if the input driver doesn't support //relative positioning natively - int success = SDL_SetRelativeMouseMode(relative && mMouseInWindow ? SDL_TRUE : SDL_FALSE); - if(relative && mMouseInWindow && success != 0) + int success = SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE); + if(relative && success != 0) mWrapPointer = true; //now remove all mouse events using the old setting from the queue diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index 1bd8947a0d..ca57464e92 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -28,6 +28,7 @@ namespace SFO bool isModifierHeld(SDL_Keymod mod); bool isKeyDown(SDL_Scancode key); + void setMouseVisible (bool visible); void setMouseRelative(bool relative); bool getMouseRelative() { return mMouseRelative; } void setGrabPointer(bool grab); @@ -36,6 +37,8 @@ namespace SFO void warpMouse(int x, int y); + void updateMouseSettings(); + private: void handleWindowEvent(const SDL_Event& evt); @@ -57,14 +60,19 @@ namespace SFO Uint16 mWarpX; Uint16 mWarpY; bool mWarpCompensate; - bool mMouseRelative; bool mWrapPointer; + + bool mWantMouseVisible; + bool mWantGrab; + bool mWantRelative; bool mGrabPointer; + bool mMouseRelative; Sint32 mMouseZ; Sint32 mMouseX; Sint32 mMouseY; + bool mWindowHasFocus; bool mMouseInWindow; SDL_Window* mSDLWindow; From 4ed4c1e3199e437871dbc6683be7b78852b61d6c Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 00:27:22 +0100 Subject: [PATCH 085/142] Add Vampirism and Sun Damage effects. Some fixes. --- apps/openmw/mwmechanics/actors.cpp | 40 ++++++++++------ apps/openmw/mwmechanics/actors.hpp | 2 - apps/openmw/mwmechanics/spells.cpp | 2 +- apps/openmw/mwrender/characterpreview.cpp | 12 +++-- apps/openmw/mwrender/npcanimation.cpp | 57 ++++++++++++++++++++--- 5 files changed, 84 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index ec22c8d7f5..b9a56e30cb 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -211,7 +211,7 @@ namespace MWMechanics float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude - - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::AbsorbHealth)).mMagnitude; + - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::AbsorbHealth+i)).mMagnitude; stat.setCurrent(stat.getCurrent() + currentDiff * duration); creatureStats.setDynamic(i, stat); @@ -219,16 +219,34 @@ namespace MWMechanics // Apply damage ticks int damageEffects[] = { - ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison + ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison, + ESM::MagicEffect::SunDamage }; + DynamicStat health = creatureStats.getHealth(); for (unsigned int i=0; i health = creatureStats.getHealth(); - health.setCurrent(health.getCurrent() - magnitude * duration); - creatureStats.setHealth(health); + + if (damageEffects[i] == ESM::MagicEffect::SunDamage) + { + // isInCell shouldn't be needed, but updateActor called during game start + if (!ptr.isInCell() || !ptr.getCell()->isExterior()) + continue; + float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour(); + float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); + float damageScale = 1.f - timeDiff / 7.f; + // When cloudy, the sun damage effect is halved + int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); + if (weather > 1) + damageScale *= 0.5; + health.setCurrent(health.getCurrent() - magnitude * duration * damageScale); + } + else + health.setCurrent(health.getCurrent() - magnitude * duration); + } + creatureStats.setHealth(health); } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr) @@ -324,7 +342,7 @@ namespace MWMechanics } } - Actors::Actors() : mDuration (0) {} + Actors::Actors() {} void Actors::addActor (const MWWorld::Ptr& ptr) { @@ -377,14 +395,8 @@ namespace MWMechanics void Actors::update (float duration, bool paused) { - mDuration += duration; - - //if (mDuration>=0.25) if (!paused) { - float totalDuration = mDuration; - mDuration = 0; - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) { const MWWorld::Class &cls = MWWorld::Class::get(iter->first); @@ -396,9 +408,9 @@ namespace MWMechanics if(iter->second->isDead()) iter->second->resurrect(); - updateActor(iter->first, totalDuration); + updateActor(iter->first, duration); if(iter->first.getTypeName() == typeid(ESM::NPC).name()) - updateNpc(iter->first, totalDuration, paused); + updateNpc(iter->first, duration, paused); if(!stats.isDead()) continue; diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 01a96f1993..b733c966be 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -30,8 +30,6 @@ namespace MWMechanics std::map mDeathCount; - float mDuration; - void updateNpc(const MWWorld::Ptr &ptr, float duration, bool paused); diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 5b18e2a3c5..6e7ac6f315 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -66,7 +66,7 @@ namespace MWMechanics int i=0; for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) { - effects.add (*it, iter->second[i]); + effects.add (*it, it->mMagnMin + (it->mMagnMax - it->mMagnMin) * iter->second[i]); ++i; } } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index b9818efbba..2cae306110 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -72,7 +72,6 @@ namespace MWRender mAnimation = new NpcAnimation(mCharacter, mNode, 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); - mAnimation->updateParts(); Ogre::Vector3 scale = mNode->getScale(); mCamera->setPosition(mPosition * scale); @@ -114,7 +113,6 @@ namespace MWRender delete mAnimation; mAnimation = new NpcAnimation(mCharacter, mNode, 0, true, (renderHeadOnly() ? NpcAnimation::VM_HeadOnly : NpcAnimation::VM_Normal)); - mAnimation->updateParts(); float scale=1.f; MWWorld::Class::get(mCharacter).adjustScale(mCharacter, scale); @@ -142,6 +140,9 @@ namespace MWRender void InventoryPreview::update(int sizeX, int sizeY) { + // TODO: can we avoid this. Vampire state needs to be updated. + mAnimation->rebuild(); + MWWorld::InventoryStore &inv = MWWorld::Class::get(mCharacter).getInventoryStore(mCharacter); MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); std::string groupname; @@ -176,11 +177,12 @@ namespace MWRender groupname = "inventoryhandtohand"; } - if(groupname != mCurrentAnimGroup) - { + // TODO see above + //if(groupname != mCurrentAnimGroup) + //{ mCurrentAnimGroup = groupname; mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); - } + //} MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index c714289416..093a40c8e5 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -19,6 +19,42 @@ #include "renderconst.hpp" #include "camera.hpp" +namespace +{ + +std::string getVampireHead(const std::string& race, bool female) +{ + static std::map , const ESM::BodyPart* > sVampireMapping; + + std::pair thisCombination = std::make_pair(race, int(female)); + + if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::Store &partStore = store.get(); + for(MWWorld::Store::iterator it = partStore.begin(); it != partStore.end(); ++it) + { + const ESM::BodyPart& bodypart = *it; + if (!bodypart.mData.mVampire) + continue; + if (bodypart.mData.mType != ESM::BodyPart::MT_Skin) + continue; + if (bodypart.mData.mPart != ESM::BodyPart::MP_Head) + continue; + if (female != (bodypart.mData.mFlags & ESM::BodyPart::BPF_Female)) + continue; + if (!Misc::StringUtils::ciEqual(bodypart.mRace, race)) + continue; + sVampireMapping[thisCombination] = &*it; + } + } + + assert(sVampireMapping[thisCombination]); + return "meshes\\" + sVampireMapping[thisCombination]->mModel; +} + +} + namespace MWRender { @@ -110,18 +146,23 @@ void NpcAnimation::updateNpcBase() const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Race *race = store.get().find(mNpc->mRace); - bool isWerewolf = MWWorld::Class::get(mPtr).getNpcStats(mPtr).isWerewolf(); + bool isWerewolf = mPtr.getClass().getNpcStats(mPtr).isWerewolf(); + bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).mMagnitude; - if(!isWerewolf) - { - mHeadModel = "meshes\\" + store.get().find(mNpc->mHead)->mModel; - mHairModel = "meshes\\" + store.get().find(mNpc->mHair)->mModel; - } - else + if (isWerewolf) { mHeadModel = "meshes\\" + store.get().find("WerewolfHead")->mModel; mHairModel = "meshes\\" + store.get().find("WerewolfHair")->mModel; } + else + { + if (vampire) + mHeadModel = getVampireHead(mNpc->mRace, mNpc->mFlags & ESM::NPC::Female); + else + mHeadModel = "meshes\\" + store.get().find(mNpc->mHead)->mModel; + + mHairModel = "meshes\\" + store.get().find(mNpc->mHair)->mModel; + } bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; std::string smodel = (mViewMode != VM_FirstPerson) ? @@ -270,6 +311,8 @@ void NpcAnimation::updateParts() // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination static std::map< std::pair,std::vector > sRaceMapping; + static std::map , std::vector > sVampireMapping; + static const int Flag_Female = 1<<0; static const int Flag_FirstPerson = 1<<1; From bf153e1c8e47e0b4347d63c8a22a2bb484ccebaa Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 00:41:52 +0100 Subject: [PATCH 086/142] Fix bug applying instant effects --- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 859d8d6991..9f7073d2af 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -157,7 +157,7 @@ namespace MWMechanics } } else - applyInstantEffect(mTarget, effectIt->mEffectID, magnitude); + applyInstantEffect(target, effectIt->mEffectID, magnitude); if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) { From 800a2845b06a3e96632834d00adf2ca058a17a03 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 03:39:55 +0100 Subject: [PATCH 087/142] Add Bound & Summon effects (will need some adjustments later) --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 133 ++++++++++++++++++++++ apps/openmw/mwmechanics/creaturestats.hpp | 5 + apps/openmw/mwmechanics/enchanting.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 6 files changed, 143 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 29381eb42e..8b49463080 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -241,7 +241,7 @@ namespace MWBase virtual void localRotateObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; - virtual void 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) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index b9a56e30cb..e06c96f7f2 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -12,6 +12,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/actionequip.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -27,6 +29,25 @@ #include "aicombat.hpp" +namespace +{ + +void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& ptr) +{ + if (bound) + { + MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, 1); + MWWorld::ActionEquip action(*ptr.getClass().getContainerStore(ptr).add(ref.getPtr(), ptr)); + action.execute(ptr); + } + else + { + ptr.getClass().getContainerStore(ptr).remove(item, 1, ptr); + } +} + +} + namespace MWMechanics { void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) @@ -247,6 +268,118 @@ namespace MWMechanics } creatureStats.setHealth(health); + + // TODO: dirty flag for magic effects to avoid some unnecessary work below? + + // Update bound effects + static std::map boundItemsMap; + if (boundItemsMap.empty()) + { + boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "battle_axe"; + boundItemsMap[ESM::MagicEffect::BoundBoots] = "boots"; + boundItemsMap[ESM::MagicEffect::BoundCuirass] = "cuirass"; + boundItemsMap[ESM::MagicEffect::BoundDagger] = "dagger"; + boundItemsMap[ESM::MagicEffect::BoundGloves] = "gauntlet"; // Note: needs both _left and _right variants, see below + boundItemsMap[ESM::MagicEffect::BoundHelm] = "helm"; + boundItemsMap[ESM::MagicEffect::BoundLongbow] = "longbow"; + boundItemsMap[ESM::MagicEffect::BoundLongsword] = "longsword"; + boundItemsMap[ESM::MagicEffect::BoundMace] = "mace"; + boundItemsMap[ESM::MagicEffect::BoundShield] = "shield"; + boundItemsMap[ESM::MagicEffect::BoundSpear] = "spear"; + } + + for (std::map::iterator it = boundItemsMap.begin(); it != boundItemsMap.end(); ++it) + { + bool found = creatureStats.mBoundItems.find(it->first) != creatureStats.mBoundItems.end(); + int magnitude = creatureStats.getMagicEffects().get(EffectKey(it->first)).mMagnitude; + if (found != (magnitude > 0)) + { + std::string item = "bound_" + it->second; + if (it->first == ESM::MagicEffect::BoundGloves) + { + adjustBoundItem(item + "_left", magnitude > 0, ptr); + adjustBoundItem(item + "_right", magnitude > 0, ptr); + } + else + adjustBoundItem(item, magnitude > 0, ptr); + + if (magnitude > 0) + creatureStats.mBoundItems.insert(it->first); + else + creatureStats.mBoundItems.erase(it->first); + } + } + + // Update summon effects + static std::map summonMap; + if (summonMap.empty()) + { + summonMap[ESM::MagicEffect::SummonAncestralGhost] = "ancestor_ghost_summon"; + summonMap[ESM::MagicEffect::SummonBear] = "BM_bear_black_summon"; + summonMap[ESM::MagicEffect::SummonBonelord] = "bonelord_summon"; + summonMap[ESM::MagicEffect::SummonBonewalker] = "bonewalker_summon"; + summonMap[ESM::MagicEffect::SummonBonewolf] = "BM_wolf_bone_summon"; + summonMap[ESM::MagicEffect::SummonCenturionSphere] = "centurion_sphere_summon"; + summonMap[ESM::MagicEffect::SummonClannfear] = "clannfear_summon"; + summonMap[ESM::MagicEffect::SummonDaedroth] = "daedroth_summon"; + summonMap[ESM::MagicEffect::SummonDremora] = "dremora_summon"; + summonMap[ESM::MagicEffect::SummonFabricant] = "fabricant_summon"; + summonMap[ESM::MagicEffect::SummonFlameAtronach] = "atronach_flame_summon"; + summonMap[ESM::MagicEffect::SummonFrostAtronach] = "atronach_frost_summon"; + summonMap[ESM::MagicEffect::SummonGoldenSaint] = "golden saint_summon"; + summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "bonewalker_greater_summ"; + summonMap[ESM::MagicEffect::SummonHunger] = "hunger_summon"; + summonMap[ESM::MagicEffect::SummonScamp] = "scamp_summon"; + summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "skeleton_summon"; + summonMap[ESM::MagicEffect::SummonStormAtronach] = "atronach_storm_summon"; + summonMap[ESM::MagicEffect::SummonWingedTwilight] = "winged twilight_summon"; + summonMap[ESM::MagicEffect::SummonWolf] = "BM_wolf_grey_summon"; + } + + for (std::map::iterator it = summonMap.begin(); it != summonMap.end(); ++it) + { + bool found = creatureStats.mSummonedCreatures.find(it->first) != creatureStats.mSummonedCreatures.end(); + int magnitude = creatureStats.getMagicEffects().get(EffectKey(it->first)).mMagnitude; + if (found != (magnitude > 0)) + { + if (magnitude > 0) + { + ESM::Position ipos = ptr.getRefData().getPosition(); + Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]); + Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); + const float distance = 50; + pos = pos + distance*rot.yAxis(); + ipos.pos[0] = pos.x; + ipos.pos[1] = pos.y; + ipos.pos[2] = pos.z; + ipos.rot[0] = 0; + ipos.rot[1] = 0; + ipos.rot[2] = 0; + + MWWorld::CellStore* store = ptr.getCell(); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->second, 1); + ref.getPtr().getCellRef().mPos = ipos; + + // TODO: Add AI to follow player and fight for him + + creatureStats.mSummonedCreatures.insert(std::make_pair(it->first, + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),*store,ipos).getRefData().getHandle())); + + } + else + { + std::string handle = creatureStats.mSummonedCreatures[it->first]; + // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation + // plays though, which is a rather lame exploit in vanilla. + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle(handle); + if (!ptr.isEmpty()) + { + MWBase::Environment::get().getWorld()->deleteObject(ptr); + creatureStats.mSummonedCreatures.erase(it->first); + } + } + } + } } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 126b0685f5..f28f50fc67 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -171,6 +171,11 @@ namespace MWMechanics void setLastHitObject(const std::string &objectid); const std::string &getLastHitObject() const; + + // Note, this is just a cache to avoid checking the whole container store every frame TODO: Put it somewhere else? + std::set mBoundItems; + // Same as above + std::map mSummonedCreatures; }; } diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index ba53a1a725..fda4d726eb 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -92,8 +92,8 @@ namespace MWMechanics MWWorld::Class::get(newItemPtr).applyEnchantment(newItemPtr, enchantmentPtr->mId, getGemCharge(), mNewItemName); // Add the new item to player inventory and remove the old one - store.add(newItemPtr, player); store.remove(mOldItemPtr, 1, player); + store.add(newItemPtr, player); if(!mSelfEnchanting) payForEnchantment(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 33cc03f9f3..19ed2079ed 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1084,9 +1084,9 @@ namespace MWWorld adjust); } - void 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) { - copyObjectToCell(ptr,Cell,pos); + return copyObjectToCell(ptr,Cell,pos); } void World::indexToPosition (int cellX, int cellY, float &x, float &y, bool centre) const diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 7ccea25029..1022a74fee 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -302,7 +302,7 @@ namespace MWWorld virtual void localRotateObject (const Ptr& ptr, float x, float y, float z); - virtual void 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) From 6641fd463512a7b8b09cb7ef196d1e82e990fd06 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 03:51:02 +0100 Subject: [PATCH 088/142] Fix bug when enchanting: only the mBase pointer was updated, not the CellRef mRefID, which is used for container stacking. The new (enchanted) item was stacking with the old item when it was added, so the enchantment completely disappears. --- apps/openmw/mwclass/armor.cpp | 1 + apps/openmw/mwclass/book.cpp | 1 + apps/openmw/mwclass/clothing.cpp | 1 + apps/openmw/mwclass/weapon.cpp | 19 ++++++++++--------- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index c8e09d4333..f916c2fb73 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -281,6 +281,7 @@ namespace MWClass newItem.mEnchant=enchId; const ESM::Armor *record = MWBase::Environment::get().getWorld()->createRecord (newItem); ref->mBase = record; + ref->mRef.mRefID = record->mId; } std::pair Armor::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index a692b30d8f..b22cbc31fc 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -171,6 +171,7 @@ namespace MWClass newItem.mEnchant=enchId; const ESM::Book *record = MWBase::Environment::get().getWorld()->createRecord (newItem); ref->mBase = record; + ref->mRef.mRefID = record->mId; } boost::shared_ptr Book::use (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 0a23821a92..8941f36275 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -227,6 +227,7 @@ namespace MWClass newItem.mEnchant=enchId; const ESM::Clothing *record = MWBase::Environment::get().getWorld()->createRecord (newItem); ref->mBase = record; + ref->mRef.mRefID = record->mId; } std::pair Clothing::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index eaed597fcf..671e81dca0 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -370,16 +370,17 @@ namespace MWClass void Weapon::applyEnchantment(const MWWorld::Ptr &ptr, const std::string& enchId, int enchCharge, const std::string& newName) const { - MWWorld::LiveCellRef *ref = - ptr.get(); + MWWorld::LiveCellRef *ref = + ptr.get(); - ESM::Weapon newItem = *ref->mBase; - newItem.mId=""; - newItem.mName=newName; - newItem.mData.mEnchant=enchCharge; - newItem.mEnchant=enchId; - const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem); - ref->mBase = record; + ESM::Weapon newItem = *ref->mBase; + newItem.mId=""; + newItem.mName=newName; + newItem.mData.mEnchant=enchCharge; + newItem.mEnchant=enchId; + const ESM::Weapon *record = MWBase::Environment::get().getWorld()->createRecord (newItem); + ref->mBase = record; + ref->mRef.mRefID = record->mId; } std::pair Weapon::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const From bab657fe2ba20e0f5a1f87be267508e1842a9d7d Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 04:11:06 +0100 Subject: [PATCH 089/142] Add a utility function to add items to a ContainerStore by RefID --- apps/openmw/mwgui/tradewindow.cpp | 3 +-- apps/openmw/mwmechanics/actors.cpp | 10 +++++----- apps/openmw/mwmechanics/alchemy.cpp | 3 +-- apps/openmw/mwmechanics/enchanting.cpp | 5 +---- apps/openmw/mwscript/containerextensions.cpp | 10 ++++------ apps/openmw/mwscript/miscextensions.cpp | 9 ++------- apps/openmw/mwworld/containerstore.cpp | 6 ++++++ apps/openmw/mwworld/containerstore.hpp | 3 +++ 8 files changed, 23 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 65e3917ed5..c1b1ff3b42 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -210,8 +210,7 @@ namespace MWGui if (amount > 0) { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), "Gold_001", amount); - playerStore.add(ref.getPtr(), player); + playerStore.add("gold_001", amount, player); } else { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e06c96f7f2..5aa846118b 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -32,17 +32,17 @@ namespace { -void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& ptr) +void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& actor) { if (bound) { - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, 1); - MWWorld::ActionEquip action(*ptr.getClass().getContainerStore(ptr).add(ref.getPtr(), ptr)); - action.execute(ptr); + MWWorld::Ptr newPtr = *actor.getClass().getContainerStore(actor).add(item, 1, actor); + MWWorld::ActionEquip action(newPtr); + action.execute(actor); } else { - ptr.getClass().getContainerStore(ptr).remove(item, 1, ptr); + actor.getClass().getContainerStore(actor).remove(item, 1, actor); } } diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 82580ce0e1..f994c28b84 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -287,8 +287,7 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) record = MWBase::Environment::get().getWorld()->createRecord (newRecord); } - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), record->mId); - MWWorld::Class::get (mAlchemist).getContainerStore (mAlchemist).add (ref.getPtr(), mAlchemist); + mAlchemist.getClass().getContainerStore (mAlchemist).add (record->mId, 1, mAlchemist); } void MWMechanics::Alchemy::increaseSkill() diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index fda4d726eb..7e11acdb0c 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -62,10 +62,7 @@ namespace MWMechanics //Exception for Azura Star, new one will be added after enchanting if(boost::iequals(mSoulGemPtr.get()->mBase->mId, "Misc_SoulGem_Azura")) - { - MWWorld::ManualRef azura (MWBase::Environment::get().getWorld()->getStore(), "Misc_SoulGem_Azura"); - store.add(azura.getPtr(), player); - } + store.add("Misc_SoulGem_Azura", 1, player); if(mSelfEnchanting) { diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index d124eca489..9636e8a628 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -17,7 +17,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/actionequip.hpp" @@ -53,24 +52,23 @@ namespace MWScript if (count == 0) return; - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), item, count); + MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore (ptr).add (item, count, ptr); // Configure item's script variables - std::string script = MWWorld::Class::get(ref.getPtr()).getScript(ref.getPtr()); + std::string script = MWWorld::Class::get(itemPtr).getScript(itemPtr); if (script != "") { const ESM::Script *esmscript = MWBase::Environment::get().getWorld()->getStore().get().find (script); - ref.getPtr().getRefData().setLocals(*esmscript); + itemPtr.getRefData().setLocals(*esmscript); } - MWWorld::Class::get (ptr).getContainerStore (ptr).add (ref.getPtr(), ptr); // Spawn a messagebox (only for items added to player's inventory and if player is talking to someone) if (ptr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer() ) { // The two GMST entries below expand to strings informing the player of what, and how many of it has been added to their inventory std::string msgBox; - std::string itemName = MWWorld::Class::get(ref.getPtr()).getName(ref.getPtr()); + std::string itemName = itemPtr.getClass().getName(itemPtr); if (count == 1) { msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage60}"); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 35f7a40447..8e2a8af8c4 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -19,7 +19,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" -#include "../mwworld/manualref.hpp" #include "../mwworld/containerstore.hpp" #include "../mwmechanics/npcstats.hpp" @@ -348,12 +347,8 @@ namespace MWScript const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); store.get().find(creature); // This line throws an exception if it can't find the creature - MWWorld::ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), gem, 1); - - ref.getPtr().getCellRef().mSoul = creature; - - MWWorld::Class::get (ptr).getContainerStore (ptr).add (ref.getPtr(), ptr); - + MWWorld::Ptr item = *ptr.getClass().getContainerStore(ptr).add(gem, 1, ptr); + item.getCellRef().mSoul = creature; } }; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index be2e0b5a3d..d1d16ee01d 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -118,6 +118,12 @@ bool MWWorld::ContainerStore::stacks(const Ptr& ptr1, const Ptr& ptr2) || cls2.getItemMaxHealth(ptr2) == ptr2.getCellRef().mCharge); } +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string &id, int count, const Ptr &actorPtr) +{ + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id, count); + return add(ref.getPtr(), actorPtr); +} + MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, const Ptr& actorPtr) { MWWorld::ContainerStoreIterator it = addImp(itemPtr); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index c430b4bfc5..df7168dfa1 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -74,6 +74,9 @@ namespace MWWorld /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. + ContainerStoreIterator add(const std::string& id, int count, const Ptr& actorPtr); + ///< Utility to construct a ManualRef and call add(ptr, actorPtr) + int remove(const std::string& itemId, int count, const Ptr& actor); ///< Remove \a count item(s) designated by \a itemId from this container. /// From b6c22ad5d9ecae5ee3e3c3f215a8cbf053145eea Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 04:27:53 +0100 Subject: [PATCH 090/142] Add starting gold for NPCs and creatures. Refactor gold removal in some gui windows (use containerstore method instead of a dependency on TradeWindow). Use real gold amount in trade window, not refill amount. --- apps/openmw/mwclass/creature.cpp | 2 ++ apps/openmw/mwclass/npc.cpp | 7 +++++ apps/openmw/mwgui/dialogue.cpp | 9 ++++-- apps/openmw/mwgui/merchantrepair.cpp | 5 +-- apps/openmw/mwgui/spellbuyingwindow.cpp | 4 +-- apps/openmw/mwgui/spellcreationdialog.cpp | 7 +++-- apps/openmw/mwgui/tradewindow.cpp | 38 ++++++++++------------- apps/openmw/mwgui/tradewindow.hpp | 2 +- apps/openmw/mwgui/trainingwindow.cpp | 4 +-- apps/openmw/mwgui/travelwindow.cpp | 8 +++-- 10 files changed, 48 insertions(+), 38 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 20f95ab0e9..9834807821 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -98,6 +98,8 @@ namespace MWClass data->mContainerStore.fill(ref->mBase->mInventory, getId(ptr), MWBase::Environment::get().getWorld()->getStore()); + data->mContainerStore.add("gold_001", ref->mBase->mData.mGold, ptr); + // store ptr.getRefData().setCustomData (data.release()); } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index e9182d0941..6970e8646e 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -184,8 +184,11 @@ namespace MWClass } // creature stats + int gold=0; if(ref->mBase->mNpdt52.mGold != -10) { + gold = ref->mBase->mNpdt52.mGold; + for (int i=0; i<27; ++i) data->mNpcStats.getSkill (i).setBase (ref->mBase->mNpdt52.mSkills[i]); @@ -207,6 +210,8 @@ namespace MWClass } else { + gold = ref->mBase->mNpdt12.mGold; + for (int i=0; i<3; ++i) data->mNpcStats.setDynamic (i, 10); @@ -236,6 +241,8 @@ namespace MWClass // store ptr.getRefData().setCustomData (data.release()); + getContainerStore(ptr).add("gold_001", gold, ptr); + getInventoryStore(ptr).autoEquip(ptr); } } diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index c9a7806918..71995f97fd 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -12,6 +12,8 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/player.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwdialogue/dialoguemanagerimp.hpp" @@ -67,23 +69,24 @@ namespace MWGui void PersuasionDialog::onPersuade(MyGUI::Widget *sender) { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWBase::MechanicsManager::PersuasionType type; if (sender == mAdmireButton) type = MWBase::MechanicsManager::PT_Admire; else if (sender == mIntimidateButton) type = MWBase::MechanicsManager::PT_Intimidate; else if (sender == mTauntButton) type = MWBase::MechanicsManager::PT_Taunt; else if (sender == mBribe10Button) { - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-10); + player.getClass().getContainerStore(player).remove("gold_001", 10, player); type = MWBase::MechanicsManager::PT_Bribe10; } else if (sender == mBribe100Button) { - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-100); + player.getClass().getContainerStore(player).remove("gold_001", 100, player); type = MWBase::MechanicsManager::PT_Bribe100; } else /*if (sender == mBribe1000Button)*/ { - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-1000); + player.getClass().getContainerStore(player).remove("gold_001", 1000, player); type = MWBase::MechanicsManager::PT_Bribe1000; } diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index 530594ddaa..4da1668209 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -13,7 +13,6 @@ #include "../mwworld/containerstore.hpp" #include "inventorywindow.hpp" -#include "tradewindow.hpp" namespace MWGui { @@ -119,7 +118,9 @@ void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1); int price = boost::lexical_cast(sender->getUserString("Price")); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + player.getClass().getContainerStore(player).remove("gold_001", price, player); startRepair(mActor); } diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index a7fcfdd021..bbd28b2de6 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -10,11 +10,11 @@ #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "inventorywindow.hpp" -#include "tradewindow.hpp" namespace MWGui { @@ -123,7 +123,7 @@ namespace MWGui MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); spells.add (mSpellsWidgetMap.find(_sender)->second); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price); + player.getClass().getContainerStore(player).remove("gold_001", price, player); startSpellBuying(mPtr); MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index e0b808b283..b9324fea17 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -8,13 +8,13 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwmechanics/spellcasting.hpp" #include "tooltips.hpp" #include "class.hpp" #include "inventorywindow.hpp" -#include "tradewindow.hpp" namespace { @@ -342,13 +342,14 @@ namespace MWGui mSpell.mName = mNameEdit->getCaption(); - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-boost::lexical_cast(mPriceLabel->getCaption())); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + player.getClass().getContainerStore(player).remove("gold_001", boost::lexical_cast(mPriceLabel->getCaption()), player); MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->createRecord(mSpell); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); spells.add (spell->mId); diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c1b1ff3b42..636b8ae9b6 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -203,18 +203,17 @@ namespace MWGui sellToNpc(item.mBase, count, true); } - void TradeWindow::addOrRemoveGold(int amount) + void TradeWindow::addOrRemoveGold(int amount, const MWWorld::Ptr& actor) { - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - MWWorld::ContainerStore& playerStore = MWWorld::Class::get(player).getContainerStore(player); + MWWorld::ContainerStore& store = MWWorld::Class::get(actor).getContainerStore(actor); if (amount > 0) { - playerStore.add("gold_001", amount, player); + store.add("gold_001", amount, actor); } else { - playerStore.remove("gold_001", - amount, player); + store.remove("gold_001", - amount, actor); } } @@ -269,6 +268,8 @@ namespace MWGui return; } + MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + if(mCurrentBalance > mCurrentMerchantOffer) { //if npc is a creature: reject (no haggle) @@ -291,7 +292,6 @@ namespace MWGui + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange()),100)); const MWMechanics::NpcStats &sellerStats = MWWorld::Class::get(mPtr).getNpcStats(mPtr); - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); float a1 = std::min(playerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100.f); @@ -331,9 +331,12 @@ namespace MWGui mTradeModel->transferItems(); playerItemModel->transferItems(); - // add or remove gold from the player. + // transfer the gold if (mCurrentBalance != 0) - addOrRemoveGold(mCurrentBalance); + { + addOrRemoveGold(mCurrentBalance, playerPtr); + addOrRemoveGold(-mCurrentBalance, mPtr); + } std::string sound = "Item Gold Up"; MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); @@ -434,22 +437,13 @@ namespace MWGui int TradeWindow::getMerchantGold() { - int merchantGold; - - if (mPtr.getTypeName() == typeid(ESM::NPC).name()) + int merchantGold = 0; + MWWorld::ContainerStore store = mPtr.getClass().getContainerStore(mPtr); + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { - MWWorld::LiveCellRef* ref = mPtr.get(); - if (ref->mBase->mNpdt52.mGold == -10) - merchantGold = ref->mBase->mNpdt12.mGold; - else - merchantGold = ref->mBase->mNpdt52.mGold; + if (Misc::StringUtils::ciEqual(it->getCellRef().mRefID, "gold_001")) + merchantGold += it->getRefData().getCount(); } - else // ESM::Creature - { - MWWorld::LiveCellRef* ref = mPtr.get(); - merchantGold = ref->mBase->mData.mGold; - } - return merchantGold; } } diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 4e905915a0..7c11bd5394 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -28,7 +28,7 @@ namespace MWGui void startTrade(const MWWorld::Ptr& actor); - void addOrRemoveGold(int gold); + void addOrRemoveGold(int gold, const MWWorld::Ptr& actor); void onFrame(float frameDuration); diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 7ddac38f54..04eddcb173 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -11,11 +11,11 @@ #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "../mwmechanics/npcstats.hpp" #include "inventorywindow.hpp" -#include "tradewindow.hpp" #include "tooltips.hpp" namespace MWGui @@ -142,7 +142,7 @@ namespace MWGui pcStats.increaseSkill (skillId, *class_, true); // remove gold - MWBase::Environment::get().getWindowManager()->getTradeWindow()->addOrRemoveGold(-price); + player.getClass().getContainerStore(player).remove("gold_001", price, player); // go back to game mode MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Training); diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 93ac8299d9..dd5da4522f 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -11,9 +11,9 @@ #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/containerstore.hpp" #include "inventorywindow.hpp" -#include "tradewindow.hpp" namespace MWGui { @@ -121,13 +121,15 @@ namespace MWGui int price; iss >> price; + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + if (MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getPlayerGold()getTradeWindow ()->addOrRemoveGold (-price); + + player.getClass().getContainerStore(player).remove("gold_001", price, player); MWBase::Environment::get().getWorld ()->getFader ()->fadeOut(1); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); ESM::Position pos = *_sender->getUserData(); std::string cellname = _sender->getUserString("Destination"); int x,y; From 6451b687d9939831c79bc5e42f0dd74d0cb0999a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 04:45:40 +0100 Subject: [PATCH 091/142] Adjust value for several item types as according to wiki --- apps/openmw/mwclass/armor.cpp | 5 ++++- apps/openmw/mwclass/lockpick.cpp | 5 ++++- apps/openmw/mwclass/probe.cpp | 5 ++++- apps/openmw/mwclass/repair.cpp | 5 ++++- apps/openmw/mwclass/weapon.cpp | 5 ++++- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index f916c2fb73..f3f36542a1 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -169,7 +169,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); } void Armor::registerSelf() diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index aff36ba81d..73b47d6af9 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -86,7 +86,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); } void Lockpick::registerSelf() diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 5cff140a65..845c2a0d00 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -85,7 +85,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); } void Probe::registerSelf() diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 38c15ac92e..dbfa9f0f62 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -76,7 +76,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); } void Repair::registerSelf() diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 671e81dca0..b1bf2b0b7f 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -154,7 +154,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mValue; + if (ptr.getCellRef().mCharge == -1) + return ref->mBase->mData.mValue; + else + return ref->mBase->mData.mValue * (ptr.getCellRef().mCharge / getItemMaxHealth(ptr)); } void Weapon::registerSelf() From 320ba98097d4ef6e79117419e08e0dc1d2440d9e Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 04:53:53 +0100 Subject: [PATCH 092/142] Correct getDerivedDisposition according to wiki (check if player expelled) --- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 8acc9866a8..ff13841a2f 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -419,7 +419,7 @@ namespace MWMechanics MWWorld::LiveCellRef* player = playerPtr.get(); const MWMechanics::NpcStats &playerStats = MWWorld::Class::get(playerPtr).getNpcStats(playerPtr); - if (Misc::StringUtils::lowerCase(npc->mBase->mRace) == Misc::StringUtils::lowerCase(player->mBase->mRace)) + if (Misc::StringUtils::ciEqual(npc->mBase->mRace, player->mBase->mRace)) x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispRaceMod")->getFloat(); x += MWBase::Environment::get().getWorld()->getStore().get().find("fDispPersonalityMult")->getFloat() @@ -435,7 +435,9 @@ namespace MWMechanics for(std::vector::const_iterator it = MWBase::Environment::get().getWorld()->getStore().get().find(Misc::StringUtils::lowerCase(npcFaction))->mReactions.begin(); it != MWBase::Environment::get().getWorld()->getStore().get().find(Misc::StringUtils::lowerCase(npcFaction))->mReactions.end(); ++it) { - if(Misc::StringUtils::lowerCase(it->mFaction) == Misc::StringUtils::lowerCase(npcFaction)) reaction = it->mReaction; + if(Misc::StringUtils::lowerCase(it->mFaction) == Misc::StringUtils::lowerCase(npcFaction) + && playerStats.getExpelled().find(Misc::StringUtils::lowerCase(it->mFaction)) == playerStats.getExpelled().end()) + reaction = it->mReaction; } rank = playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(npcFaction))->second; } @@ -446,7 +448,8 @@ namespace MWMechanics { if(playerStats.getFactionRanks().find(Misc::StringUtils::lowerCase(it->mFaction)) != playerStats.getFactionRanks().end() ) { - if(it->mReactionmReaction; + if(it->mReaction < reaction) + reaction = it->mReaction; } } rank = 0; From 61ab6e1739c504f2dd42cc155c2e65c0f94f1f1c Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 05:01:55 +0100 Subject: [PATCH 093/142] Use fWortChanceValue for making ingredient/potion effects visible --- apps/openmw/mwclass/ingredient.cpp | 11 +++++++---- apps/openmw/mwclass/potion.cpp | 11 ++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index f629cc15d1..06d9d5d235 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -156,6 +156,9 @@ namespace MWClass MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player); int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); + static const float fWortChanceValue = + MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); + MWGui::Widgets::SpellEffectList list; for (int i=0; i<4; ++i) { @@ -166,10 +169,10 @@ namespace MWClass params.mAttribute = ref->mBase->mData.mAttributes[i]; params.mSkill = ref->mBase->mData.mSkills[i]; - params.mKnown = ( (i == 0 && alchemySkill >= 15) - || (i == 1 && alchemySkill >= 30) - || (i == 2 && alchemySkill >= 45) - || (i == 3 && alchemySkill >= 60)); + params.mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue) + || (i == 1 && alchemySkill >= fWortChanceValue*2) + || (i == 2 && alchemySkill >= fWortChanceValue*3) + || (i == 3 && alchemySkill >= fWortChanceValue*4)); list.push_back(params); } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 2f9e63d138..883473eb33 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -137,13 +137,14 @@ namespace MWClass MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats (player); int alchemySkill = npcStats.getSkill (ESM::Skill::Alchemy).getBase(); int i=0; + static const float fWortChanceValue = + MWBase::Environment::get().getWorld()->getStore().get().find("fWortChanceValue")->getFloat(); for (MWGui::Widgets::SpellEffectList::iterator it = info.effects.begin(); it != info.effects.end(); ++it) { - /// \todo this code is duplicated from mwclass/ingredient, put it in a helper function - it->mKnown = ( (i == 0 && alchemySkill >= 15) - || (i == 1 && alchemySkill >= 30) - || (i == 2 && alchemySkill >= 45) - || (i == 3 && alchemySkill >= 60)); + it->mKnown = ( (i == 0 && alchemySkill >= fWortChanceValue) + || (i == 1 && alchemySkill >= fWortChanceValue*2) + || (i == 2 && alchemySkill >= fWortChanceValue*3) + || (i == 3 && alchemySkill >= fWortChanceValue*4)); ++i; } From b490e56ba182fd3bd8951b73aab3d4a43dad325a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 21 Nov 2013 06:27:19 +0100 Subject: [PATCH 094/142] Remove redundant setLocals (already done by ContainerStore::add) --- apps/openmw/mwscript/containerextensions.cpp | 9 --------- apps/openmw/mwworld/worldimp.cpp | 16 +++------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 9636e8a628..53f4c23c97 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -54,15 +54,6 @@ namespace MWScript MWWorld::Ptr itemPtr = *ptr.getClass().getContainerStore (ptr).add (item, count, ptr); - // Configure item's script variables - std::string script = MWWorld::Class::get(itemPtr).getScript(itemPtr); - if (script != "") - { - const ESM::Script *esmscript = MWBase::Environment::get().getWorld()->getStore().get().find (script); - itemPtr.getRefData().setLocals(*esmscript); - } - - // Spawn a messagebox (only for items added to player's inventory and if player is talking to someone) if (ptr == MWBase::Environment::get().getWorld ()->getPlayer ().getPlayer() ) { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 19ed2079ed..0ad06cf265 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1967,23 +1967,13 @@ namespace MWWorld if(werewolf) { - ManualRef ref(getStore(), "WerewolfRobe"); + InventoryStore &inv = actor.getClass().getInventoryStore(actor); - // Configure item's script variables - std::string script = Class::get(ref.getPtr()).getScript(ref.getPtr()); - if(script != "") - { - const ESM::Script *esmscript = getStore().get().find(script); - ref.getPtr().getRefData().setLocals(*esmscript); - } - - // Not sure this is right - InventoryStore &inv = Class::get(actor).getInventoryStore(actor); - inv.equip(InventoryStore::Slot_Robe, inv.add(ref.getPtr(), actor), actor); + inv.equip(InventoryStore::Slot_Robe, inv.ContainerStore::add("WerewolfRobe", 1, actor), actor); } else { - Class::get(actor).getContainerStore(actor).remove("WerewolfRobe", 1, actor); + actor.getClass().getContainerStore(actor).remove("WerewolfRobe", 1, actor); } if(actor.getRefData().getHandle() == "player") From 829512ded4ee1d2307735a8c61eb580ccfabc55e Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 22 Nov 2013 01:02:12 +0100 Subject: [PATCH 095/142] Fix container scripts not getting re-added when the player changes cells --- apps/openmw/mwworld/worldimp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0ad06cf265..0439958f24 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -882,6 +882,7 @@ namespace MWWorld int cellY = newCell.mCell->getGridY(); mWorldScene->changeCell(cellX, cellY, pos, false); } + addContainerScripts (ptr, &newCell); } else { From 39de0510a00b8f566701178740d38acacf4cdb1e Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 22 Nov 2013 02:12:37 +0100 Subject: [PATCH 096/142] Fix another case of container scripts not getting re-added --- apps/openmw/mwworld/worldimp.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0439958f24..fba08db951 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -749,12 +749,16 @@ namespace MWWorld void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) { - return mWorldScene->changeToInteriorCell(cellName, position); + removeContainerScripts(getPlayer().getPlayer()); + mWorldScene->changeToInteriorCell(cellName, position); + addContainerScripts(getPlayer().getPlayer(), getPlayer().getPlayer().getCell()); } void World::changeToExteriorCell (const ESM::Position& position) { - return mWorldScene->changeToExteriorCell(position); + removeContainerScripts(getPlayer().getPlayer()); + mWorldScene->changeToExteriorCell(position); + addContainerScripts(getPlayer().getPlayer(), getPlayer().getPlayer().getCell()); } void World::markCellAsUnchanged() From 0e254aa7c764ad90a206c208ad08726b68e25917 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 03:25:55 +0100 Subject: [PATCH 097/142] Fix startNewGame assigning an already freed CellStore to the player Ptr supplied to WindowManager. Fixes a crash when equipping lights after starting a new game (bug 967). Side note: The inventory preview's Ptr being assigned a cell at all doesn't make sense, as that is used to determine the light setting which should be the same no matter which cell you're in. --- apps/openmw/mwrender/characterpreview.cpp | 4 ++-- apps/openmw/mwworld/worldimp.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 2cae306110..5e659ca1d7 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -35,7 +35,7 @@ namespace MWRender , mCamera(NULL) , mNode(NULL) { - + mCharacter.mCell = NULL; } void CharacterPreview::onSetup() @@ -230,7 +230,7 @@ namespace MWRender , mRef(&mBase) { mBase = *mCharacter.get()->mBase; - mCharacter = MWWorld::Ptr(&mRef, mCharacter.getCell()); + mCharacter = MWWorld::Ptr(&mRef, NULL); } void RaceSelectionPreview::update(float angle) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index fba08db951..3c49b87390 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -267,6 +267,7 @@ namespace MWWorld // Rebuild player setupPlayer(); + mPlayer->setCell(NULL); MWWorld::Ptr player = mPlayer->getPlayer(); // removes NpcStats, ContainerStore etc From a1fe07181c2c8bec56dc18800f1a7d0c8baaaae8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 05:55:03 +0100 Subject: [PATCH 098/142] Fix rotation offset of some lights (bug 955) --- apps/openmw/mwrender/npcanimation.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 093a40c8e5..e219bcf42f 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -540,10 +540,13 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g if(skel->hasBone("BoneOffset")) { Ogre::Bone *offset = skel->getBone("BoneOffset"); + root->translate(offset->getPosition()); - root->rotate(offset->getOrientation()); - // HACK: Why an extra -90 degree rotation? + + // It appears that the BoneOffset rotation is completely bogus, at least for light models. + //root->rotate(offset->getOrientation()); root->pitch(Ogre::Degree(-90.0f)); + root->scale(offset->getScale()); root->setInitialState(); } From d2ed77f3f28e8eadb9cf29d312572d921e91b17e Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 20:24:52 +0100 Subject: [PATCH 099/142] Fix shields being visible during spellcasting --- apps/openmw/mwmechanics/character.cpp | 6 ++++++ apps/openmw/mwrender/animation.hpp | 1 + apps/openmw/mwrender/npcanimation.cpp | 30 +++++++++++++++++++++++++++ apps/openmw/mwrender/npcanimation.hpp | 2 ++ 4 files changed, 39 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c7a6b38751..7505d34056 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -426,6 +426,11 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun { forcestateupdate = true; + // Shields shouldn't be visible during spellcasting + // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", + // but they are also present in weapon drawing animation. + mAnimation->showShield(weaptype != WeapType_Spell); + std::string weapgroup; if(weaptype == WeapType_None) { @@ -443,6 +448,7 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun MWRender::Animation::Group_UpperBody, true, 1.0f, "equip start", "equip stop", 0.0f, 0); mUpperBodyState = UpperCharState_EquipingWeap; + if(isWerewolf) { const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 38e7742ef1..e28aecbc13 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -274,6 +274,7 @@ public: virtual Ogre::Vector3 runAnimation(float duration); virtual void showWeapons(bool showWeapon); + virtual void showShield(bool show) {} void enableLights(bool enable); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e219bcf42f..50e41062a8 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -110,6 +110,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v mListenerDisabled(disableListener), mViewMode(viewMode), mShowWeapons(false), + mShowShield(true), mFirstPersonOffset(0.f, 0.f, 0.f) { mNpc = mPtr.get()->mBase; @@ -307,6 +308,7 @@ void NpcAnimation::updateParts() } showWeapons(mShowWeapons); + showShield(mShowShield); // Remember body parts so we only have to search through the store once for each race/gender/viewmode combination static std::map< std::pair,std::vector > sRaceMapping; @@ -635,6 +637,34 @@ void NpcAnimation::showWeapons(bool showWeapon) } } +void NpcAnimation::showShield(bool show) +{ + mShowShield = show; + MWWorld::InventoryStore &inv = MWWorld::Class::get(mPtr).getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + + if (shield != inv.end() && shield->getTypeName() == typeid(ESM::Light).name()) + { + // ... Except for lights, which are still shown during spellcasting since they + // have their own (one-handed) casting animations + show = true; + } + if(show && shield != inv.end()) + { + Ogre::Vector3 glowColor = getEnchantmentColor(*shield); + std::string mesh = MWWorld::Class::get(*shield).getModel(*shield); + addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, + mesh, !shield->getClass().getEnchantment(*shield).empty(), &glowColor); + + if (shield->getTypeName() == typeid(ESM::Light).name()) + addExtraLight(mInsert->getCreator(), mObjectParts[ESM::PRT_Shield], shield->get()->mBase); + } + else + { + removeIndividualPart(ESM::PRT_Shield); + } +} + void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) { // During first auto equip, we don't play any sounds. diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 7116b6ef1c..c33d511ecc 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -41,6 +41,7 @@ private: std::string mHairModel; ViewMode mViewMode; bool mShowWeapons; + bool mShowShield; int mVisibilityFlags; @@ -81,6 +82,7 @@ public: virtual Ogre::Vector3 runAnimation(float timepassed); virtual void showWeapons(bool showWeapon); + virtual void showShield(bool showShield); void setViewMode(ViewMode viewMode); From f3e89e916895f0e4b4ba38d2569b394464855516 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 22:48:39 +0100 Subject: [PATCH 100/142] Fix arrow down in console --- apps/openmw/mwgui/console.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index a1e3fb7381..96bc204c15 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -246,7 +246,7 @@ namespace MWGui { if(mCurrent != mCommandHistory.end()) { - --mCurrent; + ++mCurrent; if(mCurrent != mCommandHistory.end()) mCommandLine->setCaption(*mCurrent); From 4aa9f3bcefc07344e5be487420ab046210862309 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 22:48:56 +0100 Subject: [PATCH 101/142] Don't set the enchanted item in HUD for "WhenStrikes" enchantments --- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 9f7073d2af..08db189778 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -312,7 +312,7 @@ namespace MWMechanics } if (enchantment->mData.mType == ESM::Enchantment::CastOnce) item.getContainerStore()->remove(item, 1, mCaster); - else + else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes) { if (mCaster.getRefData().getHandle() == "player") MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge From 07408a4652f56ff777f7aee3aec6e2b8058e6cc2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 22:52:20 +0100 Subject: [PATCH 102/142] Don't allow selling gold (again - when did this get broken?) --- apps/openmw/mwclass/misc.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 67f79c40ba..1a40c45554 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -241,7 +241,8 @@ namespace MWClass MWWorld::LiveCellRef *ref = item.get(); - return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc); + return !ref->mBase->mData.mIsKey && (npcServices & ESM::NPC::Misc) + && !Misc::StringUtils::ciEqual(item.getCellRef().mRefID, "gold_001"); } float Miscellaneous::getWeight(const MWWorld::Ptr &ptr) const From 14c9a4e1d391c7be655ff89ba9e3cec26a6f1d58 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 23 Nov 2013 23:12:54 +0100 Subject: [PATCH 103/142] Cap enchantment casting cost to 1 as displayed in enchanting window. Display current enchantment charge in spell window. --- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwgui/spellwindow.cpp | 12 ++++++++++-- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 6970e8646e..8ff8081bc7 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -463,7 +463,7 @@ namespace MWClass // Check if we have enough charges const float enchantCost = enchantment->mData.mCost; int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); - const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); + const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); if (weapon.getCellRef().mEnchantmentCharge == -1) weapon.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 03bb106310..42a0b98657 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -282,8 +282,16 @@ namespace MWGui MyGUI::Button* costCharge = mSpellView->createWidget(equipped ? "SpellText" : "SpellTextUnequipped", MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - std::string cost = boost::lexical_cast(enchant->mData.mCost); - std::string charge = boost::lexical_cast(enchant->mData.mCharge); /// \todo track current charge + float enchantCost = enchant->mData.mCost; + MWMechanics::NpcStats &stats = player.getClass().getNpcStats(player); + int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); + int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); + + std::string cost = boost::lexical_cast(castCost); + int currentCharge = int(item.getCellRef().mEnchantmentCharge); + if (currentCharge == -1) + currentCharge = enchant->mData.mCharge; + std::string charge = boost::lexical_cast(currentCharge); if (enchant->mData.mType == ESM::Enchantment::CastOnce) { // this is Morrowind behaviour diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 08db189778..65efc0dab9 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -295,7 +295,7 @@ namespace MWMechanics const float enchantCost = enchantment->mData.mCost; MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster); int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); - const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); + const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); if (item.getCellRef().mEnchantmentCharge == -1) item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; From 6b81fd78f18145a1c1aa1c626ca4b4ad4878b40f Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 25 Nov 2013 13:50:33 +0100 Subject: [PATCH 104/142] Particle improvements: Handle LocalSpace flag. Attach particle systems to the base node, since they need to be relative to that when LocalSpace is enabled. Get the bone in emitters/affectors so that resulting particle positions are the same. TODO: Fix Controllers to affect particle systems. --- components/nif/node.hpp | 3 +- components/nifogre/ogrenifloader.cpp | 61 +++++++------ components/nifogre/particles.cpp | 129 +++++++++++++++++++++++++-- 3 files changed, 159 insertions(+), 34 deletions(-) diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 917bc8add3..6816a79a2e 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -137,7 +137,8 @@ struct NiNode : Node AnimFlag_AutoPlay = 0x0020 }; enum BSParticleFlags { - ParticleFlag_AutoPlay = 0x0020 + ParticleFlag_AutoPlay = 0x0020, + ParticleFlag_LocalSpace = 0x0080 }; void read(NIFStream *nif) diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index bb98501f47..a530d060d2 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -497,7 +497,9 @@ class NIFObjectLoader } - static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys, const Nif::NiParticleSystemController *partctrl) + static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys, + const Nif::NiParticleSystemController *partctrl, Ogre::Bone* bone, + const std::string& skelBaseName) { Ogre::ParticleEmitter *emitter = partsys->addEmitter("Nif"); emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f, @@ -512,6 +514,8 @@ class NIFObjectLoader emitter->setParameter("vertical_angle", Ogre::StringConverter::toString(Ogre::Radian(partctrl->verticalAngle).valueDegrees())); emitter->setParameter("horizontal_direction", Ogre::StringConverter::toString(Ogre::Radian(partctrl->horizontalDir).valueDegrees())); emitter->setParameter("horizontal_angle", Ogre::StringConverter::toString(Ogre::Radian(partctrl->horizontalAngle).valueDegrees())); + emitter->setParameter("skelbase", skelBaseName); + emitter->setParameter("bone", bone->getName()); Nif::ExtraPtr e = partctrl->extra; while(!e.empty()) @@ -533,6 +537,8 @@ class NIFObjectLoader affector->setParameter("force_type", (gr->mType==0) ? "wind" : "point"); affector->setParameter("direction", Ogre::StringConverter::toString(gr->mDirection)); affector->setParameter("position", Ogre::StringConverter::toString(gr->mPosition)); + affector->setParameter("skelbase", skelBaseName); + affector->setParameter("bone", bone->getName()); } else if(e->recType == Nif::RC_NiParticleColorModifier) { @@ -565,7 +571,7 @@ class NIFObjectLoader } static void createParticleSystem(const std::string &name, const std::string &group, - Ogre::SceneManager *sceneMgr, ObjectList &objectlist, + Ogre::SceneNode *sceneNode, ObjectList &objectlist, const Nif::Node *partnode, int flags, int partflags) { const Nif::NiAutoNormalParticlesData *particledata = NULL; @@ -579,7 +585,7 @@ class NIFObjectLoader fullname += "@type="+partnode->name; Misc::StringUtils::toLower(fullname); - Ogre::ParticleSystem *partsys = sceneMgr->createParticleSystem(); + Ogre::ParticleSystem *partsys = sceneNode->getCreator()->createParticleSystem(); const Nif::NiTexturingProperty *texprop = NULL; const Nif::NiMaterialProperty *matprop = NULL; @@ -600,9 +606,9 @@ class NIFObjectLoader particledata->particleRadius*2.0f); partsys->setCullIndividually(false); partsys->setParticleQuota(particledata->numParticles); - // TODO: There is probably a field or flag to specify this, as some - // particle effects have it and some don't. - partsys->setKeepParticlesInLocalSpace(false); + partsys->setKeepParticlesInLocalSpace(partflags & (Nif::NiNode::ParticleFlag_LocalSpace)); + + sceneNode->attachObject(partsys); Nif::ControllerPtr ctrl = partnode->controller; while(!ctrl.empty()) @@ -611,12 +617,11 @@ class NIFObjectLoader { const Nif::NiParticleSystemController *partctrl = static_cast(ctrl.getPtr()); - createParticleEmitterAffectors(partsys, partctrl); - if(!partctrl->emitter.empty() && !partsys->isAttached()) + if(!partctrl->emitter.empty()) { int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partctrl->emitter->recIndex); Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); - objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), partsys); + createParticleEmitterAffectors(partsys, partctrl, trgtbone, objectlist.mSkelBase->getName()); } Ogre::ControllerValueRealPtr srcval((partflags&Nif::NiNode::ParticleFlag_AutoPlay) ? @@ -634,13 +639,6 @@ class NIFObjectLoader ctrl = ctrl->next; } - if(!partsys->isAttached()) - { - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partnode->recIndex); - Ogre::Bone *trgtbone = objectlist.mSkelBase->getSkeleton()->getBone(trgtid); - objectlist.mSkelBase->attachObjectToBone(trgtbone->getName(), partsys); - } - partsys->setVisible(!(flags&Nif::NiNode::Flag_Hidden)); objectlist.mParticles.push_back(partsys); } @@ -729,7 +727,7 @@ class NIFObjectLoader static void createObjects(const std::string &name, const std::string &group, - Ogre::SceneManager *sceneMgr, const Nif::Node *node, + Ogre::SceneNode *sceneNode, const Nif::Node *node, ObjectList &objectlist, int flags, int animflags, int partflags) { // Do not create objects for the collision shape (includes all children) @@ -784,13 +782,13 @@ class NIFObjectLoader if(node->recType == Nif::RC_NiTriShape && !(flags&0x80000000)) { - createEntity(name, group, sceneMgr, objectlist, node, flags, animflags); + createEntity(name, group, sceneNode->getCreator(), objectlist, node, flags, animflags); } if((node->recType == Nif::RC_NiAutoNormalParticles || node->recType == Nif::RC_NiRotatingParticles) && !(flags&0x40000000)) { - createParticleSystem(name, group, sceneMgr, objectlist, node, flags, partflags); + createParticleSystem(name, group, sceneNode, objectlist, node, flags, partflags); } const Nif::NiNode *ninode = dynamic_cast(node); @@ -800,7 +798,7 @@ class NIFObjectLoader for(size_t i = 0;i < children.length();i++) { if(!children[i].empty()) - createObjects(name, group, sceneMgr, children[i].getPtr(), objectlist, flags, animflags, partflags); + createObjects(name, group, sceneNode, children[i].getPtr(), objectlist, flags, animflags, partflags); } } } @@ -821,7 +819,7 @@ class NIFObjectLoader } public: - static void load(Ogre::SceneManager *sceneMgr, ObjectList &objectlist, const std::string &name, const std::string &group, int flags=0) + static void load(Ogre::SceneNode *sceneNode, ObjectList &objectlist, const std::string &name, const std::string &group, int flags=0) { Nif::NIFFile::ptr nif = Nif::NIFFile::create(name); if(nif->numRoots() < 1) @@ -845,9 +843,9 @@ public: !NIFSkeletonLoader::createSkeleton(name, group, node).isNull()) { // Create a base skeleton entity if this NIF needs one - createSkelBase(name, group, sceneMgr, node, objectlist); + createSkelBase(name, group, sceneNode->getCreator(), node, objectlist); } - createObjects(name, group, sceneMgr, node, objectlist, flags, 0, 0); + createObjects(name, group, sceneNode, node, objectlist, flags, 0, 0); } static void loadKf(Ogre::Skeleton *skel, const std::string &name, @@ -915,7 +913,7 @@ ObjectList Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, ObjectList objectlist; Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group); + NIFObjectLoader::load(parentNode, objectlist, name, group); for(size_t i = 0;i < objectlist.mEntities.size();i++) { @@ -934,7 +932,7 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena ObjectList objectlist; Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group); + NIFObjectLoader::load(parentNode, objectlist, name, group); bool isskinned = false; for(size_t i = 0;i < objectlist.mEntities.size();i++) @@ -984,6 +982,17 @@ ObjectList Loader::createObjects(Ogre::Entity *parent, const std::string &bonena } } + for(size_t i = 0;i < objectlist.mParticles.size();i++) + { + Ogre::ParticleSystem *partsys = objectlist.mParticles[i]; + if(partsys->isAttached()) + partsys->detachFromParent(); + + Ogre::TagPoint *tag = objectlist.mSkelBase->attachObjectToBone( + objectlist.mSkelBase->getSkeleton()->getRootBone()->getName(), partsys); + tag->setScale(scale); + } + return objectlist; } @@ -993,7 +1002,7 @@ ObjectList Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string nam ObjectList objectlist; Misc::StringUtils::toLower(name); - NIFObjectLoader::load(parentNode->getCreator(), objectlist, name, group, 0xC0000000); + NIFObjectLoader::load(parentNode, objectlist, name, group, 0xC0000000); if(objectlist.mSkelBase) parentNode->attachObject(objectlist.mSkelBase); diff --git a/components/nifogre/particles.cpp b/components/nifogre/particles.cpp index 707bd75e08..006a570dce 100644 --- a/components/nifogre/particles.cpp +++ b/components/nifogre/particles.cpp @@ -5,11 +5,55 @@ #include #include #include +#include +#include +#include +#include +#include +#include /* FIXME: "Nif" isn't really an appropriate emitter name. */ class NifEmitter : public Ogre::ParticleEmitter { public: + std::string mSkelBaseName; + Ogre::Bone* mBone; + + Ogre::ParticleSystem* getPartSys() { return mParent; } + + class CmdSkelBase : public Ogre::ParamCommand + { + public: + Ogre::String doGet(const void *target) const + { + assert(false && "Unimplemented"); + } + void doSet(void *target, const Ogre::String &val) + { + NifEmitter* emitter = static_cast(target); + emitter->mSkelBaseName = val; + } + }; + + class CmdBone : public Ogre::ParamCommand + { + public: + Ogre::String doGet(const void *target) const + { + assert(false && "Unimplemented"); + } + void doSet(void *target, const Ogre::String &val) + { + NifEmitter* emitter = static_cast(target); + assert(!emitter->mSkelBaseName.empty() && "Base entity needs to be set first"); + Ogre::ParticleSystem* partsys = emitter->getPartSys(); + Ogre::Entity* ent = partsys->getParentSceneNode()->getCreator()->getEntity(emitter->mSkelBaseName); + Ogre::Bone* bone = ent->getSkeleton()->getBone(val); + assert(bone); + emitter->mBone = bone; + } + }; + /** Command object for the emitter width (see Ogre::ParamCommand).*/ class CmdWidth : public Ogre::ParamCommand { @@ -119,6 +163,7 @@ public: NifEmitter(Ogre::ParticleSystem *psys) : Ogre::ParticleEmitter(psys) + , mBone(NULL) { initDefaults("Nif"); } @@ -133,6 +178,7 @@ public: /** See Ogre::ParticleEmitter. */ void _initParticle(Ogre::Particle *particle) { + assert (mBone && "No node set"); Ogre::Vector3 xOff, yOff, zOff; // Call superclass @@ -141,8 +187,8 @@ public: xOff = Ogre::Math::SymmetricRandom() * mXRange; yOff = Ogre::Math::SymmetricRandom() * mYRange; zOff = Ogre::Math::SymmetricRandom() * mZRange; - - particle->position = mPosition + xOff + yOff + zOff; + + particle->position = mBone->_getDerivedPosition() + xOff + yOff + zOff; // Generate complex data by reference genEmissionColour(particle->colour); @@ -150,7 +196,7 @@ public: // NOTE: We do not use mDirection/mAngle for the initial direction. Ogre::Radian hdir = mHorizontalDir + mHorizontalAngle*Ogre::Math::SymmetricRandom(); Ogre::Radian vdir = mVerticalDir + mVerticalAngle*Ogre::Math::SymmetricRandom(); - particle->direction = (Ogre::Quaternion(hdir, Ogre::Vector3::UNIT_Z) * + particle->direction = (mBone->_getDerivedOrientation() * Ogre::Quaternion(hdir, Ogre::Vector3::UNIT_Z) * Ogre::Quaternion(vdir, Ogre::Vector3::UNIT_X)) * Ogre::Vector3::UNIT_Z; @@ -313,6 +359,16 @@ protected: Ogre::PT_REAL), &msHorizontalAngleCmd); + dict->addParameter(Ogre::ParameterDef("bone", + "The bone where the particles should be spawned", + Ogre::PT_STRING), + &msBoneCmd); + + dict->addParameter(Ogre::ParameterDef("skelbase", + "The name of the entity containing the bone (see 'bone' parameter)", + Ogre::PT_STRING), + &msSkelBaseCmd); + return true; } return false; @@ -326,6 +382,8 @@ protected: static CmdVerticalAngle msVerticalAngleCmd; static CmdHorizontalDir msHorizontalDirCmd; static CmdHorizontalAngle msHorizontalAngleCmd; + static CmdBone msBoneCmd; + static CmdSkelBase msSkelBaseCmd; }; NifEmitter::CmdWidth NifEmitter::msWidthCmd; NifEmitter::CmdHeight NifEmitter::msHeightCmd; @@ -334,12 +392,14 @@ NifEmitter::CmdVerticalDir NifEmitter::msVerticalDirCmd; NifEmitter::CmdVerticalAngle NifEmitter::msVerticalAngleCmd; NifEmitter::CmdHorizontalDir NifEmitter::msHorizontalDirCmd; NifEmitter::CmdHorizontalAngle NifEmitter::msHorizontalAngleCmd; +NifEmitter::CmdBone NifEmitter::msBoneCmd; +NifEmitter::CmdSkelBase NifEmitter::msSkelBaseCmd; Ogre::ParticleEmitter* NifEmitterFactory::createEmitter(Ogre::ParticleSystem *psys) { - Ogre::ParticleEmitter *emit = OGRE_NEW NifEmitter(psys); - mEmitters.push_back(emit); - return emit; + Ogre::ParticleEmitter *emitter = OGRE_NEW NifEmitter(psys); + mEmitters.push_back(emitter); + return emitter; } @@ -492,6 +552,45 @@ class GravityAffector : public Ogre::ParticleAffector }; public: + std::string mSkelBaseName; + Ogre::Bone* mBone; + + Ogre::ParticleSystem* getPartSys() { return mParent; } + + class CmdSkelBase : public Ogre::ParamCommand + { + public: + Ogre::String doGet(const void *target) const + { + assert(false && "Unimplemented"); + } + void doSet(void *target, const Ogre::String &val) + { + GravityAffector* affector = static_cast(target); + affector->mSkelBaseName = val; + } + }; + + class CmdBone : public Ogre::ParamCommand + { + public: + Ogre::String doGet(const void *target) const + { + assert(false && "Unimplemented"); + } + void doSet(void *target, const Ogre::String &val) + { + GravityAffector* affector = static_cast(target); + assert(!affector->mSkelBaseName.empty() && "Base entity needs to be set first"); + Ogre::ParticleSystem* partsys = affector->getPartSys(); + Ogre::Entity* ent = partsys->getParentSceneNode()->getCreator()->getEntity(affector->mSkelBaseName); + Ogre::Bone* bone = ent->getSkeleton()->getBone(val); + assert(bone); + affector->mBone = bone; + } + }; + + /** Command object for force (see Ogre::ParamCommand).*/ class CmdForce : public Ogre::ParamCommand { @@ -585,6 +684,7 @@ public: , mForceType(Type_Wind) , mPosition(0.0f) , mDirection(0.0f) + , mBone(NULL) { mType = "Gravity"; @@ -606,6 +706,16 @@ public: dict->addParameter(Ogre::ParameterDef(force_type_title, force_type_descr, Ogre::PT_STRING), &msForceTypeCmd); dict->addParameter(Ogre::ParameterDef(direction_title, direction_descr, Ogre::PT_VECTOR3), &msDirectionCmd); dict->addParameter(Ogre::ParameterDef(position_title, position_descr, Ogre::PT_VECTOR3), &msPositionCmd); + + dict->addParameter(Ogre::ParameterDef("bone", + "The bone where the particles should be spawned", + Ogre::PT_STRING), + &msBoneCmd); + + dict->addParameter(Ogre::ParameterDef("skelbase", + "The name of the entity containing the bone (see 'bone' parameter)", + Ogre::PT_STRING), + &msSkelBaseCmd); } } @@ -647,6 +757,8 @@ public: static CmdForceType msForceTypeCmd; static CmdDirection msDirectionCmd; static CmdPosition msPositionCmd; + static CmdBone msBoneCmd; + static CmdSkelBase msSkelBaseCmd; protected: void applyWindForce(Ogre::ParticleSystem *psys, Ogre::Real timeElapsed) @@ -667,7 +779,8 @@ protected: while (!pi.end()) { Ogre::Particle *p = pi.getNext(); - const Ogre::Vector3 vec = (p->position - mPosition).normalisedCopy() * force; + const Ogre::Vector3 vec = ( + (mBone->_getDerivedOrientation() * mPosition + mBone->_getDerivedPosition()) - p->position).normalisedCopy() * force; p->direction += vec; } } @@ -684,6 +797,8 @@ GravityAffector::CmdForce GravityAffector::msForceCmd; GravityAffector::CmdForceType GravityAffector::msForceTypeCmd; GravityAffector::CmdDirection GravityAffector::msDirectionCmd; GravityAffector::CmdPosition GravityAffector::msPositionCmd; +GravityAffector::CmdBone GravityAffector::msBoneCmd; +GravityAffector::CmdSkelBase GravityAffector::msSkelBaseCmd; Ogre::ParticleAffector *GravityAffectorFactory::createAffector(Ogre::ParticleSystem *psys) { From 58dce88c7d1a93d3bb784dc479eca845c7f1bf31 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 25 Nov 2013 14:03:51 +0100 Subject: [PATCH 105/142] Handle the "tai" alias for ToggleAI --- components/compiler/extensions0.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 01792def95..de1a2cf299 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -60,6 +60,7 @@ namespace Compiler extensions.registerInstruction ("modflee", "l", opcodeModFlee, opcodeModFleeExplicit); extensions.registerInstruction ("modalarm", "l", opcodeModAlarm, opcodeModAlarmExplicit); extensions.registerInstruction ("toggleai", "", opcodeToggleAI, opcodeToggleAI); + extensions.registerInstruction ("tai", "", opcodeToggleAI, opcodeToggleAI); extensions.registerFunction ("gethello", 'l', "", opcodeGetHello, opcodeGetHelloExplicit); extensions.registerFunction ("getfight", 'l', "", opcodeGetFight, opcodeGetFightExplicit); extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit); From 9f5ff033d720335fa6329915455addbfab01cf63 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 25 Nov 2013 14:06:59 +0100 Subject: [PATCH 106/142] Handle the "GetLOS" alias for GetLineOfSight --- components/compiler/extensions0.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index de1a2cf299..e95f6f6985 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -66,6 +66,7 @@ namespace Compiler extensions.registerFunction ("getflee", 'l', "", opcodeGetFlee, opcodeGetFleeExplicit); extensions.registerFunction ("getalarm", 'l', "", opcodeGetAlarm, opcodeGetAlarmExplicit); extensions.registerFunction ("getlineofsight", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); + extensions.registerFunction ("getlos", 'l', "c", opcodeGetLineOfSight, opcodeGetLineOfSightExplicit); } } From eba068149d13b9cd6dbd9afdd7af2fc25b762033 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 25 Nov 2013 15:38:18 +0100 Subject: [PATCH 107/142] Fix AIWander crash --- apps/openmw/mwmechanics/aiwander.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index ecc41489a5..f66f7ca620 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -144,12 +144,12 @@ namespace MWMechanics mCurrentNode = mAllowedNodes[index]; mAllowedNodes.erase(mAllowedNodes.begin() + index); } - - if(mAllowedNodes.empty()) - mDistance = 0; } } + if(mAllowedNodes.empty()) + mDistance = 0; + // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. if(mDistance && (mCellX != actor.getCell()->mCell->mData.mX || mCellY != actor.getCell()->mCell->mData.mY)) mDistance = 0; @@ -200,6 +200,7 @@ namespace MWMechanics { if(!mPathFinder.isPathConstructed()) { + assert(mAllowedNodes.size()); unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size()); Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ); From bda28e2ed0e2e298f460c0b8cbddb1fd613b35e6 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 26 Nov 2013 06:42:37 +0100 Subject: [PATCH 108/142] Increase size of item selection dialog --- files/mygui/openmw_itemselection_dialog.layout | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/mygui/openmw_itemselection_dialog.layout b/files/mygui/openmw_itemselection_dialog.layout index 003eb1c6ac..5fc2862981 100644 --- a/files/mygui/openmw_itemselection_dialog.layout +++ b/files/mygui/openmw_itemselection_dialog.layout @@ -1,13 +1,13 @@ - + - + - + From 49ea1aae67ae5d987e96209e2b79d3db6072c27a Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 26 Nov 2013 15:01:22 +0100 Subject: [PATCH 109/142] Use GMST for sun damage reduction --- apps/openmw/mwmechanics/actors.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 5aa846118b..def5708a62 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -258,9 +258,12 @@ namespace MWMechanics float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); float damageScale = 1.f - timeDiff / 7.f; // When cloudy, the sun damage effect is halved + static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get().find( + "fMagicSunBlockedMult")->getFloat(); + int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); if (weather > 1) - damageScale *= 0.5; + damageScale *= fMagicSunBlockedMult; health.setCurrent(health.getCurrent() - magnitude * duration * damageScale); } else From a8c838b53a056353e6871824602a66a0120873fc Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 27 Nov 2013 18:43:42 +0100 Subject: [PATCH 110/142] Don't list deleted refs in CellStore::foreach --- apps/openmw/mwworld/cellstore.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index bcbc5e415a..8a01caf183 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -140,9 +140,12 @@ namespace MWWorld { for (typename List::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); ++iter) + { + if (!iter->mData.getCount()) + continue; if (!functor (iter->mRef, iter->mData)) return false; - + } return true; } From 03c4b680ca1f506ebb0bc8d77d3ceba5d86f1f37 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 27 Nov 2013 18:45:46 +0100 Subject: [PATCH 111/142] Fix changePointer --- apps/openmw/mwgui/windowmanagerimp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index dc753a8fa7..2bcfdfb62d 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1304,6 +1304,7 @@ namespace MWGui void WindowManager::changePointer(const std::string &name) { + MyGUI::PointerManager::getInstance().setPointer(name); onCursorChange(name); } From 97fadb24ca5b97cdced575612ba269e8d205f74e Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 27 Nov 2013 18:46:18 +0100 Subject: [PATCH 112/142] Update the Ptr in mObjects in Objects::updateObjectCell --- apps/openmw/mwrender/objects.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 4d5f6872df..827b9b52a7 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -278,7 +278,17 @@ void Objects::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) } else { node = mCellSceneNodes[newCell]; } + node->addChild(cur.getRefData().getBaseNode()); + + PtrAnimationMap::iterator iter = mObjects.find(old); + if(iter != mObjects.end()) + { + ObjectAnimation *anim = iter->second; + mObjects.erase(iter); + anim->updatePtr(cur); + mObjects[cur] = anim; + } } ObjectAnimation* Objects::getAnimation(const MWWorld::Ptr &ptr) From 4a0a16be364f2e04d1459f8b91ea266821a87bfb Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 10 Nov 2013 22:40:46 +0100 Subject: [PATCH 113/142] Fix a build error --- components/contentselector/model/contentmodel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 5f3575eb40..9183003295 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -1,6 +1,8 @@ #include "contentmodel.hpp" #include "esmfile.hpp" +#include + #include #include #include From 20ccfe2324717a6a559555ec1e6f5d26f933b4fb Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 28 Nov 2013 11:37:30 +0100 Subject: [PATCH 114/142] Play sound when enchanting --- apps/openmw/mwgui/enchantingdialog.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 98ba8ec2f2..d2e914d17e 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -4,6 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwworld/player.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" @@ -298,9 +299,15 @@ namespace MWGui int result = mEnchanting.create(); if(result==1) + { + MWBase::Environment::get().getSoundManager()->playSound("enchant success", 1.f, 1.f); MWBase::Environment::get().getWindowManager()->messageBox ("#{sEnchantmentMenu12}"); + } else + { + MWBase::Environment::get().getSoundManager()->playSound("enchant fail", 1.f, 1.f); MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage34}"); + } MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); } From 076cc9230b4ea9d870e74dcee31a1cf8d57d6d1a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 28 Nov 2013 17:31:17 +0100 Subject: [PATCH 115/142] First try at handling target magic --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 4 +- apps/openmw/mwmechanics/spellcasting.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 109 ++++++++++++++++++++--- apps/openmw/mwworld/worldimp.hpp | 11 ++- 5 files changed, 111 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 8b49463080..c092840dd8 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -417,7 +417,7 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor) = 0; - virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects, + virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) = 0; }; } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 65efc0dab9..74816d12e2 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -329,7 +329,7 @@ namespace MWMechanics inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); } - MWBase::Environment::get().getWorld()->launchProjectile(mId, enchantment->mEffects, mCaster, mSourceName); + MWBase::Environment::get().getWorld()->launchProjectile(mId, false, enchantment->mEffects, mCaster, mSourceName); return true; } @@ -434,7 +434,7 @@ namespace MWMechanics } } - MWBase::Environment::get().getWorld()->launchProjectile(mId, spell->mEffects, mCaster, mSourceName); + MWBase::Environment::get().getWorld()->launchProjectile(mId, false, spell->mEffects, mCaster, mSourceName); return true; } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index c40567fcfa..e2efaa140b 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -184,7 +184,7 @@ namespace MWMechanics private: MWWorld::Ptr mCaster; MWWorld::Ptr mTarget; - + public: bool mStack; std::string mId; // ID of spell, potion, item etc std::string mSourceName; // Display name for spell, potion, etc diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3c49b87390..dcf0d7ca99 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -804,6 +804,8 @@ namespace MWWorld return MWWorld::Ptr (); MWWorld::Ptr object = searchPtrViaHandle (result.second); + if (object.isEmpty()) + return object; float ActivationDistance; if (MWBase::Environment::get().getWindowManager()->isConsoleMode()) @@ -895,15 +897,16 @@ namespace MWWorld copyObjectToCell(ptr, newCell, pos); else if (!mWorldScene->isCellActive(newCell)) { - MWWorld::Class::get(ptr) - .copyToCell(ptr, newCell) - .getRefData() - .setBaseNode(0); - mWorldScene->removeObjectFromScene(ptr); mLocalScripts.remove(ptr); removeContainerScripts (ptr); haveToMove = false; + + MWWorld::Ptr newPtr = MWWorld::Class::get(ptr) + .copyToCell(ptr, newCell); + newPtr.getRefData().setBaseNode(0); + + objectLeftActiveCell(ptr, newPtr); } else { @@ -1292,7 +1295,8 @@ namespace MWWorld mWorldScene->update (duration, paused); - doPhysics (duration); + if (!paused) + doPhysics (duration); performUpdateSceneQueries (); @@ -2066,11 +2070,12 @@ namespace MWWorld } } - void World::launchProjectile (const std::string& id, const ESM::EffectList& effects, + void World::launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) { std::string projectileModel; std::string sound; + float speed = 0; for (std::vector::const_iterator iter (effects.mList.begin()); iter!=effects.mList.end(); ++iter) { @@ -2091,18 +2096,20 @@ namespace MWWorld else sound = schools[magicEffect->mData.mSchool] + " bolt"; + speed = magicEffect->mData.mSpeed; break; } if (projectileModel.empty()) return; - return; + // Spawn at 0.75 * ActorHeight + float height = mPhysEngine->getCharacter(actor.getRefData().getHandle())->getHalfExtents().z * 2 * 0.75; MWWorld::ManualRef ref(getStore(), projectileModel); ESM::Position pos; pos.pos[0] = actor.getRefData().getPosition().pos[0]; pos.pos[1] = actor.getRefData().getPosition().pos[1]; - pos.pos[2] = actor.getRefData().getPosition().pos[2]; + 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]; @@ -2113,6 +2120,9 @@ namespace MWWorld state.mSourceName = sourceName; state.mId = id; state.mActorHandle = actor.getRefData().getHandle(); + state.mSpeed = speed; + state.mEffects = effects; + state.mStack = stack; MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f); @@ -2122,6 +2132,7 @@ namespace MWWorld void World::moveProjectiles(float duration) { + std::map moved; for (std::map::iterator it = mProjectiles.begin(); it != mProjectiles.end();) { if (!mWorldScene->isCellActive(*it->first.getCell())) @@ -2129,9 +2140,83 @@ namespace MWWorld mProjectiles.erase(it++); continue; } - // TODO: Move - //moveObject(it->first, newPos.x, newPos.y, newPos.z); - ++it; + + MWWorld::Ptr ptr = it->first; + + Ogre::Vector3 rot(ptr.getRefData().getPosition().rot); + + // TODO: Why -rot.z, but not -rot.x? + 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); + + // This is just a guess, probably wrong + static float fProjectileMinSpeed = getStore().get().find("fProjectileMinSpeed")->getFloat(); + static float fProjectileMaxSpeed = getStore().get().find("fProjectileMaxSpeed")->getFloat(); + float speed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * it->second.mSpeed; + + Ogre::Vector3 direction = orient.yAxis(); + direction.normalise(); + Ogre::Vector3 pos(ptr.getRefData().getPosition().pos); + Ogre::Vector3 newPos = pos + direction * duration * speed; + + // Check for impact + btVector3 from(pos.x, pos.y, pos.z); + btVector3 to(newPos.x, newPos.y, newPos.z); + std::vector > collisions = mPhysEngine->rayTest2(from, to); + for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end(); ++cIt) + { + MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second); + if (obstacle.isEmpty()) + { + // Terrain. TODO: Explode + continue; + } + if (obstacle == ptr) + continue; + MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); + if (caster.isEmpty()) + caster = obstacle; + MWMechanics::CastSpell cast(caster, obstacle); + cast.mStack = it->second.mStack; + cast.mId = it->second.mId; + cast.mSourceName = it->second.mSourceName; + cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false); + + 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::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::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 (mProjectiles.find(object) != mProjectiles.end()) + { + deleteObject(movedPtr); } } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1022a74fee..80119e014f 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -97,6 +97,12 @@ namespace MWWorld // Name of item to display as effect source in magic menu (in case we casted an enchantment) std::string mSourceName; + + ESM::EffectList mEffects; + + float mSpeed; + + bool mStack; }; std::map mProjectiles; @@ -147,6 +153,9 @@ namespace MWWorld bool mTeleportEnabled; bool mLevitationEnabled; + /// Called when \a object is moved to an inactive cell + void objectLeftActiveCell (MWWorld::Ptr object, MWWorld::Ptr movedPtr); + public: World (OEngine::Render::OgreRenderer& renderer, @@ -493,7 +502,7 @@ namespace MWWorld virtual void castSpell (const MWWorld::Ptr& actor); - virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects, + virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName); }; } From b82ee4b44f36c40ffe4a92cd2c5904b7c82e4bd5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 28 Nov 2013 21:49:15 +0100 Subject: [PATCH 116/142] Fix some problems with the previous commit --- apps/openmw/mwworld/worldimp.cpp | 33 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index dcf0d7ca99..d6ec6e810c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2163,27 +2163,38 @@ namespace MWWorld btVector3 from(pos.x, pos.y, pos.z); btVector3 to(newPos.x, newPos.y, newPos.z); std::vector > collisions = mPhysEngine->rayTest2(from, to); - for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end(); ++cIt) + bool explode = false; + for (std::vector >::iterator cIt = collisions.begin(); cIt != collisions.end() && !explode; ++cIt) { MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second); - if (obstacle.isEmpty()) - { - // Terrain. TODO: Explode - continue; - } if (obstacle == ptr) continue; + + explode = true; + MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); if (caster.isEmpty()) caster = obstacle; - MWMechanics::CastSpell cast(caster, obstacle); - cast.mStack = it->second.mStack; - cast.mId = it->second.mId; - cast.mSourceName = it->second.mSourceName; - cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false); + if (obstacle.isEmpty()) + { + // Terrain + } + else + { + MWMechanics::CastSpell cast(caster, obstacle); + cast.mStack = it->second.mStack; + cast.mId = it->second.mId; + cast.mSourceName = it->second.mSourceName; + cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false); + } deleteObject(ptr); mProjectiles.erase(it++); + } + + if (explode) + { + // TODO: Explode continue; } From ffc885853a173e5febac4b5f76429149e08bcf94 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 29 Nov 2013 10:39:37 +0100 Subject: [PATCH 117/142] Fix bolt for magic effects that don't have one specified --- apps/openmw/mwrender/animation.cpp | 1 + apps/openmw/mwworld/worldimp.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 15df907b2f..dddbc2c733 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1066,6 +1066,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con for(size_t i = 0;i < params.mObjects.mParticles.size(); ++i) { Ogre::ParticleSystem* partSys = params.mObjects.mParticles[i]; + sh::Factory::getInstance()._ensureMaterial(partSys->getMaterialName(), "Default"); Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName()); static int count = 0; Ogre::String materialName = "openmw/" + Ogre::StringConverter::toString(count++); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d6ec6e810c..691c7fea47 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2086,6 +2086,8 @@ namespace MWWorld iter->mEffectID); projectileModel = magicEffect->mBolt; + if (projectileModel.empty()) + projectileModel = "VFX_DefaultBolt"; static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" From ce2d521b8ffd9d9819cb12f0f4b8542efd1ed0bb Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 29 Nov 2013 20:03:50 +0100 Subject: [PATCH 118/142] Always apply queued movement, even when there's no duration. Fixes crash with --start="bal isra". When a script disables a reference that still has movement queued, trying to apply that movement will then fail due to the reference not being in the scene. Therefore, we should make sure that movement is always applied in the frame that it's queued in. --- apps/openmw/mwworld/worldimp.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 691c7fea47..3d0bcf6d87 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1127,10 +1127,6 @@ namespace MWWorld void World::doPhysics(float duration) { - /* No duration? Shouldn't be any movement, then. */ - if(duration <= 0.0f) - return; - processDoors(duration); moveProjectiles(duration); From bcf61331ab2ddb9fac4217c94498b7615d1d0b41 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 29 Nov 2013 20:06:54 +0100 Subject: [PATCH 119/142] Bring back the option to not grab mouse. Useful if running in a mouse-controlled GUI debugger. --- apps/openmw/engine.cpp | 3 ++- apps/openmw/engine.hpp | 4 ++++ apps/openmw/main.cpp | 4 ++++ apps/openmw/mwinput/inputmanagerimp.cpp | 4 ++-- apps/openmw/mwinput/inputmanagerimp.hpp | 2 +- extern/sdl4ogre/sdlinputwrapper.cpp | 7 ++++--- extern/sdl4ogre/sdlinputwrapper.hpp | 3 ++- 7 files changed, 19 insertions(+), 8 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 4a3c418f6c..a729bdda1e 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -147,6 +147,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mEncoding(ToUTF8::WINDOWS_1252) , mEncoder(NULL) , mActivationDistanceOverride(-1) + , mGrab(true) { std::srand ( std::time(NULL) ); @@ -370,7 +371,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) std::string keybinderUser = (mCfgMgr.getUserPath() / "input.xml").string(); bool keybinderUserExists = boost::filesystem::exists(keybinderUser); - MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists); + MWInput::InputManager* input = new MWInput::InputManager (*mOgre, *this, keybinderUser, keybinderUserExists, mGrab); mEnvironment.setInputManager (input); MWGui::WindowManager* window = new MWGui::WindowManager( diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 553d290687..e0f8f94e69 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -79,6 +79,8 @@ namespace OMW bool mScriptConsoleMode; std::string mStartupScript; int mActivationDistanceOverride; + // Grab mouse? + bool mGrab; Compiler::Extensions mExtensions; Compiler::Context *mScriptContext; @@ -152,6 +154,8 @@ namespace OMW /// Start as a new game. void setNewGame(bool newGame); + void setGrabMouse(bool grab) { mGrab = grab; } + /// Initialise and enter main loop. void go(); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index a36b6e12f5..42815e330b 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -154,6 +154,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("fallback", bpo::value()->default_value(FallbackMap(), "") ->multitoken()->composing(), "fallback values") + ("no-grab", "Don't grab mouse cursor") + ("activate-dist", bpo::value ()->default_value (-1), "activation distance override"); bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) @@ -184,6 +186,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat if (!run) return false; + engine.setGrabMouse(!variables.count("no-grab")); + // Font encoding settings std::string encoding(variables["encoding"].as()); std::cout << ToUTF8::encodingUsingMessage(encoding) << std::endl; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 6901bcdb92..ab25696351 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -87,7 +87,7 @@ namespace MWInput { InputManager::InputManager(OEngine::Render::OgreRenderer &ogre, OMW::Engine& engine, - const std::string& userFile, bool userFileExists) + const std::string& userFile, bool userFileExists, bool grab) : mOgre(ogre) , mPlayer(NULL) , mEngine(engine) @@ -111,7 +111,7 @@ namespace MWInput Ogre::RenderWindow* window = ogre.getWindow (); - mInputManager = new SFO::InputWrapper(mOgre.getSDLWindow(), mOgre.getWindow()); + mInputManager = new SFO::InputWrapper(mOgre.getSDLWindow(), mOgre.getWindow(), grab); mInputManager->setMouseEventCallback (this); mInputManager->setKeyboardEventCallback (this); mInputManager->setWindowEventCallback(this); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index e7b7d6c7f5..8efa6cfc5a 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -61,7 +61,7 @@ namespace MWInput public: InputManager(OEngine::Render::OgreRenderer &_ogre, OMW::Engine& engine, - const std::string& userFile, bool userFileExists); + const std::string& userFile, bool userFileExists, bool grab); virtual ~InputManager(); diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index 9990e828aa..d48e43c010 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -9,7 +9,7 @@ namespace SFO { /// \brief General purpose wrapper for OGRE applications around SDL's event /// queue, mostly used for handling input-related events. - InputWrapper::InputWrapper(SDL_Window* window, Ogre::RenderWindow* ogreWindow) : + InputWrapper::InputWrapper(SDL_Window* window, Ogre::RenderWindow* ogreWindow, bool grab) : mSDLWindow(window), mOgreWindow(ogreWindow), mWarpCompensate(false), @@ -27,7 +27,8 @@ namespace SFO mWindowHasFocus(true), mWantGrab(false), mWantRelative(false), - mWantMouseVisible(false) + mWantMouseVisible(false), + mAllowGrab(grab) { _setupOISKeys(); } @@ -226,7 +227,7 @@ namespace SFO void InputWrapper::updateMouseSettings() { mGrabPointer = mWantGrab && mMouseInWindow && mWindowHasFocus; - SDL_SetWindowGrab(mSDLWindow, mGrabPointer ? SDL_TRUE : SDL_FALSE); + SDL_SetWindowGrab(mSDLWindow, mGrabPointer && mAllowGrab ? SDL_TRUE : SDL_FALSE); SDL_ShowCursor(mWantMouseVisible || !mWindowHasFocus); diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index ca57464e92..a2b698f860 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -16,7 +16,7 @@ namespace SFO class InputWrapper { public: - InputWrapper(SDL_Window *window, Ogre::RenderWindow* ogreWindow); + InputWrapper(SDL_Window *window, Ogre::RenderWindow* ogreWindow, bool grab); ~InputWrapper(); void setMouseEventCallback(MouseListener* listen) { mMouseListener = listen; } @@ -62,6 +62,7 @@ namespace SFO bool mWarpCompensate; bool mWrapPointer; + bool mAllowGrab; bool mWantMouseVisible; bool mWantGrab; bool mWantRelative; From 0e267b79ecec7e085b344a98dec6d6ec85739cd0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 29 Nov 2013 20:21:57 +0100 Subject: [PATCH 120/142] Don't heal dead actors when resting --- apps/openmw/mwmechanics/actors.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index def5708a62..161c8dcfe4 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -160,6 +160,8 @@ namespace MWMechanics void Actors::calculateRestoration (const MWWorld::Ptr& ptr, float duration) { + if (ptr.getClass().getCreatureStats(ptr).isDead()) + return; CreatureStats& stats = MWWorld::Class::get (ptr).getCreatureStats (ptr); const MWWorld::Store& settings = MWBase::Environment::get().getWorld()->getStore().get(); From bb4bd999ba706e672380a6921af9776c93a5cf58 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 30 Nov 2013 08:29:22 +0100 Subject: [PATCH 121/142] PlaceAt: Copy the rotation when placing a non-actor. Don't modify placement position by bounding box for non-actors. Fixes placement in Graphic Herbalism mod. --- .../openmw/mwscript/transformationextensions.cpp | 16 +++++++++++++--- apps/openmw/mwworld/worldimp.cpp | 15 +++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 4b60f47ce2..ae9ac041e1 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -490,10 +490,20 @@ namespace MWScript ipos.pos[0] = pos.x; ipos.pos[1] = pos.y; ipos.pos[2] = pos.z; - ipos.rot[0] = 0; - ipos.rot[1] = 0; - ipos.rot[2] = 0; + if (actor.getClass().isActor()) + { + // TODO: should this depend on the 'direction' parameter? + ipos.rot[0] = 0; + ipos.rot[1] = 0; + ipos.rot[2] = 0; + } + else + { + ipos.rot[0] = actor.getRefData().getPosition().rot[0]; + ipos.rot[1] = actor.getRefData().getPosition().rot[1]; + ipos.rot[2] = actor.getRefData().getPosition().rot[2]; + } // create item MWWorld::CellStore* store = actor.getCell(); MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), itemID, count); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 3d0bcf6d87..1247ae6e6b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1543,12 +1543,15 @@ namespace MWWorld MWWorld::Ptr dropped = MWWorld::Class::get(object).copyToCell(object, cell, pos); - Ogre::Vector3 min, max; - if (mPhysics->getObjectAABB(object, min, max)) { - float *pos = dropped.getRefData().getPosition().pos; - pos[0] -= (min.x + max.x) / 2; - pos[1] -= (min.y + max.y) / 2; - pos[2] -= min.z; + if (object.getClass().isActor()) + { + Ogre::Vector3 min, max; + if (mPhysics->getObjectAABB(object, min, max)) { + float *pos = dropped.getRefData().getPosition().pos; + pos[0] -= (min.x + max.x) / 2; + pos[1] -= (min.y + max.y) / 2; + pos[2] -= min.z; + } } if (mWorldScene->isCellActive(cell)) { From 6aa9e189155b1850b0750892a74450dc2ce18b12 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 30 Nov 2013 08:33:18 +0100 Subject: [PATCH 122/142] Reset filter when starting a trade --- apps/openmw/mwgui/tradewindow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index 636b8ae9b6..d544b83cf4 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -80,7 +80,7 @@ namespace MWGui } void TradeWindow::startTrade(const MWWorld::Ptr& actor) - { + { mPtr = actor; mCurrentBalance = 0; @@ -102,6 +102,8 @@ namespace MWGui // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last // or we end up using a possibly invalid model. setTitle(MWWorld::Class::get(actor).getName(actor)); + + onFilterChanged(mFilterAll); } void TradeWindow::onFilterChanged(MyGUI::Widget* _sender) From e1e9de0f02ea4d1f750e055829bbc1c86bd96219 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 30 Nov 2013 09:04:52 +0100 Subject: [PATCH 123/142] Don't hide tooltips on mouse click. Probably wasn't such a bright idea. --- apps/openmw/mwgui/tooltips.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 85c71575b6..b52c8e3ddb 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -110,11 +110,6 @@ namespace MWGui else { - const MyGUI::IntPoint& lastPressed = MyGUI::InputManager::getInstance().getLastPressedPosition(MyGUI::MouseButton::Left); - - if (mousePos == lastPressed) // mouseclick makes tooltip disappear - return; - if (mousePos.left == mLastMouseX && mousePos.top == mLastMouseY) { mRemainingDelay -= frameDuration; From 4b4025ed0fb87113700dd1208657350a58fde5e1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 30 Nov 2013 10:50:02 +0100 Subject: [PATCH 124/142] Keep the player's CharacterController when changing cells. Fixes several glitches. --- apps/openmw/mwmechanics/actors.cpp | 4 ++-- apps/openmw/mwmechanics/actors.hpp | 4 ++-- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 5 +---- apps/openmw/mwworld/scene.cpp | 8 ++++---- apps/openmw/mwworld/worldimp.cpp | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 161c8dcfe4..22a641b34b 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -516,12 +516,12 @@ namespace MWMechanics } } - void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore) + void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore, const MWWorld::Ptr& ignore) { PtrControllerMap::iterator iter = mActors.begin(); while(iter != mActors.end()) { - if(iter->first.getCell()==cellStore) + if(iter->first.getCell()==cellStore && iter->first != ignore) { delete iter->second; mActors.erase(iter++); diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index b733c966be..6afdefdbdc 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -68,8 +68,8 @@ namespace MWMechanics void updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr& ptr); ///< Updates an actor with a new Ptr - void dropActors (const MWWorld::CellStore *cellStore); - ///< Deregister all actors in the given cell. + void dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore); + ///< Deregister all actors (except for \a ignore) in the given cell. void update (float duration, bool paused); ///< Update actor stats and store desired velocity vectors in \a movement diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index ff13841a2f..1316baaeb4 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -200,10 +200,7 @@ namespace MWMechanics void MechanicsManager::drop(const MWWorld::CellStore *cellStore) { - if(!mWatched.isEmpty() && mWatched.getCell() == cellStore) - mWatched = MWWorld::Ptr(); - - mActors.dropActors(cellStore); + mActors.dropActors(cellStore, mWatched); mObjects.dropObjects(cellStore); } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 254ad98cfb..25ce038f36 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -111,7 +111,9 @@ namespace MWWorld mRendering.removeCell(*iter); MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter); + MWBase::Environment::get().getMechanicsManager()->drop (*iter); + MWBase::Environment::get().getSoundManager()->stopSound (*iter); mActiveCells.erase(*iter); } @@ -164,6 +166,7 @@ namespace MWWorld void Scene::playerCellChange(MWWorld::CellStore *cell, const ESM::Position& pos, bool adjustPlayerPos) { MWBase::World *world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr old = world->getPlayer().getPlayer(); world->getPlayer().setCell(cell); MWWorld::Ptr player = world->getPlayer().getPlayer(); @@ -183,7 +186,7 @@ namespace MWWorld MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager(); - mechMgr->add(player); + mechMgr->updateCell(old, player); mechMgr->watchActor(player); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); @@ -205,9 +208,6 @@ namespace MWWorld Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); - // remove active - MWBase::Environment::get().getMechanicsManager()->remove(MWBase::Environment::get().getWorld()->getPlayer().getPlayer()); - std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 1247ae6e6b..525c811af3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -889,7 +889,7 @@ namespace MWWorld int cellY = newCell.mCell->getGridY(); mWorldScene->changeCell(cellX, cellY, pos, false); } - addContainerScripts (ptr, &newCell); + addContainerScripts (getPlayer().getPlayer(), &newCell); } else { From 46973e8e8201380b85ca21e62a70e53e8315acf1 Mon Sep 17 00:00:00 2001 From: pvdk Date: Sat, 30 Nov 2013 12:08:37 +0100 Subject: [PATCH 125/142] Fix for Bug #982: unchecking addons is now saved to profile --- apps/launcher/datafilespage.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 734277b97d..d5eb458f80 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -116,8 +116,7 @@ void Launcher::DataFilesPage::buildView() void Launcher::DataFilesPage::removeProfile(const QString &profile) { - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/game")); - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/addon")); + mLauncherSettings.remove(QString("Profiles/") + profile); } QAbstractItemModel *Launcher::DataFilesPage::profilesModel() const From 1512ac11adcc4d1d9567447d9704088d48bba95d Mon Sep 17 00:00:00 2001 From: pvdk Date: Sat, 30 Nov 2013 12:16:57 +0100 Subject: [PATCH 126/142] Fixed the content selector checkboxes appearing as partially checked --- components/contentselector/model/contentmodel.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 9183003295..0d274474c6 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -207,8 +207,11 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int case Qt::CheckStateRole: { - if (!file->isGameFile()) - return isChecked(file->filePath()); + if (file->isGameFile()) + return QVariant(); + + return mCheckStates[file->filePath()]; + break; } From 8391891a5b7583d9f75a995251005fa7c4912aa7 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 30 Nov 2013 14:25:29 +0100 Subject: [PATCH 127/142] Ignore case for content file extensions --- apps/openmw/mwworld/worldimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 525c811af3..87a8d7d6f6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -107,7 +107,7 @@ namespace MWWorld void load(const boost::filesystem::path& filepath, int& index) { - LoadersContainer::iterator it(mLoaders.find(filepath.extension().string())); + LoadersContainer::iterator it(mLoaders.find(Misc::StringUtils::lowerCase(filepath.extension().string()))); if (it != mLoaders.end()) { it->second->load(filepath, index); From 2fe135d85f3b73be2554d5ce524591e3d7c1b1bb Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Mon, 2 Dec 2013 16:25:26 +0100 Subject: [PATCH 128/142] journals subview was bound to the wrong universal ID type --- apps/opencs/view/world/subviews.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/world/subviews.cpp b/apps/opencs/view/world/subviews.cpp index 48c32e171d..74ce03cce6 100644 --- a/apps/opencs/view/world/subviews.cpp +++ b/apps/opencs/view/world/subviews.cpp @@ -58,7 +58,7 @@ void CSVWorld::addSubViewFactories (CSVDoc::SubViewFactoryManager& manager) manager.add (CSMWorld::UniversalId::Type_Topics, new CSVDoc::SubViewFactoryWithCreator); - manager.add (CSMWorld::UniversalId::Type_Journal, + manager.add (CSMWorld::UniversalId::Type_Journals, new CSVDoc::SubViewFactoryWithCreator); manager.add (CSMWorld::UniversalId::Type_TopicInfos, From 8f4ffe4ddc569127c5d7a0819798937b95ba11a4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Dec 2013 15:55:31 +0100 Subject: [PATCH 129/142] Fix new window size not being written to settings when manually resizing window --- apps/openmw/mwrender/renderingmanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 1b891368ff..26e88fb0dc 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -878,6 +878,8 @@ void RenderingManager::setMenuTransparency(float val) void RenderingManager::windowResized(int x, int y) { + Settings::Manager::setInt("resolution x", "Video", x); + Settings::Manager::setInt("resolution y", "Video", y); mRendering.adjustViewport(); mCompositors->recreate(); From 3a82f8c1934223a9fd10bf93558adc16d360bec1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Dec 2013 15:56:00 +0100 Subject: [PATCH 130/142] Fix incorrect log file name for launcherOgre.log --- components/ogreinit/ogreinit.cpp | 2 +- libs/openengine/ogre/renderer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ogreinit/ogreinit.cpp b/components/ogreinit/ogreinit.cpp index 92a6ed0123..c8fc621c78 100644 --- a/components/ogreinit/ogreinit.cpp +++ b/components/ogreinit/ogreinit.cpp @@ -36,7 +36,7 @@ namespace OgreInit { // Set up logging first new Ogre::LogManager; - Ogre::Log *log = Ogre::LogManager::getSingleton().createLog(logPath + std::string("Ogre.log")); + Ogre::Log *log = Ogre::LogManager::getSingleton().createLog(logPath); // Disable logging to cout/cerr log->setDebugOutputEnabled(false); diff --git a/libs/openengine/ogre/renderer.cpp b/libs/openengine/ogre/renderer.cpp index 2a438e1fef..a0fe6ca849 100644 --- a/libs/openengine/ogre/renderer.cpp +++ b/libs/openengine/ogre/renderer.cpp @@ -50,7 +50,7 @@ void OgreRenderer::configure(const std::string &logPath, const std::string& rttMode ) { - mRoot = mOgreInit.init(logPath); + mRoot = mOgreInit.init(logPath + "/ogre.log"); RenderSystem* rs = mRoot->getRenderSystemByName(renderSystem); if (rs == 0) From 147bc447a5c1e469d3d321ecf442e2b2551fdf53 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Dec 2013 16:07:07 +0100 Subject: [PATCH 131/142] Add specular lighting for directional light to objects shader --- files/materials/objects.mat | 2 +- files/materials/objects.shader | 65 +++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/files/materials/objects.mat b/files/materials/objects.mat index 10fb72740f..ffba6e7ea8 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -1,7 +1,7 @@ material openmw_objects_base { diffuse 1.0 1.0 1.0 1.0 - specular 0 0 0 0 + specular 0 0 0 0 1 ambient 1.0 1.0 1.0 emissive 0.0 0.0 0.0 vertmode 0 diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 9fc9fceaf0..4acc23b79d 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -32,6 +32,10 @@ #define ENV_MAP @shPropertyBool(env_map) +#define SPECULAR 1 + +#define NEED_NORMAL (!VERTEX_LIGHTING || ENV_MAP) || SPECULAR + #ifdef SH_VERTEX_SHADER // ------------------------------------- VERTEX --------------------------------------- @@ -63,15 +67,12 @@ shOutput(float3, tangentPassthrough) #endif -#if !VERTEX_LIGHTING || ENV_MAP +#if NEED_NORMAL shOutput(float3, normalPassthrough) #endif -#ifdef NEED_DEPTH - shOutput(float, depthPassthrough) -#endif - - shOutput(float3, objSpacePositionPassthrough) + // Depth in w + shOutput(float4, objSpacePositionPassthrough) #if VERTEXCOLOR_MODE != 0 shColourInput(float4) @@ -146,7 +147,7 @@ #if NORMAL_MAP tangentPassthrough = tangent.xyz; #endif -#if !VERTEX_LIGHTING || ENV_MAP +#if NEED_NORMAL normalPassthrough = normal.xyz; #endif #if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING @@ -169,14 +170,14 @@ float4x4 fixedWVP = shMatrixMult(vpFixed, worldMatrix); - depthPassthrough = shMatrixMult(fixedWVP, shInputPosition).z; + objSpacePositionPassthrough.w = shMatrixMult(fixedWVP, shInputPosition).z; #else - depthPassthrough = shOutputPosition.z; + objSpacePositionPassthrough.w = shOutputPosition.z; #endif #endif - objSpacePositionPassthrough = shInputPosition.xyz; + objSpacePositionPassthrough.xyz = shInputPosition.xyz; #if SHADOWS lightSpacePos0 = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); @@ -262,7 +263,16 @@ #if ENV_MAP shSampler2D(envMap) shUniform(float3, env_map_color) @shUniformProperty3f(env_map_color, env_map_color) - shUniform(float3, cameraPosObjSpace) @shAutoConstant(cameraPosObjSpace, camera_position_object_space) +#endif + +#if ENV_MAP || SPECULAR + shUniform(float3, cameraPosObjSpace) @shAutoConstant(cameraPosObjSpace, camera_position_object_space) +#endif +#if SPECULAR + shUniform(float3, lightSpec0) @shAutoConstant(lightSpec0, light_specular_colour, 0) + shUniform(float3, lightPosObjSpace0) @shAutoConstant(lightPosObjSpace0, light_position_object_space, 0) + shUniform(float, matShininess) @shAutoConstant(matShininess, surface_shininess) + shUniform(float3, matSpec) @shAutoConstant(matSpec, surface_specular_colour) #endif shInput(float4, UV) @@ -270,15 +280,11 @@ #if NORMAL_MAP shInput(float3, tangentPassthrough) #endif -#if !VERTEX_LIGHTING || ENV_MAP +#if NEED_NORMAL shInput(float3, normalPassthrough) #endif -#ifdef NEED_DEPTH - shInput(float, depthPassthrough) -#endif - - shInput(float3, objSpacePositionPassthrough) + shInput(float4, objSpacePositionPassthrough) #if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING shInput(float4, colourPassthrough) @@ -340,6 +346,10 @@ SH_START_PROGRAM { +#ifdef NEED_DEPTH + float depthPassthrough = objSpacePositionPassthrough.w; +#endif + shOutputColour(0) = shSample(diffuseMap, UV.xy); #if DETAIL_MAP @@ -350,7 +360,7 @@ #endif #endif -#if !VERTEX_LIGHTING || ENV_MAP +#if NEED_NORMAL float3 normal = normalPassthrough; #endif @@ -368,7 +378,7 @@ #endif #if !VERTEX_LIGHTING - float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough,1)).xyz; + float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough.xyz,1)).xyz; float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); float3 lightDir; @@ -433,7 +443,7 @@ #if (UNDERWATER) || (FOG) - float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough,1)).xyz; + float3 worldPos = shMatrixMult(worldMatrix, float4(objSpacePositionPassthrough.xyz,1)).xyz; #endif #if UNDERWATER @@ -454,15 +464,28 @@ #endif #endif +#if ENV_MAP || SPECULAR + float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz); +#endif + #if ENV_MAP // Everything looks better with fresnel - float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz); float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0); float envFactor = shSaturate(0.25 + 0.75 * pow(facing, 1)); shOutputColour(0).xyz += shSample(envMap, UV.zw).xyz * envFactor * env_map_color; #endif +#if SPECULAR + float3 light0Dir = normalize(lightPosObjSpace0.xyz); + + float NdotL = max(dot(normal, light0Dir), 0); + float3 halfVec = normalize (light0Dir + eyeDir); + + float3 specular = pow(max(dot(normal, halfVec), 0), matShininess) * lightSpec0 * matSpec; + shOutputColour(0).xyz += specular * shadow; +#endif + #if FOG float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w); From 5f1878eb54257d54e9507d3df7d664e96b501f71 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Dec 2013 18:41:45 +0100 Subject: [PATCH 132/142] Add specular mapping (uses alpha channel of diffuse texture) --- files/materials/objects.shader | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 4acc23b79d..20abe5b7ce 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -350,7 +350,8 @@ float depthPassthrough = objSpacePositionPassthrough.w; #endif - shOutputColour(0) = shSample(diffuseMap, UV.xy); + float4 diffuse = shSample(diffuseMap, UV.xy); + shOutputColour(0) = diffuse; #if DETAIL_MAP #if @shPropertyString(detailMapUVSet) @@ -483,7 +484,7 @@ float3 halfVec = normalize (light0Dir + eyeDir); float3 specular = pow(max(dot(normal, halfVec), 0), matShininess) * lightSpec0 * matSpec; - shOutputColour(0).xyz += specular * shadow; + shOutputColour(0).xyz += specular * shadow * diffuse.a; #endif #if FOG From 47c60a703767c407e21f4ce7e0e06f6770afa12f Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Wed, 4 Dec 2013 21:48:25 +0100 Subject: [PATCH 133/142] Fix C4716 (Must return a value) error on Windows MSVC --- components/nifogre/particles.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/nifogre/particles.cpp b/components/nifogre/particles.cpp index 006a570dce..d902387b9a 100644 --- a/components/nifogre/particles.cpp +++ b/components/nifogre/particles.cpp @@ -27,6 +27,7 @@ public: Ogre::String doGet(const void *target) const { assert(false && "Unimplemented"); + return ""; } void doSet(void *target, const Ogre::String &val) { @@ -41,6 +42,7 @@ public: Ogre::String doGet(const void *target) const { assert(false && "Unimplemented"); + return ""; } void doSet(void *target, const Ogre::String &val) { @@ -563,6 +565,7 @@ public: Ogre::String doGet(const void *target) const { assert(false && "Unimplemented"); + return ""; } void doSet(void *target, const Ogre::String &val) { @@ -577,6 +580,7 @@ public: Ogre::String doGet(const void *target) const { assert(false && "Unimplemented"); + return ""; } void doSet(void *target, const Ogre::String &val) { From f695deb29dc01c33433502bfa3c9c50ca520da51 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Dec 2013 18:42:35 +0100 Subject: [PATCH 134/142] Remember window positions --- apps/openmw/mwgui/inventorywindow.cpp | 67 +++++++++++++++++--------- apps/openmw/mwgui/inventorywindow.hpp | 7 +-- apps/openmw/mwgui/messagebox.cpp | 4 -- apps/openmw/mwgui/statswindow.hpp | 2 + apps/openmw/mwgui/windowmanagerimp.cpp | 51 ++++++++++++++++++++ apps/openmw/mwgui/windowmanagerimp.hpp | 5 ++ files/mygui/openmw_stats_window.layout | 2 +- files/settings-default.cfg | 66 +++++++++++++++++++++++++ libs/openengine/gui/layout.hpp | 2 +- 9 files changed, 172 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 022e0a47d8..56c474c89a 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -37,10 +37,6 @@ namespace MWGui , mPreviewDirty(true) , mDragAndDrop(dragAndDrop) , mSelectedItem(-1) - , mPositionInventory(0, 342, 498, 258) - , mPositionContainer(0, 342, 498, 258) - , mPositionCompanion(0, 342, 498, 258) - , mPositionBarter(0, 342, 498, 258) , mGuiMode(GM_Inventory) { static_cast(mMainWidget)->eventWindowChangeCoord += MyGUI::newDelegate(this, &InventoryWindow::onWindowResize); @@ -71,8 +67,19 @@ namespace MWGui mFilterAll->setStateSelected(true); - setCoord(mPositionInventory.left, mPositionInventory.top, mPositionInventory.width, mPositionInventory.height); - onWindowResize(static_cast(mMainWidget)); + setGuiMode(mGuiMode); + + adjustPanes(); + } + + void InventoryWindow::adjustPanes() + { + const float aspect = 0.5; // fixed aspect ratio for the left pane + mLeftPane->setSize( (mMainWidget->getSize().height-44) * aspect, mMainWidget->getSize().height-44 ); + mRightPane->setCoord( mLeftPane->getPosition().left + (mMainWidget->getSize().height-44) * aspect + 4, + mRightPane->getPosition().top, + mMainWidget->getSize().width - 12 - (mMainWidget->getSize().height-44) * aspect - 15, + mMainWidget->getSize().height-44 ); } void InventoryWindow::updatePlayer() @@ -87,27 +94,36 @@ namespace MWGui void InventoryWindow::setGuiMode(GuiMode mode) { + std::string setting = "inventory"; mGuiMode = mode; switch(mode) { case GM_Container: setPinButtonVisible(false); - mMainWidget->setCoord(mPositionContainer); + setting += " container"; break; case GM_Companion: setPinButtonVisible(false); - mMainWidget->setCoord(mPositionCompanion); + setting += " companion"; break; case GM_Barter: setPinButtonVisible(false); - mMainWidget->setCoord(mPositionBarter); + setting += " barter"; break; case GM_Inventory: default: setPinButtonVisible(true); - mMainWidget->setCoord(mPositionInventory); break; } - onWindowResize(static_cast(mMainWidget)); + + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::IntPoint pos (Settings::Manager::getFloat(setting + " x", "Windows") * viewSize.width, + Settings::Manager::getFloat(setting + " y", "Windows") * viewSize.height); + MyGUI::IntSize size (Settings::Manager::getFloat(setting + " w", "Windows") * viewSize.width, + Settings::Manager::getFloat(setting + " h", "Windows") * viewSize.height); + mMainWidget->setPosition(pos); + mMainWidget->setSize(size); + adjustPanes(); + mPreviewDirty = true; } TradeItemModel* InventoryWindow::getTradeModel() @@ -256,32 +272,37 @@ namespace MWGui mItemView->update(); notifyContentChanged(); + adjustPanes(); } void InventoryWindow::onWindowResize(MyGUI::Window* _sender) { - const float aspect = 0.5; // fixed aspect ratio for the left pane - mLeftPane->setSize( (_sender->getSize().height-44) * aspect, _sender->getSize().height-44 ); - mRightPane->setCoord( mLeftPane->getPosition().left + (_sender->getSize().height-44) * aspect + 4, - mRightPane->getPosition().top, - _sender->getSize().width - 12 - (_sender->getSize().height-44) * aspect - 15, - _sender->getSize().height-44 ); - + adjustPanes(); + std::string setting = "inventory"; switch(mGuiMode) { case GM_Container: - mPositionContainer = _sender->getCoord(); + setting += " container"; break; case GM_Companion: - mPositionCompanion = _sender->getCoord(); + setting += " companion"; break; case GM_Barter: - mPositionBarter = _sender->getCoord(); + setting += " barter"; break; - case GM_Inventory: default: - mPositionInventory = _sender->getCoord(); + break; } + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + float x = _sender->getPosition().left / float(viewSize.width); + float y = _sender->getPosition().top / float(viewSize.height); + float w = _sender->getSize().width / float(viewSize.width); + float h = _sender->getSize().height / float(viewSize.height); + Settings::Manager::setFloat(setting + " x", "Windows", x); + Settings::Manager::setFloat(setting + " y", "Windows", y); + Settings::Manager::setFloat(setting + " w", "Windows", w); + Settings::Manager::setFloat(setting + " h", "Windows", h); + if (mMainWidget->getSize().width != mLastXSize || mMainWidget->getSize().height != mLastYSize) { mLastXSize = mMainWidget->getSize().width; diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 78d00fd1e7..94ecfd4c8a 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -76,11 +76,6 @@ namespace MWGui MyGUI::Button* mFilterMagic; MyGUI::Button* mFilterMisc; - MyGUI::IntCoord mPositionInventory; - MyGUI::IntCoord mPositionContainer; - MyGUI::IntCoord mPositionCompanion; - MyGUI::IntCoord mPositionBarter; - GuiMode mGuiMode; int mLastXSize; @@ -105,6 +100,8 @@ namespace MWGui void updateEncumbranceBar(); void notifyContentChanged(); + + void adjustPanes(); }; } diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 0a9ee01888..8486218f0b 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -154,10 +154,6 @@ namespace MWGui size.width = mFixedWidth; size.height = 100; // dummy - MyGUI::IntCoord coord; - coord.left = 10; // dummy - coord.top = 10; // dummy - mMessageWidget->setSize(size); MyGUI::IntSize textSize = mMessageWidget->getTextSize(); diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index ac8319bdcd..49b44a2e16 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -37,6 +37,8 @@ namespace MWGui void setBounty (int bounty) { if (bounty != mBounty) mChanged = true; this->mBounty = bounty; } void updateSkillArea(); + virtual void open() { onWindowResize(static_cast(mMainWidget)); } + private: void addSkills(const SkillList &skills, const std::string &titleId, const std::string &titleDefault, MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); void addSeparator(MyGUI::IntCoord &coord1, MyGUI::IntCoord &coord2); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 2bcfdfb62d..78986a0522 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -203,16 +203,22 @@ namespace MWGui mRecharge = new Recharge(); mMenu = new MainMenu(w,h); mMap = new MapWindow(""); + trackWindow(mMap, "map"); mStatsWindow = new StatsWindow(); + trackWindow(mStatsWindow, "stats"); mConsole = new Console(w,h, mConsoleOnlyScripts); + trackWindow(mConsole, "console"); mJournal = JournalWindow::create(JournalViewModel::create ()); mMessageBoxManager = new MessageBoxManager(); mInventoryWindow = new InventoryWindow(mDragAndDrop); mTradeWindow = new TradeWindow(); + trackWindow(mTradeWindow, "barter"); mSpellBuyingWindow = new SpellBuyingWindow(); mTravelWindow = new TravelWindow(); mDialogueWindow = new DialogueWindow(); + trackWindow(mDialogueWindow, "dialogue"); mContainerWindow = new ContainerWindow(mDragAndDrop); + trackWindow(mContainerWindow, "container"); mHud = new HUD(w,h, mShowFPSLevel, mDragAndDrop); mToolTips = new ToolTips(); mScrollWindow = new ScrollWindow(); @@ -221,7 +227,9 @@ namespace MWGui mSettingsWindow = new SettingsWindow(); mConfirmationDialog = new ConfirmationDialog(); mAlchemyWindow = new AlchemyWindow(); + trackWindow(mAlchemyWindow, "alchemy"); mSpellWindow = new SpellWindow(); + trackWindow(mSpellWindow, "spells"); mQuickKeysMenu = new QuickKeysMenu(); mLevelupDialog = new LevelupDialog(); mWaitDialog = new WaitDialog(); @@ -232,6 +240,7 @@ namespace MWGui mRepair = new Repair(); mSoulgemDialog = new SoulgemDialog(mMessageBoxManager); mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); + trackWindow(mCompanionWindow, "companion"); mInputBlocker = mGui->createWidget("",0,0,w,h,MyGUI::Align::Default,"Windows",""); @@ -926,6 +935,17 @@ namespace MWGui mLoadingScreen->onResChange (x,y); if (!mHud) return; // UI not initialized yet + + for (std::map::iterator it = mTrackedWindows.begin(); it != mTrackedWindows.end(); ++it) + { + MyGUI::IntPoint pos (Settings::Manager::getFloat(it->second + " x", "Windows") * x, + Settings::Manager::getFloat(it->second+ " y", "Windows") * y); + MyGUI::IntSize size (Settings::Manager::getFloat(it->second + " w", "Windows") * x, + Settings::Manager::getFloat(it->second + " h", "Windows") * y); + it->first->setPosition(pos); + it->first->setSize(size); + } + mHud->onResChange(x, y); mConsole->onResChange(x, y); mMenu->onResChange(x, y); @@ -1364,4 +1384,35 @@ namespace MWGui return mCursorVisible; } + void WindowManager::trackWindow(OEngine::GUI::Layout *layout, const std::string &name) + { + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + MyGUI::IntPoint pos (Settings::Manager::getFloat(name + " x", "Windows") * viewSize.width, + Settings::Manager::getFloat(name + " y", "Windows") * viewSize.height); + MyGUI::IntSize size (Settings::Manager::getFloat(name + " w", "Windows") * viewSize.width, + Settings::Manager::getFloat(name + " h", "Windows") * viewSize.height); + layout->mMainWidget->setPosition(pos); + layout->mMainWidget->setSize(size); + + MyGUI::Window* window = dynamic_cast(layout->mMainWidget); + if (!window) + throw std::runtime_error("Attempting to track size of a non-resizable window"); + window->eventWindowChangeCoord += MyGUI::newDelegate(this, &WindowManager::onWindowChangeCoord); + mTrackedWindows[window] = name; + } + + void WindowManager::onWindowChangeCoord(MyGUI::Window *_sender) + { + std::string setting = mTrackedWindows[_sender]; + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + float x = _sender->getPosition().left / float(viewSize.width); + float y = _sender->getPosition().top / float(viewSize.height); + float w = _sender->getSize().width / float(viewSize.width); + float h = _sender->getSize().height / float(viewSize.height); + Settings::Manager::setFloat(setting + " x", "Windows", x); + Settings::Manager::setFloat(setting + " y", "Windows", y); + Settings::Manager::setFloat(setting + " w", "Windows", w); + Settings::Manager::setFloat(setting + " h", "Windows", h); + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index c5b6ff2beb..4f19602958 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -16,6 +16,7 @@ namespace MyGUI { class Gui; class Widget; + class Window; class UString; } @@ -283,6 +284,10 @@ namespace MWGui private: bool mConsoleOnlyScripts; + std::map mTrackedWindows; + void trackWindow(OEngine::GUI::Layout* layout, const std::string& name); + void onWindowChangeCoord(MyGUI::Window* _sender); + OEngine::GUI::MyGUIManager *mGuiManager; OEngine::Render::OgreRenderer *mRendering; HUD *mHud; diff --git a/files/mygui/openmw_stats_window.layout b/files/mygui/openmw_stats_window.layout index 5ae3f96caf..36c28c450e 100644 --- a/files/mygui/openmw_stats_window.layout +++ b/files/mygui/openmw_stats_window.layout @@ -2,7 +2,7 @@ - + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f191430df1..94bdd8c9df 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -169,3 +169,69 @@ ui y multiplier = 1.0 [Game] # Always use the most powerful attack when striking with a weapon (chop, slash or thrust) best attack = false + +[Windows] +inventory x = 0 +inventory y = 0.4275 +inventory w = 0.6225 +inventory h = 0.5725 + +inventory container x = 0 +inventory container y = 0.4275 +inventory container w = 0.6225 +inventory container h = 0.5725 + +inventory barter x = 0 +inventory barter y = 0.4275 +inventory barter w = 0.6225 +inventory barter h = 0.5725 + +inventory companion x = 0 +inventory companion y = 0.4275 +inventory companion w = 0.6225 +inventory companion h = 0.5725 + +container x = 0.25 +container y = 0 +container w = 0.75 +container h = 0.375 + +companion x = 0.25 +companion y = 0 +companion w = 0.75 +companion h = 0.375 + +map x = 0.625 +map y = 0 +map w = 0.375 +map h = 0.5725 + +barter x = 0.25 +barter y = 0 +barter w = 0.75 +barter h = 0.375 + +alchemy x = 0.25 +alchemy y = 0.25 +alchemy w = 0.5 +alchemy h = 0.5 + +stats x = 0 +stats y = 0 +stats w = 0.375 +stats h = 0.4275 + +spells x = 0.3775 +spells y = 0.4275 +spells w = 0.375 +spells h = 0.5725 + +console x = 0 +console y = 0 +console w = 1 +console h = 0.5 + +dialogue h = 0.810 +dialogue w = 0.810 +dialogue x = 0.095 +dialogue y = 0.095 diff --git a/libs/openengine/gui/layout.hpp b/libs/openengine/gui/layout.hpp index 9040dfb90e..26a3fdab84 100644 --- a/libs/openengine/gui/layout.hpp +++ b/libs/openengine/gui/layout.hpp @@ -150,10 +150,10 @@ namespace GUI MyGUI::IntSize size = button->getTextSize(); button->setSize(size.width + 24, button->getSize().height); } + MyGUI::Widget* mMainWidget; protected: - MyGUI::Widget* mMainWidget; std::string mPrefix; std::string mLayoutName; MyGUI::VectorWidgetPtr mListWindowRoot; From 9ab8fe10388bfda90aef06c762095d48b97ed830 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 4 Dec 2013 16:03:32 +0100 Subject: [PATCH 135/142] Fix bsatool warning --- apps/bsatool/bsatool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index e6fcc2567d..3781dd066e 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -184,7 +184,7 @@ int list(Bsa::BSAFile& bsa, Arguments& info) { // List all files const Bsa::BSAFile::FileList &files = bsa.getList(); - for(int i=0; i Date: Wed, 4 Dec 2013 21:43:10 +0100 Subject: [PATCH 136/142] Add normal, specular & parallax mapping for terrain --- components/terrain/material.cpp | 53 +++++++++- components/terrain/material.hpp | 10 +- components/terrain/quadtreenode.cpp | 12 ++- components/terrain/storage.cpp | 36 ++++++- components/terrain/storage.hpp | 13 ++- files/materials/objects.shader | 1 - files/materials/terrain.shader | 155 +++++++++++++++++++++++++--- 7 files changed, 251 insertions(+), 29 deletions(-) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index ebf6046ff6..b421de5a28 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include namespace @@ -36,6 +38,8 @@ namespace Terrain : mShaders(shaders) , mShadows(false) , mSplitShadows(false) + , mNormalMapping(true) + , mParallaxMapping(true) { } @@ -85,7 +89,7 @@ namespace Terrain { assert(mLayerList.size() == mBlendmapList.size()+1); std::vector::iterator blend = mBlendmapList.begin(); - for (std::vector::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer) + for (std::vector::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer) { Ogre::Pass* pass = technique->createPass(); pass->setLightingEnabled(false); @@ -117,7 +121,7 @@ namespace Terrain } // Add the actual layer texture on top of the alpha map. - tus = pass->createTextureUnitState("textures\\" + *layer); + tus = pass->createTextureUnitState(layer->mDiffuseMap); if (!first) tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA, Ogre::LBS_TEXTURE, @@ -156,6 +160,10 @@ namespace Terrain p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(true))); p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue("0"))); p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue("0"))); + p->mShaderProperties.setProperty ("normal_map_enabled", sh::makeProperty (new sh::BooleanValue(false))); + p->mShaderProperties.setProperty ("parallax_enabled", sh::makeProperty (new sh::BooleanValue(false))); + p->mShaderProperties.setProperty ("normal_maps", + sh::makeProperty (new sh::IntValue(0))); sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap"); tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap))); @@ -210,6 +218,11 @@ namespace Terrain } } ++neededTextureUnits; // layer texture + + // Check if this layer has a normal map + if (mNormalMapping && !mLayerList[layerOffset].mNormalMap.empty()) + ++neededTextureUnits; // normal map + if (neededTextureUnits <= remainingTextureUnits) { // We can fit another! @@ -238,6 +251,8 @@ namespace Terrain p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass)))); p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures)))); + p->mShaderProperties.setProperty ("normal_map_enabled", + sh::makeProperty (new sh::BooleanValue(false))); // blend maps // the index of the first blend map used in this pass @@ -254,17 +269,39 @@ namespace Terrain } // layer maps + bool anyNormalMaps = false; + bool anyParallax = false; + size_t normalMaps = 0; for (int i = 0; i < numLayersInThisPass; ++i) { + const LayerInfo& layer = mLayerList[layerOffset+i]; + // diffuse map sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); - diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("textures\\"+mLayerList[layerOffset+i]))); + diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mDiffuseMap))); + + // normal map (optional) + bool useNormalMap = mNormalMapping && !mLayerList[layerOffset+i].mNormalMap.empty() && !renderCompositeMap; + bool useParallax = useNormalMap && mParallaxMapping && layer.mParallax; + if (useNormalMap) + { + anyNormalMaps = true; + anyParallax = anyParallax || useParallax; + sh::MaterialInstanceTextureUnit* normalTex = p->createTextureUnit ("normalMap" + Ogre::StringConverter::toString(i)); + normalTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mNormalMap))); + } + p->mShaderProperties.setProperty ("use_normal_map_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::BooleanValue(useNormalMap))); + p->mShaderProperties.setProperty ("use_parallax_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::BooleanValue(useParallax))); + boost::hash_combine(normalMaps, useNormalMap); + boost::hash_combine(normalMaps, useNormalMap && layer.mParallax); if (i+layerOffset > 0) { int blendTextureIndex = getBlendmapIndexForLayer(layerOffset+i); std::string blendTextureComponent = getBlendmapComponentForLayer(layerOffset+i); p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent))); + sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent))); } else { @@ -274,6 +311,14 @@ namespace Terrain sh::makeProperty (new sh::StringValue(""))); } } + p->mShaderProperties.setProperty ("normal_map_enabled", + sh::makeProperty (new sh::BooleanValue(anyNormalMaps))); + p->mShaderProperties.setProperty ("parallax_enabled", + sh::makeProperty (new sh::BooleanValue(anyParallax))); + // Since the permutation handler can't handle dynamic property names, + // combine normal map settings for all layers into one value + p->mShaderProperties.setProperty ("normal_maps", + sh::makeProperty (new sh::IntValue(normalMaps))); // shadow if (shadows) diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 330ed3d147..e7e0678991 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -3,6 +3,8 @@ #include +#include "storage.hpp" + namespace Terrain { @@ -15,13 +17,15 @@ namespace Terrain /// so if this parameter is true, then the supplied blend maps are expected to be packed. MaterialGenerator (bool shaders); - void setLayerList (const std::vector& layerList) { mLayerList = layerList; } + void setLayerList (const std::vector& layerList) { mLayerList = layerList; } bool hasLayers() { return mLayerList.size(); } void setBlendmapList (const std::vector& blendmapList) { mBlendmapList = blendmapList; } const std::vector& getBlendmapList() { return mBlendmapList; } void setCompositeMap (const std::string& name) { mCompositeMap = name; } void enableShadows(bool shadows) { mShadows = shadows; } + void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; } + void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; } void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; } /// Creates a material suitable for displaying a chunk of terrain using alpha-blending. @@ -43,12 +47,14 @@ namespace Terrain private: Ogre::MaterialPtr create (Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap); - std::vector mLayerList; + std::vector mLayerList; std::vector mBlendmapList; std::string mCompositeMap; bool mShaders; bool mShadows; bool mSplitShadows; + bool mNormalMapping; + bool mParallaxMapping; }; } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index ef2c610138..e16ae55ddb 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -371,7 +371,7 @@ void QuadTreeNode::destroyChunks(bool children) for (std::vector::const_iterator it = list.begin(); it != list.end(); ++it) Ogre::TextureManager::getSingleton().remove((*it)->getName()); mMaterialGenerator->setBlendmapList(std::vector()); - mMaterialGenerator->setLayerList(std::vector()); + mMaterialGenerator->setLayerList(std::vector()); mMaterialGenerator->setCompositeMap(""); } @@ -414,7 +414,7 @@ void QuadTreeNode::ensureLayerInfo() return; std::vector blendmaps; - std::vector layerList; + std::vector layerList; mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList); mMaterialGenerator->setLayerList(layerList); @@ -427,11 +427,13 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) if (mIsDummy) { - // TODO - why is this completely black? // TODO - store this default material somewhere instead of creating one for each empty cell MaterialGenerator matGen(mTerrain->getShadersEnabled()); - std::vector layer; - layer.push_back("_land_default.dds"); + std::vector layer; + LayerInfo info; + info.mDiffuseMap = "textures\\_land_default.dds"; + info.mParallax = false; + layer.push_back(info); matGen.setLayerList(layer); makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr())); return; diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp index f00677e97f..9d6b44de8d 100644 --- a/components/terrain/storage.cpp +++ b/components/terrain/storage.cpp @@ -4,9 +4,10 @@ #include #include #include +#include #include -#include +#include namespace Terrain { @@ -302,7 +303,7 @@ namespace Terrain } void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) + bool pack, std::vector &blendmaps, std::vector &layerList) { // TODO - blending isn't completely right yet; the blending radius appears to be // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap @@ -336,7 +337,7 @@ namespace Terrain { int size = textureIndicesMap.size(); textureIndicesMap[*it] = size; - layerList.push_back(getTextureName(*it)); + layerList.push_back(getLayerInfo(getTextureName(*it))); } int numTextures = textureIndices.size(); @@ -466,5 +467,34 @@ namespace Terrain return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; } + LayerInfo Storage::getLayerInfo(const std::string& texture) + { + // Already have this cached? + if (mLayerInfoMap.find(texture) != mLayerInfoMap.end()) + return mLayerInfoMap[texture]; + + LayerInfo info; + info.mParallax = false; + info.mDiffuseMap = "textures\\" + texture; + std::string texture_ = texture; + boost::replace_last(texture_, ".", "_nh."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) + { + info.mNormalMap = "textures\\" + texture_; + info.mParallax = true; + } + else + { + texture_ = texture; + boost::replace_last(texture_, ".", "_n."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) + info.mNormalMap = "textures\\" + texture_; + } + + mLayerInfoMap[texture] = info; + + return info; + } + } diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index b82f6bbb62..68fae01afa 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -11,6 +11,13 @@ namespace Terrain { + struct LayerInfo + { + std::string mDiffuseMap; + std::string mNormalMap; + bool mParallax; // Height info in normal map alpha channel? + }; + /// We keep storage of terrain data abstract here since we need different implementations for game and editor class Storage { @@ -58,7 +65,7 @@ namespace Terrain /// @param layerList names of the layer textures used will be written here void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, std::vector& blendmaps, - std::vector& layerList); + std::vector& layerList); float getHeightAt (const Ogre::Vector3& worldPos); @@ -77,6 +84,10 @@ namespace Terrain UniqueTextureId getVtexIndexAt(int cellX, int cellY, int x, int y); std::string getTextureName (UniqueTextureId id); + + std::map mLayerInfoMap; + + LayerInfo getLayerInfo(const std::string& texture); }; } diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 20abe5b7ce..fb2b304262 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -327,7 +327,6 @@ shInput(float4, lightResult) shInput(float3, directionalResult) #else - shUniform(float, lightCount) @shAutoConstant(lightCount, light_count) shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 861841a84c..eda80c9e30 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -27,6 +27,16 @@ #define COMPOSITE_MAP @shPropertyBool(display_composite_map) +#define NORMAL_MAP @shPropertyBool(normal_map_enabled) +#define PARALLAX @shPropertyBool(parallax_enabled) + +#define VERTEX_LIGHTING (!NORMAL_MAP) + +#define PARALLAX_SCALE 0.04 +#define PARALLAX_BIAS -0.02 + +// This is just for the permutation handler +#define NORMAL_MAPS @shPropertyString(normal_maps) #if NEED_DEPTH @shAllocatePassthrough(1, depth) @@ -37,8 +47,13 @@ @shAllocatePassthrough(3, worldPos) #if LIGHTING +@shAllocatePassthrough(3, normalPassthrough) +#if VERTEX_LIGHTING @shAllocatePassthrough(3, lightResult) @shAllocatePassthrough(3, directionalResult) +#else +@shAllocatePassthrough(3, colourPassthrough) +#endif #if SHADOWS @shAllocatePassthrough(4, lightSpacePos0) @@ -69,12 +84,13 @@ shNormalInput(float4) shColourInput(float4) - shUniform(float, lightCount) @shAutoConstant(lightCount, light_count) +#if VERTEX_LIGHTING shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_object_space_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) - +#endif + #if SHADOWS shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) #endif @@ -122,6 +138,13 @@ @shPassthroughAssign(worldPos, worldPos.xyz); +#if LIGHTING + @shPassthroughAssign(normalPassthrough, normal.xyz); +#endif +#if LIGHTING && !VERTEX_LIGHTING + @shPassthroughAssign(colourPassthrough, colour.xyz); +#endif + #if LIGHTING #if SHADOWS @@ -139,6 +162,7 @@ #endif +#if VERTEX_LIGHTING // Lighting float3 lightDir; float d; @@ -164,6 +188,7 @@ @shPassthroughAssign(lightResult, lightResult); @shPassthroughAssign(directionalResult, directionalResult); +#endif #endif } @@ -189,6 +214,9 @@ @shForeach(@shPropertyString(num_layers)) shSampler2D(diffuseMap@shIterator) +#if @shPropertyBool(use_normal_map_@shIterator) + shSampler2D(normalMap@shIterator) +#endif @shEndForeach #endif @@ -201,6 +229,15 @@ @shPassthroughFragmentInputs #if LIGHTING + +#if !VERTEX_LIGHTING +shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_array, @shGlobalSettingString(num_lights)) +shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) +shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) +shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) +shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix) +#endif + #if SHADOWS shSampler2D(shadowMap0) shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, @shPropertyString(shadowtexture_offset)) @@ -220,13 +257,21 @@ #if (UNDERWATER) || (FOG) shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) - shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) #endif #if UNDERWATER shUniform(float, waterLevel) @shSharedParameter(waterLevel) #endif + +// For specular +#if LIGHTING + shUniform(float3, lightSpec0) @shAutoConstant(lightSpec0, light_specular_colour, 0) + shUniform(float3, lightPos0) @shAutoConstant(lightPos0, light_position, 0) +#endif + +shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) + SH_START_PROGRAM { @@ -237,12 +282,32 @@ float2 UV = @shPassthroughReceive(UV); float3 worldPos = @shPassthroughReceive(worldPos); - - + +#if LIGHTING + float3 normal = @shPassthroughReceive(normalPassthrough); +#endif + +#if LIGHTING && !VERTEX_LIGHTING + +#if NORMAL_MAP + // derive the tangent space basis + float3 tangent = float3(1,0, 0); + + float3 binormal = normalize(cross(tangent, normal)); + tangent = normalize(cross(normal, binormal)); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + + // derive final matrix + float3x3 tbn = float3x3(tangent, binormal, normal); + #if SH_GLSL + tbn = transpose(tbn); + #endif +#endif + +#endif + #if UNDERWATER float3 waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,0,1), waterLevel); #endif - #if !IS_FIRST_PASS // Opacity the previous passes should have, i.e. 1 - (opacity of this pass) @@ -252,6 +317,8 @@ float previousAlpha = 1.f; shOutputColour(0) = float4(1,1,1,1); +float3 TSnormal = float3(0,0,1); + #if COMPOSITE_MAP shOutputColour(0).xyz = shSample(compositeMap, UV).xyz; #else @@ -266,39 +333,90 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; @shEndForeach - float3 albedo = float3(0,0,0); + float4 albedo = float4(0,0,0,1); float2 layerUV = UV * 16; + float2 thisLayerUV; + float4 normalTex; + + float3 eyeDir = normalize(cameraPos.xyz - worldPos); +#if PARALLAX + float3 TSeyeDir = normalize(shMatrixMult(tbn, eyeDir)); +#endif @shForeach(@shPropertyString(num_layers)) +#if @shPropertyBool(use_normal_map_@shIterator) + normalTex = shSample(normalMap@shIterator, thisLayerUV); +#if @shIterator == 0 && IS_FIRST_PASS + TSnormal = normalize(normalTex.xyz * 2 - 1); +#else + TSnormal = shLerp(TSnormal, normalTex.xyz * 2 - 1, blendValues@shPropertyString(blendmap_component_@shIterator)); +#endif +#endif + thisLayerUV = layerUV; + // required to play nicely with the tangents + thisLayerUV.y *= -1; +#if @shPropertyBool(use_parallax_@shIterator) + thisLayerUV += TSeyeDir.xy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS ); +#endif #if IS_FIRST_PASS #if @shIterator == 0 // first layer of first pass is the base layer and doesn't need a blend map - albedo = shSample(diffuseMap0, layerUV).rgb; + albedo = shSample(diffuseMap0, layerUV); #else - albedo = shLerp(albedo, shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator)); + albedo = shLerp(albedo, shSample(diffuseMap@shIterator, thisLayerUV), blendValues@shPropertyString(blendmap_component_@shIterator)); #endif #else #if @shIterator == 0 - albedo = shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator); + albedo = shSample(diffuseMap@shIterator, layerUV); #else - albedo = shLerp(albedo, shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator)); + albedo = shLerp(albedo, shSample(diffuseMap@shIterator, thisLayerUV), blendValues@shPropertyString(blendmap_component_@shIterator)); #endif previousAlpha *= 1.f-blendValues@shPropertyString(blendmap_component_@shIterator); #endif + + @shEndForeach - shOutputColour(0).rgb *= albedo; + shOutputColour(0).rgb *= albedo.xyz; #endif #if LIGHTING + +#if VERTEX_LIGHTING // Lighting float3 lightResult = @shPassthroughReceive(lightResult); float3 directionalResult = @shPassthroughReceive(directionalResult); - +#else + +#if NORMAL_MAP + normal = normalize (shMatrixMult( transpose(tbn), TSnormal )); +#endif + + float3 colour = @shPassthroughReceive(colourPassthrough); + float3 lightDir; + float d; + float3 lightResult = float3(0,0,0); + @shForeach(@shGlobalSettingString(num_lights)) + lightDir = lightPosition[@shIterator].xyz - (worldPos * lightPosition[@shIterator].w); + d = length(lightDir); + lightDir = normalize(lightDir); + + lightResult.xyz += lightDiffuse[@shIterator].xyz + * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) + * max(dot(normal.xyz, lightDir), 0); +#if @shIterator == 0 + float3 directionalResult = lightResult.xyz; +#endif + @shEndForeach + lightResult.xyz += lightAmbient.xyz; + lightResult.xyz *= colour.xyz; + directionalResult.xyz *= colour.xyz; +#endif + // shadows only for the first (directional) light #if SHADOWS float4 lightSpacePos0 = @shPassthroughReceive(lightSpacePos0); @@ -325,6 +443,17 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; shOutputColour(0).xyz *= (lightResult - directionalResult * (1.0-shadow)); #endif +#if LIGHTING && !COMPOSITE_MAP + // Specular + float3 light0Dir = normalize(lightPos0.xyz); + + float NdotL = max(dot(normal, light0Dir), 0); + float3 halfVec = normalize (light0Dir + eyeDir); + + float3 specular = pow(max(dot(normal, halfVec), 0), 32) * lightSpec0; + shOutputColour(0).xyz += specular * (1.f-albedo.a) * shadow; +#endif + #if FOG float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); From afa71bb622df10872c2d997731c30b409b4cb374 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 5 Dec 2013 14:23:39 +0100 Subject: [PATCH 137/142] Throw an exception if a BSA is not found --- apps/openmw/engine.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index a729bdda1e..02e7a59550 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -1,6 +1,6 @@ #include "engine.hpp" -#include "components/esm/loadcell.hpp" +#include #include #include @@ -18,6 +18,8 @@ #include #include +#include + #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" @@ -211,7 +213,9 @@ void OMW::Engine::loadBSA() } else { - std::cout << "Archive " << *archive << " not found" << std::endl; + std::stringstream message; + message << "Archive '" << *archive << "' not found"; + throw std::runtime_error(message.str()); } } } From 845bc5f7ebbc82604edef8ee1359d95cb6819220 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 5 Dec 2013 14:28:39 +0100 Subject: [PATCH 138/142] Show fatal exceptions in a message box instead of cerr when running without a terminal --- apps/openmw/main.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 42815e330b..b1bbb14f23 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -1,7 +1,9 @@ #include +#include #include +#include #include #include "engine.hpp" @@ -280,7 +282,11 @@ int main(int argc, char**argv) } catch (std::exception &e) { - std::cout << "\nERROR: " << e.what() << std::endl; + if (isatty(fileno(stdin))) + std::cerr << "\nERROR: " << e.what() << std::endl; + else + SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); + return 1; } From 062ea627b37d099e6e5749abd3735a07943fe570 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 5 Dec 2013 15:45:39 +0100 Subject: [PATCH 139/142] Add parallax mapping for objects --- components/nifogre/material.cpp | 4 +++ files/materials/core.h | 2 +- files/materials/objects.mat | 2 ++ files/materials/objects.shader | 53 ++++++++++++++++++++------------- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/components/nifogre/material.cpp b/components/nifogre/material.cpp index 55f064c555..8398dbc2ed 100644 --- a/components/nifogre/material.cpp +++ b/components/nifogre/material.cpp @@ -334,6 +334,10 @@ Ogre::String NIFMaterialLoader::getMaterial(const Nif::ShapeData *shapedata, instance->setProperty("detailMapUVSet", sh::makeProperty(new sh::IntValue(texprop->textures[Nif::NiTexturingProperty::DetailTexture].uvSet))); } + bool useParallax = !texName[Nif::NiTexturingProperty::BumpTexture].empty() + && texName[Nif::NiTexturingProperty::BumpTexture].find("_nh.") != std::string::npos; + instance->setProperty("use_parallax", sh::makeProperty(new sh::BooleanValue(useParallax))); + for(int i = 0;i < 7;i++) { if(i == Nif::NiTexturingProperty::BaseTexture || diff --git a/files/materials/core.h b/files/materials/core.h index 6f8179c08d..a912e23562 100644 --- a/files/materials/core.h +++ b/files/materials/core.h @@ -91,7 +91,7 @@ precision mediump float; #define shSamplerCube(name) uniform samplerCube name; @shUseSampler(name) - #define shMatrixMult(m, v) (m * v) + #define shMatrixMult(m, v) ((m) * (v)) #define shOutputPosition gl_Position diff --git a/files/materials/objects.mat b/files/materials/objects.mat index ffba6e7ea8..32787e159b 100644 --- a/files/materials/objects.mat +++ b/files/materials/objects.mat @@ -12,6 +12,7 @@ material openmw_objects_base use_detail_map false emissiveMapUVSet 0 detailMapUVSet 0 + use_parallax false scene_blend default depth_write default @@ -37,6 +38,7 @@ material openmw_objects_base detailMap $detailMap env_map $env_map env_map_color $env_map_color + use_parallax $use_parallax } diffuse $diffuse diff --git a/files/materials/objects.shader b/files/materials/objects.shader index fb2b304262..5a3d872a5b 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -18,6 +18,10 @@ #define EMISSIVE_MAP @shPropertyHasValue(emissiveMap) #define DETAIL_MAP @shPropertyHasValue(detailMap) +#define PARALLAX @shPropertyBool(use_parallax) +#define PARALLAX_SCALE 0.04 +#define PARALLAX_BIAS -0.02 + // right now we support 2 UV sets max. implementing them is tedious, and we're probably not going to need more #define SECOND_UV_SET (@shPropertyString(emissiveMapUVSet) || @shPropertyString(detailMapUVSet)) @@ -265,7 +269,7 @@ shUniform(float3, env_map_color) @shUniformProperty3f(env_map_color, env_map_color) #endif -#if ENV_MAP || SPECULAR +#if ENV_MAP || SPECULAR || PARALLAX shUniform(float3, cameraPosObjSpace) @shAutoConstant(cameraPosObjSpace, camera_position_object_space) #endif #if SPECULAR @@ -345,21 +349,12 @@ SH_START_PROGRAM { + float4 newUV = UV; + #ifdef NEED_DEPTH float depthPassthrough = objSpacePositionPassthrough.w; #endif - float4 diffuse = shSample(diffuseMap, UV.xy); - shOutputColour(0) = diffuse; - -#if DETAIL_MAP -#if @shPropertyString(detailMapUVSet) - shOutputColour(0) *= shSample(detailMap, UV.zw)*2; -#else - shOutputColour(0) *= shSample(detailMap, UV.xy)*2; -#endif -#endif - #if NEED_NORMAL float3 normal = normalPassthrough; #endif @@ -372,11 +367,33 @@ tbn = transpose(tbn); #endif - float3 TSnormal = shSample(normalMap, UV.xy).xyz * 2 - 1; + float4 normalTex = shSample(normalMap, UV.xy); - normal = normalize (shMatrixMult( transpose(tbn), TSnormal )); + normal = normalize (shMatrixMult( transpose(tbn), normalTex.xyz * 2 - 1 )); #endif +#if ENV_MAP || SPECULAR || PARALLAX + float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz); +#endif + +#if PARALLAX + float3 TSeyeDir = normalize(shMatrixMult(tbn, eyeDir)); + + newUV += (TSeyeDir.xyxy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS )).xyxy; +#endif + + float4 diffuse = shSample(diffuseMap, newUV.xy); + shOutputColour(0) = diffuse; + +#if DETAIL_MAP +#if @shPropertyString(detailMapUVSet) + shOutputColour(0) *= shSample(detailMap, newUV.zw)*2; +#else + shOutputColour(0) *= shSample(detailMap, newUV.xy)*2; +#endif +#endif + + #if !VERTEX_LIGHTING float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough.xyz,1)).xyz; float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz); @@ -458,16 +475,12 @@ #if EMISSIVE_MAP #if @shPropertyString(emissiveMapUVSet) - shOutputColour(0).xyz += shSample(emissiveMap, UV.zw).xyz; + shOutputColour(0).xyz += shSample(emissiveMap, newUV.zw).xyz; #else - shOutputColour(0).xyz += shSample(emissiveMap, UV.xy).xyz; + shOutputColour(0).xyz += shSample(emissiveMap, newUV.xy).xyz; #endif #endif -#if ENV_MAP || SPECULAR - float3 eyeDir = normalize(cameraPosObjSpace.xyz - objSpacePositionPassthrough.xyz); -#endif - #if ENV_MAP // Everything looks better with fresnel float facing = 1.0 - max(abs(dot(-eyeDir, normal)), 0); From bfd79bfbe6135430f02e58deb4bc6157110c039e Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Fri, 6 Dec 2013 07:36:16 +0100 Subject: [PATCH 140/142] Various fixes for CppCheck warnings. Signed-off-by: Lukasz Gromanowski --- apps/esmtool/record.hpp | 7 ++++++- apps/launcher/datafilespage.cpp | 10 ++-------- apps/opencs/model/filter/andnode.hpp | 2 -- apps/opencs/model/filter/ornode.hpp | 2 -- apps/opencs/model/filter/parser.cpp | 8 ++++---- apps/opencs/model/settings/settingsitem.hpp | 6 +++++- apps/openmw/mwmechanics/aisequence.cpp | 1 + apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwworld/weather.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 2 +- components/esm/loadland.cpp | 4 ++-- components/esm/loadland.hpp | 4 ++-- extern/oics/tinyxml.h | 2 +- libs/openengine/bullet/BtOgre.cpp | 2 ++ 14 files changed, 28 insertions(+), 26 deletions(-) diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index 78cf5d436e..45b6d04266 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -24,7 +24,12 @@ namespace EsmTool bool mPrintPlain; public: - RecordBase () { mPrintPlain = false; } + RecordBase () + : mFlags(0) + , mPrintPlain(false) + { + } + virtual ~RecordBase() {} const std::string &getId() const { diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index d5eb458f80..362d7562c6 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -75,14 +75,8 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) mLauncherSettings.setValue(QString("Profiles/currentprofile"), ui.profilesComboBox->currentText()); foreach(const ContentSelectorModel::EsmFile *item, items) { - - if (item->gameFiles().size() == 0) { - mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName()); - mGameSettings.setMultiValue(QString("content"), item->fileName()); - } else { - mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName()); - mGameSettings.setMultiValue(QString("content"), item->fileName()); - } + mLauncherSettings.setMultiValue(QString("Profiles/") + profileName, item->fileName()); + mGameSettings.setMultiValue(QString("content"), item->fileName()); } } diff --git a/apps/opencs/model/filter/andnode.hpp b/apps/opencs/model/filter/andnode.hpp index 980ea554c6..8871757008 100644 --- a/apps/opencs/model/filter/andnode.hpp +++ b/apps/opencs/model/filter/andnode.hpp @@ -7,8 +7,6 @@ namespace CSMFilter { class AndNode : public NAryNode { - bool mAnd; - public: AndNode (const std::vector >& nodes); diff --git a/apps/opencs/model/filter/ornode.hpp b/apps/opencs/model/filter/ornode.hpp index 63ed2f10ce..c39e350956 100644 --- a/apps/opencs/model/filter/ornode.hpp +++ b/apps/opencs/model/filter/ornode.hpp @@ -7,8 +7,6 @@ namespace CSMFilter { class OrNode : public NAryNode { - bool mAnd; - public: OrNode (const std::vector >& nodes); diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index 8f4fcb70c9..6e286d943b 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -61,11 +61,11 @@ namespace CSMFilter bool isString() const; }; - Token::Token (Type type) : mType (type) {} + Token::Token (Type type) : mType (type), mNumber(0.0) {} - Token::Token (Type type, const std::string& string) : mType (type), mString (string) {} + Token::Token (Type type, const std::string& string) : mType (type), mString (string), mNumber(0.0) {} - Token::Token (const std::string& string) : mType (Type_String), mString (string) {} + Token::Token (const std::string& string) : mType (Type_String), mString (string), mNumber(0.0) {} Token::Token (double number) : mType (Type_Number), mNumber (number) {} @@ -614,4 +614,4 @@ boost::shared_ptr CSMFilter::Parser::getFilter() const throw std::logic_error ("No filter available"); return mFilter; -} \ No newline at end of file +} diff --git a/apps/opencs/model/settings/settingsitem.hpp b/apps/opencs/model/settings/settingsitem.hpp index a1daee4ac9..87a85e8e4e 100644 --- a/apps/opencs/model/settings/settingsitem.hpp +++ b/apps/opencs/model/settings/settingsitem.hpp @@ -44,7 +44,11 @@ namespace CSMSettings inline QStringPair *getValuePair() { return mValuePair; } /// set value range (spinbox / integer use) - inline void setValuePair (QStringPair valuePair) { mValuePair = new QStringPair(valuePair); } + inline void setValuePair (QStringPair valuePair) + { + delete mValuePair; + mValuePair = new QStringPair(valuePair); + } inline bool isMultivalue () { return mIsMultiValue; } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index d5fb76eded..6d461e5f63 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -37,6 +37,7 @@ MWMechanics::AiSequence& MWMechanics::AiSequence::operator= (const AiSequence& s { clear(); copy (sequence); + mDone = sequence.mDone; } return *this; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 7505d34056..68f87ef4c7 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1109,7 +1109,7 @@ void CharacterController::resurrect() if(mAnimation) mAnimation->disable(mCurrentDeath); - mCurrentDeath.empty(); + mCurrentDeath.clear(); mDeathState = CharState_None; } diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 6f5dbe23fe..8b05d2256a 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -473,7 +473,7 @@ void WeatherManager::update(float duration) { // pick a random sound int sound = rand() % 4; - std::string* soundName; + std::string* soundName = NULL; if (sound == 0) soundName = &mThunderSoundID0; else if (sound == 1) soundName = &mThunderSoundID1; else if (sound == 2) soundName = &mThunderSoundID2; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 87a8d7d6f6..a1aad0011e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -214,7 +214,7 @@ namespace MWWorld : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), mSky (true), mCells (mStore, mEsm), mActivationDistanceOverride (mActivationDistanceOverride), - mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), + mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(false), mFacedDistance(FLT_MAX), mGodMode(false) { mPhysics = new PhysicsSystem(renderer); diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index ede200d79d..9c97eaa4de 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -160,10 +160,10 @@ void Land::loadData(int flags) } mEsm->restoreContext(mContext); - memset(mLandData->mNormals, 0, LAND_NUM_VERTS * 3); + memset(mLandData->mNormals, 0, sizeof(mLandData->mNormals)); if (mEsm->isNextSub("VNML")) { - condLoad(actual, DATA_VNML, mLandData->mNormals, sizeof(VNML)); + condLoad(actual, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); } if (mEsm->isNextSub("VHGT")) { diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 5649f99801..32abb77993 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -72,13 +72,13 @@ struct Land }; #pragma pack(pop) - typedef signed char VNML[LAND_NUM_VERTS * 3]; + typedef signed char VNML; struct LandData { float mHeightOffset; float mHeights[LAND_NUM_VERTS]; - VNML mNormals; + VNML mNormals[LAND_NUM_VERTS * 3]; uint16_t mTextures[LAND_NUM_TEXTURES]; bool mUsingColours; diff --git a/extern/oics/tinyxml.h b/extern/oics/tinyxml.h index e69913b59c..50ad417fe5 100644 --- a/extern/oics/tinyxml.h +++ b/extern/oics/tinyxml.h @@ -349,7 +349,7 @@ protected: { //strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe), // and the null terminator isn't needed - for( int i=0; p[i] && i<*length; ++i ) { + for( int i=0; i<*length && p[i]; ++i ) { _value[i] = p[i]; } return p + (*length); diff --git a/libs/openengine/bullet/BtOgre.cpp b/libs/openengine/bullet/BtOgre.cpp index b0fa07fd60..de9ea6f57c 100644 --- a/libs/openengine/bullet/BtOgre.cpp +++ b/libs/openengine/bullet/BtOgre.cpp @@ -819,6 +819,8 @@ namespace BtOgre { */ DynamicRenderable::DynamicRenderable() + : mVertexBufferCapacity(0) + , mIndexBufferCapacity(0) { } From baacf91de4bab1487061d3f838adf965f8a399a8 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 6 Dec 2013 11:37:29 +0100 Subject: [PATCH 141/142] Another windows build fix --- apps/openmw/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index b1bbb14f23..0dcc5b36d8 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -282,9 +282,11 @@ int main(int argc, char**argv) } catch (std::exception &e) { +#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE if (isatty(fileno(stdin))) std::cerr << "\nERROR: " << e.what() << std::endl; else +#endif SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); return 1; From e01085cac533300f0cb1cacb3bca1597a6547d49 Mon Sep 17 00:00:00 2001 From: Lukasz Gromanowski Date: Sat, 7 Dec 2013 02:57:30 +0100 Subject: [PATCH 142/142] Fixes #1015: Player status window scroll state resets on status change Removed resetting scroll state position. Signed-off-by: Lukasz Gromanowski --- apps/openmw/mwgui/statswindow.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 9facdac404..fabf1bccc6 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -420,8 +420,6 @@ namespace MWGui } mSkillWidgets.clear(); - mSkillView->setViewOffset (MyGUI::IntPoint(0,0)); - const int valueSize = 40; MyGUI::IntCoord coord1(10, 0, mSkillView->getWidth() - (10 + valueSize) - 24, 18); MyGUI::IntCoord coord2(coord1.left + coord1.width, coord1.top, valueSize, coord1.height);