Compare commits

...

2 Commits

Author SHA1 Message Date
Tecc
05057ae9b5 feat: Improve bot mount behavior to faster close distance between bot and master (#1760)
# Fix bot mount behavior when master dismounts

## Summary

Improves bot mount/dismount logic to ensure better coordination with the
master player. The bot now remains mounted when closing distance to a
recently dismounted master and mounts up to assist the master in combat.

The changes have been tested using the testcases described in the second
half of the PR description, which provide some directions on how this PR
can be tested and verified.

Closes: #1660

## Changes

- Add masterInCombat variable to track master combat state
- Modify target-based logic to consider master combat state
- Change mount priority when bot is not in combat to favor
master-following
- Remove combatReach from distance calculations (duplicate)

## Implementation

Added two methods:
- `StayMountedToCloseDistance()` - prevents premature dismounting when
the master dismounts
- `ShouldMountToCloseDistance()` - determines when to mount for master
assistance, even if master is not mounted at this time

Modified Execute() method:
- Target-based shouldMount/shouldDismount considers the master's combat
state
- Combat assistance logic separated from general following
- Mount when master in combat, but the bot is not

Distance logic:
- Combat: dismount at CalculateDismountDistance() (18-25 yards)
- Non-combat: dismount at 10 yards
- Mount threshold: 21+ yards

## Result
- Bots mount to assist masters in combat
- Bots stay mounted longer during travel
- Different dismount distances based on context
- Existing mount selection logic unchanged


# Mount Behavior Testing

## Prerequisites

1. Add a test bot: `.playerbots bot add <charactername>` or `.playerbots
bot addclass <class>`
2. Test in a level-appropriate outdoor area (mobs should not die with
one hit, as this makes testing combat assistance impossible)
3. Both master and bot must have mounts available

## Test 1: Combat Assistance Mounting

**Objective**: Verify bot mounts to assist the master in combat

**Detailed Steps**:
1. Position both master and bot dismounted
2. Command bot to stay: `/w <botname> stay`
3. Move master 35+ yards away from bot
4. Target a nearby mob and attack the mob
5. Command bot to assist: `/w <botname> follow`

**Expected Results**:
- Bot immediately mounts up (within 1-2 seconds cast time)
- Bot rides toward master while mounted
- Bot dismounts at ~18-25 yards from master/mob
- Bot engages in combat to assist

**Failure Indicators**:
- Bot runs dismounted (old behavior)
- Bot never mounts despite distance
- Bot mounts but doesn't dismount at proper assist range

## Test 2: Non-Combat Dismount Distance

**Objective**: Verify bot stays mounted longer during peaceful travel

**Detailed Steps**:
1. Both master and bot start dismounted
2. Mount up: use any mount
3. Verify bot also mounts: `/w <botname> follow`
4. Travel together for 10+ seconds to establish following
5. While moving, dismount master but continue running
6. Observe bot behavior as you move away

**Expected Results**:
- Bot stays mounted while master runs dismounted
- Bot only dismounts when within ~10 yards of master

## Test 3: Target Interference Prevention

**Objective**: Verify target selection doesn't prevent mounting when
master needs help

**Detailed Steps**:
1. Position bot 35+ yards from master: `/w <botname> stay` then move
away
2. Find a mob visible to both master and bot
3. Target the mob (do not attack): click on mob to select it
4. Verify bot can see the target: `/w <botname> attack` (bot should
respond about target)
5. Cancel bot attack: `/w <botname> follow`
6. Now attack the mob yourself (master enters combat)
7. Observe bot behavior immediately after master engages

**Expected Results**:
- Bot mounts up despite having the same target selected
- Bot rides toward combat area while mounted
- Bot dismounts at assist range (~18-25 yards)
- Target selection does not prevent proper mount behavior

**Failure Indicators**:
- Bot runs dismounted toward target (target interference)
- Bot doesn't mount because it's "focused on target"

## Test 4: Basic Mount Following

**Objective**: Verify fundamental mount matching still works

**Detailed Steps**:
1. Start both master and bot dismounted
2. Ensure bot is following: `/w <botname> follow`
3. Mount up on any available mount
4. Wait 2-3 seconds and observe bot
5. Test different mount speeds if available (60%, 100%, 280% speed)
6. Test dismounting and remounting

**Expected Results**:
- Bot mounts within 2-3 seconds of master mounting
- Bot uses appropriate mount speed to match master
- Bot dismounts when master dismounts (basic following)
- No regression in basic mount following behavior

## Test 5: Distance-Based Mounting Threshold

**Objective**: Verify bot mounts at efficient distance threshold (21+
yards)

**Detailed Steps**:
1. Both master and bot start dismounted in safe area (no mobs)
2. Command bot to stay: `/w <botname> stay`
3. Record starting position: `.gps`
4. Walk exactly 15 yards away (short distance)
5. Command bot to follow: `/w <botname> follow`
6. Observe: bot should run dismounted (distance too short)
7. Command bot to stay again: `/w <botname> stay`
8. Walk to 25+ yards away from bot
9. Record new position: `.gps`
10. Command bot to follow: `/w <botname> follow`

**Expected Results**:
- At 15 yards: Bot runs dismounted (inefficient to mount)
- At 25+ yards: Bot mounts up immediately (efficient distance)
- Distance threshold should be ~21 yards based on mount cast time
efficiency

**Distance Calculation**: Use coordinates from `.gps` to verify exact
distances

## Test 6: Druid Form Integration (Druid Master Required)

**Objective**: Verify druid form compatibility with mount system

**Detailed Steps**:
1. Master must be druid with travel form available
2. Start both dismounted: `/w <botname> follow`
3. Shift master to travel form
4. Observe bot response (should mount or shift if druid)
5. Travel together for 10+ seconds
6. Shift master to normal form while moving
7. Observe bot dismount behavior
8. If available, test flight form in appropriate zones

**Expected Results**:
- Druid bot: matches master's form (travel form)
- Non-druid bot: uses equivalent mount speed
- Form changes trigger appropriate bot responses
- Speed matching works between forms and mounts

## Test 7: Battleground Mount Behavior

**Objective**: Verify mount behavior works in PvP environments

**Detailed Steps**:
1. Queue for battleground with bot in party
2. Enter battleground together
3. Test basic mount following in BG
4. Test flag-carrying restrictions (WSG/EotS)
5. Test mounting during BG combat scenarios

**Expected Results**:
- Bot mounts appropriately in battlegrounds
- Flag carrying prevents mounting
- Combat assistance mounting still works in PvP
2025-11-16 22:49:12 +01:00
bashermens
610a032379 Bots fly/follow (movePoint core refactor), water walking fixes (#1825)
Closer the original solution, i dont wanna drift to much away without
really good reason. At this point i still see NPC and bots moving
through the levels or even falling out the levels here and there. I
verified whether the MovePoint signature changes and related params
itself in playerbots has anything todo with it, even when params are
hardcoded the behavior remains. It could be deeper problem, but for now
it seems core problem. Therefore i dont wanna change to much until the
dust has settled a bit in core itself.

I havent implemented moveTakeOff or moveLand which are basically
responsible for the transitions phases between ground and air visa
versa. I have version where i use takeOff for the animation, which means
moving vertical. For now i am gonna leave for what it is.

PS: also includes additional movement fix for AreaTriggerAction which we
missed first time after the core update on movements.

@Wishmaster117 Been testing and trying a lot in the background on find
solutions and causes. The general solutions remains removed some tweaks,
altered code here and there. With the general idea to keep closer to the
original code for now at least.

Testing:
- Class abilities: Slow fall (priest), Flight Form (druid) : Green
- BG: Green
- Swimming and walking shallow waters: Green
- Takeoff and land when following master: Green
- Boat and zeppelins: Green
- Flymount and ground walking: Green
- Water Walking (shaman), Path of Frost (DK): Green
- Water Walking (shaman), Path of Frost (DK) transisions; flying,
swimming, water walking: Green


Skipping pets when group water walking, path of frost, once core pathing
changes has settled more i will add it. Which is easy but i dont wanna
add more edge cases and code branches for now.
2025-11-16 22:39:46 +01:00
5 changed files with 275 additions and 187 deletions

View File

@@ -40,7 +40,14 @@ bool ReachAreaTriggerAction::Execute(Event event)
return true; return true;
} }
bot->GetMotionMaster()->MovePoint(at->map, at->x, at->y, at->z); bot->GetMotionMaster()->MovePoint(
/*id*/ at->map,
/*coords*/ at->x, at->y, at->z,
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
/*speed*/ 0.0f, // default speed (not handled here)
/*orientation*/ 0.0f, // keep current orientation of bot
/*generatePath*/ true, // true => terrain path (2d mmap); false => straight spline (3d vmap)
/*forceDestination*/ false);
float distance = bot->GetDistance(at->x, at->y, at->z); float distance = bot->GetDistance(at->x, at->y, at->z);
float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay; float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay;

