mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Merge branch 'liyunfan1223:master' into master
This commit is contained in:
6
.github/workflows/macos_build.yml
vendored
6
.github/workflows/macos_build.yml
vendored
@@ -20,17 +20,17 @@ jobs:
|
|||||||
name: ${{ matrix.os }}
|
name: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout AzerothCore
|
- name: Checkout AzerothCore
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: 'liyunfan1223/azerothcore-wotlk'
|
repository: 'liyunfan1223/azerothcore-wotlk'
|
||||||
ref: 'Playerbot'
|
ref: 'Playerbot'
|
||||||
- name: Checkout Playerbot Module
|
- name: Checkout Playerbot Module
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: 'liyunfan1223/mod-playerbots'
|
repository: 'liyunfan1223/mod-playerbots'
|
||||||
path: 'modules/mod-playerbots'
|
path: 'modules/mod-playerbots'
|
||||||
- name: Cache
|
- name: Cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/Library/Caches/ccache
|
path: ~/Library/Caches/ccache
|
||||||
key: ccache:${{ matrix.os }}:${{ github.ref }}:${{ github.sha }}
|
key: ccache:${{ matrix.os }}:${{ github.ref }}:${{ github.sha }}
|
||||||
|
|||||||
@@ -697,6 +697,9 @@ AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392
|
|||||||
# PvP Restricted Areas (bots don't pvp)
|
# PvP Restricted Areas (bots don't pvp)
|
||||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392"
|
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392"
|
||||||
|
|
||||||
|
# Improve react speed in battleground and arena (may cause lag)
|
||||||
|
AiPlayerbot.FastReactInBG = 1
|
||||||
|
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "ObjectGuid.h"
|
#include "ObjectGuid.h"
|
||||||
#include "PerformanceMonitor.h"
|
#include "PerformanceMonitor.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
|
#include "PlayerbotAIConfig.h"
|
||||||
#include "PlayerbotDbStore.h"
|
#include "PlayerbotDbStore.h"
|
||||||
#include "PlayerbotMgr.h"
|
#include "PlayerbotMgr.h"
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
@@ -342,10 +343,11 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
|
|||||||
UpdateAIInternal(elapsed, min);
|
UpdateAIInternal(elapsed, min);
|
||||||
inCombat = bot->IsInCombat();
|
inCombat = bot->IsInCombat();
|
||||||
// test fix lags because of BG
|
// test fix lags because of BG
|
||||||
|
bool inBG = bot->InBattleground() || bot->InArena();
|
||||||
if (bot && !inCombat)
|
if (bot && !inCombat)
|
||||||
min = true;
|
min = true;
|
||||||
|
|
||||||
if (HasRealPlayerMaster())
|
if (HasRealPlayerMaster() || (sPlayerbotAIConfig->fastReactInBG && inBG))
|
||||||
min = false;
|
min = false;
|
||||||
|
|
||||||
YieldThread(min);
|
YieldThread(min);
|
||||||
@@ -1629,7 +1631,7 @@ bool PlayerbotAI::IsCaster(Player* player) { return IsRanged(player) && player->
|
|||||||
|
|
||||||
bool PlayerbotAI::IsCombo(Player* player)
|
bool PlayerbotAI::IsCombo(Player* player)
|
||||||
{
|
{
|
||||||
int tab = AiFactory::GetPlayerSpecTab(player);
|
// int tab = AiFactory::GetPlayerSpecTab(player);
|
||||||
return player->getClass() == CLASS_ROGUE ||
|
return player->getClass() == CLASS_ROGUE ||
|
||||||
(player->getClass() == CLASS_DRUID && player->HasAura(768)); // cat druid
|
(player->getClass() == CLASS_DRUID && player->HasAura(768)); // cat druid
|
||||||
}
|
}
|
||||||
@@ -3020,8 +3022,8 @@ bool PlayerbotAI::CastSpell(uint32 spellId, Unit* target, Item* itemTarget)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
aiObjectContext->GetValue<LastMovement&>("last movement")->Get().Set(nullptr);
|
// aiObjectContext->GetValue<LastMovement&>("last movement")->Get().Set(nullptr);
|
||||||
aiObjectContext->GetValue<time_t>("stay time")->Set(0);
|
// aiObjectContext->GetValue<time_t>("stay time")->Set(0);
|
||||||
|
|
||||||
if (bot->IsFlying() || bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
if (bot->IsFlying() || bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
|
||||||
{
|
{
|
||||||
@@ -3206,8 +3208,8 @@ bool PlayerbotAI::CastSpell(uint32 spellId, float x, float y, float z, Item* ite
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
aiObjectContext->GetValue<LastMovement&>("last movement")->Get().Set(nullptr);
|
// aiObjectContext->GetValue<LastMovement&>("last movement")->Get().Set(nullptr);
|
||||||
aiObjectContext->GetValue<time_t>("stay time")->Set(0);
|
// aiObjectContext->GetValue<time_t>("stay time")->Set(0);
|
||||||
|
|
||||||
MotionMaster& mm = *bot->GetMotionMaster();
|
MotionMaster& mm = *bot->GetMotionMaster();
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ bool PlayerbotAIConfig::Initialize()
|
|||||||
pvpProhibitedZoneIds);
|
pvpProhibitedZoneIds);
|
||||||
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds", "976,35"),
|
LoadList<std::vector<uint32>>(sConfigMgr->GetOption<std::string>("AiPlayerbot.PvpProhibitedAreaIds", "976,35"),
|
||||||
pvpProhibitedAreaIds);
|
pvpProhibitedAreaIds);
|
||||||
|
fastReactInBG = sConfigMgr->GetOption<bool>("AiPlayerbot.FastReactInBG", true);
|
||||||
LoadList<std::vector<uint32>>(
|
LoadList<std::vector<uint32>>(
|
||||||
sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761"),
|
sConfigMgr->GetOption<std::string>("AiPlayerbot.RandomBotQuestIds", "7848,3802,5505,6502,7761"),
|
||||||
randomBotQuestIds);
|
randomBotQuestIds);
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ public:
|
|||||||
std::vector<uint32> randomBotGuilds;
|
std::vector<uint32> randomBotGuilds;
|
||||||
std::vector<uint32> pvpProhibitedZoneIds;
|
std::vector<uint32> pvpProhibitedZoneIds;
|
||||||
std::vector<uint32> pvpProhibitedAreaIds;
|
std::vector<uint32> pvpProhibitedAreaIds;
|
||||||
|
bool fastReactInBG;
|
||||||
|
|
||||||
bool randombotsWalkingRPG;
|
bool randombotsWalkingRPG;
|
||||||
bool randombotsWalkingRPGInDoors;
|
bool randombotsWalkingRPGInDoors;
|
||||||
|
|||||||
@@ -18,31 +18,31 @@ bool CheckMountStateAction::Execute(Event event)
|
|||||||
AI_VALUE2(bool, "combat", "self target") ? (AI_VALUE(uint8, "attacker count") > 0 ? false : true) : true;
|
AI_VALUE2(bool, "combat", "self target") ? (AI_VALUE(uint8, "attacker count") > 0 ? false : true) : true;
|
||||||
bool enemy = AI_VALUE(Unit*, "enemy player target");
|
bool enemy = AI_VALUE(Unit*, "enemy player target");
|
||||||
// ignore grind target in BG or bots will dismount near any creature (eg: the rams in AV)
|
// ignore grind target in BG or bots will dismount near any creature (eg: the rams in AV)
|
||||||
bool dps = (AI_VALUE(Unit*, "dps target") || (!bot->InBattleground() && AI_VALUE(Unit*, "grind target")));
|
bool dps = AI_VALUE(Unit*, "dps target");
|
||||||
bool fartarget =
|
// bool fartarget = (enemy && sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "enemy player
|
||||||
(enemy && sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "enemy player target"), 40.0f)) ||
|
// target"), 40.0f)) ||
|
||||||
(dps && sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "dps target"), 50.0f));
|
// (dps && sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "dps target"), 50.0f));
|
||||||
bool attackdistance = false;
|
bool attackdistance = false;
|
||||||
bool chasedistance = false;
|
// bool chasedistance = false;
|
||||||
float attack_distance = 35.0f;
|
float attack_distance = 35.0f;
|
||||||
if (PlayerbotAI::IsMelee(bot))
|
if (PlayerbotAI::IsMelee(bot))
|
||||||
{
|
{
|
||||||
attack_distance = 10.0f;
|
attack_distance = 5.0f;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
attack_distance = 40.0f;
|
attack_distance = 30.0f;
|
||||||
}
|
}
|
||||||
if (enemy)
|
|
||||||
attack_distance /= 2;
|
// if (enemy)
|
||||||
|
// attack_distance /= 2;
|
||||||
|
|
||||||
if (dps || enemy)
|
if (dps || enemy)
|
||||||
{
|
{
|
||||||
attackdistance = (enemy || dps) && sServerFacade->IsDistanceLessThan(
|
Unit* currentTarget = AI_VALUE(Unit*, "current target");
|
||||||
AI_VALUE2(float, "distance", "current target"), attack_distance);
|
attackdistance =
|
||||||
chasedistance =
|
(enemy || dps) && currentTarget &&
|
||||||
enemy && sServerFacade->IsDistanceGreaterThan(AI_VALUE2(float, "distance", "enemy player target"), 45.0f) &&
|
sServerFacade->IsDistanceLessThan(AI_VALUE2(float, "distance", "current target"), attack_distance);
|
||||||
AI_VALUE2(bool, "moving", "enemy player target");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot->IsMounted() && attackdistance)
|
if (bot->IsMounted() && attackdistance)
|
||||||
@@ -94,8 +94,7 @@ bool CheckMountStateAction::Execute(Event event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bot->InBattleground() && !attackdistance && (noattackers || fartarget) && !bot->IsInCombat() &&
|
if (bot->InBattleground() && !attackdistance && noattackers && !bot->IsInCombat() && !bot->IsMounted())
|
||||||
!bot->IsMounted())
|
|
||||||
{
|
{
|
||||||
if (bot->GetBattlegroundTypeId() == BATTLEGROUND_WS)
|
if (bot->GetBattlegroundTypeId() == BATTLEGROUND_WS)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ bool FollowAction::Execute(Event event)
|
|||||||
if (Formation::IsNullLocation(loc) || loc.GetMapId() == -1)
|
if (Formation::IsNullLocation(loc) || loc.GetMapId() == -1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
moved = MoveTo(loc.GetMapId(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ());
|
moved = MoveTo(loc.GetMapId(), loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ(), false, false, false,
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Pet* pet = bot->GetPet())
|
if (Pet* pet = bot->GetPet())
|
||||||
|
|||||||
@@ -94,15 +94,21 @@ bool MoveToRpgTargetAction::Execute(Event event)
|
|||||||
|
|
||||||
x += cos(angle) * INTERACTION_DISTANCE * distance;
|
x += cos(angle) * INTERACTION_DISTANCE * distance;
|
||||||
y += sin(angle) * INTERACTION_DISTANCE * distance;
|
y += sin(angle) * INTERACTION_DISTANCE * distance;
|
||||||
|
if (!wo->GetMap()->CheckCollisionAndGetValidCoords(wo, wo->GetPositionX(), wo->GetPositionY(), wo->GetPositionZ(),
|
||||||
|
x, y, z))
|
||||||
|
{
|
||||||
|
x = wo->GetPositionX();
|
||||||
|
y = wo->GetPositionY();
|
||||||
|
z = wo->GetPositionZ();
|
||||||
|
}
|
||||||
// WaitForReach(distance);
|
// WaitForReach(distance);
|
||||||
|
|
||||||
bool couldMove = false;
|
bool couldMove = false;
|
||||||
|
|
||||||
if (bot->IsWithinLOS(x, y, z))
|
// if (bot->IsWithinLOS(x, y, z))
|
||||||
couldMove = MoveNear(mapId, x, y, z, 0);
|
// couldMove = MoveNear(mapId, x, y, z, 0);
|
||||||
else
|
// else
|
||||||
couldMove = MoveTo(mapId, x, y, z);
|
couldMove = MoveTo(mapId, x, y, z, false, false, false, true);
|
||||||
|
|
||||||
if (!couldMove && WorldPosition(mapId, x, y, z).distance(bot) > INTERACTION_DISTANCE)
|
if (!couldMove && WorldPosition(mapId, x, y, z).distance(bot) > INTERACTION_DISTANCE)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "Event.h"
|
#include "Event.h"
|
||||||
#include "FleeManager.h"
|
#include "FleeManager.h"
|
||||||
|
#include "G3D/Vector3.h"
|
||||||
#include "GameObject.h"
|
#include "GameObject.h"
|
||||||
#include "Geometry.h"
|
#include "Geometry.h"
|
||||||
#include "LastMovementValue.h"
|
#include "LastMovementValue.h"
|
||||||
@@ -68,7 +69,7 @@ void MovementAction::JumpTo(uint32 mapId, float x, float y, float z)
|
|||||||
botAI->SetNextCheckDelay(1000);
|
botAI->SetNextCheckDelay(1000);
|
||||||
mm.Clear();
|
mm.Clear();
|
||||||
mm.MoveJump(x, y, z, speed, speed, 1);
|
mm.MoveJump(x, y, z, speed, speed, 1);
|
||||||
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation());
|
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MovementAction::MoveNear(uint32 mapId, float x, float y, float z, float distance)
|
bool MovementAction::MoveNear(uint32 mapId, float x, float y, float z, float distance)
|
||||||
@@ -160,13 +161,22 @@ bool MovementAction::MoveToLOS(WorldObject* target, bool ranged)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, bool react, bool normal_only)
|
bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, bool react, bool normal_only,
|
||||||
|
bool exact_waypoint)
|
||||||
{
|
{
|
||||||
UpdateMovementState();
|
UpdateMovementState();
|
||||||
if (!IsMovingAllowed(mapId, x, y, z))
|
if (!IsMovingAllowed(mapId, x, y, z))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (IsDuplicateMove(mapId, x, y, z))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (IsWaitingForLastMove())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// if (bot->Unit::IsFalling()) {
|
// if (bot->Unit::IsFalling()) {
|
||||||
// bot->Say("I'm falling!, flag:" + std::to_string(bot->m_movementInfo.GetMovementFlags()), LANG_UNIVERSAL);
|
// bot->Say("I'm falling!, flag:" + std::to_string(bot->m_movementInfo.GetMovementFlags()), LANG_UNIVERSAL);
|
||||||
// return false;
|
// return false;
|
||||||
@@ -177,17 +187,16 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
|||||||
// if (bot->Unit::IsFalling()) {
|
// if (bot->Unit::IsFalling()) {
|
||||||
// bot->Say("I'm falling", LANG_UNIVERSAL);
|
// bot->Say("I'm falling", LANG_UNIVERSAL);
|
||||||
// }
|
// }
|
||||||
bool generatePath = !bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && !bot->IsFlying() &&
|
// !bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) &&
|
||||||
!bot->HasUnitMovementFlag(MOVEMENTFLAG_SWIMMING) && !bot->IsInWater();
|
|
||||||
|
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 (disableMoveSplinePath || !generatePath)
|
if (exact_waypoint || disableMoveSplinePath || !generatePath)
|
||||||
{
|
{
|
||||||
float distance = bot->GetExactDist(x, y, z);
|
float distance = bot->GetExactDist(x, y, z);
|
||||||
if (distance > sPlayerbotAIConfig->contactDistance)
|
if (distance > sPlayerbotAIConfig->contactDistance)
|
||||||
{
|
{
|
||||||
WaitForReach(distance);
|
|
||||||
|
|
||||||
if (bot->IsSitState())
|
if (bot->IsSitState())
|
||||||
bot->SetStandState(UNIT_STAND_STATE_STAND);
|
bot->SetStandState(UNIT_STAND_STATE_STAND);
|
||||||
|
|
||||||
@@ -199,7 +208,8 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
|||||||
MotionMaster& mm = *bot->GetMotionMaster();
|
MotionMaster& mm = *bot->GetMotionMaster();
|
||||||
mm.Clear();
|
mm.Clear();
|
||||||
mm.MovePoint(mapId, x, y, z, generatePath);
|
mm.MovePoint(mapId, x, y, z, generatePath);
|
||||||
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation());
|
float delay = std::min((float)sPlayerbotAIConfig->maxWaitForMove, 1000.0f * MoveDelay(distance));
|
||||||
|
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,8 +225,6 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
|||||||
float distance = bot->GetExactDist(x, y, modifiedZ);
|
float distance = bot->GetExactDist(x, y, modifiedZ);
|
||||||
if (distance > sPlayerbotAIConfig->contactDistance)
|
if (distance > sPlayerbotAIConfig->contactDistance)
|
||||||
{
|
{
|
||||||
WaitForReach(distance);
|
|
||||||
|
|
||||||
if (bot->IsSitState())
|
if (bot->IsSitState())
|
||||||
bot->SetStandState(UNIT_STAND_STATE_STAND);
|
bot->SetStandState(UNIT_STAND_STATE_STAND);
|
||||||
|
|
||||||
@@ -229,7 +237,9 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle,
|
|||||||
|
|
||||||
mm.Clear();
|
mm.Clear();
|
||||||
mm.MoveSplinePath(&path);
|
mm.MoveSplinePath(&path);
|
||||||
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation());
|
// mm.MoveSplinePath(&path);
|
||||||
|
float delay = std::min((float)sPlayerbotAIConfig->maxWaitForMove, 1000.0f * MoveDelay(distance));
|
||||||
|
AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), delay);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -777,28 +787,34 @@ bool MovementAction::ReachCombatTo(Unit* target, float distance)
|
|||||||
float ty = target->GetPositionY();
|
float ty = target->GetPositionY();
|
||||||
float tz = target->GetPositionZ();
|
float tz = target->GetPositionZ();
|
||||||
float combatDistance = bot->GetCombatReach() + target->GetCombatReach();
|
float combatDistance = bot->GetCombatReach() + target->GetCombatReach();
|
||||||
float distanceToTarget = bot->GetExactDist(target) - combatDistance;
|
distance += combatDistance;
|
||||||
float angle = bot->GetAngle(target);
|
|
||||||
float needToGo = distanceToTarget - distance;
|
|
||||||
|
|
||||||
float maxDistance = sPlayerbotAIConfig->spellDistance;
|
if (target->HasUnitMovementFlag(MOVEMENTFLAG_FORWARD)) // target is moving forward, predict the position
|
||||||
if (needToGo > 0 && needToGo > maxDistance)
|
{
|
||||||
needToGo = maxDistance;
|
float needToGo = bot->GetExactDist(target) - distance;
|
||||||
else if (needToGo < 0 && needToGo < -maxDistance)
|
float timeToGo = MoveDelay(abs(needToGo)) + sPlayerbotAIConfig->reactDelay;
|
||||||
needToGo = -maxDistance;
|
float targetMoveDist = timeToGo * target->GetSpeed(MOVE_RUN);
|
||||||
|
targetMoveDist = std::min(5.0f, targetMoveDist);
|
||||||
|
tx += targetMoveDist * cos(target->GetOrientation());
|
||||||
|
ty += targetMoveDist * sin(target->GetOrientation());
|
||||||
|
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
|
||||||
|
target->GetPositionZ(), tx, ty, tz))
|
||||||
|
{
|
||||||
|
// disable prediction if position is invalid
|
||||||
|
tx = target->GetPositionX();
|
||||||
|
ty = target->GetPositionY();
|
||||||
|
tz = target->GetPositionZ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
float dx = cos(angle) * needToGo + bx;
|
PathGenerator path(bot);
|
||||||
float dy = sin(angle) * needToGo + by;
|
path.CalculatePath(tx, ty, tz, false);
|
||||||
float dz; // = std::max(bz, tz); // calc accurate z position to avoid stuck
|
PathType type = path.GetPathType();
|
||||||
if (distanceToTarget > CONTACT_DISTANCE)
|
if (type != PATHFIND_NORMAL && type != PATHFIND_INCOMPLETE)
|
||||||
{
|
return false;
|
||||||
dz = bz + (tz - bz) * (needToGo / distanceToTarget);
|
path.ShortenPathUntilDist(G3D::Vector3(tx, ty, tz), distance);
|
||||||
}
|
G3D::Vector3 endPos = path.GetPath().back();
|
||||||
else
|
return MoveTo(target->GetMapId(), endPos.x, endPos.y, endPos.z);
|
||||||
{
|
|
||||||
dz = tz;
|
|
||||||
}
|
|
||||||
return MoveTo(target->GetMapId(), dx, dy, dz);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float MovementAction::GetFollowAngle()
|
float MovementAction::GetFollowAngle()
|
||||||
@@ -847,6 +863,29 @@ bool MovementAction::IsMovingAllowed(uint32 mapId, float x, float y, float z)
|
|||||||
return IsMovingAllowed();
|
return IsMovingAllowed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MovementAction::IsDuplicateMove(uint32 mapId, float x, float y, float z)
|
||||||
|
{
|
||||||
|
LastMovement& lastMove = *context->GetValue<LastMovement&>("last movement");
|
||||||
|
|
||||||
|
// heuristic 5s
|
||||||
|
if (lastMove.msTime + sPlayerbotAIConfig->maxWaitForMove < getMSTime() ||
|
||||||
|
lastMove.lastMoveShort.GetExactDist(x, y, z) > 0.01f)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MovementAction::IsWaitingForLastMove()
|
||||||
|
{
|
||||||
|
LastMovement& lastMove = *context->GetValue<LastMovement&>("last movement");
|
||||||
|
|
||||||
|
// heuristic 5s
|
||||||
|
if (lastMove.lastdelayTime + lastMove.msTime > getMSTime())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool MovementAction::IsMovingAllowed()
|
bool MovementAction::IsMovingAllowed()
|
||||||
{
|
{
|
||||||
// do not allow if not vehicle driver
|
// do not allow if not vehicle driver
|
||||||
@@ -878,7 +917,7 @@ void MovementAction::UpdateMovementState()
|
|||||||
{
|
{
|
||||||
bot->SetSwim(true);
|
bot->SetSwim(true);
|
||||||
}
|
}
|
||||||
else
|
else if (!bot->Unit::IsInWater())
|
||||||
{
|
{
|
||||||
bot->SetSwim(false);
|
bot->SetSwim(false);
|
||||||
}
|
}
|
||||||
@@ -1529,13 +1568,14 @@ const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y,
|
|||||||
gen.CalculatePath(x, y, tempZ);
|
gen.CalculatePath(x, y, tempZ);
|
||||||
Movement::PointsArray result = gen.GetPath();
|
Movement::PointsArray result = gen.GetPath();
|
||||||
float min_length = gen.getPathLength();
|
float min_length = gen.getPathLength();
|
||||||
if ((gen.GetPathType() & PATHFIND_NORMAL) && abs(tempZ - z) < 0.5f)
|
int typeOk = PATHFIND_NORMAL | PATHFIND_INCOMPLETE;
|
||||||
|
if ((gen.GetPathType() & typeOk) && abs(tempZ - z) < 0.5f)
|
||||||
{
|
{
|
||||||
modified_z = tempZ;
|
modified_z = tempZ;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// Start searching
|
// Start searching
|
||||||
if (gen.GetPathType() & PATHFIND_NORMAL)
|
if (gen.GetPathType() & typeOk)
|
||||||
{
|
{
|
||||||
modified_z = tempZ;
|
modified_z = tempZ;
|
||||||
found = true;
|
found = true;
|
||||||
@@ -1550,7 +1590,7 @@ const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y,
|
|||||||
}
|
}
|
||||||
PathGenerator gen(bot);
|
PathGenerator gen(bot);
|
||||||
gen.CalculatePath(x, y, tempZ);
|
gen.CalculatePath(x, y, tempZ);
|
||||||
if ((gen.GetPathType() & PATHFIND_NORMAL) && gen.getPathLength() < min_length)
|
if ((gen.GetPathType() & typeOk) && gen.getPathLength() < min_length)
|
||||||
{
|
{
|
||||||
found = true;
|
found = true;
|
||||||
min_length = gen.getPathLength();
|
min_length = gen.getPathLength();
|
||||||
@@ -1567,7 +1607,7 @@ const Movement::PointsArray MovementAction::SearchForBestPath(float x, float y,
|
|||||||
}
|
}
|
||||||
PathGenerator gen(bot);
|
PathGenerator gen(bot);
|
||||||
gen.CalculatePath(x, y, tempZ);
|
gen.CalculatePath(x, y, tempZ);
|
||||||
if ((gen.GetPathType() & PATHFIND_NORMAL) && gen.getPathLength() < min_length)
|
if ((gen.GetPathType() & typeOk) && gen.getPathLength() < min_length)
|
||||||
{
|
{
|
||||||
found = true;
|
found = true;
|
||||||
min_length = gen.getPathLength();
|
min_length = gen.getPathLength();
|
||||||
@@ -2285,12 +2325,15 @@ bool MoveRandomAction::Execute(Event event)
|
|||||||
float angle = (float)rand_norm() * static_cast<float>(M_PI);
|
float angle = (float)rand_norm() * static_cast<float>(M_PI);
|
||||||
x += urand(0, distance) * cos(angle);
|
x += urand(0, distance) * cos(angle);
|
||||||
y += urand(0, distance) * sin(angle);
|
y += urand(0, distance) * sin(angle);
|
||||||
bot->UpdateGroundPositionZ(x, y, z);
|
if (!bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||||
|
bot->GetPositionZ(), x, y, z))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (map->IsInWater(bot->GetPhaseMask(), x, y, z, bot->GetCollisionHeight()))
|
if (map->IsInWater(bot->GetPhaseMask(), x, y, z, bot->GetCollisionHeight()))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
bool moved = MoveTo(bot->GetMapId(), x, y, z);
|
bool moved = MoveTo(bot->GetMapId(), x, y, z, false, false, false, true);
|
||||||
if (moved)
|
if (moved)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ protected:
|
|||||||
bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->contactDistance);
|
bool MoveNear(uint32 mapId, float x, float y, float z, float distance = sPlayerbotAIConfig->contactDistance);
|
||||||
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 normal_only = false, bool exact_waypoint = false);
|
||||||
bool MoveTo(Unit* target, float distance = 0.0f);
|
bool MoveTo(Unit* target, float distance = 0.0f);
|
||||||
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance);
|
bool MoveNear(WorldObject* target, float distance = sPlayerbotAIConfig->contactDistance);
|
||||||
float GetFollowAngle();
|
float GetFollowAngle();
|
||||||
@@ -39,6 +39,8 @@ protected:
|
|||||||
void WaitForReach(float distance);
|
void WaitForReach(float distance);
|
||||||
bool IsMovingAllowed(Unit* target);
|
bool IsMovingAllowed(Unit* target);
|
||||||
bool IsMovingAllowed(uint32 mapId, float x, float y, float z);
|
bool IsMovingAllowed(uint32 mapId, float x, float y, float z);
|
||||||
|
bool IsDuplicateMove(uint32 mapId, float x, float y, float z);
|
||||||
|
bool IsWaitingForLastMove();
|
||||||
bool IsMovingAllowed();
|
bool IsMovingAllowed();
|
||||||
bool Flee(Unit* target);
|
bool Flee(Unit* target);
|
||||||
void ClearIdleState();
|
void ClearIdleState();
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ private:
|
|||||||
}
|
}
|
||||||
static Trigger* medium_group_heal_occasion(PlayerbotAI* ai)
|
static Trigger* medium_group_heal_occasion(PlayerbotAI* ai)
|
||||||
{
|
{
|
||||||
return new AoeInGroupTrigger(ai, "group heal occasion", "medium", 0.4);
|
return new AoeInGroupTrigger(ai, "group heal occasion", "medium", 0.6);
|
||||||
}
|
}
|
||||||
static Trigger* target_changed(PlayerbotAI* botAI) { return new TargetChangedTrigger(botAI); }
|
static Trigger* target_changed(PlayerbotAI* botAI) { return new TargetChangedTrigger(botAI); }
|
||||||
static Trigger* swimming(PlayerbotAI* botAI) { return new IsSwimmingTrigger(botAI); }
|
static Trigger* swimming(PlayerbotAI* botAI) { return new IsSwimmingTrigger(botAI); }
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ WorldLocation ArrowFormation::GetLocationInternal()
|
|||||||
float y = master->GetPositionY() - masterUnit->GetY() + botUnit->GetY();
|
float y = master->GetPositionY() - masterUnit->GetY() + botUnit->GetY();
|
||||||
float z = master->GetPositionZ();
|
float z = master->GetPositionZ();
|
||||||
|
|
||||||
float ground = master->GetMapHeight(x, y, z + 30.0f);
|
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||||
if (ground <= INVALID_HEIGHT)
|
master->GetPositionZ(), x, y, z))
|
||||||
return Formation::NullLocation;
|
return Formation::NullLocation;
|
||||||
// master->UpdateGroundPositionZ(x, y, z);
|
// master->UpdateGroundPositionZ(x, y, z);
|
||||||
return WorldLocation(master->GetMapId(), x, y, z);
|
return WorldLocation(master->GetMapId(), x, y, z);
|
||||||
|
|||||||
@@ -89,12 +89,9 @@ public:
|
|||||||
float y = master->GetPositionY() + sin(angle) * range;
|
float y = master->GetPositionY() + sin(angle) * range;
|
||||||
float z = master->GetPositionZ();
|
float z = master->GetPositionZ();
|
||||||
|
|
||||||
float ground = master->GetMapHeight(x, y, z + 30.0f);
|
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||||
if (ground <= INVALID_HEIGHT)
|
master->GetPositionZ(), x, y, z))
|
||||||
return Formation::NullLocation;
|
return Formation::NullLocation;
|
||||||
|
|
||||||
// z += CONTACT_DISTANCE;
|
|
||||||
// bot->UpdateAllowedPositionZ(x, y, z);
|
|
||||||
return WorldLocation(master->GetMapId(), x, y, z);
|
return WorldLocation(master->GetMapId(), x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +112,16 @@ public:
|
|||||||
float range = sPlayerbotAIConfig->followDistance;
|
float range = sPlayerbotAIConfig->followDistance;
|
||||||
float angle = GetFollowAngle();
|
float angle = GetFollowAngle();
|
||||||
|
|
||||||
|
time_t now = time(nullptr);
|
||||||
|
if (!lastChangeTime || now - lastChangeTime >= 3)
|
||||||
|
{
|
||||||
|
Player* master = botAI->GetGroupMaster();
|
||||||
|
if (!master)
|
||||||
|
return WorldLocation();
|
||||||
|
|
||||||
|
float range = sPlayerbotAIConfig->followDistance;
|
||||||
|
float angle = GetFollowAngle();
|
||||||
|
|
||||||
time_t now = time(nullptr);
|
time_t now = time(nullptr);
|
||||||
if (!lastChangeTime || now - lastChangeTime >= 3)
|
if (!lastChangeTime || now - lastChangeTime >= 3)
|
||||||
{
|
{
|
||||||
@@ -127,12 +134,20 @@ public:
|
|||||||
float x = master->GetPositionX() + cos(angle) * range + dx;
|
float x = master->GetPositionX() + cos(angle) * range + dx;
|
||||||
float y = master->GetPositionY() + sin(angle) * range + dy;
|
float y = master->GetPositionY() + sin(angle) * range + dy;
|
||||||
float z = master->GetPositionZ();
|
float z = master->GetPositionZ();
|
||||||
float ground = master->GetMapHeight(x, y, z + 30.0f);
|
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
|
||||||
if (ground <= INVALID_HEIGHT)
|
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z))
|
||||||
return Formation::NullLocation;
|
return Formation::NullLocation;
|
||||||
|
// bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
|
||||||
|
// bot->GetPositionZ(), x, y, z);
|
||||||
|
return WorldLocation(master->GetMapId(), x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
z += CONTACT_DISTANCE;
|
float x = master->GetPositionX() + cos(angle) * range + dx;
|
||||||
bot->UpdateAllowedPositionZ(x, y, z);
|
float y = master->GetPositionY() + sin(angle) * range + dy;
|
||||||
|
float z = master->GetPositionZ();
|
||||||
|
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||||
|
master->GetPositionZ(), x, y, z))
|
||||||
|
return Formation::NullLocation;
|
||||||
return WorldLocation(master->GetMapId(), x, y, z);
|
return WorldLocation(master->GetMapId(), x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,13 +199,10 @@ public:
|
|||||||
float x = target->GetPositionX() + cos(angle) * range;
|
float x = target->GetPositionX() + cos(angle) * range;
|
||||||
float y = target->GetPositionY() + sin(angle) * range;
|
float y = target->GetPositionY() + sin(angle) * range;
|
||||||
float z = target->GetPositionZ();
|
float z = target->GetPositionZ();
|
||||||
float ground = target->GetMapHeight(x, y, z + 30.0f);
|
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||||
if (ground <= INVALID_HEIGHT)
|
master->GetPositionZ(), x, y, z))
|
||||||
return Formation::NullLocation;
|
return Formation::NullLocation;
|
||||||
|
|
||||||
z += CONTACT_DISTANCE;
|
|
||||||
bot->UpdateAllowedPositionZ(x, y, z);
|
|
||||||
|
|
||||||
return WorldLocation(bot->GetMapId(), x, y, z);
|
return WorldLocation(bot->GetMapId(), x, y, z);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -350,16 +362,18 @@ public:
|
|||||||
|
|
||||||
if (minDist)
|
if (minDist)
|
||||||
{
|
{
|
||||||
z += CONTACT_DISTANCE;
|
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
|
||||||
bot->UpdateAllowedPositionZ(minX, minY, z);
|
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z))
|
||||||
|
return Formation::NullLocation;
|
||||||
return WorldLocation(bot->GetMapId(), minX, minY, z);
|
return WorldLocation(bot->GetMapId(), minX, minY, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Formation::NullLocation;
|
return Formation::NullLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
z += CONTACT_DISTANCE;
|
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
|
||||||
bot->UpdateAllowedPositionZ(x, y, z);
|
master->GetPositionZ(), x, y, z))
|
||||||
|
return Formation::NullLocation;
|
||||||
return WorldLocation(bot->GetMapId(), x, y, z);
|
return WorldLocation(bot->GetMapId(), x, y, z);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -618,12 +632,12 @@ WorldLocation MoveFormation::MoveSingleLine(std::vector<Player*> line, float dif
|
|||||||
float lx = x + cos(angle) * radius;
|
float lx = x + cos(angle) * radius;
|
||||||
float ly = y + sin(angle) * radius;
|
float ly = y + sin(angle) * radius;
|
||||||
float lz = cz;
|
float lz = cz;
|
||||||
float ground = bot->GetMapHeight(lx, ly, lz + 30.0f);
|
|
||||||
if (ground <= INVALID_HEIGHT)
|
Player* master = botAI->GetMaster();
|
||||||
|
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
|
||||||
|
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), lx, ly, lz))
|
||||||
return Formation::NullLocation;
|
return Formation::NullLocation;
|
||||||
|
|
||||||
lz += CONTACT_DISTANCE;
|
|
||||||
bot->UpdateAllowedPositionZ(lx, ly, lz);
|
|
||||||
return WorldLocation(bot->GetMapId(), lx, ly, lz);
|
return WorldLocation(bot->GetMapId(), lx, ly, lz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "LastMovementValue.h"
|
#include "LastMovementValue.h"
|
||||||
|
|
||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
|
#include "Timer.h"
|
||||||
|
|
||||||
LastMovement::LastMovement() { clear(); }
|
LastMovement::LastMovement() { clear(); }
|
||||||
|
|
||||||
@@ -38,17 +39,19 @@ void LastMovement::clear()
|
|||||||
lastAreaTrigger = 0;
|
lastAreaTrigger = 0;
|
||||||
lastFlee = 0;
|
lastFlee = 0;
|
||||||
nextTeleport = 0;
|
nextTeleport = 0;
|
||||||
|
msTime = 0;
|
||||||
|
lastdelayTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LastMovement::Set(Unit* follow)
|
void LastMovement::Set(Unit* follow)
|
||||||
{
|
{
|
||||||
Set(0, 0.0f, 0.0f, 0.0f, 0.0f);
|
Set(0, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
setShort(WorldPosition());
|
setShort(WorldPosition());
|
||||||
setPath(TravelPath());
|
setPath(TravelPath());
|
||||||
lastFollow = follow;
|
lastFollow = follow;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LastMovement::Set(uint32 mapId, float x, float y, float z, float ori)
|
void LastMovement::Set(uint32 mapId, float x, float y, float z, float ori, float delayTime)
|
||||||
{
|
{
|
||||||
lastMoveToMapId = mapId;
|
lastMoveToMapId = mapId;
|
||||||
lastMoveToX = x;
|
lastMoveToX = x;
|
||||||
@@ -57,6 +60,8 @@ void LastMovement::Set(uint32 mapId, float x, float y, float z, float ori)
|
|||||||
lastMoveToOri = ori;
|
lastMoveToOri = ori;
|
||||||
lastFollow = nullptr;
|
lastFollow = nullptr;
|
||||||
lastMoveShort = WorldPosition(mapId, x, y, z, ori);
|
lastMoveShort = WorldPosition(mapId, x, y, z, ori);
|
||||||
|
msTime = getMSTime();
|
||||||
|
lastdelayTime = delayTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LastMovement::setShort(WorldPosition point)
|
void LastMovement::setShort(WorldPosition point)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public:
|
|||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
void Set(Unit* follow);
|
void Set(Unit* follow);
|
||||||
void Set(uint32 mapId, float x, float y, float z, float ori);
|
void Set(uint32 mapId, float x, float y, float z, float ori, float delayTime);
|
||||||
|
|
||||||
void setShort(WorldPosition point);
|
void setShort(WorldPosition point);
|
||||||
void setPath(TravelPath path);
|
void setPath(TravelPath path);
|
||||||
@@ -50,7 +50,9 @@ public:
|
|||||||
float lastMoveToY;
|
float lastMoveToY;
|
||||||
float lastMoveToZ;
|
float lastMoveToZ;
|
||||||
float lastMoveToOri;
|
float lastMoveToOri;
|
||||||
|
float lastdelayTime;
|
||||||
WorldPosition lastMoveShort;
|
WorldPosition lastMoveShort;
|
||||||
|
uint32 msTime;
|
||||||
TravelPath lastPath;
|
TravelPath lastPath;
|
||||||
time_t nextTeleport;
|
time_t nextTeleport;
|
||||||
std::future<TravelPath> future;
|
std::future<TravelPath> future;
|
||||||
|
|||||||
@@ -82,8 +82,7 @@ bool PartyMemberToHeal::Check(Unit* player)
|
|||||||
// sServerFacade->GetDistance2d(bot, player) < (player->IsPlayer() && botAI->IsTank((Player*)player) ? 50.0f
|
// sServerFacade->GetDistance2d(bot, player) < (player->IsPlayer() && botAI->IsTank((Player*)player) ? 50.0f
|
||||||
// : 40.0f);
|
// : 40.0f);
|
||||||
return player->GetMapId() == bot->GetMapId() && !player->IsCharmed() &&
|
return player->GetMapId() == bot->GetMapId() && !player->IsCharmed() &&
|
||||||
bot->GetDistance2d(player) < sPlayerbotAIConfig->healDistance * 2 &&
|
bot->GetDistance2d(player) < sPlayerbotAIConfig->healDistance * 2 && bot->IsWithinLOSInMap(player);
|
||||||
bot->IsWithinLOS(player->GetPositionX(), player->GetPositionY(), player->GetPositionZ());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Unit* PartyMemberToProtect::Calculate()
|
Unit* PartyMemberToProtect::Calculate()
|
||||||
|
|||||||
Reference in New Issue
Block a user