From 612c7f1a2fb24d9c44ff7c8321131c74ad69357e Mon Sep 17 00:00:00 2001 From: mrcheko Date: Fri, 19 Aug 2016 22:15:26 +0300 Subject: [PATCH 01/54] Revert "Revert "Merge pull request #993 from mrcheko/pathfinding"" This reverts commit 3732979eecb095a16e71180f52143d545c2c8ca5. --- apps/openmw/mwmechanics/aiactivate.cpp | 22 +- apps/openmw/mwmechanics/aicombat.cpp | 364 ++++----------------- apps/openmw/mwmechanics/aicombat.hpp | 9 +- apps/openmw/mwmechanics/aicombataction.cpp | 67 +++- apps/openmw/mwmechanics/aicombataction.hpp | 13 +- apps/openmw/mwmechanics/aifollow.cpp | 35 +- apps/openmw/mwmechanics/aipackage.cpp | 195 ++++++++--- apps/openmw/mwmechanics/aipackage.hpp | 26 +- apps/openmw/mwmechanics/aipursue.cpp | 9 +- apps/openmw/mwmechanics/aisequence.cpp | 1 + apps/openmw/mwmechanics/aiwander.cpp | 76 ++--- apps/openmw/mwmechanics/aiwander.hpp | 4 +- apps/openmw/mwmechanics/obstacle.cpp | 5 + apps/openmw/mwmechanics/obstacle.hpp | 1 + apps/openmw/mwmechanics/pathfinding.cpp | 71 +++- apps/openmw/mwmechanics/pathfinding.hpp | 26 +- 16 files changed, 436 insertions(+), 488 deletions(-) diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index a79adbc8b..745a01c8b 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -23,33 +23,29 @@ namespace MWMechanics return new AiActivate(*this); } - bool AiActivate::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) + bool AiActivate::execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { - ESM::Position pos = actor.getRefData().getPosition(); //position of the actor const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); - if(target == MWWorld::Ptr() || - !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered + if (target == MWWorld::Ptr() || + !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should check whether the target is currently registered // with the MechanicsManager - ) - return true; //Target doesn't exist + ) + return true; //Target doesn't exist - //Set the target desition from the actor + //Set the target destination for the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < MWBase::Environment::get().getWorld()->getMaxActivationDistance()) { //Stop when you get in activation range - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + if (pathTo(actor, dest, duration, MWBase::Environment::get().getWorld()->getMaxActivationDistance())) //Stop when you get in activation range + { + // activate when reached MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); - MWBase::Environment::get().getWorld()->activate(target, actor); return true; } - else { - pathTo(actor, dest, duration); //Go to the destination - } return false; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index ce66b045a..f5c64d4ab 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -27,46 +27,6 @@ namespace osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength); - - float getZAngleToDir(const osg::Vec3f& dir) - { - return std::atan2(dir.x(), dir.y()); - } - - float getXAngleToDir(const osg::Vec3f& dir) - { - return -std::asin(dir.z() / dir.length()); - } - - const float REACTION_INTERVAL = 0.25f; - - const float PATHFIND_Z_REACH = 50.0f; - // distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid - const float PATHFIND_CAUTION_DIST = 500.0f; - // distance after which actor (failed previously to shortcut) will try again - const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; - - // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; - // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH - bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY) - { - if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z() - to.z()) <= PATHFIND_Z_REACH) - { - osg::Vec3f dir = to - from; - dir.z() = 0; - dir.normalize(); - float verticalOffset = 200; // instead of '200' here we want the height of the actor - osg::Vec3f _from = from + dir*offsetXY + osg::Vec3f(0,0,1) * verticalOffset; - - // cast up-down ray and find height in world space of hit - float h = _from.z() - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, osg::Vec3f(0,0,-1), verticalOffset + PATHFIND_Z_REACH + 1); - - if(std::abs(from.z() - h) <= PATHFIND_Z_REACH) - return true; - } - - return false; - } } namespace MWMechanics @@ -80,7 +40,7 @@ namespace MWMechanics float mTimerCombatMove; bool mReadyToAttack; bool mAttack; - bool mFollowTarget; + float mAttackRange; bool mCombatMove; osg::Vec3f mLastTargetPos; const MWWorld::CellStore* mCell; @@ -89,16 +49,15 @@ namespace MWMechanics float mStrength; bool mForceNoShortcut; ESM::Position mShortcutFailPos; - osg::Vec3f mLastActorPos; MWMechanics::Movement mMovement; AiCombatStorage(): mAttackCooldown(0), - mTimerReact(0), + mTimerReact(AI_REACTION_TIME), mTimerCombatMove(0), mReadyToAttack(false), mAttack(false), - mFollowTarget(false), + mAttackRange(0), mCombatMove(false), mLastTargetPos(0,0,0), mCell(NULL), @@ -107,8 +66,8 @@ namespace MWMechanics mStrength(), mForceNoShortcut(false), mShortcutFailPos(), - mLastActorPos(0,0,0), - mMovement(){} + mMovement() + {} void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack); void updateCombatMove(float duration); @@ -179,6 +138,7 @@ namespace MWMechanics * Use the Observer Pattern to co-ordinate attacks, provide intelligence on * whether the target was hit, etc. */ + bool AiCombat::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) { // get or create temporary storage @@ -197,34 +157,38 @@ namespace MWMechanics || target.getClass().getCreatureStats(target).isDead()) return true; - //Update every frame + if (storage.mCurrentAction.get()) // need to wait to init action with it's attack range + { + //Update every frame + bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, storage.mAttackRange); + if (is_target_reached) storage.mReadyToAttack = true; + } + storage.updateCombatMove(duration); - updateActorsMovement(actor, duration, storage.mMovement); + if (storage.mReadyToAttack) updateActorsMovement(actor, duration, storage); storage.updateAttack(characterController); storage.mActionCooldown -= duration; - + float& timerReact = storage.mTimerReact; - if(timerReact < REACTION_INTERVAL) + if (timerReact < AI_REACTION_TIME) { timerReact += duration; - return false; } else { timerReact = 0; - return reactionTimeActions(actor, characterController, storage, target); + attack(actor, target, storage, characterController); } + + return false; } - bool AiCombat::reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, - AiCombatStorage& storage, MWWorld::Ptr target) + void AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController) { - MWMechanics::Movement& movement = storage.mMovement; - if (isTargetMagicallyHidden(target)) { storage.stopAttack(); - return false; // TODO: run away instead of doing nothing + return; // TODO: run away instead of doing nothing } const MWWorld::CellStore*& currentCell = storage.mCell; @@ -239,10 +203,9 @@ namespace MWMechanics float& actionCooldown = storage.mActionCooldown; if (actionCooldown > 0) - return false; + return; - float rangeAttack = 0; - float rangeFollow = 0; + float &rangeAttack = storage.mAttackRange; boost::shared_ptr& currentAction = storage.mCurrentAction; if (characterController.readyToPrepareAttack()) { @@ -250,253 +213,67 @@ namespace MWMechanics actionCooldown = currentAction->getActionCooldown(); } - if (currentAction.get()) - currentAction->getCombatRange(rangeAttack, rangeFollow); - - // FIXME: consider moving this stuff to ActionWeapon::getCombatRange const ESM::Weapon *weapon = NULL; - MWMechanics::WeaponType weaptype = WeapType_None; - float weapRange = 1.0f; - - // Get weapon characteristics - MWBase::World* world = MWBase::Environment::get().getWorld(); - static const float fCombatDistance = world->getStore().get().find("fCombatDistance")->getFloat(); - if (actorClass.hasInventoryStore(actor)) - { - //Get weapon range - MWWorld::ContainerStoreIterator weaponSlot = - MWMechanics::getActiveWeapon(actorClass.getCreatureStats(actor), actorClass.getInventoryStore(actor), &weaptype); - - if (weaptype == WeapType_HandToHand) - { - static float fHandToHandReach = - world->getStore().get().find("fHandToHandReach")->getFloat(); - weapRange = fHandToHandReach; - } - else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell && weaptype != WeapType_None) - { - // All other WeapTypes are actually weapons, so get is safe. - weapon = weaponSlot->get()->mBase; - weapRange = weapon->mData.mReach; - } - weapRange *= fCombatDistance; - } - else //is creature - { - weaptype = actorClass.getCreatureStats(actor).getDrawState() == DrawState_Spell ? WeapType_Spell : WeapType_HandToHand; - weapRange = fCombatDistance; - } - - bool distantCombat = false; - if (weaptype != WeapType_Spell) - { - // TODO: move to ActionWeapon - if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) - { - rangeAttack = 1000; - rangeFollow = 0; // not needed in ranged combat - distantCombat = true; - } - else - { - rangeAttack = weapRange; - rangeFollow = 300; - } - } - else + bool isRangedCombat = false; + if (currentAction.get()) { - distantCombat = (rangeAttack > 500); + rangeAttack = currentAction->getCombatRange(isRangedCombat); + // Get weapon characteristics + weapon = currentAction->getWeapon(); } - - bool& readyToAttack = storage.mReadyToAttack; - // start new attack - storage.startAttackIfReady(actor, characterController, weapon, distantCombat); - - /* - * Some notes on meanings of variables: - * - * rangeAttack: - * - * - Distance where attack using the actor's weapon is possible: - * longer for ranged weapons (obviously?) vs. melee weapons - * - Determined by weapon's reach parameter; hardcoded value - * for ranged weapon and for creatures - * - Once within this distance mFollowTarget is triggered - * - * rangeFollow: - * - * - Applies to melee weapons or hand to hand only (or creatures without - * weapons) - * - Distance a little further away than the actor's weapon reach - * i.e. rangeFollow > rangeAttack for melee weapons - * - Hardcoded value (0 for ranged weapons) - * - Once the target gets beyond this distance mFollowTarget is cleared - * and a path to the target needs to be found - * - * mFollowTarget: - * - * - Once triggered, the actor follows the target with LOS shortcut - * (the shortcut really only applies to cells where pathgrids are - * available, since the default path without pathgrids is direct to - * target even if LOS is not achieved) - */ - ESM::Position pos = actor.getRefData().getPosition(); osg::Vec3f vActorPos(pos.asVec3()); osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3()); osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target); float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target); - - osg::Vec3f& lastActorPos = storage.mLastActorPos; - bool& followTarget = storage.mFollowTarget; - - bool isStuck = false; - float speed = 0.0f; - if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * REACTION_INTERVAL / 2) - isStuck = true; - - lastActorPos = vActorPos; - - // check if actor can move along z-axis - bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor)) - || world->isFlying(actor); + storage.mReadyToAttack = (distToTarget <= rangeAttack); + // can't fight if attacker can't go where target is. E.g. A fish can't attack person on land. - if (distToTarget >= rangeAttack + if (distToTarget > rangeAttack && !actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target)) { // TODO: start fleeing? storage.stopAttack(); - return false; + return; } - // for distant combat we should know if target is in LOS even if distToTarget < rangeAttack - bool inLOS = distantCombat ? world->getLOS(actor, target) : true; - - // (within attack dist) || (not quite attack dist while following) - if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && followTarget && !isStuck))) + if (storage.mReadyToAttack) { - mPathFinder.clearPath(); - //Melee and Close-up combat - - // getXAngleToDir determines vertical angle to target: - // if actor can move along z-axis it will control movement dir - // if can't - it will control correct aiming. - // note: in getZAngleToDir if we preserve dir.z then horizontal angle can be inaccurate - if (distantCombat) + storage.startCombatMove(actorClass.isNpc(), isRangedCombat, distToTarget, rangeAttack); + // start new attack + storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); + + if (isRangedCombat) { + // rotate actor taking into account target movement direction and projectile speed osg::Vec3f& lastTargetPos = storage.mLastTargetPos; - vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, REACTION_INTERVAL, weaptype, - storage.mStrength); + vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength); lastTargetPos = vTargetPos; - movement.mRotation[0] = getXAngleToDir(vAimDir); - movement.mRotation[2] = getZAngleToDir(vAimDir); - } - else - { - movement.mRotation[0] = getXAngleToDir(vAimDir); - movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated - } - // (not quite attack dist while following) - if (followTarget && distToTarget > rangeAttack) - { - //Close-up combat: just run up on target - storage.stopCombatMove(); - movement.mPosition[1] = 1; + storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); + storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir); } - else // (within attack dist) - { - storage.startCombatMove(actorClass.isNpc(), distantCombat, distToTarget, rangeAttack); - - readyToAttack = true; - //only once got in melee combat, actor is allowed to use close-up shortcutting - followTarget = true; - } - } - else // remote pathfinding - { - bool preferShortcut = false; - if (!distantCombat) inLOS = world->getLOS(actor, target); - - // check if shortcut is available - bool& forceNoShortcut = storage.mForceNoShortcut; - ESM::Position& shortcutFailPos = storage.mShortcutFailPos; - - if(inLOS && (!isStuck || readyToAttack) - && (!forceNoShortcut || (shortcutFailPos.asVec3() - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) - { - if(speed == 0.0f) speed = actorClass.getSpeed(actor); - // maximum dist before pit/obstacle for actor to avoid them depending on his speed - float maxAvoidDist = REACTION_INTERVAL * speed + speed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability - preferShortcut = checkWayIsClear(vActorPos, vTargetPos, osg::Vec3f(vAimDir.x(), vAimDir.y(), 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2); - } - - // don't use pathgrid when actor can move in 3 dimensions - if (canMoveByZ) - { - preferShortcut = true; - movement.mRotation[0] = getXAngleToDir(vAimDir); - } - - if(preferShortcut) - { - movement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); - forceNoShortcut = false; - shortcutFailPos.pos[0] = shortcutFailPos.pos[1] = shortcutFailPos.pos[2] = 0; - mPathFinder.clearPath(); - } - else // if shortcut failed stick to path grid - { - if(!isStuck && shortcutFailPos.pos[0] == 0.0f && shortcutFailPos.pos[1] == 0.0f && shortcutFailPos.pos[2] == 0.0f) - { - forceNoShortcut = true; - shortcutFailPos = pos; - } - - followTarget = false; - - buildNewPath(actor, target); - - // should always return a path (even if it's just go straight on target.) - assert(mPathFinder.isPathConstructed()); - } - - if (readyToAttack) + else { - // to stop possible sideway moving after target moved out of attack range - storage.stopCombatMove(); - readyToAttack = false; + storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir); + storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated } - movement.mPosition[1] = 1; } - - return false; } - void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, MWMechanics::Movement& desiredMovement) + void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage) { + // apply combat movement MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor); - if (mPathFinder.isPathConstructed()) - { - const ESM::Position& pos = actor.getRefData().getPosition(); - if (mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) - { - actorMovementSettings.mPosition[1] = 0; - } - else - { - evadeObstacles(actor, duration, pos); - } - } - else - { - actorMovementSettings = desiredMovement; - rotateActorOnAxis(actor, 2, actorMovementSettings, desiredMovement); - rotateActorOnAxis(actor, 0, actorMovementSettings, desiredMovement); - } + actorMovementSettings.mPosition[0] = storage.mMovement.mPosition[0]; + actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1]; + actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2]; + + rotateActorOnAxis(actor, 2, actorMovementSettings, storage.mMovement); + rotateActorOnAxis(actor, 0, actorMovementSettings, storage.mMovement); } void AiCombat::rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, @@ -514,35 +291,6 @@ namespace MWMechanics } } - bool AiCombat::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) - { - if (!mPathFinder.getPath().empty()) - { - osg::Vec3f currPathTarget(PathFinder::MakeOsgVec3(mPathFinder.getPath().back())); - osg::Vec3f newPathTarget = PathFinder::MakeOsgVec3(dest); - float dist = (newPathTarget - currPathTarget).length(); - float targetPosThreshold = (cell->isExterior()) ? 300.0f : 100.0f; - return dist > targetPosThreshold; - } - else - { - // necessarily construct a new path - return true; - } - } - - void AiCombat::buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target) - { - ESM::Pathgrid::Point newPathTarget = PathFinder::MakePathgridPoint(target.getRefData().getPosition()); - - //construct new path only if target has moved away more than on [targetPosThreshold] - if (doesPathNeedRecalc(newPathTarget, actor.getCell()->getCell())) - { - ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actor.getRefData().getPosition())); - mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell(), false); - } - } - int AiCombat::getTypeId() const { return TypeIdCombat; @@ -582,13 +330,13 @@ namespace MWMechanics mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); mCombatMove = true; } - // only NPCs are smart enough to use dodge movements + // dodge movements (for NPCs only) else if (isNpc && (!isDistantCombat || (distToTarget < rangeAttack / 2))) { //apply sideway movement (kind of dodging) with some probability if (Misc::Rng::rollClosedProbability() < 0.25) { - mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; + mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability(); mCombatMove = true; } @@ -651,7 +399,7 @@ namespace MWMechanics mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9); } else - mAttackCooldown -= REACTION_INTERVAL; + mAttackCooldown -= AI_REACTION_TIME; } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 1cfac5806..4be2ac9da 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -55,19 +55,14 @@ namespace MWMechanics virtual bool canCancel() const { return false; } virtual bool shouldCancelPreviousAi() const { return false; } - protected: - virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); - private: int mTargetActorId; - void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); - bool reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController, - AiCombatStorage& storage, MWWorld::Ptr target); + void attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController); /// Transfer desired movement (from AiCombatStorage) to Actor - void updateActorsMovement(const MWWorld::Ptr& actor, float duration, MWMechanics::Movement& movement); + void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage); void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis, MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement); }; diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 39c11c678..a70410035 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -40,23 +40,21 @@ int getRangeTypes (const ESM::EffectList& effects) return types; } -void suggestCombatRange(int rangeTypes, float& rangeAttack, float& rangeFollow) +float suggestCombatRange(int rangeTypes) { if (rangeTypes & Touch) { - rangeAttack = 100.f; - rangeFollow = 300.f; + static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->getFloat(); + return fCombatDistance; } else if (rangeTypes & Target) { - rangeAttack = 1000.f; - rangeFollow = 0.f; + return 1000.f; } else { // For Self spells, distance doesn't matter, so back away slightly to avoid enemy hits - rangeAttack = 600.f; - rangeFollow = 0.f; + return 600.f; } } @@ -427,11 +425,13 @@ namespace MWMechanics } } - void ActionSpell::getCombatRange(float& rangeAttack, float& rangeFollow) + float ActionSpell::getCombatRange (bool& isRanged) const { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); int types = getRangeTypes(spell->mEffects); - suggestCombatRange(types, rangeAttack, rangeFollow); + + isRanged = (types & Target); + return suggestCombatRange(types); } void ActionEnchantedItem::prepare(const MWWorld::Ptr &actor) @@ -441,18 +441,17 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Spell); } - void ActionEnchantedItem::getCombatRange(float& rangeAttack, float& rangeFollow) + float ActionEnchantedItem::getCombatRange(bool& isRanged) const { const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(mItem->getClass().getEnchantment(*mItem)); int types = getRangeTypes(enchantment->mEffects); - suggestCombatRange(types, rangeAttack, rangeFollow); + return suggestCombatRange(types); } - void ActionPotion::getCombatRange(float& rangeAttack, float& rangeFollow) + float ActionPotion::getCombatRange(bool& isRanged) const { // distance doesn't matter, so back away slightly to avoid enemy hits - rangeAttack = 600.f; - rangeFollow = 0.f; + return 600.f; } void ActionPotion::prepare(const MWWorld::Ptr &actor) @@ -463,6 +462,8 @@ namespace MWMechanics void ActionWeapon::prepare(const MWWorld::Ptr &actor) { + mIsNpc = actor.getClass().isNpc(); + if (actor.getClass().hasInventoryStore(actor)) { if (mWeapon.isEmpty()) @@ -482,9 +483,43 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Weapon); } - void ActionWeapon::getCombatRange(float& rangeAttack, float& rangeFollow) + float ActionWeapon::getCombatRange(bool& isRanged) const + { + isRanged = false; + + static const float fCombatDistance = MWBase::Environment::get().getWorld()->getStore().get().find("fCombatDistance")->getFloat(); + + if (mWeapon.isEmpty()) + { + if (!mIsNpc) + { + return fCombatDistance; + } + else + { + static float fHandToHandReach = + MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->getFloat(); + + return fHandToHandReach * fCombatDistance; + } + } + + const ESM::Weapon* weapon = mWeapon.get()->mBase; + + if (weapon->mData.mType >= ESM::Weapon::MarksmanBow) + { + isRanged = true; + return 1000.f; + } + else + return weapon->mData.mReach * fCombatDistance; + } + + const ESM::Weapon* ActionWeapon::getWeapon() const { - // Already done in AiCombat itself + if (mWeapon.isEmpty()) + return NULL; + return mWeapon.get()->mBase; } boost::shared_ptr prepareNextAction(const MWWorld::Ptr &actor, const MWWorld::Ptr &enemy) diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index bc635ceb2..e4ce44346 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -16,8 +16,9 @@ namespace MWMechanics public: virtual ~Action() {} virtual void prepare(const MWWorld::Ptr& actor) = 0; - virtual void getCombatRange (float& rangeAttack, float& rangeFollow) = 0; + virtual float getCombatRange (bool& isRanged) const = 0; virtual float getActionCooldown() { return 0.f; } + virtual const ESM::Weapon* getWeapon() const { return NULL; }; }; class ActionSpell : public Action @@ -28,7 +29,7 @@ namespace MWMechanics /// Sets the given spell as selected on the actor's spell list. virtual void prepare(const MWWorld::Ptr& actor); - virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + virtual float getCombatRange (bool& isRanged) const; }; class ActionEnchantedItem : public Action @@ -38,7 +39,7 @@ namespace MWMechanics MWWorld::ContainerStoreIterator mItem; /// Sets the given item as selected enchanted item in the actor's InventoryStore. virtual void prepare(const MWWorld::Ptr& actor); - virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + virtual float getCombatRange (bool& isRanged) const; /// Since this action has no animation, apply a small cool down for using it virtual float getActionCooldown() { return 1.f; } @@ -51,7 +52,7 @@ namespace MWMechanics MWWorld::Ptr mPotion; /// Drinks the given potion. virtual void prepare(const MWWorld::Ptr& actor); - virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + virtual float getCombatRange (bool& isRanged) const; /// Since this action has no animation, apply a small cool down for using it virtual float getActionCooldown() { return 1.f; } @@ -62,6 +63,7 @@ namespace MWMechanics private: MWWorld::Ptr mAmmunition; MWWorld::Ptr mWeapon; + bool mIsNpc; public: /// \a weapon may be empty for hand-to-hand combat @@ -69,7 +71,8 @@ namespace MWMechanics : mAmmunition(ammo), mWeapon(weapon) {} /// Equips the given weapon. virtual void prepare(const MWWorld::Ptr& actor); - virtual void getCombatRange (float& rangeAttack, float& rangeFollow); + virtual float getCombatRange (bool& isRanged) const; + virtual const ESM::Weapon* getWeapon() const; }; float rateSpell (const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy); diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 1b843f850..196498bad 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -137,35 +137,24 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]); - - if (storage.mMoving) //Stop when you get close - storage.mMoving = (dist > followDistance); - else + if (!storage.mMoving) { - const float threshold = 10; - storage.mMoving = (dist > followDistance + threshold); + const float threshold = 10; // to avoid constant switching between moving/stopping + followDistance += threshold; } - if(!storage.mMoving) - { - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + storage.mMoving = !pathTo(actor, dest, duration, followDistance); // Go to the destination - // turn towards target anyway - float directionX = target.getRefData().getPosition().pos[0] - actor.getRefData().getPosition().pos[0]; - float directionY = target.getRefData().getPosition().pos[1] - actor.getRefData().getPosition().pos[1]; - zTurn(actor, std::atan2(directionX,directionY), osg::DegreesToRadians(5.f)); - } - else + if (storage.mMoving) { - pathTo(actor, dest, duration); //Go to the destination - } + //Check if you're far away + float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]); - //Check if you're far away - if(dist > 450) - actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run - else if(dist < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold - actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk + if (dist > 450) + actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run + else if (dist < 325) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold + actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk + } return false; } diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 34cf9b921..1131b5e6f 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -21,6 +21,13 @@ MWMechanics::AiPackage::~AiPackage() {} +MWMechanics::AiPackage::AiPackage() : + mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild + mIsShortcutting(false), + mShortcutProhibited(false), mShortcutFailPos() +{ +} + MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { return MWWorld::Ptr(); @@ -51,14 +58,20 @@ bool MWMechanics::AiPackage::getRepeat() const return false; } -MWMechanics::AiPackage::AiPackage() : mTimer(0.26f) { //mTimer starts at .26 to force initial pathbuild +void MWMechanics::AiPackage::reset() +{ + // reset all members + mTimer = AI_REACTION_TIME + 1.0f; + mIsShortcutting = false; + mShortcutProhibited = false; + mShortcutFailPos = ESM::Pathgrid::Point(); + mPathFinder.clearPath(); + mObstacleCheck.clear(); } - -bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration) +bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance) { - //Update various Timers mTimer += duration; //Update timer ESM::Position pos = actor.getRefData().getPosition(); //position of the actor @@ -73,42 +86,72 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po return false; } - //*********************** - /// Checks if you can't get to the end position at all, adds end position to end of path - /// Rebuilds path every quarter of a second, in case the target has moved - //*********************** - if(mTimer > 0.25) + // handle path building and shortcutting + ESM::Pathgrid::Point start = pos.pos; + + float distToTarget = distance(start, dest); + bool isDestReached = (distToTarget <= destTolerance); + + if (!isDestReached && mTimer > AI_REACTION_TIME) { - const ESM::Cell *cell = actor.getCell()->getCell(); - if (doesPathNeedRecalc(dest, cell)) { //Only rebuild path if it's moved - mPathFinder.buildSyncedPath(pos.pos, dest, actor.getCell(), true); //Rebuild path, in case the target has moved - mPrevDest = dest; - } + bool wasShortcutting = mIsShortcutting; + bool destInLOS = false; + if (getTypeId() != TypeIdWander) // prohibit shortcuts for AiWander + mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS); // try to shortcut first - if(!mPathFinder.getPath().empty()) //Path has points in it + if (!mIsShortcutting) { - ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path + if (wasShortcutting || doesPathNeedRecalc(dest)) // if need to rebuild path + { + mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + + // give priority to go directly on target if there is minimal opportunity + if (destInLOS && mPathFinder.getPath().size() > 1) + { + // get point just before dest + std::list::const_iterator pPointBeforeDest = mPathFinder.getPath().end(); + --pPointBeforeDest; + --pPointBeforeDest; + + // if start point is closer to the target then last point of path (excluding target itself) then go straight on the target + if (distance(start, dest) <= distance(dest, *pPointBeforeDest)) + { + mPathFinder.clearPath(); + mPathFinder.addPointToPath(dest); + } + } + } - if(distance(dest, lastPos) > 100) //End of the path is far from the destination - mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go + if (!mPathFinder.getPath().empty()) //Path has points in it + { + ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path + + if(distance(dest, lastPos) > 100) //End of the path is far from the destination + mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go + } } mTimer = 0; } - //************************ - /// Checks if you aren't moving; attempts to unstick you - //************************ - if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? + if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) // if path is finished { - // Reset mTimer so that path will be built right away when a package is repeated - mTimer = 0.26f; + // turn to destination point + zTurn(actor, getZAngleToPoint(start, dest)); + smoothTurn(actor, getXAngleToPoint(start, dest), 0); return true; } else { + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // run to target + // handle obstacles on the way evadeObstacles(actor, duration, pos); } + + // turn to next path point by X,Z axes + zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); + smoothTurn(actor, mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2]), 0); + return false; } @@ -117,30 +160,106 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); - if (mObstacleCheck.check(actor, duration)) + + // check if stuck due to obstacles + if (!mObstacleCheck.check(actor, duration)) return; + + // first check if obstacle is a door + MWWorld::Ptr door = getNearbyDoor(actor); // NOTE: checks interior cells only + if (door != MWWorld::Ptr()) { - // first check if we're walking into a door - MWWorld::Ptr door = getNearbyDoor(actor); - if (door != MWWorld::Ptr()) // NOTE: checks interior cells only + // note: AiWander currently does not open doors + if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() + && door.getCellRef().getLockLevel() <= 0 && door.getClass().getDoorState(door) == 0) { - if (!door.getCellRef().getTeleport() && door.getCellRef().getTrap().empty() - && door.getCellRef().getLockLevel() <= 0 && door.getClass().getDoorState(door) == 0) { - MWBase::Environment::get().getWorld()->activateDoor(door, 1); - } + MWBase::Environment::get().getWorld()->activateDoor(door, 1); } - else // probably walking into another NPC + } + else // any other obstacle (NPC, crate, etc.) + { + mObstacleCheck.takeEvasiveAction(movement); + } +} + +bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS) +{ + const MWWorld::Class& actorClass = actor.getClass(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + // check if actor can move along z-axis + bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || world->isFlying(actor); + + // don't use pathgrid when actor can move in 3 dimensions + bool isPathClear = actorCanMoveByZ; + + if (!isPathClear + && (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) + { + // check if target is clearly visible + isPathClear = !MWBase::Environment::get().getWorld()->castRay( + static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), + static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ)); + + if (destInLOS != NULL) *destInLOS = isPathClear; + + if (!isPathClear) + return false; + + // check if an actor can move along the shortcut path + isPathClear = checkWayIsClearForActor(startPoint, endPoint, actor); + } + + if (isPathClear) // can shortcut the path + { + mPathFinder.clearPath(); + mPathFinder.addPointToPath(endPoint); + return true; + } + + return false; +} + +bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor) +{ + bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || MWBase::Environment::get().getWorld()->isFlying(actor); + + if (actorCanMoveByZ) + return true; + + float actorSpeed = actor.getClass().getSpeed(actor); + float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability + osg::Vec3f::value_type distToTarget = osg::Vec3f(static_cast(endPoint.mX), static_cast(endPoint.mY), 0).length(); + + float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2; + + bool isClear = checkWayIsClear(PathFinder::MakeOsgVec3(startPoint), PathFinder::MakeOsgVec3(endPoint), offsetXY); + + // update shortcut prohibit state + if (isClear) + { + if (mShortcutProhibited) { - mObstacleCheck.takeEvasiveAction(movement); + mShortcutProhibited = false; + mShortcutFailPos = ESM::Pathgrid::Point(); } } - else { //Not stuck, so reset things - movement.mPosition[1] = 1; //Just run forward + if (!isClear) + { + if (mShortcutFailPos.mX == 0 && mShortcutFailPos.mY == 0 && mShortcutFailPos.mZ == 0) + { + mShortcutProhibited = true; + mShortcutFailPos = startPoint; + } } + + return isClear; } -bool MWMechanics::AiPackage::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) +bool MWMechanics::AiPackage::doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest) { - return mPathFinder.getPath().empty() || (distance(mPrevDest, dest) > 10); + return mPathFinder.getPath().empty() || (distance(mPathFinder.getPath().back(), newDest) > 10); } bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 637d4f066..4feb13fe0 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -24,6 +24,7 @@ namespace ESM namespace MWMechanics { + const float AI_REACTION_TIME = 0.25f; class CharacterController; @@ -91,14 +92,26 @@ namespace MWMechanics /// Return true if this package should repeat. Currently only used for Wander packages. virtual bool getRepeat() const; + /// Reset pathfinding state + void reset(); + bool isTargetMagicallyHidden(const MWWorld::Ptr& target); protected: - /// Causes the actor to attempt to walk to the specified location + /// Handles path building and shortcutting with obstacles avoiding /** \return If the actor has arrived at his destination **/ - bool pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration); + bool pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance = 0.0f); + + /// Check if there aren't any obstacles along the path to make shortcut possible + /// If a shortcut is possible then path will be cleared and filled with the destination point. + /// \param destInLOS If not NULL function will return ray cast check result + /// \return If can shortcut the path + bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS); + + /// Check if the way to the destination is clear, taking into account actor speed + bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); - virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); + virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest); void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); @@ -108,11 +121,14 @@ namespace MWMechanics float mTimer; - ESM::Pathgrid::Point mPrevDest; + osg::Vec3f mLastActorPos; + + bool mIsShortcutting; // if shortcutting at the moment + bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt + ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail private: bool isNearInactiveCell(const ESM::Position& actorPos); - }; } diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index be16f49a2..2b218de03 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -33,7 +33,6 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte if(actor.getClass().getCreatureStats(actor).isDead()) return true; - ESM::Position pos = actor.getRefData().getPosition(); //position of the actor const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); //The target to follow if(target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered @@ -52,14 +51,10 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte //Set the target desition from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) { //Stop when you get close - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - target.getClass().activate(target,actor).get()->execute(actor); //Arrest player + if (pathTo(actor, dest, duration, 100)) { + target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached return true; } - else { - pathTo(actor, dest, duration); //Go to the destination - } actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index f05725cc2..b03586c3b 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -234,6 +234,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac // Put repeating noncombat AI packages on the end of the stack so they can be used again if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat())) { + package->reset(); mPackages.push_back(package->clone()); } // To account for the rare case where AiPackage::execute() queued another AI package diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 87fb3b9b7..1ec9444aa 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -29,7 +29,6 @@ namespace MWMechanics { static const int COUNT_BEFORE_RESET = 10; static const float DOOR_CHECK_INTERVAL = 1.5f; - static const float REACTION_INTERVAL = 0.25f; static const int GREETING_SHOULD_START = 4; //how many reaction intervals should pass before NPC can greet player static const int GREETING_SHOULD_END = 10; @@ -74,8 +73,6 @@ namespace MWMechanics unsigned short mIdleAnimation; std::vector mBadIdles; // Idle animations that when called cause errors - PathFinder mPathFinder; - // do we need to calculate allowed nodes based on mDistance bool mPopulateAvailableNodes; @@ -86,8 +83,6 @@ namespace MWMechanics ESM::Pathgrid::Point mCurrentNode; bool mTrimCurrentNode; - ObstacleCheck mObstacleCheck; - float mDoorCheckDuration; int mStuckCount; @@ -118,7 +113,7 @@ namespace MWMechanics AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), - mRepeat(repeat), mStoredInitialActorPosition(false) + mRepeat(repeat), mStoredInitialActorPosition(false), mIsWanderDestReady(false) { mIdle.resize(8, 0); init(); @@ -223,7 +218,7 @@ namespace MWMechanics float& lastReaction = storage.mReaction; lastReaction += duration; - if (REACTION_INTERVAL <= lastReaction) + if (AI_REACTION_TIME <= lastReaction) { lastReaction = 0; return reactionTimeActions(actor, storage, currentCell, cellChange, pos, duration); @@ -273,7 +268,7 @@ namespace MWMechanics } // If Wandering manually and hit an obstacle, stop - if (storage.mIsWanderingManually && storage.mObstacleCheck.check(actor, duration, 2.0f)) { + if (storage.mIsWanderingManually && mObstacleCheck.check(actor, duration, 2.0f)) { completeManualWalking(actor, storage); } @@ -300,14 +295,14 @@ namespace MWMechanics if ((wanderState == Wander_MoveNow) && storage.mCanWanderAlongPathGrid) { // Construct a new path if there isn't one - if(!storage.mPathFinder.isPathConstructed()) + if(!mPathFinder.isPathConstructed()) { if (!storage.mAllowedNodes.empty()) { setPathToAnAllowedNode(actor, storage, pos); } } - } else if (storage.mIsWanderingManually && storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { + } else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { completeManualWalking(actor, storage); } @@ -337,7 +332,7 @@ namespace MWMechanics void AiWander::returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos) { - if (!storage.mPathFinder.isPathConstructed()) + if (!mPathFinder.isPathConstructed()) { ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition)); @@ -345,10 +340,11 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); // don't take shortcuts for wandering - storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false); + mPathFinder.buildSyncedPath(start, dest, actor.getCell()); - if (storage.mPathFinder.isPathConstructed()) + if (mPathFinder.isPathConstructed()) { + mIsWanderDestReady = true; storage.setState(Wander_Walking); } } @@ -379,9 +375,14 @@ namespace MWMechanics // Check if land creature will walk onto water or if water creature will swim onto land if ((!isWaterCreature && !destinationIsAtWater(actor, destination)) || (isWaterCreature && !destinationThroughGround(currentPositionVec3f, destination))) { - storage.mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), true); - storage.mPathFinder.addPointToPath(destinationPosition); - storage.setState(Wander_Walking, true); + mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell()); + mPathFinder.addPointToPath(destinationPosition); + + if (mPathFinder.isPathConstructed()) + { + mIsWanderDestReady = true; + storage.setState(Wander_Walking, true); + } return; } } while (--attempts); @@ -407,7 +408,7 @@ namespace MWMechanics void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) { stopWalking(actor, storage); - storage.mObstacleCheck.clear(); + mObstacleCheck.clear(); storage.setState(Wander_IdleNow); } @@ -475,7 +476,7 @@ namespace MWMechanics float duration, AiWanderStorage& storage, ESM::Position& pos) { // Are we there yet? - if (storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) + if (mIsWanderDestReady && pathTo(actor, mPathFinder.getPath().back(), duration, DESTINATION_TOLERANCE)) { stopWalking(actor, storage); storage.setState(Wander_ChooseAction); @@ -517,40 +518,27 @@ namespace MWMechanics void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos) { - // turn towards the next point in mPath - zTurn(actor, storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])); - - MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor); - if (storage.mObstacleCheck.check(actor, duration)) + if (mObstacleCheck.isEvading()) { // first check if we're walking into a door if (proximityToDoor(actor)) // NOTE: checks interior cells only { // remove allowed points then select another random destination storage.mTrimCurrentNode = true; - trimAllowedNodes(storage.mAllowedNodes, storage.mPathFinder); - storage.mObstacleCheck.clear(); - storage.mPathFinder.clearPath(); + trimAllowedNodes(storage.mAllowedNodes, mPathFinder); + mObstacleCheck.clear(); + mPathFinder.clearPath(); storage.setState(Wander_MoveNow); } - else // probably walking into another NPC - { - // TODO: diagonal should have same animation as walk forward - // but doesn't seem to do that? - storage.mObstacleCheck.takeEvasiveAction(movement); - } - storage.mStuckCount++; // TODO: maybe no longer needed - } - else - { - movement.mPosition[1] = 1; + + storage.mStuckCount++; // TODO: maybe no longer needed } // if stuck for sufficiently long, act like current location was the destination if (storage.mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset { //std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl; - storage.mObstacleCheck.clear(); + mObstacleCheck.clear(); stopWalking(actor, storage); storage.setState(Wander_ChooseAction); @@ -627,7 +615,7 @@ namespace MWMechanics if (storage.mState == Wander_Walking) { stopWalking(actor, storage); - storage.mObstacleCheck.clear(); + mObstacleCheck.clear(); storage.setState(Wander_IdleNow); } @@ -667,10 +655,12 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos)); // don't take shortcuts for wandering - storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false); + mPathFinder.buildSyncedPath(start, dest, actor.getCell()); - if (storage.mPathFinder.isPathConstructed()) + if (mPathFinder.isPathConstructed()) { + mIsWanderDestReady = true; + // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode]; storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode); @@ -726,7 +716,8 @@ namespace MWMechanics void AiWander::stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage) { - storage.mPathFinder.clearPath(); + mPathFinder.clearPath(); + mIsWanderDestReady = false; actor.getClass().getMovementSettings(actor).mPosition[1] = 0; } @@ -959,6 +950,7 @@ namespace MWMechanics , mTimeOfDay(wander->mData.mTimeOfDay) , mRepeat(wander->mData.mShouldRepeat != 0) , mStoredInitialActorPosition(wander->mStoredInitialActorPosition) + , mIsWanderDestReady(false) { if (mStoredInitialActorPosition) mInitialActorPosition = wander->mInitialActorPosition; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 64a54a66b..a46b3cbad 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -108,8 +108,10 @@ namespace MWMechanics osg::Vec3f mInitialActorPosition; bool mStoredInitialActorPosition; + bool mIsWanderDestReady; + void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage); - + void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); // constants for converting idleSelect values into groupNames diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 4394168a5..5d99fe723 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -93,6 +93,11 @@ namespace MWMechanics return mWalkState == State_Norm; } + bool ObstacleCheck::isEvading() const + { + return mWalkState == State_Evade; + } + /* * input - actor, duration (time since last check) * output - true if evasive action needs to be taken diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index c8c83d68f..d2e1cfc2e 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -33,6 +33,7 @@ namespace MWMechanics void clear(); bool isNormalState() const; + bool isEvading() const; // Returns true if there is an obstacle and an evasive action // should be taken diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 51127de2a..ef60a85a4 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -82,6 +82,42 @@ namespace MWMechanics return sqrt(x * x + y * y + z * z); } + float getZAngleToDir(const osg::Vec3f& dir) + { + return std::atan2(dir.x(), dir.y()); + } + + float getXAngleToDir(const osg::Vec3f& dir) + { + return -std::asin(dir.z() / dir.length()); + } + + float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) + { + osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin); + return getZAngleToDir(dir); + } + + float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) + { + osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin); + return getXAngleToDir(dir); + } + + bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY) + { + osg::Vec3f dir = to - from; + dir.z() = 0; + dir.normalize(); + float verticalOffset = 200; // instead of '200' here we want the height of the actor + osg::Vec3f _from = from + dir*offsetXY + osg::Z_AXIS * verticalOffset; + + // cast up-down ray and find height of hit in world space + float h = _from.z() - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -osg::Z_AXIS, verticalOffset + PATHFIND_Z_REACH + 1); + + return (std::abs(from.z() - h) <= PATHFIND_Z_REACH); + } + PathFinder::PathFinder() : mPathgrid(NULL), mCell(NULL) @@ -132,23 +168,10 @@ namespace MWMechanics */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, - bool allowShortcuts) + const MWWorld::CellStore* cell) { mPath.clear(); - if(allowShortcuts) - { - // if there's a ray cast hit, can't take a direct path - if (!MWBase::Environment::get().getWorld()->castRay( - static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), - static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ))) - { - mPath.push_back(endPoint); - return; - } - } - if(mCell != cell || !mPathgrid) { mCell = cell; @@ -243,6 +266,19 @@ namespace MWMechanics return std::atan2(directionX, directionY); } + float PathFinder::getXAngleToNext(float x, float y, float z) const + { + // This should never happen (programmers should have an if statement checking + // isPathConstructed that prevents this call if otherwise). + if(mPath.empty()) + return 0.; + + const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); + osg::Vec3f dir = MakeOsgVec3(nextPoint) - osg::Vec3f(x,y,z); + + return -std::asin(dir.z() / dir.length()); + } + bool PathFinder::checkPathCompleted(float x, float y, float tolerance) { if(mPath.empty()) @@ -264,19 +300,18 @@ namespace MWMechanics // see header for the rationale void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, - bool allowShortcuts) + const MWWorld::CellStore* cell) { if (mPath.size() < 2) { // if path has one point, then it's the destination. // don't need to worry about bad path for this case - buildPath(startPoint, endPoint, cell, allowShortcuts); + buildPath(startPoint, endPoint, cell); } else { const ESM::Pathgrid::Point oldStart(*getPath().begin()); - buildPath(startPoint, endPoint, cell, allowShortcuts); + buildPath(startPoint, endPoint, cell); if (mPath.size() >= 2) { // if 2nd waypoint of new path == 1st waypoint of old, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 00a8fe0e4..64608979b 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -16,6 +16,20 @@ namespace MWMechanics { float distance(const ESM::Pathgrid::Point& point, float x, float y, float); float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b); + float getZAngleToDir(const osg::Vec3f& dir); + float getXAngleToDir(const osg::Vec3f& dir); + float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); + float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); + + const float PATHFIND_Z_REACH = 50.0f; + //static const float sMaxSlope = 49.0f; // duplicate as in physicssystem + // distance after which actor (failed previously to shortcut) will try again + const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; + + // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; + // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH + bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY); + class PathFinder { public: @@ -39,12 +53,17 @@ namespace MWMechanics void clearPath(); + void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, + const MWWorld::CellStore* cell); + bool checkPathCompleted(float x, float y, float tolerance = PathTolerance); ///< \Returns true if we are within \a tolerance units of the last path point. /// In radians float getZAngleToNext(float x, float y) const; + float getXAngleToNext(float x, float y, float z) const; + bool isPathConstructed() const { return !mPath.empty(); @@ -68,9 +87,9 @@ namespace MWMechanics Which results in NPC "running in a circle" back to the just passed waypoint. */ void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, bool allowShortcuts = true); + const MWWorld::CellStore* cell); - void addPointToPath(ESM::Pathgrid::Point &point) + void addPointToPath(const ESM::Pathgrid::Point &point) { mPath.push_back(point); } @@ -130,9 +149,6 @@ namespace MWMechanics } private: - void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, bool allowShortcuts = true); - std::list mPath; const ESM::Pathgrid *mPathgrid; From 3ae2fc17c6b6cbe5d1ac95d53eb7e65adf22c145 Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Mon, 29 Aug 2016 18:06:56 -0400 Subject: [PATCH 02/54] Fix some issues with content file numbers in the editor. 1. Change content numbers to be relative to the plugin when saving. 2. Initialize the indices in the MasterData part of a plugin header. --- apps/opencs/model/doc/savingstages.cpp | 4 +++ apps/opencs/model/world/data.cpp | 41 ++++++++++++++++++++++++++ apps/opencs/model/world/data.hpp | 7 +++++ 3 files changed, 52 insertions(+) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index ee65248e2..6a0330180 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -321,6 +321,10 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) { CSMWorld::CellRef refRecord = ref.get(); + // Correct content file number to be relative to plugin + refRecord.mRefNum.mContentFile = mDocument.getData().getPluginContentFile( + refRecord.mRefNum.mContentFile); + // recalculate the ref's cell location std::ostringstream stream; if (!interior) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 8b0a9b384..8347cd5bc 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -902,6 +902,8 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mReader->setIndex(mReaderIndex++); mReader->open (path.string()); + mContentFileNames.insert(std::make_pair(path.filename().string(), mReader->getIndex())); + mBase = base; mProject = project; @@ -914,6 +916,35 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mMetaData.setRecord (0, Record (RecordBase::State_ModifiedOnly, 0, &metaData)); } + // Fix uninitialized master data index + for (std::vector::const_iterator masterData = mReader->getGameFiles().begin(); + masterData != mReader->getGameFiles().end(); ++masterData) + { + std::map::iterator nameResult = mContentFileNames.find(masterData->name); + if (nameResult != mContentFileNames.end()) + { + ESM::Header::MasterData& hackedMasterData = const_cast(*masterData); + hackedMasterData.index = nameResult->second; + } + } + + // Needed for saving + if (!mBase) + { + mReverseContentFiles.insert(std::make_pair(mReader->getIndex(), 0)); + } + if (mProject) + { + mReverseContentFiles.insert(std::make_pair(mReader->getIndex(), 0)); + + // A new project has no header, so extrapolate the content files from the reader index + // The base/project index of 0 will not be overwritten + for (int i = 0; i < mReader->getIndex(); ++i) + { + mReverseContentFiles.insert(std::make_pair(i, i+1)); + } + } + return mReader->getRecordCount(); } @@ -1171,6 +1202,16 @@ int CSMWorld::Data::count (RecordBase::State state) const count (state, mPathgrids); } +int CSMWorld::Data::getPluginContentFile(int currentContentFile) +{ + std::map::iterator searchResult = mReverseContentFiles.find(currentContentFile); + + if (searchResult != mReverseContentFiles.end()) + return searchResult->second; + else + return 0; // Assume faulty plugin with original content +} + std::vector CSMWorld::Data::getIds (bool listDeleted) const { std::vector ids; diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index f0c3dcd41..8fc68b9f7 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -123,6 +123,10 @@ namespace CSMWorld std::vector > mReaders; + std::map mContentFileNames; + // current index, plugin index + std::map mReverseContentFiles; + // not implemented Data (const Data&); Data& operator= (const Data&); @@ -298,6 +302,9 @@ namespace CSMWorld int count (RecordBase::State state) const; ///< Return number of top-level records with the given \a state. + /// Returns the content file number relative to the plugin being edited, 0 by default + int getPluginContentFile(int currentContentFile); + signals: void idListChanged(); From 139cd190d4b792647a5c99a350a56f03e68087ce Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 30 Aug 2016 16:52:40 +0200 Subject: [PATCH 03/54] Added link to GPLv3 in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8e6c0c0e..3a6c4a75a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ OpenMW is a recreation of the engine for the popular role-playing game Morrowind OpenMW also comes with OpenMW-CS, a replacement for Morrowind's TES Construction Set. * Version: 0.40.0 -* License: GPL (see docs/license/GPL3.txt for more information) +* License: GPLv3 (see [docs/license/GPL3.txt](https://github.com/OpenMW/openmw/blob/master/docs/license/GPL3.txt) for more information) * Website: http://www.openmw.org * IRC: #openmw on irc.freenode.net From b2ddd3c2596004d9ee9122c33ce4a57007836a42 Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Tue, 30 Aug 2016 16:42:38 -0400 Subject: [PATCH 04/54] Initialize with correct content file number instead of correcting at save stage. --- apps/opencs/model/doc/savingstages.cpp | 6 ++--- apps/opencs/model/world/data.cpp | 31 +++----------------------- apps/opencs/model/world/data.hpp | 5 ----- 3 files changed, 6 insertions(+), 36 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 6a0330180..82a965ae4 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -321,9 +321,9 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) { CSMWorld::CellRef refRecord = ref.get(); - // Correct content file number to be relative to plugin - refRecord.mRefNum.mContentFile = mDocument.getData().getPluginContentFile( - refRecord.mRefNum.mContentFile); + // Check for uninitialized content file + if (!refRecord.mRefNum.hasContentFile()) + refRecord.mRefNum.mContentFile = 0; // recalculate the ref's cell location std::ostringstream stream; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 8347cd5bc..1cc4a2cbf 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -64,7 +64,7 @@ int CSMWorld::Data::count (RecordBase::State state, const CollectionBase& collec CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourcesManager, const Fallback::Map* fallback, const boost::filesystem::path& resDir) : mEncoder (encoding), mPathgrids (mCells), mRefs (mCells), mResourcesManager (resourcesManager), mFallbackMap(fallback), - mReader (0), mDialogue (0), mReaderIndex(0), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS())) + mReader (0), mDialogue (0), mReaderIndex(1), mResourceSystem(new Resource::ResourceSystem(resourcesManager.getVFS())) { mResourceSystem->getSceneManager()->setShaderPath((resDir / "shaders").string()); @@ -899,7 +899,7 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base mReader = new ESM::ESMReader; mReader->setEncoder (&mEncoder); - mReader->setIndex(mReaderIndex++); + mReader->setIndex((project || !base) ? 0 : mReaderIndex++); mReader->open (path.string()); mContentFileNames.insert(std::make_pair(path.filename().string(), mReader->getIndex())); @@ -924,24 +924,9 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base if (nameResult != mContentFileNames.end()) { ESM::Header::MasterData& hackedMasterData = const_cast(*masterData); - hackedMasterData.index = nameResult->second; - } - } - // Needed for saving - if (!mBase) - { - mReverseContentFiles.insert(std::make_pair(mReader->getIndex(), 0)); - } - if (mProject) - { - mReverseContentFiles.insert(std::make_pair(mReader->getIndex(), 0)); - // A new project has no header, so extrapolate the content files from the reader index - // The base/project index of 0 will not be overwritten - for (int i = 0; i < mReader->getIndex(); ++i) - { - mReverseContentFiles.insert(std::make_pair(i, i+1)); + hackedMasterData.index = nameResult->second; } } @@ -1202,16 +1187,6 @@ int CSMWorld::Data::count (RecordBase::State state) const count (state, mPathgrids); } -int CSMWorld::Data::getPluginContentFile(int currentContentFile) -{ - std::map::iterator searchResult = mReverseContentFiles.find(currentContentFile); - - if (searchResult != mReverseContentFiles.end()) - return searchResult->second; - else - return 0; // Assume faulty plugin with original content -} - std::vector CSMWorld::Data::getIds (bool listDeleted) const { std::vector ids; diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 8fc68b9f7..dc00a33d2 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -124,8 +124,6 @@ namespace CSMWorld std::vector > mReaders; std::map mContentFileNames; - // current index, plugin index - std::map mReverseContentFiles; // not implemented Data (const Data&); @@ -302,9 +300,6 @@ namespace CSMWorld int count (RecordBase::State state) const; ///< Return number of top-level records with the given \a state. - /// Returns the content file number relative to the plugin being edited, 0 by default - int getPluginContentFile(int currentContentFile); - signals: void idListChanged(); From 154dcc942c6c6306aa7fcd7d7e3fcf2d025cf158 Mon Sep 17 00:00:00 2001 From: Allofich Date: Thu, 1 Sep 2016 22:43:33 +0900 Subject: [PATCH 05/54] Let NPCs use attack type regardless of movement --- apps/openmw/mwmechanics/aicombat.cpp | 20 ++++++++++---------- apps/openmw/mwmechanics/character.cpp | 21 ++++++++++++++------- apps/openmw/mwmechanics/character.hpp | 1 + 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index ce66b045a..380c9acf0 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -23,7 +23,7 @@ namespace { //chooses an attack depending on probability to avoid uniformity - ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); + std::string chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength); @@ -630,7 +630,7 @@ namespace MWMechanics characterController.setAttackingOrSpell(true); if (!distantCombat) - chooseBestAttack(weapon, mMovement); + characterController.setAIAttackType(chooseBestAttack(weapon, mMovement)); mStrength = Misc::Rng::rollClosedProbability(); @@ -678,9 +678,9 @@ namespace MWMechanics namespace { -ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement) +std::string chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement) { - ESM::Weapon::AttackType attackType; + std::string attackType; if (weapon == NULL) { @@ -690,17 +690,17 @@ ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics: { movement.mPosition[0] = (Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f; movement.mPosition[1] = 0; - attackType = ESM::Weapon::AT_Slash; + attackType = "slash"; } else if(roll <= 0.666f) //forward punch { movement.mPosition[1] = 1; - attackType = ESM::Weapon::AT_Thrust; + attackType = "thrust"; } else { movement.mPosition[1] = movement.mPosition[0] = 0; - attackType = ESM::Weapon::AT_Chop; + attackType = "chop"; } } else @@ -715,17 +715,17 @@ ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics: { movement.mPosition[0] = (Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f; movement.mPosition[1] = 0; - attackType = ESM::Weapon::AT_Slash; + attackType = "slash"; } else if(roll <= (slash + thrust)) { movement.mPosition[1] = 1; - attackType = ESM::Weapon::AT_Thrust; + attackType = "thrust"; } else { movement.mPosition[1] = movement.mPosition[0] = 0; - attackType = ESM::Weapon::AT_Chop; + attackType = "chop"; } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 38fec4e4f..93f553f19 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1208,7 +1208,6 @@ bool CharacterController::updateWeaponState() if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); - mAttackType.clear(); if(mWeaponType == WeapType_Spell) { // Unset casting flag, otherwise pressing the mouse button down would @@ -1309,14 +1308,17 @@ bool CharacterController::updateWeaponState() { if (isWeapon) { - if(mPtr == getPlayer() && - Settings::Manager::getBool("best attack", "Game")) + if(mPtr == getPlayer()) { - MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - mAttackType = getBestAttack(weapon->get()->mBase); + if (Settings::Manager::getBool("best attack", "Game")) + { + MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + mAttackType = getBestAttack(weapon->get()->mBase); + } + else + setAttackTypeBasedOnMovement(); } - else - setAttackTypeBasedOnMovement(); + // else if (mPtr != getPlayer()) use mAttackType already set by AiCombat } else setAttackTypeRandomly(); @@ -2227,6 +2229,11 @@ void CharacterController::setAttackingOrSpell(bool attackingOrSpell) mAttackingOrSpell = attackingOrSpell; } +void CharacterController::setAIAttackType(std::string attackType) +{ + mAttackType = attackType; +} + bool CharacterController::readyToPrepareAttack() const { return (mHitState == CharState_None || mHitState == CharState_Block) diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 4661d6983..deaf63539 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -269,6 +269,7 @@ public: bool isSneaking() const; void setAttackingOrSpell(bool attackingOrSpell); + void setAIAttackType(std::string attackType); // set and used by AiCombat bool readyToPrepareAttack() const; bool readyToStartAttack() const; From 123e57a65cb3a198702ea09e2e199780b0a09a08 Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Fri, 2 Sep 2016 06:57:36 +0200 Subject: [PATCH 06/54] Include the OpenAL32.dll file into builds --- CI/before_script.msvc.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 61343c104..98c1984f4 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -509,6 +509,8 @@ printf "OpenAL-Soft 1.17.2... " add_cmake_opts -DOPENAL_INCLUDE_DIR="${OPENAL_SDK}/include/AL" \ -DOPENAL_LIBRARY="${OPENAL_SDK}/libs/Win${BITS}/OpenAL32.lib" + add_runtime_dlls "$(pwd)/openal-soft-1.17.2-bin/bin/WIN${BITS}/soft_oal.dll:OpenAL32.dll" + echo Done. } cd $DEPS @@ -631,7 +633,7 @@ printf "SDL 2.0.4... " export SDL2DIR="$(real_pwd)/SDL2-2.0.4" - add_runtime_dlls "${SDL2DIR}/lib/x${ARCHSUFFIX}/SDL2.dll" + add_runtime_dlls "$(pwd)/SDL2-2.0.4/lib/x${ARCHSUFFIX}/SDL2.dll" echo Done. } @@ -691,8 +693,16 @@ if [ -z $CI ]; then echo "- Copying Runtime DLLs..." mkdir -p $BUILD_CONFIG for DLL in $RUNTIME_DLLS; do - echo " $(basename $DLL)." - cp "$DLL" $BUILD_CONFIG/ + TARGET="$(basename "$DLL")" + if [[ "$DLL" == *":"* ]]; then + IFS=':'; SPLIT=( ${DLL} ); unset IFS + + DLL=${SPLIT[0]} + TARGET=${SPLIT[1]} + fi + + echo " ${TARGET}." + cp "$DLL" "$BUILD_CONFIG/$TARGET" done echo From 286e4bb98f6fda7187e2fc2bf322adb0ef2ef4ef Mon Sep 17 00:00:00 2001 From: Allofich Date: Thu, 1 Sep 2016 23:12:10 +0900 Subject: [PATCH 07/54] Remove attacktype movement --- apps/openmw/mwmechanics/aicombat.cpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 380c9acf0..0d2499e7a 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -687,21 +687,11 @@ std::string chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &m //hand-to-hand deal equal damage for each type float roll = Misc::Rng::rollClosedProbability(); if(roll <= 0.333f) //side punch - { - movement.mPosition[0] = (Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f; - movement.mPosition[1] = 0; attackType = "slash"; - } else if(roll <= 0.666f) //forward punch - { - movement.mPosition[1] = 1; attackType = "thrust"; - } else - { - movement.mPosition[1] = movement.mPosition[0] = 0; attackType = "chop"; - } } else { @@ -712,21 +702,11 @@ std::string chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &m float roll = Misc::Rng::rollClosedProbability() * (slash + chop + thrust); if(roll <= slash) - { - movement.mPosition[0] = (Misc::Rng::rollClosedProbability() < 0.5f) ? 1.0f : -1.0f; - movement.mPosition[1] = 0; attackType = "slash"; - } else if(roll <= (slash + thrust)) - { - movement.mPosition[1] = 1; attackType = "thrust"; - } else - { - movement.mPosition[1] = movement.mPosition[0] = 0; attackType = "chop"; - } } return attackType; From 0d63d75bb0faac2895273ace55f9458347e5e47b Mon Sep 17 00:00:00 2001 From: Allofich Date: Sat, 3 Sep 2016 22:40:24 +0900 Subject: [PATCH 08/54] Remove no longer used parameter --- apps/openmw/mwmechanics/aicombat.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 0d2499e7a..2926bcb95 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -23,7 +23,7 @@ namespace { //chooses an attack depending on probability to avoid uniformity - std::string chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); + std::string chooseBestAttack(const ESM::Weapon* weapon); osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength); @@ -630,7 +630,7 @@ namespace MWMechanics characterController.setAttackingOrSpell(true); if (!distantCombat) - characterController.setAIAttackType(chooseBestAttack(weapon, mMovement)); + characterController.setAIAttackType(chooseBestAttack(weapon)); mStrength = Misc::Rng::rollClosedProbability(); @@ -678,7 +678,7 @@ namespace MWMechanics namespace { -std::string chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement) +std::string chooseBestAttack(const ESM::Weapon* weapon) { std::string attackType; From 0775ed75ea67f75d06b1f59363bc8e5e2f09409b Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sat, 3 Sep 2016 17:41:03 +0300 Subject: [PATCH 09/54] fix zero div in getXAngleToDir --- apps/openmw/mwmechanics/pathfinding.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index ef60a85a4..85e120d18 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -89,7 +89,8 @@ namespace MWMechanics float getXAngleToDir(const osg::Vec3f& dir) { - return -std::asin(dir.z() / dir.length()); + float dirLen = dir.length(); + return (dirLen != 0) ? -std::asin(dir.z() / dirLen) : 0; } float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) @@ -276,7 +277,7 @@ namespace MWMechanics const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); osg::Vec3f dir = MakeOsgVec3(nextPoint) - osg::Vec3f(x,y,z); - return -std::asin(dir.z() / dir.length()); + return getXAngleToDir(dir); } bool PathFinder::checkPathCompleted(float x, float y, float tolerance) From 5c2bc515fefe103d894283a8420d630ef3580ce3 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 4 Sep 2016 01:57:38 +0900 Subject: [PATCH 10/54] Remove overridden code in aicombat --- apps/openmw/mwmechanics/aicombat.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 2926bcb95..aa6bcdef0 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -682,18 +682,7 @@ std::string chooseBestAttack(const ESM::Weapon* weapon) { std::string attackType; - if (weapon == NULL) - { - //hand-to-hand deal equal damage for each type - float roll = Misc::Rng::rollClosedProbability(); - if(roll <= 0.333f) //side punch - attackType = "slash"; - else if(roll <= 0.666f) //forward punch - attackType = "thrust"; - else - attackType = "chop"; - } - else + if (weapon != NULL) { //the more damage attackType deals the more probability it has int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; From ee432690e6d0b3307322bddec0d512de34588b19 Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Fri, 2 Sep 2016 21:15:05 -0400 Subject: [PATCH 11/54] Make it easier to place objects in the scene editor. This fix allows objects to be dropped onto the ground, and prevents objects from being selected through the ground. --- apps/opencs/view/render/instancemode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index f5b15cb55..cf51e93b7 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -92,7 +92,7 @@ osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos) } CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) -: EditMode (worldspaceWidget, QIcon (":placeholder"), Mask_Reference, "Instance editing", +: EditMode (worldspaceWidget, QIcon (":placeholder"), Mask_Reference | Mask_Terrain, "Instance editing", parent), mSubMode (0), mSubModeId ("move"), mSelectionMode (0), mDragMode (DragMode_None), mDragAxis (-1), mLocked (false), mUnitScaleDist(1) { From fab7549b429c2d6b46dbc41d56c36d83110d46de Mon Sep 17 00:00:00 2001 From: Aesylwinn Date: Sat, 3 Sep 2016 17:49:49 -0400 Subject: [PATCH 12/54] Fix windows path issue in editor debug run. - The problem was caused by spaces in the data directory --- apps/opencs/model/doc/runner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index 5a0bc39be..84bc61a9a 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -81,7 +81,7 @@ void CSMDoc::Runner::start (bool delayed) arguments << ("--script-run="+mStartup->fileName());; arguments << - QString::fromUtf8 (("--data="+mProjectPath.parent_path().string()).c_str()); + QString::fromUtf8 (("--data=\""+mProjectPath.parent_path().string()+"\"").c_str()); for (std::vector::const_iterator iter (mContentFiles.begin()); iter!=mContentFiles.end(); ++iter) From b4577fe751ae10988fd74798ed019f21c22dcdbf Mon Sep 17 00:00:00 2001 From: Allofich Date: Wed, 31 Aug 2016 17:55:17 +0900 Subject: [PATCH 13/54] Allow multiple spell hit sounds on single target --- apps/openmw/mwmechanics/spellcasting.cpp | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index cdf8a2a67..b228f6dcd 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -356,7 +356,6 @@ namespace MWMechanics ESM::EffectList reflectedEffects; std::vector appliedLastingEffects; - bool firstAppliedEffect = true; bool anyHarmfulEffect = false; // HACK: cache target's magic effects here, and add any applied effects to it. Use the cached effects for determining resistance. @@ -545,20 +544,15 @@ namespace MWMechanics if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) { - // Play sound, only for the first effect - if (firstAppliedEffect) - { - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!magicEffect->mHitSound.empty()) - sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); - firstAppliedEffect = false; - } + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!magicEffect->mHitSound.empty()) + sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); // Add VFX const ESM::Static* castStatic; From 446c0a4fa8636c40f0bf95d7679f67dcc0d6e5c8 Mon Sep 17 00:00:00 2001 From: Allofich Date: Wed, 31 Aug 2016 18:25:59 +0900 Subject: [PATCH 14/54] Play all of a spell's casting effects --- apps/openmw/mwmechanics/spellcasting.cpp | 50 ++++++++++++------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index b228f6dcd..4478c6bd0 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -880,7 +880,6 @@ namespace MWMechanics inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); } - std::string projectileModel; std::string sound; float speed = 0; @@ -970,36 +969,39 @@ namespace MWMechanics const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); const ESM::Spell *spell = store.get().find(spellid); - const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); - const ESM::MagicEffect *effect; - effect = store.get().find(effectentry.mEffectID); + for (std::vector::const_iterator iter = spell->mEffects.mList.begin(); + iter != spell->mEffects.mList.end(); ++iter) + { + const ESM::MagicEffect *effect; + effect = store.get().find(iter->mEffectID); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); - if (mCaster.getClass().isActor()) // TODO: Non-actors (except for large statics?) should also create a spell cast vfx - { - const ESM::Static* castStatic; - if (!effect->mCasting.empty()) - castStatic = store.get().find (effect->mCasting); - else - castStatic = store.get().find ("VFX_DefaultCast"); + if (mCaster.getClass().isActor()) // TODO: Non-actors (except for large statics?) should also create a spell cast vfx + { + const ESM::Static* castStatic; + if (!effect->mCasting.empty()) + castStatic = store.get().find (effect->mCasting); + else + castStatic = store.get().find ("VFX_DefaultCast"); - animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); - } + animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); + } - if (!mCaster.getClass().isActor()) - animation->addSpellCastGlow(effect); + if (!mCaster.getClass().isActor()) + animation->addSpellCastGlow(effect); - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!effect->mCastSound.empty()) - sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!effect->mCastSound.empty()) + sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); + } } int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) From f1a18027f24cc246d19a0d2a2da666f59a47db70 Mon Sep 17 00:00:00 2001 From: Allofich Date: Wed, 31 Aug 2016 19:03:28 +0900 Subject: [PATCH 15/54] Shoot projectiles of spells with multiple effects --- apps/openmw/mwmechanics/spellcasting.cpp | 111 ++++++++++------------- apps/openmw/mwmechanics/spellcasting.hpp | 4 + 2 files changed, 50 insertions(+), 65 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 4478c6bd0..df6d2c600 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -27,41 +27,6 @@ #include "npcstats.hpp" #include "actorutil.hpp" -namespace -{ - - /// Get projectile properties (model, sound and speed) for a spell with the given effects - /// If \a model is empty, the spell has no ranged effects and should not spawn a projectile. - void getProjectileInfo (const ESM::EffectList& effects, std::string& model, std::string& sound, float& speed) - { - for (std::vector::const_iterator iter (effects.mList.begin()); - iter!=effects.mList.end(); ++iter) - { - if (iter->mRange != ESM::RT_Target) - continue; - - const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - iter->mEffectID); - - model = magicEffect->mBolt; - if (model.empty()) - model = "VFX_DefaultBolt"; - - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - if (!magicEffect->mBoltSound.empty()) - sound = magicEffect->mBoltSound; - else - sound = schools[magicEffect->mData.mSchool] + " bolt"; - - speed = magicEffect->mData.mSpeed; - break; - } - } - -} - namespace MWMechanics { @@ -317,6 +282,50 @@ namespace MWMechanics { } + /// Get projectile properties (model, sound and speed) for a spell with the given effects and launch. + /// If \a model is empty, the spell has no ranged effects and should not spawn a projectile. + void CastSpell::getProjectileInfoAndLaunch (const ESM::EffectList& effects) + { + std::string model; + std::string sound; + float speed = 0; + osg::Vec3f fallbackDirection (0,1,0); + for (std::vector::const_iterator iter (effects.mList.begin()); + iter!=effects.mList.end(); ++iter) + { + if (iter->mRange != ESM::RT_Target) + continue; + + const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( + iter->mEffectID); + + model = magicEffect->mBolt; + if (model.empty()) + model = "VFX_DefaultBolt"; + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + if (!magicEffect->mBoltSound.empty()) + sound = magicEffect->mBoltSound; + else + sound = schools[magicEffect->mData.mSchool] + " bolt"; + + speed = magicEffect->mData.mSpeed; + + // Fall back to a "caster to target" direction if we have no other means of determining it + // (e.g. when cast by a non-actor) + if (!mTarget.isEmpty()) + fallbackDirection = + osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- + osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); + + if (!model.empty()) + MWBase::Environment::get().getWorld()->launchMagicBolt(model, sound, mId, speed, + false, effects, mCaster, mSourceName, fallbackDirection); + } + } + void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) { @@ -785,17 +794,7 @@ namespace MWMechanics } if (launchProjectile) - { - std::string projectileModel; - std::string sound; - float speed = 0; - getProjectileInfo(enchantment->mEffects, projectileModel, sound, speed); - if (!projectileModel.empty()) - MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, - false, enchantment->mEffects, mCaster, mSourceName, - // Not needed, enchantments can only be cast by actors - osg::Vec3f(1,0,0)); - } + getProjectileInfoAndLaunch(enchantment->mEffects); else if (!mTarget.isEmpty()) inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target); @@ -876,27 +875,9 @@ namespace MWMechanics inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self); if (!mTarget.isEmpty()) - { inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); - } - - std::string projectileModel; - std::string sound; - float speed = 0; - getProjectileInfo(spell->mEffects, projectileModel, sound, speed); - if (!projectileModel.empty()) - { - osg::Vec3f fallbackDirection (0,1,0); - // Fall back to a "caster to target" direction if we have no other means of determining it - // (e.g. when cast by a non-actor) - if (!mTarget.isEmpty()) - fallbackDirection = - osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- - osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed, - false, spell->mEffects, mCaster, mSourceName, fallbackDirection); - } + getProjectileInfoAndLaunch(spell->mEffects); return true; } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index aba263b01..e300b1818 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -94,6 +94,10 @@ namespace MWMechanics void playSpellCastingEffects(const std::string &spellid); + /// Get the models, sounds and speeds for all projectiles + /// in the given effects, and launch them. + void getProjectileInfoAndLaunch (const ESM::EffectList& effects); + /// @note \a target can be any type of object, not just actors. /// @note \a caster can be any type of object, or even an empty object. void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, From f6c3a62b3e592d62fdf35515d4527cae8c03a3c2 Mon Sep 17 00:00:00 2001 From: Allofich Date: Wed, 31 Aug 2016 19:50:27 +0900 Subject: [PATCH 16/54] Use average speed for multiple spell projectiles --- apps/openmw/mwmechanics/spellcasting.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index df6d2c600..a9c0bb9f7 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -286,9 +286,26 @@ namespace MWMechanics /// If \a model is empty, the spell has no ranged effects and should not spawn a projectile. void CastSpell::getProjectileInfoAndLaunch (const ESM::EffectList& effects) { + // All projectiles should use the same speed. From observations in the + // original engine, this seems to be the average of the constituent effects. + // First we get this average speed. + float speed = 0; + int count = 0; + for (std::vector::const_iterator iter (effects.mList.begin()); + iter!=effects.mList.end(); ++iter) + { + const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( + iter->mEffectID); + speed += magicEffect->mData.mSpeed; + count++; + } + + if (count != 0) + speed /= count; + std::string model; std::string sound; - float speed = 0; + osg::Vec3f fallbackDirection (0,1,0); for (std::vector::const_iterator iter (effects.mList.begin()); iter!=effects.mList.end(); ++iter) @@ -311,8 +328,6 @@ namespace MWMechanics else sound = schools[magicEffect->mData.mSchool] + " bolt"; - speed = magicEffect->mData.mSpeed; - // Fall back to a "caster to target" direction if we have no other means of determining it // (e.g. when cast by a non-actor) if (!mTarget.isEmpty()) @@ -320,8 +335,7 @@ namespace MWMechanics osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); - if (!model.empty()) - MWBase::Environment::get().getWorld()->launchMagicBolt(model, sound, mId, speed, + MWBase::Environment::get().getWorld()->launchMagicBolt(model, sound, mId, speed, false, effects, mCaster, mSourceName, fallbackDirection); } } From f36e5ef403a79aec2a751001ba4ec8aeb1285e9c Mon Sep 17 00:00:00 2001 From: Allofich Date: Wed, 31 Aug 2016 21:03:41 +0900 Subject: [PATCH 17/54] Use last effect of spell for hand effect color --- apps/openmw/mwmechanics/character.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 93f553f19..782e59696 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1236,10 +1236,10 @@ bool CharacterController::updateWeaponState() cast.playSpellCastingEffects(spellid); const ESM::Spell *spell = store.get().find(spellid); - const ESM::ENAMstruct &effectentry = spell->mEffects.mList.at(0); + const ESM::ENAMstruct &lasteffect = spell->mEffects.mList.at(spell->mEffects.mList.size() - 1); const ESM::MagicEffect *effect; - effect = store.get().find(effectentry.mEffectID); + effect = store.get().find(lasteffect.mEffectID); const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); if (mAnimation->getNode("Bip01 L Hand")) @@ -1248,7 +1248,7 @@ bool CharacterController::updateWeaponState() if (mAnimation->getNode("Bip01 R Hand")) mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); - switch(effectentry.mRange) + switch(lasteffect.mRange) { case 0: mAttackType = "self"; break; case 1: mAttackType = "touch"; break; From 3300ef5db76cdbffd01b67cc6047c20b119f3db8 Mon Sep 17 00:00:00 2001 From: Allofich Date: Thu, 1 Sep 2016 02:27:02 +0900 Subject: [PATCH 18/54] Don't apply projectile effects multiple times --- apps/openmw/mwmechanics/spellcasting.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index a9c0bb9f7..38191ec4a 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -307,6 +307,9 @@ namespace MWMechanics std::string sound; osg::Vec3f fallbackDirection (0,1,0); + + bool isFirstProjectile = true; + for (std::vector::const_iterator iter (effects.mList.begin()); iter!=effects.mList.end(); ++iter) { @@ -335,8 +338,18 @@ namespace MWMechanics osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getWorld()->launchMagicBolt(model, sound, mId, speed, + // Only send the effects data with the first projectile, so we don't have the impact sounds + // playing multiple times. + if (isFirstProjectile) + MWBase::Environment::get().getWorld()->launchMagicBolt(model, sound, mId, speed, false, effects, mCaster, mSourceName, fallbackDirection); + else + { + const ESM::EffectList empty; + MWBase::Environment::get().getWorld()->launchMagicBolt(model, sound, mId, speed, + false, empty, mCaster, mSourceName, fallbackDirection); + } + isFirstProjectile = false; } } From c4d77b6a8b3a8454a2ada65f97ea9b8b0de85b06 Mon Sep 17 00:00:00 2001 From: Allofich Date: Fri, 2 Sep 2016 20:10:13 +0900 Subject: [PATCH 19/54] Casting animation comes from the first effect --- apps/openmw/mwmechanics/character.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 782e59696..2c21fb17f 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1236,19 +1236,27 @@ bool CharacterController::updateWeaponState() cast.playSpellCastingEffects(spellid); const ESM::Spell *spell = store.get().find(spellid); - const ESM::ENAMstruct &lasteffect = spell->mEffects.mList.at(spell->mEffects.mList.size() - 1); + + const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.at(spell->mEffects.mList.size() - 1); const ESM::MagicEffect *effect; - effect = store.get().find(lasteffect.mEffectID); + + 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"); - 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); + for (int 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(lasteffect.mRange) + switch(firstEffect.mRange) { case 0: mAttackType = "self"; break; case 1: mAttackType = "touch"; break; From ecec7d8215b80e6dd99b35ffb1c3a7d23ad6eedb Mon Sep 17 00:00:00 2001 From: Allofich Date: Fri, 2 Sep 2016 22:51:20 +0900 Subject: [PATCH 20/54] Fix spells having explosions for wrong range type --- apps/openmw/mwworld/worldimp.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5652b2d78..4448a9c17 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3163,8 +3163,9 @@ namespace MWWorld { const ESM::MagicEffect* effect = getStore().get().find(effectIt->mEffectID); - if (effectIt->mArea <= 0) - continue; // Not an area effect + if (effectIt->mArea <= 0 || effectIt->mRange != rangeType) + continue; // Not an area effect or not right range type + // Spawn the explosion orb effect const ESM::Static* areaStatic; From 96e1726e4d102c117d27007791d0afb00b425681 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sat, 3 Sep 2016 00:16:24 +0900 Subject: [PATCH 21/54] Fix warnings --- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2c21fb17f..bb8929f8b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1245,7 +1245,7 @@ bool CharacterController::updateWeaponState() const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); - for (int iter = 0; iter < spell->mEffects.mList.size(); ++iter) // play hands vfx for each effect + 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); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 38191ec4a..9fcd488ca 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -345,7 +345,7 @@ namespace MWMechanics false, effects, mCaster, mSourceName, fallbackDirection); else { - const ESM::EffectList empty; + ESM::EffectList empty; MWBase::Environment::get().getWorld()->launchMagicBolt(model, sound, mId, speed, false, empty, mCaster, mSourceName, fallbackDirection); } From 10842462c7020e11aa54274d4e6c848d42b5fa93 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 4 Sep 2016 01:54:09 +0900 Subject: [PATCH 22/54] Send lists of models and sounds to launchMagicBolt --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 26 +++++-------- apps/openmw/mwworld/projectilemanager.cpp | 45 +++++++++++------------ apps/openmw/mwworld/projectilemanager.hpp | 11 ++++-- apps/openmw/mwworld/worldimp.cpp | 4 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 6 files changed, 43 insertions(+), 47 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 549931b9a..8dda90b64 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -486,7 +486,7 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor) = 0; - virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId, + virtual void launchMagicBolt (const std::vector& models, const std::vector& sounds, const std::string& spellId, float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) = 0; virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 9fcd488ca..734509b6b 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -304,11 +304,12 @@ namespace MWMechanics speed /= count; std::string model; + std::vector models; std::string sound; + std::vector sounds; + ESM::EffectList projectileEffects; - osg::Vec3f fallbackDirection (0,1,0); - - bool isFirstProjectile = true; + osg::Vec3f fallbackDirection (0,1,0); for (std::vector::const_iterator iter (effects.mList.begin()); iter!=effects.mList.end(); ++iter) @@ -322,6 +323,7 @@ namespace MWMechanics model = magicEffect->mBolt; if (model.empty()) model = "VFX_DefaultBolt"; + models.push_back(model); static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" @@ -330,6 +332,9 @@ namespace MWMechanics sound = magicEffect->mBoltSound; else sound = schools[magicEffect->mData.mSchool] + " bolt"; + sounds.push_back(sound); + projectileEffects.mList.push_back(*iter); + } // Fall back to a "caster to target" direction if we have no other means of determining it // (e.g. when cast by a non-actor) @@ -338,19 +343,8 @@ namespace MWMechanics osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); - // Only send the effects data with the first projectile, so we don't have the impact sounds - // playing multiple times. - if (isFirstProjectile) - MWBase::Environment::get().getWorld()->launchMagicBolt(model, sound, mId, speed, - false, effects, mCaster, mSourceName, fallbackDirection); - else - { - ESM::EffectList empty; - MWBase::Environment::get().getWorld()->launchMagicBolt(model, sound, mId, speed, - false, empty, mCaster, mSourceName, fallbackDirection); - } - isFirstProjectile = false; - } + MWBase::Environment::get().getWorld()->launchMagicBolt(models, sounds, mId, speed, + false, projectileEffects, mCaster, mSourceName, fallbackDirection); } void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 90fba827f..0825be3c3 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -112,7 +112,7 @@ namespace MWWorld state.mEffectAnimationTime->addTime(duration); } - void ProjectileManager::launchMagicBolt(const std::string &model, const std::string &sound, + void ProjectileManager::launchMagicBolt(const std::vector &models, const std::vector &sounds, const std::string &spellId, float speed, bool stack, const ESM::EffectList &effects, const Ptr &caster, const std::string &sourceName, const osg::Vec3f& fallbackDirection) @@ -137,7 +137,6 @@ namespace MWWorld MagicBoltState state; state.mSourceName = sourceName; - state.mId = model; state.mSpellId = spellId; state.mCasterHandle = caster; if (caster.getClass().isActor()) @@ -146,23 +145,23 @@ namespace MWWorld state.mActorId = -1; state.mSpeed = speed; state.mStack = stack; - state.mSoundId = sound; + state.mIdMagic = models; + state.mSoundId = sounds; - // Only interested in "on target" effects - for (std::vector::const_iterator iter (effects.mList.begin()); - iter!=effects.mList.end(); ++iter) - { - if (iter->mRange == ESM::RT_Target) - state.mEffects.mList.push_back(*iter); - } + // Should have already had non-projectile effects removed + state.mEffects = effects; - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), model); - MWWorld::Ptr ptr = ref.getPtr(); + for (int iter = 0; iter != models.size(); ++iter) + { + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), models.at(iter)); + MWWorld::Ptr ptr = ref.getPtr(); - createModel(state, ptr.getClass().getModel(ptr), pos, orient, true); + createModel(state, ptr.getClass().getModel(ptr), pos, orient, true); - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - state.mSound = sndMgr->playSound3D(pos, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if (iter < sounds.size()) + state.mSound = sndMgr->playSound3D(pos, sounds.at(iter), 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + } mMagicBolts.push_back(state); } @@ -173,7 +172,7 @@ namespace MWWorld state.mActorId = actor.getClass().getCreatureStats(actor).getActorId(); state.mBowId = bow.getCellRef().getRefId(); state.mVelocity = orient * osg::Vec3f(0,1,0) * speed; - state.mId = projectile.getCellRef().getRefId(); + state.mIdArrow = projectile.getCellRef().getRefId(); state.mCasterHandle = actor; state.mAttackStrength = attackStrength; @@ -286,7 +285,7 @@ namespace MWWorld { if (result.mHit) { - MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mId); + MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); // Try to get a Ptr to the bow that was used. It might no longer exist. MWWorld::Ptr bow = projectileRef.getPtr(); @@ -338,7 +337,7 @@ namespace MWWorld writer.startRecord(ESM::REC_PROJ); ESM::ProjectileState state; - state.mId = it->mId; + state.mId = it->mIdArrow; state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); state.mActorId = it->mActorId; @@ -357,14 +356,14 @@ namespace MWWorld writer.startRecord(ESM::REC_MPRJ); ESM::MagicBoltState state; - state.mId = it->mId; + state.mId = it->mIdMagic.at(0); state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); state.mActorId = it->mActorId; state.mSpellId = it->mSpellId; state.mEffects = it->mEffects; - state.mSound = it->mSoundId; + state.mSound = it->mSoundId.at(0); state.mSourceName = it->mSourceName; state.mSpeed = it->mSpeed; state.mStack = it->mStack; @@ -386,7 +385,7 @@ namespace MWWorld state.mActorId = esm.mActorId; state.mBowId = esm.mBowId; state.mVelocity = esm.mVelocity; - state.mId = esm.mId; + state.mIdArrow = esm.mId; state.mAttackStrength = esm.mAttackStrength; std::string model; @@ -413,7 +412,7 @@ namespace MWWorld MagicBoltState state; state.mSourceName = esm.mSourceName; - state.mId = esm.mId; + state.mIdMagic.push_back(esm.mId); state.mSpellId = esm.mSpellId; state.mActorId = esm.mActorId; state.mSpeed = esm.mSpeed; @@ -437,7 +436,7 @@ namespace MWWorld MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); state.mSound = sndMgr->playSound3D(esm.mPosition, esm.mSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - state.mSoundId = esm.mSound; + state.mSoundId.push_back(esm.mSound); mMagicBolts.push_back(state); return true; diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 74d4c1dc5..709beb7cf 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -49,7 +49,7 @@ namespace MWWorld MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics); /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. - void launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId, + void launchMagicBolt (const std::vector& models, const std::vector &sounds, const std::string &spellId, float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); @@ -84,8 +84,11 @@ namespace MWWorld MWWorld::Ptr getCaster(); - // MW-id of this projectile - std::string mId; + // MW-ids of a magic projectile + std::vector mIdMagic; + + // MW-id of an arrow projectile + std::string mIdArrow; }; struct MagicBoltState : public State @@ -102,7 +105,7 @@ namespace MWWorld bool mStack; MWBase::SoundPtr mSound; - std::string mSoundId; + std::vector mSoundId; }; struct ProjectileState : public State diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4448a9c17..7b27137e7 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2705,11 +2705,11 @@ namespace MWWorld mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); } - void World::launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId, + void World::launchMagicBolt (const std::vector &models, const std::vector &sounds, const std::string &spellId, float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) { - mProjectileManager->launchMagicBolt(model, sound, spellId, speed, stack, effects, caster, sourceName, fallbackDirection); + mProjectileManager->launchMagicBolt(models, sounds, spellId, speed, stack, effects, caster, sourceName, fallbackDirection); } const std::vector& World::getContentFiles() const diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index da665e27c..eb1ed2458 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -594,7 +594,7 @@ namespace MWWorld */ virtual void castSpell (const MWWorld::Ptr& actor); - virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId, + virtual void launchMagicBolt (const std::vector& models, const std::vector& sounds, const std::string& spellId, float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, From 37f07f74353905201c77347c9f0e09e9551493e9 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 4 Sep 2016 17:39:14 +0900 Subject: [PATCH 23/54] Combine into one multi-effect magic projectile --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 40 +++++++++++++++-------- apps/openmw/mwrender/animation.cpp | 5 +-- apps/openmw/mwworld/projectilemanager.cpp | 30 ++++++++++------- apps/openmw/mwworld/projectilemanager.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 4 +-- apps/openmw/mwworld/worldimp.hpp | 2 +- 7 files changed, 53 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 8dda90b64..4737a0e22 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -486,7 +486,7 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor) = 0; - virtual void launchMagicBolt (const std::vector& models, const std::vector& sounds, const std::string& spellId, + virtual void launchMagicBolt (const std::vector& projectileIDs, const std::vector& sounds, const std::string& spellId, float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) = 0; virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 734509b6b..654e4b51b 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -303,8 +303,8 @@ namespace MWMechanics if (count != 0) speed /= count; - std::string model; - std::vector models; + std::string projectileID; + std::vector projectileIDs; std::string sound; std::vector sounds; ESM::EffectList projectileEffects; @@ -320,10 +320,10 @@ namespace MWMechanics const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( iter->mEffectID); - model = magicEffect->mBolt; - if (model.empty()) - model = "VFX_DefaultBolt"; - models.push_back(model); + projectileID = magicEffect->mBolt; + if (projectileID.empty()) + projectileID = "VFX_DefaultBolt"; + projectileIDs.push_back(projectileID); static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" @@ -335,16 +335,28 @@ namespace MWMechanics sounds.push_back(sound); projectileEffects.mList.push_back(*iter); } + + if (projectileEffects.mList.size() > 1) // add a VFX_Multiple projectile if there are multiple projectile effects + { + std::vector::iterator it; + it = projectileIDs.begin(); + char numstr[8]; + sprintf(numstr, "%zd", (effects.mList.size())); + std::string ID = "VFX_Multiple"; + ID = ID + numstr; + it = projectileIDs.insert(it, ID); + } - // Fall back to a "caster to target" direction if we have no other means of determining it - // (e.g. when cast by a non-actor) - if (!mTarget.isEmpty()) - fallbackDirection = - osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- - osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); + // Fall back to a "caster to target" direction if we have no other means of determining it + // (e.g. when cast by a non-actor) + if (!mTarget.isEmpty()) + fallbackDirection = + osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- + osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getWorld()->launchMagicBolt(models, sounds, mId, speed, - false, projectileEffects, mCaster, mSourceName, fallbackDirection); + if (!projectileEffects.mList.empty()) + MWBase::Environment::get().getWorld()->launchMagicBolt(projectileIDs, sounds, mId, speed, + false, projectileEffects, mCaster, mSourceName, fallbackDirection); } void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 386a4a53b..c65e5bc85 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1296,13 +1296,14 @@ namespace MWRender void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, std::string texture) { if (!mObjectRoot.get()) + { + std::cout << "no objectroot" << std::endl; return; - + } // Early out if we already have this effect for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) return; - EffectParams params; params.mModelName = model; osg::ref_ptr parentNode; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 0825be3c3..1e7b82836 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -94,6 +94,17 @@ namespace MWWorld mResourceSystem->getSceneManager()->getInstance(model, attachTo); + if (state.mIdMagic.size() > 1) + for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) + { + char numstr[8]; + sprintf(numstr, "%zd", iter); + std::string node = "Dummy0"; + node = node + numstr; + const ESM::Weapon* weapon = MWBase::Environment::get().getWorld()->getStore().get().find (state.mIdMagic.at(iter)); + mResourceSystem->getSceneManager()->getInstance("meshes\\" + weapon->mModel, attachTo); + } + SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; state.mNode->accept(disableFreezeOnCullVisitor); @@ -112,7 +123,7 @@ namespace MWWorld state.mEffectAnimationTime->addTime(duration); } - void ProjectileManager::launchMagicBolt(const std::vector &models, const std::vector &sounds, + void ProjectileManager::launchMagicBolt(const std::vector &projectileIDs, const std::vector &sounds, const std::string &spellId, float speed, bool stack, const ESM::EffectList &effects, const Ptr &caster, const std::string &sourceName, const osg::Vec3f& fallbackDirection) @@ -145,23 +156,20 @@ namespace MWWorld state.mActorId = -1; state.mSpeed = speed; state.mStack = stack; - state.mIdMagic = models; + state.mIdMagic = projectileIDs; state.mSoundId = sounds; // Should have already had non-projectile effects removed state.mEffects = effects; - for (int iter = 0; iter != models.size(); ++iter) - { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), models.at(iter)); - MWWorld::Ptr ptr = ref.getPtr(); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectileIDs.at(0)); + MWWorld::Ptr ptr = ref.getPtr(); - createModel(state, ptr.getClass().getModel(ptr), pos, orient, true); + createModel(state, ptr.getClass().getModel(ptr), pos, orient, true); - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if (iter < sounds.size()) - state.mSound = sndMgr->playSound3D(pos, sounds.at(iter), 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - } + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if (projectileIDs.size() == 1) + state.mSound = sndMgr->playSound3D(pos, sounds.at(0), 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); mMagicBolts.push_back(state); } diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 709beb7cf..63bcb70b9 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -49,7 +49,7 @@ namespace MWWorld MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics); /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. - void launchMagicBolt (const std::vector& models, const std::vector &sounds, const std::string &spellId, + void launchMagicBolt (const std::vector& projectileIDs, const std::vector &sounds, const std::string &spellId, float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 7b27137e7..cf5845adf 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2705,11 +2705,11 @@ namespace MWWorld mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); } - void World::launchMagicBolt (const std::vector &models, const std::vector &sounds, const std::string &spellId, + void World::launchMagicBolt (const std::vector &projectileIDs, const std::vector &sounds, const std::string &spellId, float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) { - mProjectileManager->launchMagicBolt(models, sounds, spellId, speed, stack, effects, caster, sourceName, fallbackDirection); + mProjectileManager->launchMagicBolt(projectileIDs, sounds, spellId, speed, stack, effects, caster, sourceName, fallbackDirection); } const std::vector& World::getContentFiles() const diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index eb1ed2458..f26e2b9c0 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -594,7 +594,7 @@ namespace MWWorld */ virtual void castSpell (const MWWorld::Ptr& actor); - virtual void launchMagicBolt (const std::vector& models, const std::vector& sounds, const std::string& spellId, + virtual void launchMagicBolt (const std::vector& projectileIDs, const std::vector& sounds, const std::string& spellId, float speed, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, From f49ebee56a2f2390e52a5bac485f0b138cf9105d Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 4 Sep 2016 23:57:06 +0900 Subject: [PATCH 24/54] Play sounds for multi-effect projectile --- apps/openmw/mwworld/projectilemanager.cpp | 29 ++++++++++++++++------- apps/openmw/mwworld/projectilemanager.hpp | 2 +- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 1e7b82836..f5d229f09 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -168,9 +168,11 @@ namespace MWWorld createModel(state, ptr.getClass().getModel(ptr), pos, orient, true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if (projectileIDs.size() == 1) - state.mSound = sndMgr->playSound3D(pos, sounds.at(0), 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); - + for (size_t it = 0; it != sounds.size(); it++) + { + state.mSound.push_back(sndMgr->playSound3D(pos, sounds.at(it), 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop)); + } + mMagicBolts.push_back(state); } @@ -212,8 +214,10 @@ namespace MWWorld osg::Vec3f pos(it->mNode->getPosition()); osg::Vec3f newPos = pos + direction * duration * speed; - if (it->mSound.get()) - it->mSound->setPosition(newPos); + for (size_t soundIter = 0; soundIter != it->mSound.size(); soundIter++) + { + it->mSound.at(soundIter)->setPosition(newPos); + } it->mNode->setPosition(newPos); @@ -253,7 +257,11 @@ namespace MWWorld MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject, ESM::RT_Target, it->mSpellId, it->mSourceName); - MWBase::Environment::get().getSoundManager()->stopSound(it->mSound); + for (size_t soundIter = 0; soundIter != it->mSound.size(); soundIter++) + { + MWBase::Environment::get().getSoundManager()->stopSound(it->mSound.at(soundIter)); + } + mParent->removeChild(it->mNode); it = mMagicBolts.erase(it); @@ -333,7 +341,10 @@ namespace MWWorld for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) { mParent->removeChild(it->mNode); - MWBase::Environment::get().getSoundManager()->stopSound(it->mSound); + for (size_t soundIter = 0; soundIter != it->mSound.size(); soundIter++) + { + MWBase::Environment::get().getSoundManager()->stopSound(it->mSound.at(soundIter)); + } } mMagicBolts.clear(); } @@ -442,8 +453,8 @@ namespace MWWorld createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - state.mSound = sndMgr->playSound3D(esm.mPosition, esm.mSound, 1.0f, 1.0f, - MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + state.mSound.push_back(sndMgr->playSound3D(esm.mPosition, esm.mSound, 1.0f, 1.0f, + MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop)); state.mSoundId.push_back(esm.mSound); mMagicBolts.push_back(state); diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 63bcb70b9..8d330d5a8 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -104,7 +104,7 @@ namespace MWWorld bool mStack; - MWBase::SoundPtr mSound; + std::vector mSound; std::vector mSoundId; }; From 60384399eed24f86426e1d96159f1ebebc29ab9a Mon Sep 17 00:00:00 2001 From: Allofich Date: Mon, 5 Sep 2016 00:49:22 +0900 Subject: [PATCH 25/54] Fix errors and warnings --- apps/openmw/mwmechanics/spellcasting.cpp | 3 ++- apps/openmw/mwworld/projectilemanager.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 654e4b51b..1b2fbfdc8 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -341,7 +342,7 @@ namespace MWMechanics std::vector::iterator it; it = projectileIDs.begin(); char numstr[8]; - sprintf(numstr, "%zd", (effects.mList.size())); + sprintf(numstr, "%d", (int)(effects.mList.size())); std::string ID = "VFX_Multiple"; ID = ID + numstr; it = projectileIDs.insert(it, ID); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index f5d229f09..a7b4be9c9 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -1,5 +1,7 @@ #include "projectilemanager.hpp" +#include + #include #include @@ -92,13 +94,13 @@ namespace MWWorld attachTo = rotateNode; } - mResourceSystem->getSceneManager()->getInstance(model, attachTo); + osg::ref_ptr ptr = mResourceSystem->getSceneManager()->getInstance(model, attachTo); if (state.mIdMagic.size() > 1) for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) { char numstr[8]; - sprintf(numstr, "%zd", iter); + sprintf(numstr, "%d", (int)iter); std::string node = "Dummy0"; node = node + numstr; const ESM::Weapon* weapon = MWBase::Environment::get().getWorld()->getStore().get().find (state.mIdMagic.at(iter)); From a36f7babc1c219016d049f353a4aa20c0254b733 Mon Sep 17 00:00:00 2001 From: Allofich Date: Mon, 5 Sep 2016 02:14:37 +0900 Subject: [PATCH 26/54] Double scaling of spell explosions --- apps/openmw/mwworld/worldimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5652b2d78..6c5fae014 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3173,7 +3173,7 @@ namespace MWWorld else areaStatic = getStore().get().find ("VFX_DefaultArea"); - mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, static_cast(effectIt->mArea)); + mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, static_cast(effectIt->mArea * 2)); // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) static const std::string schools[] = { From 5a0d3feb98a44e7dd7618f5077eea6d102bba12f Mon Sep 17 00:00:00 2001 From: Allofich Date: Mon, 5 Sep 2016 02:52:00 +0900 Subject: [PATCH 27/54] Use C++ int-to-string conversion --- apps/openmw/mwmechanics/spellcasting.cpp | 10 ++++------ apps/openmw/mwworld/projectilemanager.cpp | 8 +++----- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 1b2fbfdc8..cb4e04fa1 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include @@ -339,13 +339,11 @@ namespace MWMechanics if (projectileEffects.mList.size() > 1) // add a VFX_Multiple projectile if there are multiple projectile effects { + std::ostringstream ID; + ID << "VFX_Multiple" << projectileEffects.mList.size(); std::vector::iterator it; it = projectileIDs.begin(); - char numstr[8]; - sprintf(numstr, "%d", (int)(effects.mList.size())); - std::string ID = "VFX_Multiple"; - ID = ID + numstr; - it = projectileIDs.insert(it, ID); + it = projectileIDs.insert(it, ID.str()); } // Fall back to a "caster to target" direction if we have no other means of determining it diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index a7b4be9c9..ec4ee6941 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -1,6 +1,6 @@ #include "projectilemanager.hpp" -#include +#include #include @@ -99,10 +99,8 @@ namespace MWWorld if (state.mIdMagic.size() > 1) for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) { - char numstr[8]; - sprintf(numstr, "%d", (int)iter); - std::string node = "Dummy0"; - node = node + numstr; + std::ostringstream nodeName; + nodeName << "Dummy" << std::setw(2) << std::setfill('0') << iter; const ESM::Weapon* weapon = MWBase::Environment::get().getWorld()->getStore().get().find (state.mIdMagic.at(iter)); mResourceSystem->getSceneManager()->getInstance("meshes\\" + weapon->mModel, attachTo); } From c6cd1f813b5384e4f4b229d5f10337a7dc94a8d7 Mon Sep 17 00:00:00 2001 From: Allofich Date: Mon, 5 Sep 2016 02:59:33 +0900 Subject: [PATCH 28/54] Attach projectiles to nodes of multi-effect bolts --- apps/openmw/mwworld/projectilemanager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index ec4ee6941..d55173ee4 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -102,7 +102,10 @@ namespace MWWorld std::ostringstream nodeName; nodeName << "Dummy" << std::setw(2) << std::setfill('0') << iter; const ESM::Weapon* weapon = MWBase::Environment::get().getWorld()->getStore().get().find (state.mIdMagic.at(iter)); - mResourceSystem->getSceneManager()->getInstance("meshes\\" + weapon->mModel, attachTo); + SceneUtil::FindByNameVisitor findVisitor(nodeName.str()); + attachTo->accept(findVisitor); + if (findVisitor.mFoundNode) + mResourceSystem->getSceneManager()->getInstance("meshes\\" + weapon->mModel, findVisitor.mFoundNode); } SceneUtil::DisableFreezeOnCullVisitor disableFreezeOnCullVisitor; From dbd7c038b28d19bf74a7cbdc7313ade95316b536 Mon Sep 17 00:00:00 2001 From: Allofich Date: Mon, 5 Sep 2016 03:31:48 +0900 Subject: [PATCH 29/54] Add loading code for multi-effect projectiles --- apps/openmw/mwmechanics/spellcasting.cpp | 17 +++---- apps/openmw/mwrender/animation.cpp | 5 +- apps/openmw/mwworld/projectilemanager.cpp | 60 ++++++++++++++++++----- apps/openmw/mwworld/projectilemanager.hpp | 4 +- 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index cb4e04fa1..8736379f2 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -304,9 +304,7 @@ namespace MWMechanics if (count != 0) speed /= count; - std::string projectileID; std::vector projectileIDs; - std::string sound; std::vector sounds; ESM::EffectList projectileEffects; @@ -321,23 +319,22 @@ namespace MWMechanics const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( iter->mEffectID); - projectileID = magicEffect->mBolt; - if (projectileID.empty()) - projectileID = "VFX_DefaultBolt"; - projectileIDs.push_back(projectileID); + if (magicEffect->mBolt.empty()) + projectileIDs.push_back("VFX_DefaultBolt"); + else + projectileIDs.push_back(magicEffect->mBolt); static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" }; if (!magicEffect->mBoltSound.empty()) - sound = magicEffect->mBoltSound; + sounds.push_back(magicEffect->mBoltSound); else - sound = schools[magicEffect->mData.mSchool] + " bolt"; - sounds.push_back(sound); + sounds.push_back(schools[magicEffect->mData.mSchool] + " bolt"); projectileEffects.mList.push_back(*iter); } - if (projectileEffects.mList.size() > 1) // add a VFX_Multiple projectile if there are multiple projectile effects + if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects { std::ostringstream ID; ID << "VFX_Multiple" << projectileEffects.mList.size(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index c65e5bc85..386a4a53b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1296,14 +1296,13 @@ namespace MWRender void Animation::addEffect (const std::string& model, int effectId, bool loop, const std::string& bonename, std::string texture) { if (!mObjectRoot.get()) - { - std::cout << "no objectroot" << std::endl; return; - } + // Early out if we already have this effect for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) return; + EffectParams params; params.mModelName = model; osg::ref_ptr parentNode; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index d55173ee4..ffb2063c2 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -160,7 +160,7 @@ namespace MWWorld state.mSpeed = speed; state.mStack = stack; state.mIdMagic = projectileIDs; - state.mSoundId = sounds; + state.mSoundIds = sounds; // Should have already had non-projectile effects removed state.mEffects = effects; @@ -173,7 +173,7 @@ namespace MWWorld MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (size_t it = 0; it != sounds.size(); it++) { - state.mSound.push_back(sndMgr->playSound3D(pos, sounds.at(it), 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop)); + state.mSounds.push_back(sndMgr->playSound3D(pos, sounds.at(it), 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop)); } mMagicBolts.push_back(state); @@ -217,9 +217,9 @@ namespace MWWorld osg::Vec3f pos(it->mNode->getPosition()); osg::Vec3f newPos = pos + direction * duration * speed; - for (size_t soundIter = 0; soundIter != it->mSound.size(); soundIter++) + for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++) { - it->mSound.at(soundIter)->setPosition(newPos); + it->mSounds.at(soundIter)->setPosition(newPos); } it->mNode->setPosition(newPos); @@ -260,9 +260,9 @@ namespace MWWorld MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject, ESM::RT_Target, it->mSpellId, it->mSourceName); - for (size_t soundIter = 0; soundIter != it->mSound.size(); soundIter++) + for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++) { - MWBase::Environment::get().getSoundManager()->stopSound(it->mSound.at(soundIter)); + MWBase::Environment::get().getSoundManager()->stopSound(it->mSounds.at(soundIter)); } mParent->removeChild(it->mNode); @@ -344,9 +344,9 @@ namespace MWWorld for (std::vector::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) { mParent->removeChild(it->mNode); - for (size_t soundIter = 0; soundIter != it->mSound.size(); soundIter++) + for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++) { - MWBase::Environment::get().getSoundManager()->stopSound(it->mSound.at(soundIter)); + MWBase::Environment::get().getSoundManager()->stopSound(it->mSounds.at(soundIter)); } } mMagicBolts.clear(); @@ -385,7 +385,7 @@ namespace MWWorld state.mSpellId = it->mSpellId; state.mEffects = it->mEffects; - state.mSound = it->mSoundId.at(0); + state.mSound = it->mSoundIds.at(0); state.mSourceName = it->mSourceName; state.mSpeed = it->mSpeed; state.mStack = it->mStack; @@ -441,10 +441,41 @@ namespace MWWorld state.mStack = esm.mStack; state.mEffects = esm.mEffects; + std::string projectileID; + std::vector projectileIDs; + + if (esm.mEffects.mList.size() > 1) + { + std::ostringstream ID; + ID << "VFX_Multiple" << esm.mEffects.mList.size(); + state.mIdMagic.push_back(ID.str()); + } + + for (std::vector::const_iterator iter (esm.mEffects.mList.begin()); + iter != esm.mEffects.mList.end(); ++iter) + { + const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( + iter->mEffectID); + + projectileID = magicEffect->mBolt; + if (projectileID.empty()) + projectileID = "VFX_DefaultBolt"; + state.mIdMagic.push_back(projectileID); + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + if (!magicEffect->mBoltSound.empty()) + state.mSoundIds.push_back(magicEffect->mBoltSound); + else + state.mSoundIds.push_back(schools[magicEffect->mData.mSchool] + " bolt"); + } + std::string model; try { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); model = ptr.getClass().getModel(ptr); } @@ -456,9 +487,12 @@ namespace MWWorld createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - state.mSound.push_back(sndMgr->playSound3D(esm.mPosition, esm.mSound, 1.0f, 1.0f, - MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop)); - state.mSoundId.push_back(esm.mSound); + + for (size_t soundIter = 0; soundIter != state.mSoundIds.size(); soundIter++) + { + state.mSounds.push_back(sndMgr->playSound3D(esm.mPosition, state.mSoundIds.at(soundIter), 1.0f, 1.0f, + MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop)); + } mMagicBolts.push_back(state); return true; diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 8d330d5a8..46a6b10de 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -104,8 +104,8 @@ namespace MWWorld bool mStack; - std::vector mSound; - std::vector mSoundId; + std::vector mSounds; + std::vector mSoundIds; }; struct ProjectileState : public State From f8270f6bd573b90660ca05a3e4effb0e76905918 Mon Sep 17 00:00:00 2001 From: Allofich Date: Mon, 5 Sep 2016 04:22:57 +0900 Subject: [PATCH 30/54] Consolidations and cleanup for multi-effect spells --- apps/openmw/mwbase/world.hpp | 5 +- apps/openmw/mwmechanics/spellcasting.cpp | 67 ++------------ apps/openmw/mwmechanics/spellcasting.hpp | 5 +- apps/openmw/mwworld/projectilemanager.cpp | 105 ++++++++++++---------- apps/openmw/mwworld/projectilemanager.hpp | 5 +- apps/openmw/mwworld/worldimp.cpp | 7 +- apps/openmw/mwworld/worldimp.hpp | 5 +- 7 files changed, 77 insertions(+), 122 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 4737a0e22..c78bd2053 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -486,9 +486,8 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor) = 0; - virtual void launchMagicBolt (const std::vector& projectileIDs, const std::vector& sounds, const std::string& spellId, - float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) = 0; + virtual void launchMagicBolt (const std::string& spellId, bool stack, const ESM::EffectList& effects, + const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) = 0; virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) = 0; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 8736379f2..f1c920421 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -285,64 +285,10 @@ namespace MWMechanics /// Get projectile properties (model, sound and speed) for a spell with the given effects and launch. /// If \a model is empty, the spell has no ranged effects and should not spawn a projectile. - void CastSpell::getProjectileInfoAndLaunch (const ESM::EffectList& effects) - { - // All projectiles should use the same speed. From observations in the - // original engine, this seems to be the average of the constituent effects. - // First we get this average speed. - float speed = 0; - int count = 0; - for (std::vector::const_iterator iter (effects.mList.begin()); - iter!=effects.mList.end(); ++iter) - { - const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - iter->mEffectID); - speed += magicEffect->mData.mSpeed; - count++; - } - - if (count != 0) - speed /= count; - - std::vector projectileIDs; - std::vector sounds; - ESM::EffectList projectileEffects; - + void CastSpell::launchMagicBolt (const ESM::EffectList& effects) + { osg::Vec3f fallbackDirection (0,1,0); - for (std::vector::const_iterator iter (effects.mList.begin()); - iter!=effects.mList.end(); ++iter) - { - if (iter->mRange != ESM::RT_Target) - continue; - - const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - iter->mEffectID); - - if (magicEffect->mBolt.empty()) - projectileIDs.push_back("VFX_DefaultBolt"); - else - projectileIDs.push_back(magicEffect->mBolt); - - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - if (!magicEffect->mBoltSound.empty()) - sounds.push_back(magicEffect->mBoltSound); - else - sounds.push_back(schools[magicEffect->mData.mSchool] + " bolt"); - projectileEffects.mList.push_back(*iter); - } - - if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects - { - std::ostringstream ID; - ID << "VFX_Multiple" << projectileEffects.mList.size(); - std::vector::iterator it; - it = projectileIDs.begin(); - it = projectileIDs.insert(it, ID.str()); - } - // Fall back to a "caster to target" direction if we have no other means of determining it // (e.g. when cast by a non-actor) if (!mTarget.isEmpty()) @@ -350,9 +296,8 @@ namespace MWMechanics osg::Vec3f(mTarget.getRefData().getPosition().asVec3())- osg::Vec3f(mCaster.getRefData().getPosition().asVec3()); - if (!projectileEffects.mList.empty()) - MWBase::Environment::get().getWorld()->launchMagicBolt(projectileIDs, sounds, mId, speed, - false, projectileEffects, mCaster, mSourceName, fallbackDirection); + MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, effects, + mCaster, mSourceName, fallbackDirection); } void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, @@ -823,7 +768,7 @@ namespace MWMechanics } if (launchProjectile) - getProjectileInfoAndLaunch(enchantment->mEffects); + launchMagicBolt(enchantment->mEffects); else if (!mTarget.isEmpty()) inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target); @@ -906,7 +851,7 @@ namespace MWMechanics if (!mTarget.isEmpty()) inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); - getProjectileInfoAndLaunch(spell->mEffects); + launchMagicBolt(spell->mEffects); return true; } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index e300b1818..85277401f 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -94,9 +94,8 @@ namespace MWMechanics void playSpellCastingEffects(const std::string &spellid); - /// Get the models, sounds and speeds for all projectiles - /// in the given effects, and launch them. - void getProjectileInfoAndLaunch (const ESM::EffectList& effects); + /// Launch a bolt with the given effects. + void launchMagicBolt (const ESM::EffectList& effects); /// @note \a target can be any type of object, not just actors. /// @note \a caster can be any type of object, or even an empty object. diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index ffb2063c2..509cee80c 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -35,6 +35,55 @@ #include "../mwphysics/physicssystem.hpp" +namespace +{ + ESM::EffectList getMagicBoltData(std::vector& projectileIDs, std::vector& sounds, float& speed, const ESM::EffectList& effects) + { + int count = 0; + ESM::EffectList projectileEffects; + for (std::vector::const_iterator iter (effects.mList.begin()); + iter!=effects.mList.end(); ++iter) + { + const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( + iter->mEffectID); + + // All the projectiles should use the same speed. From observations in the + // original engine, this seems to be the average of the constituent effects. + speed += magicEffect->mData.mSpeed; + count++; + + if (iter->mRange != ESM::RT_Target) + continue; + + if (magicEffect->mBolt.empty()) + projectileIDs.push_back("VFX_DefaultBolt"); + else + projectileIDs.push_back(magicEffect->mBolt); + + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + if (!magicEffect->mBoltSound.empty()) + sounds.push_back(magicEffect->mBoltSound); + else + sounds.push_back(schools[magicEffect->mData.mSchool] + " bolt"); + projectileEffects.mList.push_back(*iter); + } + + if (count != 0) + speed /= count; + + if (projectileEffects.mList.size() > 1) // insert a VFX_Multiple projectile if there are multiple projectile effects + { + std::ostringstream ID; + ID << "VFX_Multiple" << effects.mList.size(); + std::vector::iterator it; + it = projectileIDs.begin(); + it = projectileIDs.insert(it, ID.str()); + } + return projectileEffects; + } +} namespace MWWorld { @@ -126,10 +175,8 @@ namespace MWWorld state.mEffectAnimationTime->addTime(duration); } - void ProjectileManager::launchMagicBolt(const std::vector &projectileIDs, const std::vector &sounds, - const std::string &spellId, float speed, bool stack, - const ESM::EffectList &effects, const Ptr &caster, const std::string &sourceName, - const osg::Vec3f& fallbackDirection) + void ProjectileManager::launchMagicBolt(const std::string &spellId, bool stack, const ESM::EffectList &effects, const Ptr &caster, + const std::string &sourceName, const osg::Vec3f& fallbackDirection) { osg::Vec3f pos = caster.getRefData().getPosition().asVec3(); if (caster.getClass().isActor()) @@ -157,23 +204,23 @@ namespace MWWorld state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); else state.mActorId = -1; - state.mSpeed = speed; state.mStack = stack; - state.mIdMagic = projectileIDs; - state.mSoundIds = sounds; - // Should have already had non-projectile effects removed - state.mEffects = effects; + state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, effects); + + // Non-projectile should have been removed by getMagicBoltData + if (state.mEffects.mList.size() == 0) + return; - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectileIDs.at(0)); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); MWWorld::Ptr ptr = ref.getPtr(); createModel(state, ptr.getClass().getModel(ptr), pos, orient, true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - for (size_t it = 0; it != sounds.size(); it++) + for (size_t it = 0; it != state.mSoundIds.size(); it++) { - state.mSounds.push_back(sndMgr->playSound3D(pos, sounds.at(it), 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop)); + state.mSounds.push_back(sndMgr->playSound3D(pos, state.mSoundIds.at(it), 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop)); } mMagicBolts.push_back(state); @@ -437,40 +484,8 @@ namespace MWWorld state.mIdMagic.push_back(esm.mId); state.mSpellId = esm.mSpellId; state.mActorId = esm.mActorId; - state.mSpeed = esm.mSpeed; state.mStack = esm.mStack; - state.mEffects = esm.mEffects; - - std::string projectileID; - std::vector projectileIDs; - - if (esm.mEffects.mList.size() > 1) - { - std::ostringstream ID; - ID << "VFX_Multiple" << esm.mEffects.mList.size(); - state.mIdMagic.push_back(ID.str()); - } - - for (std::vector::const_iterator iter (esm.mEffects.mList.begin()); - iter != esm.mEffects.mList.end(); ++iter) - { - const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - iter->mEffectID); - - projectileID = magicEffect->mBolt; - if (projectileID.empty()) - projectileID = "VFX_DefaultBolt"; - state.mIdMagic.push_back(projectileID); - - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - if (!magicEffect->mBoltSound.empty()) - state.mSoundIds.push_back(magicEffect->mBoltSound); - else - state.mSoundIds.push_back(schools[magicEffect->mData.mSchool] + " bolt"); - } + state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, esm.mEffects); std::string model; try diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 46a6b10de..55ce0cd12 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -49,9 +49,8 @@ namespace MWWorld MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics); /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. - void launchMagicBolt (const std::vector& projectileIDs, const std::vector &sounds, const std::string &spellId, - float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); + void launchMagicBolt (const std::string &spellId, bool stack, const ESM::EffectList& effects, + const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& pos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index cf5845adf..4cb75b8c6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2705,11 +2705,10 @@ namespace MWWorld mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); } - void World::launchMagicBolt (const std::vector &projectileIDs, const std::vector &sounds, const std::string &spellId, - float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) + void World::launchMagicBolt (const std::string &spellId, bool stack, const ESM::EffectList& effects, + const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection) { - mProjectileManager->launchMagicBolt(projectileIDs, sounds, spellId, speed, stack, effects, caster, sourceName, fallbackDirection); + mProjectileManager->launchMagicBolt(spellId, stack, effects, caster, sourceName, fallbackDirection); } const std::vector& World::getContentFiles() const diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index f26e2b9c0..8774e549f 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -594,9 +594,8 @@ namespace MWWorld */ virtual void castSpell (const MWWorld::Ptr& actor); - virtual void launchMagicBolt (const std::vector& projectileIDs, const std::vector& sounds, const std::string& spellId, - float speed, bool stack, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); + virtual void launchMagicBolt (const std::string& spellId, bool stack, const ESM::EffectList& effects, + const MWWorld::Ptr& caster, const std::string& sourceName, const osg::Vec3f& fallbackDirection); virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength); From c617e9075528627a33b6c0ba809c564cb75e077a Mon Sep 17 00:00:00 2001 From: Allofich Date: Mon, 5 Sep 2016 05:41:24 +0900 Subject: [PATCH 31/54] Fix for loading projectile speed --- apps/openmw/mwworld/projectilemanager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 509cee80c..aa5a1b650 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -486,6 +486,10 @@ namespace MWWorld state.mActorId = esm.mActorId; state.mStack = esm.mStack; state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, esm.mEffects); + state.mSpeed = esm.mSpeed; // speed is derived from non-projectile effects as well as + // projectile effects, so we can't calculate it from the save + // file's effect list, which is already trimmed of non-projectile + // effects. We need to use the stored value. std::string model; try From fbc9b90ebe01c02fd313d244f6f458393f41ad64 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 5 Sep 2016 00:04:11 +0200 Subject: [PATCH 32/54] Cleanup --- apps/openmw/mwmechanics/spellcasting.cpp | 2 -- apps/openmw/mwworld/projectilemanager.cpp | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index f1c920421..95f2d940f 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -283,8 +283,6 @@ namespace MWMechanics { } - /// Get projectile properties (model, sound and speed) for a spell with the given effects and launch. - /// If \a model is empty, the spell has no ranged effects and should not spawn a projectile. void CastSpell::launchMagicBolt (const ESM::EffectList& effects) { osg::Vec3f fallbackDirection (0,1,0); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index aa5a1b650..844800a41 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -143,7 +143,7 @@ namespace MWWorld attachTo = rotateNode; } - osg::ref_ptr ptr = mResourceSystem->getSceneManager()->getInstance(model, attachTo); + mResourceSystem->getSceneManager()->getInstance(model, attachTo); if (state.mIdMagic.size() > 1) for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) @@ -209,7 +209,7 @@ namespace MWWorld state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, effects); // Non-projectile should have been removed by getMagicBoltData - if (state.mEffects.mList.size() == 0) + if (state.mEffects.mList.empty()) return; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0)); From a22fc43947bcbc09385474a39d7e5f4341129657 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Mon, 5 Sep 2016 15:18:34 +0300 Subject: [PATCH 33/54] aiwander: reset path on cell change remove redundant mIsWanderDestReady var --- apps/openmw/mwmechanics/aiwander.cpp | 12 +++--------- apps/openmw/mwmechanics/aiwander.hpp | 2 -- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 1ec9444aa..2a591d768 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -113,7 +113,7 @@ namespace MWMechanics AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), - mRepeat(repeat), mStoredInitialActorPosition(false), mIsWanderDestReady(false) + mRepeat(repeat), mStoredInitialActorPosition(false) { mIdle.resize(8, 0); init(); @@ -191,7 +191,6 @@ namespace MWMechanics { // get or create temporary storage AiWanderStorage& storage = state.get(); - const MWWorld::CellStore*& currentCell = storage.mCell; MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor); @@ -201,6 +200,7 @@ namespace MWMechanics bool cellChange = currentCell && (actor.getCell() != currentCell); if(!currentCell || cellChange) { + stopWalking(actor, storage); currentCell = actor.getCell(); storage.mPopulateAvailableNodes = true; } @@ -344,7 +344,6 @@ namespace MWMechanics if (mPathFinder.isPathConstructed()) { - mIsWanderDestReady = true; storage.setState(Wander_Walking); } } @@ -380,7 +379,6 @@ namespace MWMechanics if (mPathFinder.isPathConstructed()) { - mIsWanderDestReady = true; storage.setState(Wander_Walking, true); } return; @@ -476,7 +474,7 @@ namespace MWMechanics float duration, AiWanderStorage& storage, ESM::Position& pos) { // Are we there yet? - if (mIsWanderDestReady && pathTo(actor, mPathFinder.getPath().back(), duration, DESTINATION_TOLERANCE)) + if (pathTo(actor, mPathFinder.getPath().back(), duration, DESTINATION_TOLERANCE)) { stopWalking(actor, storage); storage.setState(Wander_ChooseAction); @@ -659,8 +657,6 @@ namespace MWMechanics if (mPathFinder.isPathConstructed()) { - mIsWanderDestReady = true; - // Remove this node as an option and add back the previously used node (stops NPC from picking the same node): ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode]; storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode); @@ -717,7 +713,6 @@ namespace MWMechanics void AiWander::stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage) { mPathFinder.clearPath(); - mIsWanderDestReady = false; actor.getClass().getMovementSettings(actor).mPosition[1] = 0; } @@ -950,7 +945,6 @@ namespace MWMechanics , mTimeOfDay(wander->mData.mTimeOfDay) , mRepeat(wander->mData.mShouldRepeat != 0) , mStoredInitialActorPosition(wander->mStoredInitialActorPosition) - , mIsWanderDestReady(false) { if (mStoredInitialActorPosition) mInitialActorPosition = wander->mInitialActorPosition; diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index a46b3cbad..da7553ca0 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -108,8 +108,6 @@ namespace MWMechanics osg::Vec3f mInitialActorPosition; bool mStoredInitialActorPosition; - bool mIsWanderDestReady; - void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage); void trimAllowedNodes(std::vector& nodes, const PathFinder& pathfinder); From aa441f26486752e2ae95b3aed57bcee25587e185 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Tue, 6 Sep 2016 01:11:10 +0300 Subject: [PATCH 34/54] AiPackage: fix path recalc on cell change AiTravel: remove unneeded code --- apps/openmw/mwmechanics/aipackage.cpp | 6 +++--- apps/openmw/mwmechanics/aipackage.hpp | 2 +- apps/openmw/mwmechanics/aitravel.cpp | 16 ---------------- apps/openmw/mwmechanics/aitravel.hpp | 7 ------- apps/openmw/mwmechanics/pathfinding.cpp | 4 ++++ apps/openmw/mwmechanics/pathfinding.hpp | 2 ++ 6 files changed, 10 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 1131b5e6f..9f6ef2597 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -101,7 +101,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr if (!mIsShortcutting) { - if (wasShortcutting || doesPathNeedRecalc(dest)) // if need to rebuild path + if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path { mPathFinder.buildSyncedPath(start, dest, actor.getCell()); @@ -257,9 +257,9 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& return isClear; } -bool MWMechanics::AiPackage::doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest) +bool MWMechanics::AiPackage::doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell) { - return mPathFinder.getPath().empty() || (distance(mPathFinder.getPath().back(), newDest) > 10); + return mPathFinder.getPath().empty() || (distance(mPathFinder.getPath().back(), newDest) > 10) || mPathFinder.getPathCell() != currentCell; } bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 4feb13fe0..c0df76c32 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -111,7 +111,7 @@ namespace MWMechanics /// Check if the way to the destination is clear, taking into account actor speed bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); - virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest); + virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell); void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 1585a3007..7cef7aff1 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -30,15 +30,11 @@ namespace MWMechanics { AiTravel::AiTravel(float x, float y, float z) : mX(x),mY(y),mZ(z) - , mCellX(std::numeric_limits::max()) - , mCellY(std::numeric_limits::max()) { } AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ) - , mCellX(std::numeric_limits::max()) - , mCellY(std::numeric_limits::max()) { } @@ -66,18 +62,6 @@ namespace MWMechanics return false; } - bool AiTravel::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) - { - bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; - if (!mPathFinder.isPathConstructed() || cellChange) - { - mCellX = cell->mData.mX; - mCellY = cell->mData.mY; - return true; - } - return false; - } - int AiTravel::getTypeId() const { return TypeIdTravel; diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 9f263fd46..8c75bded1 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -34,17 +34,10 @@ namespace MWMechanics virtual int getTypeId() const; - protected: - virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); - private: float mX; float mY; float mZ; - - int mCellX; - int mCellY; - }; } diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 85e120d18..0c7e9cdba 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -328,4 +328,8 @@ namespace MWMechanics } } + const MWWorld::CellStore* PathFinder::getPathCell() const + { + return mCell; + } } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 64608979b..83c56ca7d 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -79,6 +79,8 @@ namespace MWMechanics return mPath; } + const MWWorld::CellStore* getPathCell() const; + /** Synchronize new path with old one to avoid visiting 1 waypoint 2 times @note BuildPath() takes closest PathGrid point to NPC as first point of path. From 03a35c38df9a8fc788d98978f1ab73f98f8d1866 Mon Sep 17 00:00:00 2001 From: Marc Zinnschlag Date: Tue, 6 Sep 2016 16:33:26 +0200 Subject: [PATCH 35/54] add missing item when executing the Equip instruction --- apps/openmw/mwscript/containerextensions.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 366e88f1e..0b4c9a0fc 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -102,7 +102,7 @@ namespace MWScript || ::Misc::StringUtils::ciEqual(item, "gold_025") || ::Misc::StringUtils::ciEqual(item, "gold_100")) item = "gold_001"; - + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); runtime.push (store.count(item)); @@ -136,7 +136,7 @@ namespace MWScript || ::Misc::StringUtils::ciEqual(item, "gold_025") || ::Misc::StringUtils::ciEqual(item, "gold_100")) item = "gold_001"; - + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore (ptr); std::string itemName; @@ -188,7 +188,11 @@ namespace MWScript break; } if (it == invStore.end()) - throw std::runtime_error("Item to equip not found"); + { + it = ptr.getClass().getContainerStore (ptr).add (item, 1, ptr); + std::cerr << "Implicitly adding one " << item << " to container " + "to fulfil requirements of Equip instruction" << std::endl; + } if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr()) MWBase::Environment::get().getWindowManager()->useItem(*it); From 88d992a0200abd14f8b3814d0bd0a8f1d9259362 Mon Sep 17 00:00:00 2001 From: Allofich Date: Thu, 8 Sep 2016 00:05:45 +0900 Subject: [PATCH 36/54] Create area effect visual for non-area spells --- apps/openmw/mwworld/worldimp.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4ebd41662..152a2f5b4 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3162,9 +3162,8 @@ namespace MWWorld { const ESM::MagicEffect* effect = getStore().get().find(effectIt->mEffectID); - if (effectIt->mArea <= 0 || effectIt->mRange != rangeType) - continue; // Not an area effect or not right range type - + if (effectIt->mRange != rangeType) + continue; // Not right range type // Spawn the explosion orb effect const ESM::Static* areaStatic; @@ -3173,7 +3172,13 @@ namespace MWWorld else areaStatic = getStore().get().find ("VFX_DefaultArea"); - mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, static_cast(effectIt->mArea * 2)); + if (effectIt->mArea <= 0) + { + mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, 1.0f); + continue; + } + else + mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, static_cast(effectIt->mArea * 2)); // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) static const std::string schools[] = { From 02610828c1e41739fbb757ca2c07c8a3a8639a6f Mon Sep 17 00:00:00 2001 From: Allofich Date: Thu, 8 Sep 2016 02:05:24 +0900 Subject: [PATCH 37/54] Don't play area vfx for non-area spells on actors. --- apps/openmw/mwworld/worldimp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 152a2f5b4..82f99b7ea 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3162,8 +3162,8 @@ namespace MWWorld { const ESM::MagicEffect* effect = getStore().get().find(effectIt->mEffectID); - if (effectIt->mRange != rangeType) - continue; // Not right range type + if ((effectIt->mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()) || effectIt->mRange != rangeType) + continue; // Not right range type, or not area effect and hit an actor // Spawn the explosion orb effect const ESM::Static* areaStatic; From c01d9e9ec5c4b872130de6d6810726b98ef7776c Mon Sep 17 00:00:00 2001 From: "Alexander \"Ace\" Olofsson" Date: Thu, 8 Sep 2016 02:19:12 +0200 Subject: [PATCH 38/54] Only package the Qt 5 platform DLLs on Qt 5 --- CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e028844c8..439644bd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -441,10 +441,11 @@ if(WIN32) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/Plugin_MyGUI_OpenMW_Resources.dll" DESTINATION ".") ENDIF(BUILD_MYGUI_PLUGIN) - INSTALL(DIRECTORY - "${OpenMW_BINARY_DIR}/Release/platforms" - "${OpenMW_BINARY_DIR}/resources" - DESTINATION ".") + IF(DESIRED_QT_VERSION MATCHES 5) + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/Release/platforms" DESTINATION ".") + ENDIF() + + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION ".") FILE(GLOB plugin_dir "${OpenMW_BINARY_DIR}/Release/osgPlugins-*") INSTALL(DIRECTORY ${plugin_dir} DESTINATION ".") From 72786fef9d935df0d2b28dd74d1beafc8f134d7f Mon Sep 17 00:00:00 2001 From: mrcheko Date: Fri, 9 Sep 2016 23:57:19 +0300 Subject: [PATCH 39/54] prevent running in circles around path points addresses http://bugs.openmw.org/issues/2229 --- apps/openmw/mwmechanics/aipackage.cpp | 43 +++++++++++++++++++++++++-- apps/openmw/mwmechanics/aipackage.hpp | 5 ++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 9f6ef2597..fa952c414 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -19,12 +19,15 @@ #include "actorutil.hpp" #include "coordinateconverter.hpp" +#include + MWMechanics::AiPackage::~AiPackage() {} MWMechanics::AiPackage::AiPackage() : mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild mIsShortcutting(false), - mShortcutProhibited(false), mShortcutFailPos() + mShortcutProhibited(false), mShortcutFailPos(), + mRotateOnTheRunChecks(0) { } @@ -104,6 +107,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path { mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity if (destInLOS && mPathFinder.getPath().size() > 1) @@ -143,7 +147,13 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr } else { - actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // run to target + if (mRotateOnTheRunChecks == 0 + || isReachableRotatingOnTheRun(actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point + { + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; // move to the target + if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--; + } + // handle obstacles on the way evadeObstacles(actor, duration, pos); } @@ -292,3 +302,32 @@ bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos) return false; } } + +bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest) +{ + // get actor's shortest radius for moving in circle + float speed = actor.getClass().getSpeed(actor); + speed += speed * 0.1f; // 10% real speed inaccuracy + float radius = speed / MAX_VEL_ANGULAR_RADIANS; + + // get radius direction to the center + const float* rot = actor.getRefData().getPosition().rot; + osg::Quat quatRot(rot[0], -osg::X_AXIS, rot[1], -osg::Y_AXIS, rot[2], -osg::Z_AXIS); + osg::Vec3f dir = quatRot * osg::Y_AXIS; // actor's orientation direction is a tangent to circle + osg::Vec3f radiusDir = dir ^ osg::Z_AXIS; // radius is perpendicular to a tangent + radiusDir.normalize(); + radiusDir *= radius; + + // pick up the nearest center candidate + osg::Vec3f dest_ = PathFinder::MakeOsgVec3(dest); + osg::Vec3f pos = actor.getRefData().getPosition().asVec3(); + osg::Vec3f center1 = pos - radiusDir; + osg::Vec3f center2 = pos + radiusDir; + osg::Vec3f center = (center1 - dest_).length2() < (center2 - dest_).length2() ? center1 : center2; + + float distToDest = (center - dest_).length(); + + // if pathpoint is reachable for the actor rotating on the run: + // no points of actor's circle should be farther from the center than destination point + return (radius <= distToDest); +} diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index c0df76c32..4960c14c3 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -97,6 +97,9 @@ namespace MWMechanics bool isTargetMagicallyHidden(const MWWorld::Ptr& target); + /// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise actor should rotate while standing. + static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest); + protected: /// Handles path building and shortcutting with obstacles avoiding /** \return If the actor has arrived at his destination **/ @@ -123,6 +126,8 @@ namespace MWMechanics osg::Vec3f mLastActorPos; + short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility + bool mIsShortcutting; // if shortcutting at the moment bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail From 7bc4535c0d9d6ff7887a54a58eda58430d342ba4 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sat, 10 Sep 2016 23:30:46 +0900 Subject: [PATCH 40/54] Make NPCs dodge according to target's weapon reach --- apps/openmw/mwmechanics/aicombat.cpp | 34 ++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 379119f60..ca3ab746e 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -69,7 +69,7 @@ namespace MWMechanics mMovement() {} - void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack); + void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& target); void updateCombatMove(float duration); void stopCombatMove(); void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, @@ -242,7 +242,7 @@ namespace MWMechanics if (storage.mReadyToAttack) { - storage.startCombatMove(actorClass.isNpc(), isRangedCombat, distToTarget, rangeAttack); + storage.startCombatMove(actorClass.isNpc(), isRangedCombat, distToTarget, rangeAttack, target); // start new attack storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); @@ -306,7 +306,6 @@ namespace MWMechanics return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); } - AiCombat *MWMechanics::AiCombat::clone() const { return new AiCombat(*this); @@ -323,7 +322,7 @@ namespace MWMechanics sequence.mPackages.push_back(package); } - void AiCombatStorage::startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack) + void AiCombatStorage::startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& target) { if (mMovement.mPosition[0] || mMovement.mPosition[1]) { @@ -331,10 +330,28 @@ namespace MWMechanics mCombatMove = true; } // dodge movements (for NPCs only) - else if (isNpc && (!isDistantCombat || (distToTarget < rangeAttack / 2))) + else if (isNpc) { - //apply sideway movement (kind of dodging) with some probability - if (Misc::Rng::rollClosedProbability() < 0.25) + // get the range of the target's weapon + float rangeAttackOfTarget = 0.f; + MWWorld::Ptr targetWeapon = MWWorld::Ptr(); + bool isRangedCombat = false; + const MWWorld::Class& targetClass = target.getClass(); + if (targetClass.hasInventoryStore(target)) + { + MWMechanics::WeaponType weapType = WeapType_None; + MWWorld::ContainerStoreIterator weaponSlot = + MWMechanics::getActiveWeapon(targetClass.getCreatureStats(target), targetClass.getInventoryStore(target), &weapType); + if (weapType != WeapType_PickProbe && weapType != WeapType_Spell && weapType != WeapType_None && weapType != WeapType_HandToHand) + targetWeapon = *weaponSlot; + } + boost::shared_ptr targetWeaponAction (new ActionWeapon(targetWeapon)); + if (targetWeaponAction.get()) + rangeAttackOfTarget = targetWeaponAction->getCombatRange(isRangedCombat); + + // apply sideway movement (kind of dodging) with some probability + // if NPC is within range of target's weapon + if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25) { mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability(); @@ -342,6 +359,9 @@ namespace MWMechanics } } + // Original engine behavior seems to be to back up during ranged combat + // according to fCombatDistance or opponent's weapon range, unless opponent + // is also using a ranged weapon if (isDistantCombat && distToTarget < rangeAttack / 4) { mMovement.mPosition[1] = -1; From c98d4e04738c29149ca19cf69ae0ee98db100d46 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sat, 10 Sep 2016 23:53:02 +0900 Subject: [PATCH 41/54] Allow dodging for bipedal creatures --- apps/openmw/mwmechanics/aicombat.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index ca3ab746e..e51502f84 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -69,7 +69,7 @@ namespace MWMechanics mMovement() {} - void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& target); + void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); void updateCombatMove(float duration); void stopCombatMove(); void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, @@ -242,7 +242,7 @@ namespace MWMechanics if (storage.mReadyToAttack) { - storage.startCombatMove(actorClass.isNpc(), isRangedCombat, distToTarget, rangeAttack, target); + storage.startCombatMove(actorClass.isNpc(), isRangedCombat, distToTarget, rangeAttack, actor, target); // start new attack storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); @@ -322,21 +322,22 @@ namespace MWMechanics sequence.mPackages.push_back(package); } - void AiCombatStorage::startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& target) + void AiCombatStorage::startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) { if (mMovement.mPosition[0] || mMovement.mPosition[1]) { mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(); mCombatMove = true; } - // dodge movements (for NPCs only) - else if (isNpc) + // dodge movements (for NPCs and bipedal creatures) + else if (isNpc || ((actor.get()->mBase->mFlags & ESM::Creature::Bipedal) != 0)) { // get the range of the target's weapon float rangeAttackOfTarget = 0.f; - MWWorld::Ptr targetWeapon = MWWorld::Ptr(); bool isRangedCombat = false; + MWWorld::Ptr targetWeapon = MWWorld::Ptr(); const MWWorld::Class& targetClass = target.getClass(); + if (targetClass.hasInventoryStore(target)) { MWMechanics::WeaponType weapType = WeapType_None; @@ -345,12 +346,14 @@ namespace MWMechanics if (weapType != WeapType_PickProbe && weapType != WeapType_Spell && weapType != WeapType_None && weapType != WeapType_HandToHand) targetWeapon = *weaponSlot; } + boost::shared_ptr targetWeaponAction (new ActionWeapon(targetWeapon)); + if (targetWeaponAction.get()) rangeAttackOfTarget = targetWeaponAction->getCombatRange(isRangedCombat); // apply sideway movement (kind of dodging) with some probability - // if NPC is within range of target's weapon + // if actor is within range of target's weapon if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25) { mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right From 3f6543860a870f6c7005bb34c63848f289c233c9 Mon Sep 17 00:00:00 2001 From: Allofich Date: Sun, 11 Sep 2016 00:00:12 +0900 Subject: [PATCH 42/54] Make creatures use fHandToHandReach --- apps/openmw/mwmechanics/aicombataction.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index a70410035..6dea53e98 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -491,17 +491,9 @@ namespace MWMechanics if (mWeapon.isEmpty()) { - if (!mIsNpc) - { - return fCombatDistance; - } - else - { - static float fHandToHandReach = - MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->getFloat(); - - return fHandToHandReach * fCombatDistance; - } + static float fHandToHandReach = + MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->getFloat(); + return fHandToHandReach * fCombatDistance; } const ESM::Weapon* weapon = mWeapon.get()->mBase; From fd2281e8259918965a1e4b0484bcc3acd66ceb9c Mon Sep 17 00:00:00 2001 From: emlai Date: Sat, 10 Sep 2016 21:25:28 +0300 Subject: [PATCH 43/54] Fix typo --- components/settings/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 0e5324bd9..0c0600cc0 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -390,7 +390,7 @@ std::string Manager::getString(const std::string &setting, const std::string &ca return it->second; throw std::runtime_error(std::string("Trying to retrieve a non-existing setting: ") + setting - + ".\nMake sure the settings-default.cfg file file was properly installed."); + + ".\nMake sure the settings-default.cfg file was properly installed."); } float Manager::getFloat (const std::string& setting, const std::string& category) From 06cc4cf35919600c23dbfd942af9b2f5f87474a6 Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Sun, 11 Sep 2016 20:37:55 -0700 Subject: [PATCH 44/54] Initial commit for OpenMW Modding Reference directory and files --- docs/source/index.rst | 1 + docs/source/openmw-mods/differences.rst | 58 +++++++++++++++++++++++++ docs/source/openmw-mods/foreword.rst | 4 ++ docs/source/openmw-mods/index.rst | 15 +++++++ 4 files changed, 78 insertions(+) create mode 100644 docs/source/openmw-mods/differences.rst create mode 100644 docs/source/openmw-mods/foreword.rst create mode 100644 docs/source/openmw-mods/index.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 32844146c..fdcb6ddaf 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -10,6 +10,7 @@ Components openmw/index openmw-cs/index + openmw-mods/index Indices and tables diff --git a/docs/source/openmw-mods/differences.rst b/docs/source/openmw-mods/differences.rst new file mode 100644 index 000000000..be0d96c93 --- /dev/null +++ b/docs/source/openmw-mods/differences.rst @@ -0,0 +1,58 @@ +Modding OpenMW vs Morrowind +################################# + +A brief overview of the differences between the two engines. +============================================================ + +OpenMW is designed to be able to use all the normal Morrowind mod files such as .esm/.esp plugins, texture replacers, mesh replacers, etc. + +.. warning:: + All external programs and libraries that depend on ''morrowind.exe'' cannot function with OpenMW. This means you should assume mods dependent on Morrowind Graphics Extender, Morrowind Code Patch, Morrowind Script Extender, etc, will *not* work correctly. + +Multiple Data Folders +--------------------- + +The largest difference between OpenMW and Morrowind in terms of data structure is OpenMW's support of multiple data folders. This has many advantages, especially when it comes to unistalling mods and preventing unintentional overwrites of files. + +.. warning:: + Most mods can still be installed into the root OpenMW data folder, but this is not recommended. + +To install mods via this new feature: + +#. Open ''openmw.cfg'' with your preffered text editor. It is located as described in https://wiki.openmw.org/index.php?title=Paths and *not* in your OpenMW root directory. +#. Find or search for ''data=''. This is located very near the bottom of the file. +#. Add a new line below this line and make a new entry of the format ''data=path/to/your/mod'' +#. Make as many of these entries as you need for each mod folder you want to include. +#. Save ''openmw.cfg'' + +.. note:: + All mod folders must adhere to the same file structure as ''~/Morrowind/Data Files/''. + +.. TODO create a PATHS ReST file that I can reference instead of the Wiki. + +To uninstall these mods simply delete that mod's respective ''data='' entry. +The mods are loaded in the order of these entries, with the top being overwritten by mods added towards the bottom. + +.. note:: + Mods that depend on .esm/.esp plugins can be rearranged within the OpenMW Launcher, but mesh/texture replacer mods can only be reordered by moving their ''data='' entry. + +OpenMW Launcher +--------------- + +The launcher included with OpenMW is similar to the original Morrowind Launcher. Go to the Data Files tab to enable and disable plugins. You can also drag list items to modify the load order. Content lists can be created at the bottom by clicking the New Content List button, creating a list name, then setting up a new modlist. This is helpful for different player profiles and testing out different load orders. + +.. TODO use a substitution image for the New Content List button. + +Settings.cfg +------------ + +The ''settings.cfg'' file is essentially the same as the .ini files for Morrowind. It is located in the same directory as ''openmw.cfg''. This is where many video, audio, GUI, input, etc. settings can be modified. Some are available in-game, but many are only available in this configuration file. Please see https://wiki.openmw.org/index.php?title=Settings for the complete listing. + +.. TODO Create a proper ReST document tree for all the settings rather than Wiki. + +Open Source Resources Support +----------------------------- + +While OpenMW supports all of the original files that Morrowind supported, we've expanded support to many open source file formats. These are summarized below: + + diff --git a/docs/source/openmw-mods/foreword.rst b/docs/source/openmw-mods/foreword.rst new file mode 100644 index 000000000..9fe52aa54 --- /dev/null +++ b/docs/source/openmw-mods/foreword.rst @@ -0,0 +1,4 @@ +Foreword +######## + +OpenMW is a complete game engine built to be content agnostic. The majority of this guide is applicable to any non-Morrowind project using its engine. That being said, it was designed with the extensive modding community of Morrowind in mind. Therefore, if you are already familiar with modding in Morrowind, you will likely be able to start modding in OpenMW with little to no instruction. We do recommend you at least refer to :doc:'differences' to find out about what's different between OpenMW and the original Morrowind engine. For everyone else, or just a good refresher, read on! \ No newline at end of file diff --git a/docs/source/openmw-mods/index.rst b/docs/source/openmw-mods/index.rst new file mode 100644 index 000000000..755501d2f --- /dev/null +++ b/docs/source/openmw-mods/index.rst @@ -0,0 +1,15 @@ +######################## +OpenMW Modding Reference +######################## + +The following document is the complete reference guide to modifying, or modding, your OpenMW setup. It does not cover content creation itself, only how to alter or add to your OpenMW gameplay experience. To learn more about creating new content for OpenMW, please refer to :doc:'../openmw-cs/index'. + +.. warning:: + OpenMW is still software in development. This manual does not cover any of its shortcomings. It is written as if everything was working as inteded. Please report any software problems as bugs in the software, not errors in the manual. + +.. toctree:: + :caption: Table of Contents + :maxdepth: 2 + + foreword + differences From 3bbde312b97eb9a635ac9812830ade1708796980 Mon Sep 17 00:00:00 2001 From: Allofich Date: Mon, 12 Sep 2016 19:54:06 +0900 Subject: [PATCH 45/54] Remove unneeded code --- apps/openmw/mwmechanics/aicombat.cpp | 2 +- apps/openmw/mwmechanics/aicombataction.cpp | 2 -- apps/openmw/mwmechanics/aicombataction.hpp | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index e51502f84..5f5685bb0 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -330,7 +330,7 @@ namespace MWMechanics mCombatMove = true; } // dodge movements (for NPCs and bipedal creatures) - else if (isNpc || ((actor.get()->mBase->mFlags & ESM::Creature::Bipedal) != 0)) + else if (actor.getClass().isBipedal(actor)) { // get the range of the target's weapon float rangeAttackOfTarget = 0.f; diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 6dea53e98..094df1db3 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -462,8 +462,6 @@ namespace MWMechanics void ActionWeapon::prepare(const MWWorld::Ptr &actor) { - mIsNpc = actor.getClass().isNpc(); - if (actor.getClass().hasInventoryStore(actor)) { if (mWeapon.isEmpty()) diff --git a/apps/openmw/mwmechanics/aicombataction.hpp b/apps/openmw/mwmechanics/aicombataction.hpp index e4ce44346..c280d3c11 100644 --- a/apps/openmw/mwmechanics/aicombataction.hpp +++ b/apps/openmw/mwmechanics/aicombataction.hpp @@ -63,7 +63,6 @@ namespace MWMechanics private: MWWorld::Ptr mAmmunition; MWWorld::Ptr mWeapon; - bool mIsNpc; public: /// \a weapon may be empty for hand-to-hand combat From bce01669310344b7a020ae1122a3206d285f68a2 Mon Sep 17 00:00:00 2001 From: Allofich Date: Mon, 12 Sep 2016 20:40:40 +0900 Subject: [PATCH 46/54] Don't play blood effects for resisted hits --- apps/openmw/mwclass/creature.cpp | 20 +++++++++--------- apps/openmw/mwclass/creature.hpp | 2 +- apps/openmw/mwclass/npc.cpp | 27 ++++++++++++------------ apps/openmw/mwclass/npc.hpp | 2 +- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwmechanics/combat.cpp | 7 ++---- apps/openmw/mwmechanics/spellcasting.cpp | 2 +- apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/class.hpp | 2 +- 9 files changed, 31 insertions(+), 35 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 51c83eee1..934ec62f5 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -264,7 +264,7 @@ namespace MWClass if(Misc::Rng::roll0to99() >= hitchance) { - victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, false); + victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } @@ -318,15 +318,12 @@ namespace MWClass if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) damage = 0; - if (damage > 0) - MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); - MWMechanics::diseaseContact(victim, ptr); - victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, true); + victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); } - void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const + void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, osg::Vec3f hitPosition, bool successful) const { // NOTE: 'object' and/or 'attacker' may be empty. @@ -342,10 +339,10 @@ namespace MWClass setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } - if(!object.isEmpty()) + if (!object.isEmpty()) getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId()); - if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) + if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) { const std::string &script = ptr.get()->mBase->mScript; /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ @@ -353,14 +350,14 @@ namespace MWClass ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } - if(!successful) + if (!successful) { // Missed MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f); return; } - if(!object.isEmpty()) + if (!object.isEmpty()) getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId()); if (damage > 0.0f && !object.isEmpty()) @@ -391,7 +388,10 @@ namespace MWClass if(ishealth) { if (!attacker.isEmpty()) + { damage = scaleDamage(damage, attacker, ptr); + MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition); + } MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index bea56887a..c6c699166 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -58,7 +58,7 @@ namespace MWClass virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const; - virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const; + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, osg::Vec3f hitPosition, bool successful) const; virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 98fdd7671..0189f6e36 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -591,7 +591,7 @@ namespace MWClass if (Misc::Rng::roll0to99() >= hitchance) { - othercls.onHit(victim, 0.0f, false, weapon, ptr, false); + othercls.onHit(victim, 0.0f, false, weapon, ptr, osg::Vec3f(), false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } @@ -646,15 +646,12 @@ namespace MWClass if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage, attackStrength)) damage = 0; - if (healthdmg && damage > 0) - MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); - MWMechanics::diseaseContact(victim, ptr); - othercls.onHit(victim, damage, healthdmg, weapon, ptr, true); + othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); } - void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const + void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, osg::Vec3f hitPosition, bool successful) const { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); @@ -671,10 +668,10 @@ namespace MWClass setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } - if(!object.isEmpty()) + if (!object.isEmpty()) getCreatureStats(ptr).setLastHitAttemptObject(object.getCellRef().getRefId()); - if(setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) + if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer()) { const std::string &script = ptr.getClass().getScript(ptr); /* Set the OnPCHitMe script variable. The script is responsible for clearing it. */ @@ -682,14 +679,14 @@ namespace MWClass ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1); } - if(!successful) + if (!successful) { // Missed sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f); return; } - if(!object.isEmpty()) + if (!object.isEmpty()) getCreatureStats(ptr).setLastHitObject(object.getCellRef().getRefId()); @@ -699,7 +696,7 @@ namespace MWClass if (damage < 0.001f) damage = 0; - if(damage > 0.0f && !attacker.isEmpty()) + if (damage > 0.0f && !attacker.isEmpty()) { // 'ptr' is losing health. Play a 'hit' voiced dialog entry if not already saying // something, alert the character controller, scripts, etc. @@ -725,7 +722,7 @@ namespace MWClass else getCreatureStats(ptr).setHitRecovery(true); // Is this supposed to always occur? - if(damage > 0 && ishealth) + if (damage > 0 && ishealth) { // Hit percentages: // cuirass = 30% @@ -787,16 +784,18 @@ namespace MWClass } } - if(ishealth) + if (ishealth) { if (!attacker.isEmpty()) damage = scaleDamage(damage, attacker, ptr); - if(damage > 0.0f) + if (damage > 0.0f) { sndMgr->playSound3D(ptr, "Health Damage", 1.0f, 1.0f); if (ptr == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->activateHitOverlay(); + if (!attacker.isEmpty()) + MWBase::Environment::get().getWorld()->spawnBloodEffect(ptr, hitPosition); } MWMechanics::DynamicStat health(getCreatureStats(ptr).getHealth()); health.setCurrent(health.getCurrent() - damage); diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 95edbd408..339ee056a 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -73,7 +73,7 @@ namespace MWClass virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const; - virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const; + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, osg::Vec3f hitPosition, bool successful) const; virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index bb8929f8b..e351229d0 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1771,7 +1771,7 @@ void CharacterController::update(float duration) 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(), true); + 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)) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index df2793bcb..b454f8e3a 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -190,7 +190,7 @@ namespace MWMechanics if (Misc::Rng::roll0to99() >= getHitChance(attacker, victim, skillValue)) { - victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, false); + victim.getClass().onHit(victim, 0.0f, false, projectile, attacker, osg::Vec3f(), false); MWMechanics::reduceWeaponCondition(0.f, false, weapon, attacker); return; } @@ -218,9 +218,6 @@ namespace MWMechanics if (weapon != projectile) appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition); - if (damage > 0) - MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); - // Non-enchanted arrows shot at enemies have a chance to turn up in their inventory if (victim != getPlayer() && !appliedEnchantment) @@ -230,7 +227,7 @@ namespace MWMechanics victim.getClass().getContainerStore(victim).add(projectile, 1, victim); } - victim.getClass().onHit(victim, damage, true, projectile, attacker, true); + victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true); } float getHitChance(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, int skillValue) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 95f2d940f..5024dfeec 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -569,7 +569,7 @@ namespace MWMechanics // Notify the target actor they've been hit if (anyHarmfulEffect && target.getClass().isActor() && target != caster && !caster.isEmpty() && caster.getClass().isActor()) - target.getClass().onHit(target, 0.f, true, MWWorld::Ptr(), caster, true); + target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); } bool CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude) diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index ecfe7cb7c..699b8a27e 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -98,7 +98,7 @@ namespace MWWorld throw std::runtime_error("class cannot block"); } - void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, bool successful) const + void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, osg::Vec3f hitPosition, bool successful) const { throw std::runtime_error("class cannot be hit"); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 23128ea9d..51afca0bb 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -120,7 +120,7 @@ namespace MWWorld /// enums. ignored for creature attacks. /// (default implementation: throw an exception) - virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const; + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, osg::Vec3f hitPosition, bool successful) const; ///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is /// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the /// actor responsible for the attack, and \a successful specifies if the hit is From 34851349de2361ccd292fce9c52364a5e4e0c7b1 Mon Sep 17 00:00:00 2001 From: Allofich Date: Tue, 13 Sep 2016 00:49:31 +0900 Subject: [PATCH 47/54] Pass hitPosition by const reference --- apps/openmw/mwclass/creature.cpp | 2 +- apps/openmw/mwclass/creature.hpp | 2 +- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwclass/npc.hpp | 2 +- apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/class.hpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 934ec62f5..ff4ae6148 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -323,7 +323,7 @@ namespace MWClass victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); } - void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, osg::Vec3f hitPosition, bool successful) const + void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const { // NOTE: 'object' and/or 'attacker' may be empty. diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index c6c699166..654df60b6 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -58,7 +58,7 @@ namespace MWClass virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const; - virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, osg::Vec3f hitPosition, bool successful) const; + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const; virtual boost::shared_ptr activate (const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const; diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 0189f6e36..52debfb34 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -651,7 +651,7 @@ namespace MWClass othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); } - void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, osg::Vec3f hitPosition, bool successful) const + void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 339ee056a..941605176 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -73,7 +73,7 @@ namespace MWClass virtual void hit(const MWWorld::Ptr& ptr, float attackStrength, int type) const; - virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, osg::Vec3f hitPosition, bool successful) const; + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const; virtual void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: list getModel(). diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 699b8a27e..12b69c7ca 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -98,7 +98,7 @@ namespace MWWorld throw std::runtime_error("class cannot block"); } - void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, osg::Vec3f hitPosition, bool successful) const + void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const { throw std::runtime_error("class cannot be hit"); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 51afca0bb..01d20032b 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -120,7 +120,7 @@ namespace MWWorld /// enums. ignored for creature attacks. /// (default implementation: throw an exception) - virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, osg::Vec3f hitPosition, bool successful) const; + virtual void onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const; ///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is /// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the /// actor responsible for the attack, and \a successful specifies if the hit is From 65dc12cdd61ebf0984313251b100c9e052449b47 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 13 Sep 2016 02:48:36 +0200 Subject: [PATCH 48/54] Remove unused parameter --- apps/openmw/mwmechanics/aicombat.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 5f5685bb0..c821eac0d 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -69,7 +69,7 @@ namespace MWMechanics mMovement() {} - void startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); + void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target); void updateCombatMove(float duration); void stopCombatMove(); void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController, @@ -242,7 +242,7 @@ namespace MWMechanics if (storage.mReadyToAttack) { - storage.startCombatMove(actorClass.isNpc(), isRangedCombat, distToTarget, rangeAttack, actor, target); + storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target); // start new attack storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat); @@ -322,7 +322,7 @@ namespace MWMechanics sequence.mPackages.push_back(package); } - void AiCombatStorage::startCombatMove(bool isNpc, bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) + void AiCombatStorage::startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target) { if (mMovement.mPosition[0] || mMovement.mPosition[1]) { From 1362264561731371b685ded5928ecb913c4fbe06 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 13 Sep 2016 02:49:19 +0200 Subject: [PATCH 49/54] Fix warning --- apps/openmw/mwmechanics/aipackage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index fa952c414..abdc57042 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -25,9 +25,9 @@ MWMechanics::AiPackage::~AiPackage() {} MWMechanics::AiPackage::AiPackage() : mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild + mRotateOnTheRunChecks(0), mIsShortcutting(false), - mShortcutProhibited(false), mShortcutFailPos(), - mRotateOnTheRunChecks(0) + mShortcutProhibited(false), mShortcutFailPos() { } From 7c5c304ed58223045bdaf03f900dc8c00f53e6c6 Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Mon, 12 Sep 2016 23:23:24 -0700 Subject: [PATCH 50/54] Started working on a style guide for tutorials --- docs/source/openmw-mods/differences.rst | 2 +- docs/source/openmw-mods/index.rst | 1 + docs/source/openmw-mods/mod-install.rst | 6 +++ docs/source/tutorial-style-guide.txt | 51 +++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 docs/source/openmw-mods/mod-install.rst create mode 100644 docs/source/tutorial-style-guide.txt diff --git a/docs/source/openmw-mods/differences.rst b/docs/source/openmw-mods/differences.rst index be0d96c93..b112c398b 100644 --- a/docs/source/openmw-mods/differences.rst +++ b/docs/source/openmw-mods/differences.rst @@ -7,7 +7,7 @@ A brief overview of the differences between the two engines. OpenMW is designed to be able to use all the normal Morrowind mod files such as .esm/.esp plugins, texture replacers, mesh replacers, etc. .. warning:: - All external programs and libraries that depend on ''morrowind.exe'' cannot function with OpenMW. This means you should assume mods dependent on Morrowind Graphics Extender, Morrowind Code Patch, Morrowind Script Extender, etc, will *not* work correctly. + All external programs and libraries that depend on ''morrowind.exe'' cannot function with OpenMW. This means you should assume mods dependent on Morrowind Graphics Extender, Morrowind Code Patch, Morrowind Script Extender, etc, will *not* work correctly, nor will the tools themselves. Multiple Data Folders --------------------- diff --git a/docs/source/openmw-mods/index.rst b/docs/source/openmw-mods/index.rst index 755501d2f..60eae8bde 100644 --- a/docs/source/openmw-mods/index.rst +++ b/docs/source/openmw-mods/index.rst @@ -13,3 +13,4 @@ The following document is the complete reference guide to modifying, or modding, foreword differences + mod-install \ No newline at end of file diff --git a/docs/source/openmw-mods/mod-install.rst b/docs/source/openmw-mods/mod-install.rst new file mode 100644 index 000000000..682ff8b9c --- /dev/null +++ b/docs/source/openmw-mods/mod-install.rst @@ -0,0 +1,6 @@ +How To Install Mods +################### + +The following is a detailed guide on how to install mods in OpenMW using best practices. + +#. \ No newline at end of file diff --git a/docs/source/tutorial-style-guide.txt b/docs/source/tutorial-style-guide.txt new file mode 100644 index 000000000..258297642 --- /dev/null +++ b/docs/source/tutorial-style-guide.txt @@ -0,0 +1,51 @@ +#################### +Tutorial Style Guide +#################### + +Please contact Ravenwing about any questions relating to this guide. + +Foreword +-------- + +I shall try to be as brief as possible without sacrificing clarity, just as you should be when writing documentation. The SCOPE of this guide is limited to all non-source code documentation. + +References +---------- + +-Syntax: reStructuredText (ReST): http://docutils.sourceforge.net/rst.html +-Parser: Sphinx: http://www.sphinx-doc.org/en/stable/ +-British and English Spelling: https://www.spellzone.com/pages/british-american.cfm + +Language +-------- + +British English +Sorry, I'm American and I'll probably have a million relapses, but this seems to have been decided and uniformity is key! + +Text Wrapping +------------- + +DO NOT manually decide where each line ends! Enable text wrapping in your text editor! (usually under View) Even Notepad allows automatic text wrapping, so use it. If you program, you may be used to keeping your lines under 80 characters wide and manually pressing enter, but don't. This is because we are writing mostly prose. When someone has to come in and edit portions of text that puts all this out of alignment, it takes FOREVER to readjust everything. Plus, indentation is very important in ReST, and you may forget to indent properly. + +Indentations +------------ + +This isn't as important, especially since Sphinx converts tabs to spaces, but I find it much easier to keep large blocks of things aligned if you just tab. If you can edit your tab width, please set it to 4 spaces. + +Spaces +------ + +Use only one space after each sentence. Some people were taught two spaces, but this is a carry-over from typewriters. Even though your text editor is probably using monospaced characters like a typewriter, the formats Sphinx is converting into will make all the adjustments they need to be beautifully legible. + +Commas +------ + +Oxford comma. Use it. I know this goes against using British English, but this is technical writing. You cannot assume our audience will know from context if the last two items in a list are grouped or not. +I also prefer parentheticals to commas around gerund phrases. + +Files and Extensions +-------------------- + +When referring to a filename or filepath use the double single quotes as for inline literals. (e.g. ''morrowind.exe'') +When referring to a file extension by itself use all caps without the dot beforehand. (e.g. ZIP instead of .zip or .ZIP) + From b35921dc6c1685c3f858536c7ac264c9db5073d7 Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Tue, 13 Sep 2016 19:31:45 -0700 Subject: [PATCH 51/54] Corrected the back quotes for literals --- docs/source/openmw-mods/differences.rst | 20 ++++++++++---------- docs/source/openmw-mods/foreword.rst | 2 +- docs/source/openmw-mods/index.rst | 2 +- docs/source/openmw-mods/mod-install.rst | 2 +- docs/source/tutorial-style-guide.txt | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/source/openmw-mods/differences.rst b/docs/source/openmw-mods/differences.rst index b112c398b..61f9b2a75 100644 --- a/docs/source/openmw-mods/differences.rst +++ b/docs/source/openmw-mods/differences.rst @@ -4,10 +4,10 @@ Modding OpenMW vs Morrowind A brief overview of the differences between the two engines. ============================================================ -OpenMW is designed to be able to use all the normal Morrowind mod files such as .esm/.esp plugins, texture replacers, mesh replacers, etc. +OpenMW is designed to be able to use all the normal Morrowind mod files such as ESM/ESP plugins, texture replacers, mesh replacers, etc. .. warning:: - All external programs and libraries that depend on ''morrowind.exe'' cannot function with OpenMW. This means you should assume mods dependent on Morrowind Graphics Extender, Morrowind Code Patch, Morrowind Script Extender, etc, will *not* work correctly, nor will the tools themselves. + All external programs and libraries that depend on ``morrowind.exe`` cannot function with OpenMW. This means you should assume mods dependent on Morrowind Graphics Extender, Morrowind Code Patch, Morrowind Script Extender, etc, will *not* work correctly, nor will the tools themselves. Multiple Data Folders --------------------- @@ -19,22 +19,22 @@ The largest difference between OpenMW and Morrowind in terms of data structure i To install mods via this new feature: -#. Open ''openmw.cfg'' with your preffered text editor. It is located as described in https://wiki.openmw.org/index.php?title=Paths and *not* in your OpenMW root directory. -#. Find or search for ''data=''. This is located very near the bottom of the file. -#. Add a new line below this line and make a new entry of the format ''data=path/to/your/mod'' +#. Open ``openmw.cfg`` with your preffered text editor. It is located as described in https://wiki.openmw.org/index.php?title=Paths and *not* in your OpenMW root directory. +#. Find or search for ``data=``. This is located very near the bottom of the file. +#. Add a new line below this line and make a new entry of the format ``data=path/to/your/mod`` #. Make as many of these entries as you need for each mod folder you want to include. -#. Save ''openmw.cfg'' +#. Save ``openmw.cfg`` .. note:: - All mod folders must adhere to the same file structure as ''~/Morrowind/Data Files/''. + All mod folders must adhere to the same file structure as ``~/Morrowind/Data Files/``. .. TODO create a PATHS ReST file that I can reference instead of the Wiki. -To uninstall these mods simply delete that mod's respective ''data='' entry. +To uninstall these mods simply delete that mod's respective ``data=`` entry. The mods are loaded in the order of these entries, with the top being overwritten by mods added towards the bottom. .. note:: - Mods that depend on .esm/.esp plugins can be rearranged within the OpenMW Launcher, but mesh/texture replacer mods can only be reordered by moving their ''data='' entry. + Mods that depend on ESM/ESP plugins can be rearranged within the OpenMW Launcher, but mesh/texture replacer mods can only be reordered by moving their ``data=`` entry. OpenMW Launcher --------------- @@ -46,7 +46,7 @@ The launcher included with OpenMW is similar to the original Morrowind Launcher. Settings.cfg ------------ -The ''settings.cfg'' file is essentially the same as the .ini files for Morrowind. It is located in the same directory as ''openmw.cfg''. This is where many video, audio, GUI, input, etc. settings can be modified. Some are available in-game, but many are only available in this configuration file. Please see https://wiki.openmw.org/index.php?title=Settings for the complete listing. +The ``settings.cfg`` file is essentially the same as the INI files for Morrowind. It is located in the same directory as ``openmw.cfg``. This is where many video, audio, GUI, input, etc. settings can be modified. Some are available in-game, but many are only available in this configuration file. Please see https://wiki.openmw.org/index.php?title=Settings for the complete listing. .. TODO Create a proper ReST document tree for all the settings rather than Wiki. diff --git a/docs/source/openmw-mods/foreword.rst b/docs/source/openmw-mods/foreword.rst index 9fe52aa54..cf72b2aa3 100644 --- a/docs/source/openmw-mods/foreword.rst +++ b/docs/source/openmw-mods/foreword.rst @@ -1,4 +1,4 @@ Foreword ######## -OpenMW is a complete game engine built to be content agnostic. The majority of this guide is applicable to any non-Morrowind project using its engine. That being said, it was designed with the extensive modding community of Morrowind in mind. Therefore, if you are already familiar with modding in Morrowind, you will likely be able to start modding in OpenMW with little to no instruction. We do recommend you at least refer to :doc:'differences' to find out about what's different between OpenMW and the original Morrowind engine. For everyone else, or just a good refresher, read on! \ No newline at end of file +OpenMW is a complete game engine built to be content agnostic. The majority of this guide is applicable to any non-Morrowind project using its engine. That being said, it was designed with the extensive modding community of Morrowind in mind. Therefore, if you are already familiar with modding in Morrowind, you will likely be able to start modding in OpenMW with little to no instruction. We do recommend you at least refer to :doc:`differences` to find out about what's different between OpenMW and the original Morrowind engine. For everyone else, or just a good refresher, read on! \ No newline at end of file diff --git a/docs/source/openmw-mods/index.rst b/docs/source/openmw-mods/index.rst index 60eae8bde..49113426c 100644 --- a/docs/source/openmw-mods/index.rst +++ b/docs/source/openmw-mods/index.rst @@ -2,7 +2,7 @@ OpenMW Modding Reference ######################## -The following document is the complete reference guide to modifying, or modding, your OpenMW setup. It does not cover content creation itself, only how to alter or add to your OpenMW gameplay experience. To learn more about creating new content for OpenMW, please refer to :doc:'../openmw-cs/index'. +The following document is the complete reference guide to modifying, or modding, your OpenMW setup. It does not cover content creation itself, only how to alter or add to your OpenMW gameplay experience. To learn more about creating new content for OpenMW, please refer to :doc:`../openmw-cs/index`. .. warning:: OpenMW is still software in development. This manual does not cover any of its shortcomings. It is written as if everything was working as inteded. Please report any software problems as bugs in the software, not errors in the manual. diff --git a/docs/source/openmw-mods/mod-install.rst b/docs/source/openmw-mods/mod-install.rst index 682ff8b9c..7211b0736 100644 --- a/docs/source/openmw-mods/mod-install.rst +++ b/docs/source/openmw-mods/mod-install.rst @@ -3,4 +3,4 @@ How To Install Mods The following is a detailed guide on how to install mods in OpenMW using best practices. -#. \ No newline at end of file +#. Most mods \ No newline at end of file diff --git a/docs/source/tutorial-style-guide.txt b/docs/source/tutorial-style-guide.txt index 258297642..82ca64966 100644 --- a/docs/source/tutorial-style-guide.txt +++ b/docs/source/tutorial-style-guide.txt @@ -46,6 +46,6 @@ I also prefer parentheticals to commas around gerund phrases. Files and Extensions -------------------- -When referring to a filename or filepath use the double single quotes as for inline literals. (e.g. ''morrowind.exe'') +When referring to a filename or filepath use the double single quotes as for inline literals. (e.g. ``morrowind.exe``) When referring to a file extension by itself use all caps without the dot beforehand. (e.g. ZIP instead of .zip or .ZIP) From f6b38323a3dbb37124445c761ef2a2991013c76d Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Tue, 13 Sep 2016 20:17:58 -0700 Subject: [PATCH 52/54] Started adding to the detailed mod install guide --- docs/source/openmw-mods/mod-install.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/openmw-mods/mod-install.rst b/docs/source/openmw-mods/mod-install.rst index 7211b0736..ecdd2892b 100644 --- a/docs/source/openmw-mods/mod-install.rst +++ b/docs/source/openmw-mods/mod-install.rst @@ -3,4 +3,5 @@ How To Install Mods The following is a detailed guide on how to install mods in OpenMW using best practices. -#. Most mods \ No newline at end of file +#. Your mod probably comes in some kind of archive, such as ``*.zip``, ``*.rar``, ``*.7z`` or something along those lines. Unpack this archive into its own folder. +#. Ensure the structure of this folder is correct. There may be multiple levels of folders the mod came packaged in, but whichever folder contains the plugin files, ``*.esp`` or ``*.omwaddon``, \ No newline at end of file From 04cbf5dd02e1b730e75f7198bcde11ae4a8f3c33 Mon Sep 17 00:00:00 2001 From: Ryan Tucker Date: Tue, 13 Sep 2016 22:16:01 -0700 Subject: [PATCH 53/54] Added to mod installation guide --- docs/source/openmw-mods/mod-install.rst | 30 ++++++++++++++++++++----- docs/source/tutorial-style-guide.txt | 6 +++-- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/docs/source/openmw-mods/mod-install.rst b/docs/source/openmw-mods/mod-install.rst index ecdd2892b..317769990 100644 --- a/docs/source/openmw-mods/mod-install.rst +++ b/docs/source/openmw-mods/mod-install.rst @@ -1,7 +1,27 @@ -How To Install Mods -################### +How To Install and Use Mods +########################### -The following is a detailed guide on how to install mods in OpenMW using best practices. +The following is a detailed guide on how to install and enable mods in OpenMW using best practices. -#. Your mod probably comes in some kind of archive, such as ``*.zip``, ``*.rar``, ``*.7z`` or something along those lines. Unpack this archive into its own folder. -#. Ensure the structure of this folder is correct. There may be multiple levels of folders the mod came packaged in, but whichever folder contains the plugin files, ``*.esp`` or ``*.omwaddon``, \ No newline at end of file +Install +------- + +#. Your mod probably comes in some kind of archive, such as ``.zip``, ``.rar``, ``.7z``, or something along those lines. Unpack this archive into its own folder. +#. Ensure the structure of this folder is correct. + #. Locate the plugin files, ``.esp`` or ``.omwaddon``. The folder containing the plugin files we will call your *data folder* + #. Check that all resource folders (``Meshes``, ``Textures``, etc.) containing additional resource files (the actual meshes, textures, etc.) are in the *data folder*. +.. note:: + There may be multiple levels of folders, but the location of the plugins must be the same as the resource folders. +#. Open your ``openmw.cfg`` file in your preferred plain text editor. It is located as described in https://wiki.openmw.org/index.php?title=Paths and *not* in your OpenMW root directory. +#. Find or search for ``data=``. This is located very near the bottom of the file. If you are using Morrowind, this first entry should already point to your Morrowind data directory, ``Data Files``; otherwise it will point to your game file, ``.omwgame``. +#. Create a new line underneath and type: ``data="path/to/your/data folder"`` Remember, the *data folder* is where your mod's plugin files are. The double quotes around this path name are *required*. +#. Save your ``openmw.cfg`` file. + +You have now installed your mod. Any simple replacer mods that only contain resource files such as meshes or textures will now automatically be loaded in the order of their ``data=*`` entry. This is important to note because replacer mods that replace the same resource will overwrite previous ones as you go down the list. + +Enable +------ + +Any mods that have plugin files must be enabled to work. + +#. \ No newline at end of file diff --git a/docs/source/tutorial-style-guide.txt b/docs/source/tutorial-style-guide.txt index 82ca64966..b37992057 100644 --- a/docs/source/tutorial-style-guide.txt +++ b/docs/source/tutorial-style-guide.txt @@ -46,6 +46,8 @@ I also prefer parentheticals to commas around gerund phrases. Files and Extensions -------------------- -When referring to a filename or filepath use the double single quotes as for inline literals. (e.g. ``morrowind.exe``) -When referring to a file extension by itself use all caps without the dot beforehand. (e.g. ZIP instead of .zip or .ZIP) +When referring to a filename or filepath use the double back quotes as for inline literals. (e.g. ``morrowind.exe``) +When referring to a file extension by itself, use ``.lowerCaseExtension``. (e.g. ``.esm``) +If referring to a folder or file by a general name, use *emphasis* +If referring to a folder by its actual name, even without the path, use ``inline literal`` \ No newline at end of file From 322a0ba8bb6f74a9af2618db539862baa4956572 Mon Sep 17 00:00:00 2001 From: Allofich Date: Thu, 15 Sep 2016 01:44:53 +0900 Subject: [PATCH 54/54] Initialize speed for magic projectiles --- apps/openmw/mwworld/projectilemanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 844800a41..c70f3de99 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -40,6 +40,7 @@ namespace ESM::EffectList getMagicBoltData(std::vector& projectileIDs, std::vector& sounds, float& speed, const ESM::EffectList& effects) { int count = 0; + speed = 0.0f; ESM::EffectList projectileEffects; for (std::vector::const_iterator iter (effects.mList.begin()); iter!=effects.mList.end(); ++iter)