From 7feba7e49816485c43fb7bb46c2895317eb56c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Tue, 7 Nov 2017 13:07:11 +0100 Subject: [PATCH 01/91] basic setup for 360 screenshots --- apps/openmw/mwbase/world.hpp | 1 + apps/openmw/mwinput/inputmanagerimp.cpp | 33 +- apps/openmw/mwmechanics/character.cpp.orig | 2464 ++++++++++++++++++++ apps/openmw/mwmechanics/character.cpp.rej | 46 + apps/openmw/mwrender/renderingmanager.cpp | 5 + apps/openmw/mwrender/renderingmanager.hpp | 1 + apps/openmw/mwstate/statemanagerimp.cpp | 1 - apps/openmw/mwworld/worldimp.cpp | 5 + apps/openmw/mwworld/worldimp.hpp | 1 + 9 files changed, 2553 insertions(+), 4 deletions(-) create mode 100644 apps/openmw/mwmechanics/character.cpp.orig create mode 100644 apps/openmw/mwmechanics/character.cpp.rej diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 038535939..80a9e11a1 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -450,6 +450,7 @@ namespace MWBase /// \todo this does not belong here virtual void screenshot (osg::Image* image, int w, int h) = 0; + virtual void screenshot360 (osg::Image* image, int w) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index a5bb93b6c..192eadbde 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1,6 +1,7 @@ #include "inputmanagerimp.hpp" #include +#include #include #include @@ -1014,10 +1015,36 @@ namespace MWInput void InputManager::screenshot() { - mScreenCaptureHandler->setFramesToCapture(1); - mScreenCaptureHandler->captureNextFrame(*mViewer); + // MOVE THIS ELSEWHERE LATER! + int screenshotW = 512; + osg::ref_ptr screenshot (new osg::Image); + MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(), screenshotW); - MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); + osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); + + if (!readerwriter) + { + std::cerr << "Error: Unable to write screenshot, can't find a jpg ReaderWriter" << std::endl; + return; + } + + std::ofstream outfile; + outfile.open("test.jpg"); + + osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*screenshot, outfile); + + if (!result.success()) + { + outfile << "Error: Unable to write screenshot: " << result.message() << " code " << result.status() << std::endl; + return; + } + + outfile.close(); + +// mScreenCaptureHandler->setFramesToCapture(1); +// mScreenCaptureHandler->captureNextFrame(*mViewer); + +// MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); } void InputManager::toggleInventory() diff --git a/apps/openmw/mwmechanics/character.cpp.orig b/apps/openmw/mwmechanics/character.cpp.orig new file mode 100644 index 000000000..f262850a4 --- /dev/null +++ b/apps/openmw/mwmechanics/character.cpp.orig @@ -0,0 +1,2464 @@ +/* + * OpenMW - The completely unofficial reimplementation of Morrowind + * + * This file (character.cpp) is part of the OpenMW package. + * + * OpenMW is distributed as free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License + * version 3, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 3 along with this program. If not, see + * http://www.gnu.org/licenses/ . + */ + +#include "character.hpp" + +#include + +#include + +#include + +#include + +#include "../mwrender/animation.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" + +#include "movement.hpp" +#include "npcstats.hpp" +#include "creaturestats.hpp" +#include "security.hpp" +#include "actorutil.hpp" +#include "spellcasting.hpp" + +namespace +{ + +// Wraps a value to (-PI, PI] +void wrap(float& rad) +{ + if (rad>0) + rad = std::fmod(rad+osg::PI, 2.0f*osg::PI)-osg::PI; + else + rad = std::fmod(rad-osg::PI, 2.0f*osg::PI)+osg::PI; +} + +std::string toString(int num) +{ + std::ostringstream stream; + stream << num; + return stream.str(); +} + +std::string getBestAttack (const ESM::Weapon* weapon) +{ + int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; + int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; + int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + if (slash == chop && slash == thrust) + return "slash"; + else if (thrust >= chop && thrust >= slash) + return "thrust"; + else if (slash >= chop && slash >= thrust) + return "slash"; + else + return "chop"; +} + +// Converts a movement Run state to its equivalent Walk state. +MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState state) +{ + using namespace MWMechanics; + CharacterState ret = state; + switch (state) + { + case CharState_RunForward: + ret = CharState_WalkForward; + break; + case CharState_RunBack: + ret = CharState_WalkBack; + break; + case CharState_RunLeft: + ret = CharState_WalkLeft; + break; + case CharState_RunRight: + ret = CharState_WalkRight; + break; + case CharState_SwimRunForward: + ret = CharState_SwimWalkForward; + break; + case CharState_SwimRunBack: + ret = CharState_SwimWalkBack; + break; + case CharState_SwimRunLeft: + ret = CharState_SwimWalkLeft; + break; + case CharState_SwimRunRight: + ret = CharState_SwimWalkRight; + break; + default: + break; + } + return ret; +} + +float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight) +{ + MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Store &store = world->getStore().get(); + + const float fallDistanceMin = store.find("fFallDamageDistanceMin")->getFloat(); + + if (fallHeight >= fallDistanceMin) + { + const float acrobaticsSkill = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Acrobatics)); + const float jumpSpellBonus = ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude(); + const float fallAcroBase = store.find("fFallAcroBase")->getFloat(); + const float fallAcroMult = store.find("fFallAcroMult")->getFloat(); + const float fallDistanceBase = store.find("fFallDistanceBase")->getFloat(); + const float fallDistanceMult = store.find("fFallDistanceMult")->getFloat(); + + float x = fallHeight - fallDistanceMin; + x -= (1.5f * acrobaticsSkill) + jumpSpellBonus; + x = std::max(0.0f, x); + + float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill); + x = fallDistanceBase + fallDistanceMult * x; + x *= a; + + return x; + } + return 0.f; +} + +} + +namespace MWMechanics +{ + +struct StateInfo { + CharacterState state; + const char groupname[32]; +}; + +static const StateInfo sMovementList[] = { + { CharState_WalkForward, "walkforward" }, + { CharState_WalkBack, "walkback" }, + { CharState_WalkLeft, "walkleft" }, + { CharState_WalkRight, "walkright" }, + + { CharState_SwimWalkForward, "swimwalkforward" }, + { CharState_SwimWalkBack, "swimwalkback" }, + { CharState_SwimWalkLeft, "swimwalkleft" }, + { CharState_SwimWalkRight, "swimwalkright" }, + + { CharState_RunForward, "runforward" }, + { CharState_RunBack, "runback" }, + { CharState_RunLeft, "runleft" }, + { CharState_RunRight, "runright" }, + + { CharState_SwimRunForward, "swimrunforward" }, + { CharState_SwimRunBack, "swimrunback" }, + { CharState_SwimRunLeft, "swimrunleft" }, + { CharState_SwimRunRight, "swimrunright" }, + + { CharState_SneakForward, "sneakforward" }, + { CharState_SneakBack, "sneakback" }, + { CharState_SneakLeft, "sneakleft" }, + { CharState_SneakRight, "sneakright" }, + + { CharState_Jump, "jump" }, + + { CharState_TurnLeft, "turnleft" }, + { CharState_TurnRight, "turnright" }, + { CharState_SwimTurnLeft, "swimturnleft" }, + { CharState_SwimTurnRight, "swimturnright" }, +}; +static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])]; + + +class FindCharState { + CharacterState state; + +public: + FindCharState(CharacterState _state) : state(_state) { } + + bool operator()(const StateInfo &info) const + { return info.state == state; } +}; + + +static const struct WeaponInfo { + WeaponType type; + const char shortgroup[16]; + const char longgroup[16]; +} sWeaponTypeList[] = { + { WeapType_HandToHand, "hh", "handtohand" }, + { WeapType_OneHand, "1h", "weapononehand" }, + { WeapType_TwoHand, "2c", "weapontwohand" }, + { WeapType_TwoWide, "2w", "weapontwowide" }, + { WeapType_BowAndArrow, "1h", "bowandarrow" }, + { WeapType_Crossbow, "crossbow", "crossbow" }, + { WeapType_Thrown, "1h", "throwweapon" }, + { WeapType_PickProbe, "1h", "pickprobe" }, + { WeapType_Spell, "spell", "spellcast" }, +}; +static const WeaponInfo *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])]; + +class FindWeaponType { + WeaponType type; + +public: + FindWeaponType(WeaponType _type) : type(_type) { } + + bool operator()(const WeaponInfo &weap) const + { return weap.type == type; } +}; + +std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const +{ + int numAnims=0; + while (mAnimation->hasAnimation(prefix + toString(numAnims+1))) + ++numAnims; + + int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] + if (num) + *num = roll; + return prefix + toString(roll); +} + +void CharacterController::refreshHitRecoilAnims() +{ + bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); + bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); + bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); + bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); + if(mHitState == CharState_None) + { + if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 + || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) + && mAnimation->hasAnimation("knockout")) + { + if (isSwimming && mAnimation->hasAnimation("swimknockout")) + { + mHitState = CharState_SwimKnockOut; + mCurrentHit = "swimknockout"; + } + else + { + mHitState = CharState_KnockOut; + mCurrentHit = "knockout"; + } + + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); + mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); + } + else if(knockdown && mAnimation->hasAnimation("knockdown")) + { + if (isSwimming && mAnimation->hasAnimation("swimknockdown")) + { + mHitState = CharState_SwimKnockDown; + mCurrentHit = "swimknockdown"; + } + else + { + mHitState = CharState_KnockDown; + mCurrentHit = "knockdown"; + } + + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); + } + else if (recovery) + { + std::string anim = isSwimming ? chooseRandomGroup("swimhit") : chooseRandomGroup("hit"); + if (isSwimming && mAnimation->hasAnimation(anim)) + { + mHitState = CharState_SwimHit; + mCurrentHit = anim; + mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); + } + else + { + anim = chooseRandomGroup("hit"); + if (mAnimation->hasAnimation(anim)) + { + mHitState = CharState_Hit; + mCurrentHit = anim; + mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); + } + } + } + else if (block && mAnimation->hasAnimation("shield")) + { + mHitState = CharState_Block; + mCurrentHit = "shield"; + MWRender::Animation::AnimPriority priorityBlock (Priority_Hit); + priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; + mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0); + } + + // Cancel upper body animations + if (isKnockedOut() || isKnockedDown()) + { + if (mUpperBodyState > UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } + else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_Nothing; + } + } + } + else if(!mAnimation->isPlaying(mCurrentHit)) + { + mCurrentHit.erase(); + if (knockdown) + mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); + if (recovery) + mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false); + if (block) + mPtr.getClass().getCreatureStats(mPtr).setBlock(false); + mHitState = CharState_None; + } + else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0) + { + mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown; + mAnimation->disable(mCurrentHit); + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); + } + if (mHitState != CharState_None) + mIdleState = CharState_None; +} + +void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, bool force) +{ + if(force || jump != mJumpState) + { + mIdleState = CharState_None; + bool startAtLoop = (jump == mJumpState); + mJumpState = jump; + + std::string jumpAnimName; + MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; + if(mJumpState != JumpState_None) + { + jumpAnimName = "jump"; + if(weap != sWeaponTypeListEnd) + { + jumpAnimName += weap->shortgroup; + if(!mAnimation->hasAnimation(jumpAnimName)) + { + jumpmask = MWRender::Animation::BlendMask_LowerBody; + jumpAnimName = "jump"; + } + } + } + + if(mJumpState == JumpState_InAir) + { + mAnimation->disable(mCurrentJump); + mCurrentJump = jumpAnimName; + if (mAnimation->hasAnimation("jump")) + mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false, + 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); + } + else if (mJumpState == JumpState_Landing) + { + if (startAtLoop) + mAnimation->disable(mCurrentJump); + + if (mAnimation->hasAnimation("jump")) + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, + 1.0f, "loop stop", "stop", 0.0f, 0); + } + else // JumpState_None + { + if (mCurrentJump.length() > 0) + { + mAnimation->disable(mCurrentJump); + mCurrentJump.clear(); + } + } + } +} + +void CharacterController::refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force) +{ + if(force || movement != mMovementState) + { + mMovementState = movement; + + if (movement != CharState_None) + mIdleState = CharState_None; + + std::string movementAnimName; + MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; + const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); + if(movestate != sMovementListEnd) + { + movementAnimName = movestate->groupname; + if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos) + { + movementAnimName += weap->shortgroup; + if(!mAnimation->hasAnimation(movementAnimName)) + { + movemask = MWRender::Animation::BlendMask_LowerBody; + movementAnimName = movestate->groupname; + } + } + + if(!mAnimation->hasAnimation(movementAnimName)) + { + std::string::size_type swimpos = movementAnimName.find("swim"); + if(swimpos == std::string::npos) + { + std::string::size_type runpos = movementAnimName.find("run"); + if (runpos != std::string::npos) + { + movementAnimName.replace(runpos, runpos+3, "walk"); + if (!mAnimation->hasAnimation(movementAnimName)) + movementAnimName.clear(); + } + else + movementAnimName.clear(); + } + else + { + movementAnimName.erase(swimpos, 4); + if (weap != sWeaponTypeListEnd) + { + std::string weapMovementAnimName = movementAnimName + weap->shortgroup; + if(mAnimation->hasAnimation(weapMovementAnimName)) + movementAnimName = weapMovementAnimName; + else + movemask = MWRender::Animation::BlendMask_LowerBody; + } + + if (!mAnimation->hasAnimation(movementAnimName)) + movementAnimName.clear(); + } + } + } + + /* If we're playing the same animation, restart from the loop start instead of the + * beginning. */ + int mode = ((movementAnimName == mCurrentMovement) ? 2 : 1); + + mMovementAnimationControlled = true; + + mAnimation->disable(mCurrentMovement); + mCurrentMovement = movementAnimName; + if(!mCurrentMovement.empty()) + { + bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) + && !MWBase::Environment::get().getWorld()->isFlying(mPtr); + + // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity + // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. + std::string anim = mCurrentMovement; + mAdjustMovementAnimSpeed = true; + if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() + && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) + { + CharacterState walkState = runStateToWalkState(mMovementState); + const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState)); + anim = stateinfo->groupname; + + mMovementAnimSpeed = mAnimation->getVelocity(anim); + if (mMovementAnimSpeed <= 1.0f) + { + // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), + // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist + // we will play without any scaling. + // Makes the speed attribute of most water creatures totally useless. + // And again, this can not be fixed without patching game data. + mAdjustMovementAnimSpeed = false; + mMovementAnimSpeed = 1.f; + } + } + else + { + mMovementAnimSpeed = mAnimation->getVelocity(anim); + + if (mMovementAnimSpeed <= 1.0f) + { + // The first person anims don't have any velocity to calculate a speed multiplier from. + // We use the third person velocities instead. + // FIXME: should be pulled from the actual animation, but it is not presently loaded. + mMovementAnimSpeed = (isrunning ? 222.857f : 154.064f); + mMovementAnimationControlled = false; + } + } + + mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, + 1.f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul, true); + } + } +} + +void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force) +{ + if(force || idle != mIdleState || + ((idle == mIdleState) && !mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) + { + mIdleState = idle; + size_t numLoops = ~0ul; + + std::string idleGroup; + MWRender::Animation::AnimPriority idlePriority (Priority_Default); + // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to + // "idle"+weapon or "idle". + if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim")) + { + idleGroup = "idleswim"; + idlePriority = Priority_SwimIdle; + } + else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak")) + { + idleGroup = "idlesneak"; + idlePriority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; + } + else if(mIdleState != CharState_None) + { + idleGroup = "idle"; + if(weap != sWeaponTypeListEnd) + { + idleGroup += weap->shortgroup; + if(!mAnimation->hasAnimation(idleGroup)) + idleGroup = "idle"; + + // play until the Loop Stop key 2 to 5 times, then play until the Stop key + // this replicates original engine behavior for the "Idle1h" 1st-person animation + numLoops = 1 + Misc::Rng::rollDice(4); + } + } + + mAnimation->disable(mCurrentIdle); + mCurrentIdle = idleGroup; + if(!mCurrentIdle.empty()) + mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false, + 1.0f, "start", "stop", 0.0f, numLoops, true); + } +} + +void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) +{ + if (mPtr.getClass().isActor()) + refreshHitRecoilAnims(); + + const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); + if (!mPtr.getClass().isBipedal(mPtr)) + weap = sWeaponTypeListEnd; + + refreshJumpAnims(weap, jump, force); + refreshMovementAnims(weap, movement, force); + + // idle handled last as it can depend on the other states + // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), + // the idle animation should be displayed + if ((mUpperBodyState != UpperCharState_Nothing + || (mMovementState != CharState_None && !isTurning()) + || mHitState != CharState_None) + && !mPtr.getClass().isBipedal(mPtr)) + idle = CharState_None; + + refreshIdleAnims(weap, idle, force); +} + + +void getWeaponGroup(WeaponType weaptype, std::string &group) +{ + const WeaponInfo *info = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(weaptype)); + if(info != sWeaponTypeListEnd) + group = info->longgroup; + else + group.clear(); +} + + +MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype) +{ + if(stats.getDrawState() == DrawState_Spell) + { + *weaptype = WeapType_Spell; + return inv.end(); + } + + if(stats.getDrawState() == MWMechanics::DrawState_Weapon) + { + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon == inv.end()) + *weaptype = WeapType_HandToHand; + else + { + const std::string &type = weapon->getTypeName(); + if(type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) + *weaptype = WeapType_PickProbe; + else if(type == typeid(ESM::Weapon).name()) + { + MWWorld::LiveCellRef *ref = weapon->get(); + ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; + switch(weaponType) + { + case ESM::Weapon::ShortBladeOneHand: + case ESM::Weapon::LongBladeOneHand: + case ESM::Weapon::BluntOneHand: + case ESM::Weapon::AxeOneHand: + case ESM::Weapon::Arrow: + case ESM::Weapon::Bolt: + *weaptype = WeapType_OneHand; + break; + case ESM::Weapon::LongBladeTwoHand: + case ESM::Weapon::BluntTwoClose: + case ESM::Weapon::AxeTwoHand: + *weaptype = WeapType_TwoHand; + break; + case ESM::Weapon::BluntTwoWide: + case ESM::Weapon::SpearTwoWide: + *weaptype = WeapType_TwoWide; + break; + case ESM::Weapon::MarksmanBow: + *weaptype = WeapType_BowAndArrow; + break; + case ESM::Weapon::MarksmanCrossbow: + *weaptype = WeapType_Crossbow; + break; + case ESM::Weapon::MarksmanThrown: + *weaptype = WeapType_Thrown; + break; + } + } + } + + return weapon; + } + + return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); +} + +void CharacterController::playDeath(float startpoint, CharacterState death) +{ + switch (death) + { + case CharState_SwimDeath: + mCurrentDeath = "swimdeath"; + break; + case CharState_SwimDeathKnockDown: + mCurrentDeath = "swimdeathknockdown"; + break; + case CharState_SwimDeathKnockOut: + mCurrentDeath = "swimdeathknockout"; + break; + case CharState_DeathKnockDown: + mCurrentDeath = "deathknockdown"; + break; + case CharState_DeathKnockOut: + mCurrentDeath = "deathknockout"; + break; + default: + mCurrentDeath = "death" + toString(death - CharState_Death1 + 1); + } + mDeathState = death; + + mPtr.getClass().getCreatureStats(mPtr).setDeathAnimation(mDeathState - CharState_Death1); + + // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually. + // Note that these animations wouldn't actually be visible (due to the Death animation's priority being higher). + // However, they could still trigger text keys, such as Hit events, or sounds. + mMovementState = CharState_None; + mAnimation->disable(mCurrentMovement); + mCurrentMovement = ""; + mUpperBodyState = UpperCharState_Nothing; + mAnimation->disable(mCurrentWeapon); + mCurrentWeapon = ""; + mHitState = CharState_None; + mAnimation->disable(mCurrentHit); + mCurrentHit = ""; + mIdleState = CharState_None; + mAnimation->disable(mCurrentIdle); + mCurrentIdle = ""; + mJumpState = JumpState_None; + mAnimation->disable(mCurrentJump); + mCurrentJump = ""; + mMovementAnimationControlled = true; + + mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, + false, 1.0f, "start", "stop", startpoint, 0); +} + +CharacterState CharacterController::chooseRandomDeathState() const +{ + int selected=0; + chooseRandomGroup("death", &selected); + return static_cast(CharState_Death1 + (selected-1)); +} + +void CharacterController::playRandomDeath(float startpoint) +{ + if (mPtr == getPlayer()) + { + // The first-person animations do not include death, so we need to + // force-switch to third person before playing the death animation. + MWBase::Environment::get().getWorld()->useDeathCamera(); + } + + if(mHitState == CharState_SwimKnockDown && mAnimation->hasAnimation("swimdeathknockdown")) + { + mDeathState = CharState_SwimDeathKnockDown; + } + else if(mHitState == CharState_SwimKnockOut && mAnimation->hasAnimation("swimdeathknockout")) + { + mDeathState = CharState_SwimDeathKnockOut; + } + else if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) + { + mDeathState = CharState_SwimDeath; + } + else if (mHitState == CharState_KnockDown && mAnimation->hasAnimation("deathknockdown")) + { + mDeathState = CharState_DeathKnockDown; + } + else if (mHitState == CharState_KnockOut && mAnimation->hasAnimation("deathknockout")) + { + mDeathState = CharState_DeathKnockOut; + } + else + { + mDeathState = chooseRandomDeathState(); + } + playDeath(startpoint, mDeathState); +} + +CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) + : mPtr(ptr) + , mAnimation(anim) + , mIdleState(CharState_None) + , mMovementState(CharState_None) + , mMovementAnimSpeed(0.f) + , mAdjustMovementAnimSpeed(false) + , mHasMovedInXY(false) + , mMovementAnimationControlled(true) + , mDeathState(CharState_None) + , mFloatToSurface(true) + , mHitState(CharState_None) + , mUpperBodyState(UpperCharState_Nothing) + , mJumpState(JumpState_None) + , mWeaponType(WeapType_None) + , mAttackStrength(0.f) + , mSkipAnim(false) + , mSecondsOfSwimming(0) + , mSecondsOfRunning(0) + , mTurnAnimationThreshold(0) + , mAttackingOrSpell(false) +{ + if(!mAnimation) + return; + + mAnimation->setTextKeyListener(this); + + const MWWorld::Class &cls = mPtr.getClass(); + if(cls.isActor()) + { + /* Accumulate along X/Y only for now, until we can figure out how we should + * handle knockout and death which moves the character down. */ + mAnimation->setAccumulation(osg::Vec3f(1.0f, 1.0f, 0.0f)); + + if (cls.hasInventoryStore(mPtr)) + { + getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); + if (mWeaponType != WeapType_None) + { + mUpperBodyState = UpperCharState_WeapEquiped; + getWeaponGroup(mWeaponType, mCurrentWeapon); + } + + if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) + { + mAnimation->showWeapons(true); + mAnimation->setWeaponGroup(mCurrentWeapon); + } + + mAnimation->showCarriedLeft(updateCarriedLeftVisible(mWeaponType)); + } + + if(!cls.getCreatureStats(mPtr).isDead()) + mIdleState = CharState_Idle; + else + { + const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); + if (cStats.isDeathAnimationFinished()) + { + // Set the death state, but don't play it yet + // We will play it in the first frame, but only if no script set the skipAnim flag + signed char deathanim = cStats.getDeathAnimation(); + if (deathanim == -1) + mDeathState = chooseRandomDeathState(); + else + mDeathState = static_cast(CharState_Death1 + deathanim); + + mFloatToSurface = false; + } + // else: nothing to do, will detect death in the next frame and start playing death animation + } + } + else + { + /* Don't accumulate with non-actors. */ + mAnimation->setAccumulation(osg::Vec3f(0.f, 0.f, 0.f)); + + mIdleState = CharState_Idle; + } + + + if(mDeathState == CharState_None) + refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); + + mAnimation->runAnimation(0.f); + + unpersistAnimationState(); +} + +CharacterController::~CharacterController() +{ + if (mAnimation) + { + persistAnimationState(); + mAnimation->setTextKeyListener(NULL); + } +} + +void split(const std::string &s, char delim, std::vector &elems) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } +} + +void CharacterController::handleTextKey(const std::string &groupname, const std::multimap::const_iterator &key, const std::multimap &map) +{ + const std::string &evt = key->second; + + if(evt.compare(0, 7, "sound: ") == 0) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->stopSound3D(mPtr, evt.substr(7)); + sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); + return; + } + if(evt.compare(0, 10, "soundgen: ") == 0) + { + std::string soundgen = evt.substr(10); + + // The event can optionally contain volume and pitch modifiers + float volume=1.f, pitch=1.f; + if (soundgen.find(" ") != std::string::npos) + { + std::vector tokens; + split(soundgen, ' ', tokens); + soundgen = tokens[0]; + if (tokens.size() >= 2) + { + std::stringstream stream; + stream << tokens[1]; + stream >> volume; + } + if (tokens.size() >= 3) + { + std::stringstream stream; + stream << tokens[2]; + stream >> pitch; + } + } + + std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); + if(!sound.empty()) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0 || evt.compare(10, evt.size()-10, "land") == 0) + { + // Don't make foot sounds local for the player, it makes sense to keep them + // positioned on the ground. + sndMgr->playSound3D(mPtr, sound, volume, pitch, MWSound::Type::Foot, + MWSound::PlayMode::NoPlayerLocal); + } + else + { + sndMgr->stopSound3D(mPtr, sound); + sndMgr->playSound3D(mPtr, sound, volume, pitch); + } + } + return; + } + + if(evt.compare(0, groupname.size(), groupname) != 0 || + evt.compare(groupname.size(), 2, ": ") != 0) + { + // Not ours, skip it + return; + } + size_t off = groupname.size()+2; + size_t len = evt.size() - off; + + if(evt.compare(off, len, "equip attach") == 0) + mAnimation->showWeapons(true); + else if(evt.compare(off, len, "unequip detach") == 0) + mAnimation->showWeapons(false); + else if(evt.compare(off, len, "chop hit") == 0) + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); + else if(evt.compare(off, len, "slash hit") == 0) + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); + else if(evt.compare(off, len, "thrust hit") == 0) + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); + else if(evt.compare(off, len, "hit") == 0) + { + if (groupname == "attack1" || groupname == "swimattack1") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); + else if (groupname == "attack2" || groupname == "swimattack2") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); + else if (groupname == "attack3" || groupname == "swimattack3") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); + else + mPtr.getClass().hit(mPtr, mAttackStrength); + } + else if (!groupname.empty() + && (groupname.compare(0, groupname.size()-1, "attack") == 0 || groupname.compare(0, groupname.size()-1, "swimattack") == 0) + && evt.compare(off, len, "start") == 0) + { + std::multimap::const_iterator hitKey = key; + + // Not all animations have a hit key defined. If there is none, the hit happens with the start key. + bool hasHitKey = false; + while (hitKey != map.end()) + { + if (hitKey->second == groupname + ": hit") + { + hasHitKey = true; + break; + } + if (hitKey->second == groupname + ": stop") + break; + ++hitKey; + } + if (!hasHitKey) + { + if (groupname == "attack1" || groupname == "swimattack1") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); + else if (groupname == "attack2" || groupname == "swimattack2") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); + else if (groupname == "attack3" || groupname == "swimattack3") + mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); + } + } + else if (evt.compare(off, len, "shoot attach") == 0) + mAnimation->attachArrow(); + else if (evt.compare(off, len, "shoot release") == 0) + mAnimation->releaseArrow(mAttackStrength); + else if (evt.compare(off, len, "shoot follow attach") == 0) + mAnimation->attachArrow(); + + else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release" + // Make sure this key is actually for the RangeType we are casting. The flame atronach has + // the same animation for all range types, so there are 3 "release" keys on the same time, one for each range type. + && evt.compare(off, len, mAttackType + " release") == 0) + { + MWBase::Environment::get().getWorld()->castSpell(mPtr); + } + + else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) + mPtr.getClass().block(mPtr); +} + +void CharacterController::updatePtr(const MWWorld::Ptr &ptr) +{ + mPtr = ptr; +} + +void CharacterController::updateIdleStormState(bool inwater) +{ + bool inStormDirection = false; + if (MWBase::Environment::get().getWorld()->isInStorm()) + { + osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); + osg::Vec3f characterDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); + inStormDirection = std::acos(stormDirection * characterDirection / (stormDirection.length() * characterDirection.length())) + > osg::DegreesToRadians(120.f); + } + if (inStormDirection && !inwater && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm")) + { + float complete = 0; + mAnimation->getInfo("idlestorm", &complete); + + if (complete == 0) + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, + 1.0f, "start", "loop start", 0.0f, 0); + else if (complete == 1) + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, + 1.0f, "loop start", "loop stop", 0.0f, ~0ul); + } + else + { + if (mUpperBodyState == UpperCharState_Nothing) + { + if (mAnimation->isPlaying("idlestorm")) + { + if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) + { + mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, true, + 1.0f, "loop stop", "stop", 0.0f, 0); + } + } + } + else + mAnimation->disable("idlestorm"); + } +} + +bool CharacterController::updateCreatureState() +{ + const MWWorld::Class &cls = mPtr.getClass(); + CreatureStats &stats = cls.getCreatureStats(mPtr); + + WeaponType weapType = WeapType_None; + if(stats.getDrawState() == DrawState_Weapon) + weapType = WeapType_HandToHand; + else if (stats.getDrawState() == DrawState_Spell) + weapType = WeapType_Spell; + + if (weapType != mWeaponType) + { + mWeaponType = weapType; + if (mAnimation->isPlaying(mCurrentWeapon)) + mAnimation->disable(mCurrentWeapon); + } + + if(mAttackingOrSpell) + { + if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) + { + MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); + + std::string startKey = "start"; + std::string stopKey = "stop"; + if (weapType == WeapType_Spell) + { + const std::string spellid = stats.getSpells().getSelectedSpell(); + if (!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) + { + MWMechanics::CastSpell cast(mPtr, NULL); + cast.playSpellCastingEffects(spellid); + + if (!mAnimation->hasAnimation("spellcast")) + MWBase::Environment::get().getWorld()->castSpell(mPtr); // No "release" text key to use, so cast immediately + else + { + const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellid); + const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); + + switch(effectentry.mRange) + { + case 0: mAttackType = "self"; break; + case 1: mAttackType = "touch"; break; + case 2: mAttackType = "target"; break; + } + + startKey = mAttackType + " " + startKey; + stopKey = mAttackType + " " + stopKey; + mCurrentWeapon = "spellcast"; + } + } + else + mCurrentWeapon = ""; + } + if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation + { + bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); + int roll = Misc::Rng::rollDice(3); // [0, 2] + if (roll == 0) + mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack1") ? "swimattack1" : "attack1"; + else if (roll == 1) + mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack2") ? "swimattack2" : "attack2"; + else + mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack3") ? "swimattack3" : "attack3"; + } + + if (!mCurrentWeapon.empty()) + { + mAnimation->play(mCurrentWeapon, Priority_Weapon, + MWRender::Animation::BlendMask_All, true, + 1, startKey, stopKey, + 0.0f, 0); + mUpperBodyState = UpperCharState_StartToMinAttack; + + mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); + + if (weapType == WeapType_HandToHand) + playSwishSound(0.0f); + } + } + + mAttackingOrSpell = false; + } + + bool animPlaying = mAnimation->getInfo(mCurrentWeapon); + if (!animPlaying) + mUpperBodyState = UpperCharState_Nothing; + return false; +} + +bool CharacterController::updateCarriedLeftVisible(WeaponType weaptype) const +{ + // Shields/torches shouldn't be visible during any operation involving two hands + // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", + // but they are also present in weapon drawing animation. + switch (weaptype) + { + case WeapType_Spell: + case WeapType_BowAndArrow: + case WeapType_Crossbow: + case WeapType_HandToHand: + case WeapType_TwoHand: + case WeapType_TwoWide: + return false; + default: + return true; + } +} + +bool CharacterController::updateWeaponState() +{ + const MWWorld::Class &cls = mPtr.getClass(); + CreatureStats &stats = cls.getCreatureStats(mPtr); + WeaponType weaptype = WeapType_None; + if(stats.getDrawState() == DrawState_Weapon) + weaptype = WeapType_HandToHand; + else if (stats.getDrawState() == DrawState_Spell) + weaptype = WeapType_Spell; + + const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); + + std::string soundid; + if (mPtr.getClass().hasInventoryStore(mPtr)) + { + MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + if(weapon != inv.end() && !(weaptype == WeapType_None && mWeaponType == WeapType_Spell)) + { + soundid = (weaptype == WeapType_None) ? + weapon->getClass().getDownSoundId(*weapon) : + weapon->getClass().getUpSoundId(*weapon); + } + } + + MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); + priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; + + bool forcestateupdate = false; + + // We should not play equipping animation and sound during weapon->weapon transition + bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell && + mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell; + + if(weaptype != mWeaponType && !isKnockedOut() && + !isKnockedDown() && !isRecovery()) + { + forcestateupdate = true; + + mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); + + std::string weapgroup; + if(weaptype == WeapType_None) + { + if ((!isWerewolf || mWeaponType != WeapType_Spell)) + { + getWeaponGroup(mWeaponType, weapgroup); + mAnimation->play(weapgroup, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + 1.0f, "unequip start", "unequip stop", 0.0f, 0); + mUpperBodyState = UpperCharState_UnEquipingWeap; + } + } + else + { + getWeaponGroup(weaptype, weapgroup); + mAnimation->setWeaponGroup(weapgroup); + + if (!isStillWeapon) + { + mAnimation->showWeapons(false); + mAnimation->play(weapgroup, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + 1.0f, "equip start", "equip stop", 0.0f, 0); + mUpperBodyState = UpperCharState_EquipingWeap; + } + + if(isWerewolf) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Sound *sound = store.get().searchRandom("WolfEquip"); + if(sound) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); + } + } + } + + if(!soundid.empty() && !isStillWeapon) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mPtr, soundid, 1.0f, 1.0f); + } + + mWeaponType = weaptype; + getWeaponGroup(mWeaponType, mCurrentWeapon); + } + + if(isWerewolf) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) + && mHasMovedInXY + && !MWBase::Environment::get().getWorld()->isSwimming(mPtr) + && mWeaponType == WeapType_None) + { + if(!sndMgr->getSoundPlaying(mPtr, "WolfRun")) + sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWSound::Type::Sfx, + MWSound::PlayMode::Loop); + } + else + sndMgr->stopSound3D(mPtr, "WolfRun"); + } + + // Cancel attack if we no longer have ammunition + bool ammunition = true; + bool isWeapon = false; + float weapSpeed = 1.f; + if (mPtr.getClass().hasInventoryStore(mPtr)) + { + MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()); + if(isWeapon) + weapSpeed = weapon->get()->mBase->mData.mSpeed; + + MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (mWeaponType == WeapType_Crossbow) + ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt); + else if (mWeaponType == WeapType_BowAndArrow) + ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow); + if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } + } + + float complete; + bool animPlaying; + if(mAttackingOrSpell) + { + mIdleState = CharState_None; + if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) + { + MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); + mAttackStrength = 0; + if(mWeaponType == WeapType_Spell) + { + // Unset casting flag, otherwise pressing the mouse button down would + // continue casting every frame if there is no animation + mAttackingOrSpell = false; + if (mPtr == getPlayer()) + { + MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); + } + + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + + // For the player, set the spell we want to cast + // This has to be done at the start of the casting animation, + // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) + if (mPtr == getPlayer()) + { + std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); + stats.getSpells().setSelectedSpell(selectedSpell); + } + std::string spellid = stats.getSpells().getSelectedSpell(); + + if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) + { + MWMechanics::CastSpell cast(mPtr, NULL); + cast.playSpellCastingEffects(spellid); + + const ESM::Spell *spell = store.get().find(spellid); + const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back(); + const ESM::MagicEffect *effect; + + effect = store.get().find(lastEffect.mEffectID); // use last effect of list for color of VFX_Hands + + const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); + + for (size_t iter = 0; iter < spell->mEffects.mList.size(); ++iter) // play hands vfx for each effect + { + if (mAnimation->getNode("Bip01 L Hand")) + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); + + if (mAnimation->getNode("Bip01 R Hand")) + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); + } + + const ESM::ENAMstruct &firstEffect = spell->mEffects.mList.at(0); // first effect used for casting animation + + switch(firstEffect.mRange) + { + case 0: mAttackType = "self"; break; + case 1: mAttackType = "touch"; break; + case 2: mAttackType = "target"; break; + } + + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + weapSpeed, mAttackType+" start", mAttackType+" stop", + 0.0f, 0); + mUpperBodyState = UpperCharState_CastingSpell; + } + if (mPtr.getClass().hasInventoryStore(mPtr)) + { + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + if (inv.getSelectedEnchantItem() != inv.end()) + { + // Enchanted items cast immediately (no animation) + MWBase::Environment::get().getWorld()->castSpell(mPtr); + } + } + + } + else if(mWeaponType == WeapType_PickProbe) + { + MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + MWWorld::Ptr item = *weapon; + // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject(); + std::string resultMessage, resultSound; + + if(!target.isEmpty()) + { + if(item.getTypeName() == typeid(ESM::Lockpick).name()) + Security(mPtr).pickLock(target, item, resultMessage, resultSound); + else if(item.getTypeName() == typeid(ESM::Probe).name()) + Security(mPtr).probeTrap(target, item, resultMessage, resultSound); + } + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + 1.0f, "start", "stop", 0.0, 0); + mUpperBodyState = UpperCharState_FollowStartToFollowStop; + + if(!resultMessage.empty()) + MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); + if(!resultSound.empty()) + MWBase::Environment::get().getSoundManager()->playSound3D(target, resultSound, + 1.0f, 1.0f); + } + else if (ammunition) + { + if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || + mWeaponType == WeapType_Thrown) + mAttackType = "shoot"; + else + { + if(mPtr == getPlayer()) + { + if (isWeapon) + { + if (Settings::Manager::getBool("best attack", "Game")) + { + MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + mAttackType = getBestAttack(weapon->get()->mBase); + } + else + setAttackTypeBasedOnMovement(); + } + else + setAttackTypeRandomly(mAttackType); + } + // else if (mPtr != getPlayer()) use mAttackType set by AiCombat + } + + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, + weapSpeed, mAttackType+" start", mAttackType+" min attack", + 0.0f, 0); + mUpperBodyState = UpperCharState_StartToMinAttack; + } + } + + animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) + mAttackStrength = complete; + } + else + { + animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); + if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) + { + float attackStrength = complete; + if (!mPtr.getClass().isNpc()) + { + // most creatures don't actually have an attack wind-up animation, so use a uniform random value + // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings) + // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far. + attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); + } + + if(mWeaponType != WeapType_Crossbow && mWeaponType != WeapType_BowAndArrow) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + + if(isWerewolf) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const ESM::Sound *sound = store.get().searchRandom("WolfSwing"); + if(sound) + sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); + } + else + { + playSwishSound(attackStrength); + } + } + mAttackStrength = attackStrength; + + mAnimation->disable(mCurrentWeapon); + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, + weapSpeed, mAttackType+" max attack", mAttackType+" min hit", + 1.0f-complete, 0); + + complete = 0.f; + mUpperBodyState = UpperCharState_MaxAttackToMinHit; + } + else if (isKnockedDown()) + { + if (mUpperBodyState > UpperCharState_WeapEquiped) + mUpperBodyState = UpperCharState_WeapEquiped; + mAnimation->disable(mCurrentWeapon); + } + } + + mAnimation->setPitchFactor(0.f); + if (mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown) + { + switch (mUpperBodyState) + { + case UpperCharState_StartToMinAttack: + mAnimation->setPitchFactor(complete); + break; + case UpperCharState_MinAttackToMaxAttack: + case UpperCharState_MaxAttackToMinHit: + case UpperCharState_MinHitToHit: + mAnimation->setPitchFactor(1.f); + break; + case UpperCharState_FollowStartToFollowStop: + if (animPlaying) + mAnimation->setPitchFactor(1.f-complete); + break; + default: + break; + } + } + else if (mWeaponType == WeapType_Crossbow) + { + switch (mUpperBodyState) + { + case UpperCharState_EquipingWeap: + mAnimation->setPitchFactor(complete); + break; + case UpperCharState_UnEquipingWeap: + mAnimation->setPitchFactor(1.f-complete); + break; + case UpperCharState_WeapEquiped: + case UpperCharState_StartToMinAttack: + case UpperCharState_MinAttackToMaxAttack: + case UpperCharState_MaxAttackToMinHit: + case UpperCharState_MinHitToHit: + case UpperCharState_FollowStartToFollowStop: + mAnimation->setPitchFactor(1.f); + break; + default: + break; + } + } + + if(!animPlaying) + { + if(mUpperBodyState == UpperCharState_EquipingWeap || + mUpperBodyState == UpperCharState_FollowStartToFollowStop || + mUpperBodyState == UpperCharState_CastingSpell) + { + if (ammunition && mWeaponType == WeapType_Crossbow) + mAnimation->attachArrow(); + + mUpperBodyState = UpperCharState_WeapEquiped; + } + else if(mUpperBodyState == UpperCharState_UnEquipingWeap) + mUpperBodyState = UpperCharState_Nothing; + } + else if(complete >= 1.0f) + { + std::string start, stop; + switch(mUpperBodyState) + { + case UpperCharState_StartToMinAttack: + start = mAttackType+" min attack"; + stop = mAttackType+" max attack"; + mUpperBodyState = UpperCharState_MinAttackToMaxAttack; + break; + case UpperCharState_MinAttackToMaxAttack: + //hack to avoid body pos desync when jumping/sneaking in 'max attack' state + if(!mAnimation->isPlaying(mCurrentWeapon)) + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, + 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); + break; + case UpperCharState_MaxAttackToMinHit: + if(mAttackType == "shoot") + { + start = mAttackType+" min hit"; + stop = mAttackType+" release"; + } + else + { + start = mAttackType+" min hit"; + stop = mAttackType+" hit"; + } + mUpperBodyState = UpperCharState_MinHitToHit; + break; + case UpperCharState_MinHitToHit: + if(mAttackType == "shoot") + { + start = mAttackType+" follow start"; + stop = mAttackType+" follow stop"; + } + else + { + float str = mAttackStrength; + start = mAttackType+((str < 0.5f) ? " small follow start" + : (str < 1.0f) ? " medium follow start" + : " large follow start"); + stop = mAttackType+((str < 0.5f) ? " small follow stop" + : (str < 1.0f) ? " medium follow stop" + : " large follow stop"); + } + mUpperBodyState = UpperCharState_FollowStartToFollowStop; + break; + default: + break; + } + + if(!start.empty()) + { + mAnimation->disable(mCurrentWeapon); + if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, true, + weapSpeed, start, stop, 0.0f, 0); + else + mAnimation->play(mCurrentWeapon, priorityWeapon, + MWRender::Animation::BlendMask_All, false, + weapSpeed, start, stop, 0.0f, 0); + } + } + + if (mPtr.getClass().hasInventoryStore(mPtr)) + { + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() + && updateCarriedLeftVisible(mWeaponType)) + + { + mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, + false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); + } + else if (mAnimation->isPlaying("torch")) + { + mAnimation->disable("torch"); + } + } + + mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped); + + return forcestateupdate; +} + +void CharacterController::updateAnimQueue() +{ + if(mAnimQueue.size() > 1) + { + if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + { + mAnimation->disable(mAnimQueue.front().mGroup); + mAnimQueue.pop_front(); + + bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, + MWRender::Animation::BlendMask_All, false, + 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); + } + } + + if(!mAnimQueue.empty()) + mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); +} + +void CharacterController::update(float duration) +{ + MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Class &cls = mPtr.getClass(); + osg::Vec3f movement(0.f, 0.f, 0.f); + float speed = 0.f; + + updateMagicEffects(); + + bool godmode = mPtr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); + + if(!cls.isActor()) + updateAnimQueue(); + else if(!cls.getCreatureStats(mPtr).isDead()) + { + bool onground = world->isOnGround(mPtr); + bool inwater = world->isSwimming(mPtr); + bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak); + bool flying = world->isFlying(mPtr); + // Can't run while flying (see speed formula in Npc/Creature::getSpeed) + bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying; + CreatureStats &stats = cls.getCreatureStats(mPtr); + + //Force Jump Logic + + bool isMoving = (std::abs(cls.getMovementSettings(mPtr).mPosition[0]) > .5 || std::abs(cls.getMovementSettings(mPtr).mPosition[1]) > .5); + if(!inwater && !flying) + { + //Force Jump + if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)) + { + if(onground) + { + cls.getMovementSettings(mPtr).mPosition[2] = 1; + } + else + cls.getMovementSettings(mPtr).mPosition[2] = 0; + } + //Force Move Jump, only jump if they're otherwise moving + if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving) + { + + if(onground) + { + cls.getMovementSettings(mPtr).mPosition[2] = 1; + } + else + cls.getMovementSettings(mPtr).mPosition[2] = 0; + } + } + + osg::Vec3f vec(cls.getMovementSettings(mPtr).asVec3()); + vec.normalize(); + + if(mHitState != CharState_None && mJumpState == JumpState_None) + vec = osg::Vec3f(0.f, 0.f, 0.f); + osg::Vec3f rot = cls.getRotationVector(mPtr); + + speed = cls.getSpeed(mPtr); + + vec.x() *= speed; + vec.y() *= speed; + + CharacterState movestate = CharState_None; + CharacterState idlestate = CharState_SpecialIdle; + JumpingState jumpstate = JumpState_None; + + bool forcestateupdate = false; + + mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f; + isrunning = isrunning && mHasMovedInXY; + + // advance athletics + if(mHasMovedInXY && mPtr == getPlayer()) + { + if(inwater) + { + mSecondsOfSwimming += duration; + while(mSecondsOfSwimming > 1) + { + cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1); + mSecondsOfSwimming -= 1; + } + } + else if(isrunning && !sneak) + { + mSecondsOfRunning += duration; + while(mSecondsOfRunning > 1) + { + cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0); + mSecondsOfRunning -= 1; + } + } + } + + // reduce fatigue + const MWWorld::Store &gmst = world->getStore().get(); + float fatigueLoss = 0; + static const float fFatigueRunBase = gmst.find("fFatigueRunBase")->getFloat(); + static const float fFatigueRunMult = gmst.find("fFatigueRunMult")->getFloat(); + static const float fFatigueSwimWalkBase = gmst.find("fFatigueSwimWalkBase")->getFloat(); + static const float fFatigueSwimRunBase = gmst.find("fFatigueSwimRunBase")->getFloat(); + static const float fFatigueSwimWalkMult = gmst.find("fFatigueSwimWalkMult")->getFloat(); + static const float fFatigueSwimRunMult = gmst.find("fFatigueSwimRunMult")->getFloat(); + static const float fFatigueSneakBase = gmst.find("fFatigueSneakBase")->getFloat(); + static const float fFatigueSneakMult = gmst.find("fFatigueSneakMult")->getFloat(); + + if (cls.getEncumbrance(mPtr) <= cls.getCapacity(mPtr)) + { + const float encumbrance = cls.getEncumbrance(mPtr) / cls.getCapacity(mPtr); + + if (sneak) + fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult; + else + { + if (inwater) + { + if (!isrunning) + fatigueLoss = fFatigueSwimWalkBase + encumbrance * fFatigueSwimWalkMult; + else + fatigueLoss = fFatigueSwimRunBase + encumbrance * fFatigueSwimRunMult; + } + else if (isrunning) + fatigueLoss = fFatigueRunBase + encumbrance * fFatigueRunMult; + } + } + fatigueLoss *= duration; + DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); + + if (!godmode) + { + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss, fatigue.getCurrent() < 0); + cls.getCreatureStats(mPtr).setFatigue(fatigue); + } + + if(sneak || inwater || flying) + vec.z() = 0.0f; + + bool inJump = true; + if(!onground && !flying && !inwater) + { + // In the air (either getting up —ascending part of jump— or falling). + + forcestateupdate = (mJumpState != JumpState_InAir); + jumpstate = JumpState_InAir; + + static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); + static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); + float factor = fJumpMoveBase + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics)/100.f; + factor = std::min(1.f, factor); + vec.x() *= factor; + vec.y() *= factor; + vec.z() = 0.0f; + } + else if(vec.z() > 0.0f && mJumpState == JumpState_None) + { + // Started a jump. + float z = cls.getJump(mPtr); + if (z > 0) + { + if(vec.x() == 0 && vec.y() == 0) + vec = osg::Vec3f(0.0f, 0.0f, z); + else + { + osg::Vec3f lat (vec.x(), vec.y(), 0.0f); + lat.normalize(); + vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f; + } + + // advance acrobatics + if (mPtr == getPlayer()) + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); + + // decrease fatigue + const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->getFloat(); + const float fatigueJumpMult = gmst.find("fFatigueJumpMult")->getFloat(); + float normalizedEncumbrance = mPtr.getClass().getNormalizedEncumbrance(mPtr); + if (normalizedEncumbrance > 1) + normalizedEncumbrance = 1; + const float fatigueDecrease = fatigueJumpBase + normalizedEncumbrance * fatigueJumpMult; + + if (!godmode) + { + fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); + cls.getCreatureStats(mPtr).setFatigue(fatigue); + } + } + } + else if(mJumpState == JumpState_InAir && !inwater && !flying) + { + forcestateupdate = true; + jumpstate = JumpState_Landing; + vec.z() = 0.0f; + + float height = cls.getCreatureStats(mPtr).land(); + float healthLost = getFallDamage(mPtr, height); + + if (healthLost > 0.0f) + { + const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm(); + + // inflict fall damages + if (!godmode) + { + DynamicStat health = cls.getCreatureStats(mPtr).getHealth(); + float realHealthLost = static_cast(healthLost * (1.0f - 0.25f * fatigueTerm)); + health.setCurrent(health.getCurrent() - realHealthLost); + cls.getCreatureStats(mPtr).setHealth(health); + cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), osg::Vec3f(), true); + } + + const int acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics); + if (healthLost > (acrobaticsSkill * fatigueTerm)) + { + cls.getCreatureStats(mPtr).setKnockedDown(true); + } + else + { + // report acrobatics progression + if (mPtr == getPlayer()) + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); + } + } + } + else + { + jumpstate = mAnimation->isPlaying(mCurrentJump) ? JumpState_Landing : JumpState_None; + + vec.z() = 0.0f; + + inJump = false; + + if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) + { + if(vec.x() > 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) + : (sneak ? CharState_SneakRight + : (isrunning ? CharState_RunRight : CharState_WalkRight))); + else if(vec.x() < 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) + : (sneak ? CharState_SneakLeft + : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); + } + else if(vec.y() != 0.0f) + { + if(vec.y() > 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) + : (sneak ? CharState_SneakForward + : (isrunning ? CharState_RunForward : CharState_WalkForward))); + else if(vec.y() < 0.0f) + movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) + : (sneak ? CharState_SneakBack + : (isrunning ? CharState_RunBack : CharState_WalkBack))); + } + else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) + { + if(rot.z() > 0.0f) + { + movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; + mAnimation->disable(mCurrentJump); + } + else if(rot.z() < 0.0f) + { + movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; + mAnimation->disable(mCurrentJump); + } + } + } + + mTurnAnimationThreshold -= duration; + if (isTurning()) + mTurnAnimationThreshold = 0.05f; + else if (movestate == CharState_None && isTurning() + && mTurnAnimationThreshold > 0) + { + movestate = mMovementState; + } + + if(movestate != CharState_None && !isTurning()) + clearAnimQueue(); + + if(mAnimQueue.empty() || inwater || sneak) + { + idlestate = (inwater ? CharState_IdleSwim : (sneak && !inJump ? CharState_IdleSneak : CharState_Idle)); + } + else + updateAnimQueue(); + + if (!mSkipAnim) + { + // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. + if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) + forcestateupdate = updateWeaponState() || forcestateupdate; + else + forcestateupdate = updateCreatureState() || forcestateupdate; + + refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate); + updateIdleStormState(inwater); + } + + if (inJump) + mMovementAnimationControlled = false; + + if (isTurning()) + { + if (duration > 0) + mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI))); + } + else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed) + { + float speedmult = speed / mMovementAnimSpeed; + mAnimation->adjustSpeedMult(mCurrentMovement, speedmult); + } + + if (!mSkipAnim) + { + if(!isKnockedDown() && !isKnockedOut()) + { + if (rot != osg::Vec3f()) + world->rotateObject(mPtr, rot.x(), rot.y(), rot.z(), true); + } + else //avoid z-rotating for knockdown + { + if (rot.x() != 0 && rot.y() != 0) + world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true); + } + + if (!mMovementAnimationControlled) + world->queueMovement(mPtr, vec); + } + else + // We must always queue movement, even if there is none, to apply gravity. + world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); + + movement = vec; + cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; + // Can't reset jump state (mPosition[2]) here; we don't know for sure whether the PhysicSystem will actually handle it in this frame + // due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled. + + if (!mSkipAnim) + updateHeadTracking(duration); + } + else if(cls.getCreatureStats(mPtr).isDead()) + { + // initial start of death animation for actors that started the game as dead + // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag + if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty()) + { + playDeath(1.f, mDeathState); + } + // We must always queue movement, even if there is none, to apply gravity. + world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); + } + + osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); + if(duration > 0.0f) + moved /= duration; + else + moved = osg::Vec3f(0.f, 0.f, 0.f); + + // Ensure we're moving in generally the right direction... + if(speed > 0.f) + { + float l = moved.length(); + + if((movement.x() < 0.0f && movement.x() < moved.x()*2.0f) || + (movement.x() > 0.0f && movement.x() > moved.x()*2.0f)) + moved.x() = movement.x(); + if((movement.y() < 0.0f && movement.y() < moved.y()*2.0f) || + (movement.y() > 0.0f && movement.y() > moved.y()*2.0f)) + moved.y() = movement.y(); + if((movement.z() < 0.0f && movement.z() < moved.z()*2.0f) || + (movement.z() > 0.0f && movement.z() > moved.z()*2.0f)) + moved.z() = movement.z(); + // but keep the original speed + float newLength = moved.length(); + if (newLength > 0) + moved *= (l / newLength); + } + + if (mSkipAnim) + mAnimation->updateEffects(duration); + + if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr)) + moved.z() = 1.0; + + // Update movement + if(mMovementAnimationControlled && mPtr.getClass().isActor()) + world->queueMovement(mPtr, moved); + + mSkipAnim = false; + + mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead()); +} + +void CharacterController::persistAnimationState() +{ + ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); + + state.mScriptedAnims.clear(); + for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) + { + if (!iter->mPersist) + continue; + + ESM::AnimationState::ScriptedAnimation anim; + anim.mGroup = iter->mGroup; + + if (iter == mAnimQueue.begin()) + { + anim.mLoopCount = mAnimation->getCurrentLoopCount(anim.mGroup); + float complete; + mAnimation->getInfo(anim.mGroup, &complete, NULL); + anim.mTime = complete; + } + else + { + anim.mLoopCount = iter->mLoopCount; + anim.mTime = 0.f; + } + + state.mScriptedAnims.push_back(anim); + } +} + +void CharacterController::unpersistAnimationState() +{ + const ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); + + if (!state.mScriptedAnims.empty()) + { + clearAnimQueue(); + for (ESM::AnimationState::ScriptedAnimations::const_iterator iter = state.mScriptedAnims.begin(); iter != state.mScriptedAnims.end(); ++iter) + { + AnimationQueueEntry entry; + entry.mGroup = iter->mGroup; + entry.mLoopCount = iter->mLoopCount; + entry.mPersist = true; + + mAnimQueue.push_back(entry); + } + + const ESM::AnimationState::ScriptedAnimation& anim = state.mScriptedAnims.front(); + float complete = anim.mTime; + if (anim.mAbsolute) + { + float start = mAnimation->getTextKeyTime(anim.mGroup+": start"); + float stop = mAnimation->getTextKeyTime(anim.mGroup+": stop"); + float time = std::max(start, std::min(stop, anim.mTime)); + complete = (time - start) / (stop - start); + } + + mAnimation->disable(mCurrentIdle); + mCurrentIdle.clear(); + mIdleState = CharState_SpecialIdle; + + bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); + mAnimation->play(anim.mGroup, + Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, + "start", "stop", complete, anim.mLoopCount, loopfallback); + } +} + +bool CharacterController::playGroup(const std::string &groupname, int mode, int count, bool persist) +{ + if(!mAnimation || !mAnimation->hasAnimation(groupname)) + return false; + + // If this animation is a looped animation (has a "loop start" key) that is already playing + // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count + // and remove any other animations that were queued. + // This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners correctly. + if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && + mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 && + mAnimation->isPlaying(groupname)) + { + float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop stop"); + + if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key + endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop"); + + if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop)) + { + mAnimQueue.resize(1); + return true; + } + } + + count = std::max(count, 1); + + AnimationQueueEntry entry; + entry.mGroup = groupname; + entry.mLoopCount = count-1; + entry.mPersist = persist; + + if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) + { + clearAnimQueue(); + mAnimQueue.push_back(entry); + + mAnimation->disable(mCurrentIdle); + mCurrentIdle.clear(); + + mIdleState = CharState_SpecialIdle; + bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0); + mAnimation->play(groupname, Priority_Default, + MWRender::Animation::BlendMask_All, false, 1.0f, + ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback); + } + else if(mode == 0) + { + mAnimQueue.resize(1); + mAnimQueue.push_back(entry); + } + return true; +} + +void CharacterController::skipAnim() +{ + mSkipAnim = true; +} + +bool CharacterController::isAnimPlaying(const std::string &groupName) +{ + if(mAnimation == NULL) + return false; + return mAnimation->isPlaying(groupName); +} + + +void CharacterController::clearAnimQueue() +{ + if(!mAnimQueue.empty()) + mAnimation->disable(mAnimQueue.front().mGroup); + mAnimQueue.clear(); +} + +void CharacterController::forceStateUpdate() +{ + if(!mAnimation) + return; + clearAnimQueue(); + + refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); + if(mDeathState != CharState_None) + { + playRandomDeath(); + } + + mAnimation->runAnimation(0.f); +} + +CharacterController::KillResult CharacterController::kill() +{ + if (mDeathState == CharState_None) + { + playRandomDeath(); + + mAnimation->disable(mCurrentIdle); + + mIdleState = CharState_None; + mCurrentIdle.clear(); + return Result_DeathAnimStarted; + } + + MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); + if (isAnimPlaying(mCurrentDeath)) + return Result_DeathAnimPlaying; + if (!cStats.isDeathAnimationFinished()) + { + cStats.setDeathAnimationFinished(true); + return Result_DeathAnimJustFinished; + } + return Result_DeathAnimFinished; +} + +void CharacterController::resurrect() +{ + if(mDeathState == CharState_None) + return; + + if(mAnimation) + mAnimation->disable(mCurrentDeath); + mCurrentDeath.clear(); + mDeathState = CharState_None; +} + +void CharacterController::updateContinuousVfx() +{ + // Keeping track of when to stop a continuous VFX seems to be very difficult to do inside the spells code, + // as it's extremely spread out (ActiveSpells, Spells, InventoryStore effects, etc...) so we do it here. + + // Stop any effects that are no longer active + std::vector effects; + mAnimation->getLoopingEffects(effects); + + for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) + { + if (mPtr.getClass().getCreatureStats(mPtr).isDead() + || mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).getMagnitude() <= 0) + mAnimation->removeEffect(*it); + } +} + +void CharacterController::updateMagicEffects() +{ + if (!mPtr.getClass().isActor()) + return; + float alpha = 1.f; + if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555). + { + if (mPtr == getPlayer()) + alpha = 0.4f; + else + alpha = 0.f; + } + float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); + if (chameleon) + { + alpha *= std::max(0.2f, (100.f - chameleon)/100.f); + } + mAnimation->setAlpha(alpha); + + bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f; + mAnimation->setVampire(vampire); + + float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude(); + mAnimation->setLightEffect(light); +} + +void CharacterController::setAttackTypeBasedOnMovement() +{ + float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition; + + if (move[1] && !move[0]) // forward-backward + mAttackType = "thrust"; + else if (move[0] && !move[1]) //sideway + mAttackType = "slash"; + else + mAttackType = "chop"; +} + +bool CharacterController::isAttackPrepairing() const +{ + return mUpperBodyState == UpperCharState_StartToMinAttack || + mUpperBodyState == UpperCharState_MinAttackToMaxAttack; +} + +bool CharacterController::isReadyToBlock() const +{ + return updateCarriedLeftVisible(mWeaponType); +} + +bool CharacterController::isKnockedDown() const +{ + return mHitState == CharState_KnockDown || + mHitState == CharState_SwimKnockDown; +} + +bool CharacterController::isKnockedOut() const +{ + return mHitState == CharState_KnockOut || + mHitState == CharState_SwimKnockOut; +} + +bool CharacterController::isTurning() const +{ + return mMovementState == CharState_TurnLeft || + mMovementState == CharState_TurnRight || + mMovementState == CharState_SwimTurnLeft || + mMovementState == CharState_SwimTurnRight; +} + +bool CharacterController::isRecovery() const +{ + return mHitState == CharState_Hit || + mHitState == CharState_SwimHit; +} + +bool CharacterController::isAttackingOrSpell() const +{ + return mUpperBodyState != UpperCharState_Nothing && + mUpperBodyState != UpperCharState_WeapEquiped; +} + +bool CharacterController::isSneaking() const +{ + return mIdleState == CharState_IdleSneak || + mMovementState == CharState_SneakForward || + mMovementState == CharState_SneakBack || + mMovementState == CharState_SneakLeft || + mMovementState == CharState_SneakRight; +} + +bool CharacterController::isRunning() const +{ + return mMovementState == CharState_RunForward || + mMovementState == CharState_RunBack || + mMovementState == CharState_RunLeft || + mMovementState == CharState_RunRight || + mMovementState == CharState_SwimRunForward || + mMovementState == CharState_SwimRunBack || + mMovementState == CharState_SwimRunLeft || + mMovementState == CharState_SwimRunRight; +} + +void CharacterController::setAttackingOrSpell(bool attackingOrSpell) +{ + mAttackingOrSpell = attackingOrSpell; +} + +void CharacterController::setAIAttackType(const std::string& attackType) +{ + mAttackType = attackType; +} + +void CharacterController::setAttackTypeRandomly(std::string& attackType) +{ + float random = Misc::Rng::rollProbability(); + if (random >= 2/3.f) + attackType = "thrust"; + else if (random >= 1/3.f) + attackType = "slash"; + else + attackType = "chop"; +} + +bool CharacterController::readyToPrepareAttack() const +{ + return (mHitState == CharState_None || mHitState == CharState_Block) + && mUpperBodyState <= UpperCharState_WeapEquiped; +} + +bool CharacterController::readyToStartAttack() const +{ + if (mHitState != CharState_None && mHitState != CharState_Block) + return false; + + if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr)) + return mUpperBodyState == UpperCharState_WeapEquiped; + else + return mUpperBodyState == UpperCharState_Nothing; +} + +float CharacterController::getAttackStrength() const +{ + return mAttackStrength; +} + +void CharacterController::setActive(bool active) +{ + mAnimation->setActive(active); +} + +void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target) +{ + mHeadTrackTarget = target; +} + +void CharacterController::playSwishSound(float attackStrength) +{ + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + + std::string sound = "Weapon Swish"; + if(attackStrength < 0.5f) + sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack + else if(attackStrength < 1.0f) + sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack + else + sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack +} + +void CharacterController::updateHeadTracking(float duration) +{ + const osg::Node* head = mAnimation->getNode("Bip01 Head"); + if (!head) + return; + + float zAngleRadians = 0.f; + float xAngleRadians = 0.f; + + if (!mHeadTrackTarget.isEmpty()) + { + osg::NodePathList nodepaths = head->getParentalNodePaths(); + if (nodepaths.empty()) + return; + osg::Matrixf mat = osg::computeLocalToWorld(nodepaths[0]); + osg::Vec3f headPos = mat.getTrans(); + + osg::Vec3f direction; + if (const MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) + { + const osg::Node* node = anim->getNode("Head"); + if (node == NULL) + node = anim->getNode("Bip01 Head"); + if (node != NULL) + { + nodepaths = node->getParentalNodePaths(); + if (!nodepaths.empty()) + direction = osg::computeLocalToWorld(nodepaths[0]).getTrans() - headPos; + } + else + // no head node to look at, fall back to look at center of collision box + direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget); + } + direction.normalize(); + + if (!mPtr.getRefData().getBaseNode()) + return; + const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); + + zAngleRadians = std::atan2(direction.x(), direction.y()) - std::atan2(actorDirection.x(), actorDirection.y()); + xAngleRadians = -std::asin(direction.z()); + + wrap(zAngleRadians); + wrap(xAngleRadians); + + xAngleRadians = std::min(xAngleRadians, osg::DegreesToRadians(40.f)); + xAngleRadians = std::max(xAngleRadians, osg::DegreesToRadians(-40.f)); + zAngleRadians = std::min(zAngleRadians, osg::DegreesToRadians(30.f)); + zAngleRadians = std::max(zAngleRadians, osg::DegreesToRadians(-30.f)); + } + + float factor = duration*5; + factor = std::min(factor, 1.f); + xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * (-xAngleRadians); + zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * (-zAngleRadians); + + mAnimation->setHeadPitch(xAngleRadians); + mAnimation->setHeadYaw(zAngleRadians); +} + +} diff --git a/apps/openmw/mwmechanics/character.cpp.rej b/apps/openmw/mwmechanics/character.cpp.rej new file mode 100644 index 000000000..f04f72a90 --- /dev/null +++ b/apps/openmw/mwmechanics/character.cpp.rej @@ -0,0 +1,46 @@ +--- apps/openmw/mwmechanics/character.cpp ++++ apps/openmw/mwmechanics/character.cpp +@@ -372,29 +372,28 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState + } + } + +- if(mJumpState == JumpState_InAir) ++ if (!mCurrentJump.empty()) + { + mAnimation->disable(mCurrentJump); +- mCurrentJump = jumpAnimName; +- if (mAnimation->hasAnimation("jump")) +- mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false, ++ mCurrentJump.clear(); ++ } ++ ++ if(mJumpState == JumpState_InAir) ++ { ++ if (mAnimation->hasAnimation(jumpAnimName)) ++ { ++ mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, + 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); ++ mCurrentJump = jumpAnimName; ++ } + } + else if (mJumpState == JumpState_Landing) + { +- if (startAtLoop) +- mAnimation->disable(mCurrentJump); +- +- if (mAnimation->hasAnimation("jump")) ++ if (mAnimation->hasAnimation(jumpAnimName)) ++ { + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, + 1.0f, "loop stop", "stop", 0.0f, 0); +- } +- else // JumpState_None +- { +- if (mCurrentJump.length() > 0) +- { +- mAnimation->disable(mCurrentJump); +- mCurrentJump.clear(); ++ mCurrentJump = jumpAnimName; + } + } + } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4fbcdc4d5..9d580a12a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -620,6 +620,11 @@ namespace MWRender mutable bool mDone; }; + void RenderingManager::screenshot360(osg::Image* image, int w) + { + screenshot(image,w,w); + } + void RenderingManager::screenshot(osg::Image *image, int w, int h) { osg::ref_ptr rttCamera (new osg::Camera); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index f0087e43d..d0ac9a500 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -126,6 +126,7 @@ namespace MWRender /// Take a screenshot of w*h onto the given image, not including the GUI. void screenshot(osg::Image* image, int w, int h); + void screenshot360(osg::Image* image, int w); struct RayResult { diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 14ee5adee..c643480a9 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -655,5 +655,4 @@ void MWState::StateManager::writeScreenshot(std::vector &imageData) const std::string data = ostream.str(); imageData = std::vector(data.begin(), data.end()); - } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 9c7fba9fa..f9a030a80 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2280,6 +2280,11 @@ namespace MWWorld { mRendering->screenshot(image, w, h); } + + void World::screenshot360 (osg::Image* image, int w) + { + mRendering->screenshot360(image, w); + } void World::activateDoor(const MWWorld::Ptr& door) { diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 7af7b2968..1882b88b9 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -560,6 +560,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; + void screenshot360 (osg::Image* image, int w) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise From ce55d7c2f584bd9d80cb362451755c01055e0f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Tue, 7 Nov 2017 15:02:01 +0100 Subject: [PATCH 02/91] basic cubemap rendering --- apps/openmw/mwrender/renderingmanager.cpp | 38 +++++++++++++++++++++-- apps/openmw/mwrender/renderingmanager.hpp | 2 +- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9d580a12a..eaee3a803 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -16,6 +16,8 @@ #include #include +#include + #include #include @@ -622,10 +624,37 @@ namespace MWRender void RenderingManager::screenshot360(osg::Image* image, int w) { - screenshot(image,w,w); + osg::Vec3 directions[6] = { + osg::Vec3(0,0,-1), + osg::Vec3(-1,0,0), + osg::Vec3(0,0,1), + osg::Vec3(1,0,0), + osg::Vec3(0,1,0), + osg::Vec3(0,-1,0), + }; + + double fovBackup = mFieldOfView; + mFieldOfView = 90.0; // each side sees 90 degrees + + for (int i = 0; i < 6; i++) // for each cube side + { + osg::ref_ptr sideImage (new osg::Image); + screenshot(sideImage.get(),w,w,directions[i]); + + if (i == 0) + { + image->allocateImage(w * 6,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); + std::cout << image->s() << " " << image->t() << std::endl; + } + + osg::copyImage(sideImage.get(),0,0,0,sideImage->s(),sideImage->t(),sideImage->r(), + image,w * i,0,0); + } + + mFieldOfView = fovBackup; } - void RenderingManager::screenshot(osg::Image *image, int w, int h) + void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction) { osg::ref_ptr rttCamera (new osg::Camera); rttCamera->setNodeMask(Mask_RenderToTexture); @@ -634,7 +663,10 @@ namespace MWRender rttCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); rttCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); - rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix()); + rttCamera->setViewMatrix( + mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction) + ); + rttCamera->setViewport(0, 0, w, h); osg::ref_ptr texture (new osg::Texture2D); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index d0ac9a500..add7aae4d 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -125,7 +125,7 @@ namespace MWRender void setWaterHeight(float level); /// Take a screenshot of w*h onto the given image, not including the GUI. - void screenshot(osg::Image* image, int w, int h); + void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1)); void screenshot360(osg::Image* image, int w); struct RayResult From 5afe02505b6816e3f44c819047508047902b7bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Tue, 7 Nov 2017 16:12:31 +0100 Subject: [PATCH 03/91] hide player in first person 360 screenshot --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 192eadbde..61434521e 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1016,7 +1016,7 @@ namespace MWInput void InputManager::screenshot() { // MOVE THIS ELSEWHERE LATER! - int screenshotW = 512; + int screenshotW = 1024; osg::ref_ptr screenshot (new osg::Image); MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(), screenshotW); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index eaee3a803..4901bc176 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -636,21 +636,24 @@ namespace MWRender double fovBackup = mFieldOfView; mFieldOfView = 90.0; // each side sees 90 degrees + if (mCamera->isFirstPerson()) + mPlayerAnimation->getObjectRoot()->setNodeMask(0); + for (int i = 0; i < 6; i++) // for each cube side { osg::ref_ptr sideImage (new osg::Image); screenshot(sideImage.get(),w,w,directions[i]); if (i == 0) - { image->allocateImage(w * 6,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); - std::cout << image->s() << " " << image->t() << std::endl; - } osg::copyImage(sideImage.get(),0,0,0,sideImage->s(),sideImage->t(),sideImage->r(), image,w * i,0,0); } + if (mCamera->isFirstPerson()) + mPlayerAnimation->getObjectRoot()->setNodeMask(1); + mFieldOfView = fovBackup; } @@ -667,6 +670,8 @@ namespace MWRender mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction) ); +// TODO: water reflections have to be transformed as well!!!!! + rttCamera->setViewport(0, 0, w, h); osg::ref_ptr texture (new osg::Texture2D); From 3be9e2ee9548ce4795fc3f9eb5149681d9f11f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Tue, 7 Nov 2017 19:47:36 +0100 Subject: [PATCH 04/91] make spherical screenshot class --- apps/openmw/mwrender/renderingmanager.cpp | 36 +++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4901bc176..f25f28bab 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -622,8 +622,34 @@ namespace MWRender mutable bool mDone; }; + class SphericalScreenshot + { + public: + SphericalScreenshot(int size) + { + mSize = size; + + for (int i = 0; i < 6; ++i) + mImages.push_back(new osg::Image); + } + + osg::Image *getImage(int index) + { + return mImages[index].get(); + } + + protected: + std::vector> mImages; + int mSize; + }; + void RenderingManager::screenshot360(osg::Image* image, int w) { + int resultW = 1024; + int resultH = 768; + + SphericalScreenshot s(w); + osg::Vec3 directions[6] = { osg::Vec3(0,0,-1), osg::Vec3(-1,0,0), @@ -641,14 +667,14 @@ namespace MWRender for (int i = 0; i < 6; i++) // for each cube side { - osg::ref_ptr sideImage (new osg::Image); - screenshot(sideImage.get(),w,w,directions[i]); + osg::Image *sideImage = s.getImage(i); + screenshot(sideImage,w,w,directions[i]); if (i == 0) - image->allocateImage(w * 6,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); + //image->allocateImage(resultW,resultH,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); + image->allocateImage(6 * w,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); - osg::copyImage(sideImage.get(),0,0,0,sideImage->s(),sideImage->t(),sideImage->r(), - image,w * i,0,0); + osg::copyImage(sideImage,0,0,0,sideImage->s(),sideImage->t(),sideImage->r(),image,w * i,0,0); } if (mCamera->isFirstPerson()) From 4761a3d98b4d50dd041631490aa5c654f4c847a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Tue, 7 Nov 2017 22:13:05 +0100 Subject: [PATCH 05/91] dirty cylindrical projection --- apps/openmw/mwrender/renderingmanager.cpp | 85 +++++++++++++++++++++-- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f25f28bab..f22a0778d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -638,6 +638,80 @@ namespace MWRender return mImages[index].get(); } + void create(osg::Image *dest, int w, int h) + { + dest->allocateImage(w,h,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); + + for (int j = 0; j < h; ++j) + for (int i = 0; i < w; ++i) + dest->setColor(getColorByDirection(cylindricalCoords(i / ((float) w), j / ((float) h))),i,j); + } + + osg::Vec3d cylindricalCoords(double x, double y) + { + osg::Vec3 result = osg::Vec3d(sin(x * 2 * osg::PI),cos(x * 2 * osg::PI),y * 2.0 - 1.0); + result.normalize(); + return result; + } + + osg::Vec4 getColorByDirection(osg::Vec3d d) + { + double x, y; + double ma; // see OpenGL 4.4 specification page 241 + int side; + + double ax, ay, az; + ax = d.x() > 0 ? d.x() : -d.x(); + ay = d.y() > 0 ? d.y() : -d.y(); + az = d.z() > 0 ? d.z() : -d.z(); + + + if (ax > ay) + { + if (ax > az) + { + side = d.x() > 0 ? 1 : 3; + ma = ax; + } + else + { + side = d.z() > 0 ? 5 : 4; + ma = az; + } + } + else + { + if (ay > az) + { + side = d.y() > 0 ? 0 : 2; + ma = ay; + } + else + { + side = d.z() > 0 ? 5 : 4; + ma = az; + } + } + + switch (side) + { + case 0: x = d.x(); y = d.z(); break; + case 1: x = -d.y(); y = d.z(); break; + case 2: x = -d.x(); y = d.z(); break; + case 3: x = d.y(); y = d.z(); break; + case 4: x = d.x(); y = d.y(); break; + case 5: x = d.x(); y = -d.y(); break; + default: break; + } + + x = 0.5 * (x / ma + 1); + y = 0.5 * (y / ma + 1); + + return mImages[side]->getColor( + std::min(std::max(int(x * mSize),0),mSize - 1), + std::min(std::max(int(y * mSize),0),mSize - 1)); //osg::Vec4(d.x(),d.y(),d.z(),1); + } + protected: std::vector> mImages; int mSize; @@ -645,9 +719,6 @@ namespace MWRender void RenderingManager::screenshot360(osg::Image* image, int w) { - int resultW = 1024; - int resultH = 768; - SphericalScreenshot s(w); osg::Vec3 directions[6] = { @@ -670,16 +741,18 @@ namespace MWRender osg::Image *sideImage = s.getImage(i); screenshot(sideImage,w,w,directions[i]); - if (i == 0) + // if (i == 0) //image->allocateImage(resultW,resultH,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); - image->allocateImage(6 * w,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); + //image->allocateImage(6 * w,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); - osg::copyImage(sideImage,0,0,0,sideImage->s(),sideImage->t(),sideImage->r(),image,w * i,0,0); + // osg::copyImage(sideImage,0,0,0,sideImage->s(),sideImage->t(),sideImage->r(),image,w * i,0,0); } if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(1); + s.create(image,1600,768); + mFieldOfView = fovBackup; } From 5f365181813c34505782a0efac62c940bf3814aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 8 Nov 2017 09:15:45 +0100 Subject: [PATCH 06/91] spherical mapping --- apps/openmw/mwrender/renderingmanager.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f22a0778d..73b761ff2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -644,7 +644,7 @@ namespace MWRender for (int j = 0; j < h; ++j) for (int i = 0; i < w; ++i) - dest->setColor(getColorByDirection(cylindricalCoords(i / ((float) w), j / ((float) h))),i,j); + dest->setColor(getColorByDirection(sphericalCoords(i / ((float) w), j / ((float) h))),i,j); } osg::Vec3d cylindricalCoords(double x, double y) @@ -654,6 +654,17 @@ namespace MWRender return result; } + osg::Vec3d sphericalCoords(double x, double y) + { + x = x * 2 * osg::PI; + y = (y - 0.5) * osg::PI; + + osg::Vec3 result = osg::Vec3(0.0,cos(y),sin(y)); + result = osg::Vec3(cos(x) * result.y(),sin(x) * result.y(),result.z()); + + return result; + } + osg::Vec4 getColorByDirection(osg::Vec3d d) { double x, y; From 5698d70806c52d7912e3d303851818ce3233c589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 8 Nov 2017 12:58:27 +0100 Subject: [PATCH 07/91] small planet mapping --- apps/openmw/mwrender/renderingmanager.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 73b761ff2..9e2773870 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -644,12 +644,12 @@ namespace MWRender for (int j = 0; j < h; ++j) for (int i = 0; i < w; ++i) - dest->setColor(getColorByDirection(sphericalCoords(i / ((float) w), j / ((float) h))),i,j); + dest->setColor(getColorByDirection(smallPlanetCoords(i / ((float) w), j / ((float) h))),i,j); } osg::Vec3d cylindricalCoords(double x, double y) { - osg::Vec3 result = osg::Vec3d(sin(x * 2 * osg::PI),cos(x * 2 * osg::PI),y * 2.0 - 1.0); + osg::Vec3 result = osg::Vec3d(cos(x * 2 * osg::PI),sin(x * 2 * osg::PI),y * 2.0 - 1.0); result.normalize(); return result; } @@ -665,6 +665,21 @@ namespace MWRender return result; } + osg::Vec3d smallPlanetCoords(double x, double y) + { + osg::Vec2d fromCenter = osg::Vec2d(x,y) - osg::Vec2d(0.5,0.5); + + double magnitude = fromCenter.length(); + + fromCenter.normalize(); + double dot = fromCenter * osg::Vec2d(0.0,1.0); + + x = x > 0.5 ? 0.5 - (dot + 1) / 4.0 : 0.5 + (dot + 1) / 4.0; + y = pow(std::min(1.0,magnitude / 0.5),0.5); + + return sphericalCoords(x,y); + } + osg::Vec4 getColorByDirection(osg::Vec3d d) { double x, y; @@ -720,7 +735,7 @@ namespace MWRender return mImages[side]->getColor( std::min(std::max(int(x * mSize),0),mSize - 1), - std::min(std::max(int(y * mSize),0),mSize - 1)); //osg::Vec4(d.x(),d.y(),d.z(),1); + std::min(std::max(int(y * mSize),0),mSize - 1)); } protected: @@ -762,7 +777,7 @@ namespace MWRender if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(1); - s.create(image,1600,768); + s.create(image,1600,1600); mFieldOfView = fovBackup; } From f60840754fcf7da27bb41640ee9c5ab3cb91cfb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 14:44:42 +0100 Subject: [PATCH 08/91] disable water effects for spherical screenshots --- apps/openmw/mwrender/renderingmanager.cpp | 14 ++++------- apps/openmw/mwrender/water.cpp | 29 +++++++++++++++++++---- apps/openmw/mwrender/water.hpp | 1 + 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9e2773870..b1f1888d0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -644,7 +644,7 @@ namespace MWRender for (int j = 0; j < h; ++j) for (int i = 0; i < w; ++i) - dest->setColor(getColorByDirection(smallPlanetCoords(i / ((float) w), j / ((float) h))),i,j); + dest->setColor(getColorByDirection(sphericalCoords(i / ((float) w), j / ((float) h))),i,j); } osg::Vec3d cylindricalCoords(double x, double y) @@ -766,12 +766,6 @@ namespace MWRender { osg::Image *sideImage = s.getImage(i); screenshot(sideImage,w,w,directions[i]); - - // if (i == 0) - //image->allocateImage(resultW,resultH,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); - //image->allocateImage(6 * w,w,sideImage->r(),sideImage->getPixelFormat(),sideImage->getDataType()); - - // osg::copyImage(sideImage,0,0,0,sideImage->s(),sideImage->t(),sideImage->r(),image,w * i,0,0); } if (mCamera->isFirstPerson()) @@ -795,8 +789,6 @@ namespace MWRender mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction) ); -// TODO: water reflections have to be transformed as well!!!!! - rttCamera->setViewport(0, 0, w, h); osg::ref_ptr texture (new osg::Texture2D); @@ -823,10 +815,14 @@ namespace MWRender // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() + mWater->setEffectsEnabled(false); + mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); + mWater->setEffectsEnabled(true); + callback->waitTillDone(); // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index c4dffb7a4..70e7b36ad 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -207,7 +207,6 @@ osg::ref_ptr readPngImage (const std::string& file) return result.getImage(); } - class Refraction : public osg::Camera { public: @@ -221,7 +220,7 @@ public: setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("RefractionCamera"); - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + setupCullMask(true); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); @@ -262,6 +261,12 @@ public: attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); } + void setupCullMask(bool enabled) + { + setCullMask(!enabled ? 0 : + Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); + } + void setScene(osg::Node* scene) { if (mScene) @@ -304,9 +309,9 @@ public: setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("ReflectionCamera"); - bool reflectActors = Settings::Manager::getBool("reflect actors", "Water"); + mReflectActors = Settings::Manager::getBool("reflect actors", "Water"); - setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(reflectActors ? Mask_Actor : 0)); + setupCullMask(true); setNodeMask(Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); @@ -334,6 +339,12 @@ public: addChild(mClipCullNode); } + void setupCullMask(bool enabled) + { + setCullMask(!enabled ? 0 : + Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(mReflectActors ? Mask_Actor : 0)); + } + void setWaterLevel(float waterLevel) { setViewMatrix(osg::Matrix::scale(1,1,-1) * osg::Matrix::translate(0,0,2 * waterLevel)); @@ -357,6 +368,7 @@ private: osg::ref_ptr mReflectionTexture; osg::ref_ptr mClipCullNode; osg::ref_ptr mScene; + bool mReflectActors; }; /// DepthClampCallback enables GL_DEPTH_CLAMP for the current draw, if supported. @@ -697,4 +709,13 @@ void Water::clearRipples() mSimulation->clear(); } +void Water::setEffectsEnabled(bool enabled) +{ + if (mReflection) + mReflection->setupCullMask(enabled); + + if (mRefraction) + mRefraction->setupCullMask(enabled); +} + } diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index a4fd1ed36..ed6e40f1a 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -106,6 +106,7 @@ namespace MWRender void removeCell(const MWWorld::CellStore* store); ///< remove all emitters in this cell void clearRipples(); + void setEffectsEnabled(bool enabled); void changeCell(const MWWorld::CellStore* store); void setHeight(const float height); From 1b97a541f4e6ed5fafb41ff8213641c93e5c831e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 16:06:29 +0100 Subject: [PATCH 09/91] make a new action for 360 screenshot --- apps/openmw/mwinput/inputmanagerimp.cpp | 22 ++++++++++++++++------ apps/openmw/mwinput/inputmanagerimp.hpp | 3 +++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 61434521e..5d1c236ca 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -247,6 +247,9 @@ namespace MWInput case A_Screenshot: screenshot(); break; + case A_Screenshot360: + screenshot360(); + break; case A_Inventory: toggleInventory (); break; @@ -1015,7 +1018,13 @@ namespace MWInput void InputManager::screenshot() { - // MOVE THIS ELSEWHERE LATER! + mScreenCaptureHandler->setFramesToCapture(1); + mScreenCaptureHandler->captureNextFrame(*mViewer); + MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); + } + + void InputManager::screenshot360() + { int screenshotW = 1024; osg::ref_ptr screenshot (new osg::Image); MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(), screenshotW); @@ -1040,11 +1049,6 @@ namespace MWInput } outfile.close(); - -// mScreenCaptureHandler->setFramesToCapture(1); -// mScreenCaptureHandler->captureNextFrame(*mViewer); - -// MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); } void InputManager::toggleInventory() @@ -1230,6 +1234,7 @@ namespace MWInput defaultKeyBindings[A_QuickKey9] = SDL_SCANCODE_9; defaultKeyBindings[A_QuickKey10] = SDL_SCANCODE_0; defaultKeyBindings[A_Screenshot] = SDL_SCANCODE_F12; + defaultKeyBindings[A_Screenshot360] = SDL_SCANCODE_F8; defaultKeyBindings[A_ToggleHUD] = SDL_SCANCODE_F11; defaultKeyBindings[A_ToggleDebug] = SDL_SCANCODE_F10; defaultKeyBindings[A_AlwaysRun] = SDL_SCANCODE_CAPSLOCK; @@ -1365,6 +1370,9 @@ namespace MWInput if (action == A_Screenshot) return "Screenshot"; + if (action == A_Screenshot360) + return "Screenshot 360"; + descriptions[A_Use] = "sUse"; descriptions[A_Activate] = "sActivate"; descriptions[A_MoveBackward] = "sBack"; @@ -1526,6 +1534,7 @@ namespace MWInput ret.push_back(A_QuickSave); ret.push_back(A_QuickLoad); ret.push_back(A_Screenshot); + ret.push_back(A_Screenshot360); ret.push_back(A_QuickKeysMenu); ret.push_back(A_QuickKey1); ret.push_back(A_QuickKey2); @@ -1557,6 +1566,7 @@ namespace MWInput ret.push_back(A_QuickSave); ret.push_back(A_QuickLoad); ret.push_back(A_Screenshot); + ret.push_back(A_Screenshot360); ret.push_back(A_QuickKeysMenu); ret.push_back(A_QuickKey1); ret.push_back(A_QuickKey2); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index cba7fc743..07b43d0ac 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -227,6 +227,7 @@ namespace MWInput void toggleInventory(); void toggleConsole(); void screenshot(); + void screenshot360(); void toggleJournal(); void activate(); void toggleWalking(); @@ -257,6 +258,8 @@ namespace MWInput A_Screenshot, // Take a screenshot + A_Screenshot360, // Take a 360 degree screenshot + A_Inventory, // Toggle inventory screen A_Console, // Toggle console screen From d4fd08a63fc696e415957a15d9d06774654059fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 16:49:46 +0100 Subject: [PATCH 10/91] save 360 screenshots in the configured directory --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwinput/inputmanagerimp.cpp | 11 ++++++----- apps/openmw/mwrender/renderingmanager.cpp | 3 ++- apps/openmw/mwrender/renderingmanager.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 ++-- apps/openmw/mwworld/worldimp.hpp | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 80a9e11a1..6d39229f0 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -450,7 +450,7 @@ namespace MWBase /// \todo this does not belong here virtual void screenshot (osg::Image* image, int w, int h) = 0; - virtual void screenshot360 (osg::Image* image, int w) = 0; + virtual void screenshot360 (osg::Image* image) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 5d1c236ca..cd2e8408b 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1020,20 +1020,21 @@ namespace MWInput { mScreenCaptureHandler->setFramesToCapture(1); mScreenCaptureHandler->captureNextFrame(*mViewer); - MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); + MWBase::Environment::get().getWindowManager()->messageBox("Screenshot saved"); } void InputManager::screenshot360() { - int screenshotW = 1024; osg::ref_ptr screenshot (new osg::Image); - MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(), screenshotW); + MWBase::Environment::get().getWorld()->screenshot360(screenshot.get()); +(*mScreenCaptureHandler->getCaptureOperation()) (*(screenshot.get()),0); +/* osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); if (!readerwriter) { - std::cerr << "Error: Unable to write screenshot, can't find a jpg ReaderWriter" << std::endl; + std::cerr << "Error: Unable to write 360 degree screenshot, can't find a jpg ReaderWriter" << std::endl; return; } @@ -1048,7 +1049,7 @@ namespace MWInput return; } - outfile.close(); + outfile.close(); */ } void InputManager::toggleInventory() diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b1f1888d0..4ea602f3f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -743,8 +743,9 @@ namespace MWRender int mSize; }; - void RenderingManager::screenshot360(osg::Image* image, int w) + void RenderingManager::screenshot360(osg::Image* image) { + int w = 1024; SphericalScreenshot s(w); osg::Vec3 directions[6] = { diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index add7aae4d..e6e2e1c0a 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -126,7 +126,7 @@ namespace MWRender /// Take a screenshot of w*h onto the given image, not including the GUI. void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1)); - void screenshot360(osg::Image* image, int w); + void screenshot360(osg::Image* image); struct RayResult { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f9a030a80..f51f9af3d 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2281,9 +2281,9 @@ namespace MWWorld mRendering->screenshot(image, w, h); } - void World::screenshot360 (osg::Image* image, int w) + void World::screenshot360 (osg::Image* image) { - mRendering->screenshot360(image, w); + mRendering->screenshot360(image); } void World::activateDoor(const MWWorld::Ptr& door) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 1882b88b9..a68982412 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -560,7 +560,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; - void screenshot360 (osg::Image* image, int w) override; + void screenshot360 (osg::Image* image) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise From 8f321140256d835adb0d0779eedef732b4cda1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 18:26:27 +0100 Subject: [PATCH 11/91] segfault fix --- apps/openmw/engine.cpp | 9 ++-- apps/openmw/engine.hpp | 3 +- apps/openmw/mwinput/inputmanagerimp.cpp | 26 ++-------- apps/openmw/mwinput/inputmanagerimp.hpp | 4 +- apps/openmw/mwrender/renderingmanager.cpp | 62 +++++++++++++++-------- apps/openmw/mwrender/renderingmanager.hpp | 2 +- 6 files changed, 56 insertions(+), 50 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 3e5ab7ce6..499158fd4 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -484,7 +484,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) else gameControllerdb = ""; //if it doesn't exist, pass in an empty string - MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, keybinderUser, keybinderUserExists, gameControllerdb, mGrab); + MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, gameControllerdb, mGrab); mEnvironment.setInputManager (input); std::string myguiResources = (mResDir / "mygui").string(); @@ -641,8 +641,11 @@ void OMW::Engine::go() settingspath = loadSettings (settings); - mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(), - Settings::Manager::getString("screenshot format", "General"))); + mScreenCaptureOperation = new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(), + Settings::Manager::getString("screenshot format", "General")); + + mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); + mViewer->addEventHandler(mScreenCaptureHandler); mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index bf144bfed..3cbd4b6e7 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -7,7 +7,7 @@ #include #include - +#include #include "mwbase/environment.hpp" @@ -82,6 +82,7 @@ namespace OMW boost::filesystem::path mResDir; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; + osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; std::string mCellName; std::vector mContentFiles; bool mSkipMenu; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index cd2e8408b..e1c85744e 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -37,12 +37,14 @@ namespace MWInput SDL_Window* window, osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler, + osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, const std::string& controllerBindingsFile, bool grab) : mWindow(window) , mWindowVisible(true) , mViewer(viewer) , mScreenCaptureHandler(screenCaptureHandler) + , mScreenCaptureOperation(screenCaptureOperation) , mJoystickLastUsed(false) , mPlayer(NULL) , mInputManager(NULL) @@ -1027,29 +1029,7 @@ namespace MWInput { osg::ref_ptr screenshot (new osg::Image); MWBase::Environment::get().getWorld()->screenshot360(screenshot.get()); -(*mScreenCaptureHandler->getCaptureOperation()) (*(screenshot.get()),0); - -/* - osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg"); - - if (!readerwriter) - { - std::cerr << "Error: Unable to write 360 degree screenshot, can't find a jpg ReaderWriter" << std::endl; - return; - } - - std::ofstream outfile; - outfile.open("test.jpg"); - - osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*screenshot, outfile); - - if (!result.success()) - { - outfile << "Error: Unable to write screenshot: " << result.message() << " code " << result.status() << std::endl; - return; - } - - outfile.close(); */ + (*mScreenCaptureOperation) (*(screenshot.get()),0); } void InputManager::toggleInventory() diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 07b43d0ac..5d3e88eab 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -4,6 +4,7 @@ #include "../mwgui/mode.hpp" #include +#include #include #include @@ -14,7 +15,6 @@ #include "../mwbase/inputmanager.hpp" - namespace MWWorld { class Player; @@ -74,6 +74,7 @@ namespace MWInput SDL_Window* window, osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler, + osgViewer::ScreenCaptureHandler::CaptureOperation *screenCaptureOperation, const std::string& userFile, bool userFileExists, const std::string& controllerBindingsFile, bool grab); @@ -158,6 +159,7 @@ namespace MWInput bool mWindowVisible; osg::ref_ptr mViewer; osg::ref_ptr mScreenCaptureHandler; + osgViewer::ScreenCaptureHandler::CaptureOperation *mScreenCaptureOperation; bool mJoystickLastUsed; MWWorld::Player* mPlayer; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4ea602f3f..a08e7a0a2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -625,6 +625,13 @@ namespace MWRender class SphericalScreenshot { public: + typedef enum + { + MAPPING_CYLINDRICAL = 0, + MAPPING_SPHERICAL, + MAPPING_SMALL_PLANET + } SphericalScreenshotMapping; + SphericalScreenshot(int size) { mSize = size; @@ -638,13 +645,26 @@ namespace MWRender return mImages[index].get(); } - void create(osg::Image *dest, int w, int h) + void create(osg::Image *dest, int w, int h, SphericalScreenshotMapping mapping) { dest->allocateImage(w,h,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); for (int j = 0; j < h; ++j) for (int i = 0; i < w; ++i) - dest->setColor(getColorByDirection(sphericalCoords(i / ((float) w), j / ((float) h))),i,j); + { + osg::Vec3d coords; + osg::Vec2d normalizedXY = osg::Vec2d(i / ((float) w), j / ((float) h)); + + switch (mapping) + { + case MAPPING_CYLINDRICAL: coords = cylindricalCoords(normalizedXY.x(),normalizedXY.y()); break; + case MAPPING_SPHERICAL: coords = sphericalCoords(normalizedXY.x(),normalizedXY.y()); break; + case MAPPING_SMALL_PLANET: coords = smallPlanetCoords(normalizedXY.x(),normalizedXY.y()); break; + default: break; + } + + dest->setColor(getColorByDirection(coords),i,j); + } } osg::Vec3d cylindricalCoords(double x, double y) @@ -682,18 +702,18 @@ namespace MWRender osg::Vec4 getColorByDirection(osg::Vec3d d) { + // for details see OpenGL 4.4 specification page 241 + double x, y; - double ma; // see OpenGL 4.4 specification page 241 + double ma; int side; double ax, ay, az; - ax = d.x() > 0 ? d.x() : -d.x(); + ax = d.x() > 0 ? d.x() : -d.x(); // abs behaves weirdly for some reason ay = d.y() > 0 ? d.y() : -d.y(); az = d.z() > 0 ? d.z() : -d.z(); - if (ax > ay) - { if (ax > az) { side = d.x() > 0 ? 1 : 3; @@ -704,9 +724,7 @@ namespace MWRender side = d.z() > 0 ? 5 : 4; ma = az; } - } else - { if (ay > az) { side = d.y() > 0 ? 0 : 2; @@ -717,12 +735,11 @@ namespace MWRender side = d.z() > 0 ? 5 : 4; ma = az; } - } switch (side) { - case 0: x = d.x(); y = d.z(); break; - case 1: x = -d.y(); y = d.z(); break; + case 0: x = d.x(); y = d.z(); break; + case 1: x = -d.y(); y = d.z(); break; case 2: x = -d.x(); y = d.z(); break; case 3: x = d.y(); y = d.z(); break; case 4: x = d.x(); y = d.y(); break; @@ -745,8 +762,10 @@ namespace MWRender void RenderingManager::screenshot360(osg::Image* image) { - int w = 1024; - SphericalScreenshot s(w); + int cubeWidth = 1024; + int screenshotWidth = 1600; + int screenshotHeight = 1280; + SphericalScreenshot s(cubeWidth); osg::Vec3 directions[6] = { osg::Vec3(0,0,-1), @@ -766,18 +785,18 @@ namespace MWRender for (int i = 0; i < 6; i++) // for each cube side { osg::Image *sideImage = s.getImage(i); - screenshot(sideImage,w,w,directions[i]); + screenshot(sideImage,cubeWidth,cubeWidth,directions[i],true); } if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(1); - s.create(image,1600,1600); + s.create(image,screenshotWidth,screenshotHeight,SphericalScreenshot::MAPPING_SPHERICAL); mFieldOfView = fovBackup; } - void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction) + void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction, bool disableWaterEffects) { osg::ref_ptr rttCamera (new osg::Camera); rttCamera->setNodeMask(Mask_RenderToTexture); @@ -786,9 +805,7 @@ namespace MWRender rttCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); rttCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); - rttCamera->setViewMatrix( - mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction) - ); + rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction)); rttCamera->setViewport(0, 0, w, h); @@ -816,13 +833,16 @@ namespace MWRender // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() - mWater->setEffectsEnabled(false); + + if (disableWaterEffects) + mWater->setEffectsEnabled(false); mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); - mWater->setEffectsEnabled(true); + if (disableWaterEffects) + mWater->setEffectsEnabled(true); callback->waitTillDone(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index e6e2e1c0a..cb0723397 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -125,7 +125,7 @@ namespace MWRender void setWaterHeight(float level); /// Take a screenshot of w*h onto the given image, not including the GUI. - void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1)); + void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1), bool disableWaterEffects=false); void screenshot360(osg::Image* image); struct RayResult From d763e9fe46be5189e4531e074be6a90309d6f77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 20:25:29 +0100 Subject: [PATCH 12/91] add settings for spherical screenshots --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 ++ apps/openmw/mwrender/renderingmanager.cpp | 15 +++++++++------ components/settings/settings.cpp | 12 ++++++++++++ components/settings/settings.hpp | 1 + 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index e1c85744e..9ea1f8e16 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1029,6 +1029,8 @@ namespace MWInput { osg::ref_ptr screenshot (new osg::Image); MWBase::Environment::get().getWorld()->screenshot360(screenshot.get()); + + // calling mScreenCaptureHandler->getCaptureOperation() here caused segfault for some reason (*mScreenCaptureOperation) (*(screenshot.get()),0); } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a08e7a0a2..bac150459 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -627,8 +627,8 @@ namespace MWRender public: typedef enum { - MAPPING_CYLINDRICAL = 0, - MAPPING_SPHERICAL, + MAPPING_SPHERICAL = 0, + MAPPING_CYLINDRICAL, MAPPING_SMALL_PLANET } SphericalScreenshotMapping; @@ -762,9 +762,12 @@ namespace MWRender void RenderingManager::screenshot360(osg::Image* image) { - int cubeWidth = 1024; - int screenshotWidth = 1600; - int screenshotHeight = 1280; + int screenshotWidth = Settings::Manager::tryGetInt("s360 width","Video",mViewer->getCamera()->getViewport()->width()); + int screenshotHeight = Settings::Manager::tryGetInt("s360 height","Video",mViewer->getCamera()->getViewport()->height()); + SphericalScreenshot::SphericalScreenshotMapping mapping = static_cast( + Settings::Manager::tryGetInt("s360 mapping","Video",SphericalScreenshot::MAPPING_SPHERICAL)); + + int cubeWidth = screenshotWidth / 2; SphericalScreenshot s(cubeWidth); osg::Vec3 directions[6] = { @@ -791,7 +794,7 @@ namespace MWRender if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(1); - s.create(image,screenshotWidth,screenshotHeight,SphericalScreenshot::MAPPING_SPHERICAL); + s.create(image,screenshotWidth,mapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotHeight : screenshotWidth,mapping); mFieldOfView = fovBackup; } diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index e93642ee2..4e250974a 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -401,6 +401,18 @@ int Manager::getInt (const std::string& setting, const std::string& category) return parseInt( getString(setting, category) ); } +int Manager::tryGetInt (const std::string &setting, const std::string& category, int defaultValue) +{ + try + { + return getInt(setting,category); + } + catch (std::runtime_error) + { + return defaultValue; + } +} + bool Manager::getBool (const std::string& setting, const std::string& category) { return parseBool( getString(setting, category) ); diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 7adcb9b39..9e80c21a6 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -39,6 +39,7 @@ namespace Settings ///< returns the list of changed settings and then clears it static int getInt (const std::string& setting, const std::string& category); + static int tryGetInt (const std::string &setting, const std::string& category, int defaultValue); static float getFloat (const std::string& setting, const std::string& category); static std::string getString (const std::string& setting, const std::string& category); static bool getBool (const std::string& setting, const std::string& category); From e54c0a90fadda522a25fdc8fe5ce581ee75780a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 21:14:02 +0100 Subject: [PATCH 13/91] fix mirrored spherical screenshots --- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index bac150459..c018c1707 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -669,14 +669,14 @@ namespace MWRender osg::Vec3d cylindricalCoords(double x, double y) { - osg::Vec3 result = osg::Vec3d(cos(x * 2 * osg::PI),sin(x * 2 * osg::PI),y * 2.0 - 1.0); + osg::Vec3 result = osg::Vec3d(cos(-1 * x * 2 * osg::PI),sin(-1 * x * 2 * osg::PI),y * 2.0 - 1.0); result.normalize(); return result; } osg::Vec3d sphericalCoords(double x, double y) { - x = x * 2 * osg::PI; + x = -1 * x * 2 * osg::PI; y = (y - 0.5) * osg::PI; osg::Vec3 result = osg::Vec3(0.0,cos(y),sin(y)); From 1b184d871646f6ec8954dca838c4ec4ee6194015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 23:09:13 +0100 Subject: [PATCH 14/91] correct player mask --- apps/openmw/mwrender/renderingmanager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c018c1707..9c6eac3c0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -782,6 +782,8 @@ namespace MWRender double fovBackup = mFieldOfView; mFieldOfView = 90.0; // each side sees 90 degrees + int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask(); + if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(0); @@ -791,11 +793,9 @@ namespace MWRender screenshot(sideImage,cubeWidth,cubeWidth,directions[i],true); } - if (mCamera->isFirstPerson()) - mPlayerAnimation->getObjectRoot()->setNodeMask(1); - s.create(image,screenshotWidth,mapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotHeight : screenshotWidth,mapping); + mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); mFieldOfView = fovBackup; } From 43c49e2f3135f13297badb42d752c35ed4625a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 9 Nov 2017 23:12:23 +0100 Subject: [PATCH 15/91] delete accidentally commited files --- apps/openmw/mwmechanics/character.cpp.orig | 2464 -------------------- apps/openmw/mwmechanics/character.cpp.rej | 46 - 2 files changed, 2510 deletions(-) delete mode 100644 apps/openmw/mwmechanics/character.cpp.orig delete mode 100644 apps/openmw/mwmechanics/character.cpp.rej diff --git a/apps/openmw/mwmechanics/character.cpp.orig b/apps/openmw/mwmechanics/character.cpp.orig deleted file mode 100644 index f262850a4..000000000 --- a/apps/openmw/mwmechanics/character.cpp.orig +++ /dev/null @@ -1,2464 +0,0 @@ -/* - * OpenMW - The completely unofficial reimplementation of Morrowind - * - * This file (character.cpp) is part of the OpenMW package. - * - * OpenMW is distributed as free software: you can redistribute it - * and/or modify it under the terms of the GNU General Public License - * version 3, as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * version 3 along with this program. If not, see - * http://www.gnu.org/licenses/ . - */ - -#include "character.hpp" - -#include - -#include - -#include - -#include - -#include "../mwrender/animation.hpp" - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" -#include "../mwbase/soundmanager.hpp" -#include "../mwbase/windowmanager.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/inventorystore.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/player.hpp" - -#include "movement.hpp" -#include "npcstats.hpp" -#include "creaturestats.hpp" -#include "security.hpp" -#include "actorutil.hpp" -#include "spellcasting.hpp" - -namespace -{ - -// Wraps a value to (-PI, PI] -void wrap(float& rad) -{ - if (rad>0) - rad = std::fmod(rad+osg::PI, 2.0f*osg::PI)-osg::PI; - else - rad = std::fmod(rad-osg::PI, 2.0f*osg::PI)+osg::PI; -} - -std::string toString(int num) -{ - std::ostringstream stream; - stream << num; - return stream.str(); -} - -std::string getBestAttack (const ESM::Weapon* weapon) -{ - int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; - int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; - int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; - if (slash == chop && slash == thrust) - return "slash"; - else if (thrust >= chop && thrust >= slash) - return "thrust"; - else if (slash >= chop && slash >= thrust) - return "slash"; - else - return "chop"; -} - -// Converts a movement Run state to its equivalent Walk state. -MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState state) -{ - using namespace MWMechanics; - CharacterState ret = state; - switch (state) - { - case CharState_RunForward: - ret = CharState_WalkForward; - break; - case CharState_RunBack: - ret = CharState_WalkBack; - break; - case CharState_RunLeft: - ret = CharState_WalkLeft; - break; - case CharState_RunRight: - ret = CharState_WalkRight; - break; - case CharState_SwimRunForward: - ret = CharState_SwimWalkForward; - break; - case CharState_SwimRunBack: - ret = CharState_SwimWalkBack; - break; - case CharState_SwimRunLeft: - ret = CharState_SwimWalkLeft; - break; - case CharState_SwimRunRight: - ret = CharState_SwimWalkRight; - break; - default: - break; - } - return ret; -} - -float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight) -{ - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &store = world->getStore().get(); - - const float fallDistanceMin = store.find("fFallDamageDistanceMin")->getFloat(); - - if (fallHeight >= fallDistanceMin) - { - const float acrobaticsSkill = static_cast(ptr.getClass().getSkill(ptr, ESM::Skill::Acrobatics)); - const float jumpSpellBonus = ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude(); - const float fallAcroBase = store.find("fFallAcroBase")->getFloat(); - const float fallAcroMult = store.find("fFallAcroMult")->getFloat(); - const float fallDistanceBase = store.find("fFallDistanceBase")->getFloat(); - const float fallDistanceMult = store.find("fFallDistanceMult")->getFloat(); - - float x = fallHeight - fallDistanceMin; - x -= (1.5f * acrobaticsSkill) + jumpSpellBonus; - x = std::max(0.0f, x); - - float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill); - x = fallDistanceBase + fallDistanceMult * x; - x *= a; - - return x; - } - return 0.f; -} - -} - -namespace MWMechanics -{ - -struct StateInfo { - CharacterState state; - const char groupname[32]; -}; - -static const StateInfo sMovementList[] = { - { CharState_WalkForward, "walkforward" }, - { CharState_WalkBack, "walkback" }, - { CharState_WalkLeft, "walkleft" }, - { CharState_WalkRight, "walkright" }, - - { CharState_SwimWalkForward, "swimwalkforward" }, - { CharState_SwimWalkBack, "swimwalkback" }, - { CharState_SwimWalkLeft, "swimwalkleft" }, - { CharState_SwimWalkRight, "swimwalkright" }, - - { CharState_RunForward, "runforward" }, - { CharState_RunBack, "runback" }, - { CharState_RunLeft, "runleft" }, - { CharState_RunRight, "runright" }, - - { CharState_SwimRunForward, "swimrunforward" }, - { CharState_SwimRunBack, "swimrunback" }, - { CharState_SwimRunLeft, "swimrunleft" }, - { CharState_SwimRunRight, "swimrunright" }, - - { CharState_SneakForward, "sneakforward" }, - { CharState_SneakBack, "sneakback" }, - { CharState_SneakLeft, "sneakleft" }, - { CharState_SneakRight, "sneakright" }, - - { CharState_Jump, "jump" }, - - { CharState_TurnLeft, "turnleft" }, - { CharState_TurnRight, "turnright" }, - { CharState_SwimTurnLeft, "swimturnleft" }, - { CharState_SwimTurnRight, "swimturnright" }, -}; -static const StateInfo *sMovementListEnd = &sMovementList[sizeof(sMovementList)/sizeof(sMovementList[0])]; - - -class FindCharState { - CharacterState state; - -public: - FindCharState(CharacterState _state) : state(_state) { } - - bool operator()(const StateInfo &info) const - { return info.state == state; } -}; - - -static const struct WeaponInfo { - WeaponType type; - const char shortgroup[16]; - const char longgroup[16]; -} sWeaponTypeList[] = { - { WeapType_HandToHand, "hh", "handtohand" }, - { WeapType_OneHand, "1h", "weapononehand" }, - { WeapType_TwoHand, "2c", "weapontwohand" }, - { WeapType_TwoWide, "2w", "weapontwowide" }, - { WeapType_BowAndArrow, "1h", "bowandarrow" }, - { WeapType_Crossbow, "crossbow", "crossbow" }, - { WeapType_Thrown, "1h", "throwweapon" }, - { WeapType_PickProbe, "1h", "pickprobe" }, - { WeapType_Spell, "spell", "spellcast" }, -}; -static const WeaponInfo *sWeaponTypeListEnd = &sWeaponTypeList[sizeof(sWeaponTypeList)/sizeof(sWeaponTypeList[0])]; - -class FindWeaponType { - WeaponType type; - -public: - FindWeaponType(WeaponType _type) : type(_type) { } - - bool operator()(const WeaponInfo &weap) const - { return weap.type == type; } -}; - -std::string CharacterController::chooseRandomGroup (const std::string& prefix, int* num) const -{ - int numAnims=0; - while (mAnimation->hasAnimation(prefix + toString(numAnims+1))) - ++numAnims; - - int roll = Misc::Rng::rollDice(numAnims) + 1; // [1, numAnims] - if (num) - *num = roll; - return prefix + toString(roll); -} - -void CharacterController::refreshHitRecoilAnims() -{ - bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); - bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); - bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); - bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); - if(mHitState == CharState_None) - { - if ((mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0 - || mPtr.getClass().getCreatureStats(mPtr).getFatigue().getBase() == 0) - && mAnimation->hasAnimation("knockout")) - { - if (isSwimming && mAnimation->hasAnimation("swimknockout")) - { - mHitState = CharState_SwimKnockOut; - mCurrentHit = "swimknockout"; - } - else - { - mHitState = CharState_KnockOut; - mCurrentHit = "knockout"; - } - - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); - mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); - } - else if(knockdown && mAnimation->hasAnimation("knockdown")) - { - if (isSwimming && mAnimation->hasAnimation("swimknockdown")) - { - mHitState = CharState_SwimKnockDown; - mCurrentHit = "swimknockdown"; - } - else - { - mHitState = CharState_KnockDown; - mCurrentHit = "knockdown"; - } - - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - else if (recovery) - { - std::string anim = isSwimming ? chooseRandomGroup("swimhit") : chooseRandomGroup("hit"); - if (isSwimming && mAnimation->hasAnimation(anim)) - { - mHitState = CharState_SwimHit; - mCurrentHit = anim; - mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - else - { - anim = chooseRandomGroup("hit"); - if (mAnimation->hasAnimation(anim)) - { - mHitState = CharState_Hit; - mCurrentHit = anim; - mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - } - } - else if (block && mAnimation->hasAnimation("shield")) - { - mHitState = CharState_Block; - mCurrentHit = "shield"; - MWRender::Animation::AnimPriority priorityBlock (Priority_Hit); - priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; - mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0); - } - - // Cancel upper body animations - if (isKnockedOut() || isKnockedDown()) - { - if (mUpperBodyState > UpperCharState_WeapEquiped) - { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; - } - else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) - { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_Nothing; - } - } - } - else if(!mAnimation->isPlaying(mCurrentHit)) - { - mCurrentHit.erase(); - if (knockdown) - mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(false); - if (recovery) - mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false); - if (block) - mPtr.getClass().getCreatureStats(mPtr).setBlock(false); - mHitState = CharState_None; - } - else if (isKnockedOut() && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0) - { - mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown; - mAnimation->disable(mCurrentHit); - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); - } - if (mHitState != CharState_None) - mIdleState = CharState_None; -} - -void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, bool force) -{ - if(force || jump != mJumpState) - { - mIdleState = CharState_None; - bool startAtLoop = (jump == mJumpState); - mJumpState = jump; - - std::string jumpAnimName; - MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; - if(mJumpState != JumpState_None) - { - jumpAnimName = "jump"; - if(weap != sWeaponTypeListEnd) - { - jumpAnimName += weap->shortgroup; - if(!mAnimation->hasAnimation(jumpAnimName)) - { - jumpmask = MWRender::Animation::BlendMask_LowerBody; - jumpAnimName = "jump"; - } - } - } - - if(mJumpState == JumpState_InAir) - { - mAnimation->disable(mCurrentJump); - mCurrentJump = jumpAnimName; - if (mAnimation->hasAnimation("jump")) - mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false, - 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); - } - else if (mJumpState == JumpState_Landing) - { - if (startAtLoop) - mAnimation->disable(mCurrentJump); - - if (mAnimation->hasAnimation("jump")) - mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, - 1.0f, "loop stop", "stop", 0.0f, 0); - } - else // JumpState_None - { - if (mCurrentJump.length() > 0) - { - mAnimation->disable(mCurrentJump); - mCurrentJump.clear(); - } - } - } -} - -void CharacterController::refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force) -{ - if(force || movement != mMovementState) - { - mMovementState = movement; - - if (movement != CharState_None) - mIdleState = CharState_None; - - std::string movementAnimName; - MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; - const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); - if(movestate != sMovementListEnd) - { - movementAnimName = movestate->groupname; - if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos) - { - movementAnimName += weap->shortgroup; - if(!mAnimation->hasAnimation(movementAnimName)) - { - movemask = MWRender::Animation::BlendMask_LowerBody; - movementAnimName = movestate->groupname; - } - } - - if(!mAnimation->hasAnimation(movementAnimName)) - { - std::string::size_type swimpos = movementAnimName.find("swim"); - if(swimpos == std::string::npos) - { - std::string::size_type runpos = movementAnimName.find("run"); - if (runpos != std::string::npos) - { - movementAnimName.replace(runpos, runpos+3, "walk"); - if (!mAnimation->hasAnimation(movementAnimName)) - movementAnimName.clear(); - } - else - movementAnimName.clear(); - } - else - { - movementAnimName.erase(swimpos, 4); - if (weap != sWeaponTypeListEnd) - { - std::string weapMovementAnimName = movementAnimName + weap->shortgroup; - if(mAnimation->hasAnimation(weapMovementAnimName)) - movementAnimName = weapMovementAnimName; - else - movemask = MWRender::Animation::BlendMask_LowerBody; - } - - if (!mAnimation->hasAnimation(movementAnimName)) - movementAnimName.clear(); - } - } - } - - /* If we're playing the same animation, restart from the loop start instead of the - * beginning. */ - int mode = ((movementAnimName == mCurrentMovement) ? 2 : 1); - - mMovementAnimationControlled = true; - - mAnimation->disable(mCurrentMovement); - mCurrentMovement = movementAnimName; - if(!mCurrentMovement.empty()) - { - bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) - && !MWBase::Environment::get().getWorld()->isFlying(mPtr); - - // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity - // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. - std::string anim = mCurrentMovement; - mAdjustMovementAnimSpeed = true; - if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() - && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) - { - CharacterState walkState = runStateToWalkState(mMovementState); - const StateInfo *stateinfo = std::find_if(sMovementList, sMovementListEnd, FindCharState(walkState)); - anim = stateinfo->groupname; - - mMovementAnimSpeed = mAnimation->getVelocity(anim); - if (mMovementAnimSpeed <= 1.0f) - { - // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), - // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist - // we will play without any scaling. - // Makes the speed attribute of most water creatures totally useless. - // And again, this can not be fixed without patching game data. - mAdjustMovementAnimSpeed = false; - mMovementAnimSpeed = 1.f; - } - } - else - { - mMovementAnimSpeed = mAnimation->getVelocity(anim); - - if (mMovementAnimSpeed <= 1.0f) - { - // The first person anims don't have any velocity to calculate a speed multiplier from. - // We use the third person velocities instead. - // FIXME: should be pulled from the actual animation, but it is not presently loaded. - mMovementAnimSpeed = (isrunning ? 222.857f : 154.064f); - mMovementAnimationControlled = false; - } - } - - mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, - 1.f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul, true); - } - } -} - -void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force) -{ - if(force || idle != mIdleState || - ((idle == mIdleState) && !mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) - { - mIdleState = idle; - size_t numLoops = ~0ul; - - std::string idleGroup; - MWRender::Animation::AnimPriority idlePriority (Priority_Default); - // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to - // "idle"+weapon or "idle". - if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim")) - { - idleGroup = "idleswim"; - idlePriority = Priority_SwimIdle; - } - else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak")) - { - idleGroup = "idlesneak"; - idlePriority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; - } - else if(mIdleState != CharState_None) - { - idleGroup = "idle"; - if(weap != sWeaponTypeListEnd) - { - idleGroup += weap->shortgroup; - if(!mAnimation->hasAnimation(idleGroup)) - idleGroup = "idle"; - - // play until the Loop Stop key 2 to 5 times, then play until the Stop key - // this replicates original engine behavior for the "Idle1h" 1st-person animation - numLoops = 1 + Misc::Rng::rollDice(4); - } - } - - mAnimation->disable(mCurrentIdle); - mCurrentIdle = idleGroup; - if(!mCurrentIdle.empty()) - mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, numLoops, true); - } -} - -void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) -{ - if (mPtr.getClass().isActor()) - refreshHitRecoilAnims(); - - const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); - if (!mPtr.getClass().isBipedal(mPtr)) - weap = sWeaponTypeListEnd; - - refreshJumpAnims(weap, jump, force); - refreshMovementAnims(weap, movement, force); - - // idle handled last as it can depend on the other states - // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), - // the idle animation should be displayed - if ((mUpperBodyState != UpperCharState_Nothing - || (mMovementState != CharState_None && !isTurning()) - || mHitState != CharState_None) - && !mPtr.getClass().isBipedal(mPtr)) - idle = CharState_None; - - refreshIdleAnims(weap, idle, force); -} - - -void getWeaponGroup(WeaponType weaptype, std::string &group) -{ - const WeaponInfo *info = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(weaptype)); - if(info != sWeaponTypeListEnd) - group = info->longgroup; - else - group.clear(); -} - - -MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::InventoryStore &inv, WeaponType *weaptype) -{ - if(stats.getDrawState() == DrawState_Spell) - { - *weaptype = WeapType_Spell; - return inv.end(); - } - - if(stats.getDrawState() == MWMechanics::DrawState_Weapon) - { - MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if(weapon == inv.end()) - *weaptype = WeapType_HandToHand; - else - { - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) - *weaptype = WeapType_PickProbe; - else if(type == typeid(ESM::Weapon).name()) - { - MWWorld::LiveCellRef *ref = weapon->get(); - ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; - switch(weaponType) - { - case ESM::Weapon::ShortBladeOneHand: - case ESM::Weapon::LongBladeOneHand: - case ESM::Weapon::BluntOneHand: - case ESM::Weapon::AxeOneHand: - case ESM::Weapon::Arrow: - case ESM::Weapon::Bolt: - *weaptype = WeapType_OneHand; - break; - case ESM::Weapon::LongBladeTwoHand: - case ESM::Weapon::BluntTwoClose: - case ESM::Weapon::AxeTwoHand: - *weaptype = WeapType_TwoHand; - break; - case ESM::Weapon::BluntTwoWide: - case ESM::Weapon::SpearTwoWide: - *weaptype = WeapType_TwoWide; - break; - case ESM::Weapon::MarksmanBow: - *weaptype = WeapType_BowAndArrow; - break; - case ESM::Weapon::MarksmanCrossbow: - *weaptype = WeapType_Crossbow; - break; - case ESM::Weapon::MarksmanThrown: - *weaptype = WeapType_Thrown; - break; - } - } - } - - return weapon; - } - - return inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); -} - -void CharacterController::playDeath(float startpoint, CharacterState death) -{ - switch (death) - { - case CharState_SwimDeath: - mCurrentDeath = "swimdeath"; - break; - case CharState_SwimDeathKnockDown: - mCurrentDeath = "swimdeathknockdown"; - break; - case CharState_SwimDeathKnockOut: - mCurrentDeath = "swimdeathknockout"; - break; - case CharState_DeathKnockDown: - mCurrentDeath = "deathknockdown"; - break; - case CharState_DeathKnockOut: - mCurrentDeath = "deathknockout"; - break; - default: - mCurrentDeath = "death" + toString(death - CharState_Death1 + 1); - } - mDeathState = death; - - mPtr.getClass().getCreatureStats(mPtr).setDeathAnimation(mDeathState - CharState_Death1); - - // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually. - // Note that these animations wouldn't actually be visible (due to the Death animation's priority being higher). - // However, they could still trigger text keys, such as Hit events, or sounds. - mMovementState = CharState_None; - mAnimation->disable(mCurrentMovement); - mCurrentMovement = ""; - mUpperBodyState = UpperCharState_Nothing; - mAnimation->disable(mCurrentWeapon); - mCurrentWeapon = ""; - mHitState = CharState_None; - mAnimation->disable(mCurrentHit); - mCurrentHit = ""; - mIdleState = CharState_None; - mAnimation->disable(mCurrentIdle); - mCurrentIdle = ""; - mJumpState = JumpState_None; - mAnimation->disable(mCurrentJump); - mCurrentJump = ""; - mMovementAnimationControlled = true; - - mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, - false, 1.0f, "start", "stop", startpoint, 0); -} - -CharacterState CharacterController::chooseRandomDeathState() const -{ - int selected=0; - chooseRandomGroup("death", &selected); - return static_cast(CharState_Death1 + (selected-1)); -} - -void CharacterController::playRandomDeath(float startpoint) -{ - if (mPtr == getPlayer()) - { - // The first-person animations do not include death, so we need to - // force-switch to third person before playing the death animation. - MWBase::Environment::get().getWorld()->useDeathCamera(); - } - - if(mHitState == CharState_SwimKnockDown && mAnimation->hasAnimation("swimdeathknockdown")) - { - mDeathState = CharState_SwimDeathKnockDown; - } - else if(mHitState == CharState_SwimKnockOut && mAnimation->hasAnimation("swimdeathknockout")) - { - mDeathState = CharState_SwimDeathKnockOut; - } - else if(MWBase::Environment::get().getWorld()->isSwimming(mPtr) && mAnimation->hasAnimation("swimdeath")) - { - mDeathState = CharState_SwimDeath; - } - else if (mHitState == CharState_KnockDown && mAnimation->hasAnimation("deathknockdown")) - { - mDeathState = CharState_DeathKnockDown; - } - else if (mHitState == CharState_KnockOut && mAnimation->hasAnimation("deathknockout")) - { - mDeathState = CharState_DeathKnockOut; - } - else - { - mDeathState = chooseRandomDeathState(); - } - playDeath(startpoint, mDeathState); -} - -CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) - : mPtr(ptr) - , mAnimation(anim) - , mIdleState(CharState_None) - , mMovementState(CharState_None) - , mMovementAnimSpeed(0.f) - , mAdjustMovementAnimSpeed(false) - , mHasMovedInXY(false) - , mMovementAnimationControlled(true) - , mDeathState(CharState_None) - , mFloatToSurface(true) - , mHitState(CharState_None) - , mUpperBodyState(UpperCharState_Nothing) - , mJumpState(JumpState_None) - , mWeaponType(WeapType_None) - , mAttackStrength(0.f) - , mSkipAnim(false) - , mSecondsOfSwimming(0) - , mSecondsOfRunning(0) - , mTurnAnimationThreshold(0) - , mAttackingOrSpell(false) -{ - if(!mAnimation) - return; - - mAnimation->setTextKeyListener(this); - - const MWWorld::Class &cls = mPtr.getClass(); - if(cls.isActor()) - { - /* Accumulate along X/Y only for now, until we can figure out how we should - * handle knockout and death which moves the character down. */ - mAnimation->setAccumulation(osg::Vec3f(1.0f, 1.0f, 0.0f)); - - if (cls.hasInventoryStore(mPtr)) - { - getActiveWeapon(cls.getCreatureStats(mPtr), cls.getInventoryStore(mPtr), &mWeaponType); - if (mWeaponType != WeapType_None) - { - mUpperBodyState = UpperCharState_WeapEquiped; - getWeaponGroup(mWeaponType, mCurrentWeapon); - } - - if(mWeaponType != WeapType_None && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) - { - mAnimation->showWeapons(true); - mAnimation->setWeaponGroup(mCurrentWeapon); - } - - mAnimation->showCarriedLeft(updateCarriedLeftVisible(mWeaponType)); - } - - if(!cls.getCreatureStats(mPtr).isDead()) - mIdleState = CharState_Idle; - else - { - const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); - if (cStats.isDeathAnimationFinished()) - { - // Set the death state, but don't play it yet - // We will play it in the first frame, but only if no script set the skipAnim flag - signed char deathanim = cStats.getDeathAnimation(); - if (deathanim == -1) - mDeathState = chooseRandomDeathState(); - else - mDeathState = static_cast(CharState_Death1 + deathanim); - - mFloatToSurface = false; - } - // else: nothing to do, will detect death in the next frame and start playing death animation - } - } - else - { - /* Don't accumulate with non-actors. */ - mAnimation->setAccumulation(osg::Vec3f(0.f, 0.f, 0.f)); - - mIdleState = CharState_Idle; - } - - - if(mDeathState == CharState_None) - refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); - - mAnimation->runAnimation(0.f); - - unpersistAnimationState(); -} - -CharacterController::~CharacterController() -{ - if (mAnimation) - { - persistAnimationState(); - mAnimation->setTextKeyListener(NULL); - } -} - -void split(const std::string &s, char delim, std::vector &elems) { - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } -} - -void CharacterController::handleTextKey(const std::string &groupname, const std::multimap::const_iterator &key, const std::multimap &map) -{ - const std::string &evt = key->second; - - if(evt.compare(0, 7, "sound: ") == 0) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->stopSound3D(mPtr, evt.substr(7)); - sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); - return; - } - if(evt.compare(0, 10, "soundgen: ") == 0) - { - std::string soundgen = evt.substr(10); - - // The event can optionally contain volume and pitch modifiers - float volume=1.f, pitch=1.f; - if (soundgen.find(" ") != std::string::npos) - { - std::vector tokens; - split(soundgen, ' ', tokens); - soundgen = tokens[0]; - if (tokens.size() >= 2) - { - std::stringstream stream; - stream << tokens[1]; - stream >> volume; - } - if (tokens.size() >= 3) - { - std::stringstream stream; - stream << tokens[2]; - stream >> pitch; - } - } - - std::string sound = mPtr.getClass().getSoundIdFromSndGen(mPtr, soundgen); - if(!sound.empty()) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(evt.compare(10, evt.size()-10, "left") == 0 || evt.compare(10, evt.size()-10, "right") == 0 || evt.compare(10, evt.size()-10, "land") == 0) - { - // Don't make foot sounds local for the player, it makes sense to keep them - // positioned on the ground. - sndMgr->playSound3D(mPtr, sound, volume, pitch, MWSound::Type::Foot, - MWSound::PlayMode::NoPlayerLocal); - } - else - { - sndMgr->stopSound3D(mPtr, sound); - sndMgr->playSound3D(mPtr, sound, volume, pitch); - } - } - return; - } - - if(evt.compare(0, groupname.size(), groupname) != 0 || - evt.compare(groupname.size(), 2, ": ") != 0) - { - // Not ours, skip it - return; - } - size_t off = groupname.size()+2; - size_t len = evt.size() - off; - - if(evt.compare(off, len, "equip attach") == 0) - mAnimation->showWeapons(true); - else if(evt.compare(off, len, "unequip detach") == 0) - mAnimation->showWeapons(false); - else if(evt.compare(off, len, "chop hit") == 0) - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); - else if(evt.compare(off, len, "slash hit") == 0) - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); - else if(evt.compare(off, len, "thrust hit") == 0) - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); - else if(evt.compare(off, len, "hit") == 0) - { - if (groupname == "attack1" || groupname == "swimattack1") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); - else if (groupname == "attack2" || groupname == "swimattack2") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); - else if (groupname == "attack3" || groupname == "swimattack3") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); - else - mPtr.getClass().hit(mPtr, mAttackStrength); - } - else if (!groupname.empty() - && (groupname.compare(0, groupname.size()-1, "attack") == 0 || groupname.compare(0, groupname.size()-1, "swimattack") == 0) - && evt.compare(off, len, "start") == 0) - { - std::multimap::const_iterator hitKey = key; - - // Not all animations have a hit key defined. If there is none, the hit happens with the start key. - bool hasHitKey = false; - while (hitKey != map.end()) - { - if (hitKey->second == groupname + ": hit") - { - hasHitKey = true; - break; - } - if (hitKey->second == groupname + ": stop") - break; - ++hitKey; - } - if (!hasHitKey) - { - if (groupname == "attack1" || groupname == "swimattack1") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); - else if (groupname == "attack2" || groupname == "swimattack2") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); - else if (groupname == "attack3" || groupname == "swimattack3") - mPtr.getClass().hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); - } - } - else if (evt.compare(off, len, "shoot attach") == 0) - mAnimation->attachArrow(); - else if (evt.compare(off, len, "shoot release") == 0) - mAnimation->releaseArrow(mAttackStrength); - else if (evt.compare(off, len, "shoot follow attach") == 0) - mAnimation->attachArrow(); - - else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release" - // Make sure this key is actually for the RangeType we are casting. The flame atronach has - // the same animation for all range types, so there are 3 "release" keys on the same time, one for each range type. - && evt.compare(off, len, mAttackType + " release") == 0) - { - MWBase::Environment::get().getWorld()->castSpell(mPtr); - } - - else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) - mPtr.getClass().block(mPtr); -} - -void CharacterController::updatePtr(const MWWorld::Ptr &ptr) -{ - mPtr = ptr; -} - -void CharacterController::updateIdleStormState(bool inwater) -{ - bool inStormDirection = false; - if (MWBase::Environment::get().getWorld()->isInStorm()) - { - osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection(); - osg::Vec3f characterDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); - inStormDirection = std::acos(stormDirection * characterDirection / (stormDirection.length() * characterDirection.length())) - > osg::DegreesToRadians(120.f); - } - if (inStormDirection && !inwater && mUpperBodyState == UpperCharState_Nothing && mAnimation->hasAnimation("idlestorm")) - { - float complete = 0; - mAnimation->getInfo("idlestorm", &complete); - - if (complete == 0) - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, - 1.0f, "start", "loop start", 0.0f, 0); - else if (complete == 1) - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false, - 1.0f, "loop start", "loop stop", 0.0f, ~0ul); - } - else - { - if (mUpperBodyState == UpperCharState_Nothing) - { - if (mAnimation->isPlaying("idlestorm")) - { - if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) - { - mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, true, - 1.0f, "loop stop", "stop", 0.0f, 0); - } - } - } - else - mAnimation->disable("idlestorm"); - } -} - -bool CharacterController::updateCreatureState() -{ - const MWWorld::Class &cls = mPtr.getClass(); - CreatureStats &stats = cls.getCreatureStats(mPtr); - - WeaponType weapType = WeapType_None; - if(stats.getDrawState() == DrawState_Weapon) - weapType = WeapType_HandToHand; - else if (stats.getDrawState() == DrawState_Spell) - weapType = WeapType_Spell; - - if (weapType != mWeaponType) - { - mWeaponType = weapType; - if (mAnimation->isPlaying(mCurrentWeapon)) - mAnimation->disable(mCurrentWeapon); - } - - if(mAttackingOrSpell) - { - if(mUpperBodyState == UpperCharState_Nothing && mHitState == CharState_None) - { - MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); - - std::string startKey = "start"; - std::string stopKey = "stop"; - if (weapType == WeapType_Spell) - { - const std::string spellid = stats.getSpells().getSelectedSpell(); - if (!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) - { - MWMechanics::CastSpell cast(mPtr, NULL); - cast.playSpellCastingEffects(spellid); - - if (!mAnimation->hasAnimation("spellcast")) - MWBase::Environment::get().getWorld()->castSpell(mPtr); // No "release" text key to use, so cast immediately - else - { - const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellid); - const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); - - switch(effectentry.mRange) - { - case 0: mAttackType = "self"; break; - case 1: mAttackType = "touch"; break; - case 2: mAttackType = "target"; break; - } - - startKey = mAttackType + " " + startKey; - stopKey = mAttackType + " " + stopKey; - mCurrentWeapon = "spellcast"; - } - } - else - mCurrentWeapon = ""; - } - if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation - { - bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); - int roll = Misc::Rng::rollDice(3); // [0, 2] - if (roll == 0) - mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack1") ? "swimattack1" : "attack1"; - else if (roll == 1) - mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack2") ? "swimattack2" : "attack2"; - else - mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack3") ? "swimattack3" : "attack3"; - } - - if (!mCurrentWeapon.empty()) - { - mAnimation->play(mCurrentWeapon, Priority_Weapon, - MWRender::Animation::BlendMask_All, true, - 1, startKey, stopKey, - 0.0f, 0); - mUpperBodyState = UpperCharState_StartToMinAttack; - - mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); - - if (weapType == WeapType_HandToHand) - playSwishSound(0.0f); - } - } - - mAttackingOrSpell = false; - } - - bool animPlaying = mAnimation->getInfo(mCurrentWeapon); - if (!animPlaying) - mUpperBodyState = UpperCharState_Nothing; - return false; -} - -bool CharacterController::updateCarriedLeftVisible(WeaponType weaptype) const -{ - // Shields/torches shouldn't be visible during any operation involving two hands - // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", - // but they are also present in weapon drawing animation. - switch (weaptype) - { - case WeapType_Spell: - case WeapType_BowAndArrow: - case WeapType_Crossbow: - case WeapType_HandToHand: - case WeapType_TwoHand: - case WeapType_TwoWide: - return false; - default: - return true; - } -} - -bool CharacterController::updateWeaponState() -{ - const MWWorld::Class &cls = mPtr.getClass(); - CreatureStats &stats = cls.getCreatureStats(mPtr); - WeaponType weaptype = WeapType_None; - if(stats.getDrawState() == DrawState_Weapon) - weaptype = WeapType_HandToHand; - else if (stats.getDrawState() == DrawState_Spell) - weaptype = WeapType_Spell; - - const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); - - std::string soundid; - if (mPtr.getClass().hasInventoryStore(mPtr)) - { - MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); - if(weapon != inv.end() && !(weaptype == WeapType_None && mWeaponType == WeapType_Spell)) - { - soundid = (weaptype == WeapType_None) ? - weapon->getClass().getDownSoundId(*weapon) : - weapon->getClass().getUpSoundId(*weapon); - } - } - - MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); - priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; - - bool forcestateupdate = false; - - // We should not play equipping animation and sound during weapon->weapon transition - bool isStillWeapon = weaptype > WeapType_HandToHand && weaptype < WeapType_Spell && - mWeaponType > WeapType_HandToHand && mWeaponType < WeapType_Spell; - - if(weaptype != mWeaponType && !isKnockedOut() && - !isKnockedDown() && !isRecovery()) - { - forcestateupdate = true; - - mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); - - std::string weapgroup; - if(weaptype == WeapType_None) - { - if ((!isWerewolf || mWeaponType != WeapType_Spell)) - { - getWeaponGroup(mWeaponType, weapgroup); - mAnimation->play(weapgroup, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "unequip start", "unequip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_UnEquipingWeap; - } - } - else - { - getWeaponGroup(weaptype, weapgroup); - mAnimation->setWeaponGroup(weapgroup); - - if (!isStillWeapon) - { - mAnimation->showWeapons(false); - mAnimation->play(weapgroup, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "equip start", "equip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_EquipingWeap; - } - - if(isWerewolf) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfEquip"); - if(sound) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); - } - } - } - - if(!soundid.empty() && !isStillWeapon) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, soundid, 1.0f, 1.0f); - } - - mWeaponType = weaptype; - getWeaponGroup(mWeaponType, mCurrentWeapon); - } - - if(isWerewolf) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) - && mHasMovedInXY - && !MWBase::Environment::get().getWorld()->isSwimming(mPtr) - && mWeaponType == WeapType_None) - { - if(!sndMgr->getSoundPlaying(mPtr, "WolfRun")) - sndMgr->playSound3D(mPtr, "WolfRun", 1.0f, 1.0f, MWSound::Type::Sfx, - MWSound::PlayMode::Loop); - } - else - sndMgr->stopSound3D(mPtr, "WolfRun"); - } - - // Cancel attack if we no longer have ammunition - bool ammunition = true; - bool isWeapon = false; - float weapSpeed = 1.f; - if (mPtr.getClass().hasInventoryStore(mPtr)) - { - MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); - isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()); - if(isWeapon) - weapSpeed = weapon->get()->mBase->mData.mSpeed; - - MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - if (mWeaponType == WeapType_Crossbow) - ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt); - else if (mWeaponType == WeapType_BowAndArrow) - ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow); - if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) - { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; - } - } - - float complete; - bool animPlaying; - if(mAttackingOrSpell) - { - mIdleState = CharState_None; - if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) - { - MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); - mAttackStrength = 0; - if(mWeaponType == WeapType_Spell) - { - // Unset casting flag, otherwise pressing the mouse button down would - // continue casting every frame if there is no animation - mAttackingOrSpell = false; - if (mPtr == getPlayer()) - { - MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); - } - - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - - // For the player, set the spell we want to cast - // This has to be done at the start of the casting animation, - // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) - if (mPtr == getPlayer()) - { - std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); - stats.getSpells().setSelectedSpell(selectedSpell); - } - std::string spellid = stats.getSpells().getSelectedSpell(); - - if(!spellid.empty() && MWBase::Environment::get().getWorld()->startSpellCast(mPtr)) - { - MWMechanics::CastSpell cast(mPtr, NULL); - cast.playSpellCastingEffects(spellid); - - const ESM::Spell *spell = store.get().find(spellid); - const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back(); - const ESM::MagicEffect *effect; - - effect = store.get().find(lastEffect.mEffectID); // use last effect of list for color of VFX_Hands - - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); - - for (size_t iter = 0; iter < spell->mEffects.mList.size(); ++iter) // play hands vfx for each effect - { - if (mAnimation->getNode("Bip01 L Hand")) - mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); - - if (mAnimation->getNode("Bip01 R Hand")) - mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); - } - - const ESM::ENAMstruct &firstEffect = spell->mEffects.mList.at(0); // first effect used for casting animation - - switch(firstEffect.mRange) - { - case 0: mAttackType = "self"; break; - case 1: mAttackType = "touch"; break; - case 2: mAttackType = "target"; break; - } - - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - weapSpeed, mAttackType+" start", mAttackType+" stop", - 0.0f, 0); - mUpperBodyState = UpperCharState_CastingSpell; - } - if (mPtr.getClass().hasInventoryStore(mPtr)) - { - MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - if (inv.getSelectedEnchantItem() != inv.end()) - { - // Enchanted items cast immediately (no animation) - MWBase::Environment::get().getWorld()->castSpell(mPtr); - } - } - - } - else if(mWeaponType == WeapType_PickProbe) - { - MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - MWWorld::Ptr item = *weapon; - // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject(); - std::string resultMessage, resultSound; - - if(!target.isEmpty()) - { - if(item.getTypeName() == typeid(ESM::Lockpick).name()) - Security(mPtr).pickLock(target, item, resultMessage, resultSound); - else if(item.getTypeName() == typeid(ESM::Probe).name()) - Security(mPtr).probeTrap(target, item, resultMessage, resultSound); - } - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "start", "stop", 0.0, 0); - mUpperBodyState = UpperCharState_FollowStartToFollowStop; - - if(!resultMessage.empty()) - MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); - if(!resultSound.empty()) - MWBase::Environment::get().getSoundManager()->playSound3D(target, resultSound, - 1.0f, 1.0f); - } - else if (ammunition) - { - if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || - mWeaponType == WeapType_Thrown) - mAttackType = "shoot"; - else - { - if(mPtr == getPlayer()) - { - if (isWeapon) - { - if (Settings::Manager::getBool("best attack", "Game")) - { - MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - mAttackType = getBestAttack(weapon->get()->mBase); - } - else - setAttackTypeBasedOnMovement(); - } - else - setAttackTypeRandomly(mAttackType); - } - // else if (mPtr != getPlayer()) use mAttackType set by AiCombat - } - - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, false, - weapSpeed, mAttackType+" start", mAttackType+" min attack", - 0.0f, 0); - mUpperBodyState = UpperCharState_StartToMinAttack; - } - } - - animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) - mAttackStrength = complete; - } - else - { - animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) - { - float attackStrength = complete; - if (!mPtr.getClass().isNpc()) - { - // most creatures don't actually have an attack wind-up animation, so use a uniform random value - // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings) - // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far. - attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability()); - } - - if(mWeaponType != WeapType_Crossbow && mWeaponType != WeapType_BowAndArrow) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - - if(isWerewolf) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfSwing"); - if(sound) - sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); - } - else - { - playSwishSound(attackStrength); - } - } - mAttackStrength = attackStrength; - - mAnimation->disable(mCurrentWeapon); - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, false, - weapSpeed, mAttackType+" max attack", mAttackType+" min hit", - 1.0f-complete, 0); - - complete = 0.f; - mUpperBodyState = UpperCharState_MaxAttackToMinHit; - } - else if (isKnockedDown()) - { - if (mUpperBodyState > UpperCharState_WeapEquiped) - mUpperBodyState = UpperCharState_WeapEquiped; - mAnimation->disable(mCurrentWeapon); - } - } - - mAnimation->setPitchFactor(0.f); - if (mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown) - { - switch (mUpperBodyState) - { - case UpperCharState_StartToMinAttack: - mAnimation->setPitchFactor(complete); - break; - case UpperCharState_MinAttackToMaxAttack: - case UpperCharState_MaxAttackToMinHit: - case UpperCharState_MinHitToHit: - mAnimation->setPitchFactor(1.f); - break; - case UpperCharState_FollowStartToFollowStop: - if (animPlaying) - mAnimation->setPitchFactor(1.f-complete); - break; - default: - break; - } - } - else if (mWeaponType == WeapType_Crossbow) - { - switch (mUpperBodyState) - { - case UpperCharState_EquipingWeap: - mAnimation->setPitchFactor(complete); - break; - case UpperCharState_UnEquipingWeap: - mAnimation->setPitchFactor(1.f-complete); - break; - case UpperCharState_WeapEquiped: - case UpperCharState_StartToMinAttack: - case UpperCharState_MinAttackToMaxAttack: - case UpperCharState_MaxAttackToMinHit: - case UpperCharState_MinHitToHit: - case UpperCharState_FollowStartToFollowStop: - mAnimation->setPitchFactor(1.f); - break; - default: - break; - } - } - - if(!animPlaying) - { - if(mUpperBodyState == UpperCharState_EquipingWeap || - mUpperBodyState == UpperCharState_FollowStartToFollowStop || - mUpperBodyState == UpperCharState_CastingSpell) - { - if (ammunition && mWeaponType == WeapType_Crossbow) - mAnimation->attachArrow(); - - mUpperBodyState = UpperCharState_WeapEquiped; - } - else if(mUpperBodyState == UpperCharState_UnEquipingWeap) - mUpperBodyState = UpperCharState_Nothing; - } - else if(complete >= 1.0f) - { - std::string start, stop; - switch(mUpperBodyState) - { - case UpperCharState_StartToMinAttack: - start = mAttackType+" min attack"; - stop = mAttackType+" max attack"; - mUpperBodyState = UpperCharState_MinAttackToMaxAttack; - break; - case UpperCharState_MinAttackToMaxAttack: - //hack to avoid body pos desync when jumping/sneaking in 'max attack' state - if(!mAnimation->isPlaying(mCurrentWeapon)) - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, false, - 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); - break; - case UpperCharState_MaxAttackToMinHit: - if(mAttackType == "shoot") - { - start = mAttackType+" min hit"; - stop = mAttackType+" release"; - } - else - { - start = mAttackType+" min hit"; - stop = mAttackType+" hit"; - } - mUpperBodyState = UpperCharState_MinHitToHit; - break; - case UpperCharState_MinHitToHit: - if(mAttackType == "shoot") - { - start = mAttackType+" follow start"; - stop = mAttackType+" follow stop"; - } - else - { - float str = mAttackStrength; - start = mAttackType+((str < 0.5f) ? " small follow start" - : (str < 1.0f) ? " medium follow start" - : " large follow start"); - stop = mAttackType+((str < 0.5f) ? " small follow stop" - : (str < 1.0f) ? " medium follow stop" - : " large follow stop"); - } - mUpperBodyState = UpperCharState_FollowStartToFollowStop; - break; - default: - break; - } - - if(!start.empty()) - { - mAnimation->disable(mCurrentWeapon); - if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - weapSpeed, start, stop, 0.0f, 0); - else - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, false, - weapSpeed, start, stop, 0.0f, 0); - } - } - - if (mPtr.getClass().hasInventoryStore(mPtr)) - { - const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() - && updateCarriedLeftVisible(mWeaponType)) - - { - mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, - false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); - } - else if (mAnimation->isPlaying("torch")) - { - mAnimation->disable("torch"); - } - } - - mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped); - - return forcestateupdate; -} - -void CharacterController::updateAnimQueue() -{ - if(mAnimQueue.size() > 1) - { - if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) - { - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.pop_front(); - - bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, - MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); - } - } - - if(!mAnimQueue.empty()) - mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); -} - -void CharacterController::update(float duration) -{ - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Class &cls = mPtr.getClass(); - osg::Vec3f movement(0.f, 0.f, 0.f); - float speed = 0.f; - - updateMagicEffects(); - - bool godmode = mPtr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - - if(!cls.isActor()) - updateAnimQueue(); - else if(!cls.getCreatureStats(mPtr).isDead()) - { - bool onground = world->isOnGround(mPtr); - bool inwater = world->isSwimming(mPtr); - bool sneak = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Sneak); - bool flying = world->isFlying(mPtr); - // Can't run while flying (see speed formula in Npc/Creature::getSpeed) - bool isrunning = cls.getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run) && !flying; - CreatureStats &stats = cls.getCreatureStats(mPtr); - - //Force Jump Logic - - bool isMoving = (std::abs(cls.getMovementSettings(mPtr).mPosition[0]) > .5 || std::abs(cls.getMovementSettings(mPtr).mPosition[1]) > .5); - if(!inwater && !flying) - { - //Force Jump - if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceJump)) - { - if(onground) - { - cls.getMovementSettings(mPtr).mPosition[2] = 1; - } - else - cls.getMovementSettings(mPtr).mPosition[2] = 0; - } - //Force Move Jump, only jump if they're otherwise moving - if(stats.getMovementFlag(MWMechanics::CreatureStats::Flag_ForceMoveJump) && isMoving) - { - - if(onground) - { - cls.getMovementSettings(mPtr).mPosition[2] = 1; - } - else - cls.getMovementSettings(mPtr).mPosition[2] = 0; - } - } - - osg::Vec3f vec(cls.getMovementSettings(mPtr).asVec3()); - vec.normalize(); - - if(mHitState != CharState_None && mJumpState == JumpState_None) - vec = osg::Vec3f(0.f, 0.f, 0.f); - osg::Vec3f rot = cls.getRotationVector(mPtr); - - speed = cls.getSpeed(mPtr); - - vec.x() *= speed; - vec.y() *= speed; - - CharacterState movestate = CharState_None; - CharacterState idlestate = CharState_SpecialIdle; - JumpingState jumpstate = JumpState_None; - - bool forcestateupdate = false; - - mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f; - isrunning = isrunning && mHasMovedInXY; - - // advance athletics - if(mHasMovedInXY && mPtr == getPlayer()) - { - if(inwater) - { - mSecondsOfSwimming += duration; - while(mSecondsOfSwimming > 1) - { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1); - mSecondsOfSwimming -= 1; - } - } - else if(isrunning && !sneak) - { - mSecondsOfRunning += duration; - while(mSecondsOfRunning > 1) - { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0); - mSecondsOfRunning -= 1; - } - } - } - - // reduce fatigue - const MWWorld::Store &gmst = world->getStore().get(); - float fatigueLoss = 0; - static const float fFatigueRunBase = gmst.find("fFatigueRunBase")->getFloat(); - static const float fFatigueRunMult = gmst.find("fFatigueRunMult")->getFloat(); - static const float fFatigueSwimWalkBase = gmst.find("fFatigueSwimWalkBase")->getFloat(); - static const float fFatigueSwimRunBase = gmst.find("fFatigueSwimRunBase")->getFloat(); - static const float fFatigueSwimWalkMult = gmst.find("fFatigueSwimWalkMult")->getFloat(); - static const float fFatigueSwimRunMult = gmst.find("fFatigueSwimRunMult")->getFloat(); - static const float fFatigueSneakBase = gmst.find("fFatigueSneakBase")->getFloat(); - static const float fFatigueSneakMult = gmst.find("fFatigueSneakMult")->getFloat(); - - if (cls.getEncumbrance(mPtr) <= cls.getCapacity(mPtr)) - { - const float encumbrance = cls.getEncumbrance(mPtr) / cls.getCapacity(mPtr); - - if (sneak) - fatigueLoss = fFatigueSneakBase + encumbrance * fFatigueSneakMult; - else - { - if (inwater) - { - if (!isrunning) - fatigueLoss = fFatigueSwimWalkBase + encumbrance * fFatigueSwimWalkMult; - else - fatigueLoss = fFatigueSwimRunBase + encumbrance * fFatigueSwimRunMult; - } - else if (isrunning) - fatigueLoss = fFatigueRunBase + encumbrance * fFatigueRunMult; - } - } - fatigueLoss *= duration; - DynamicStat fatigue = cls.getCreatureStats(mPtr).getFatigue(); - - if (!godmode) - { - fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss, fatigue.getCurrent() < 0); - cls.getCreatureStats(mPtr).setFatigue(fatigue); - } - - if(sneak || inwater || flying) - vec.z() = 0.0f; - - bool inJump = true; - if(!onground && !flying && !inwater) - { - // In the air (either getting up —ascending part of jump— or falling). - - forcestateupdate = (mJumpState != JumpState_InAir); - jumpstate = JumpState_InAir; - - static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); - static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); - float factor = fJumpMoveBase + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics)/100.f; - factor = std::min(1.f, factor); - vec.x() *= factor; - vec.y() *= factor; - vec.z() = 0.0f; - } - else if(vec.z() > 0.0f && mJumpState == JumpState_None) - { - // Started a jump. - float z = cls.getJump(mPtr); - if (z > 0) - { - if(vec.x() == 0 && vec.y() == 0) - vec = osg::Vec3f(0.0f, 0.0f, z); - else - { - osg::Vec3f lat (vec.x(), vec.y(), 0.0f); - lat.normalize(); - vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f; - } - - // advance acrobatics - if (mPtr == getPlayer()) - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); - - // decrease fatigue - const float fatigueJumpBase = gmst.find("fFatigueJumpBase")->getFloat(); - const float fatigueJumpMult = gmst.find("fFatigueJumpMult")->getFloat(); - float normalizedEncumbrance = mPtr.getClass().getNormalizedEncumbrance(mPtr); - if (normalizedEncumbrance > 1) - normalizedEncumbrance = 1; - const float fatigueDecrease = fatigueJumpBase + normalizedEncumbrance * fatigueJumpMult; - - if (!godmode) - { - fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease); - cls.getCreatureStats(mPtr).setFatigue(fatigue); - } - } - } - else if(mJumpState == JumpState_InAir && !inwater && !flying) - { - forcestateupdate = true; - jumpstate = JumpState_Landing; - vec.z() = 0.0f; - - float height = cls.getCreatureStats(mPtr).land(); - float healthLost = getFallDamage(mPtr, height); - - if (healthLost > 0.0f) - { - const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm(); - - // inflict fall damages - if (!godmode) - { - DynamicStat health = cls.getCreatureStats(mPtr).getHealth(); - float realHealthLost = static_cast(healthLost * (1.0f - 0.25f * fatigueTerm)); - health.setCurrent(health.getCurrent() - realHealthLost); - cls.getCreatureStats(mPtr).setHealth(health); - cls.onHit(mPtr, realHealthLost, true, MWWorld::Ptr(), MWWorld::Ptr(), osg::Vec3f(), true); - } - - const int acrobaticsSkill = cls.getSkill(mPtr, ESM::Skill::Acrobatics); - if (healthLost > (acrobaticsSkill * fatigueTerm)) - { - cls.getCreatureStats(mPtr).setKnockedDown(true); - } - else - { - // report acrobatics progression - if (mPtr == getPlayer()) - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); - } - } - } - else - { - jumpstate = mAnimation->isPlaying(mCurrentJump) ? JumpState_Landing : JumpState_None; - - vec.z() = 0.0f; - - inJump = false; - - if(std::abs(vec.x()/2.0f) > std::abs(vec.y())) - { - if(vec.x() > 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunRight : CharState_SwimWalkRight) - : (sneak ? CharState_SneakRight - : (isrunning ? CharState_RunRight : CharState_WalkRight))); - else if(vec.x() < 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunLeft : CharState_SwimWalkLeft) - : (sneak ? CharState_SneakLeft - : (isrunning ? CharState_RunLeft : CharState_WalkLeft))); - } - else if(vec.y() != 0.0f) - { - if(vec.y() > 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunForward : CharState_SwimWalkForward) - : (sneak ? CharState_SneakForward - : (isrunning ? CharState_RunForward : CharState_WalkForward))); - else if(vec.y() < 0.0f) - movestate = (inwater ? (isrunning ? CharState_SwimRunBack : CharState_SwimWalkBack) - : (sneak ? CharState_SneakBack - : (isrunning ? CharState_RunBack : CharState_WalkBack))); - } - else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) - { - if(rot.z() > 0.0f) - { - movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; - mAnimation->disable(mCurrentJump); - } - else if(rot.z() < 0.0f) - { - movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; - mAnimation->disable(mCurrentJump); - } - } - } - - mTurnAnimationThreshold -= duration; - if (isTurning()) - mTurnAnimationThreshold = 0.05f; - else if (movestate == CharState_None && isTurning() - && mTurnAnimationThreshold > 0) - { - movestate = mMovementState; - } - - if(movestate != CharState_None && !isTurning()) - clearAnimQueue(); - - if(mAnimQueue.empty() || inwater || sneak) - { - idlestate = (inwater ? CharState_IdleSwim : (sneak && !inJump ? CharState_IdleSneak : CharState_Idle)); - } - else - updateAnimQueue(); - - if (!mSkipAnim) - { - // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. - if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) - forcestateupdate = updateWeaponState() || forcestateupdate; - else - forcestateupdate = updateCreatureState() || forcestateupdate; - - refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate); - updateIdleStormState(inwater); - } - - if (inJump) - mMovementAnimationControlled = false; - - if (isTurning()) - { - if (duration > 0) - mAnimation->adjustSpeedMult(mCurrentMovement, std::min(1.5f, std::abs(rot.z()) / duration / static_cast(osg::PI))); - } - else if (mMovementState != CharState_None && mAdjustMovementAnimSpeed) - { - float speedmult = speed / mMovementAnimSpeed; - mAnimation->adjustSpeedMult(mCurrentMovement, speedmult); - } - - if (!mSkipAnim) - { - if(!isKnockedDown() && !isKnockedOut()) - { - if (rot != osg::Vec3f()) - world->rotateObject(mPtr, rot.x(), rot.y(), rot.z(), true); - } - else //avoid z-rotating for knockdown - { - if (rot.x() != 0 && rot.y() != 0) - world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true); - } - - if (!mMovementAnimationControlled) - world->queueMovement(mPtr, vec); - } - else - // We must always queue movement, even if there is none, to apply gravity. - world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); - - movement = vec; - cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; - // Can't reset jump state (mPosition[2]) here; we don't know for sure whether the PhysicSystem will actually handle it in this frame - // due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled. - - if (!mSkipAnim) - updateHeadTracking(duration); - } - else if(cls.getCreatureStats(mPtr).isDead()) - { - // initial start of death animation for actors that started the game as dead - // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag - if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty()) - { - playDeath(1.f, mDeathState); - } - // We must always queue movement, even if there is none, to apply gravity. - world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); - } - - osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); - if(duration > 0.0f) - moved /= duration; - else - moved = osg::Vec3f(0.f, 0.f, 0.f); - - // Ensure we're moving in generally the right direction... - if(speed > 0.f) - { - float l = moved.length(); - - if((movement.x() < 0.0f && movement.x() < moved.x()*2.0f) || - (movement.x() > 0.0f && movement.x() > moved.x()*2.0f)) - moved.x() = movement.x(); - if((movement.y() < 0.0f && movement.y() < moved.y()*2.0f) || - (movement.y() > 0.0f && movement.y() > moved.y()*2.0f)) - moved.y() = movement.y(); - if((movement.z() < 0.0f && movement.z() < moved.z()*2.0f) || - (movement.z() > 0.0f && movement.z() > moved.z()*2.0f)) - moved.z() = movement.z(); - // but keep the original speed - float newLength = moved.length(); - if (newLength > 0) - moved *= (l / newLength); - } - - if (mSkipAnim) - mAnimation->updateEffects(duration); - - if (mFloatToSurface && cls.isActor() && cls.getCreatureStats(mPtr).isDead() && cls.canSwim(mPtr)) - moved.z() = 1.0; - - // Update movement - if(mMovementAnimationControlled && mPtr.getClass().isActor()) - world->queueMovement(mPtr, moved); - - mSkipAnim = false; - - mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead()); -} - -void CharacterController::persistAnimationState() -{ - ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); - - state.mScriptedAnims.clear(); - for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) - { - if (!iter->mPersist) - continue; - - ESM::AnimationState::ScriptedAnimation anim; - anim.mGroup = iter->mGroup; - - if (iter == mAnimQueue.begin()) - { - anim.mLoopCount = mAnimation->getCurrentLoopCount(anim.mGroup); - float complete; - mAnimation->getInfo(anim.mGroup, &complete, NULL); - anim.mTime = complete; - } - else - { - anim.mLoopCount = iter->mLoopCount; - anim.mTime = 0.f; - } - - state.mScriptedAnims.push_back(anim); - } -} - -void CharacterController::unpersistAnimationState() -{ - const ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); - - if (!state.mScriptedAnims.empty()) - { - clearAnimQueue(); - for (ESM::AnimationState::ScriptedAnimations::const_iterator iter = state.mScriptedAnims.begin(); iter != state.mScriptedAnims.end(); ++iter) - { - AnimationQueueEntry entry; - entry.mGroup = iter->mGroup; - entry.mLoopCount = iter->mLoopCount; - entry.mPersist = true; - - mAnimQueue.push_back(entry); - } - - const ESM::AnimationState::ScriptedAnimation& anim = state.mScriptedAnims.front(); - float complete = anim.mTime; - if (anim.mAbsolute) - { - float start = mAnimation->getTextKeyTime(anim.mGroup+": start"); - float stop = mAnimation->getTextKeyTime(anim.mGroup+": stop"); - float time = std::max(start, std::min(stop, anim.mTime)); - complete = (time - start) / (stop - start); - } - - mAnimation->disable(mCurrentIdle); - mCurrentIdle.clear(); - mIdleState = CharState_SpecialIdle; - - bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); - mAnimation->play(anim.mGroup, - Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, - "start", "stop", complete, anim.mLoopCount, loopfallback); - } -} - -bool CharacterController::playGroup(const std::string &groupname, int mode, int count, bool persist) -{ - if(!mAnimation || !mAnimation->hasAnimation(groupname)) - return false; - - // If this animation is a looped animation (has a "loop start" key) that is already playing - // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count - // and remove any other animations that were queued. - // This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners correctly. - if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && - mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 && - mAnimation->isPlaying(groupname)) - { - float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop stop"); - - if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key - endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop"); - - if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop)) - { - mAnimQueue.resize(1); - return true; - } - } - - count = std::max(count, 1); - - AnimationQueueEntry entry; - entry.mGroup = groupname; - entry.mLoopCount = count-1; - entry.mPersist = persist; - - if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) - { - clearAnimQueue(); - mAnimQueue.push_back(entry); - - mAnimation->disable(mCurrentIdle); - mCurrentIdle.clear(); - - mIdleState = CharState_SpecialIdle; - bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0); - mAnimation->play(groupname, Priority_Default, - MWRender::Animation::BlendMask_All, false, 1.0f, - ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback); - } - else if(mode == 0) - { - mAnimQueue.resize(1); - mAnimQueue.push_back(entry); - } - return true; -} - -void CharacterController::skipAnim() -{ - mSkipAnim = true; -} - -bool CharacterController::isAnimPlaying(const std::string &groupName) -{ - if(mAnimation == NULL) - return false; - return mAnimation->isPlaying(groupName); -} - - -void CharacterController::clearAnimQueue() -{ - if(!mAnimQueue.empty()) - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.clear(); -} - -void CharacterController::forceStateUpdate() -{ - if(!mAnimation) - return; - clearAnimQueue(); - - refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); - if(mDeathState != CharState_None) - { - playRandomDeath(); - } - - mAnimation->runAnimation(0.f); -} - -CharacterController::KillResult CharacterController::kill() -{ - if (mDeathState == CharState_None) - { - playRandomDeath(); - - mAnimation->disable(mCurrentIdle); - - mIdleState = CharState_None; - mCurrentIdle.clear(); - return Result_DeathAnimStarted; - } - - MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr); - if (isAnimPlaying(mCurrentDeath)) - return Result_DeathAnimPlaying; - if (!cStats.isDeathAnimationFinished()) - { - cStats.setDeathAnimationFinished(true); - return Result_DeathAnimJustFinished; - } - return Result_DeathAnimFinished; -} - -void CharacterController::resurrect() -{ - if(mDeathState == CharState_None) - return; - - if(mAnimation) - mAnimation->disable(mCurrentDeath); - mCurrentDeath.clear(); - mDeathState = CharState_None; -} - -void CharacterController::updateContinuousVfx() -{ - // Keeping track of when to stop a continuous VFX seems to be very difficult to do inside the spells code, - // as it's extremely spread out (ActiveSpells, Spells, InventoryStore effects, etc...) so we do it here. - - // Stop any effects that are no longer active - std::vector effects; - mAnimation->getLoopingEffects(effects); - - for (std::vector::iterator it = effects.begin(); it != effects.end(); ++it) - { - if (mPtr.getClass().getCreatureStats(mPtr).isDead() - || mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(MWMechanics::EffectKey(*it)).getMagnitude() <= 0) - mAnimation->removeEffect(*it); - } -} - -void CharacterController::updateMagicEffects() -{ - if (!mPtr.getClass().isActor()) - return; - float alpha = 1.f; - if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555). - { - if (mPtr == getPlayer()) - alpha = 0.4f; - else - alpha = 0.f; - } - float chameleon = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude(); - if (chameleon) - { - alpha *= std::max(0.2f, (100.f - chameleon)/100.f); - } - mAnimation->setAlpha(alpha); - - bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f; - mAnimation->setVampire(vampire); - - float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude(); - mAnimation->setLightEffect(light); -} - -void CharacterController::setAttackTypeBasedOnMovement() -{ - float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition; - - if (move[1] && !move[0]) // forward-backward - mAttackType = "thrust"; - else if (move[0] && !move[1]) //sideway - mAttackType = "slash"; - else - mAttackType = "chop"; -} - -bool CharacterController::isAttackPrepairing() const -{ - return mUpperBodyState == UpperCharState_StartToMinAttack || - mUpperBodyState == UpperCharState_MinAttackToMaxAttack; -} - -bool CharacterController::isReadyToBlock() const -{ - return updateCarriedLeftVisible(mWeaponType); -} - -bool CharacterController::isKnockedDown() const -{ - return mHitState == CharState_KnockDown || - mHitState == CharState_SwimKnockDown; -} - -bool CharacterController::isKnockedOut() const -{ - return mHitState == CharState_KnockOut || - mHitState == CharState_SwimKnockOut; -} - -bool CharacterController::isTurning() const -{ - return mMovementState == CharState_TurnLeft || - mMovementState == CharState_TurnRight || - mMovementState == CharState_SwimTurnLeft || - mMovementState == CharState_SwimTurnRight; -} - -bool CharacterController::isRecovery() const -{ - return mHitState == CharState_Hit || - mHitState == CharState_SwimHit; -} - -bool CharacterController::isAttackingOrSpell() const -{ - return mUpperBodyState != UpperCharState_Nothing && - mUpperBodyState != UpperCharState_WeapEquiped; -} - -bool CharacterController::isSneaking() const -{ - return mIdleState == CharState_IdleSneak || - mMovementState == CharState_SneakForward || - mMovementState == CharState_SneakBack || - mMovementState == CharState_SneakLeft || - mMovementState == CharState_SneakRight; -} - -bool CharacterController::isRunning() const -{ - return mMovementState == CharState_RunForward || - mMovementState == CharState_RunBack || - mMovementState == CharState_RunLeft || - mMovementState == CharState_RunRight || - mMovementState == CharState_SwimRunForward || - mMovementState == CharState_SwimRunBack || - mMovementState == CharState_SwimRunLeft || - mMovementState == CharState_SwimRunRight; -} - -void CharacterController::setAttackingOrSpell(bool attackingOrSpell) -{ - mAttackingOrSpell = attackingOrSpell; -} - -void CharacterController::setAIAttackType(const std::string& attackType) -{ - mAttackType = attackType; -} - -void CharacterController::setAttackTypeRandomly(std::string& attackType) -{ - float random = Misc::Rng::rollProbability(); - if (random >= 2/3.f) - attackType = "thrust"; - else if (random >= 1/3.f) - attackType = "slash"; - else - attackType = "chop"; -} - -bool CharacterController::readyToPrepareAttack() const -{ - return (mHitState == CharState_None || mHitState == CharState_Block) - && mUpperBodyState <= UpperCharState_WeapEquiped; -} - -bool CharacterController::readyToStartAttack() const -{ - if (mHitState != CharState_None && mHitState != CharState_Block) - return false; - - if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr)) - return mUpperBodyState == UpperCharState_WeapEquiped; - else - return mUpperBodyState == UpperCharState_Nothing; -} - -float CharacterController::getAttackStrength() const -{ - return mAttackStrength; -} - -void CharacterController::setActive(bool active) -{ - mAnimation->setActive(active); -} - -void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target) -{ - mHeadTrackTarget = target; -} - -void CharacterController::playSwishSound(float attackStrength) -{ - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - - std::string sound = "Weapon Swish"; - if(attackStrength < 0.5f) - sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack - else if(attackStrength < 1.0f) - sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack - else - sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack -} - -void CharacterController::updateHeadTracking(float duration) -{ - const osg::Node* head = mAnimation->getNode("Bip01 Head"); - if (!head) - return; - - float zAngleRadians = 0.f; - float xAngleRadians = 0.f; - - if (!mHeadTrackTarget.isEmpty()) - { - osg::NodePathList nodepaths = head->getParentalNodePaths(); - if (nodepaths.empty()) - return; - osg::Matrixf mat = osg::computeLocalToWorld(nodepaths[0]); - osg::Vec3f headPos = mat.getTrans(); - - osg::Vec3f direction; - if (const MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) - { - const osg::Node* node = anim->getNode("Head"); - if (node == NULL) - node = anim->getNode("Bip01 Head"); - if (node != NULL) - { - nodepaths = node->getParentalNodePaths(); - if (!nodepaths.empty()) - direction = osg::computeLocalToWorld(nodepaths[0]).getTrans() - headPos; - } - else - // no head node to look at, fall back to look at center of collision box - direction = MWBase::Environment::get().getWorld()->aimToTarget(mPtr, mHeadTrackTarget); - } - direction.normalize(); - - if (!mPtr.getRefData().getBaseNode()) - return; - const osg::Vec3f actorDirection = mPtr.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0,1,0); - - zAngleRadians = std::atan2(direction.x(), direction.y()) - std::atan2(actorDirection.x(), actorDirection.y()); - xAngleRadians = -std::asin(direction.z()); - - wrap(zAngleRadians); - wrap(xAngleRadians); - - xAngleRadians = std::min(xAngleRadians, osg::DegreesToRadians(40.f)); - xAngleRadians = std::max(xAngleRadians, osg::DegreesToRadians(-40.f)); - zAngleRadians = std::min(zAngleRadians, osg::DegreesToRadians(30.f)); - zAngleRadians = std::max(zAngleRadians, osg::DegreesToRadians(-30.f)); - } - - float factor = duration*5; - factor = std::min(factor, 1.f); - xAngleRadians = (1.f-factor) * mAnimation->getHeadPitch() + factor * (-xAngleRadians); - zAngleRadians = (1.f-factor) * mAnimation->getHeadYaw() + factor * (-zAngleRadians); - - mAnimation->setHeadPitch(xAngleRadians); - mAnimation->setHeadYaw(zAngleRadians); -} - -} diff --git a/apps/openmw/mwmechanics/character.cpp.rej b/apps/openmw/mwmechanics/character.cpp.rej deleted file mode 100644 index f04f72a90..000000000 --- a/apps/openmw/mwmechanics/character.cpp.rej +++ /dev/null @@ -1,46 +0,0 @@ ---- apps/openmw/mwmechanics/character.cpp -+++ apps/openmw/mwmechanics/character.cpp -@@ -372,29 +372,28 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState - } - } - -- if(mJumpState == JumpState_InAir) -+ if (!mCurrentJump.empty()) - { - mAnimation->disable(mCurrentJump); -- mCurrentJump = jumpAnimName; -- if (mAnimation->hasAnimation("jump")) -- mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false, -+ mCurrentJump.clear(); -+ } -+ -+ if(mJumpState == JumpState_InAir) -+ { -+ if (mAnimation->hasAnimation(jumpAnimName)) -+ { -+ mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, - 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul); -+ mCurrentJump = jumpAnimName; -+ } - } - else if (mJumpState == JumpState_Landing) - { -- if (startAtLoop) -- mAnimation->disable(mCurrentJump); -- -- if (mAnimation->hasAnimation("jump")) -+ if (mAnimation->hasAnimation(jumpAnimName)) -+ { - mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, - 1.0f, "loop stop", "stop", 0.0f, 0); -- } -- else // JumpState_None -- { -- if (mCurrentJump.length() > 0) -- { -- mAnimation->disable(mCurrentJump); -- mCurrentJump.clear(); -+ mCurrentJump = jumpAnimName; - } - } - } From 5a07d135ae356837e64b050550ade27990bd9d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 10 Nov 2017 10:34:46 +0100 Subject: [PATCH 16/91] add settings for cubemap size --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9c6eac3c0..3416d1333 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -766,8 +766,8 @@ namespace MWRender int screenshotHeight = Settings::Manager::tryGetInt("s360 height","Video",mViewer->getCamera()->getViewport()->height()); SphericalScreenshot::SphericalScreenshotMapping mapping = static_cast( Settings::Manager::tryGetInt("s360 mapping","Video",SphericalScreenshot::MAPPING_SPHERICAL)); + int cubeWidth = Settings::Manager::tryGetInt("s360 cubemap size","Video",screenshotWidth / 2); - int cubeWidth = screenshotWidth / 2; SphericalScreenshot s(cubeWidth); osg::Vec3 directions[6] = { From 5baff05bace7598a653b18eb3b8bf2489c35761f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 10 Nov 2017 10:50:28 +0100 Subject: [PATCH 17/91] add cubemap mapping --- apps/openmw/mwrender/renderingmanager.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3416d1333..a19af4822 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -629,7 +629,8 @@ namespace MWRender { MAPPING_SPHERICAL = 0, MAPPING_CYLINDRICAL, - MAPPING_SMALL_PLANET + MAPPING_SMALL_PLANET, + MAPPING_CUBEMAP } SphericalScreenshotMapping; SphericalScreenshot(int size) @@ -647,6 +648,16 @@ namespace MWRender void create(osg::Image *dest, int w, int h, SphericalScreenshotMapping mapping) { + if (mapping == MAPPING_CUBEMAP) + { + dest->allocateImage(mSize * 6,mSize,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); + + for (int i = 0; i < 6; i++) + osg::copyImage(mImages[i].get(),0,0,0,mImages[i]->s(),mImages[i]->t(),mImages[i]->r(),dest,i * mSize,0,0); + + return; + } + dest->allocateImage(w,h,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); for (int j = 0; j < h; ++j) @@ -768,6 +779,9 @@ namespace MWRender Settings::Manager::tryGetInt("s360 mapping","Video",SphericalScreenshot::MAPPING_SPHERICAL)); int cubeWidth = Settings::Manager::tryGetInt("s360 cubemap size","Video",screenshotWidth / 2); + if (mapping == SphericalScreenshot::MAPPING_CUBEMAP) + cubeWidth = screenshotWidth / 6; // the image will consist of 6 cube sides in a row + SphericalScreenshot s(cubeWidth); osg::Vec3 directions[6] = { From 9ab3a0c44b9068e477db9cb4fdfabcf1323b4b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 10 Nov 2017 11:16:25 +0100 Subject: [PATCH 18/91] set cubemap width differently --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a19af4822..dbf62a784 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -780,7 +780,7 @@ namespace MWRender int cubeWidth = Settings::Manager::tryGetInt("s360 cubemap size","Video",screenshotWidth / 2); if (mapping == SphericalScreenshot::MAPPING_CUBEMAP) - cubeWidth = screenshotWidth / 6; // the image will consist of 6 cube sides in a row + screenshotWidth = cubeWidth * 6; // the image will consist of 6 cube sides in a row SphericalScreenshot s(cubeWidth); From 497b33e4037ad9b296fc6bb757b2d9655735b9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 10 Nov 2017 14:28:09 +0100 Subject: [PATCH 19/91] small corrections --- apps/openmw/mwrender/renderingmanager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index dbf62a784..6b94c72f8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -713,16 +713,16 @@ namespace MWRender osg::Vec4 getColorByDirection(osg::Vec3d d) { - // for details see OpenGL 4.4 specification page 241 + // for details see OpenGL 4.4 specification page 225 double x, y; double ma; int side; double ax, ay, az; - ax = d.x() > 0 ? d.x() : -d.x(); // abs behaves weirdly for some reason - ay = d.y() > 0 ? d.y() : -d.y(); - az = d.z() > 0 ? d.z() : -d.z(); + ax = fabs(d.x()); + ay = fabs(d.y()); + az = fabs(d.z()); if (ax > ay) if (ax > az) From 319ed2f9b86372fa604535cde1f1117e0fdd6793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 10 Nov 2017 15:23:44 +0100 Subject: [PATCH 20/91] disable 360 screenshots in vanity/preview mode --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwinput/inputmanagerimp.cpp | 7 +++---- apps/openmw/mwrender/renderingmanager.cpp | 7 ++++++- apps/openmw/mwrender/renderingmanager.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 ++-- apps/openmw/mwworld/worldimp.hpp | 2 +- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 6d39229f0..f91d1bc2a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -450,7 +450,7 @@ namespace MWBase /// \todo this does not belong here virtual void screenshot (osg::Image* image, int w, int h) = 0; - virtual void screenshot360 (osg::Image* image) = 0; + virtual bool screenshot360 (osg::Image* image) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 9ea1f8e16..bbd6feb66 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1028,10 +1028,9 @@ namespace MWInput void InputManager::screenshot360() { osg::ref_ptr screenshot (new osg::Image); - MWBase::Environment::get().getWorld()->screenshot360(screenshot.get()); - - // calling mScreenCaptureHandler->getCaptureOperation() here caused segfault for some reason - (*mScreenCaptureOperation) (*(screenshot.get()),0); + if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) + (*mScreenCaptureOperation) (*(screenshot.get()),0); + // calling mScreenCaptureHandler->getCaptureOperation() here caused segfault for some reason } void InputManager::toggleInventory() diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 6b94c72f8..aefc55c39 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -771,8 +771,11 @@ namespace MWRender int mSize; }; - void RenderingManager::screenshot360(osg::Image* image) + bool RenderingManager::screenshot360(osg::Image* image) { + if (mCamera->isVanityOrPreviewModeEnabled()) + return false; + int screenshotWidth = Settings::Manager::tryGetInt("s360 width","Video",mViewer->getCamera()->getViewport()->width()); int screenshotHeight = Settings::Manager::tryGetInt("s360 height","Video",mViewer->getCamera()->getViewport()->height()); SphericalScreenshot::SphericalScreenshotMapping mapping = static_cast( @@ -811,6 +814,8 @@ namespace MWRender mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); mFieldOfView = fovBackup; + + return true; } void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction, bool disableWaterEffects) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index cb0723397..a4cb9bf8f 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -126,7 +126,7 @@ namespace MWRender /// Take a screenshot of w*h onto the given image, not including the GUI. void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1), bool disableWaterEffects=false); - void screenshot360(osg::Image* image); + bool screenshot360(osg::Image* image); struct RayResult { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f51f9af3d..0d0079677 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2281,9 +2281,9 @@ namespace MWWorld mRendering->screenshot(image, w, h); } - void World::screenshot360 (osg::Image* image) + bool World::screenshot360(osg::Image* image) { - mRendering->screenshot360(image); + return mRendering->screenshot360(image); } void World::activateDoor(const MWWorld::Ptr& door) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index a68982412..3a54d28ae 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -560,7 +560,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; - void screenshot360 (osg::Image* image) override; + bool screenshot360 (osg::Image* image) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise From 1f49612ca38aa8e7bc546d84e664644f5009645a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 10 Nov 2017 19:18:16 +0100 Subject: [PATCH 21/91] enable water effects for 360 screenshots --- apps/openmw/mwrender/renderingmanager.cpp | 14 ++++++-------- apps/openmw/mwrender/renderingmanager.hpp | 2 +- apps/openmw/mwrender/water.cpp | 10 ++++++++++ apps/openmw/mwrender/water.hpp | 4 ++++ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index aefc55c39..ff54ab1dc 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -807,7 +807,7 @@ namespace MWRender for (int i = 0; i < 6; i++) // for each cube side { osg::Image *sideImage = s.getImage(i); - screenshot(sideImage,cubeWidth,cubeWidth,directions[i],true); + screenshot(sideImage,cubeWidth,cubeWidth,directions[i]); } s.create(image,screenshotWidth,mapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotHeight : screenshotWidth,mapping); @@ -818,7 +818,7 @@ namespace MWRender return true; } - void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction, bool disableWaterEffects) + void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction) { osg::ref_ptr rttCamera (new osg::Camera); rttCamera->setNodeMask(Mask_RenderToTexture); @@ -844,6 +844,10 @@ namespace MWRender rttCamera->setUpdateCallback(new NoTraverseCallback); rttCamera->addChild(mSceneRoot); + + rttCamera->addChild(mWater->getReflectionCamera()); + rttCamera->addChild(mWater->getRefractionCamera()); + rttCamera->setCullMask(mViewer->getCamera()->getCullMask() & (~Mask_GUI)); mRootNode->addChild(rttCamera); @@ -856,16 +860,10 @@ namespace MWRender // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() - if (disableWaterEffects) - mWater->setEffectsEnabled(false); - mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); - if (disableWaterEffects) - mWater->setEffectsEnabled(true); - callback->waitTillDone(); // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index a4cb9bf8f..9e0394593 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -125,7 +125,7 @@ namespace MWRender void setWaterHeight(float level); /// Take a screenshot of w*h onto the given image, not including the GUI. - void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1), bool disableWaterEffects=false); + void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1)); bool screenshot360(osg::Image* image); struct RayResult diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 70e7b36ad..8b047eb84 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -476,6 +476,16 @@ void Water::updateWaterMaterial() updateVisible(); } +osg::Camera *Water::getReflectionCamera() +{ + return mReflection; +} + +osg::Camera *Water::getRefractionCamera() +{ + return mRefraction; +} + void Water::createSimpleWaterStateSet(osg::Node* node, float alpha) { osg::ref_ptr stateset = SceneUtil::createSimpleWaterStateSet(alpha, MWRender::RenderBin_Water); diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index ed6e40f1a..6985ddca6 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -113,6 +114,9 @@ namespace MWRender void update(float dt); + osg::Camera *getReflectionCamera(); + osg::Camera *getRefractionCamera(); + void processChangedSettings(const Settings::CategorySettingVector& settings); osg::Uniform *getRainIntensityUniform(); From 525f8b4d8e4b0544ff4202519723fab65ad25565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 11 Nov 2017 13:51:42 +0100 Subject: [PATCH 22/91] get rid of special key for 360 screenshot --- apps/openmw/mwinput/inputmanagerimp.cpp | 43 ++++++++------ apps/openmw/mwinput/inputmanagerimp.hpp | 1 - apps/openmw/mwrender/renderingmanager.cpp | 71 ++++++++++++++++++----- 3 files changed, 82 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index bbd6feb66..10288bbc0 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -249,9 +249,6 @@ namespace MWInput case A_Screenshot: screenshot(); break; - case A_Screenshot360: - screenshot360(); - break; case A_Inventory: toggleInventory (); break; @@ -1020,17 +1017,31 @@ namespace MWInput void InputManager::screenshot() { - mScreenCaptureHandler->setFramesToCapture(1); - mScreenCaptureHandler->captureNextFrame(*mViewer); - MWBase::Environment::get().getWindowManager()->messageBox("Screenshot saved"); - } + bool regularScreenshot = true; - void InputManager::screenshot360() - { - osg::ref_ptr screenshot (new osg::Image); - if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) - (*mScreenCaptureOperation) (*(screenshot.get()),0); - // calling mScreenCaptureHandler->getCaptureOperation() here caused segfault for some reason + try + { + // FIXME: the same string "screenshot type" is queried here AND in renderingmanager.cpp + std::string s = Settings::Manager::getString("screenshot type","Video"); + regularScreenshot = s.size() == 0; + } + catch (std::runtime_error) + { + } + + if (regularScreenshot) + { + mScreenCaptureHandler->setFramesToCapture(1); + mScreenCaptureHandler->captureNextFrame(*mViewer); + MWBase::Environment::get().getWindowManager()->messageBox("Screenshot saved"); + } + else + { + osg::ref_ptr screenshot (new osg::Image); + + if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) + (*mScreenCaptureOperation) (*(screenshot.get()),0); + } } void InputManager::toggleInventory() @@ -1216,7 +1227,6 @@ namespace MWInput defaultKeyBindings[A_QuickKey9] = SDL_SCANCODE_9; defaultKeyBindings[A_QuickKey10] = SDL_SCANCODE_0; defaultKeyBindings[A_Screenshot] = SDL_SCANCODE_F12; - defaultKeyBindings[A_Screenshot360] = SDL_SCANCODE_F8; defaultKeyBindings[A_ToggleHUD] = SDL_SCANCODE_F11; defaultKeyBindings[A_ToggleDebug] = SDL_SCANCODE_F10; defaultKeyBindings[A_AlwaysRun] = SDL_SCANCODE_CAPSLOCK; @@ -1352,9 +1362,6 @@ namespace MWInput if (action == A_Screenshot) return "Screenshot"; - if (action == A_Screenshot360) - return "Screenshot 360"; - descriptions[A_Use] = "sUse"; descriptions[A_Activate] = "sActivate"; descriptions[A_MoveBackward] = "sBack"; @@ -1516,7 +1523,6 @@ namespace MWInput ret.push_back(A_QuickSave); ret.push_back(A_QuickLoad); ret.push_back(A_Screenshot); - ret.push_back(A_Screenshot360); ret.push_back(A_QuickKeysMenu); ret.push_back(A_QuickKey1); ret.push_back(A_QuickKey2); @@ -1548,7 +1554,6 @@ namespace MWInput ret.push_back(A_QuickSave); ret.push_back(A_QuickLoad); ret.push_back(A_Screenshot); - ret.push_back(A_Screenshot360); ret.push_back(A_QuickKeysMenu); ret.push_back(A_QuickKey1); ret.push_back(A_QuickKey2); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 5d3e88eab..82b62e79b 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -229,7 +229,6 @@ namespace MWInput void toggleInventory(); void toggleConsole(); void screenshot(); - void screenshot360(); void toggleJournal(); void activate(); void toggleWalking(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ff54ab1dc..682f35cc6 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -41,6 +41,8 @@ #include #include +#include + #include "../mwworld/cellstore.hpp" #include "sky.hpp" @@ -773,19 +775,62 @@ namespace MWRender bool RenderingManager::screenshot360(osg::Image* image) { - if (mCamera->isVanityOrPreviewModeEnabled()) + int screenshotW = mViewer->getCamera()->getViewport()->width(); + int screenshotH = mViewer->getCamera()->getViewport()->height(); + SphericalScreenshot::SphericalScreenshotMapping screenshotMapping = SphericalScreenshot::MAPPING_SPHERICAL; + int cubeSize = screenshotW / 2; + + try + { + std::string settingStr = Settings::Manager::getString("screenshot type","Video"); + std::vector settingArgs; + boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" ")); + + if (settingArgs.size() > 0) + { + std::string typeStrings[4] = {"spherical","cylindrical","planet","cubemap"}; + bool found = false; + + for (int i = 0; i < 4; ++i) + if (settingArgs[0].compare(typeStrings[i]) == 0) + { + screenshotMapping = (SphericalScreenshot::SphericalScreenshotMapping) i; + found = true; + break; + } + + if (!found) + { + std::cerr << "Wrong screenshot type: " << settingArgs[0] << "." << std::endl; + return false; + } + } + + if (settingArgs.size() > 1) + screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str())); + + if (settingArgs.size() > 2) + screenshotH = std::min(10000,std::atoi(settingArgs[2].c_str())); + + if (settingArgs.size() > 3) + cubeSize = std::min(5000,std::atoi(settingArgs[3].c_str())); + } + catch (std::runtime_error) + { + std::cerr << "Wrong parameters for screenshot type." << std::endl; return false; + } - int screenshotWidth = Settings::Manager::tryGetInt("s360 width","Video",mViewer->getCamera()->getViewport()->width()); - int screenshotHeight = Settings::Manager::tryGetInt("s360 height","Video",mViewer->getCamera()->getViewport()->height()); - SphericalScreenshot::SphericalScreenshotMapping mapping = static_cast( - Settings::Manager::tryGetInt("s360 mapping","Video",SphericalScreenshot::MAPPING_SPHERICAL)); - int cubeWidth = Settings::Manager::tryGetInt("s360 cubemap size","Video",screenshotWidth / 2); + if (mCamera->isVanityOrPreviewModeEnabled()) + { + std::cerr << "Spherical screenshots are not allowed in preview mode." << std::endl; + return false; + } - if (mapping == SphericalScreenshot::MAPPING_CUBEMAP) - screenshotWidth = cubeWidth * 6; // the image will consist of 6 cube sides in a row + if (screenshotMapping == SphericalScreenshot::MAPPING_CUBEMAP) + screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row - SphericalScreenshot s(cubeWidth); + SphericalScreenshot s(cubeSize); osg::Vec3 directions[6] = { osg::Vec3(0,0,-1), @@ -797,20 +842,20 @@ namespace MWRender }; double fovBackup = mFieldOfView; - mFieldOfView = 90.0; // each side sees 90 degrees + mFieldOfView = 90.0; // each cubemap side sees 90 degrees int maskBackup = mPlayerAnimation->getObjectRoot()->getNodeMask(); if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(0); - for (int i = 0; i < 6; i++) // for each cube side + for (int i = 0; i < 6; i++) // for each cubemap side { osg::Image *sideImage = s.getImage(i); - screenshot(sideImage,cubeWidth,cubeWidth,directions[i]); + screenshot(sideImage,cubeSize,cubeSize,directions[i]); } - s.create(image,screenshotWidth,mapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotHeight : screenshotWidth,mapping); + s.create(image,screenshotW,screenshotMapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotH : screenshotW,screenshotMapping); mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); mFieldOfView = fovBackup; From e804c4a011f1e6a1a1d14a39ddb4e48f981e3c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 11 Nov 2017 13:54:14 +0100 Subject: [PATCH 23/91] remove no longer used method --- components/settings/settings.cpp | 12 ------------ components/settings/settings.hpp | 1 - 2 files changed, 13 deletions(-) diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 4e250974a..e93642ee2 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -401,18 +401,6 @@ int Manager::getInt (const std::string& setting, const std::string& category) return parseInt( getString(setting, category) ); } -int Manager::tryGetInt (const std::string &setting, const std::string& category, int defaultValue) -{ - try - { - return getInt(setting,category); - } - catch (std::runtime_error) - { - return defaultValue; - } -} - bool Manager::getBool (const std::string& setting, const std::string& category) { return parseBool( getString(setting, category) ); diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 9e80c21a6..7adcb9b39 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -39,7 +39,6 @@ namespace Settings ///< returns the list of changed settings and then clears it static int getInt (const std::string& setting, const std::string& category); - static int tryGetInt (const std::string &setting, const std::string& category, int defaultValue); static float getFloat (const std::string& setting, const std::string& category); static std::string getString (const std::string& setting, const std::string& category); static bool getBool (const std::string& setting, const std::string& category); From d71d984cfae6b476eed0a404c0d8a97c81083af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 11 Nov 2017 14:14:24 +0100 Subject: [PATCH 24/91] more unused stuff cleanup --- apps/openmw/mwinput/inputmanagerimp.hpp | 2 -- apps/openmw/mwrender/water.cpp | 28 ++++--------------------- apps/openmw/mwrender/water.hpp | 1 - apps/openmw/mwstate/statemanagerimp.cpp | 1 + 4 files changed, 5 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 82b62e79b..bc62ef7dc 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -259,8 +259,6 @@ namespace MWInput A_Screenshot, // Take a screenshot - A_Screenshot360, // Take a 360 degree screenshot - A_Inventory, // Toggle inventory screen A_Console, // Toggle console screen diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 8b047eb84..ee5b0d599 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -207,6 +207,7 @@ osg::ref_ptr readPngImage (const std::string& file) return result.getImage(); } + class Refraction : public osg::Camera { public: @@ -220,7 +221,7 @@ public: setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("RefractionCamera"); - setupCullMask(true); + setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); setNodeMask(Mask_RenderToTexture); setViewport(0, 0, rttSize, rttSize); @@ -261,12 +262,6 @@ public: attach(osg::Camera::DEPTH_BUFFER, mRefractionDepthTexture); } - void setupCullMask(bool enabled) - { - setCullMask(!enabled ? 0 : - Mask_Effect|Mask_Scene|Mask_Terrain|Mask_Actor|Mask_ParticleSystem|Mask_Sky|Mask_Sun|Mask_Player|Mask_Lighting); - } - void setScene(osg::Node* scene) { if (mScene) @@ -309,9 +304,9 @@ public: setSmallFeatureCullingPixelSize(Settings::Manager::getInt("small feature culling pixel size", "Water")); setName("ReflectionCamera"); - mReflectActors = Settings::Manager::getBool("reflect actors", "Water"); + bool reflectActors = Settings::Manager::getBool("reflect actors", "Water"); - setupCullMask(true); + setCullMask(Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(reflectActors ? Mask_Actor : 0)); setNodeMask(Mask_RenderToTexture); unsigned int rttSize = Settings::Manager::getInt("rtt size", "Water"); @@ -339,12 +334,6 @@ public: addChild(mClipCullNode); } - void setupCullMask(bool enabled) - { - setCullMask(!enabled ? 0 : - Mask_Effect|Mask_Scene|Mask_Terrain|Mask_ParticleSystem|Mask_Sky|Mask_Player|Mask_Lighting|(mReflectActors ? Mask_Actor : 0)); - } - void setWaterLevel(float waterLevel) { setViewMatrix(osg::Matrix::scale(1,1,-1) * osg::Matrix::translate(0,0,2 * waterLevel)); @@ -719,13 +708,4 @@ void Water::clearRipples() mSimulation->clear(); } -void Water::setEffectsEnabled(bool enabled) -{ - if (mReflection) - mReflection->setupCullMask(enabled); - - if (mRefraction) - mRefraction->setupCullMask(enabled); -} - } diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 6985ddca6..e2413cfa0 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -107,7 +107,6 @@ namespace MWRender void removeCell(const MWWorld::CellStore* store); ///< remove all emitters in this cell void clearRipples(); - void setEffectsEnabled(bool enabled); void changeCell(const MWWorld::CellStore* store); void setHeight(const float height); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index c643480a9..14ee5adee 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -655,4 +655,5 @@ void MWState::StateManager::writeScreenshot(std::vector &imageData) const std::string data = ostream.str(); imageData = std::vector(data.begin(), data.end()); + } From 4fc532d873f163c9d582c9d5354aa59baea7f2f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 11 Nov 2017 14:32:28 +0100 Subject: [PATCH 25/91] reference screenshot settings only from one place --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwinput/inputmanagerimp.cpp | 9 ++-- apps/openmw/mwrender/renderingmanager.cpp | 59 ++++++++++------------- apps/openmw/mwrender/renderingmanager.hpp | 2 +- apps/openmw/mwrender/water.cpp | 1 - apps/openmw/mwworld/worldimp.cpp | 4 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 7 files changed, 35 insertions(+), 44 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f91d1bc2a..f72f74c53 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -450,7 +450,7 @@ namespace MWBase /// \todo this does not belong here virtual void screenshot (osg::Image* image, int w, int h) = 0; - virtual bool screenshot360 (osg::Image* image) = 0; + virtual bool screenshot360 (osg::Image* image, std::string settingStr) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 10288bbc0..0215e43df 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1019,11 +1019,12 @@ namespace MWInput { bool regularScreenshot = true; + std::string settingStr; + try { - // FIXME: the same string "screenshot type" is queried here AND in renderingmanager.cpp - std::string s = Settings::Manager::getString("screenshot type","Video"); - regularScreenshot = s.size() == 0; + settingStr = Settings::Manager::getString("screenshot type","Video"); + regularScreenshot = settingStr.size() == 0; } catch (std::runtime_error) { @@ -1039,7 +1040,7 @@ namespace MWInput { osg::ref_ptr screenshot (new osg::Image); - if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get())) + if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(),settingStr)) (*mScreenCaptureOperation) (*(screenshot.get()),0); } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 682f35cc6..5fbc5d397 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -773,53 +773,44 @@ namespace MWRender int mSize; }; - bool RenderingManager::screenshot360(osg::Image* image) + bool RenderingManager::screenshot360(osg::Image* image, std::string settingStr) { int screenshotW = mViewer->getCamera()->getViewport()->width(); int screenshotH = mViewer->getCamera()->getViewport()->height(); SphericalScreenshot::SphericalScreenshotMapping screenshotMapping = SphericalScreenshot::MAPPING_SPHERICAL; int cubeSize = screenshotW / 2; - try - { - std::string settingStr = Settings::Manager::getString("screenshot type","Video"); - std::vector settingArgs; - boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" ")); - - if (settingArgs.size() > 0) - { - std::string typeStrings[4] = {"spherical","cylindrical","planet","cubemap"}; - bool found = false; + std::vector settingArgs; + boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" ")); - for (int i = 0; i < 4; ++i) - if (settingArgs[0].compare(typeStrings[i]) == 0) - { - screenshotMapping = (SphericalScreenshot::SphericalScreenshotMapping) i; - found = true; - break; - } + if (settingArgs.size() > 0) + { + std::string typeStrings[4] = {"spherical","cylindrical","planet","cubemap"}; + bool found = false; - if (!found) + for (int i = 0; i < 4; ++i) + if (settingArgs[0].compare(typeStrings[i]) == 0) { - std::cerr << "Wrong screenshot type: " << settingArgs[0] << "." << std::endl; - return false; + screenshotMapping = (SphericalScreenshot::SphericalScreenshotMapping) i; + found = true; + break; } + + if (!found) + { + std::cerr << "Wrong screenshot type: " << settingArgs[0] << "." << std::endl; + return false; } - - if (settingArgs.size() > 1) - screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str())); + } + + if (settingArgs.size() > 1) + screenshotW = std::min(10000,std::atoi(settingArgs[1].c_str())); - if (settingArgs.size() > 2) - screenshotH = std::min(10000,std::atoi(settingArgs[2].c_str())); + if (settingArgs.size() > 2) + screenshotH = std::min(10000,std::atoi(settingArgs[2].c_str())); - if (settingArgs.size() > 3) - cubeSize = std::min(5000,std::atoi(settingArgs[3].c_str())); - } - catch (std::runtime_error) - { - std::cerr << "Wrong parameters for screenshot type." << std::endl; - return false; - } + if (settingArgs.size() > 3) + cubeSize = std::min(5000,std::atoi(settingArgs[3].c_str())); if (mCamera->isVanityOrPreviewModeEnabled()) { diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 9e0394593..01630c408 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -126,7 +126,7 @@ namespace MWRender /// Take a screenshot of w*h onto the given image, not including the GUI. void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1)); - bool screenshot360(osg::Image* image); + bool screenshot360(osg::Image* image, std::string settingStr); struct RayResult { diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index ee5b0d599..bfd671971 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -357,7 +357,6 @@ private: osg::ref_ptr mReflectionTexture; osg::ref_ptr mClipCullNode; osg::ref_ptr mScene; - bool mReflectActors; }; /// DepthClampCallback enables GL_DEPTH_CLAMP for the current draw, if supported. diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0d0079677..1f8b01eac 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2281,9 +2281,9 @@ namespace MWWorld mRendering->screenshot(image, w, h); } - bool World::screenshot360(osg::Image* image) + bool World::screenshot360(osg::Image* image, std::string settingStr) { - return mRendering->screenshot360(image); + return mRendering->screenshot360(image,settingStr); } void World::activateDoor(const MWWorld::Ptr& door) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 3a54d28ae..e432e77d3 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -560,7 +560,7 @@ namespace MWWorld /// \todo this does not belong here void screenshot (osg::Image* image, int w, int h) override; - bool screenshot360 (osg::Image* image) override; + bool screenshot360 (osg::Image* image, std::string settingStr) override; /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise From 511a5686dad417e8c5827a99dc29f85aa4487cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 11 Nov 2017 15:10:54 +0100 Subject: [PATCH 26/91] planet mapping adjustment --- apps/openmw/mwinput/inputmanagerimp.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 0215e43df..cfae8e872 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1042,6 +1042,7 @@ namespace MWInput if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(),settingStr)) (*mScreenCaptureOperation) (*(screenshot.get()),0); + // mScreenCaptureHandler->getCaptureOperation() causes crash for some reason } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5fbc5d397..b2d97e81d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -654,7 +654,7 @@ namespace MWRender { dest->allocateImage(mSize * 6,mSize,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); - for (int i = 0; i < 6; i++) + for (int i = 0; i < 6; ++i) osg::copyImage(mImages[i].get(),0,0,0,mImages[i]->s(),mImages[i]->t(),mImages[i]->r(),dest,i * mSize,0,0); return; @@ -778,7 +778,9 @@ namespace MWRender int screenshotW = mViewer->getCamera()->getViewport()->width(); int screenshotH = mViewer->getCamera()->getViewport()->height(); SphericalScreenshot::SphericalScreenshotMapping screenshotMapping = SphericalScreenshot::MAPPING_SPHERICAL; - int cubeSize = screenshotW / 2; + int cubeSize = screenshotMapping == SphericalScreenshot::SphericalScreenshotMapping::MAPPING_SMALL_PLANET ? + screenshotW: // planet mapping needs higher resolution + screenshotW / 2; std::vector settingArgs; boost::algorithm::split(settingArgs,settingStr,boost::is_any_of(" ")); From 1ab854446c4098bf34cee03d829428dcab34eef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Sat, 11 Nov 2017 17:21:01 +0100 Subject: [PATCH 27/91] remove unused include --- apps/openmw/mwinput/inputmanagerimp.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index cfae8e872..b491fb996 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1,7 +1,6 @@ #include "inputmanagerimp.hpp" #include -#include #include #include From 823218bb611adbbe07a1a4727a6f381cc7b896b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Tue, 14 Nov 2017 18:23:12 +0100 Subject: [PATCH 28/91] freeze screen during screenshot taking --- apps/openmw/mwrender/renderingmanager.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b2d97e81d..a1b508f01 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -890,6 +890,13 @@ namespace MWRender mRootNode->addChild(rttCamera); + rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + GLbitfield maskBackup = mViewer->getCamera()->getClearMask(); + double clearDepthBackup = mViewer->getCamera()->getClearDepth(); + mViewer->getCamera()->setClearMask(GL_DEPTH_BUFFER_BIT); + mViewer->getCamera()->setClearDepth(0); + // The draw needs to complete before we can copy back our image. osg::ref_ptr callback (new NotifyDrawCompletedCallback); rttCamera->setFinalDrawCallback(callback); @@ -907,6 +914,9 @@ namespace MWRender // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + mViewer->getCamera()->setClearMask(maskBackup); + mViewer->getCamera()->setClearDepth(clearDepthBackup); + rttCamera->removeChildren(0, rttCamera->getNumChildren()); mRootNode->removeChild(rttCamera); } From 1c3d45f641cc6e5b01b29b33ec6caf158cccde4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 15 Nov 2017 15:20:59 +0100 Subject: [PATCH 29/91] dirty 360 screenshot GPU setup --- apps/openmw/mwrender/renderingmanager.cpp | 101 ++++++++++++++++++---- apps/openmw/mwrender/renderingmanager.hpp | 3 + files/shaders/s360_fragment.glsl | 9 ++ files/shaders/s360_vertex.glsl | 9 ++ 4 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 files/shaders/s360_fragment.glsl create mode 100644 files/shaders/s360_vertex.glsl diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index a1b508f01..878056955 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include @@ -24,6 +26,7 @@ #include #include #include +#include #include @@ -853,12 +856,93 @@ namespace MWRender mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); mFieldOfView = fovBackup; + +osg::ref_ptr cubeTexture (new osg::TextureCubeMap); + +for (int i = 0; i < 6; ++i) + cubeTexture->setImage(i,s.getImage(i)); + +osg::ref_ptr screenshotCamera (new osg::Camera); +osg::ref_ptr quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0))); + +std::map defineMap; + +Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); +osg::ref_ptr fragmentShader (shaderMgr.getShader("s360_fragment.glsl",defineMap,osg::Shader::FRAGMENT)); +osg::ref_ptr vertexShader (shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); +osg::ref_ptr stateset = new osg::StateSet; + +osg::ref_ptr program (new osg::Program); +program->addShader(fragmentShader); +program->addShader(vertexShader); +stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + +stateset->addUniform(new osg::Uniform("cubeMap",0)); +stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); + +quad->setStateSet(stateset); +quad->setUpdateCallback(NULL); + +screenshotCamera->addChild(quad); + +mRootNode->addChild(screenshotCamera); + +renderCameraToImage(screenshotCamera,image,1000,640); + + +screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren()); +mRootNode->removeChild(screenshotCamera); + + return true; } + void RenderingManager::renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h) + { + camera->setNodeMask(Mask_RenderToTexture); + camera->attach(osg::Camera::COLOR_BUFFER, image); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,osg::Camera::PIXEL_BUFFER_RTT); + + camera->setViewport(0, 0, w, h); + + osg::ref_ptr texture (new osg::Texture2D); + texture->setInternalFormat(GL_RGB); + texture->setTextureSize(w,h); + texture->setResizeNonPowerOfTwoHint(false); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + camera->attach(osg::Camera::COLOR_BUFFER,texture); + + image->setDataType(GL_UNSIGNED_BYTE); + image->setPixelFormat(texture->getInternalFormat()); + + // The draw needs to complete before we can copy back our image. + osg::ref_ptr callback (new NotifyDrawCompletedCallback); + camera->setFinalDrawCallback(callback); + + // at the time this function is called we are in the middle of a frame, + // so out of order calls are necessary to get a correct frameNumber for the next frame. + // refer to the advance() and frame() order in Engine::go() + + mViewer->eventTraversal(); + mViewer->updateTraversal(); + mViewer->renderingTraversals(); + + callback->waitTillDone(); + + // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed + mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + } + void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction) { osg::ref_ptr rttCamera (new osg::Camera); + + rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); + rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction)); + rttCamera->setNodeMask(Mask_RenderToTexture); rttCamera->attach(osg::Camera::COLOR_BUFFER, image); rttCamera->setRenderOrder(osg::Camera::PRE_RENDER); @@ -897,22 +981,7 @@ namespace MWRender mViewer->getCamera()->setClearMask(GL_DEPTH_BUFFER_BIT); mViewer->getCamera()->setClearDepth(0); - // The draw needs to complete before we can copy back our image. - osg::ref_ptr callback (new NotifyDrawCompletedCallback); - rttCamera->setFinalDrawCallback(callback); - - // at the time this function is called we are in the middle of a frame, - // so out of order calls are necessary to get a correct frameNumber for the next frame. - // refer to the advance() and frame() order in Engine::go() - - mViewer->eventTraversal(); - mViewer->updateTraversal(); - mViewer->renderingTraversals(); - - callback->waitTillDone(); - - // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed - mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); + renderCameraToImage(rttCamera.get(),image,w,h); mViewer->getCamera()->setClearMask(maskBackup); mViewer->getCamera()->setClearDepth(clearDepthBackup); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 01630c408..e6dfd3999 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -214,6 +215,8 @@ namespace MWRender void reportStats() const; + void renderCameraToImage(osg::Camera *camera, osg::Image *image, int w, int h); + osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); osg::ref_ptr mIntersectionVisitor; diff --git a/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl new file mode 100644 index 000000000..4bf91abb3 --- /dev/null +++ b/files/shaders/s360_fragment.glsl @@ -0,0 +1,9 @@ +#version 120 + +varying vec2 uv; +uniform samplerCube cubeMap; + +void main(void) +{ + gl_FragData[0] = textureCube(cubeMap,vec3(uv.x * 2.0 - 1.0,uv.y * 2.0 - 1.0,1)); +} diff --git a/files/shaders/s360_vertex.glsl b/files/shaders/s360_vertex.glsl new file mode 100644 index 000000000..84cecfdaf --- /dev/null +++ b/files/shaders/s360_vertex.glsl @@ -0,0 +1,9 @@ +#version 120 + +varying vec2 uv; + +void main(void) +{ + gl_Position = gl_Vertex; + uv = (gl_Vertex.xy + vec2(1.0)) / 2.0; +} From 226fb9c26bdbbcb7a406f3ca874319d0eb4a7119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 15 Nov 2017 16:07:01 +0100 Subject: [PATCH 30/91] render cubemaps in OGL coordinates --- apps/openmw/mwrender/renderingmanager.cpp | 85 ++++++++++++----------- apps/openmw/mwrender/renderingmanager.hpp | 2 +- files/shaders/s360_fragment.glsl | 14 +++- files/shaders/s360_vertex.glsl | 2 +- 4 files changed, 61 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 878056955..bb7fbd5de 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -829,13 +829,20 @@ namespace MWRender SphericalScreenshot s(cubeSize); osg::Vec3 directions[6] = { - osg::Vec3(0,0,-1), - osg::Vec3(-1,0,0), - osg::Vec3(0,0,1), - osg::Vec3(1,0,0), - osg::Vec3(0,1,0), - osg::Vec3(0,-1,0), - }; + osg::Vec3(0,0,1), + osg::Vec3(0,0,-1), + osg::Vec3(-1,0,0), + osg::Vec3(1,0,0), + osg::Vec3(0,1,0), + osg::Vec3(0,-1,0)}; + + double rotations[] = { + -osg::PI / 2.0, + osg::PI / 2.0, + osg::PI, + 0, + osg::PI / 2.0, + osg::PI / 2.0}; double fovBackup = mFieldOfView; mFieldOfView = 90.0; // each cubemap side sees 90 degrees @@ -847,51 +854,55 @@ namespace MWRender for (int i = 0; i < 6; i++) // for each cubemap side { + osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]) * osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); osg::Image *sideImage = s.getImage(i); - screenshot(sideImage,cubeSize,cubeSize,directions[i]); + screenshot(sideImage,cubeSize,cubeSize,transform); + sideImage->flipHorizontal(); } - s.create(image,screenshotW,screenshotMapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotH : screenshotW,screenshotMapping); +// s.create(image,screenshotW,screenshotMapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotH : screenshotW,screenshotMapping); mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); mFieldOfView = fovBackup; -osg::ref_ptr cubeTexture (new osg::TextureCubeMap); -for (int i = 0; i < 6; ++i) - cubeTexture->setImage(i,s.getImage(i)); -osg::ref_ptr screenshotCamera (new osg::Camera); -osg::ref_ptr quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0))); + osg::ref_ptr cubeTexture (new osg::TextureCubeMap); -std::map defineMap; + for (int i = 0; i < 6; ++i) + cubeTexture->setImage(i,s.getImage(i)); -Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); -osg::ref_ptr fragmentShader (shaderMgr.getShader("s360_fragment.glsl",defineMap,osg::Shader::FRAGMENT)); -osg::ref_ptr vertexShader (shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); -osg::ref_ptr stateset = new osg::StateSet; + osg::ref_ptr screenshotCamera (new osg::Camera); + osg::ref_ptr quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0))); -osg::ref_ptr program (new osg::Program); -program->addShader(fragmentShader); -program->addShader(vertexShader); -stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + std::map defineMap; -stateset->addUniform(new osg::Uniform("cubeMap",0)); -stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); - -quad->setStateSet(stateset); -quad->setUpdateCallback(NULL); + Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); + osg::ref_ptr fragmentShader (shaderMgr.getShader("s360_fragment.glsl",defineMap,osg::Shader::FRAGMENT)); + osg::ref_ptr vertexShader (shaderMgr.getShader("s360_vertex.glsl", defineMap, osg::Shader::VERTEX)); + osg::ref_ptr stateset = new osg::StateSet; + + osg::ref_ptr program (new osg::Program); + program->addShader(fragmentShader); + program->addShader(vertexShader); + stateset->setAttributeAndModes(program, osg::StateAttribute::ON); + + stateset->addUniform(new osg::Uniform("cubeMap",0)); + stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); + + quad->setStateSet(stateset); + quad->setUpdateCallback(NULL); -screenshotCamera->addChild(quad); + screenshotCamera->addChild(quad); -mRootNode->addChild(screenshotCamera); + mRootNode->addChild(screenshotCamera); -renderCameraToImage(screenshotCamera,image,1000,640); + renderCameraToImage(screenshotCamera,image,1000,640); -screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren()); -mRootNode->removeChild(screenshotCamera); + screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren()); + mRootNode->removeChild(screenshotCamera); return true; @@ -936,20 +947,16 @@ mRootNode->removeChild(screenshotCamera); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); } - void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Vec3 direction) + void RenderingManager::screenshot(osg::Image *image, int w, int h, osg::Matrixd cameraTransform) { osg::ref_ptr rttCamera (new osg::Camera); - - rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); - rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction)); - rttCamera->setNodeMask(Mask_RenderToTexture); rttCamera->attach(osg::Camera::COLOR_BUFFER, image); rttCamera->setRenderOrder(osg::Camera::PRE_RENDER); rttCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); rttCamera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); rttCamera->setProjectionMatrixAsPerspective(mFieldOfView, w/float(h), mNearClip, mViewDistance); - rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * osg::Matrixd::rotate(osg::Vec3(0,0,-1),direction)); + rttCamera->setViewMatrix(mViewer->getCamera()->getViewMatrix() * cameraTransform); rttCamera->setViewport(0, 0, w, h); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index e6dfd3999..8fd96cff9 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -126,7 +126,7 @@ namespace MWRender void setWaterHeight(float level); /// Take a screenshot of w*h onto the given image, not including the GUI. - void screenshot(osg::Image* image, int w, int h, osg::Vec3 direction=osg::Vec3(0,0,-1)); + void screenshot(osg::Image* image, int w, int h, osg::Matrixd cameraTransform=osg::Matrixd()); bool screenshot360(osg::Image* image, std::string settingStr); struct RayResult diff --git a/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl index 4bf91abb3..3ff8e93c1 100644 --- a/files/shaders/s360_fragment.glsl +++ b/files/shaders/s360_fragment.glsl @@ -3,7 +3,19 @@ varying vec2 uv; uniform samplerCube cubeMap; +#define PI 3.1415926535 + +vec3 cylindricalCoords(vec2 coords) +{ + return normalize(vec3(cos(-1 * coords.x * 2 * PI),sin(-1 * coords.x * 2 * PI),coords.y * 2.0 - 1.0)); +} + void main(void) { - gl_FragData[0] = textureCube(cubeMap,vec3(uv.x * 2.0 - 1.0,uv.y * 2.0 - 1.0,1)); + vec3 c; + c.x = uv.x * 2.0 - 1.0; + c.y = uv.y * 2.0 - 1.0; + c.z = 1.0; + + gl_FragData[0] = textureCube(cubeMap,vec3(cylindricalCoords(uv))); } diff --git a/files/shaders/s360_vertex.glsl b/files/shaders/s360_vertex.glsl index 84cecfdaf..b7fbf28a4 100644 --- a/files/shaders/s360_vertex.glsl +++ b/files/shaders/s360_vertex.glsl @@ -5,5 +5,5 @@ varying vec2 uv; void main(void) { gl_Position = gl_Vertex; - uv = (gl_Vertex.xy + vec2(1.0)) / 2.0; + uv = (gl_Vertex.xy * vec2(1.0,-1.0) + vec2(1.0)) / 2.0; } From af38d3a47d46a8d78f6f41564714026c7cdca6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 15 Nov 2017 17:01:16 +0100 Subject: [PATCH 31/91] get rid of sphericalscreenshot class --- apps/openmw/mwrender/renderingmanager.cpp | 203 ++++------------------ files/shaders/s360_fragment.glsl | 40 ++++- files/shaders/s360_vertex.glsl | 2 +- 3 files changed, 75 insertions(+), 170 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index bb7fbd5de..3b82b549d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -627,161 +627,12 @@ namespace MWRender mutable bool mDone; }; - class SphericalScreenshot - { - public: - typedef enum - { - MAPPING_SPHERICAL = 0, - MAPPING_CYLINDRICAL, - MAPPING_SMALL_PLANET, - MAPPING_CUBEMAP - } SphericalScreenshotMapping; - - SphericalScreenshot(int size) - { - mSize = size; - - for (int i = 0; i < 6; ++i) - mImages.push_back(new osg::Image); - } - - osg::Image *getImage(int index) - { - return mImages[index].get(); - } - - void create(osg::Image *dest, int w, int h, SphericalScreenshotMapping mapping) - { - if (mapping == MAPPING_CUBEMAP) - { - dest->allocateImage(mSize * 6,mSize,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); - - for (int i = 0; i < 6; ++i) - osg::copyImage(mImages[i].get(),0,0,0,mImages[i]->s(),mImages[i]->t(),mImages[i]->r(),dest,i * mSize,0,0); - - return; - } - - dest->allocateImage(w,h,mImages[0]->r(),mImages[0]->getPixelFormat(),mImages[0]->getDataType()); - - for (int j = 0; j < h; ++j) - for (int i = 0; i < w; ++i) - { - osg::Vec3d coords; - osg::Vec2d normalizedXY = osg::Vec2d(i / ((float) w), j / ((float) h)); - - switch (mapping) - { - case MAPPING_CYLINDRICAL: coords = cylindricalCoords(normalizedXY.x(),normalizedXY.y()); break; - case MAPPING_SPHERICAL: coords = sphericalCoords(normalizedXY.x(),normalizedXY.y()); break; - case MAPPING_SMALL_PLANET: coords = smallPlanetCoords(normalizedXY.x(),normalizedXY.y()); break; - default: break; - } - - dest->setColor(getColorByDirection(coords),i,j); - } - } - - osg::Vec3d cylindricalCoords(double x, double y) - { - osg::Vec3 result = osg::Vec3d(cos(-1 * x * 2 * osg::PI),sin(-1 * x * 2 * osg::PI),y * 2.0 - 1.0); - result.normalize(); - return result; - } - - osg::Vec3d sphericalCoords(double x, double y) - { - x = -1 * x * 2 * osg::PI; - y = (y - 0.5) * osg::PI; - - osg::Vec3 result = osg::Vec3(0.0,cos(y),sin(y)); - result = osg::Vec3(cos(x) * result.y(),sin(x) * result.y(),result.z()); - - return result; - } - - osg::Vec3d smallPlanetCoords(double x, double y) - { - osg::Vec2d fromCenter = osg::Vec2d(x,y) - osg::Vec2d(0.5,0.5); - - double magnitude = fromCenter.length(); - - fromCenter.normalize(); - double dot = fromCenter * osg::Vec2d(0.0,1.0); - - x = x > 0.5 ? 0.5 - (dot + 1) / 4.0 : 0.5 + (dot + 1) / 4.0; - y = pow(std::min(1.0,magnitude / 0.5),0.5); - - return sphericalCoords(x,y); - } - - osg::Vec4 getColorByDirection(osg::Vec3d d) - { - // for details see OpenGL 4.4 specification page 225 - - double x, y; - double ma; - int side; - - double ax, ay, az; - ax = fabs(d.x()); - ay = fabs(d.y()); - az = fabs(d.z()); - - if (ax > ay) - if (ax > az) - { - side = d.x() > 0 ? 1 : 3; - ma = ax; - } - else - { - side = d.z() > 0 ? 5 : 4; - ma = az; - } - else - if (ay > az) - { - side = d.y() > 0 ? 0 : 2; - ma = ay; - } - else - { - side = d.z() > 0 ? 5 : 4; - ma = az; - } - - switch (side) - { - case 0: x = d.x(); y = d.z(); break; - case 1: x = -d.y(); y = d.z(); break; - case 2: x = -d.x(); y = d.z(); break; - case 3: x = d.y(); y = d.z(); break; - case 4: x = d.x(); y = d.y(); break; - case 5: x = d.x(); y = -d.y(); break; - default: break; - } - - x = 0.5 * (x / ma + 1); - y = 0.5 * (y / ma + 1); - - return mImages[side]->getColor( - std::min(std::max(int(x * mSize),0),mSize - 1), - std::min(std::max(int(y * mSize),0),mSize - 1)); - } - - protected: - std::vector> mImages; - int mSize; - }; - bool RenderingManager::screenshot360(osg::Image* image, std::string settingStr) { int screenshotW = mViewer->getCamera()->getViewport()->width(); int screenshotH = mViewer->getCamera()->getViewport()->height(); - SphericalScreenshot::SphericalScreenshotMapping screenshotMapping = SphericalScreenshot::MAPPING_SPHERICAL; - int cubeSize = screenshotMapping == SphericalScreenshot::SphericalScreenshotMapping::MAPPING_SMALL_PLANET ? + int screenshotMapping = 0; + int cubeSize = screenshotMapping == 2 ? screenshotW: // planet mapping needs higher resolution screenshotW / 2; @@ -796,7 +647,7 @@ namespace MWRender for (int i = 0; i < 4; ++i) if (settingArgs[0].compare(typeStrings[i]) == 0) { - screenshotMapping = (SphericalScreenshot::SphericalScreenshotMapping) i; + screenshotMapping = i; found = true; break; } @@ -823,16 +674,23 @@ namespace MWRender return false; } - if (screenshotMapping == SphericalScreenshot::MAPPING_CUBEMAP) + bool rawCubemap = screenshotMapping == 3; + + if (rawCubemap) screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row + else if (screenshotMapping == 2) + screenshotH = screenshotW; // use square resolution for planet mapping - SphericalScreenshot s(cubeSize); + std::vector> images; + + for (int i = 0; i < 6; ++i) + images.push_back(new osg::Image); osg::Vec3 directions[6] = { - osg::Vec3(0,0,1), + rawCubemap ? osg::Vec3(1,0,0) : osg::Vec3(0,0,1), osg::Vec3(0,0,-1), osg::Vec3(-1,0,0), - osg::Vec3(1,0,0), + rawCubemap ? osg::Vec3(0,0,1) : osg::Vec3(1,0,0), osg::Vec3(0,1,0), osg::Vec3(0,-1,0)}; @@ -854,24 +712,40 @@ namespace MWRender for (int i = 0; i < 6; i++) // for each cubemap side { - osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]) * osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); - osg::Image *sideImage = s.getImage(i); + osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]); + + if (!rawCubemap) + transform *= osg::Matrixd::rotate(rotations[i],osg::Vec3(0,0,-1)); + + osg::Image *sideImage = images[i].get(); screenshot(sideImage,cubeSize,cubeSize,transform); - sideImage->flipHorizontal(); - } -// s.create(image,screenshotW,screenshotMapping != SphericalScreenshot::MAPPING_SMALL_PLANET ? screenshotH : screenshotW,screenshotMapping); + if (!rawCubemap) + sideImage->flipHorizontal(); + } mPlayerAnimation->getObjectRoot()->setNodeMask(maskBackup); mFieldOfView = fovBackup; + if (rawCubemap) // for raw cubemap don't run on GPU, just merge the images + { + image->allocateImage(cubeSize * 6,cubeSize,images[0]->r(),images[0]->getPixelFormat(),images[0]->getDataType()); + for (int i = 0; i < 6; ++i) + osg::copyImage(images[i].get(),0,0,0,images[i]->s(),images[i]->t(),images[i]->r(),image,i * cubeSize,0,0); + return true; + } + + // run on GPU now: osg::ref_ptr cubeTexture (new osg::TextureCubeMap); + + cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); + cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); for (int i = 0; i < 6; ++i) - cubeTexture->setImage(i,s.getImage(i)); + cubeTexture->setImage(i,images[i].get()); osg::ref_ptr screenshotCamera (new osg::Camera); osg::ref_ptr quad (new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0),2.0))); @@ -889,6 +763,7 @@ namespace MWRender stateset->setAttributeAndModes(program, osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("cubeMap",0)); + stateset->addUniform(new osg::Uniform("mapping",screenshotMapping)); stateset->setTextureAttributeAndModes(0,cubeTexture,osg::StateAttribute::ON); quad->setStateSet(stateset); @@ -898,13 +773,11 @@ namespace MWRender mRootNode->addChild(screenshotCamera); - renderCameraToImage(screenshotCamera,image,1000,640); - + renderCameraToImage(screenshotCamera,image,screenshotW,screenshotH); screenshotCamera->removeChildren(0,screenshotCamera->getNumChildren()); mRootNode->removeChild(screenshotCamera); - return true; } diff --git a/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl index 3ff8e93c1..e2a6e2c99 100644 --- a/files/shaders/s360_fragment.glsl +++ b/files/shaders/s360_fragment.glsl @@ -2,20 +2,52 @@ varying vec2 uv; uniform samplerCube cubeMap; +uniform int mapping; #define PI 3.1415926535 +vec3 sphericalCoords(vec2 coords) +{ + coords.x = -1 * coords.x * 2 * PI; + coords.y = (coords.y - 0.5) * PI; + + vec3 result = vec3(0.0,cos(coords.y),sin(coords.y)); + result = vec3(cos(coords.x) * result.y,sin(coords.x) * result.y,result.z); + + return result; +} + vec3 cylindricalCoords(vec2 coords) { return normalize(vec3(cos(-1 * coords.x * 2 * PI),sin(-1 * coords.x * 2 * PI),coords.y * 2.0 - 1.0)); } +vec3 planetCoords(vec2 coords) +{ + vec2 fromCenter = coords - vec2(0.5,0.5); + + float magnitude = length(fromCenter); + + fromCenter = normalize(fromCenter); + + float dotProduct = dot(fromCenter,vec2(0.0,1.0)); + + coords.x = coords.x > 0.5 ? 0.5 - (dotProduct + 1.0) / 4.0 : 0.5 + (dotProduct + 1.0) / 4.0; + coords.y = max(0.0,1.0 - pow(magnitude / 0.5,0.5)); + return sphericalCoords(coords); +} + void main(void) { vec3 c; - c.x = uv.x * 2.0 - 1.0; - c.y = uv.y * 2.0 - 1.0; - c.z = 1.0; - gl_FragData[0] = textureCube(cubeMap,vec3(cylindricalCoords(uv))); + switch (mapping) + { + case 0: c = sphericalCoords(uv); break; + case 1: c = cylindricalCoords(uv); break; + case 2: c = planetCoords(uv); break; + default: c = sphericalCoords(uv); break; + } + + gl_FragData[0] = textureCube(cubeMap,c); } diff --git a/files/shaders/s360_vertex.glsl b/files/shaders/s360_vertex.glsl index b7fbf28a4..ad96620c3 100644 --- a/files/shaders/s360_vertex.glsl +++ b/files/shaders/s360_vertex.glsl @@ -5,5 +5,5 @@ varying vec2 uv; void main(void) { gl_Position = gl_Vertex; - uv = (gl_Vertex.xy * vec2(1.0,-1.0) + vec2(1.0)) / 2.0; + uv = (gl_Vertex.xy * vec2(1.0,-1.0) + vec2(1.0)) / 2; } From 1a4f351e3d9fafbb60c0baa1d0485376b55c65aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 12:17:52 +0100 Subject: [PATCH 32/91] move camera freeze code --- apps/openmw/mwrender/renderingmanager.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3b82b549d..c571d516f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -806,6 +806,11 @@ namespace MWRender osg::ref_ptr callback (new NotifyDrawCompletedCallback); camera->setFinalDrawCallback(callback); + GLbitfield maskBackup = mViewer->getCamera()->getClearMask(); + double clearDepthBackup = mViewer->getCamera()->getClearDepth(); + mViewer->getCamera()->setClearMask(GL_DEPTH_BUFFER_BIT); // don't render the main camera + mViewer->getCamera()->setClearDepth(0); + // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() @@ -816,6 +821,9 @@ namespace MWRender callback->waitTillDone(); + mViewer->getCamera()->setClearMask(maskBackup); + mViewer->getCamera()->setClearDepth(clearDepthBackup); + // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); } @@ -856,16 +864,8 @@ namespace MWRender rttCamera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - GLbitfield maskBackup = mViewer->getCamera()->getClearMask(); - double clearDepthBackup = mViewer->getCamera()->getClearDepth(); - mViewer->getCamera()->setClearMask(GL_DEPTH_BUFFER_BIT); - mViewer->getCamera()->setClearDepth(0); - renderCameraToImage(rttCamera.get(),image,w,h); - mViewer->getCamera()->setClearMask(maskBackup); - mViewer->getCamera()->setClearDepth(clearDepthBackup); - rttCamera->removeChildren(0, rttCamera->getNumChildren()); mRootNode->removeChild(rttCamera); } From 56c74fb96fcf59a43919d8f303ffb222817d4857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 12:46:46 +0100 Subject: [PATCH 33/91] add scene switch node --- apps/openmw/mwrender/renderingmanager.cpp | 11 ++++++++--- apps/openmw/mwrender/renderingmanager.hpp | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c571d516f..bc16a5ea2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -205,7 +205,9 @@ namespace MWRender mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); - mRootNode->addChild(sceneRoot); + mSceneSwitch = new osg::Switch; + mSceneSwitch->addChild(sceneRoot); + mRootNode->addChild(mSceneSwitch); mPathgrid.reset(new Pathgrid(mRootNode)); @@ -814,13 +816,16 @@ namespace MWRender // at the time this function is called we are in the middle of a frame, // so out of order calls are necessary to get a correct frameNumber for the next frame. // refer to the advance() and frame() order in Engine::go() - + + mSceneSwitch->setAllChildrenOff(); // don't render the scene for main camera + mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); - callback->waitTillDone(); + mSceneSwitch->setAllChildrenOn(); + mViewer->getCamera()->setClearMask(maskBackup); mViewer->getCamera()->setClearDepth(clearDepthBackup); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 8fd96cff9..4a271228e 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -224,6 +224,7 @@ namespace MWRender osg::ref_ptr mViewer; osg::ref_ptr mRootNode; osg::ref_ptr mSceneRoot; + osg::ref_ptr mSceneSwitch; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mWorkQueue; From bccff768ffa8f890b4a7d335883730f9957187f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 13:41:09 +0100 Subject: [PATCH 34/91] select closest power of 2 resolution for the cubemap --- apps/openmw/mwrender/renderingmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index bc16a5ea2..18d2dea2a 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -683,6 +683,9 @@ namespace MWRender else if (screenshotMapping == 2) screenshotH = screenshotW; // use square resolution for planet mapping + if (!rawCubemap) + cubeSize = pow(2,round(log2(cubeSize))); // select closest power of 2 for GPU + std::vector> images; for (int i = 0; i < 6; ++i) From 902862aa8bc79b98e373cfd715026a41e21bcdd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 13:50:10 +0100 Subject: [PATCH 35/91] display confirming message for all screenshots --- apps/openmw/mwinput/inputmanagerimp.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index b491fb996..5aa98b6de 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1017,6 +1017,7 @@ namespace MWInput void InputManager::screenshot() { bool regularScreenshot = true; + bool screenshotTaken = false; std::string settingStr; @@ -1033,16 +1034,22 @@ namespace MWInput { mScreenCaptureHandler->setFramesToCapture(1); mScreenCaptureHandler->captureNextFrame(*mViewer); - MWBase::Environment::get().getWindowManager()->messageBox("Screenshot saved"); + screenshotTaken = true; } else { osg::ref_ptr screenshot (new osg::Image); if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(),settingStr)) + { (*mScreenCaptureOperation) (*(screenshot.get()),0); // mScreenCaptureHandler->getCaptureOperation() causes crash for some reason + screenshotTaken = true; + } } + + if (screenshotTaken) + MWBase::Environment::get().getWindowManager()->messageBox("Screenshot saved"); } void InputManager::toggleInventory() From 2b5f14754577c351e44cc8e18875b4abdb04c7a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 20:06:34 +0100 Subject: [PATCH 36/91] allow non-power-of-2 sized cubemaps --- apps/openmw/mwrender/renderingmanager.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 18d2dea2a..92480f45b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -683,9 +683,6 @@ namespace MWRender else if (screenshotMapping == 2) screenshotH = screenshotW; // use square resolution for planet mapping - if (!rawCubemap) - cubeSize = pow(2,round(log2(cubeSize))); // select closest power of 2 for GPU - std::vector> images; for (int i = 0; i < 6; ++i) @@ -745,6 +742,7 @@ namespace MWRender // run on GPU now: osg::ref_ptr cubeTexture (new osg::TextureCubeMap); + cubeTexture->setResizeNonPowerOfTwoHint(false); cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); From 3ae53105679b9b5dba9cf84b3f37f043fb3c48e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 22:08:33 +0100 Subject: [PATCH 37/91] use loading screen to freeze the screen --- apps/openmw/mwgui/loadingscreen.cpp | 14 +++++++++++--- apps/openmw/mwgui/loadingscreen.hpp | 4 +++- apps/openmw/mwrender/renderingmanager.cpp | 19 +++++-------------- .../loadinglistener/loadinglistener.hpp | 2 +- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 4753c39f2..023a693c9 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -153,7 +153,7 @@ namespace MWGui virtual osg::BoundingSphere computeBound(const osg::Node&) const { return osg::BoundingSphere(); } }; - void LoadingScreen::loadingOn() + void LoadingScreen::loadingOn(bool visible) { mLoadingOnTime = mTimer.time_m(); // Early-out if already on @@ -170,7 +170,10 @@ namespace MWGui // We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound() mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); - mShowWallpaper = (MWBase::Environment::get().getStateManager()->getState() + mVisible = visible; + mLoadingBox->setVisible(mVisible); + + mShowWallpaper = mVisible && (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); setVisible(true); @@ -181,10 +184,15 @@ namespace MWGui } MWBase::Environment::get().getWindowManager()->pushGuiMode(mShowWallpaper ? GM_LoadingWallpaper : GM_Loading); + + if (!mVisible) + draw(); } void LoadingScreen::loadingOff() { + mLoadingBox->setVisible(true); // restore + if (mLastRenderTime < mLoadingOnTime) { // the loading was so fast that we didn't show loading screen at all @@ -307,7 +315,7 @@ namespace MWGui void LoadingScreen::draw() { - if (!needToDrawLoadingScreen()) + if (mVisible && !needToDrawLoadingScreen()) return; if (mShowWallpaper && mTimer.time_m() > mLastWallpaperChangeTime + 5000*1) diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 8536972a3..bdd210d00 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -35,7 +35,7 @@ namespace MWGui /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details virtual void setLabel (const std::string& label, bool important); - virtual void loadingOn(); + virtual void loadingOn(bool visible=true); virtual void loadingOff(); virtual void setProgressRange (size_t range); virtual void setProgress (size_t value); @@ -63,6 +63,8 @@ namespace MWGui bool mImportantLabel; + bool mVisible; + size_t mProgress; bool mShowWallpaper; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 92480f45b..037cf9403 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -47,6 +47,9 @@ #include #include "../mwworld/cellstore.hpp" +#include "../mwgui/loadingscreen.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include "sky.hpp" #include "effectmanager.hpp" @@ -809,26 +812,14 @@ namespace MWRender osg::ref_ptr callback (new NotifyDrawCompletedCallback); camera->setFinalDrawCallback(callback); - GLbitfield maskBackup = mViewer->getCamera()->getClearMask(); - double clearDepthBackup = mViewer->getCamera()->getClearDepth(); - mViewer->getCamera()->setClearMask(GL_DEPTH_BUFFER_BIT); // don't render the main camera - mViewer->getCamera()->setClearDepth(0); - - // at the time this function is called we are in the middle of a frame, - // so out of order calls are necessary to get a correct frameNumber for the next frame. - // refer to the advance() and frame() order in Engine::go() - - mSceneSwitch->setAllChildrenOff(); // don't render the scene for main camera + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOn(false); mViewer->eventTraversal(); mViewer->updateTraversal(); mViewer->renderingTraversals(); callback->waitTillDone(); - mSceneSwitch->setAllChildrenOn(); - - mViewer->getCamera()->setClearMask(maskBackup); - mViewer->getCamera()->setClearDepth(clearDepthBackup); + MWBase::Environment::get().getWindowManager()->getLoadingScreen()->loadingOff(); // now that we've "used up" the current frame, get a fresh framenumber for the next frame() following after the screenshot is completed mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp index 1d48cce0b..6c7a3b090 100644 --- a/components/loadinglistener/loadinglistener.hpp +++ b/components/loadinglistener/loadinglistener.hpp @@ -20,7 +20,7 @@ namespace Loading /// @note To get the loading screen to actually update, you must call setProgress / increaseProgress periodically. /// @note It is best to use the ScopedLoad object instead of using loadingOn()/loadingOff() directly, /// so that the loading is exception safe. - virtual void loadingOn() {} + virtual void loadingOn(bool visible=true) {} virtual void loadingOff() {} /// Set the total range of progress (e.g. the number of objects to load). From fc507c66f7d289760134c204571978cda65ff343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 22:17:00 +0100 Subject: [PATCH 38/91] remove no longer needed stuff --- apps/openmw/mwrender/renderingmanager.cpp | 4 +--- apps/openmw/mwrender/renderingmanager.hpp | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 037cf9403..fd352f694 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -208,9 +208,7 @@ namespace MWRender mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); - mSceneSwitch = new osg::Switch; - mSceneSwitch->addChild(sceneRoot); - mRootNode->addChild(mSceneSwitch); + mRootNode->addChild(mSceneRoot); mPathgrid.reset(new Pathgrid(mRootNode)); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 4a271228e..8fd96cff9 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -224,7 +224,6 @@ namespace MWRender osg::ref_ptr mViewer; osg::ref_ptr mRootNode; osg::ref_ptr mSceneRoot; - osg::ref_ptr mSceneSwitch; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mWorkQueue; From ddbf6c162fa47896047c3a1d4c53225e1a44d752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 22:43:41 +0100 Subject: [PATCH 39/91] use linear filtering for the cubemap --- apps/openmw/mwrender/renderingmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index fd352f694..55bf2f2c4 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -744,6 +744,9 @@ namespace MWRender osg::ref_ptr cubeTexture (new osg::TextureCubeMap); cubeTexture->setResizeNonPowerOfTwoHint(false); + + cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::LINEAR); + cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::LINEAR); cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); From df61a302592fe40c28eb839a50861f311e8c21db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Thu, 16 Nov 2017 23:20:24 +0100 Subject: [PATCH 40/91] preincrement --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 55bf2f2c4..7b734f6ce 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -713,7 +713,7 @@ namespace MWRender if (mCamera->isFirstPerson()) mPlayerAnimation->getObjectRoot()->setNodeMask(0); - for (int i = 0; i < 6; i++) // for each cubemap side + for (int i = 0; i < 6; ++i) // for each cubemap side { osg::Matrixd transform = osg::Matrixd::rotate(osg::Vec3(0,0,-1),directions[i]); From ea5e078526b8d8d26f50bf5f7a7cf70ac791f656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 1 Dec 2017 19:54:48 +0100 Subject: [PATCH 41/91] Update shaders in CMakeLists --- files/shaders/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 5833a592f..7baca78ef 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -16,6 +16,8 @@ set(SHADER_FILES terrain_fragment.glsl lighting.glsl parallax.glsl + s360_fragment.glsl + s360_vertex.glsl ) copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}") From 390838e084c9fa2ab4a66151af3fb9dc512037b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 1 Dec 2017 21:03:29 +0100 Subject: [PATCH 42/91] Replace switch with ifs in shader --- files/shaders/s360_fragment.glsl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/files/shaders/s360_fragment.glsl b/files/shaders/s360_fragment.glsl index e2a6e2c99..f52e1478e 100644 --- a/files/shaders/s360_fragment.glsl +++ b/files/shaders/s360_fragment.glsl @@ -41,13 +41,12 @@ void main(void) { vec3 c; - switch (mapping) - { - case 0: c = sphericalCoords(uv); break; - case 1: c = cylindricalCoords(uv); break; - case 2: c = planetCoords(uv); break; - default: c = sphericalCoords(uv); break; - } + if (mapping == 0) + c = sphericalCoords(uv); + else if (mapping == 1) + c = cylindricalCoords(uv); + else + c = planetCoords(uv); gl_FragData[0] = textureCube(cubeMap,c); } From 37aa7612831864e3c24db9c4e3689ce0bb93ca27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Fri, 1 Dec 2017 21:47:26 +0100 Subject: [PATCH 43/91] Change spherical screenshot filtering to nearest --- apps/openmw/mwrender/renderingmanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 7b734f6ce..3b9100126 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -745,8 +745,8 @@ namespace MWRender osg::ref_ptr cubeTexture (new osg::TextureCubeMap); cubeTexture->setResizeNonPowerOfTwoHint(false); - cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::LINEAR); - cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::LINEAR); + cubeTexture->setFilter(osg::Texture::MIN_FILTER,osg::Texture::NEAREST); + cubeTexture->setFilter(osg::Texture::MAG_FILTER,osg::Texture::NEAREST); cubeTexture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); cubeTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); From a142a67972f835d914c88a20528bf636630b2691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 6 Dec 2017 21:16:30 +0100 Subject: [PATCH 44/91] Add default settings for 360 screenshots --- apps/openmw/mwinput/inputmanagerimp.cpp | 2 +- files/settings-default.cfg | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 5aa98b6de..e01deb669 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1024,7 +1024,7 @@ namespace MWInput try { settingStr = Settings::Manager::getString("screenshot type","Video"); - regularScreenshot = settingStr.size() == 0; + regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; } catch (std::runtime_error) { diff --git a/files/settings-default.cfg b/files/settings-default.cfg index aec667a9c..c0f9e85ce 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -359,6 +359,10 @@ contrast = 1.0 # Video gamma setting. (>0.0). No effect in Linux. gamma = 1.0 +# Type of screenshot to take (regular, cylindrical, spherical or planet), optionally followed by +# screenshot width, height and cubemap resolution in pixels. (e.g. spherical 1600 1000 1200) +screenshot type = regular + [Water] # Enable water shader with reflections and optionally refraction. From b7026df551e56181f87aa1929348b5b661c422ea Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 25 Mar 2018 13:05:42 +0300 Subject: [PATCH 45/91] Improve the offered price formula (Fixes #2222) --- .../openmw/mwmechanics/mechanicsmanagerimp.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 94317bbf2..0a3f8cad6 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -629,22 +629,12 @@ namespace MWMechanics float d = static_cast(std::min(sellerStats.getSkill(ESM::Skill::Mercantile).getModified(), 100)); float e = std::min(0.1f * sellerStats.getAttribute(ESM::Attribute::Luck).getModified(), 10.f); float f = std::min(0.2f * sellerStats.getAttribute(ESM::Attribute::Personality).getModified(), 10.f); - float pcTerm = (clampedDisposition - 50 + a + b + c) * playerStats.getFatigueTerm(); float npcTerm = (d + e + f) * sellerStats.getFatigueTerm(); - float buyTerm = 0.01f * (100 - 0.5f * (pcTerm - npcTerm)); - float sellTerm = 0.01f * (50 - 0.5f * (npcTerm - pcTerm)); - - float x; - if(buying) x = buyTerm; - else x = std::min(buyTerm, sellTerm); - int offerPrice; - if (x < 1) - offerPrice = int(x * basePrice); - else - offerPrice = basePrice + int((x - 1) * basePrice); - offerPrice = std::max(1, offerPrice); - return offerPrice; + float buyTerm = 0.01f * std::max(75.f, (100 - 0.5f * (pcTerm - npcTerm))); + float sellTerm = 0.01f * std::min(75.f, (50 - 0.5f * (npcTerm - pcTerm))); + int offerPrice = int(basePrice * (buying ? buyTerm : sellTerm)); + return std::max(1, offerPrice); } int MechanicsManager::countDeaths (const std::string& id) const From 24c1ee774477c44736f30881e4211d99693da115 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 4 Apr 2018 15:34:34 +0300 Subject: [PATCH 46/91] Use relative stat difference for haggling --- apps/openmw/mwmechanics/trading.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp index ede0d2de7..eee5e6449 100644 --- a/apps/openmw/mwmechanics/trading.cpp +++ b/apps/openmw/mwmechanics/trading.cpp @@ -32,7 +32,6 @@ namespace MWMechanics // Is the player buying? bool buying = (merchantOffer < 0); - int a = std::abs(merchantOffer); int b = std::abs(playerOffer); int d = (buying) @@ -56,7 +55,7 @@ namespace MWMechanics float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); float x = gmst.find("fBargainOfferMulti")->getFloat() * d + gmst.find("fBargainOfferBase")->getFloat() - + std::abs(int(pcTerm - npcTerm)); + + int(pcTerm - npcTerm); int roll = Misc::Rng::rollDice(100) + 1; From 01f12a6bd55ede0372265d368d6d1d30b2bbf159 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Fri, 8 Jun 2018 19:01:48 +0300 Subject: [PATCH 47/91] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc2a9acc1..d1ec6e58e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ 0.45.0 ------ + Bug #2222: Fatigue's effect on selling price is backwards Bug #2835: Player able to slowly move when overencumbered Bug #4221: Characters get stuck in V-shaped terrain Bug #4293: Faction members are not aware of faction ownerships in barter From 4ba361fea6d1ecef2e13e2bad2267c6644df5e6a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 10 Apr 2018 07:06:44 +0400 Subject: [PATCH 48/91] Unhardcode sunset and sunrise settings (bug #1990) --- apps/openmw/mwworld/weather.cpp | 119 ++++++++++++++++++++++---------- apps/openmw/mwworld/weather.hpp | 43 +++++++++++- 2 files changed, 123 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 155e4340a..d12e633be 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -43,49 +42,67 @@ namespace } template -T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings) const +T TimeOfDayInterpolator::getValue(const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const { - // TODO: use pre/post sunset/sunrise time values in [Weather] section + WeatherSetting setting = timeSettings.getSetting(prefix); + float preSunriseTime = setting.mPreSunriseTime; + float postSunriseTime = setting.mPostSunriseTime; + float preSunsetTime = setting.mPreSunsetTime; + float postSunsetTime = setting.mPostSunsetTime; // night - if (gameHour <= timeSettings.mNightEnd || gameHour >= timeSettings.mNightStart + 1) + if (gameHour < timeSettings.mNightEnd - preSunriseTime || gameHour > timeSettings.mNightStart + postSunsetTime) return mNightValue; // sunrise - else if (gameHour >= timeSettings.mNightEnd && gameHour <= timeSettings.mDayStart + 1) + else if (gameHour >= timeSettings.mNightEnd - preSunriseTime && gameHour <= timeSettings.mDayStart + postSunriseTime) { - if (gameHour <= timeSettings.mSunriseTime) + float duration = timeSettings.mDayStart + postSunriseTime - timeSettings.mNightEnd + preSunriseTime; + float middle = timeSettings.mNightEnd - preSunriseTime + duration / 2.f; + + if (gameHour <= middle) { // fade in - float advance = timeSettings.mSunriseTime - gameHour; - float factor = advance / 0.5f; + float advance = middle - gameHour; + float factor = 0.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunriseValue, mNightValue, factor); } else { // fade out - float advance = gameHour - timeSettings.mSunriseTime; - float factor = advance / 3.f; + float advance = gameHour - middle; + float factor = 1.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunriseValue, mDayValue, factor); } } // day - else if (gameHour >= timeSettings.mDayStart + 1 && gameHour <= timeSettings.mDayEnd - 1) + else if (gameHour > timeSettings.mDayStart + postSunriseTime && gameHour < timeSettings.mDayEnd - preSunsetTime) return mDayValue; // sunset - else if (gameHour >= timeSettings.mDayEnd - 1 && gameHour <= timeSettings.mNightStart + 1) + else if (gameHour >= timeSettings.mDayEnd - preSunsetTime && gameHour <= timeSettings.mNightStart + postSunsetTime) { - if (gameHour <= timeSettings.mDayEnd + 1) + float duration = timeSettings.mNightStart + postSunsetTime - timeSettings.mDayEnd + preSunsetTime; + float middle = timeSettings.mDayEnd - preSunsetTime + duration / 2.f; + + if (gameHour <= middle) { // fade in - float advance = (timeSettings.mDayEnd + 1) - gameHour; - float factor = (advance / 2); + float advance = middle - gameHour; + float factor = 0.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunsetValue, mDayValue, factor); } else { // fade out - float advance = gameHour - (timeSettings.mDayEnd + 1); - float factor = advance / 2.f; + float advance = gameHour - middle; + float factor = 1.f; + if (duration > 0) + factor = advance / duration * 2; return lerp(mSunsetValue, mNightValue, factor); } } @@ -539,10 +556,28 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const Fall , mPlayingSoundID() { mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; - mTimeSettings.mNightEnd = mSunriseTime - 0.5f; + mTimeSettings.mNightEnd = mSunriseTime; mTimeSettings.mDayStart = mSunriseTime + mSunriseDuration; mTimeSettings.mDayEnd = mSunsetTime; - mTimeSettings.mSunriseTime = mSunriseTime; + + mTimeSettings.addSetting(fallback, "Sky"); + mTimeSettings.addSetting(fallback, "Ambient"); + mTimeSettings.addSetting(fallback, "Fog"); + mTimeSettings.addSetting(fallback, "Sun"); + + // Morrowind handles stars settings differently for other ones + mTimeSettings.mStarsPostSunsetStart = fallback.getFallbackFloat("Weather_Stars_Post-Sunset_Start"); + mTimeSettings.mStarsPreSunriseFinish = fallback.getFallbackFloat("Weather_Stars_Pre-Sunrise_Finish"); + mTimeSettings.mStarsFadingDuration = fallback.getFallbackFloat("Weather_Stars_Fading_Duration"); + + WeatherSetting starSetting = { + mTimeSettings.mStarsPreSunriseFinish, + mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPreSunriseFinish, + mTimeSettings.mStarsPostSunsetStart, + mTimeSettings.mStarsFadingDuration - mTimeSettings.mStarsPostSunsetStart + }; + + mTimeSettings.mSunriseTransitions["Stars"] = starSetting; mWeatherSettings.reserve(10); // These distant land fog factor and offset values are the defaults MGE XE provides. Should be @@ -698,10 +733,13 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time, const float nightDuration = 24.f - dayDuration; double theta; - if ( !is_night ) { + if ( !is_night ) + { theta = static_cast(osg::PI) * (adjustedHour - mSunriseTime) / dayDuration; - } else { - theta = static_cast(osg::PI) * (1.f - (adjustedHour - adjustedNightStart) / nightDuration); + } + else + { + theta = static_cast(osg::PI) + static_cast(osg::PI) * (adjustedHour - adjustedNightStart) / nightDuration; } osg::Vec3f final( @@ -711,7 +749,7 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time, mRendering.setSunDirection( final * -1 ); } - float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings); + float underwaterFog = mUnderwaterFog.getValue(time.getHour(), mTimeSettings, "Fog"); float peakHour = mSunriseTime + (mSunsetTime - mSunriseTime) / 2; if (time.getHour() < mSunriseTime || time.getHour() > mSunsetTime) @@ -784,7 +822,7 @@ unsigned int WeatherManager::getWeatherID() const bool WeatherManager::useTorches(float hour) const { - bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart - 1; + bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart; return isDark && !mPrecipitation; } @@ -1060,20 +1098,25 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; - mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart - 1); + mResult.mNight = (gameHour < mSunriseTime || gameHour > mTimeSettings.mNightStart + mTimeSettings.mStarsPostSunsetStart - mTimeSettings.mStarsFadingDuration); - mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings); + mResult.mFogDepth = current.mLandFogDepth.getValue(gameHour, mTimeSettings, "Fog"); + mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings, "Fog"); + mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings, "Ambient"); + mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings, "Sun"); + mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings, "Sky"); + mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings, "Stars"); mResult.mDLFogFactor = current.mDL.FogFactor; mResult.mDLFogOffset = current.mDL.FogOffset; - mResult.mFogColor = current.mFogColor.getValue(gameHour, mTimeSettings); - mResult.mAmbientColor = current.mAmbientColor.getValue(gameHour, mTimeSettings); - mResult.mSunColor = current.mSunColor.getValue(gameHour, mTimeSettings); - mResult.mSkyColor = current.mSkyColor.getValue(gameHour, mTimeSettings); - mResult.mNightFade = mNightFade.getValue(gameHour, mTimeSettings); - if (gameHour >= mSunsetTime - mSunPreSunsetTime) + WeatherSetting setting = mTimeSettings.getSetting("Sun"); + float preSunsetTime = setting.mPreSunsetTime; + + if (gameHour >= mTimeSettings.mDayEnd - preSunsetTime) { - float factor = (gameHour - (mSunsetTime - mSunPreSunsetTime)) / mSunPreSunsetTime; + float factor = 1.f; + if (preSunsetTime > 0) + factor = (gameHour - (mTimeSettings.mDayEnd - preSunsetTime)) / preSunsetTime; factor = std::min(1.f, factor); mResult.mSunDiscColor = lerp(osg::Vec4f(1,1,1,1), current.mSunDiscSunsetColor, factor); // The SunDiscSunsetColor in the INI isn't exactly the resulting color on screen, most likely because @@ -1087,15 +1130,17 @@ inline void WeatherManager::calculateResult(const int weatherID, const float gam else mResult.mSunDiscColor = osg::Vec4f(1,1,1,1); - if (gameHour >= mSunsetTime) + if (gameHour >= mTimeSettings.mDayEnd) { - float fade = std::min(1.f, (gameHour - mSunsetTime) / 2.f); + // sunset + float fade = std::min(1.f, (gameHour - mTimeSettings.mDayEnd) / (mTimeSettings.mNightStart - mTimeSettings.mDayEnd)); fade = fade*fade; mResult.mSunDiscColor.a() = 1.f - fade; } - else if (gameHour >= mSunriseTime && gameHour <= mSunriseTime + 1) + else if (gameHour >= mTimeSettings.mNightEnd && gameHour <= mTimeSettings.mNightEnd + mSunriseDuration / 2.f) { - mResult.mSunDiscColor.a() = gameHour - mSunriseTime; + // sunrise + mResult.mSunDiscColor.a() = gameHour - mTimeSettings.mNightEnd; } else mResult.mSunDiscColor.a() = 1; diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 4be0d3e20..cf6868356 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -7,6 +7,8 @@ #include +#include + #include "../mwbase/soundmanager.hpp" #include "../mwrender/sky.hpp" @@ -38,6 +40,13 @@ namespace MWWorld { class TimeStamp; + struct WeatherSetting + { + float mPreSunriseTime; + float mPostSunriseTime; + float mPreSunsetTime; + float mPostSunsetTime; + }; struct TimeOfDaySettings { @@ -45,7 +54,37 @@ namespace MWWorld float mNightEnd; float mDayStart; float mDayEnd; - float mSunriseTime; + + std::map mSunriseTransitions; + + float mStarsPostSunsetStart; + float mStarsPreSunriseFinish; + float mStarsFadingDuration; + + WeatherSetting getSetting(const std::string& type) const + { + std::map::const_iterator it = mSunriseTransitions.find(type); + if (it != mSunriseTransitions.end()) + { + return it->second; + } + else + { + return { 1.f, 1.f, 1.f, 1.f }; + } + } + + void addSetting(const Fallback::Map& fallback, const std::string& type) + { + WeatherSetting setting = { + fallback.getFallbackFloat("Weather_" + type + "_Pre-Sunrise_Time"), + fallback.getFallbackFloat("Weather_" + type + "_Post-Sunrise_Time"), + fallback.getFallbackFloat("Weather_" + type + "_Pre-Sunset_Time"), + fallback.getFallbackFloat("Weather_" + type + "_Post-Sunset_Time") + }; + + mSunriseTransitions[type] = setting; + } }; /// Interpolates between 4 data points (sunrise, day, sunset, night) based on the time of day. @@ -59,7 +98,7 @@ namespace MWWorld { } - T getValue (const float gameHour, const TimeOfDaySettings& timeSettings) const; + T getValue (const float gameHour, const TimeOfDaySettings& timeSettings, const std::string& prefix) const; private: T mSunriseValue, mDayValue, mSunsetValue, mNightValue; From 5ead6353ac38cd3df0a09287034ee69da8bee554 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 7 Jun 2018 16:44:46 +0400 Subject: [PATCH 49/91] Add missing changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd1acff2a..f36636571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.45.0 ------ + Bug #1990: Sunrise/sunset not set correct Bug #2835: Player able to slowly move when overencumbered Bug #3374: Touch spells not hitting kwama foragers Bug #3591: Angled hit distance too low From 0375bedab229358b8fe04c29cd16d382623f4370 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 23 Nov 2017 19:57:36 +0400 Subject: [PATCH 50/91] Equip previous item after a bound item expires (bug #2326) --- apps/openmw/mwmechanics/actors.cpp | 106 +++++++++++++++++++++++------ apps/openmw/mwmechanics/actors.hpp | 4 ++ 2 files changed, 91 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 7990373a7..10e9b7afc 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -50,27 +50,36 @@ bool isConscious(const MWWorld::Ptr& ptr) return !stats.isDead() && !stats.getKnockedDown(); } -void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& actor) +int getBoundItemSlot (const std::string& itemId) { - if (bound) + static std::map boundItemsMap; + if (boundItemsMap.empty()) { - if (actor.getClass().getContainerStore(actor).count(item) == 0) - { - MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - MWWorld::Ptr newPtr = *store.MWWorld::ContainerStore::add(item, 1, actor); - MWWorld::ActionEquip action(newPtr); - action.execute(actor); - MWWorld::ConstContainerStoreIterator rightHand = store.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - // change draw state only if the item is in player's right hand - if (actor == MWMechanics::getPlayer() - && rightHand != store.end() && newPtr == *rightHand) - { - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); - } - } + std::string boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundBootsID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundCuirassID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundLeftGauntletID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundRightGauntletID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundHelmID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet; + + boundId = MWBase::Environment::get().getWorld()->getStore().get().find("sMagicBoundShieldID")->getString(); + boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_CarriedLeft; } - else - actor.getClass().getInventoryStore(actor).remove(item, 1, actor, true); + + int slot = MWWorld::InventoryStore::Slot_CarriedRight; + std::map::iterator it = boundItemsMap.find(itemId); + if (it != boundItemsMap.end()) + slot = it->second; + + return slot; } class CheckActorCommanded : public MWMechanics::EffectSourceVisitor @@ -139,7 +148,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float namespace MWMechanics { - const float aiProcessingDistance = 7168; const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance; @@ -227,6 +235,65 @@ namespace MWMechanics } }; + void Actors::adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + + if (bound) + { + if (actor.getClass().getContainerStore(actor).count(itemId) != 0) + return; + + int slot = getBoundItemSlot(itemId); + + MWWorld::Ptr prevItem = *store.getSlot(slot); + + MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); + MWWorld::ActionEquip action(boundPtr); + action.execute(actor); + + if (actor != MWMechanics::getPlayer()) + return; + + MWWorld::Ptr newItem = *store.getSlot(slot); + + if (newItem.isEmpty() || boundPtr != newItem) + return; + + // change draw state only if the item is in player's right hand + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); + + mPreviousItems[slot] = std::make_pair(itemId, prevItem.isEmpty() ? "" : prevItem.getCellRef().getRefId()); + } + else + { + store.remove(itemId, 1, actor, true); + + if (actor != MWMechanics::getPlayer()) + return; + + int slot = getBoundItemSlot(itemId); + + std::pair prevItem = mPreviousItems[slot]; + + if (prevItem.first != itemId) + return; + + MWWorld::Ptr ptr = MWWorld::Ptr(); + if (prevItem.second != "") + ptr = store.search (prevItem.second); + + mPreviousItems.erase(slot); + + if (ptr.isEmpty()) + return; + + MWWorld::ActionEquip action(ptr); + action.execute(actor); + } + } + void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) { // magic effects @@ -1875,6 +1942,7 @@ namespace MWMechanics } mActors.clear(); mDeathCount.clear(); + mPreviousItems.clear(); } void Actors::updateMagicEffects(const MWWorld::Ptr &ptr) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 15f2d3dc8..f0e157db1 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -25,6 +25,10 @@ namespace MWMechanics class Actors { std::map mDeathCount; + typedef std::map> PreviousItems; + PreviousItems mPreviousItems; + + void adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor); void updateNpc(const MWWorld::Ptr &ptr, float duration); From 9b72a6ac69ddd4b282ff3c9ed46ca4d8b7cde44c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 27 Nov 2017 13:00:14 +0400 Subject: [PATCH 51/91] Use the MWWorld::Ptr() instead of string ID --- apps/openmw/mwmechanics/actors.cpp | 23 +++++++++++++++-------- apps/openmw/mwmechanics/actors.hpp | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 10e9b7afc..76c5744b3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -264,7 +264,7 @@ namespace MWMechanics if (slot == MWWorld::InventoryStore::Slot_CarriedRight) MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); - mPreviousItems[slot] = std::make_pair(itemId, prevItem.isEmpty() ? "" : prevItem.getCellRef().getRefId()); + mPreviousItems[slot] = std::make_pair(itemId, prevItem); } else { @@ -275,21 +275,28 @@ namespace MWMechanics int slot = getBoundItemSlot(itemId); - std::pair prevItem = mPreviousItems[slot]; + std::pair prevItem = mPreviousItems[slot]; if (prevItem.first != itemId) return; - MWWorld::Ptr ptr = MWWorld::Ptr(); - if (prevItem.second != "") - ptr = store.search (prevItem.second); - mPreviousItems.erase(slot); - if (ptr.isEmpty()) + if (prevItem.second.isEmpty()) + return; + + // check if the item is still in the player's inventory + MWWorld::ContainerStoreIterator it = store.begin(); + for (; it != store.end(); ++it) + { + if (*it == prevItem.second) + break; + } + + if (it == store.end()) return; - MWWorld::ActionEquip action(ptr); + MWWorld::ActionEquip action(prevItem.second); action.execute(actor); } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index f0e157db1..40e9e1d21 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -25,7 +25,7 @@ namespace MWMechanics class Actors { std::map mDeathCount; - typedef std::map> PreviousItems; + typedef std::map> PreviousItems; PreviousItems mPreviousItems; void adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor); From d1b1cb748d94cda070c5383973329613620097d0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Dec 2017 16:26:43 +0400 Subject: [PATCH 52/91] Reequip previous item only if the expired bound item was equipped --- apps/openmw/mwmechanics/actors.cpp | 41 +++++++++++++++--------------- apps/openmw/mwmechanics/actors.hpp | 2 +- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 76c5744b3..4019067e3 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -238,15 +238,14 @@ namespace MWMechanics void Actors::adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor) { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + int slot = getBoundItemSlot(itemId); if (bound) { if (actor.getClass().getContainerStore(actor).count(itemId) != 0) return; - int slot = getBoundItemSlot(itemId); - - MWWorld::Ptr prevItem = *store.getSlot(slot); + MWWorld::ContainerStoreIterator prevItem = store.getSlot(slot); MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); MWWorld::ActionEquip action(boundPtr); @@ -264,39 +263,40 @@ namespace MWMechanics if (slot == MWWorld::InventoryStore::Slot_CarriedRight) MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); - mPreviousItems[slot] = std::make_pair(itemId, prevItem); + if (prevItem != store.end()) + mPreviousItems[itemId] = *prevItem; } else { + MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot); + + bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual((*currentItem).getCellRef().getRefId(), itemId); + store.remove(itemId, 1, actor, true); if (actor != MWMechanics::getPlayer()) return; - int slot = getBoundItemSlot(itemId); + MWWorld::Ptr prevItem = mPreviousItems[itemId]; - std::pair prevItem = mPreviousItems[slot]; + mPreviousItems.erase(itemId); - if (prevItem.first != itemId) - return; - - mPreviousItems.erase(slot); - - if (prevItem.second.isEmpty()) + if (prevItem.isEmpty()) return; // check if the item is still in the player's inventory MWWorld::ContainerStoreIterator it = store.begin(); for (; it != store.end(); ++it) { - if (*it == prevItem.second) + if (*it == prevItem) break; } - if (it == store.end()) + // we should equip previous item only if expired bound item was equipped. + if (it == store.end() || !wasEquipped) return; - MWWorld::ActionEquip action(prevItem.second); + MWWorld::ActionEquip action(prevItem); action.execute(actor); } } @@ -830,9 +830,15 @@ namespace MWMechanics float magnitude = effects.get(it->first).getMagnitude(); if (found != (magnitude > 0)) { + if (magnitude > 0) + creatureStats.mBoundItems.insert(it->first); + else + creatureStats.mBoundItems.erase(it->first); + std::string itemGmst = it->second; std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( itemGmst)->getString(); + if (it->first == ESM::MagicEffect::BoundGloves) { item = MWBase::Environment::get().getWorld()->getStore().get().find( @@ -844,11 +850,6 @@ namespace MWMechanics } else adjustBoundItem(item, magnitude > 0, ptr); - - if (magnitude > 0) - creatureStats.mBoundItems.insert(it->first); - else - creatureStats.mBoundItems.erase(it->first); } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 40e9e1d21..e696abf01 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -25,7 +25,7 @@ namespace MWMechanics class Actors { std::map mDeathCount; - typedef std::map> PreviousItems; + typedef std::map PreviousItems; PreviousItems mPreviousItems; void adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor); From 4de9d9fa778402eff3de8c9e8d56b44132aaca80 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Dec 2017 17:18:16 +0400 Subject: [PATCH 53/91] Split adjustBoundItem() --- apps/openmw/mwmechanics/actors.cpp | 96 +++++++++++++++--------------- apps/openmw/mwmechanics/actors.hpp | 3 +- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 4019067e3..d1e74e576 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -235,70 +235,71 @@ namespace MWMechanics } }; - void Actors::adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor) + void Actors::addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) { MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); int slot = getBoundItemSlot(itemId); - if (bound) - { - if (actor.getClass().getContainerStore(actor).count(itemId) != 0) - return; + if (actor.getClass().getContainerStore(actor).count(itemId) != 0) + return; - MWWorld::ContainerStoreIterator prevItem = store.getSlot(slot); + MWWorld::ContainerStoreIterator prevItem = store.getSlot(slot); - MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); - MWWorld::ActionEquip action(boundPtr); - action.execute(actor); + MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); + MWWorld::ActionEquip action(boundPtr); + action.execute(actor); - if (actor != MWMechanics::getPlayer()) - return; + if (actor != MWMechanics::getPlayer()) + return; - MWWorld::Ptr newItem = *store.getSlot(slot); + MWWorld::Ptr newItem = *store.getSlot(slot); - if (newItem.isEmpty() || boundPtr != newItem) - return; + if (newItem.isEmpty() || boundPtr != newItem) + return; - // change draw state only if the item is in player's right hand - if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); + // change draw state only if the item is in player's right hand + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); - if (prevItem != store.end()) - mPreviousItems[itemId] = *prevItem; - } - else - { - MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot); + if (prevItem != store.end()) + mPreviousItems[itemId] = *prevItem; + } - bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual((*currentItem).getCellRef().getRefId(), itemId); + void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + int slot = getBoundItemSlot(itemId); - store.remove(itemId, 1, actor, true); + MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot); - if (actor != MWMechanics::getPlayer()) - return; + bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual((*currentItem).getCellRef().getRefId(), itemId); - MWWorld::Ptr prevItem = mPreviousItems[itemId]; + store.remove(itemId, 1, actor, true); - mPreviousItems.erase(itemId); + if (actor != MWMechanics::getPlayer()) + return; - if (prevItem.isEmpty()) - return; + MWWorld::Ptr prevItem = mPreviousItems[itemId]; - // check if the item is still in the player's inventory - MWWorld::ContainerStoreIterator it = store.begin(); - for (; it != store.end(); ++it) - { - if (*it == prevItem) - break; - } + mPreviousItems.erase(itemId); - // we should equip previous item only if expired bound item was equipped. - if (it == store.end() || !wasEquipped) - return; + if (prevItem.isEmpty()) + return; - MWWorld::ActionEquip action(prevItem); - action.execute(actor); + // check if the item is still in the player's inventory + MWWorld::ContainerStoreIterator it = store.begin(); + for (; it != store.end(); ++it) + { + if (*it == prevItem) + break; } + + // we should equip previous item only if expired bound item was equipped. + if (it == store.end() || !wasEquipped) + return; + + MWWorld::ActionEquip action(prevItem); + action.execute(actor); } void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) @@ -839,17 +840,14 @@ namespace MWMechanics std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( itemGmst)->getString(); + magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); + if (it->first == ESM::MagicEffect::BoundGloves) { - item = MWBase::Environment::get().getWorld()->getStore().get().find( - "sMagicBoundLeftGauntletID")->getString(); - adjustBoundItem(item, magnitude > 0, ptr); item = MWBase::Environment::get().getWorld()->getStore().get().find( "sMagicBoundRightGauntletID")->getString(); - adjustBoundItem(item, magnitude > 0, ptr); + magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); } - else - adjustBoundItem(item, magnitude > 0, ptr); } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index e696abf01..b3e1f95db 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -28,7 +28,8 @@ namespace MWMechanics typedef std::map PreviousItems; PreviousItems mPreviousItems; - void adjustBoundItem (const std::string& itemId, bool bound, const MWWorld::Ptr& actor); + void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); + void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); void updateNpc(const MWWorld::Ptr &ptr, float duration); From f977c6876ff391cc4624483647bf3197791482c5 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 23 Feb 2018 14:48:40 +0400 Subject: [PATCH 54/91] Bound items: store item ID instead of pointer --- apps/openmw/mwmechanics/actors.cpp | 28 ++++++++++++++++++---------- apps/openmw/mwmechanics/actors.hpp | 2 +- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d1e74e576..8a13b492f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -262,7 +262,7 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); if (prevItem != store.end()) - mPreviousItems[itemId] = *prevItem; + mPreviousItems[itemId] = (*prevItem).getCellRef().getRefId(); } void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) @@ -279,26 +279,34 @@ namespace MWMechanics if (actor != MWMechanics::getPlayer()) return; - MWWorld::Ptr prevItem = mPreviousItems[itemId]; + std::string prevItemId = mPreviousItems[itemId]; mPreviousItems.erase(itemId); - if (prevItem.isEmpty()) + if (prevItemId.empty()) return; - // check if the item is still in the player's inventory - MWWorld::ContainerStoreIterator it = store.begin(); - for (; it != store.end(); ++it) + // Find the item by id + MWWorld::Ptr item; + for (MWWorld::ContainerStoreIterator iter = store.begin(); iter != store.end(); ++iter) { - if (*it == prevItem) - break; + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), prevItemId)) + { + if (item.isEmpty() || + // Prefer the stack with the lowest remaining uses + !item.getClass().hasItemHealth(*iter) || + iter->getClass().getItemHealth(*iter) < item.getClass().getItemHealth(item)) + { + item = *iter; + } + } } // we should equip previous item only if expired bound item was equipped. - if (it == store.end() || !wasEquipped) + if (item.isEmpty() || !wasEquipped) return; - MWWorld::ActionEquip action(prevItem); + MWWorld::ActionEquip action(item); action.execute(actor); } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index b3e1f95db..90b646f0e 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -25,7 +25,7 @@ namespace MWMechanics class Actors { std::map mDeathCount; - typedef std::map PreviousItems; + typedef std::map PreviousItems; PreviousItems mPreviousItems; void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); From 9fd2d57b8663dee2df8ca739f15d7b39b769b3a6 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 9 Mar 2018 08:56:04 +0400 Subject: [PATCH 55/91] Move previous items to player --- apps/openmw/mwmechanics/actors.cpp | 14 ++++++++------ apps/openmw/mwmechanics/actors.hpp | 3 --- apps/openmw/mwworld/player.cpp | 16 ++++++++++++++++ apps/openmw/mwworld/player.hpp | 9 +++++++++ 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 8a13b492f..6549c169b 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -262,7 +262,10 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); if (prevItem != store.end()) - mPreviousItems[itemId] = (*prevItem).getCellRef().getRefId(); + { + MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); + player->setPreviousItem(itemId, prevItem->getCellRef().getRefId()); + } } void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) @@ -272,16 +275,16 @@ namespace MWMechanics MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot); - bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual((*currentItem).getCellRef().getRefId(), itemId); + bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId); store.remove(itemId, 1, actor, true); if (actor != MWMechanics::getPlayer()) return; - std::string prevItemId = mPreviousItems[itemId]; - - mPreviousItems.erase(itemId); + MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); + std::string prevItemId = player->getPreviousItem(itemId); + player->erasePreviousItem(itemId); if (prevItemId.empty()) return; @@ -1956,7 +1959,6 @@ namespace MWMechanics } mActors.clear(); mDeathCount.clear(); - mPreviousItems.clear(); } void Actors::updateMagicEffects(const MWWorld::Ptr &ptr) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 90b646f0e..0de1f4d6c 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include "../mwbase/world.hpp" @@ -25,8 +24,6 @@ namespace MWMechanics class Actors { std::map mDeathCount; - typedef std::map PreviousItems; - PreviousItems mPreviousItems; void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 34c5f713d..193663098 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -287,6 +287,7 @@ namespace MWWorld mAttackingOrSpell = false; mCurrentCrimeId = -1; mPaidCrimeId = -1; + mPreviousItems.clear(); mLastKnownExteriorPosition = osg::Vec3f(0,0,0); for (int i=0; i + #include "../mwworld/refdata.hpp" #include "../mwworld/livecellref.hpp" @@ -46,6 +48,9 @@ namespace MWWorld int mCurrentCrimeId; // the id assigned witnesses int mPaidCrimeId; // the last id paid off (0 bounty) + typedef std::map PreviousItems; // previous equipped items, needed for bound spells + PreviousItems mPreviousItems; + // Saved stats prior to becoming a werewolf MWMechanics::SkillValue mSaveSkills[ESM::Skill::Length]; MWMechanics::AttributeValue mSaveAttributes[ESM::Attribute::Length]; @@ -120,6 +125,10 @@ namespace MWWorld int getNewCrimeId(); // get new id for witnesses void recordCrimeId(); // record the paid crime id when bounty is 0 int getCrimeId() const; // get the last paid crime id + + void setPreviousItem(const std::string& boundItemId, const std::string& previousItemId); + std::string getPreviousItem(const std::string& boundItemId); + void erasePreviousItem(const std::string& boundItemId); }; } #endif From acd3cba5fae441602389bd2dbef4579e8e70a842 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 9 Mar 2018 10:20:17 +0400 Subject: [PATCH 56/91] Store previous items in the savegame --- CHANGELOG.md | 1 + apps/openmw/mwworld/player.cpp | 4 ++++ components/esm/player.cpp | 18 ++++++++++++++++++ components/esm/player.hpp | 3 +++ components/esm/savedgame.cpp | 2 +- 5 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3101056e..a19b012c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.45.0 ------ + Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped Bug #2835: Player able to slowly move when overencumbered Bug #3374: Touch spells not hitting kwama foragers Bug #3591: Angled hit distance too low diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 193663098..5439447fd 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -342,6 +342,8 @@ namespace MWWorld for (int i=0; ifirst); + esm.writeHNString ("PREV", it->second); + } + for (int i=0; i mSaveAttributes[ESM::Attribute::Length]; StatState mSaveSkills[ESM::Skill::Length]; + typedef std::map PreviousItems; // previous equipped items, needed for bound spells + PreviousItems mPreviousItems; + void load (ESMReader &esm); void save (ESMWriter &esm) const; }; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index c96261c64..ea9fef4fb 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -5,7 +5,7 @@ #include "defs.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 4; +int ESM::SavedGame::sCurrentFormat = 5; void ESM::SavedGame::load (ESMReader &esm) { From 7502a7dc4d299cdacbf46275ad2c820e71c0e125 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 07:52:53 +0000 Subject: [PATCH 57/91] what does this give us from a CI perspective? --- .gitlab-ci.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..90525e5a9 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,26 @@ +# use the official gcc image, based on debian +# can use verions as well, like gcc:5.2 +# see https://hub.docker.com/_/gcc/ +image: gcc + +build: + stage: build + # instead of calling g++ directly you can also use some build toolkit like make + # install the necessary build tools when needed + # before_script: + - apt update && apt -y install build-essential cmake git + script: + - whois + # artifacts: + # paths: + # - mybinary + # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time + # cache: + # paths: + # - "*.o" + +# run tests using the binary built before +test: + stage: test + script: + - whois From 04dc74a1d6898dbd26961cfb13127117b74cc0fc Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 08:05:30 +0000 Subject: [PATCH 58/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 90525e5a9..3b0dca5fe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,12 +8,12 @@ build: # instead of calling g++ directly you can also use some build toolkit like make # install the necessary build tools when needed # before_script: - - apt update && apt -y install build-essential cmake git + # - apt update && apt -y install make autoconf script: - whois - # artifacts: - # paths: - # - mybinary +# artifacts: +# paths: +# - mybinary # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time # cache: # paths: From 8714f48ce7b1bed792e6cd582710a1ad3da72fbd Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 08:22:44 +0000 Subject: [PATCH 59/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3b0dca5fe..394f07136 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,7 @@ build: # before_script: # - apt update && apt -y install make autoconf script: - - whois + - ls /etc # artifacts: # paths: # - mybinary @@ -23,4 +23,4 @@ build: test: stage: test script: - - whois + - ls From d986354d53af6a22162257eebfb146f46237adc1 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 08:30:18 +0000 Subject: [PATCH 60/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 394f07136..b614160e9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,13 @@ build: # before_script: # - apt update && apt -y install make autoconf script: + - ls ./ - ls /etc + - ls /bin + - ls /usr/bin + - cat /etc/lsb-release + - apt-get install -y cmake + - mkdir build; cd build; cmake ../openmw # artifacts: # paths: # - mybinary From de1cad86abb974e0d88c33d030cf518624204737 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 08:37:26 +0000 Subject: [PATCH 61/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b614160e9..f8df83a5f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,9 +14,8 @@ build: - ls /etc - ls /bin - ls /usr/bin - - cat /etc/lsb-release - apt-get install -y cmake - - mkdir build; cd build; cmake ../openmw + - mkdir build; cd build; cmake ../ # artifacts: # paths: # - mybinary From 8c4731728c97822af846ae8e3322e1b77bdd568b Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 08:41:07 +0000 Subject: [PATCH 62/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f8df83a5f..7d1adb5ab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,6 +14,8 @@ build: - ls /etc - ls /bin - ls /usr/bin + - apt-get update + - apt-cache search cmake - apt-get install -y cmake - mkdir build; cd build; cmake ../ # artifacts: From 20d8a424d6a5dd32aa373321a8f21aeba454f0ab Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 09:09:37 +0000 Subject: [PATCH 63/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7d1adb5ab..262c8147c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,8 +16,14 @@ build: - ls /usr/bin - apt-get update - apt-cache search cmake - - apt-get install -y cmake + - apt-get install -y cmake + - apt-get install -y libunshield-dev libtinyxml-dev + - apt-get install -y libboost-filesystem-dev libboost-program-options-dev libboost-system-dev + - apt-get install -y libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev + - apt-get install -y libsdl2-dev libqt4-dev libopenal-dev + - apt-get install -y libbullet-dev libopenscenegraph-3.4-dev libmygui-dev - mkdir build; cd build; cmake ../ + - make -j4 # artifacts: # paths: # - mybinary From a89441e879d0bf31a7f1d9502a367371d5bd0b6d Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 09:26:10 +0000 Subject: [PATCH 64/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 262c8147c..28fdec103 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,11 +17,13 @@ build: - apt-get update - apt-cache search cmake - apt-get install -y cmake - - apt-get install -y libunshield-dev libtinyxml-dev + - apt-get install -y - apt-get install -y libboost-filesystem-dev libboost-program-options-dev libboost-system-dev - apt-get install -y libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev - - apt-get install -y libsdl2-dev libqt4-dev libopenal-dev - - apt-get install -y libbullet-dev libopenscenegraph-3.4-dev libmygui-dev + - apt-get install -y libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libmygui-dev libunshield-dev libtinyxml-dev + - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb + - dpkg -i *.deb - mkdir build; cd build; cmake ../ - make -j4 # artifacts: From e5dff83e38583a968f95217dd8c4fc183ef8c7ec Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 09:26:42 +0000 Subject: [PATCH 65/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 28fdec103..67481a71f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,7 +17,6 @@ build: - apt-get update - apt-cache search cmake - apt-get install -y cmake - - apt-get install -y - apt-get install -y libboost-filesystem-dev libboost-program-options-dev libboost-system-dev - apt-get install -y libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev - apt-get install -y libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libmygui-dev libunshield-dev libtinyxml-dev From bc0eb3349bf037ca599da1db2f21a15d17dc747c Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 10:33:35 +0000 Subject: [PATCH 66/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 67481a71f..b372e6dbf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,24 +10,22 @@ build: # before_script: # - apt update && apt -y install make autoconf script: - - ls ./ - - ls /etc - - ls /bin - - ls /usr/bin - apt-get update - - apt-cache search cmake - - apt-get install -y cmake - - apt-get install -y libboost-filesystem-dev libboost-program-options-dev libboost-system-dev + - apt-get install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev - apt-get install -y libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev - - apt-get install -y libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libmygui-dev libunshield-dev libtinyxml-dev + - apt-get install -y libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev + - apt-get install -y libmygui-dev libullet-dev # to be updated to latest below because stretch is too old - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.ogreplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.ogreplatform0debian1v5_3.2.2+dfsg-1_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb - dpkg -i *.deb - mkdir build; cd build; cmake ../ - make -j4 -# artifacts: -# paths: -# - mybinary + artifacts: + paths: + - build # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time # cache: # paths: From e3832cd2e2cbb3b46d3c71db14cf54289acf4ad4 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 11:24:27 +0000 Subject: [PATCH 67/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b372e6dbf..f6e599a09 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ build: - apt-get install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev - apt-get install -y libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev - apt-get install -y libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev - - apt-get install -y libmygui-dev libullet-dev # to be updated to latest below because stretch is too old + - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.ogreplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.ogreplatform0debian1v5_3.2.2+dfsg-1_amd64.deb From dddceba8f2d2911f4340094307e8e27536001e12 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 11:32:37 +0000 Subject: [PATCH 68/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f6e599a09..be9986e6d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,10 +17,11 @@ build: - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb - - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.ogreplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.ogreplatform0debian1v5_3.2.2+dfsg-1_amd64.deb +# - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.ogreplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.ogreplatform0debian1v5_3.2.2+dfsg-1_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb - - dpkg -i *.deb + - dpkg -i *.deb --ignore-depends - mkdir build; cd build; cmake ../ - make -j4 artifacts: From a166534226dd34d7ca2e5da8a9ee805c8e9b02a7 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 11:41:19 +0000 Subject: [PATCH 69/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index be9986e6d..fed43f495 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -21,7 +21,7 @@ build: - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb - - dpkg -i *.deb --ignore-depends + - dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb - mkdir build; cd build; cmake ../ - make -j4 artifacts: From 1c736ea06448ee9660668fc31e1ca4f92d593550 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 12:46:19 +0000 Subject: [PATCH 70/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fed43f495..ac0fb1175 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,10 +5,6 @@ image: gcc build: stage: build - # instead of calling g++ directly you can also use some build toolkit like make - # install the necessary build tools when needed - # before_script: - # - apt update && apt -y install make autoconf script: - apt-get update - apt-get install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev @@ -17,23 +13,23 @@ build: - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb -# - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.ogreplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.ogreplatform0debian1v5_3.2.2+dfsg-1_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb - dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb - mkdir build; cd build; cmake ../ - make -j4 + - DESTDIR=artifacts make install artifacts: paths: - - build + - build/artifacts/ # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time - # cache: - # paths: - # - "*.o" + cache: + paths: + - "*.o" # run tests using the binary built before -test: - stage: test - script: - - ls +#test: +# stage: test +# script: +# - ls From 9c45cc7e48897171fc27c219bd1e7d9533b81ff9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jun 2018 22:05:00 +0400 Subject: [PATCH 71/91] Use player reference instead of pointer --- apps/openmw/mwmechanics/actors.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6549c169b..de394c446 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -257,15 +257,14 @@ namespace MWMechanics if (newItem.isEmpty() || boundPtr != newItem) return; + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + // change draw state only if the item is in player's right hand if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - MWBase::Environment::get().getWorld()->getPlayer().setDrawState(MWMechanics::DrawState_Weapon); + player.setDrawState(MWMechanics::DrawState_Weapon); if (prevItem != store.end()) - { - MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); - player->setPreviousItem(itemId, prevItem->getCellRef().getRefId()); - } + player.setPreviousItem(itemId, prevItem->getCellRef().getRefId()); } void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) @@ -282,9 +281,9 @@ namespace MWMechanics if (actor != MWMechanics::getPlayer()) return; - MWWorld::Player* player = &MWBase::Environment::get().getWorld()->getPlayer(); - std::string prevItemId = player->getPreviousItem(itemId); - player->erasePreviousItem(itemId); + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + std::string prevItemId = player.getPreviousItem(itemId); + player.erasePreviousItem(itemId); if (prevItemId.empty()) return; From cc396f4dfdace36c9efd12526d52eefb5392a762 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 19:06:59 +0000 Subject: [PATCH 72/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ac0fb1175..5f2cbf912 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,6 +6,7 @@ image: gcc build: stage: build script: + - nproc - apt-get update - apt-get install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev - apt-get install -y libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev @@ -18,7 +19,7 @@ build: - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb - dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb - mkdir build; cd build; cmake ../ - - make -j4 + - make -j3 - DESTDIR=artifacts make install artifacts: paths: @@ -29,7 +30,7 @@ build: - "*.o" # run tests using the binary built before -#test: -# stage: test -# script: -# - ls +test: + stage: test + script: + - true From 3c933ebaad915b6726e55aff10b228df4fce9b34 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 12 Jun 2018 19:10:15 +0000 Subject: [PATCH 73/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5f2cbf912..2a4607a38 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,4 +33,4 @@ build: test: stage: test script: - - true + - ls From 058cfb553ce2a20a0150793796b4b5c32527eb9b Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Tue, 12 Jun 2018 22:18:06 -0500 Subject: [PATCH 74/91] Adding CFBundleIdentifier to OpenMW's Info.plist file for Macs --- files/mac/openmw-Info.plist.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/mac/openmw-Info.plist.in b/files/mac/openmw-Info.plist.in index 7583b45ad..20dc36afa 100644 --- a/files/mac/openmw-Info.plist.in +++ b/files/mac/openmw-Info.plist.in @@ -8,6 +8,8 @@ English CFBundleExecutable openmw-launcher + CFBundleIdentifier + org.openmw.openmw CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString From b37f3251263ec86d2c8c1622989616d1c674d64e Mon Sep 17 00:00:00 2001 From: Thunderforge Date: Tue, 12 Jun 2018 22:20:16 -0500 Subject: [PATCH 75/91] #4324/Updating Changelog.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f407a156e..95a947d70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 Bug #4432: Guards behaviour is incorrect if they do not have AI packages Bug #4433: Guard behaviour is incorrect with Alarm = 0 + Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts Feature #4345: Add equivalents for the command line commands to Launcher Feature #4444: Per-group KF-animation files support From 2e2be76e3ff700a3348d6420571b650a9e2cdd40 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 13 Jun 2018 08:20:31 +0000 Subject: [PATCH 76/91] Update .gitlab-ci.yml --- .gitlab-ci.yml | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2a4607a38..18ae73124 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,23 +3,29 @@ # see https://hub.docker.com/_/gcc/ image: gcc +cache: + key: apt-cache + paths: + - apt-cache/ + +before_script: + - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR + - apt-get update -yq + - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev +# - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old + - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb + - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb + - dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb + build: stage: build script: - nproc - - apt-get update - - apt-get install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev - - apt-get install -y libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev - - apt-get install -y libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev - - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - - curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb - - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb - - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb - - curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb - - dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb - - mkdir build; cd build; cmake ../ - - make -j3 + - mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../ + - make -j2 - DESTDIR=artifacts make install artifacts: paths: From dcfbd554bbd1ac264a31f4c5afdf2261d0e3f579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 13 Jun 2018 11:12:46 +0200 Subject: [PATCH 77/91] Remove try catch block --- apps/openmw/mwinput/inputmanagerimp.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 2a101e58f..1e8a12e2c 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1041,14 +1041,8 @@ namespace MWInput std::string settingStr; - try - { - settingStr = Settings::Manager::getString("screenshot type","Video"); - regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; - } - catch (std::runtime_error) - { - } + settingStr = Settings::Manager::getString("screenshot type","Video"); + regularScreenshot = settingStr.size() == 0 || settingStr.compare("regular") == 0; if (regularScreenshot) { From a49649c313ce267318d203a0687afe53ac37d70b Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 13 Jun 2018 09:29:30 +0000 Subject: [PATCH 78/91] Try to get it to run and build on my docker instance. --- .gitlab-ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 18ae73124..961965763 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,6 +3,11 @@ # see https://hub.docker.com/_/gcc/ image: gcc +job: + tags: + - openmw + - debian + cache: key: apt-cache paths: @@ -25,7 +30,7 @@ build: script: - nproc - mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../ - - make -j2 + - make -j4 - DESTDIR=artifacts make install artifacts: paths: From 559754fa7683dd33084013e977689fdc7dbf06dc Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 13 Jun 2018 09:31:39 +0000 Subject: [PATCH 79/91] try this dance again --- .gitlab-ci.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 961965763..2c47affd6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,11 +3,6 @@ # see https://hub.docker.com/_/gcc/ image: gcc -job: - tags: - - openmw - - debian - cache: key: apt-cache paths: From da37585a8e708fd974d99ffd3f01a55ae16fac1f Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 13 Jun 2018 10:15:07 +0000 Subject: [PATCH 80/91] Update .gitlab-ci.yml so that we only build with -j2 --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2c47affd6..18ae73124 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,7 +25,7 @@ build: script: - nproc - mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../ - - make -j4 + - make -j2 - DESTDIR=artifacts make install artifacts: paths: From 3f4d5598a524790b33eaace4be7d5b20335817d0 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 13 Jun 2018 11:55:33 +0000 Subject: [PATCH 81/91] Update README.md to be more generic about OpenMW --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7d92879e2..a6d0cacd5 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ OpenMW [![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/github/openmw/openmw?svg=true)](https://ci.appveyor.com/project/psi29a/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) -OpenMW is a open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own and install the original game for OpenMW to work. +OpenMW is an open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind. -OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. +OpenMW also comes with OpenMW-CS, a replacement for Bethesda's Construction Set. * Version: 0.44.0 * License: GPLv3 (see [LICENSE](https://github.com/OpenMW/openmw/blob/master/LICENSE) for more information) From 032768a505e041a627dd455d1973c1b99f32f95d Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 13 Jun 2018 12:38:03 +0000 Subject: [PATCH 82/91] try to use as many cores as possible --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 18ae73124..74bc74ca8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,9 +23,9 @@ before_script: build: stage: build script: - - nproc + - cores_to_use=$((`nproc`-2)); if (( $cores_to_use < 1 )); then cores_to_use=1; fi - mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=MinSizeRel ../ - - make -j2 + - make -j$cores_to_use - DESTDIR=artifacts make install artifacts: paths: From 7d2394273e3a85739a2bc4834937e6c21a633cf3 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 13 Jun 2018 14:55:03 +0200 Subject: [PATCH 83/91] added statusbar to search window (Fixes #3276) --- CHANGELOG.md | 1 + apps/opencs/view/tools/searchsubview.cpp | 22 ++++++++++++++++++++++ apps/opencs/view/tools/searchsubview.hpp | 10 ++++++++++ 3 files changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc66bc61..8a436b4ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 Bug #4432: Guards behaviour is incorrect if they do not have AI packages Bug #4433: Guard behaviour is incorrect with Alarm = 0 + Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts Feature #4345: Add equivalents for the command line commands to Launcher Feature #4444: Per-group KF-animation files support diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index 493defa5a..b50e78227 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -8,6 +8,9 @@ #include "../../model/world/idtablebase.hpp" #include "../../model/prefs/state.hpp" +#include "../world/tablebottombox.hpp" +#include "../world/creator.hpp" + #include "reporttable.hpp" #include "searchbox.hpp" @@ -73,6 +76,9 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc: layout->addWidget (mTable = new ReportTable (document, id, true), 2); + layout->addWidget (mBottom = + new CSVWorld::TableBottomBox (CSVWorld::NullCreatorFactory(), document, id, this), 0); + QWidget *widget = new QWidget; widget->setLayout (layout); @@ -93,6 +99,12 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc: this, SLOT (startSearch (const CSMTools::Search&))); connect (&mSearchBox, SIGNAL (replaceAll()), this, SLOT (replaceAllRequest())); + + connect (document.getReport (id), SIGNAL (rowsRemoved (const QModelIndex&, int, int)), + this, SLOT (tableSizeUpdate())); + + connect (document.getReport (id), SIGNAL (rowsInserted (const QModelIndex&, int, int)), + this, SLOT (tableSizeUpdate())); } void CSVTools::SearchSubView::setEditLock (bool locked) @@ -101,6 +113,11 @@ void CSVTools::SearchSubView::setEditLock (bool locked) mSearchBox.setEditLock (locked); } +void CSVTools::SearchSubView::setStatusBar (bool show) +{ + mBottom->setStatusBar(show); +} + void CSVTools::SearchSubView::stateChanged (int state, CSMDoc::Document *document) { mSearchBox.setSearchMode (!(state & CSMDoc::State_Searching)); @@ -126,3 +143,8 @@ void CSVTools::SearchSubView::replaceAllRequest() { replace (false); } + +void CSVTools::SearchSubView::tableSizeUpdate() +{ + mBottom->tableSizeChanged (mDocument.getReport (getUniversalId())->rowCount(), 0, 0); +} diff --git a/apps/opencs/view/tools/searchsubview.hpp b/apps/opencs/view/tools/searchsubview.hpp index ac0a5a762..d22367722 100644 --- a/apps/opencs/view/tools/searchsubview.hpp +++ b/apps/opencs/view/tools/searchsubview.hpp @@ -15,6 +15,11 @@ namespace CSMDoc class Document; } +namespace CSVWorld +{ + class TableBottomBox; +} + namespace CSVTools { class ReportTable; @@ -28,6 +33,7 @@ namespace CSVTools CSMDoc::Document& mDocument; CSMTools::Search mSearch; bool mLocked; + CSVWorld::TableBottomBox *mBottom; private: @@ -43,6 +49,8 @@ namespace CSVTools virtual void setEditLock (bool locked); + virtual void setStatusBar (bool show); + private slots: void stateChanged (int state, CSMDoc::Document *document); @@ -52,6 +60,8 @@ namespace CSVTools void replaceRequest(); void replaceAllRequest(); + + void tableSizeUpdate(); }; } From 9bd940e1535667faa87b1c2971c2672c9b10e894 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 13 Jun 2018 13:06:37 +0000 Subject: [PATCH 84/91] Update README.md to indicate that our gitlab pipeline is building --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a6d0cacd5..9af9ef976 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ OpenMW ====== -[![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/github/openmw/openmw?svg=true)](https://ci.appveyor.com/project/psi29a/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) +[![Build Status](https://api.travis-ci.org/OpenMW/openmw.svg)](https://travis-ci.org/OpenMW/openmw) [![Build status](https://ci.appveyor.com/api/projects/status/github/openmw/openmw?svg=true)](https://ci.appveyor.com/project/psi29a/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) [![pipeline status](https://gitlab.com/OpenMW/openmw/badges/master/pipeline.svg)](https://gitlab.com/OpenMW/openmw/commits/master) OpenMW is an open-source game engine that supports playing Morrowind by Bethesda Softworks. You need to own the game for OpenMW to play Morrowind. From 81b78a82e822941f35e73e531c89c975d1241536 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 1 Dec 2017 10:07:02 +0400 Subject: [PATCH 85/91] AI: try to open doors every AI_REACTION_TIME seconds (bug #4454) --- CHANGELOG.md | 1 + apps/openmw/mwclass/container.cpp | 15 +++--- apps/openmw/mwclass/door.cpp | 19 +++---- apps/openmw/mwmechanics/aipackage.cpp | 69 +++++++++++++------------- apps/openmw/mwmechanics/aipackage.hpp | 1 + apps/openmw/mwmechanics/obstacle.cpp | 21 +++++--- apps/openmw/mwmechanics/obstacle.hpp | 2 +- apps/openmw/mwworld/containerstore.cpp | 2 +- 8 files changed, 66 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cc66bc61..90a198bea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 Bug #4432: Guards behaviour is incorrect if they do not have AI packages Bug #4433: Guard behaviour is incorrect with Alarm = 0 + Bug #4454: AI opens doors too slow Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts Feature #4345: Add equivalents for the command line commands to Launcher Feature #4444: Per-group KF-animation files support diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index b6a46cff8..1d51a7830 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -139,24 +139,21 @@ namespace MWClass const std::string trapActivationSound = "Disarm Trap Fail"; MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - const MWWorld::InventoryStore& invStore = player.getClass().getInventoryStore(player); + MWWorld::InventoryStore& invStore = player.getClass().getInventoryStore(player); bool isLocked = ptr.getCellRef().getLockLevel() > 0; bool isTrapped = !ptr.getCellRef().getTrap().empty(); bool hasKey = false; std::string keyName; - // make key id lowercase - std::string keyId = ptr.getCellRef().getKey(); - Misc::StringUtils::lowerCaseInPlace(keyId); - for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it) + const std::string keyId = ptr.getCellRef().getKey(); + if (!keyId.empty()) { - std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::lowerCaseInPlace(refId); - if (refId == keyId) + MWWorld::Ptr keyPtr = invStore.search(keyId); + if (!keyPtr.isEmpty()) { hasKey = true; - keyName = it->getClass().getName(*it); + keyName = keyPtr.getClass().getName(keyPtr); } } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 903ec4958..eba87a47b 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -114,7 +114,7 @@ namespace MWClass const std::string lockedSound = "LockedDoor"; const std::string trapActivationSound = "Disarm Trap Fail"; - const MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); + MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); bool isLocked = ptr.getCellRef().getLockLevel() > 0; bool isTrapped = !ptr.getCellRef().getTrap().empty(); @@ -135,21 +135,14 @@ namespace MWClass animation->addSpellCastGlow(effect, 1); // 1 second glow to match the time taken for a door opening or closing } - // make key id lowercase - std::string keyId = ptr.getCellRef().getKey(); + const std::string keyId = ptr.getCellRef().getKey(); if (!keyId.empty()) { - Misc::StringUtils::lowerCaseInPlace(keyId); - for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it) + MWWorld::Ptr keyPtr = invStore.search(keyId); + if (!keyPtr.isEmpty()) { - std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::lowerCaseInPlace(refId); - if (refId == keyId) - { - hasKey = true; - keyName = it->getClass().getName(*it); - break; - } + hasKey = true; + keyName = keyPtr.getClass().getName(keyPtr); } } diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 6a0f5b013..e6cca0523 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -120,6 +120,9 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr if (!isDestReached && mTimer > AI_REACTION_TIME) { + if (actor.getClass().isBipedal(actor)) + openDoors(actor); + bool wasShortcutting = mIsShortcutting; bool destInLOS = false; @@ -209,41 +212,10 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur // first check if obstacle is a door static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); - MWWorld::Ptr door = getNearbyDoor(actor, distance); - if (door != MWWorld::Ptr() && actor.getClass().isBipedal(actor)) + const MWWorld::Ptr door = getNearbyDoor(actor, distance); + if (!door.isEmpty() && actor.getClass().isBipedal(actor)) { - // note: AiWander currently does not open doors - if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getClass().getDoorState(door) == 0) - { - if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 )) - { - MWBase::Environment::get().getWorld()->activate(door, actor); - return; - } - - std::string keyId = door.getCellRef().getKey(); - if (keyId.empty()) - return; - - bool hasKey = false; - const MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); - - // make key id lowercase - Misc::StringUtils::lowerCaseInPlace(keyId); - for (MWWorld::ConstContainerStoreIterator it = invStore.cbegin(); it != invStore.cend(); ++it) - { - std::string refId = it->getCellRef().getRefId(); - Misc::StringUtils::lowerCaseInPlace(refId); - if (refId == keyId) - { - hasKey = true; - break; - } - } - - if (hasKey) - MWBase::Environment::get().getWorld()->activate(door, actor); - } + openDoors(actor); } else { @@ -251,6 +223,35 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur } } +void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor) +{ + static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); + + const MWWorld::Ptr door = getNearbyDoor(actor, distance); + if (door == MWWorld::Ptr()) + return; + + // note: AiWander currently does not open doors + if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getClass().getDoorState(door) == 0) + { + if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 )) + { + MWBase::Environment::get().getWorld()->activate(door, actor); + return; + } + + const std::string keyId = door.getCellRef().getKey(); + if (keyId.empty()) + return; + + MWWorld::ContainerStore &invStore = actor.getClass().getContainerStore(actor); + MWWorld::Ptr keyPtr = invStore.search(keyId); + + if (!keyPtr.isEmpty()) + MWBase::Environment::get().getWorld()->activate(door, actor); + } +} + const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const MWWorld::CellStore *cell) { const ESM::CellId& id = cell->getCell()->getCellId(); diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 7e8f905ad..2b685accc 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -123,6 +123,7 @@ namespace MWMechanics virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell); void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); + void openDoors(const MWWorld::Ptr& actor); const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell); diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 3c6f14bfd..0635a5520 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -26,13 +26,13 @@ namespace MWMechanics bool proximityToDoor(const MWWorld::Ptr& actor, float minDist) { - if(getNearbyDoor(actor, minDist)!=MWWorld::Ptr()) - return true; - else + if(getNearbyDoor(actor, minDist).isEmpty()) return false; + else + return true; } - MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist) + const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist) { MWWorld::CellStore *cell = actor.getCell(); @@ -50,6 +50,16 @@ namespace MWMechanics const MWWorld::LiveCellRef& ref = *it; osg::Vec3f doorPos(ref.mData.getPosition().asVec3()); + + // FIXME: cast + const MWWorld::Ptr doorPtr = MWWorld::Ptr(&const_cast &>(ref), actor.getCell()); + + int doorState = doorPtr.getClass().getDoorState(doorPtr); + float doorRot = ref.mData.getPosition().rot[2] - doorPtr.getCellRef().getPosition().rot[2]; + + if (doorState != 0 || doorRot != 0) + continue; // the door is already opened/opening + doorPos.z() = 0; float angle = std::acos(actorDir * (doorPos - pos) / (actorDir.length() * (doorPos - pos).length())); @@ -62,8 +72,7 @@ namespace MWMechanics if ((pos - doorPos).length2() > minDist*minDist) continue; - // FIXME cast - return MWWorld::Ptr(&const_cast &>(ref), actor.getCell()); // found, stop searching + return doorPtr; // found, stop searching } return MWWorld::Ptr(); // none found diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index f71207346..6a84e0ef9 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -17,7 +17,7 @@ namespace MWMechanics /// Returns door pointer within range. No guarantee is given as to which one /** \return Pointer to the door, or NULL if none exists **/ - MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); + const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); class ObstacleCheck { diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index dd5d7a853..657d59c59 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -47,7 +47,7 @@ namespace for (typename MWWorld::CellRefList::List::iterator iter (list.mList.begin()); iter!=list.mList.end(); ++iter) { - if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2)) + if (Misc::StringUtils::ciEqual(iter->mBase->mId, id2) && iter->mData.getCount()) { MWWorld::Ptr ptr (&*iter, 0); ptr.setContainerStore (store); From 7ca56ccd291868c4ad11e7c1210252393d935221 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Wed, 13 Jun 2018 15:48:24 +0200 Subject: [PATCH 86/91] set search status bar to 'no results' message when search yields no results --- apps/opencs/model/doc/document.cpp | 7 ++++--- apps/opencs/model/doc/document.hpp | 4 +++- apps/opencs/view/tools/searchsubview.cpp | 13 +++++++++++++ apps/opencs/view/tools/searchsubview.hpp | 2 ++ apps/opencs/view/world/tablebottombox.cpp | 16 ++++++++++++++++ apps/opencs/view/world/tablebottombox.hpp | 3 +++ 6 files changed, 41 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 7a825ba39..e45d13aa9 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -320,12 +320,13 @@ CSMDoc::Document::Document (const Files::ConfigurationManager& configuration, connect (&mUndoStack, SIGNAL (cleanChanged (bool)), this, SLOT (modificationStateChanged (bool))); connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); - connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); + connect (&mTools, SIGNAL (done (int, bool)), this, SIGNAL (operationDone (int, bool))); + connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool))); connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)), this, SIGNAL (mergeDone (CSMDoc::Document*))); connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int))); - connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool))); + connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone2 (int, bool))); connect ( &mSaving, SIGNAL (reportMessage (const CSMDoc::Message&, int)), @@ -437,7 +438,7 @@ void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type) std::cout << message.mMessage << std::endl; } -void CSMDoc::Document::operationDone (int type, bool failed) +void CSMDoc::Document::operationDone2 (int type, bool failed) { if (type==CSMDoc::State_Saving && !failed) mDirty = false; diff --git a/apps/opencs/model/doc/document.hpp b/apps/opencs/model/doc/document.hpp index d31fd5aca..4c442428e 100644 --- a/apps/opencs/model/doc/document.hpp +++ b/apps/opencs/model/doc/document.hpp @@ -168,13 +168,15 @@ namespace CSMDoc /// document. This signal must be handled to avoid a leak. void mergeDone (CSMDoc::Document *document); + void operationDone (int type, bool failed); + private slots: void modificationStateChanged (bool clean); void reportMessage (const CSMDoc::Message& message, int type); - void operationDone (int type, bool failed); + void operationDone2 (int type, bool failed); void runStateChanged(); diff --git a/apps/opencs/view/tools/searchsubview.cpp b/apps/opencs/view/tools/searchsubview.cpp index b50e78227..9bada22af 100644 --- a/apps/opencs/view/tools/searchsubview.cpp +++ b/apps/opencs/view/tools/searchsubview.cpp @@ -3,6 +3,7 @@ #include #include "../../model/doc/document.hpp" +#include "../../model/doc/state.hpp" #include "../../model/tools/search.hpp" #include "../../model/tools/reportmodel.hpp" #include "../../model/world/idtablebase.hpp" @@ -105,6 +106,9 @@ CSVTools::SearchSubView::SearchSubView (const CSMWorld::UniversalId& id, CSMDoc: connect (document.getReport (id), SIGNAL (rowsInserted (const QModelIndex&, int, int)), this, SLOT (tableSizeUpdate())); + + connect (&document, SIGNAL (operationDone (int, bool)), + this, SLOT (operationDone (int, bool))); } void CSVTools::SearchSubView::setEditLock (bool locked) @@ -148,3 +152,12 @@ void CSVTools::SearchSubView::tableSizeUpdate() { mBottom->tableSizeChanged (mDocument.getReport (getUniversalId())->rowCount(), 0, 0); } + +void CSVTools::SearchSubView::operationDone (int type, bool failed) +{ + if (type==CSMDoc::State_Searching && !failed && + !mDocument.getReport (getUniversalId())->rowCount()) + { + mBottom->setStatusMessage ("No Results"); + } +} diff --git a/apps/opencs/view/tools/searchsubview.hpp b/apps/opencs/view/tools/searchsubview.hpp index d22367722..c0f3eac84 100644 --- a/apps/opencs/view/tools/searchsubview.hpp +++ b/apps/opencs/view/tools/searchsubview.hpp @@ -62,6 +62,8 @@ namespace CSVTools void replaceAllRequest(); void tableSizeUpdate(); + + void operationDone (int type, bool failed); }; } diff --git a/apps/opencs/view/world/tablebottombox.cpp b/apps/opencs/view/world/tablebottombox.cpp index cfde5c694..f6b060a8f 100644 --- a/apps/opencs/view/world/tablebottombox.cpp +++ b/apps/opencs/view/world/tablebottombox.cpp @@ -28,6 +28,12 @@ void CSVWorld::TableBottomBox::updateStatus() { if (mShowStatusBar) { + if (!mStatusMessage.isEmpty()) + { + mStatus->setText (mStatusMessage); + return; + } + static const char *sLabels[4] = { "record", "deleted", "touched", "selected" }; static const char *sLabelsPlural[4] = { "records", "deleted", "touched", "selected" }; @@ -178,10 +184,17 @@ void CSVWorld::TableBottomBox::currentWidgetChanged(int /*index*/) updateSize(); } +void CSVWorld::TableBottomBox::setStatusMessage (const QString& message) +{ + mStatusMessage = message; + updateStatus(); +} + void CSVWorld::TableBottomBox::selectionSizeChanged (int size) { if (mStatusCount[3]!=size) { + mStatusMessage = ""; mStatusCount[3] = size; updateStatus(); } @@ -210,7 +223,10 @@ void CSVWorld::TableBottomBox::tableSizeChanged (int size, int deleted, int modi } if (changed) + { + mStatusMessage = ""; updateStatus(); + } } void CSVWorld::TableBottomBox::positionChanged (int row, int column) diff --git a/apps/opencs/view/world/tablebottombox.hpp b/apps/opencs/view/world/tablebottombox.hpp index 5402c466e..baa68087b 100644 --- a/apps/opencs/view/world/tablebottombox.hpp +++ b/apps/opencs/view/world/tablebottombox.hpp @@ -39,6 +39,7 @@ namespace CSVWorld bool mHasPosition; int mRow; int mColumn; + QString mStatusMessage; private: @@ -73,6 +74,8 @@ namespace CSVWorld /// /// \note The BotomBox does not partake in the deletion of records. + void setStatusMessage (const QString& message); + signals: void requestFocus (const std::string& id); From 61c968d5507670f9e3fce874e8abd48f0c819a85 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jun 2018 12:56:58 +0400 Subject: [PATCH 87/91] Ignore broken items when search for replacement (bug #4453) --- apps/openmw/mwgui/quickkeysmenu.cpp | 88 ++++++++++++++++---------- apps/openmw/mwgui/quickkeysmenu.hpp | 1 + apps/openmw/mwmechanics/actors.cpp | 18 +----- apps/openmw/mwworld/containerstore.cpp | 24 +++++++ apps/openmw/mwworld/containerstore.hpp | 3 + 5 files changed, 85 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 2ce9d04e5..08192625f 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -81,6 +81,47 @@ namespace MWGui delete mMagicSelectionDialog; } + void QuickKeysMenu::onOpen() + { + WindowBase::onOpen(); + + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + + // Check if quick keys are still valid + for (int i=0; i<10; ++i) + { + ItemWidget* button = mQuickKeyButtons[i]; + int type = mAssigned[i]; + + switch (type) + { + case Type_Unassigned: + case Type_HandToHand: + case Type_Magic: + break; + case Type_Item: + case Type_MagicItem: + { + MWWorld::Ptr item = *button->getUserData(); + // Make sure the item is available and is not broken + if (item.getRefData().getCount() < 1 || + (item.getClass().hasItemHealth(item) && + item.getClass().getItemHealth(item) <= 0)) + { + // Try searching for a compatible replacement + std::string id = item.getCellRef().getRefId(); + + item = store.findReplacement(id); + button->setUserData(MWWorld::Ptr(item)); + break; + } + } + } + } + + } + void QuickKeysMenu::unassign(ItemWidget* key, int index) { key->clearUserStrings(); @@ -122,12 +163,10 @@ namespace MWGui assert(index != -1); mSelectedIndex = index; - { - // open assign dialog - if (!mAssignDialog) - mAssignDialog = new QuickKeysMenuAssign(this); - mAssignDialog->setVisible (true); - } + // open assign dialog + if (!mAssignDialog) + mAssignDialog = new QuickKeysMenuAssign(this); + mAssignDialog->setVisible (true); } void QuickKeysMenu::onOkButtonClicked (MyGUI::Widget *sender) @@ -296,21 +335,16 @@ namespace MWGui if (type == Type_Item || type == Type_MagicItem) { MWWorld::Ptr item = *button->getUserData(); - // make sure the item is available - if (item.getRefData ().getCount() < 1) + // Make sure the item is available and is not broken + if (item.getRefData().getCount() < 1 || + (item.getClass().hasItemHealth(item) && + item.getClass().getItemHealth(item) <= 0)) { // Try searching for a compatible replacement std::string id = item.getCellRef().getRefId(); - for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) - { - if (Misc::StringUtils::ciEqual(it->getCellRef().getRefId(), id)) - { - item = *it; - button->setUserData(MWWorld::Ptr(item)); - break; - } - } + item = store.findReplacement(id); + button->setUserData(MWWorld::Ptr(item)); if (item.getRefData().getCount() < 1) { @@ -498,6 +532,9 @@ namespace MWGui ESM::QuickKeys keys; keys.load(reader); + MWWorld::Ptr player = MWMechanics::getPlayer(); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + int i=0; for (std::vector::const_iterator it = keys.mKeys.begin(); it != keys.mKeys.end(); ++it) { @@ -519,22 +556,7 @@ namespace MWGui case Type_MagicItem: { // Find the item by id - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - MWWorld::Ptr item; - for (MWWorld::ContainerStoreIterator iter = store.begin(); iter != store.end(); ++iter) - { - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) - { - if (item.isEmpty() || - // Prefer the stack with the lowest remaining uses - !item.getClass().hasItemHealth(*iter) || - iter->getClass().getItemHealth(*iter) < item.getClass().getItemHealth(item)) - { - item = *iter; - } - } - } + MWWorld::Ptr item = store.findReplacement(id); if (item.isEmpty()) unassign(button, i); diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index 0070aa55b..b5bc60b19 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -34,6 +34,7 @@ namespace MWGui void onAssignMagicItem (MWWorld::Ptr item); void onAssignMagic (const std::string& spellId); void onAssignMagicCancel (); + void onOpen(); void activateQuickKey(int index); void updateActivatedQuickKey(); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index de394c446..f1bc6907c 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -288,23 +288,9 @@ namespace MWMechanics if (prevItemId.empty()) return; - // Find the item by id - MWWorld::Ptr item; - for (MWWorld::ContainerStoreIterator iter = store.begin(); iter != store.end(); ++iter) - { - if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), prevItemId)) - { - if (item.isEmpty() || - // Prefer the stack with the lowest remaining uses - !item.getClass().hasItemHealth(*iter) || - iter->getClass().getItemHealth(*iter) < item.getClass().getItemHealth(item)) - { - item = *iter; - } - } - } - + // Find previous item (or its replacement) by id. // we should equip previous item only if expired bound item was equipped. + MWWorld::Ptr item = store.findReplacement(prevItemId); if (item.isEmpty() || !wasEquipped) return; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index dd5d7a853..c92d51701 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -675,6 +675,30 @@ int MWWorld::ContainerStore::getType (const ConstPtr& ptr) "Object '" + ptr.getCellRef().getRefId() + "' of type " + ptr.getTypeName() + " can not be placed into a container"); } +MWWorld::Ptr MWWorld::ContainerStore::findReplacement(const std::string& id) +{ + MWWorld::Ptr item; + int itemHealth = 1; + for (MWWorld::ContainerStoreIterator iter = begin(); iter != end(); ++iter) + { + int iterHealth = iter->getClass().hasItemHealth(*iter) ? iter->getClass().getItemHealth(*iter) : 1; + if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), id)) + { + // Prefer the stack with the lowest remaining uses + // Try to get item with zero durability only if there are no other items found + if (item.isEmpty() || + (iterHealth > 0 && iterHealth < itemHealth) || + (itemHealth <= 0 && iterHealth > 0)) + { + item = *iter; + itemHealth = iterHealth; + } + } + } + + return item; +} + MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) { { diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index dbb82cbda..b67eb6552 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -198,6 +198,9 @@ namespace MWWorld ///< This function throws an exception, if ptr does not point to an object, that can be /// put into a container. + Ptr findReplacement(const std::string& id); + ///< Returns replacement for object with given id. Prefer used items (with low durability left). + Ptr search (const std::string& id); virtual void writeState (ESM::InventoryState& state) const; From 48d74a8781b8ba1f50fce9bc0eeb1cf09c34caa6 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 13 Jun 2018 15:34:13 +0000 Subject: [PATCH 88/91] Disable testing for now, not yet necessary. --- .gitlab-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 74bc74ca8..9f5442ae4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,8 +35,8 @@ build: paths: - "*.o" -# run tests using the binary built before -test: - stage: test - script: - - ls +# TODO: run tests using the binary built before +#test: +# stage: test +# script: +# - ls From e814843cdbf82d501f9fb64caa8a91d34f9adf30 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Jun 2018 22:28:34 +0400 Subject: [PATCH 89/91] Add missing changelog entries --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7871998a3..dfea001f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Bug #2222: Fatigue's effect on selling price is backwards Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped Bug #2835: Player able to slowly move when overencumbered + Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y Bug #3374: Touch spells not hitting kwama foragers Bug #3591: Angled hit distance too low Bug #3629: DB assassin attack never triggers creature spawning @@ -20,11 +21,14 @@ Bug #4293: Faction members are not aware of faction ownerships in barter Bug #4327: Missing animations during spell/weapon stance switching Bug #4368: Settings window ok button doesn't have key focus by default + Bug #4393: NPCs walk back to where they were after using ResetActors Bug #4419: MRK NiStringExtraData is handled incorrectly Bug #4426: RotateWorld behavior is incorrect Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 Bug #4432: Guards behaviour is incorrect if they do not have AI packages Bug #4433: Guard behaviour is incorrect with Alarm = 0 + Bug #4451: Script fails to compile when using "Begin, [ScriptName]" syntax + Bug #4453: Quick keys behaviour is invalid for equipment Bug #4454: AI opens doors too slow Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts From 5387e3ba8f5475a030cd98948441d39dbfd7f58c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 13 Jun 2018 21:15:22 +0200 Subject: [PATCH 90/91] Update CHANGELOG --- CHANGELOG.md | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac2a5472e..3a59aca8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,140 @@ +0.45.0 +------ + + Bug #1990: Sunrise/sunset not set correct + Bug #2222: Fatigue's effect on selling price is backwards + Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped + Bug #2835: Player able to slowly move when overencumbered + Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y + Bug #3374: Touch spells not hitting kwama foragers + Bug #3591: Angled hit distance too low + Bug #3629: DB assassin attack never triggers creature spawning + Bug #3876: Landscape texture painting is misaligned + Bug #3897: Have Goodbye give all choices the effects of Goodbye + Bug #3993: Terrain texture blending map is not upscaled + Bug #3997: Almalexia doesn't pace + Bug #4036: Weird behaviour of AI packages if package target has non-unique ID + Bug #4047: OpenMW not reporting its version number in MacOS; OpenMW-CS not doing it fully + Bug #4215: OpenMW shows book text after last
tag + Bug #4221: Characters get stuck in V-shaped terrain + Bug #4251: Stationary NPCs do not return to their position after combat + Bug #4293: Faction members are not aware of faction ownerships in barter + Bug #4327: Missing animations during spell/weapon stance switching + Bug #4368: Settings window ok button doesn't have key focus by default + Bug #4393: NPCs walk back to where they were after using ResetActors + Bug #4419: MRK NiStringExtraData is handled incorrectly + Bug #4426: RotateWorld behavior is incorrect + Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2 + Bug #4432: Guards behaviour is incorrect if they do not have AI packages + Bug #4433: Guard behaviour is incorrect with Alarm = 0 + Bug #4451: Script fails to compile when using "Begin, [ScriptName]" syntax + Bug #4453: Quick keys behaviour is invalid for equipment + Bug #4454: AI opens doors too slow + Feature #3276: Editor: Search- Show number of (remaining) search results and indicate a search without any results + Feature #4222: 360° screenshots + Feature #4324: Add CFBundleIdentifier in Info.plist to allow for macOS function key shortcuts + Feature #4345: Add equivalents for the command line commands to Launcher + Feature #4444: Per-group KF-animation files support + +0.44.0 +------ + + Bug #1428: Daedra summoning scripts aren't executed when the item is taken through the inventory + Bug #1987: Some glyphs are not supported + Bug #2254: Magic related visual effects are not rendered when loading a saved game + Bug #2485: Journal alphabetical index doesn't match "Morrowind content language" setting + Bug #2703: OnPCHitMe is not handled correctly + Bug #2829: Incorrect order for content list consisting of a game file and an esp without dependencies + Bug #2841: "Total eclipse" happens if weather settings are not defined. + Bug #2897: Editor: Rename "Original creature" field + Bug #3278: Editor: Unchecking "Auto Calc" flag changes certain values + Bug #3343: Editor: ID sorting is case-sensitive in certain tables + Bug #3557: Resource priority confusion when using the local data path as installation root + Bug #3587: Pathgrid and Flying Creatures wrong behaviour – abotWhereAreAllBirdsGoing + Bug #3603: SetPos should not skip weather transitions + Bug #3618: Myar Aranath total conversion can't be started due to capital-case extension of the master file + Bug #3638: Fast forwarding can move NPC inside objects + Bug #3664: Combat music does not start in dialogue + Bug #3696: Newlines are accompanied by empty rectangle glyph in dialogs + Bug #3708: Controllers broken on macOS + Bug #3726: Items with suppressed activation can be picked up via the inventory menu + Bug #3783: [Mod] Abot's Silt Striders 1.16 - silt strider "falls" to ground and glides on floor during travel + Bug #3863: Can be forced to not resist arrest if you cast Calm Humanoid on aggroed death warrant guards + Bug #3884: Incorrect enemy behavior when exhausted + Bug #3926: Installation Wizard places Morrowind.esm after Tribunal/Bloodmoon if it has a later file creation date + Bug #4061: Scripts error on special token included in name + Bug #4111: Crash when mouse over soulgem with a now-missing soul + Bug #4122: Swim animation should not be interrupted during underwater attack + Bug #4134: Battle music behaves different than vanilla + Bug #4135: Reflecting an absorb spell different from vanilla + Bug #4136: Enchanted weapons without "ignore normal weapons" flag don't bypass creature "ignore normal weapons" effect + Bug #4143: Antialiasing produces graphical artifacts when used with shader lighting + Bug #4159: NPCs' base skeleton files should not be optimized + Bug #4177: Jumping/landing animation interference/flickering + Bug #4179: NPCs do not face target + Bug #4180: Weapon switch sound playing even though no weapon is switched + Bug #4184: Guards can initiate dialogue even though you are far above them + Bug #4190: Enchanted clothes changes visibility with Chameleon on equip/unequip + Bug #4191: "screenshot saved" message also appears in the screenshot image + Bug #4192: Archers in OpenMW have shorter attack range than archers in Morrowind + Bug #4210: Some dialogue topics are not highlighted on first encounter + Bug #4211: FPS drops after minimizing the game during rainy weather + Bug #4216: Thrown weapon projectile doesn't rotate + Bug #4223: Displayed spell casting chance must be 0 if player doesn't have enough magicka to cast it + Bug #4225: Double "Activate" key presses with Mouse and Gamepad. + Bug #4226: The current player's class should be default value in the class select menu + Bug #4229: Tribunal/Bloodmoon summoned creatures fight other summons + Bug #4233: W and A keys override S and D Keys + Bug #4235: Wireframe mode affects local map + Bug #4239: Quick load from container screen causes crash + Bug #4242: Crime greetings display in Journal + Bug #4245: Merchant NPCs sell ingredients growing on potted plants they own + Bug #4246: Take armor condition into account when calcuting armor rating + Bug #4250: Jumping is not as fluid as it was pre-0.43.0 + Bug #4252: "Error in frame: FFmpeg exception: Failed to allocate input stream" message spam if OpenMW encounter non-music file in the Music folder + Bug #4261: Magic effects from eaten ingredients always have 1 sec duration + Bug #4263: Arrow position is incorrect in 3rd person view during attack for beast races + Bug #4264: Player in god mode can be affected by some negative spell effects + Bug #4269: Crash when hovering the faction section and the 'sAnd' GMST is missing (as in MW 1.0) + Bug #4272: Root note transformations are discarded again + Bug #4279: Sometimes cells are not marked as explored on the map + Bug #4298: Problem with MessageBox and chargen menu interaction order + Bug #4301: Optimizer breaks LOD nodes + Bug #4308: PlaceAtMe doesn't inherit scale of calling object + Bug #4309: Only harmful effects with resistance effect set are resistable + Bug #4313: Non-humanoid creatures are capable of opening doors + Bug #4314: Rainy weather slows down the game when changing from indoors/outdoors + Bug #4319: Collisions for certain meshes are incorrectly ignored + Bug #4320: Using mouse 1 to move forward causes selection dialogues to jump selections forward. + Bug #4322: NPC disposition: negative faction reaction modifier doesn't take PC rank into account + Bug #4328: Ownership by dead actors is not cleared from picked items + Bug #4334: Torch and shield usage inconsistent with original game + Bug #4336: Wizard: Incorrect Morrowind assets path autodetection + Bug #4343: Error message for coc and starting cell shouldn't imply that it only works for interior cells + Bug #4346: Count formatting does not work well with very high numbers + Bug #4351: Using AddSoulgem fills all soul gems of the specified type + Bug #4391: No visual indication is provided when an unavailable spell fails to be chosen via a quick key + Bug #4392: Inventory filter breaks after loading a game + Bug #4405: No default terrain in empty cells when distant terrain is enabled + Bug #4410: [Mod] Arktwend: OpenMW does not use default marker definitions + Bug #4412: openmw-iniimporter ignores data paths from config + Bug #4413: Moving with 0 strength uses all of your fatigue + Bug #4420: Camera flickering when I open up and close menus while sneaking + Bug #4435: Item health is considered a signed integer + Bug #4441: Adding items to currently disabled weapon-wielding creatures crashes the game + Feature #1786: Round up encumbrance value in the encumbrance bar + Feature #2694: Editor: rename "model" column to make its purpose clear + Feature #3870: Editor: Terrain Texture Brush Button + Feature #3872: Editor: Edit functions in terrain texture editing mode + Feature #4054: Launcher: Create menu for settings.cfg options + Feature #4064: Option for fast travel services to charge for the first companion + Feature #4142: Implement fWereWolfHealth GMST + Feature #4174: Multiple quicksaves + Feature #4407: Support NiLookAtController + Feature #4423: Rebalance soul gem values + Task #4015: Use AppVeyor build artifact features to make continuous builds available + Editor: New (and more complete) icon set + 0.43.0 ------ From 7178ee3a6e94b1001042a8bbbc292e023709f3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20=C4=8C=C3=AD=C5=BE?= Date: Wed, 13 Jun 2018 21:34:43 +0200 Subject: [PATCH 91/91] Add FIXME comment --- 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 1e8a12e2c..c440de455 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1057,7 +1057,7 @@ namespace MWInput if (MWBase::Environment::get().getWorld()->screenshot360(screenshot.get(),settingStr)) { (*mScreenCaptureOperation) (*(screenshot.get()),0); - // mScreenCaptureHandler->getCaptureOperation() causes crash for some reason + // FIXME: mScreenCaptureHandler->getCaptureOperation() causes crash for some reason screenshotTaken = true; } }