View File

@@ -81,7 +81,7 @@ bool CheckMountStateAction::isUseful()
// to mostly be an issue in tunnels of WSG and AV) // to mostly be an issue in tunnels of WSG and AV)
float posZ = bot->GetPositionZ(); float posZ = bot->GetPositionZ();
float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ); float groundLevel = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), posZ);
if (!bot->IsMounted() && posZ < groundLevel) if (!bot->IsMounted() && !bot->HasWaterWalkAura() && posZ < groundLevel)
return false; return false;
// Not useful when bot does not have mount strat and is not currently mounted // Not useful when bot does not have mount strat and is not currently mounted
@@ -122,18 +122,21 @@ bool CheckMountStateAction::Execute(Event /*event*/)
bool shouldMount = false; bool shouldMount = false;
Unit* currentTarget = AI_VALUE(Unit*, "current target"); Unit* currentTarget = AI_VALUE(Unit*, "current target");
if (currentTarget) bool masterInCombat = master && master->IsInCombat();
if (currentTarget && (bot->IsInCombat() || masterInCombat))
{ {
// Use target-based logic if bot is in combat OR master is in combat and needs assistance
float dismountDistance = CalculateDismountDistance(); float dismountDistance = CalculateDismountDistance();
float mountDistance = CalculateMountDistance(); float mountDistance = CalculateMountDistance();
float combatReach = bot->GetCombatReach() + currentTarget->GetCombatReach();
float distanceToTarget = bot->GetExactDist(currentTarget); float distanceToTarget = bot->GetExactDist(currentTarget);
shouldDismount = (distanceToTarget <= dismountDistance + combatReach); shouldDismount = (distanceToTarget <= dismountDistance);
shouldMount = (distanceToTarget > mountDistance + combatReach); shouldMount = (distanceToTarget > mountDistance);
} }
else else
{ {
// If neither bot nor master is in combat, prioritize master-following
shouldMount = true; shouldMount = true;
} }
@@ -160,10 +163,19 @@ bool CheckMountStateAction::Execute(Event /*event*/)
else if (ShouldDismountForMaster(master) && bot->IsMounted()) else if (ShouldDismountForMaster(master) && bot->IsMounted())
{ {
// If master dismounted, stay mounted until close enough to assist
if (StayMountedToCloseDistance())
return false;
Dismount(); Dismount();
return true; return true;
} }
// Mount up to close distance to master if beneficial - allow mounting even if master is in combat
// as long as the bot itself is not in combat and has no attackers
else if (!bot->IsMounted() && noAttackers && !bot->IsInCombat() && ShouldMountToCloseDistance())
return Mount();
return false; return false;
} }
@@ -397,6 +409,50 @@ bool CheckMountStateAction::TryRandomMountFiltered(const std::map<int32, std::ve
return false; return false;
} }
bool CheckMountStateAction::StayMountedToCloseDistance() const
{
// Keep the bot mounted while closing distance to a recently dismounted master.
// Rationale: if the master dismounts far away, immediately dismounting slows the bot down
// and delays assistance. Instead, remain mounted until within reasonable proximity
// of the master, then dismount to help.
if (!master)
return false;
float distToMaster = sServerFacade->GetDistance2d(bot, master);
// If master is in combat, dismount at combat assist range to help immediately
if (master->IsInCombat())
{
float assistRange = CalculateDismountDistance();
return distToMaster > assistRange;
}
// If master is not in combat, use smaller proximity range for general following
float masterProximityRange = 10.0f; // Close enough to be near master but not attack range
return distToMaster > masterProximityRange;
}
bool CheckMountStateAction::ShouldMountToCloseDistance() const
{
// Mount up to close distance to master if beneficial
// Uses the same logic as CalculateMountDistance() which already considers the 2-second mount cast time
// This handles cases where master is in combat but bot isn't, and bot needs to mount to reach master
if (!master)
return false;
// Only mount to close distance when actively following
if (!botAI->HasStrategy("follow", BOT_STATE_NON_COMBAT))
return false;
float distToMaster = sServerFacade->GetDistance2d(bot, master);
float mountDistance = CalculateMountDistance();
// Mount if distance is greater than the calculated mount distance threshold
return distToMaster > mountDistance;
}
float CheckMountStateAction::CalculateDismountDistance() const float CheckMountStateAction::CalculateDismountDistance() const
{ {
// Warrior bots should dismount far enough to charge (because it's important for generating some initial rage), // Warrior bots should dismount far enough to charge (because it's important for generating some initial rage),

View File

@@ -60,6 +60,8 @@ private:
bool TryPreferredMount(Player* master) const; bool TryPreferredMount(Player* master) const;
uint32 GetMountType(Player* master) const; uint32 GetMountType(Player* master) const;
bool TryRandomMountFiltered(const std::map<int32, std::vector<uint32>>& spells, int32 masterSpeed) const; bool TryRandomMountFiltered(const std::map<int32, std::vector<uint32>>& spells, int32 masterSpeed) const;
bool StayMountedToCloseDistance() const;
bool ShouldMountToCloseDistance() const;
}; };
#endif #endif

