2013-09-25 18:01:36 +02:00
|
|
|
#include "aicombat.hpp"
|
|
|
|
|
2020-06-11 23:23:30 +02:00
|
|
|
#include <components/misc/coordinateconverter.hpp>
|
2015-04-22 17:58:55 +02:00
|
|
|
#include <components/misc/rng.hpp>
|
2015-03-15 14:07:47 +13:00
|
|
|
|
2022-01-22 15:58:41 +01:00
|
|
|
#include <components/esm3/aisequence.hpp>
|
2013-09-25 18:01:36 +02:00
|
|
|
|
2020-09-04 14:14:56 +02:00
|
|
|
#include <components/misc/mathutil.hpp>
|
|
|
|
|
2021-11-06 00:34:06 +01:00
|
|
|
#include <components/detournavigator/navigatorutils.hpp>
|
2018-09-30 08:38:55 +04:00
|
|
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
|
|
|
|
2022-11-06 20:49:59 +01:00
|
|
|
#include "../mwphysics/raycasting.hpp"
|
2018-09-30 08:38:55 +04:00
|
|
|
|
2013-10-31 09:43:12 +01:00
|
|
|
#include "../mwworld/class.hpp"
|
2014-02-23 20:11:05 +01:00
|
|
|
#include "../mwworld/esmstore.hpp"
|
2014-01-15 22:56:55 +02:00
|
|
|
|
|
|
|
#include "../mwbase/dialoguemanager.hpp"
|
2013-10-31 09:43:12 +01:00
|
|
|
#include "../mwbase/environment.hpp"
|
2017-02-10 06:28:23 +01:00
|
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
2022-08-17 19:16:01 +02:00
|
|
|
#include "../mwbase/world.hpp"
|
2014-01-15 22:56:55 +02:00
|
|
|
|
2016-12-26 00:18:52 +09:00
|
|
|
#include "actorutil.hpp"
|
2017-11-27 18:30:31 +01:00
|
|
|
#include "aicombataction.hpp"
|
2015-07-25 00:28:47 +02:00
|
|
|
#include "character.hpp"
|
2014-02-16 18:48:03 +01:00
|
|
|
#include "creaturestats.hpp"
|
2014-01-29 20:29:07 +01:00
|
|
|
#include "movement.hpp"
|
2014-08-28 00:41:52 +02:00
|
|
|
#include "pathgrid.hpp"
|
2015-07-25 00:28:47 +02:00
|
|
|
#include "steering.hpp"
|
2022-08-17 19:16:01 +02:00
|
|
|
#include "weapontype.hpp"
|
2014-08-28 00:41:52 +02:00
|
|
|
|
2013-10-27 14:03:58 +01:00
|
|
|
namespace
|
|
|
|
{
|
2014-01-17 20:33:49 +02:00
|
|
|
|
|
|
|
// chooses an attack depending on probability to avoid uniformity
|
2022-07-26 17:23:00 +02:00
|
|
|
std::string_view chooseBestAttack(const ESM::Weapon* weapon);
|
2014-06-08 20:59:26 +04:00
|
|
|
|
2015-06-03 19:41:19 +02:00
|
|
|
osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target,
|
2014-06-08 20:59:26 +04:00
|
|
|
const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength);
|
2013-10-27 14:03:58 +01:00
|
|
|
}
|
2013-09-25 18:01:36 +02:00
|
|
|
|
|
|
|
namespace MWMechanics
|
|
|
|
{
|
2017-11-21 20:00:51 +04:00
|
|
|
AiCombat::AiCombat(const MWWorld::Ptr& actor)
|
|
|
|
{
|
|
|
|
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
|
|
|
|
}
|
2014-06-12 23:27:04 +02:00
|
|
|
|
2014-06-14 02:31:01 +02:00
|
|
|
AiCombat::AiCombat(const ESM::AiSequence::AiCombat* combat)
|
|
|
|
{
|
|
|
|
mTargetActorId = combat->mTargetActorId;
|
|
|
|
}
|
|
|
|
|
2014-06-12 23:27:04 +02:00
|
|
|
void AiCombat::init() {}
|
2013-09-25 18:01:36 +02:00
|
|
|
|
2014-04-19 08:16:56 +10:00
|
|
|
/*
|
|
|
|
* Current AiCombat movement states (as of 0.29.0), ignoring the details of the
|
|
|
|
* attack states such as CombatMove, Strike and ReadyToAttack:
|
|
|
|
*
|
2014-04-20 08:31:02 +10:00
|
|
|
* +----(within strike range)----->attack--(beyond strike range)-->follow
|
|
|
|
* | | ^ | |
|
|
|
|
* | | | | |
|
|
|
|
* pursue<---(beyond follow range)-----+ +----(within strike range)---+ |
|
|
|
|
* ^ |
|
|
|
|
* | |
|
|
|
|
* +-------------------------(beyond follow range)--------------------+
|
2014-04-19 08:16:56 +10:00
|
|
|
*
|
|
|
|
*
|
2014-04-20 08:31:02 +10:00
|
|
|
* Below diagram is high level only, the code detail is a little different
|
|
|
|
* (but including those detail will just complicate the diagram w/o adding much)
|
2014-04-19 08:16:56 +10:00
|
|
|
*
|
2014-04-20 08:31:02 +10:00
|
|
|
* +----------(same)-------------->attack---------(same)---------->follow
|
|
|
|
* | |^^ |||
|
|
|
|
* | ||| |||
|
|
|
|
* | +--(same)-----------------+|+----------(same)------------+||
|
|
|
|
* | | | ||
|
|
|
|
* | | | (in range) ||
|
|
|
|
* | <---+ (too far) | ||
|
|
|
|
* pursue<-------------------------[door open]<-----+ ||
|
|
|
|
* ^^^ | ||
|
|
|
|
* ||| | ||
|
|
|
|
* ||+----------evade-----+ | ||
|
|
|
|
* || | [closed door] | ||
|
|
|
|
* |+----> maybe stuck, check --------------> back up, check door ||
|
|
|
|
* | ^ | ^ | ^ ||
|
|
|
|
* | | | | | | ||
|
|
|
|
* | | +---+ +---+ ||
|
|
|
|
* | +-------------------------------------------------------+|
|
|
|
|
* | |
|
|
|
|
* +---------------------------(same)---------------------------------+
|
2014-04-19 08:16:56 +10:00
|
|
|
*
|
|
|
|
* FIXME:
|
|
|
|
*
|
|
|
|
* The new scheme is way too complicated, should really be implemented as a
|
|
|
|
* proper state machine.
|
|
|
|
*
|
|
|
|
* TODO:
|
|
|
|
*
|
2016-12-14 22:11:22 +01:00
|
|
|
* Use the observer pattern to coordinate attacks, provide intelligence on
|
2014-04-19 08:16:56 +10:00
|
|
|
* whether the target was hit, etc.
|
|
|
|
*/
|
2016-08-19 22:15:26 +03:00
|
|
|
|
2015-06-26 17:47:04 +02:00
|
|
|
bool AiCombat::execute(
|
|
|
|
const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
|
2013-09-25 18:01:36 +02:00
|
|
|
{
|
2014-10-10 23:32:15 +02:00
|
|
|
// get or create temporary storage
|
2014-10-08 23:00:36 +02:00
|
|
|
AiCombatStorage& storage = state.get<AiCombatStorage>();
|
2022-09-22 21:26:05 +03:00
|
|
|
|
2014-01-15 22:56:55 +02:00
|
|
|
// General description
|
2015-07-26 17:32:29 +12:00
|
|
|
if (actor.getClass().getCreatureStats(actor).isDead())
|
2014-05-25 21:03:37 +04:00
|
|
|
return true;
|
2013-09-25 18:01:36 +02:00
|
|
|
|
2014-05-15 03:01:48 +02:00
|
|
|
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
|
2014-07-27 20:30:52 +02:00
|
|
|
if (target.isEmpty())
|
2023-01-15 17:08:57 +01:00
|
|
|
return true;
|
2014-05-15 03:01:48 +02:00
|
|
|
|
2014-09-14 10:49:33 +02:00
|
|
|
if (!target.getRefData().getCount()
|
|
|
|
|| !target.getRefData().isEnabled() // Really we should be checking whether the target is currently
|
|
|
|
// registered with the MechanicsManager
|
|
|
|
|| target.getClass().getCreatureStats(target).isDead())
|
2014-01-15 22:56:55 +02:00
|
|
|
return true;
|
2013-11-18 12:33:09 +01:00
|
|
|
|
2019-06-27 19:50:54 +03:00
|
|
|
if (actor == target) // This should never happen.
|
|
|
|
return true;
|
|
|
|
|
2016-11-16 20:15:25 +01:00
|
|
|
if (!storage.isFleeing())
|
2016-08-19 22:15:26 +03:00
|
|
|
{
|
2016-12-06 22:23:06 +09:00
|
|
|
if (storage.mCurrentAction.get()) // need to wait to init action with its attack range
|
2016-11-16 20:15:25 +01:00
|
|
|
{
|
2016-12-06 22:23:06 +09:00
|
|
|
// Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame.
|
|
|
|
updateLOS(actor, target, duration, storage);
|
2021-02-14 04:07:08 +01:00
|
|
|
const float targetReachedTolerance
|
|
|
|
= storage.mLOS && !storage.mUseCustomDestination ? storage.mAttackRange : 0.0f;
|
|
|
|
const osg::Vec3f destination = storage.mUseCustomDestination
|
|
|
|
? storage.mCustomDestination
|
|
|
|
: target.getRefData().getPosition().asVec3();
|
|
|
|
const bool is_target_reached = pathTo(actor, destination, duration, targetReachedTolerance);
|
2016-11-16 20:15:25 +01:00
|
|
|
if (is_target_reached)
|
|
|
|
storage.mReadyToAttack = true;
|
|
|
|
}
|
2016-08-19 22:15:26 +03:00
|
|
|
|
2016-11-16 20:15:25 +01:00
|
|
|
storage.updateCombatMove(duration);
|
2020-02-08 07:16:52 +00:00
|
|
|
storage.mRotateMove = false;
|
2016-11-16 20:15:25 +01:00
|
|
|
if (storage.mReadyToAttack)
|
|
|
|
updateActorsMovement(actor, duration, storage);
|
2020-02-08 07:16:52 +00:00
|
|
|
if (storage.mRotateMove)
|
|
|
|
return false;
|
2022-02-01 18:47:20 +00:00
|
|
|
storage.updateAttack(actor, characterController);
|
2016-11-16 20:15:25 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
updateFleeing(actor, target, duration, storage);
|
|
|
|
}
|
2015-08-09 14:18:55 +12:00
|
|
|
storage.mActionCooldown -= duration;
|
2016-08-19 22:15:26 +03:00
|
|
|
|
2021-03-19 23:23:26 +01:00
|
|
|
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
|
|
|
|
return false;
|
2016-08-19 22:15:26 +03:00
|
|
|
|
2021-03-19 23:23:26 +01:00
|
|
|
return attack(actor, target, storage, characterController);
|
2015-07-26 17:32:29 +12:00
|
|
|
}
|
2013-09-28 12:25:37 +02:00
|
|
|
|
2016-12-26 00:18:52 +09:00
|
|
|
bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage,
|
|
|
|
CharacterController& characterController)
|
2015-07-26 17:32:29 +12:00
|
|
|
{
|
2014-10-10 23:32:15 +02:00
|
|
|
const MWWorld::CellStore*& currentCell = storage.mCell;
|
2014-10-08 23:00:36 +02:00
|
|
|
bool cellChange = currentCell && (actor.getCell() != currentCell);
|
|
|
|
if (!currentCell || cellChange)
|
2014-04-20 08:31:02 +10:00
|
|
|
{
|
2014-10-08 23:00:36 +02:00
|
|
|
currentCell = actor.getCell();
|
2014-04-20 08:31:02 +10:00
|
|
|
}
|
|
|
|
|
2016-11-16 20:15:25 +01:00
|
|
|
bool forceFlee = false;
|
|
|
|
if (!canFight(actor, target))
|
|
|
|
{
|
|
|
|
storage.stopAttack();
|
2022-02-01 18:47:20 +00:00
|
|
|
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false);
|
2016-11-16 20:15:25 +01:00
|
|
|
storage.mActionCooldown = 0.f;
|
2017-02-02 02:15:10 +09:00
|
|
|
// Continue combat if target is player or player follower/escorter and an attack has been attempted
|
2022-02-25 04:02:21 +02:00
|
|
|
const auto& playerFollowersAndEscorters
|
|
|
|
= MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(MWMechanics::getPlayer());
|
2017-02-02 02:15:10 +09:00
|
|
|
bool targetSidesWithPlayer
|
|
|
|
= (std::find(playerFollowersAndEscorters.begin(), playerFollowersAndEscorters.end(), target)
|
|
|
|
!= playerFollowersAndEscorters.end());
|
|
|
|
if ((target == MWMechanics::getPlayer() || targetSidesWithPlayer)
|
2017-02-06 21:32:36 +09:00
|
|
|
&& ((actor.getClass().getCreatureStats(actor).getHitAttemptActorId()
|
|
|
|
== target.getClass().getCreatureStats(target).getActorId())
|
|
|
|
|| (target.getClass().getCreatureStats(target).getHitAttemptActorId()
|
|
|
|
== actor.getClass().getCreatureStats(actor).getActorId())))
|
2016-12-26 00:18:52 +09:00
|
|
|
forceFlee = true;
|
2017-02-02 02:15:10 +09:00
|
|
|
else // Otherwise end combat
|
2016-12-26 00:18:52 +09:00
|
|
|
return true;
|
2016-11-16 20:15:25 +01:00
|
|
|
}
|
|
|
|
|
2015-07-26 17:32:29 +12:00
|
|
|
const MWWorld::Class& actorClass = actor.getClass();
|
2014-08-28 00:41:52 +02:00
|
|
|
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
|
|
|
|
2015-07-26 17:32:29 +12:00
|
|
|
float& actionCooldown = storage.mActionCooldown;
|
2022-04-06 17:06:55 +02:00
|
|
|
std::unique_ptr<Action>& currentAction = storage.mCurrentAction;
|
2016-11-16 20:15:25 +01:00
|
|
|
|
|
|
|
if (!forceFlee)
|
2014-08-28 00:41:52 +02:00
|
|
|
{
|
2016-11-16 20:15:25 +01:00
|
|
|
if (actionCooldown > 0)
|
2016-12-26 00:18:52 +09:00
|
|
|
return false;
|
2016-11-16 20:15:25 +01:00
|
|
|
|
|
|
|
if (characterController.readyToPrepareAttack())
|
|
|
|
{
|
|
|
|
currentAction = prepareNextAction(actor, target);
|
|
|
|
actionCooldown = currentAction->getActionCooldown();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-05-29 13:24:48 +02:00
|
|
|
currentAction = std::make_unique<ActionFlee>();
|
2014-10-08 23:00:36 +02:00
|
|
|
actionCooldown = currentAction->getActionCooldown();
|
2014-08-28 00:41:52 +02:00
|
|
|
}
|
2014-12-02 12:42:01 -02:00
|
|
|
|
2016-11-16 20:15:25 +01:00
|
|
|
if (!currentAction)
|
2016-12-26 00:18:52 +09:00
|
|
|
return false;
|
2016-11-16 20:15:25 +01:00
|
|
|
|
|
|
|
if (storage.isFleeing() != currentAction->isFleeing())
|
2016-08-14 18:02:13 +02:00
|
|
|
{
|
2016-11-16 20:15:25 +01:00
|
|
|
if (currentAction->isFleeing())
|
|
|
|
{
|
|
|
|
storage.startFleeing();
|
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type
The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID
Slowly going through all the changes to make, still hundreds of errors
a lot of functions/structures use std::string or stringview to designate an ID. So it takes time
Continues slowly replacing ids. There are technically more and more compilation errors
I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type
Continue moving forward, changes to the stores
slowly moving along
Starting to see the fruit of those changes.
still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type.
More replacements. Things are starting to get easier
I can see more and more often the issue is that the function is awaiting a RefId, but is given a string
there is less need to go down functions and to fix a long list of them.
Still moving forward, and for the first time error count is going down!
Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably
Cells are back to using string for the name, haven't fixed everything yet. Many other changes
Under the bar of 400 compilation errors.
more good progress <100 compile errors!
More progress
Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string
some more progress on other fronts
Mostly game settings clean
one error opened a lot of other errors. Down to 18, but more will prbably appear
only link errors left??
Fixed link errors
OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
|
|
|
MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("flee"));
|
2016-12-26 00:18:52 +09:00
|
|
|
return false;
|
2016-11-16 20:15:25 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
storage.stopFleeing();
|
2016-08-14 18:02:13 +02:00
|
|
|
}
|
|
|
|
|
2016-11-16 20:15:25 +01:00
|
|
|
bool isRangedCombat = false;
|
|
|
|
float& rangeAttack = storage.mAttackRange;
|
|
|
|
|
|
|
|
rangeAttack = currentAction->getCombatRange(isRangedCombat);
|
|
|
|
|
|
|
|
// Get weapon characteristics
|
|
|
|
const ESM::Weapon* weapon = currentAction->getWeapon();
|
|
|
|
|
2014-04-20 20:35:07 +04:00
|
|
|
ESM::Position pos = actor.getRefData().getPosition();
|
2021-02-14 04:07:08 +01:00
|
|
|
const osg::Vec3f vActorPos(pos.asVec3());
|
|
|
|
const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3());
|
2015-09-17 01:08:16 +02:00
|
|
|
|
2015-11-18 19:00:43 +01:00
|
|
|
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
|
2016-08-14 18:02:13 +02:00
|
|
|
|
2016-12-06 22:23:06 +09:00
|
|
|
storage.mReadyToAttack = (currentAction->isAttackingOrSpell() && distToTarget <= rangeAttack && storage.mLOS);
|
2015-02-14 16:51:54 +01:00
|
|
|
|
2020-10-10 23:06:43 +02:00
|
|
|
if (isRangedCombat)
|
2016-08-14 18:02:13 +02:00
|
|
|
{
|
2020-10-10 23:06:43 +02:00
|
|
|
// rotate actor taking into account target movement direction and projectile speed
|
2021-04-07 12:07:03 +04:00
|
|
|
osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME,
|
|
|
|
(weapon ? weapon->mData.mType : 0), storage.mStrength);
|
2016-08-14 18:02:13 +02:00
|
|
|
|
2020-10-10 23:06:43 +02:00
|
|
|
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
|
|
|
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-04-07 12:07:03 +04:00
|
|
|
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, false);
|
2020-10-10 23:06:43 +02:00
|
|
|
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
|
|
|
storage.mMovement.mRotation[2] = getZAngleToDir(
|
|
|
|
(vTargetPos - vActorPos)); // using vAimDir results in spastic movements since the head is animated
|
|
|
|
}
|
2020-09-04 14:14:56 +02:00
|
|
|
|
2021-02-14 04:07:08 +01:00
|
|
|
storage.mLastTargetPos = vTargetPos;
|
|
|
|
|
2020-10-10 23:06:43 +02:00
|
|
|
if (storage.mReadyToAttack)
|
|
|
|
{
|
2020-09-04 14:14:56 +02:00
|
|
|
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
|
|
|
|
// start new attack
|
|
|
|
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
|
2014-01-15 22:56:55 +02:00
|
|
|
}
|
2021-02-14 04:07:08 +01:00
|
|
|
|
|
|
|
// If actor uses custom destination it has to try to rebuild path because environment can change
|
|
|
|
// (door is opened between actor and target) or target position has changed and current custom destination
|
|
|
|
// is not good enough to attack target.
|
|
|
|
if (storage.mCurrentAction->isAttackingOrSpell()
|
|
|
|
&& ((!storage.mReadyToAttack && !mPathFinder.isPathConstructed())
|
|
|
|
|| (storage.mUseCustomDestination && (storage.mCustomDestination - vTargetPos).length() > rangeAttack)))
|
2021-02-10 22:34:15 +01:00
|
|
|
{
|
2021-04-17 10:53:47 +04:00
|
|
|
const MWBase::World* world = MWBase::Environment::get().getWorld();
|
2021-02-14 04:07:08 +01:00
|
|
|
// Try to build path to the target.
|
2022-06-17 00:28:44 +02:00
|
|
|
const auto agentBounds = world->getPathfindingAgentBounds(actor);
|
2021-02-14 04:07:08 +01:00
|
|
|
const auto navigatorFlags = getNavigatorFlags(actor);
|
|
|
|
const auto areaCosts = getAreaCosts(actor);
|
2023-04-10 15:45:58 +02:00
|
|
|
const ESM::Pathgrid* pathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
|
|
|
|
const auto& pathGridGraph = getPathGridGraph(pathgrid);
|
2022-06-17 00:28:44 +02:00
|
|
|
mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, agentBounds,
|
2021-08-18 22:46:14 +02:00
|
|
|
navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full);
|
2021-02-10 22:34:15 +01:00
|
|
|
|
|
|
|
if (!mPathFinder.isPathConstructed())
|
|
|
|
{
|
2021-02-14 04:07:08 +01:00
|
|
|
// If there is no path, try to find a point on a line from the actor position to target projected
|
|
|
|
// on navmesh to attack the target from there.
|
|
|
|
const auto navigator = world->getNavigator();
|
2022-06-17 00:28:44 +02:00
|
|
|
const auto hit
|
|
|
|
= DetourNavigator::raycast(*navigator, agentBounds, vActorPos, vTargetPos, navigatorFlags);
|
2021-02-14 04:07:08 +01:00
|
|
|
|
|
|
|
if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack)
|
|
|
|
{
|
|
|
|
// If the point is close enough, try to find a path to that point.
|
2022-06-17 00:28:44 +02:00
|
|
|
mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, agentBounds,
|
2021-08-18 22:46:14 +02:00
|
|
|
navigatorFlags, areaCosts, storage.mAttackRange, PathType::Full);
|
2021-02-14 04:07:08 +01:00
|
|
|
if (mPathFinder.isPathConstructed())
|
|
|
|
{
|
|
|
|
// If path to that point is found use it as custom destination.
|
|
|
|
storage.mCustomDestination = *hit;
|
|
|
|
storage.mUseCustomDestination = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mPathFinder.isPathConstructed())
|
|
|
|
{
|
|
|
|
storage.mUseCustomDestination = false;
|
|
|
|
storage.stopAttack();
|
2022-02-01 18:47:20 +00:00
|
|
|
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(false);
|
2022-05-29 13:24:48 +02:00
|
|
|
currentAction = std::make_unique<ActionFlee>();
|
2021-02-14 04:07:08 +01:00
|
|
|
actionCooldown = currentAction->getActionCooldown();
|
|
|
|
storage.startFleeing();
|
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type
The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID
Slowly going through all the changes to make, still hundreds of errors
a lot of functions/structures use std::string or stringview to designate an ID. So it takes time
Continues slowly replacing ids. There are technically more and more compilation errors
I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type
Continue moving forward, changes to the stores
slowly moving along
Starting to see the fruit of those changes.
still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type.
More replacements. Things are starting to get easier
I can see more and more often the issue is that the function is awaiting a RefId, but is given a string
there is less need to go down functions and to fix a long list of them.
Still moving forward, and for the first time error count is going down!
Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably
Cells are back to using string for the name, haven't fixed everything yet. Many other changes
Under the bar of 400 compilation errors.
more good progress <100 compile errors!
More progress
Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string
some more progress on other fronts
Mostly game settings clean
one error opened a lot of other errors. Down to 18, but more will prbably appear
only link errors left??
Fixed link errors
OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
|
|
|
MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("flee"));
|
2021-02-14 04:07:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
storage.mUseCustomDestination = false;
|
2021-02-10 22:34:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-26 00:18:52 +09:00
|
|
|
return false;
|
2014-01-15 22:56:55 +02:00
|
|
|
}
|
2013-09-28 12:25:37 +02:00
|
|
|
|
2016-12-06 22:23:06 +09:00
|
|
|
void MWMechanics::AiCombat::updateLOS(
|
|
|
|
const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage)
|
2016-11-16 20:15:25 +01:00
|
|
|
{
|
|
|
|
static const float LOS_UPDATE_DURATION = 0.5f;
|
2016-12-06 22:23:06 +09:00
|
|
|
if (storage.mUpdateLOSTimer <= 0.f)
|
2016-11-16 20:15:25 +01:00
|
|
|
{
|
2016-12-06 22:23:06 +09:00
|
|
|
storage.mLOS = MWBase::Environment::get().getWorld()->getLOS(actor, target);
|
|
|
|
storage.mUpdateLOSTimer = LOS_UPDATE_DURATION;
|
2016-11-16 20:15:25 +01:00
|
|
|
}
|
|
|
|
else
|
2016-12-06 22:23:06 +09:00
|
|
|
storage.mUpdateLOSTimer -= duration;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MWMechanics::AiCombat::updateFleeing(
|
|
|
|
const MWWorld::Ptr& actor, const MWWorld::Ptr& target, float duration, MWMechanics::AiCombatStorage& storage)
|
|
|
|
{
|
|
|
|
static const float BLIND_RUN_DURATION = 1.0f;
|
|
|
|
|
|
|
|
updateLOS(actor, target, duration, storage);
|
2016-11-16 20:15:25 +01:00
|
|
|
|
|
|
|
AiCombatStorage::FleeState& state = storage.mFleeState;
|
|
|
|
switch (state)
|
|
|
|
{
|
|
|
|
case AiCombatStorage::FleeState_None:
|
|
|
|
return;
|
|
|
|
|
|
|
|
case AiCombatStorage::FleeState_Idle:
|
2022-09-22 21:26:05 +03:00
|
|
|
{
|
2016-11-16 20:15:25 +01:00
|
|
|
float triggerDist = getMaxAttackDistance(target);
|
2023-02-04 18:45:53 +01:00
|
|
|
const MWWorld::Cell* cellVariant = storage.mCell->getCell();
|
|
|
|
if (storage.mLOS && (triggerDist >= 1000 || getDistanceMinusHalfExtents(actor, target) <= triggerDist))
|
2016-11-16 20:15:25 +01:00
|
|
|
{
|
|
|
|
const ESM::Pathgrid* pathgrid
|
2023-02-04 18:45:53 +01:00
|
|
|
= MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*cellVariant);
|
2016-11-16 20:15:25 +01:00
|
|
|
|
|
|
|
bool runFallback = true;
|
2022-09-22 21:26:05 +03:00
|
|
|
|
2016-11-16 20:15:25 +01:00
|
|
|
if (pathgrid != nullptr && !pathgrid->mPoints.empty()
|
|
|
|
&& !actor.getClass().isPureWaterCreature(actor))
|
|
|
|
{
|
|
|
|
ESM::Pathgrid::PointList points;
|
2023-01-27 01:13:17 +01:00
|
|
|
Misc::CoordinateConverter coords(*storage.mCell->getCell());
|
2016-11-16 20:15:25 +01:00
|
|
|
|
|
|
|
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
|
|
|
coords.toLocal(localPos);
|
|
|
|
|
2021-09-23 01:47:19 +02:00
|
|
|
int closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos);
|
|
|
|
for (int i = 0; i < static_cast<int>(pathgrid->mPoints.size()); i++)
|
2016-11-16 20:15:25 +01:00
|
|
|
{
|
2018-08-18 13:42:11 +03:00
|
|
|
if (i != closestPointIndex
|
2023-04-10 15:45:58 +02:00
|
|
|
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, i))
|
2016-11-16 20:15:25 +01:00
|
|
|
{
|
|
|
|
points.push_back(pathgrid->mPoints[static_cast<size_t>(i)]);
|
|
|
|
}
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
2016-11-16 20:15:25 +01:00
|
|
|
|
|
|
|
if (!points.empty())
|
|
|
|
{
|
2022-03-06 21:56:02 +02:00
|
|
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
|
|
|
ESM::Pathgrid::Point dest = points[Misc::Rng::rollDice(points.size(), prng)];
|
2016-11-16 20:15:25 +01:00
|
|
|
coords.toWorld(dest);
|
|
|
|
|
|
|
|
state = AiCombatStorage::FleeState_RunToDestination;
|
|
|
|
storage.mFleeDest = ESM::Pathgrid::Point(dest.mX, dest.mY, dest.mZ);
|
|
|
|
|
|
|
|
runFallback = false;
|
|
|
|
}
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
2016-11-16 20:15:25 +01:00
|
|
|
|
|
|
|
if (runFallback)
|
|
|
|
{
|
|
|
|
state = AiCombatStorage::FleeState_RunBlindly;
|
|
|
|
storage.mFleeBlindRunTimer = 0.0f;
|
|
|
|
}
|
|
|
|
}
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
2016-11-16 20:15:25 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case AiCombatStorage::FleeState_RunBlindly:
|
2022-09-22 21:26:05 +03:00
|
|
|
{
|
2016-11-16 20:15:25 +01:00
|
|
|
// timer to prevent twitchy movement that can be observed in vanilla MW
|
|
|
|
if (storage.mFleeBlindRunTimer < BLIND_RUN_DURATION)
|
|
|
|
{
|
|
|
|
storage.mFleeBlindRunTimer += duration;
|
2022-09-22 21:26:05 +03:00
|
|
|
|
2020-07-13 20:04:23 +02:00
|
|
|
storage.mMovement.mRotation[0] = -actor.getRefData().getPosition().rot[0];
|
2016-11-16 20:15:25 +01:00
|
|
|
storage.mMovement.mRotation[2] = osg::PI
|
|
|
|
+ getZAngleToDir(
|
|
|
|
target.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3());
|
|
|
|
storage.mMovement.mPosition[1] = 1;
|
|
|
|
updateActorsMovement(actor, duration, storage);
|
|
|
|
}
|
2022-09-22 21:26:05 +03:00
|
|
|
else
|
2016-11-16 20:15:25 +01:00
|
|
|
state = AiCombatStorage::FleeState_Idle;
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
2016-11-16 20:15:25 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case AiCombatStorage::FleeState_RunToDestination:
|
2022-09-22 21:26:05 +03:00
|
|
|
{
|
2018-08-29 18:38:12 +03:00
|
|
|
static const float fFleeDistance = MWBase::Environment::get()
|
2018-09-30 08:38:55 +04:00
|
|
|
.getWorld()
|
2018-08-29 18:38:12 +03:00
|
|
|
->getStore()
|
|
|
|
.get<ESM::GameSetting>()
|
|
|
|
.find("fFleeDistance")
|
|
|
|
->mValue.getFloat();
|
2022-09-22 21:26:05 +03:00
|
|
|
|
2016-11-16 20:15:25 +01:00
|
|
|
float dist
|
|
|
|
= (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
|
2016-12-06 22:23:06 +09:00
|
|
|
if ((dist > fFleeDistance && !storage.mLOS)
|
2018-08-18 13:42:11 +03:00
|
|
|
|| pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration))
|
2016-11-16 20:15:25 +01:00
|
|
|
{
|
|
|
|
state = AiCombatStorage::FleeState_Idle;
|
|
|
|
}
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
2016-11-16 20:15:25 +01:00
|
|
|
break;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-08-19 22:15:26 +03:00
|
|
|
void AiCombat::updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage)
|
2015-07-26 17:23:45 +12:00
|
|
|
{
|
2016-08-19 22:15:26 +03:00
|
|
|
// apply combat movement
|
2020-09-04 14:14:56 +02:00
|
|
|
float deltaAngle = storage.mMovement.mRotation[2] - actor.getRefData().getPosition().rot[2];
|
|
|
|
osg::Vec2f movement = Misc::rotateVec2f(
|
|
|
|
osg::Vec2f(storage.mMovement.mPosition[0], storage.mMovement.mPosition[1]), -deltaAngle);
|
|
|
|
|
2015-07-26 17:23:45 +12:00
|
|
|
MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor);
|
2020-09-04 14:14:56 +02:00
|
|
|
actorMovementSettings.mPosition[0] = movement.x();
|
|
|
|
actorMovementSettings.mPosition[1] = movement.y();
|
2016-08-19 22:15:26 +03:00
|
|
|
actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2];
|
|
|
|
|
2018-08-15 19:05:57 +04:00
|
|
|
rotateActorOnAxis(actor, 2, actorMovementSettings, storage);
|
|
|
|
rotateActorOnAxis(actor, 0, actorMovementSettings, storage);
|
2015-07-26 17:23:45 +12:00
|
|
|
}
|
2014-01-29 20:29:07 +01:00
|
|
|
|
2015-08-09 14:18:55 +12:00
|
|
|
void AiCombat::rotateActorOnAxis(
|
2018-08-15 19:05:57 +04:00
|
|
|
const MWWorld::Ptr& actor, int axis, MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage)
|
2015-07-26 17:23:45 +12:00
|
|
|
{
|
|
|
|
actorMovementSettings.mRotation[axis] = 0;
|
2020-09-04 14:14:56 +02:00
|
|
|
bool isRangedCombat = false;
|
|
|
|
storage.mCurrentAction->getCombatRange(isRangedCombat);
|
|
|
|
float eps = isRangedCombat ? osg::DegreesToRadians(0.5) : osg::DegreesToRadians(3.f);
|
|
|
|
float targetAngleRadians = storage.mMovement.mRotation[axis];
|
2020-02-08 07:16:52 +00:00
|
|
|
storage.mRotateMove = !smoothTurn(actor, targetAngleRadians, axis, eps);
|
2014-01-15 22:56:55 +02:00
|
|
|
}
|
2013-09-28 12:25:37 +02:00
|
|
|
|
2014-05-17 19:20:57 +04:00
|
|
|
MWWorld::Ptr AiCombat::getTarget() const
|
2014-01-07 04:12:37 +04:00
|
|
|
{
|
2022-04-17 17:15:00 +00:00
|
|
|
if (mCachedTarget.isEmpty() || mCachedTarget.getRefData().isDeleted()
|
|
|
|
|| !mCachedTarget.getRefData().isEnabled())
|
|
|
|
{
|
|
|
|
mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
|
|
|
|
}
|
|
|
|
return mCachedTarget;
|
2014-01-07 04:12:37 +04:00
|
|
|
}
|
|
|
|
|
2014-06-12 23:27:04 +02:00
|
|
|
void AiCombat::writeState(ESM::AiSequence::AiSequence& sequence) const
|
|
|
|
{
|
2022-05-29 13:24:48 +02:00
|
|
|
auto combat = std::make_unique<ESM::AiSequence::AiCombat>();
|
2014-06-12 23:27:04 +02:00
|
|
|
combat->mTargetActorId = mTargetActorId;
|
|
|
|
|
|
|
|
ESM::AiSequence::AiPackageContainer package;
|
|
|
|
package.mType = ESM::AiSequence::Ai_Combat;
|
2022-02-23 00:39:30 +01:00
|
|
|
package.mPackage = std::move(combat);
|
|
|
|
sequence.mPackages.push_back(std::move(package));
|
2014-06-12 23:27:04 +02:00
|
|
|
}
|
2015-08-09 14:06:52 +12:00
|
|
|
|
2022-03-06 21:56:02 +02:00
|
|
|
AiCombatStorage::AiCombatStorage()
|
|
|
|
: mAttackCooldown(0.0f)
|
|
|
|
, mReaction(MWBase::Environment::get().getWorld()->getPrng())
|
|
|
|
, mTimerCombatMove(0.0f)
|
|
|
|
, mReadyToAttack(false)
|
|
|
|
, mAttack(false)
|
|
|
|
, mAttackRange(0.0f)
|
|
|
|
, mCombatMove(false)
|
|
|
|
, mRotateMove(false)
|
|
|
|
, mLastTargetPos(0, 0, 0)
|
|
|
|
, mCell(nullptr)
|
|
|
|
, mCurrentAction()
|
|
|
|
, mActionCooldown(0.0f)
|
|
|
|
, mStrength()
|
|
|
|
, mForceNoShortcut(false)
|
|
|
|
, mShortcutFailPos()
|
|
|
|
, mMovement()
|
|
|
|
, mFleeState(FleeState_None)
|
|
|
|
, mLOS(false)
|
|
|
|
, mUpdateLOSTimer(0.0f)
|
|
|
|
, mFleeBlindRunTimer(0.0f)
|
|
|
|
, mUseCustomDestination(false)
|
|
|
|
, mCustomDestination()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2016-09-13 02:48:36 +02:00
|
|
|
void AiCombatStorage::startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack,
|
|
|
|
const MWWorld::Ptr& actor, const MWWorld::Ptr& target)
|
2015-08-09 14:06:52 +12:00
|
|
|
{
|
2022-03-06 21:56:02 +02:00
|
|
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
|
|
|
|
2017-11-07 17:57:23 +00:00
|
|
|
// get the range of the target's weapon
|
|
|
|
MWWorld::Ptr targetWeapon = MWWorld::Ptr();
|
|
|
|
const MWWorld::Class& targetClass = target.getClass();
|
|
|
|
|
|
|
|
if (targetClass.hasInventoryStore(target))
|
|
|
|
{
|
2018-12-26 13:45:28 +04:00
|
|
|
int weapType = ESM::Weapon::None;
|
|
|
|
MWWorld::ContainerStoreIterator weaponSlot = MWMechanics::getActiveWeapon(target, &weapType);
|
|
|
|
if (weapType > ESM::Weapon::None)
|
2017-11-07 17:57:23 +00:00
|
|
|
targetWeapon = *weaponSlot;
|
|
|
|
}
|
|
|
|
|
2017-11-11 12:00:23 +00:00
|
|
|
bool targetUsesRanged = false;
|
|
|
|
float rangeAttackOfTarget = ActionWeapon(targetWeapon).getCombatRange(targetUsesRanged);
|
2022-08-15 02:58:44 +03:00
|
|
|
|
|
|
|
if (mMovement.mPosition[0])
|
2015-08-09 14:06:52 +12:00
|
|
|
{
|
2022-03-06 21:56:02 +02:00
|
|
|
mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(prng);
|
2015-08-09 14:06:52 +12:00
|
|
|
mCombatMove = true;
|
|
|
|
}
|
2022-08-15 02:58:44 +03:00
|
|
|
// dodge movements (for NPCs and bipedal creatures)
|
|
|
|
// Note: do not use for ranged combat yet since in couple with back up behaviour can move actor out of cliff
|
|
|
|
else if (actor.getClass().isBipedal(actor) && !isDistantCombat)
|
|
|
|
{
|
|
|
|
float moveDuration = 0;
|
|
|
|
float angleToTarget
|
|
|
|
= Misc::normalizeAngle(mMovement.mRotation[2] - actor.getRefData().getPosition().rot[2]);
|
|
|
|
// Apply a big side step if enemy tries to get around and come from behind.
|
|
|
|
// Otherwise apply a random side step (kind of dodging) with some probability
|
|
|
|
// if actor is within range of target's weapon.
|
|
|
|
if (std::abs(angleToTarget) > osg::PI / 4)
|
|
|
|
moveDuration = 0.2f;
|
|
|
|
else if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability(prng) < 0.25)
|
|
|
|
moveDuration = 0.1f + 0.1f * Misc::Rng::rollClosedProbability(prng);
|
|
|
|
if (moveDuration > 0)
|
|
|
|
{
|
|
|
|
mMovement.mPosition[0] = Misc::Rng::rollProbability(prng) < 0.5 ? 1.0f : -1.0f; // to the left/right
|
|
|
|
mTimerCombatMove = moveDuration;
|
|
|
|
mCombatMove = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mMovement.mPosition[1] = 0;
|
|
|
|
if (isDistantCombat)
|
2018-09-30 08:38:55 +04:00
|
|
|
{
|
|
|
|
// Backing up behaviour
|
|
|
|
// Actor backs up slightly further away than opponent's weapon range
|
|
|
|
// (in vanilla - only as far as oponent's weapon range),
|
|
|
|
// or not at all if opponent is using a ranged weapon
|
|
|
|
|
|
|
|
if (targetUsesRanged
|
|
|
|
|| distToTarget > rangeAttackOfTarget * 1.5) // Don't back up if the target is wielding ranged weapon
|
|
|
|
return;
|
|
|
|
|
|
|
|
// actor should not back up into water
|
|
|
|
if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(actor), 0.5f))
|
|
|
|
return;
|
|
|
|
|
|
|
|
int mask
|
|
|
|
= MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door;
|
|
|
|
|
|
|
|
// Actor can not back up if there is no free space behind
|
|
|
|
// Currently we take the 35% of actor's height from the ground as vector height.
|
|
|
|
// This approach allows us to detect small obstacles (e.g. crates) and curved walls.
|
|
|
|
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
|
|
|
|
osg::Vec3f pos = actor.getRefData().getPosition().asVec3();
|
|
|
|
osg::Vec3f source = pos + osg::Vec3f(0, 0, 0.75f * halfExtents.z());
|
|
|
|
osg::Vec3f fallbackDirection = actor.getRefData().getBaseNode()->getAttitude() * osg::Vec3f(0, -1, 0);
|
|
|
|
osg::Vec3f destination = source + fallbackDirection * (halfExtents.y() + 16);
|
|
|
|
|
2022-11-06 20:49:59 +01:00
|
|
|
const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting();
|
|
|
|
bool isObstacleDetected = rayCasting->castRay(source, destination, mask).mHit;
|
2018-09-30 08:38:55 +04:00
|
|
|
if (isObstacleDetected)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Check if there is nothing behind - probably actor is near cliff.
|
|
|
|
// A current approach: cast ray 1.5-yard ray down in 1.5 yard behind actor from 35% of actor's height.
|
|
|
|
// If we did not hit anything, there is a cliff behind actor.
|
|
|
|
source = pos + osg::Vec3f(0, 0, 0.75f * halfExtents.z()) + fallbackDirection * (halfExtents.y() + 96);
|
|
|
|
destination = source - osg::Vec3f(0, 0, 0.75f * halfExtents.z() + 96);
|
2022-11-06 20:49:59 +01:00
|
|
|
bool isCliffDetected = !rayCasting->castRay(source, destination, mask).mHit;
|
2018-09-30 08:38:55 +04:00
|
|
|
if (isCliffDetected)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mMovement.mPosition[1] = -1;
|
|
|
|
}
|
2015-08-09 14:06:52 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
void AiCombatStorage::updateCombatMove(float duration)
|
|
|
|
{
|
|
|
|
if (mCombatMove)
|
|
|
|
{
|
|
|
|
mTimerCombatMove -= duration;
|
|
|
|
if (mTimerCombatMove <= 0)
|
|
|
|
{
|
|
|
|
stopCombatMove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AiCombatStorage::stopCombatMove()
|
|
|
|
{
|
|
|
|
mTimerCombatMove = 0;
|
2022-08-15 02:58:44 +03:00
|
|
|
mMovement.mPosition[0] = 0;
|
2015-08-09 14:06:52 +12:00
|
|
|
mCombatMove = false;
|
|
|
|
}
|
2015-08-09 14:08:42 +12:00
|
|
|
|
|
|
|
void AiCombatStorage::startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController,
|
|
|
|
const ESM::Weapon* weapon, bool distantCombat)
|
|
|
|
{
|
|
|
|
if (mReadyToAttack && characterController.readyToStartAttack())
|
|
|
|
{
|
|
|
|
if (mAttackCooldown <= 0)
|
|
|
|
{
|
|
|
|
mAttack = true; // attack starts just now
|
2022-02-01 18:47:20 +00:00
|
|
|
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(true);
|
2015-08-09 14:08:42 +12:00
|
|
|
|
|
|
|
if (!distantCombat)
|
2016-09-03 22:40:24 +09:00
|
|
|
characterController.setAIAttackType(chooseBestAttack(weapon));
|
2015-08-09 14:08:42 +12:00
|
|
|
|
2022-03-06 21:56:02 +02:00
|
|
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
|
|
|
mStrength = Misc::Rng::rollClosedProbability(prng);
|
2015-08-09 14:08:42 +12:00
|
|
|
|
|
|
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
|
|
|
|
2018-08-29 18:38:12 +03:00
|
|
|
float baseDelay = store.get<ESM::GameSetting>().find("fCombatDelayCreature")->mValue.getFloat();
|
2015-08-09 14:08:42 +12:00
|
|
|
if (actor.getClass().isNpc())
|
|
|
|
{
|
2018-08-29 18:38:12 +03:00
|
|
|
baseDelay = store.get<ESM::GameSetting>().find("fCombatDelayNPC")->mValue.getFloat();
|
2019-01-26 16:50:19 +03:00
|
|
|
}
|
2015-08-09 14:08:42 +12:00
|
|
|
|
2019-01-26 16:50:19 +03:00
|
|
|
// Say a provoking combat phrase
|
|
|
|
const int iVoiceAttackOdds
|
|
|
|
= store.get<ESM::GameSetting>().find("iVoiceAttackOdds")->mValue.getInteger();
|
2022-03-06 21:56:02 +02:00
|
|
|
if (Misc::Rng::roll0to99(prng) < iVoiceAttackOdds)
|
2019-01-26 16:50:19 +03:00
|
|
|
{
|
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type
The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID
Slowly going through all the changes to make, still hundreds of errors
a lot of functions/structures use std::string or stringview to designate an ID. So it takes time
Continues slowly replacing ids. There are technically more and more compilation errors
I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type
Continue moving forward, changes to the stores
slowly moving along
Starting to see the fruit of those changes.
still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type.
More replacements. Things are starting to get easier
I can see more and more often the issue is that the function is awaiting a RefId, but is given a string
there is less need to go down functions and to fix a long list of them.
Still moving forward, and for the first time error count is going down!
Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably
Cells are back to using string for the name, haven't fixed everything yet. Many other changes
Under the bar of 400 compilation errors.
more good progress <100 compile errors!
More progress
Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string
some more progress on other fronts
Mostly game settings clean
one error opened a lot of other errors. Down to 18, but more will prbably appear
only link errors left??
Fixed link errors
OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
|
|
|
MWBase::Environment::get().getDialogueManager()->say(actor, ESM::RefId::stringRefId("attack"));
|
2015-08-09 14:08:42 +12:00
|
|
|
}
|
2022-03-06 21:56:02 +02:00
|
|
|
mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(prng), baseDelay + 0.9);
|
2015-08-09 14:08:42 +12:00
|
|
|
}
|
|
|
|
else
|
2016-08-19 22:15:26 +03:00
|
|
|
mAttackCooldown -= AI_REACTION_TIME;
|
2015-08-09 14:08:42 +12:00
|
|
|
}
|
|
|
|
}
|
2015-08-09 14:10:08 +12:00
|
|
|
|
2022-02-01 18:47:20 +00:00
|
|
|
void AiCombatStorage::updateAttack(const MWWorld::Ptr& actor, CharacterController& characterController)
|
2015-08-09 14:10:08 +12:00
|
|
|
{
|
2022-07-31 14:41:05 +03:00
|
|
|
if (mAttack)
|
2015-08-09 14:10:08 +12:00
|
|
|
{
|
2022-07-31 14:41:05 +03:00
|
|
|
float attackStrength = characterController.calculateWindUp();
|
|
|
|
mAttack
|
|
|
|
= !characterController.readyToPrepareAttack() && attackStrength < mStrength && attackStrength != -1.f;
|
2015-08-09 14:10:08 +12:00
|
|
|
}
|
2022-02-01 18:47:20 +00:00
|
|
|
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mAttack);
|
2015-08-09 14:10:08 +12:00
|
|
|
}
|
2015-08-09 14:29:38 +12:00
|
|
|
|
|
|
|
void AiCombatStorage::stopAttack()
|
|
|
|
{
|
|
|
|
mMovement.mPosition[0] = 0;
|
|
|
|
mMovement.mPosition[1] = 0;
|
|
|
|
mMovement.mPosition[2] = 0;
|
|
|
|
mReadyToAttack = false;
|
|
|
|
mAttack = false;
|
|
|
|
}
|
2016-11-16 20:15:25 +01:00
|
|
|
|
|
|
|
void AiCombatStorage::startFleeing()
|
|
|
|
{
|
|
|
|
stopFleeing();
|
|
|
|
mFleeState = FleeState_Idle;
|
|
|
|
}
|
|
|
|
|
|
|
|
void AiCombatStorage::stopFleeing()
|
|
|
|
{
|
|
|
|
mMovement.mPosition[0] = 0;
|
|
|
|
mMovement.mPosition[1] = 0;
|
|
|
|
mMovement.mPosition[2] = 0;
|
|
|
|
mFleeState = FleeState_None;
|
|
|
|
mFleeDest = ESM::Pathgrid::Point(0, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AiCombatStorage::isFleeing()
|
|
|
|
{
|
|
|
|
return mFleeState != FleeState_None;
|
|
|
|
}
|
2014-01-17 20:33:49 +02:00
|
|
|
}
|
2014-01-15 22:56:55 +02:00
|
|
|
|
2014-01-17 20:33:49 +02:00
|
|
|
namespace
|
|
|
|
{
|
2014-01-15 22:56:55 +02:00
|
|
|
|
2022-07-26 17:23:00 +02:00
|
|
|
std::string_view chooseBestAttack(const ESM::Weapon* weapon)
|
2014-06-08 20:59:26 +04:00
|
|
|
{
|
2018-10-09 10:21:12 +04:00
|
|
|
if (weapon != nullptr)
|
2022-09-22 21:26:05 +03:00
|
|
|
{
|
2014-06-08 20:59:26 +04:00
|
|
|
// 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;
|
2022-09-22 21:26:05 +03:00
|
|
|
|
2022-03-06 21:56:02 +02:00
|
|
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
|
|
|
float roll = Misc::Rng::rollClosedProbability(prng) * (slash + chop + thrust);
|
2015-08-04 18:15:58 +12:00
|
|
|
if (roll <= slash)
|
2022-07-26 17:23:00 +02:00
|
|
|
return "slash";
|
2015-08-04 18:15:58 +12:00
|
|
|
else if (roll <= (slash + thrust))
|
2022-07-26 17:23:00 +02:00
|
|
|
return "thrust";
|
2014-01-23 23:14:20 +02:00
|
|
|
else
|
2022-07-26 17:23:00 +02:00
|
|
|
return "chop";
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
2022-07-26 17:23:00 +02:00
|
|
|
return MWMechanics::CharacterController::getRandomAttackType();
|
2014-06-08 20:59:26 +04:00
|
|
|
}
|
2014-02-21 11:35:46 +01:00
|
|
|
|
2014-06-08 20:59:26 +04:00
|
|
|
osg::Vec3f AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target,
|
|
|
|
const osg::Vec3f& vLastTargetPos, float duration, int weapType, float strength)
|
2014-01-17 20:33:49 +02:00
|
|
|
{
|
2018-08-29 18:38:12 +03:00
|
|
|
float projSpeed;
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& gmst
|
|
|
|
= MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
2014-06-08 20:59:26 +04:00
|
|
|
|
2018-08-10 20:07:38 +03:00
|
|
|
// get projectile speed (depending on weapon type)
|
|
|
|
if (MWMechanics::getWeaponType(weapType)->mWeaponClass == ESM::WeaponType::Thrown)
|
2014-06-08 20:59:26 +04:00
|
|
|
{
|
2018-08-29 18:38:12 +03:00
|
|
|
static float fThrownWeaponMinSpeed = gmst.find("fThrownWeaponMinSpeed")->mValue.getFloat();
|
|
|
|
static float fThrownWeaponMaxSpeed = gmst.find("fThrownWeaponMaxSpeed")->mValue.getFloat();
|
2014-06-08 20:59:26 +04:00
|
|
|
|
2018-08-10 20:07:38 +03:00
|
|
|
projSpeed = fThrownWeaponMinSpeed + (fThrownWeaponMaxSpeed - fThrownWeaponMinSpeed) * strength;
|
|
|
|
}
|
|
|
|
else if (weapType != 0)
|
2022-09-22 21:26:05 +03:00
|
|
|
{
|
2018-08-10 20:07:38 +03:00
|
|
|
static float fProjectileMinSpeed = gmst.find("fProjectileMinSpeed")->mValue.getFloat();
|
2018-08-29 18:38:12 +03:00
|
|
|
static float fProjectileMaxSpeed = gmst.find("fProjectileMaxSpeed")->mValue.getFloat();
|
2014-06-08 20:59:26 +04:00
|
|
|
|
|
|
|
projSpeed = fProjectileMinSpeed + (fProjectileMaxSpeed - fProjectileMinSpeed) * strength;
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
2014-06-08 20:59:26 +04:00
|
|
|
else // weapType is 0 ==> it's a target spell projectile
|
2022-09-22 21:26:05 +03:00
|
|
|
{
|
2014-06-08 20:59:26 +04:00
|
|
|
projSpeed = gmst.find("fTargetSpellMaxSpeed")->mValue.getFloat();
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
2014-06-08 20:59:26 +04:00
|
|
|
|
2021-04-07 12:07:03 +04:00
|
|
|
// idea: perpendicular to dir to target speed components of target move vector and projectile vector should be
|
2014-06-08 20:59:26 +04:00
|
|
|
// the same
|
|
|
|
|
2015-06-03 19:41:19 +02:00
|
|
|
osg::Vec3f vTargetPos = target.getRefData().getPosition().asVec3();
|
2014-06-08 20:59:26 +04:00
|
|
|
osg::Vec3f vDirToTarget = MWBase::Environment::get().getWorld()->aimToTarget(actor, target, true);
|
|
|
|
float distToTarget = vDirToTarget.length();
|
|
|
|
|
2015-06-03 19:41:19 +02:00
|
|
|
osg::Vec3f vTargetMoveDir = vTargetPos - vLastTargetPos;
|
|
|
|
vTargetMoveDir /= duration; // |vTargetMoveDir| is target real speed in units/sec now
|
2014-06-08 20:59:26 +04:00
|
|
|
|
2015-06-03 19:41:19 +02:00
|
|
|
osg::Vec3f vPerpToDir = vDirToTarget ^ osg::Vec3f(0, 0, 1); // cross product
|
|
|
|
|
|
|
|
vPerpToDir.normalize();
|
|
|
|
osg::Vec3f vDirToTargetNormalized = vDirToTarget;
|
|
|
|
vDirToTargetNormalized.normalize();
|
2014-06-08 20:59:26 +04:00
|
|
|
|
|
|
|
// dot product
|
|
|
|
float velPerp = vTargetMoveDir * vPerpToDir;
|
|
|
|
float velDir = vTargetMoveDir * vDirToTargetNormalized;
|
|
|
|
|
|
|
|
// time to collision between target and projectile
|
|
|
|
float t_collision;
|
2015-06-03 19:41:19 +02:00
|
|
|
|
2021-04-25 21:58:48 +00:00
|
|
|
float projVelDirSquared = projSpeed * projSpeed - velPerp * velPerp;
|
|
|
|
if (projVelDirSquared > 0)
|
2022-09-22 21:26:05 +03:00
|
|
|
{
|
2021-04-25 21:58:48 +00:00
|
|
|
osg::Vec3f vTargetMoveDirNormalized = vTargetMoveDir;
|
|
|
|
vTargetMoveDirNormalized.normalize();
|
2014-06-08 20:59:26 +04:00
|
|
|
|
2015-06-03 19:41:19 +02:00
|
|
|
float projDistDiff = vDirToTarget * vTargetMoveDirNormalized; // dot product
|
|
|
|
projDistDiff = std::sqrt(distToTarget * distToTarget - projDistDiff * projDistDiff);
|
2014-06-08 20:59:26 +04:00
|
|
|
|
2021-04-25 21:58:48 +00:00
|
|
|
t_collision = projDistDiff / (std::sqrt(projVelDirSquared) - velDir);
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
|
|
|
else
|
2021-04-25 21:58:48 +00:00
|
|
|
t_collision = 0; // speed of projectile is not enough to reach moving target
|
2022-09-22 21:26:05 +03:00
|
|
|
|
2015-09-17 01:08:16 +02:00
|
|
|
return vDirToTarget + vTargetMoveDir * t_collision;
|
2013-09-25 18:01:36 +02:00
|
|
|
}
|
|
|
|
|
2014-01-29 00:03:00 +02:00
|
|
|
}
|