From 67abc60264a6aec4d5b9080150bb22e76cde02f5 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Sun, 8 Jun 2014 20:59:26 +0400 Subject: [PATCH 1/5] aiming to moving target in ranged combat ai 1) Taking into account target move vector and speed. However aiming is not ideal, since attack strength can't be controlled directly. I did achieve almost 100% accuracy updating it everyframe but then thought it would be unfair, cause AI should mimic human targetting. 2) Also added in this commit func to measure real attack durations for weapon. --- apps/openmw/mwmechanics/aicombat.cpp | 346 +++++++++++++++++------ apps/openmw/mwmechanics/aicombat.hpp | 9 +- apps/openmw/mwrender/animation.cpp | 19 +- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwrender/weaponanimation.cpp | 2 +- 5 files changed, 278 insertions(+), 100 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 8f8188559..7a846e00d 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -1,8 +1,6 @@ #include "aicombat.hpp" #include -#include - #include "../mwworld/class.hpp" #include "../mwworld/timestamp.hpp" @@ -14,6 +12,8 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwrender/animation.hpp" + #include "creaturestats.hpp" #include "steering.hpp" @@ -30,7 +30,12 @@ namespace } //chooses an attack depending on probability to avoid uniformity - void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); + ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement); + + void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2]); + + Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, + float duration, int weapType, float strength); float getZAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f) { @@ -76,18 +81,20 @@ namespace namespace MWMechanics { - static const float MAX_ATTACK_DURATION = 0.35f; static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp AiCombat::AiCombat(const MWWorld::Ptr& actor) : mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()), + mLastTargetPos(actor.getRefData().getPosition().pos), mTimerAttack(0), mTimerReact(0), mTimerCombatMove(0), mFollowTarget(false), mReadyToAttack(false), mAttack(false), + mStrength(0), + mMinMaxAttackDuration(), mCombatMove(false), mMovement(), mForceNoShortcut(false), @@ -158,9 +165,9 @@ namespace MWMechanics return true; if (!actor.getClass().isNpc() && target == MWBase::Environment::get().getWorld()->getPlayerPtr() && - (actor.getClass().canSwim(actor) && !actor.getClass().canWalk(actor) // pure water creature - && !MWBase::Environment::get().getWorld()->isSwimming(target)) // Player moved out of water - || (!actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(target))) // creature can't swim to Player + (actor.getClass().canSwim(actor) && !actor.getClass().canWalk(actor) // 1. pure water creature and Player moved out of water + && !MWBase::Environment::get().getWorld()->isSwimming(target)) + || (!actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(target))) // 2. creature can't swim to Player { actor.getClass().getCreatureStats(actor).setHostile(false); actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false); @@ -194,6 +201,27 @@ namespace MWMechanics } mTimerAttack -= duration; + + //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f + float attacksPeriod = 1.0f; + + ESM::Weapon::AttackType attackType; + + if(mReadyToAttack) + { + if (mMinMaxAttackDuration[0][0] == 0) + { + getMinMaxAttackDuration(actor, mMinMaxAttackDuration); + } + + if (mTimerAttack <= 0) mAttack = false; + } + else + { + mTimerAttack = -attacksPeriod; + mAttack = false; + } + actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack); float tReaction = 0.25f; @@ -213,40 +241,6 @@ namespace MWMechanics mCell = actor.getCell(); } - //actual attacking logic - //TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f - float attacksPeriod = 1.0f; - if(mReadyToAttack) - { - if(mTimerAttack <= -attacksPeriod) - { - //TODO: should depend on time between 'start' to 'min attack' - //for better controlling of NPCs' attack strength. - //Also it seems that this time is different for slash/thrust/chop - mTimerAttack = MAX_ATTACK_DURATION * static_cast(rand())/RAND_MAX; - mAttack = true; - - //say a provoking combat phrase - if (actor.getClass().isNpc()) - { - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - int chance = store.get().find("iVoiceAttackOdds")->getInt(); - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - if (roll < chance) - { - MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); - } - } - } - else if (mTimerAttack <= 0) - mAttack = false; - } - else - { - mTimerAttack = -attacksPeriod; - mAttack = false; - } - const MWWorld::Class &actorCls = actor.getClass(); const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype; @@ -270,9 +264,9 @@ namespace MWMechanics if (weaptype == WeapType_HandToHand) { - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - weapRange = gmst.find("fHandToHandReach")->getFloat(); + static float fHandToHandReach = + MWBase::Environment::get().getWorld()->getStore().get().find("fHandToHandReach")->getFloat(); + weapRange = fHandToHandReach; } else if (weaptype != WeapType_PickProbe && weaptype != WeapType_Spell) { @@ -289,6 +283,49 @@ namespace MWMechanics weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) } + float rangeAttack; + float rangeFollow; + bool distantCombat = false; + if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) + { + rangeAttack = 1000; // TODO: should depend on archer skill + rangeFollow = 0; // not needed in ranged combat + distantCombat = true; + } + else + { + rangeAttack = weapRange; + rangeFollow = 300; + } + + // start new attack + if(mReadyToAttack) + { + if(mTimerAttack <= -attacksPeriod) + { + mAttack = true; // attack starts just now + + if (!distantCombat) attackType = chooseBestAttack(weapon, mMovement); + else attackType = ESM::Weapon::AT_Chop; // cause it's =0 + + mStrength = static_cast(rand()) / RAND_MAX; + mTimerAttack = mMinMaxAttackDuration[attackType][0] + + (mMinMaxAttackDuration[attackType][1] - mMinMaxAttackDuration[attackType][0]) * mStrength; + + //say a provoking combat phrase + if (actor.getClass().isNpc()) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + int chance = store.get().find("iVoiceAttackOdds")->getInt(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < chance) + { + MWBase::Environment::get().getDialogueManager()->say(actor, "attack"); + } + } + } + } + /* * Some notes on meanings of variables: @@ -319,21 +356,6 @@ namespace MWMechanics * target even if LOS is not achieved) */ - float rangeAttack; - float rangeFollow; - bool distantCombat = false; - if (weaptype == WeapType_BowAndArrow || weaptype == WeapType_Crossbow || weaptype == WeapType_Thrown) - { - rangeAttack = 1000; // TODO: should depend on archer skill - rangeFollow = 0; // not needed in ranged combat - distantCombat = true; - } - else - { - rangeAttack = weapRange; - rangeFollow = 300; - } - ESM::Position pos = actor.getRefData().getPosition(); Ogre::Vector3 vActorPos(pos.pos); Ogre::Vector3 vTargetPos(target.getRefData().getPosition().pos); @@ -342,40 +364,46 @@ namespace MWMechanics bool isStuck = false; float speed = 0.0f; - if(mMovement.mPosition[1] && (Ogre::Vector3(mLastPos.pos) - vActorPos).length() < (speed = actorCls.getSpeed(actor)) * tReaction / 2) + if(mMovement.mPosition[1] && (mLastActorPos - vActorPos).length() < (speed = actorCls.getSpeed(actor)) * tReaction / 2) isStuck = true; - mLastPos = pos; + mLastActorPos = vActorPos; // check if actor can move along z-axis bool canMoveByZ = (actorCls.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) || MWBase::Environment::get().getWorld()->isFlying(actor); - // determine vertical angle to target - // if actor can move along z-axis it will control movement dir - // if can't - it will control correct aiming - mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); - - // (within strike dist) || (not quite strike dist while following) + // (within attack dist) || (not quite attack dist while following) if(distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck) ) { //Melee and Close-up combat - // if we preserve dir.z then horizontal angle can be inaccurate - mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0)); + // 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) + { + Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, mLastTargetPos, tReaction, weaptype, mStrength); + mLastTargetPos = vTargetPos; + mMovement.mRotation[0] = getXAngleToDir(vAimDir); + mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vAimDir.x, vAimDir.y, 0)); + } + else + { + mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget); + mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0)); + } - // (not quite strike dist while following) + // (not quite attack dist while following) if (mFollowTarget && distToTarget > rangeAttack) { //Close-up combat: just run up on target mMovement.mPosition[1] = 1; } - else // (within strike dist) + else // (within attack dist) { - mMovement.mPosition[1] = 0; - - // set slash/thrust/chop attack - if (mAttack && !distantCombat) chooseBestAttack(weapon, mMovement); + if (!mAttack) mMovement.mPosition[1] = 0; if(mMovement.mPosition[0] || mMovement.mPosition[1]) { @@ -479,9 +507,9 @@ namespace MWMechanics //special run attack; it shouldn't affect melee combat tactics if(actorCls.getMovementSettings(actor).mPosition[1] == 1) { - //check if actor can overcome the distance = distToTarget - attackerWeapRange - //less than in time of playing weapon anim from 'start' to 'hit' tags (t_swing) - //then start attacking + /* check if actor can overcome the distance = distToTarget - attackerWeapRange + less than in time of swinging with weapon (t_swing), then start attacking + */ float speed1 = actorCls.getSpeed(actor); float speed2 = target.getClass().getSpeed(target); if(target.getClass().getMovementSettings(target).mPosition[0] == 0 @@ -491,13 +519,16 @@ namespace MWMechanics float s1 = distToTarget - weapRange; float t = s1/speed1; float s2 = speed2 * t; - float t_swing = (MAX_ATTACK_DURATION/2) / weapSpeed;//instead of 0.17 should be the time of playing weapon anim from 'start' to 'hit' tags + float t_swing = + mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0] + + (mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * static_cast(rand()) / RAND_MAX; + if (t + s2/speed1 <= t_swing) { mReadyToAttack = true; if(mTimerAttack <= -attacksPeriod) { - mTimerAttack = MAX_ATTACK_DURATION * static_cast(rand())/RAND_MAX; + mTimerAttack = t_swing; mAttack = true; } } @@ -650,8 +681,10 @@ namespace MWMechanics namespace { -void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement) +ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement) { + ESM::Weapon::AttackType attackType; + if (weapon == NULL) { //hand-to-hand deal equal damage for each type @@ -660,34 +693,161 @@ void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement { movement.mPosition[0] = (static_cast(rand())/RAND_MAX < 0.5f)? 1: -1; movement.mPosition[1] = 0; + attackType = ESM::Weapon::AT_Slash; } else if(roll <= 0.666f) //forward punch + { movement.mPosition[1] = 1; + attackType = ESM::Weapon::AT_Thrust; + } else { movement.mPosition[1] = movement.mPosition[0] = 0; + attackType = ESM::Weapon::AT_Chop; } + } + else + { + //the more damage attackType deals the more probability it has + int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; + int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; + int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + + float total = slash + chop + thrust; + + float roll = static_cast(rand())/RAND_MAX; + if(roll <= static_cast(slash)/total) + { + movement.mPosition[0] = (static_cast(rand())/RAND_MAX < 0.5f)? 1: -1; + movement.mPosition[1] = 0; + attackType = ESM::Weapon::AT_Slash; + } + else if(roll <= (static_cast(slash) + static_cast(thrust))/total) + { + movement.mPosition[1] = 1; + attackType = ESM::Weapon::AT_Thrust; + } + else + { + movement.mPosition[1] = movement.mPosition[0] = 0; + attackType = ESM::Weapon::AT_Chop; + } + } + + return attackType; +} + +void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations)[2]) +{ + if (!actor.getClass().hasInventoryStore(actor)) // creatures + { + fMinMaxDurations[0][0] = fMinMaxDurations[0][1] = 0.1f; + fMinMaxDurations[1][0] = fMinMaxDurations[1][1] = 0.1f; + fMinMaxDurations[2][0] = fMinMaxDurations[2][1] = 0.1f; return; } - //the more damage attackType deals the more probability it has - int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; - int chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1])/2; - int thrust = (weapon->mData.mThrust[0] + weapon->mData.mThrust[1])/2; + // get weapon information: type and speed + const ESM::Weapon *weapon = NULL; + MWMechanics::WeaponType weaptype; - float total = slash + chop + thrust; + MWWorld::ContainerStoreIterator weaponSlot = + MWMechanics::getActiveWeapon(actor.getClass().getCreatureStats(actor), actor.getClass().getInventoryStore(actor), &weaptype); - float roll = static_cast(rand())/RAND_MAX; - if(roll <= static_cast(slash)/total) + float weapSpeed; + if (weaptype != MWMechanics::WeapType_HandToHand) { - movement.mPosition[0] = (static_cast(rand())/RAND_MAX < 0.5f)? 1: -1; - movement.mPosition[1] = 0; + weapon = weaponSlot->get()->mBase; + weapSpeed = weapon->mData.mSpeed; + } + else weapSpeed = 1.0f; + + MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(actor); + + std::string weapGroup; + MWMechanics::getWeaponGroup(weaptype, weapGroup); + weapGroup = weapGroup + ": "; + + bool bRangedWeap = (weaptype >= MWMechanics::WeapType_BowAndArrow && weaptype <= MWMechanics::WeapType_Thrown); + + const char *attackType[] = {"chop ", "slash ", "thrust ", "shoot "}; + + std::string textKey = "start"; + std::string textKey2; + + // get durations for each attack type + for (int i = 0; i < (bRangedWeap ? 1 : 3); i++) + { + float start1 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey, false); + + textKey2 = "min attack"; + float start2 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2, false); + + fMinMaxDurations[i][0] = (start2 - start1) / weapSpeed; + + textKey2 = "max attack"; + start1 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2, false); + + fMinMaxDurations[i][1] = fMinMaxDurations[i][0] + (start1 - start2) / weapSpeed; + } + +} + +Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, + float duration, int weapType, float strength) +{ + float projSpeed; + + // get projectile speed (depending on weapon type) + if (weapType == ESM::Weapon::MarksmanThrown) + { + static float fThrownWeaponMinSpeed = + MWBase::Environment::get().getWorld()->getStore().get().find("fThrownWeaponMinSpeed")->getFloat(); + static float fThrownWeaponMaxSpeed = + MWBase::Environment::get().getWorld()->getStore().get().find("fThrownWeaponMaxSpeed")->getFloat(); + + projSpeed = + fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * strength; } - else if(roll <= (static_cast(slash) + static_cast(thrust))/total) - movement.mPosition[1] = 1; else - movement.mPosition[1] = movement.mPosition[0] = 0; + { + static float fProjectileMinSpeed = + MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMinSpeed")->getFloat(); + static float fProjectileMaxSpeed = + MWBase::Environment::get().getWorld()->getStore().get().find("fProjectileMaxSpeed")->getFloat(); + + projSpeed = + fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * strength; + } + + // idea: perpendicular to dir to target speed components of target move vector and projectile vector should be the same + + Ogre::Vector3 vActorPos = Ogre::Vector3(actor.getRefData().getPosition().pos); + Ogre::Vector3 vTargetPos = Ogre::Vector3(target.getRefData().getPosition().pos); + Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos; + float distToTarget = vDirToTarget.length(); + + Ogre::Vector3 vTargetMoveDir = vTargetPos - vLastTargetPos; + vTargetMoveDir /= duration; // |vTargetMoveDir| is target real speed in units/sec now + + Ogre::Vector3 vPerpToDir = vDirToTarget.crossProduct(Ogre::Vector3::UNIT_Z); + + float velPerp = vTargetMoveDir.dotProduct(vPerpToDir.normalisedCopy()); + float velDir = vTargetMoveDir.dotProduct(vDirToTarget.normalisedCopy()); + + // time to collision between target and projectile + float t_collision; + + float projVelDirSquared = projSpeed * projSpeed - velPerp * velPerp; + float projDistDiff = vDirToTarget.dotProduct(vTargetMoveDir.normalisedCopy()); + projDistDiff = sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff); + + if (projVelDirSquared > 0) + t_collision = projDistDiff / (sqrt(projVelDirSquared) - velDir); + else t_collision = 0; // speed of projectile is not enough to reach moving target + + return vTargetPos + vTargetMoveDir * t_collision - vActorPos; } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 4b728ff22..e168dfc95 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -8,6 +8,8 @@ #include "movement.hpp" #include "obstacle.hpp" +#include + #include "../mwworld/cellstore.hpp" // for Doors #include "../mwbase/world.hpp" @@ -48,12 +50,17 @@ namespace MWMechanics bool mCombatMove; bool mBackOffDoor; + float mStrength; // this is actually make sense only in ranged combat + float mMinMaxAttackDuration[3][2]; // slash, thrust, chop has different durations + bool mForceNoShortcut; ESM::Position mShortcutFailPos; - ESM::Position mLastPos; + Ogre::Vector3 mLastActorPos; MWMechanics::Movement mMovement; + int mTargetActorId; + Ogre::Vector3 mLastTargetPos; const MWWorld::CellStore* mCell; ObstacleCheck mObstacleCheck; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3b8b91b0e..641ddb43e 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -896,15 +896,26 @@ bool Animation::getInfo(const std::string &groupname, float *complete, float *sp return true; } -float Animation::getStartTime(const std::string &groupname) const +float Animation::getStartTime(const std::string &groupname, bool onlyGroup) const { AnimSourceList::const_iterator iter(mAnimSources.begin()); for(;iter != mAnimSources.end();iter++) { const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; - NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); - if(found != keys.end()) - return found->first; + if (onlyGroup) + { + NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); + if(found != keys.end()) + return found->first; + } + else + { + for(NifOgre::TextKeyMap::const_iterator iter(keys.begin()); iter != keys.end(); ++iter) + { + if(iter->second.compare(0, groupname.size(), groupname) == 0) + return iter->first; + } + } } return -1.f; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 564bb73ef..2fc57f0e9 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -275,7 +275,7 @@ public: bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const; /// Get the absolute position in the animation track of the first text key with the given group. - float getStartTime(const std::string &groupname) const; + float getStartTime(const std::string &groupname, bool onlyGroup) const; /// Get the current absolute position in the animation track for the animation that is currently playing from the given group. float getCurrentTime(const std::string& groupname) const; diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 5f953bd83..3d4886cea 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -32,7 +32,7 @@ float WeaponAnimationTime::getValue() const void WeaponAnimationTime::setGroup(const std::string &group) { mWeaponGroup = group; - mStartTime = mAnimation->getStartTime(mWeaponGroup); + mStartTime = mAnimation->getStartTime(mWeaponGroup, true); } void WeaponAnimationTime::updateStartTime() From 698cbba6ef6bb3e139da218b001686f0583d0a54 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Mon, 9 Jun 2014 23:02:06 +0400 Subject: [PATCH 2/5] old bug + comment fix --- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 5727996d9..76a84cc9e 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -928,7 +928,7 @@ namespace MWMechanics if (timerUpdateAITargets == 0 && iter->first.getTypeName() == typeid(ESM::Creature).name() && !listGuards.empty()) { sBasePoint = Ogre::Vector3(iter->first.getRefData().getPosition().pos); - listGuards.sort(comparePtrDist); // try to engage combat starting from the nearest creature + listGuards.sort(comparePtrDist); // try to engage combat starting from the nearest guard for (std::list::iterator it = listGuards.begin(); it != listGuards.end(); ++it) { diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index db4e59929..b487ffb21 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -975,7 +975,7 @@ bool CharacterController::updateWeaponState() } //if playing combat animation and lowerbody is not busy switch to whole body animation - if((weaptype != WeapType_None || UpperCharState_UnEquipingWeap) && animPlaying) + if((weaptype != WeapType_None || mUpperBodyState == UpperCharState_UnEquipingWeap) && animPlaying) { if( mMovementState != CharState_None || mJumpState != JumpState_None || From 979128b2c55f3c38a5160cf82222aa5325824916 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Tue, 10 Jun 2014 14:20:29 +0400 Subject: [PATCH 3/5] Combat music; some minor combat fixes --- apps/openmw/mwmechanics/actors.cpp | 19 +++++++++++++++++++ apps/openmw/mwmechanics/aicombat.cpp | 8 ++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 76a84cc9e..7102fc0ee 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -970,6 +970,9 @@ namespace MWMechanics } // Kill dead actors, update some variables + + int hostilesCount = 0; // need to know this to play Battle music + for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const MWWorld::Class &cls = iter->first.getClass(); @@ -988,6 +991,8 @@ namespace MWMechanics if(!stats.isDead()) { + if (stats.isHostile()) hostilesCount++; + if(iter->second->isDead()) { // Actor has been resurrected. Notify the CharacterController and re-enable collision. @@ -1045,6 +1050,20 @@ namespace MWMechanics } } + // check if we still have any player enemies to switch music + static bool isBattleMusic = false; + + if (isBattleMusic && hostilesCount == 0) + { + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); + isBattleMusic = false; + } + else if (!isBattleMusic && hostilesCount > 0) + { + MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); + isBattleMusic = true; + } + // if player is in sneak state see if anyone detects him if (player.getClass().getCreatureStats(player).getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak)) { diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 5e2847ed2..2218623da 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -405,8 +405,6 @@ namespace MWMechanics } else // (within attack dist) { - if (!mAttack) mMovement.mPosition[1] = 0; - if(mMovement.mPosition[0] || mMovement.mPosition[1]) { mTimerCombatMove = 0.1f + 0.1f * static_cast(rand())/RAND_MAX; @@ -501,6 +499,12 @@ namespace MWMechanics } mMovement.mPosition[1] = 1; + if (mReadyToAttack) + { + // to stop possible sideway moving after target moved out of attack range + mCombatMove = true; + mTimerCombatMove = 0; + } mReadyToAttack = false; } From d6d9df6cec4c3c5e1dc02bc756f4174243caff56 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Wed, 11 Jun 2014 00:20:46 +0400 Subject: [PATCH 4/5] split getStartTime --- apps/openmw/mwmechanics/aicombat.cpp | 12 ++++++-- apps/openmw/mwrender/animation.cpp | 35 ++++++++++++++---------- apps/openmw/mwrender/animation.hpp | 5 +++- apps/openmw/mwrender/weaponanimation.cpp | 2 +- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 2218623da..4a3c724d5 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -785,15 +785,21 @@ void getMinMaxAttackDuration(const MWWorld::Ptr& actor, float (*fMinMaxDurations // get durations for each attack type for (int i = 0; i < (bRangedWeap ? 1 : 3); i++) { - float start1 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey, false); + float start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey); + + if (start1 < 0) + { + fMinMaxDurations[i][0] = fMinMaxDurations[i][1] = 0.1f; + continue; + } textKey2 = "min attack"; - float start2 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2, false); + float start2 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2); fMinMaxDurations[i][0] = (start2 - start1) / weapSpeed; textKey2 = "max attack"; - start1 = anim->getStartTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2, false); + start1 = anim->getTextKeyTime(weapGroup + (bRangedWeap ? attackType[3] : attackType[i]) + textKey2); fMinMaxDurations[i][1] = fMinMaxDurations[i][0] + (start1 - start2) / weapSpeed; } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6c2746557..1c86fab89 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -903,27 +903,32 @@ bool Animation::getInfo(const std::string &groupname, float *complete, float *sp return true; } -float Animation::getStartTime(const std::string &groupname, bool onlyGroup) const +float Animation::getStartTime(const std::string &groupname) const { - AnimSourceList::const_iterator iter(mAnimSources.begin()); - for(;iter != mAnimSources.end();iter++) + for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) { const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; - if (onlyGroup) + + NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); + if(found != keys.end()) + return found->first; + } + return -1.f; +} + +float Animation::getTextKeyTime(const std::string &textKey) const +{ + for(AnimSourceList::const_iterator iter(mAnimSources.begin()); iter != mAnimSources.end(); ++iter) + { + const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; + + for(NifOgre::TextKeyMap::const_iterator iterKey(keys.begin()); iterKey != keys.end(); ++iterKey) { - NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); - if(found != keys.end()) - return found->first; - } - else - { - for(NifOgre::TextKeyMap::const_iterator iter(keys.begin()); iter != keys.end(); ++iter) - { - if(iter->second.compare(0, groupname.size(), groupname) == 0) - return iter->first; - } + if(iterKey->second.compare(0, textKey.size(), textKey) == 0) + return iterKey->first; } } + return -1.f; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 2fc57f0e9..b2d69b79a 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -275,7 +275,10 @@ public: bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const; /// Get the absolute position in the animation track of the first text key with the given group. - float getStartTime(const std::string &groupname, bool onlyGroup) const; + float getStartTime(const std::string &groupname) const; + + /// Get the absolute position in the animation track of the text key + float getTextKeyTime(const std::string &textKey) const; /// Get the current absolute position in the animation track for the animation that is currently playing from the given group. float getCurrentTime(const std::string& groupname) const; diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index 3d4886cea..5f953bd83 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -32,7 +32,7 @@ float WeaponAnimationTime::getValue() const void WeaponAnimationTime::setGroup(const std::string &group) { mWeaponGroup = group; - mStartTime = mAnimation->getStartTime(mWeaponGroup, true); + mStartTime = mAnimation->getStartTime(mWeaponGroup); } void WeaponAnimationTime::updateStartTime() From df7213185fd90ce93e69a33a0ce63e34e80af343 Mon Sep 17 00:00:00 2001 From: mrcheko Date: Thu, 12 Jun 2014 23:42:33 +0400 Subject: [PATCH 5/5] warning fixes --- apps/openmw/mwmechanics/aicombat.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 4a3c724d5..94bb2cd90 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -167,15 +167,18 @@ namespace MWMechanics const MWWorld::Class& actorClass = actor.getClass(); MWBase::World* world = MWBase::Environment::get().getWorld(); - if (!actorClass.isNpc() && target == world->getPlayerPtr() && - (actorClass.canSwim(actor) && !actor.getClass().canWalk(actor) // 1. pure water creature and Player moved out of water - && !world->isSwimming(target)) - || (!actorClass.canSwim(actor) && world->isSwimming(target))) // 2. creature can't swim to Player + if (!actorClass.isNpc() && + // 1. pure water creature and Player moved out of water + ((target == world->getPlayerPtr() && + actorClass.canSwim(actor) && !actor.getClass().canWalk(actor) && !world->isSwimming(target)) + // 2. creature can't swim to target + || (!actorClass.canSwim(actor) && world->isSwimming(target)))) { - actorClass.getCreatureStats(actor).setHostile(false); + if (target == world->getPlayerPtr()) + actorClass.getCreatureStats(actor).setHostile(false); actorClass.getCreatureStats(actor).setAttackingOrSpell(false); return true; - } + } //Update every frame if(mCombatMove) @@ -246,7 +249,7 @@ namespace MWMechanics const ESM::Weapon *weapon = NULL; MWMechanics::WeaponType weaptype; - float weapRange, weapSpeed = 1.0f; + float weapRange; actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); @@ -275,7 +278,6 @@ namespace MWMechanics // All other WeapTypes are actually weapons, so get is safe. weapon = weaponSlot->get()->mBase; weapRange = weapon->mData.mReach; - weapSpeed = weapon->mData.mSpeed; } weapRange *= 100.0f; }