View File

@@ -10,6 +10,7 @@
#include <iomanip> #include <iomanip>
#include <string> #include <string>
#include "Corpse.h"
#include "Event.h" #include "Event.h"
#include "FleeManager.h" #include "FleeManager.h"
#include "G3D/Vector3.h" #include "G3D/Vector3.h"
@@ -41,7 +42,6 @@
#include "Unit.h" #include "Unit.h"
#include "Vehicle.h" #include "Vehicle.h"
#include "WaypointMovementGenerator.h" #include "WaypointMovementGenerator.h"
#include "Corpse.h"
MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name)
{ {
@@ -192,14 +192,15 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
{ {
return false; return false;
} }
bool generatePath = !bot->IsFlying() && !bot->isSwimming(); bool generatePath = !bot->IsFlying() && !bot->isSwimming();
bool disableMoveSplinePath = sPlayerbotAIConfig->disableMoveSplinePath >= 2 || bool disableMoveSplinePath =
sPlayerbotAIConfig->disableMoveSplinePath >= 2 ||
(sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground()); (sPlayerbotAIConfig->disableMoveSplinePath == 1 && bot->InBattleground());
if (Vehicle* vehicle = bot->GetVehicle()) if (Vehicle* vehicle = bot->GetVehicle())
{ {
VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot); VehicleSeatEntry const* seat = vehicle->GetSeatForPassenger(bot);
Unit* vehicleBase = vehicle->GetBase(); Unit* vehicleBase = vehicle->GetBase();
// If the mover (vehicle) can fly, we DO NOT want an mmaps path (2D ground) => disable pathfinding
generatePath = !vehicleBase || !vehicleBase->CanFly(); generatePath = !vehicleBase || !vehicleBase->CanFly();
if (!vehicleBase || !seat || !seat->CanControl()) // is passenger and cant move anyway if (!vehicleBase || !seat || !seat->CanControl()) // is passenger and cant move anyway
return false; return false;
@@ -207,22 +208,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
float distance = vehicleBase->GetExactDist(x, y, z); // use vehicle distance, not bot float distance = vehicleBase->GetExactDist(x, y, z); // use vehicle distance, not bot
if (distance > 0.01f) if (distance > 0.01f)
{ {
MotionMaster& mm = *vehicleBase->GetMotionMaster(); // need to move vehicle, not bot DoMovePoint(vehicleBase, x, y, z, generatePath, backwards);
// Disable ground pathing if the bot/master/vehicle are flying
auto isFlying = [](Unit* u){ return u && (u->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) || u->IsInFlight()); };
bool allowPathVeh = generatePath;
Unit* masterVeh = botAI ? botAI->GetMaster() : nullptr;
if (isFlying(vehicleBase) || isFlying(bot) || isFlying(masterVeh))
allowPathVeh = false;
mm.Clear();
if (!backwards)
{
mm.MovePoint(0, x, y, z, FORCED_MOVEMENT_NONE, 0.0f, 0.0f, allowPathVeh);
}
else
{
mm.MovePointBackwards(0, x, y, z, allowPathVeh);
}
float speed = backwards ? vehicleBase->GetSpeed(MOVE_RUN_BACK) : vehicleBase->GetSpeed(MOVE_RUN); float speed = backwards ? vehicleBase->GetSpeed(MOVE_RUN_BACK) : vehicleBase->GetSpeed(MOVE_RUN);
float delay = 1000.0f * (distance / speed); float delay = 1000.0f * (distance / speed);
if (lessDelay) if (lessDelay)
@@ -248,23 +234,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bot->CastStop(); // bot->CastStop();
// botAI->InterruptSpell(); // botAI->InterruptSpell();
// } // }
DoMovePoint(bot, x, y, z, generatePath, backwards);
MotionMaster& mm = *bot->GetMotionMaster();
// No ground pathfinding if the bot/master are flying => allow true 3D (Z) movement
auto isFlying = [](Unit* u){ return u && (u->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) || u->IsInFlight()); };
bool allowPath = generatePath;
Unit* master = botAI ? botAI->GetMaster() : nullptr;
if (isFlying(bot) || isFlying(master))
allowPath = false;
mm.Clear();
if (!backwards)
{
mm.MovePoint(0, x, y, z, FORCED_MOVEMENT_NONE, 0.0f, 0.0f, allowPath);
}
else
{
mm.MovePointBackwards(0, x, y, z, allowPath);
}
float delay = 1000.0f * MoveDelay(distance, backwards); float delay = 1000.0f * MoveDelay(distance, backwards);
if (lessDelay) if (lessDelay)
{ {
@@ -282,9 +252,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
Movement::PointsArray path = Movement::PointsArray path =
SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig->maxMovementSearchTime, normal_only); SearchForBestPath(x, y, z, modifiedZ, sPlayerbotAIConfig->maxMovementSearchTime, normal_only);
if (modifiedZ == INVALID_HEIGHT) if (modifiedZ == INVALID_HEIGHT)
{
return false; return false;
}
float distance = bot->GetExactDist(x, y, modifiedZ); float distance = bot->GetExactDist(x, y, modifiedZ);
if (distance > 0.01f) if (distance > 0.01f)
{ {
@@ -296,24 +264,8 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bot->CastStop(); // bot->CastStop();
// botAI->InterruptSpell(); // botAI->InterruptSpell();
// } // }
MotionMaster& mm = *bot->GetMotionMaster();
G3D::Vector3 endP = path.back(); G3D::Vector3 endP = path.back();
// No ground pathfinding if the bot/master are flying => allow true 3D (Z) movement DoMovePoint(bot, x, y, z, generatePath, backwards);
auto isFlying = [](Unit* u){ return u && (u->HasUnitMovementFlag(MOVEMENTFLAG_FLYING) || u->IsInFlight()); };
bool allowPath = generatePath;
Unit* master = botAI ? botAI->GetMaster() : nullptr;
if (isFlying(bot) || isFlying(master))
allowPath = false;
mm.Clear();
if (!backwards)
{
mm.MovePoint(0, x, y, z, FORCED_MOVEMENT_NONE, 0.0f, 0.0f, allowPath);
}
else
{
mm.MovePointBackwards(0, x, y, z, allowPath);
}
float delay = 1000.0f * MoveDelay(distance, backwards); float delay = 1000.0f * MoveDelay(distance, backwards);
if (lessDelay) if (lessDelay)
{ {
@@ -581,9 +533,7 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
// bool goTaxi = bot->ActivateTaxiPathTo({ tEntry->from, tEntry->to }, unit, 1); // bool goTaxi = bot->ActivateTaxiPathTo({ tEntry->from, tEntry->to }, unit, 1);
// if (botAI->HasCheat(BotCheatMask::gold)) // if (botAI->HasCheat(BotCheatMask::gold))
// {
// bot->SetMoney(botMoney); // bot->SetMoney(botMoney);
// }
// LOG_DEBUG("playerbots", "goTaxi"); // LOG_DEBUG("playerbots", "goTaxi");
// return goTaxi; // return goTaxi;
// } // }
@@ -882,8 +832,8 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
float predictDis = std::min(3.0f, target->GetObjectSize() * 2); float predictDis = std::min(3.0f, target->GetObjectSize() * 2);
tx += cos(target->GetOrientation()) * predictDis; tx += cos(target->GetOrientation()) * predictDis;
ty += sin(target->GetOrientation()) * predictDis; ty += sin(target->GetOrientation()) * predictDis;
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
tx, ty, tz)) target->GetPositionZ(), tx, ty, tz))
{ {
tx = target->GetPositionX(); tx = target->GetPositionX();
ty = target->GetPositionY(); ty = target->GetPositionY();
@@ -1001,9 +951,8 @@ bool MovementAction::IsMovingAllowed()
return false; return false;
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->IsBeingTeleported() || bot->HasRootAura() || bot->HasSpiritOfRedemptionAura() || bot->HasConfuseAura() ||
bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() || bot->IsCharmed() || bot->HasStunAura() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false; return false;
if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE) if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE)
@@ -1022,63 +971,58 @@ bool MovementAction::Follow(Unit* target, float distance) { return Follow(target
void MovementAction::UpdateMovementState() void MovementAction::UpdateMovementState()
{ {
int8 botInLiquidState = bot->GetLiquidData().Status; // state flags
const float gLvlZ = bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ());
const bool onGround = bot->GetPositionZ() < gLvlZ + 1.f;
const bool wantsToFly = bot->HasIncreaseMountedFlightSpeedAura() || bot->HasFlyAura();
const auto master = botAI ? botAI->GetMaster() : nullptr; // real or not
const bool masterIsFlying = master && master->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
const bool isFlying = bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING);
const auto liquidState = bot->GetLiquidData().Status; // default LIQUID_MAP_NO_WATER
const bool isWaterArea = liquidState != LIQUID_MAP_NO_WATER;
const bool isUnderWater = liquidState == LIQUID_MAP_UNDER_WATER;
const bool isInWater = liquidState == LIQUID_MAP_IN_WATER;
const bool isWaterWalking = bot->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
const bool isSwimming = bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
const bool wantsToWaterWalk = bot->HasWaterWalkAura();
const bool wantsToSwim = isInWater || isUnderWater;
if (botInLiquidState == LIQUID_MAP_IN_WATER || botInLiquidState == LIQUID_MAP_UNDER_WATER) // handle water state
if (isWaterArea)
{ {
bot->SetSwim(true); // water walking
if (wantsToWaterWalk && !isWaterWalking && !isUnderWater && !isFlying)
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
bot->AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
bot->SendMovementFlagUpdate();
}
// swimming
else if (wantsToSwim && !isSwimming && !wantsToWaterWalk && !isFlying)
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
bot->AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
bot->SendMovementFlagUpdate();
}
} }
else else
{ {
bot->SetSwim(false); // reset flags, if not will inherit incorrect walk speed here and there
} // when transistions between land and water.
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
bool onGround = bot->GetPositionZ() < bot->RemoveUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
bot->GetMapWaterOrGroundLevel(bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ()) + 1.0f;
// Keep bot->SendMovementFlagUpdate() withing the if statements to not intefere with bot behavior on ground/(shallow) waters
bool hasFlightAura = bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) || bot->HasAuraType(SPELL_AURA_FLY);
if (hasFlightAura)
{
bool changed = false;
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY))
{
bot->AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
changed = true;
}
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
{
bot->AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
changed = true;
}
if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING))
{
bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
changed = true;
}
if (changed)
bot->SendMovementFlagUpdate(); bot->SendMovementFlagUpdate();
} }
else if (!hasFlightAura)
// handle flying state
if (wantsToFly && !isFlying && masterIsFlying)
{ {
bool changed = false; bot->AddUnitMovementFlag(MOVEMENTFLAG_FLYING);
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FLYING)) bot->SendMovementFlagUpdate();
}
else if ((!wantsToFly || onGround) && isFlying)
{ {
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING); bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FLYING);
changed = true;
}
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY))
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
changed = true;
}
if (bot->HasUnitMovementFlag(MOVEMENTFLAG_CAN_FLY))
{
bot->RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
changed = true;
}
if (changed)
bot->SendMovementFlagUpdate(); bot->SendMovementFlagUpdate();
} }
@@ -1180,6 +1124,13 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
if (!target) if (!target)
return false; return false;
if (!bot->InBattleground() && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
sPlayerbotAIConfig->followDistance))
{
// botAI->TellError("No need to follow");
return false;
}
/* /*
if (!bot->InBattleground() if (!bot->InBattleground()
&& sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target->GetPositionX(), && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target->GetPositionX(),
@@ -1297,17 +1248,21 @@ bool MovementAction::Follow(Unit* target, float distance, float angle)
return MoveTo(target, sPlayerbotAIConfig->followDistance); return MoveTo(target, sPlayerbotAIConfig->followDistance);
} }
if (sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
sPlayerbotAIConfig->followDistance))
{
// botAI->TellError("No need to follow");
return false;
}
if (target->IsFriendlyTo(bot) && bot->IsMounted() && AI_VALUE(GuidVector, "all targets").empty()) if (target->IsFriendlyTo(bot) && bot->IsMounted() && AI_VALUE(GuidVector, "all targets").empty())
distance += angle; distance += angle;
// Do not cancel follow if the 2D distance is short but the Z still differs (e.g., master above). if (!bot->InBattleground() && sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target),
float dz1 = fabs(bot->GetPositionZ() - target->GetPositionZ()); sPlayerbotAIConfig->followDistance))
if (!bot->InBattleground()
&& sServerFacade->IsDistanceLessOrEqualThan(sServerFacade->GetDistance2d(bot, target), sPlayerbotAIConfig->followDistance)
&& dz1 < sPlayerbotAIConfig->contactDistance)
{ {
// botAI->TellError("No need to follow"); // botAI->TellError("No need to follow");
return false; // truly in range (2D and Z) => no need to move return false;
} }
bot->HandleEmoteCommand(0); bot->HandleEmoteCommand(0);
@@ -1388,7 +1343,7 @@ float MovementAction::MoveDelay(float distance, bool backwards)
} }
else else
{ {
speed = backwards ? bot->GetSpeed(MOVE_RUN_BACK) :bot->GetSpeed(MOVE_RUN); speed = backwards ? bot->GetSpeed(MOVE_RUN_BACK) : bot->GetSpeed(MOVE_RUN);
} }
float delay = distance / speed; float delay = distance / speed;
return delay; return delay;
@@ -1418,8 +1373,7 @@ void MovementAction::SetNextMovementDelay(float delayMillis)
{ {
AI_VALUE(LastMovement&, "last movement") AI_VALUE(LastMovement&, "last movement")
.Set(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(), .Set(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetOrientation(),
delayMillis, delayMillis, MovementPriority::MOVEMENT_FORCED);
MovementPriority::MOVEMENT_FORCED);
} }
bool MovementAction::Flee(Unit* target) bool MovementAction::Flee(Unit* target)
@@ -1633,7 +1587,8 @@ bool MovementAction::MoveAway(Unit* target, float distance, bool backwards)
dz = bot->GetPositionZ(); dz = bot->GetPositionZ();
exact = false; exact = false;
} }
if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false, backwards)) if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false,
backwards))
{ {
return true; return true;
} }
@@ -1655,7 +1610,8 @@ bool MovementAction::MoveAway(Unit* target, float distance, bool backwards)
dz = bot->GetPositionZ(); dz = bot->GetPositionZ();
exact = false; exact = false;
} }
if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false, backwards)) if (MoveTo(target->GetMapId(), dx, dy, dz, false, false, true, exact, MovementPriority::MOVEMENT_COMBAT, false,
backwards))
{ {
return true; return true;
} }
@@ -1704,7 +1660,7 @@ bool MovementAction::Move(float angle, float distance)
float x = bot->GetPositionX() + cos(angle) * distance; float x = bot->GetPositionX() + cos(angle) * distance;
float y = bot->GetPositionY() + sin(angle) * distance; float y = bot->GetPositionY() + sin(angle) * distance;
//TODO do we need GetMapWaterOrGroundLevel() if we're using CheckCollisionAndGetValidCoords() ? // TODO do we need GetMapWaterOrGroundLevel() if we're using CheckCollisionAndGetValidCoords() ?
float z = bot->GetMapWaterOrGroundLevel(x, y, bot->GetPositionZ()); float z = bot->GetMapWaterOrGroundLevel(x, y, bot->GetPositionZ());
if (z == -100000.0f || z == -200000.0f) if (z == -100000.0f || z == -200000.0f)
z = bot->GetPositionZ(); z = bot->GetPositionZ();
@@ -1758,11 +1714,9 @@ bool MovementAction::MoveInside(uint32 mapId, float x, float y, float z, float d
// min_length = gen.getPathLength(); // min_length = gen.getPathLength();
// current_z = modified_z; // current_z = modified_z;
// if (abs(current_z - z) < 0.5f) // if (abs(current_z - z) < 0.5f)
// {
// return current_z; // return current_z;
// } // }
// } // }
// }
// for (delta = range / 2 + step; delta <= range; delta += 2) { // for (delta = range / 2 + step; delta <= range; delta += 2) {
// modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta); // modified_z = bot->GetMapWaterOrGroundLevel(x, y, z + delta);
// PathGenerator gen(bot); // PathGenerator gen(bot);
@@ -1857,6 +1811,59 @@ const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y,
return result; return result;
} }
void MovementAction::DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards)
{
if (!unit)
return;
MotionMaster* mm = unit->GetMotionMaster();
if (!mm)
return;
// enable flying
if (unit->HasUnitMovementFlag(MOVEMENTFLAG_FLYING))
{
unit->AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
unit->AddUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
}
else
{
unit->RemoveUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
unit->RemoveUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
}
// enable water walking
if (unit->HasUnitMovementFlag(MOVEMENTFLAG_WATERWALKING))
{
float gLvlZ = unit->GetMapWaterOrGroundLevel(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ());
unit->UpdatePosition(unit->GetPositionX(), unit->GetPositionY(), gLvlZ, false);
// z = gLvlZ; do not overwrite Z axex, otherwise you wont be able to steer the bots into swimming when water
// walking.
}
mm->Clear();
if (backwards)
{
mm->MovePointBackwards(
/*id*/ 0,
/*coords*/ x, y, z,
/*generatePath*/ generatePath,
/*forceDestination*/ false);
return;
}
else
{
mm->MovePoint(
/*id*/ 0,
/*coords*/ x, y, z,
/*forcedMovement*/ FORCED_MOVEMENT_NONE,
/*speed*/ 0.f,
/*orientation*/ 0.f,
/*generatePath*/ generatePath, // true => terrain path (2d mmap); false => straight spline (3d vmap)
/*forceDestination*/ false);
}
}
bool FleeAction::Execute(Event event) bool FleeAction::Execute(Event event)
{ {
return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true); return MoveAway(AI_VALUE(Unit*, "current target"), sPlayerbotAIConfig->fleeDistance, true);
@@ -1937,7 +1944,8 @@ bool AvoidAoeAction::AvoidAuraWithDynamicObj()
{ {
return false; return false;
} }
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellInfo->Id) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end()) if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellInfo->Id) !=
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
return false; return false;
DynamicObject* dynOwner = aura->GetDynobjOwner(); DynamicObject* dynOwner = aura->GetDynobjOwner();
@@ -2002,7 +2010,8 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage()
continue; continue;
} }
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellId) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end()) if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(spellId) !=
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
continue; continue;
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId); const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
@@ -2028,7 +2037,8 @@ bool AvoidAoeAction::AvoidGameObjectWithDamage()
lastTellTimer = time(NULL); lastTellTimer = time(NULL);
lastMoveTimer = getMSTime(); lastMoveTimer = getMSTime();
std::ostringstream out; std::ostringstream out;
out << "I'm avoiding " << name.str() << " (" << spellInfo->Id << ")" << " Radius " << radius << " - [Trap]"; out << "I'm avoiding " << name.str() << " (" << spellInfo->Id << ")" << " Radius " << radius
<< " - [Trap]";
bot->Say(out.str(), LANG_UNIVERSAL); bot->Say(out.str(), LANG_UNIVERSAL);
} }
return true; return true;
@@ -2071,7 +2081,8 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
sSpellMgr->GetSpellInfo(spellInfo->Effects[aurEff->GetEffIndex()].TriggerSpell); sSpellMgr->GetSpellInfo(spellInfo->Effects[aurEff->GetEffIndex()].TriggerSpell);
if (!triggerSpellInfo) if (!triggerSpellInfo)
continue; continue;
if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(triggerSpellInfo->Id) != sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end()) if (sPlayerbotAIConfig->aoeAvoidSpellWhitelist.find(triggerSpellInfo->Id) !=
sPlayerbotAIConfig->aoeAvoidSpellWhitelist.end())
return false; return false;
for (int j = 0; j < MAX_SPELL_EFFECTS; j++) for (int j = 0; j < MAX_SPELL_EFFECTS; j++)
{ {
@@ -2093,7 +2104,8 @@ bool AvoidAoeAction::AvoidUnitWithDamageAura()
lastTellTimer = time(NULL); lastTellTimer = time(NULL);
lastMoveTimer = getMSTime(); lastMoveTimer = getMSTime();
std::ostringstream out; std::ostringstream out;
out << "I'm avoiding " << name.str() << " (" << triggerSpellInfo->Id << ")" << " Radius " << radius << " - [Unit Trigger]"; out << "I'm avoiding " << name.str() << " (" << triggerSpellInfo->Id << ")"
<< " Radius " << radius << " - [Unit Trigger]";
bot->Say(out.str(), LANG_UNIVERSAL); bot->Say(out.str(), LANG_UNIVERSAL);
} }
} }
@@ -2112,7 +2124,8 @@ Position MovementAction::BestPositionForMeleeToFlee(Position pos, float radius)
if (currentTarget) if (currentTarget)
{ {
// Normally, move to left or right is the best position // Normally, move to left or right is the best position
bool isTanking = (!currentTarget->isFrozen() && !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot); bool isTanking = (!currentTarget->isFrozen()
&& !currentTarget->HasRootAura()) && (currentTarget->GetVictim() == bot);
float angle = bot->GetAngle(currentTarget); float angle = bot->GetAngle(currentTarget);
float angleLeft = angle + (float)M_PI / 2; float angleLeft = angle + (float)M_PI / 2;
float angleRight = angle - (float)M_PI / 2; float angleRight = angle - (float)M_PI / 2;
@@ -2327,8 +2340,7 @@ bool CombatFormationMoveAction::isUseful()
bool CombatFormationMoveAction::Execute(Event event) bool CombatFormationMoveAction::Execute(Event event)
{ {
float dis = AI_VALUE(float, "disperse distance"); float dis = AI_VALUE(float, "disperse distance");
if (dis <= 0.0f || if (dis <= 0.0f || (!bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_NON_COMBAT)) ||
(!bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_NON_COMBAT)) ||
(bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_COMBAT))) (bot->IsInCombat() && botAI->HasStrategy("stay", BotState::BOT_STATE_COMBAT)))
return false; return false;
Player* playerToLeave = NearestGroupMember(dis); Player* playerToLeave = NearestGroupMember(dis);
@@ -2489,12 +2501,13 @@ bool TankFaceAction::Execute(Event event)
float goodAngle2 = Position::NormalizeOrientation(averageAngle - M_PI * 3 / 5); float goodAngle2 = Position::NormalizeOrientation(averageAngle - M_PI * 3 / 5);
// if dist < bot->GetMeleeRange(target) / 2, target will move backward // if dist < bot->GetMeleeRange(target) / 2, target will move backward
float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() - target->GetCombatReach(); float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() -
target->GetCombatReach();
std::vector<Position> availablePos; std::vector<Position> availablePos;
float x, y, z; float x, y, z;
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1); target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
x, y, z)) bot->GetPositionZ(), x, y, z))
{ {
/// @todo: movement control now is a mess, prepare to rewrite /// @todo: movement control now is a mess, prepare to rewrite
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info"); std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
@@ -2506,8 +2519,8 @@ bool TankFaceAction::Execute(Event event)
} }
} }
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2); target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
x, y, z)) bot->GetPositionZ(), x, y, z))
{ {
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info"); std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
Position pos(x, y, z); Position pos(x, y, z);
@@ -2520,13 +2533,15 @@ bool TankFaceAction::Execute(Event event)
if (availablePos.empty()) if (availablePos.empty())
return false; return false;
Position nearest = GetNearestPosition(availablePos); Position nearest = GetNearestPosition(availablePos);
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false,
false, true, MovementPriority::MOVEMENT_COMBAT);
} }
bool RearFlankAction::isUseful() bool RearFlankAction::isUseful()
{ {
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
if (!target) { return false; } if (!target)
return false;
// Need to double the front angle check to account for mirrored angle. // Need to double the front angle check to account for mirrored angle.
bool inFront = target->HasInArc(2.f * minAngle, bot); bool inFront = target->HasInArc(2.f * minAngle, bot);
@@ -2540,7 +2555,8 @@ bool RearFlankAction::isUseful()
bool RearFlankAction::Execute(Event event) bool RearFlankAction::Execute(Event event)
{ {
Unit* target = AI_VALUE(Unit*, "current target"); Unit* target = AI_VALUE(Unit*, "current target");
if (!target) { return false; } if (!target)
return false;
float angle = frand(minAngle, maxAngle); float angle = frand(minAngle, maxAngle);
float baseDistance = bot->GetMeleeRange(target) * 0.5f; float baseDistance = bot->GetMeleeRange(target) * 0.5f;
@@ -2559,8 +2575,8 @@ bool RearFlankAction::Execute(Event event)
destination = &rightFlank; destination = &rightFlank;
} }
return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(), destination->GetPositionZ(), return MoveTo(bot->GetMapId(), destination->GetPositionX(), destination->GetPositionY(),
false, false, false, true, MovementPriority::MOVEMENT_COMBAT); destination->GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT);
} }
bool DisperseSetAction::Execute(Event event) bool DisperseSetAction::Execute(Event event)
@@ -2688,9 +2704,8 @@ bool SetFacingTargetAction::isUseful() { return !AI_VALUE2(bool, "facing", "curr
bool SetFacingTargetAction::isPossible() bool SetFacingTargetAction::isPossible()
{ {
if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) || if (bot->isFrozen() || bot->IsPolymorphed() || (bot->isDead() && !bot->HasPlayerFlag(PLAYER_FLAGS_GHOST)) ||
bot->IsBeingTeleported() || bot->HasConfuseAura() || bot->IsCharmed() || bot->IsBeingTeleported() || bot->HasConfuseAura() || bot->IsCharmed() || bot->HasStunAura() ||
bot->HasStunAura() || bot->IsInFlight() || bot->IsInFlight() || bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
bot->HasUnitState(UNIT_STATE_LOST_CONTROL))
return false; return false;
return true; return true;
@@ -2720,12 +2735,13 @@ bool SetBehindTargetAction::Execute(Event event)
float goodAngle1 = Position::NormalizeOrientation(target->GetOrientation() + M_PI * 3 / 5); float goodAngle1 = Position::NormalizeOrientation(target->GetOrientation() + M_PI * 3 / 5);
float goodAngle2 = Position::NormalizeOrientation(target->GetOrientation() - M_PI * 3 / 5); float goodAngle2 = Position::NormalizeOrientation(target->GetOrientation() - M_PI * 3 / 5);
float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() - target->GetCombatReach(); float dist = std::max(bot->GetExactDist(target), bot->GetMeleeRange(target) / 2) - bot->GetCombatReach() -
target->GetCombatReach();
std::vector<Position> availablePos; std::vector<Position> availablePos;
float x, y, z; float x, y, z;
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1); target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle1);
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
x, y, z)) bot->GetPositionZ(), x, y, z))
{ {
/// @todo: movement control now is a mess, prepare to rewrite /// @todo: movement control now is a mess, prepare to rewrite
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info"); std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
@@ -2737,8 +2753,8 @@ bool SetBehindTargetAction::Execute(Event event)
} }
} }
target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2); target->GetNearPoint(bot, x, y, z, 0.0f, dist, goodAngle2);
if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), if (bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
x, y, z)) bot->GetPositionZ(), x, y, z))
{ {
std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info"); std::list<FleeInfo>& infoList = AI_VALUE(std::list<FleeInfo>&, "recently flee info");
Position pos(x, y, z); Position pos(x, y, z);
@@ -2751,7 +2767,8 @@ bool SetBehindTargetAction::Execute(Event event)
if (availablePos.empty()) if (availablePos.empty())
return false; return false;
Position nearest = GetNearestPosition(availablePos); Position nearest = GetNearestPosition(availablePos);
return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false, false, true, MovementPriority::MOVEMENT_COMBAT); return MoveTo(bot->GetMapId(), nearest.GetPositionX(), nearest.GetPositionY(), nearest.GetPositionZ(), false, false,
false, true, MovementPriority::MOVEMENT_COMBAT);
} }
bool MoveOutOfCollisionAction::Execute(Event event) bool MoveOutOfCollisionAction::Execute(Event event)
@@ -2905,10 +2922,7 @@ bool MoveAwayFromCreatureAction::Execute(Event event)
return false; return false;
} }
bool MoveAwayFromCreatureAction::isPossible() bool MoveAwayFromCreatureAction::isPossible() { return bot->CanFreeMove(); }
{
return bot->CanFreeMove();
}
bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event) bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event)
{ {
@@ -2995,7 +3009,4 @@ bool MoveAwayFromPlayerWithDebuffAction::Execute(Event event)
return false; return false;
} }
bool MoveAwayFromPlayerWithDebuffAction::isPossible() bool MoveAwayFromPlayerWithDebuffAction::isPossible() { return bot->CanFreeMove(); }
{
return bot->CanFreeMove();
}

View File

@@ -29,12 +29,17 @@ public:
protected: protected:
bool JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); bool JumpTo(uint32 mapId, float x, float y, float z, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->contactDistance,
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool MoveToLOS(WorldObject* target, bool ranged = false); bool MoveToLOS(WorldObject* target, bool ranged = false);
bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false, bool MoveTo(uint32 mapId, float x, float y, float z, bool idle = false, bool react = false,
bool normal_only = false, bool exact_waypoint = false, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, bool lessDelay = false, bool backwards = false); bool normal_only = false, bool exact_waypoint = false,
bool MoveTo(WorldObject* target, float distance = 0.0f, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); MovementPriority priority = MovementPriority::MOVEMENT_NORMAL, bool lessDelay = false,
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); bool backwards = false);
bool MoveTo(WorldObject* target, float distance = 0.0f,
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance,
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
float GetFollowAngle(); float GetFollowAngle();
bool Follow(Unit* target, float distance = sPlayerbotAIConfig->followDistance); bool Follow(Unit* target, float distance = sPlayerbotAIConfig->followDistance);
bool Follow(Unit* target, float distance, float angle); bool Follow(Unit* target, float distance, float angle);
@@ -51,10 +56,11 @@ protected:
bool Flee(Unit* target); bool Flee(Unit* target);
void ClearIdleState(); void ClearIdleState();
void UpdateMovementState(); void UpdateMovementState();
bool MoveAway(Unit* target, float distance = sPlayerbotAIConfig -> fleeDistance, bool backwards = false); bool MoveAway(Unit* target, float distance = sPlayerbotAIConfig->fleeDistance, bool backwards = false);
bool MoveFromGroup(float distance); bool MoveFromGroup(float distance);
bool Move(float angle, float distance); bool Move(float angle, float distance);
bool MoveInside(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->followDistance, MovementPriority priority = MovementPriority::MOVEMENT_NORMAL); bool MoveInside(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->followDistance,
MovementPriority priority = MovementPriority::MOVEMENT_NORMAL);
void CreateWp(Player* wpOwner, float x, float y, float z, float o, uint32 entry, bool important = false); void CreateWp(Player* wpOwner, float x, float y, float z, float o, uint32 entry, bool important = false);
Position BestPositionForMeleeToFlee(Position pos, float radius); Position BestPositionForMeleeToFlee(Position pos, float radius);
Position BestPositionForRangedToFlee(Position pos, float radius); Position BestPositionForRangedToFlee(Position pos, float radius);
@@ -74,6 +80,7 @@ private:
const Movement::PointsArray SearchForBestPath(float x, float y, float z, float& modified_z, int maxSearchCount = 5, const Movement::PointsArray SearchForBestPath(float x, float y, float z, float& modified_z, int maxSearchCount = 5,
bool normal_only = false, float step = 8.0f); bool normal_only = false, float step = 8.0f);
bool wasMovementRestricted = false; bool wasMovementRestricted = false;
void DoMovePoint(Unit* unit, float x, float y, float z, bool generatePath, bool backwards);
}; };
class FleeAction : public MovementAction class FleeAction : public MovementAction
@@ -149,11 +156,12 @@ public:
class RearFlankAction : public MovementAction class RearFlankAction : public MovementAction
{ {
// 90 degree minimum angle prevents any frontal cleaves/breaths and avoids parry-hasting the boss. // 90 degree minimum angle prevents any frontal cleaves/breaths and avoids parry-hasting the boss.
// 120 degree maximum angle leaves a 120 degree symmetrical cone at the tail end which is usually enough to avoid tail swipes. // 120 degree maximum angle leaves a 120 degree symmetrical cone at the tail end which is usually enough to avoid
// Some dragons or mobs may have different danger zone angles, override if needed. // tail swipes. Some dragons or mobs may have different danger zone angles, override if needed.
public: public:
RearFlankAction(PlayerbotAI* botAI, float distance = 0.0f, float minAngle = ANGLE_90_DEG, float maxAngle = ANGLE_120_DEG) RearFlankAction(PlayerbotAI* botAI, float distance = 0.0f, float minAngle = ANGLE_90_DEG,
float maxAngle = ANGLE_120_DEG)
: MovementAction(botAI, "rear flank") : MovementAction(botAI, "rear flank")
{ {
this->distance = distance; this->distance = distance;
@@ -297,7 +305,9 @@ class MoveAwayFromCreatureAction : public MovementAction
{ {
public: public:
MoveAwayFromCreatureAction(PlayerbotAI* botAI, std::string name, uint32 creatureId, float range, bool alive = true) MoveAwayFromCreatureAction(PlayerbotAI* botAI, std::string name, uint32 creatureId, float range, bool alive = true)
: MovementAction(botAI, name), creatureId(creatureId), range(range), alive(alive) {} : MovementAction(botAI, name), creatureId(creatureId), range(range), alive(alive)
{
}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isPossible() override; bool isPossible() override;
@@ -312,7 +322,9 @@ class MoveAwayFromPlayerWithDebuffAction : public MovementAction
{ {
public: public:
MoveAwayFromPlayerWithDebuffAction(PlayerbotAI* botAI, std::string name, uint32 spellId, float range) MoveAwayFromPlayerWithDebuffAction(PlayerbotAI* botAI, std::string name, uint32 spellId, float range)
: MovementAction(botAI, name), spellId(spellId), range(range) {} : MovementAction(botAI, name), spellId(spellId), range(range)
{
}
bool Execute(Event event) override; bool Execute(Event event) override;
bool isPossible() override; bool isPossible() override;