Files
mod-playerbots/src/strategy/values/Formations.cpp
2024-08-12 11:07:14 +08:00

684 lines
21 KiB
C++

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
* and/or modify it under version 2 of the License, or (at your option), any later version.
*/
#include "Formations.h"
#include "Arrow.h"
#include "Event.h"
#include "Map.h"
#include "Playerbots.h"
#include "ServerFacade.h"
WorldLocation Formation::NullLocation = WorldLocation();
bool IsSameLocation(WorldLocation const& a, WorldLocation const& b)
{
return a.GetPositionX() == b.GetPositionX() && a.GetPositionY() == b.GetPositionY() &&
a.GetPositionZ() == b.GetPositionZ() && a.GetMapId() == b.GetMapId();
}
bool Formation::IsNullLocation(WorldLocation const& loc) { return IsSameLocation(loc, Formation::NullLocation); }
WorldLocation MoveAheadFormation::GetLocation()
{
Player* master = botAI->GetGroupMaster();
if (!master || master == bot)
return WorldLocation();
WorldLocation loc = GetLocationInternal();
if (Formation::IsNullLocation(loc))
return loc;
float x = loc.GetPositionX();
float y = loc.GetPositionY();
float z = loc.GetPositionZ();
// if (master->isMoving())
// {
// float ori = master->GetOrientation();
// float x1 = x + sPlayerbotAIConfig->tooCloseDistance * cos(ori);
// float y1 = y + sPlayerbotAIConfig->tooCloseDistance * sin(ori);
// float ground = master->GetMap()->GetHeight(x1, y1, z);
// if (ground > INVALID_HEIGHT)
// {
// x = x1;
// y = y1;
// }
// }
// float ground = master->GetMap()->GetHeight(x, y, z);
// if (ground <= INVALID_HEIGHT)
// return Formation::NullLocation;
// z += CONTACT_DISTANCE;
// bot->UpdateAllowedPositionZ(x, y, z);
return WorldLocation(master->GetMapId(), x, y, z);
}
class MeleeFormation : public FollowFormation
{
public:
MeleeFormation(PlayerbotAI* botAI) : FollowFormation(botAI, "melee") {}
std::string const GetTargetName() override { return "master target"; }
};
class QueueFormation : public FollowFormation
{
public:
QueueFormation(PlayerbotAI* botAI) : FollowFormation(botAI, "queue") {}
std::string const GetTargetName() override { return "line target"; }
};
class NearFormation : public MoveAheadFormation
{
public:
NearFormation(PlayerbotAI* botAI) : MoveAheadFormation(botAI, "near") {}
WorldLocation GetLocationInternal() override
{
Player* master = GetMaster();
if (!master)
return WorldLocation();
float range = sPlayerbotAIConfig->followDistance;
float angle = GetFollowAngle();
float x = master->GetPositionX() + cos(angle) * range;
float y = master->GetPositionY() + sin(angle) * range;
float z = master->GetPositionZ();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range;
y = master->GetPositionY() + sin(angle) * range;
z = master->GetPositionZ() + master->GetHoverHeight();
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(master->GetMapId(), x, y, z);
}
float GetMaxDistance() override { return sPlayerbotAIConfig->followDistance; }
};
class ChaosFormation : public MoveAheadFormation
{
public:
ChaosFormation(PlayerbotAI* botAI) : MoveAheadFormation(botAI, "chaos"), lastChangeTime(0) {}
WorldLocation GetLocationInternal() override
{
Player* master = botAI->GetGroupMaster();
if (!master)
return WorldLocation();
float range = sPlayerbotAIConfig->followDistance;
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);
if (!lastChangeTime || now - lastChangeTime >= 3)
{
lastChangeTime = now;
dx = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig->tooCloseDistance;
dy = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig->tooCloseDistance;
dr = sqrt(dx * dx + dy * dy);
}
float x = master->GetPositionX() + cos(angle) * range + dx;
float y = master->GetPositionY() + sin(angle) * range + dy;
float z = master->GetPositionZ();
z = bot->GetMapHeight(x, y, z + 5.0f);
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + dx;
y = master->GetPositionY() + sin(angle) * range + dy;
z = master->GetPositionZ() + master->GetHoverHeight();
z = master->GetMapHeight(x, y, z);
}
// bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
// bot->GetPositionZ(), x, y, z);
return WorldLocation(master->GetMapId(), x, y, z);
}
float x = master->GetPositionX() + cos(angle) * range + dx;
float y = master->GetPositionY() + sin(angle) * range + dy;
float z = master->GetPositionZ();
z = bot->GetMapHeight(x, y, z + 5.0f);
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + dx;
y = master->GetPositionY() + sin(angle) * range + dy;
z = master->GetPositionZ() + master->GetHoverHeight();
z = master->GetMapHeight(x, y, z);
}
return WorldLocation(master->GetMapId(), x, y, z);
}
float GetMaxDistance() override { return sPlayerbotAIConfig->followDistance + dr; }
private:
time_t lastChangeTime;
float dx = 0.f;
float dy = 0.f;
float dr = 0.f;
};
class CircleFormation : public MoveFormation
{
public:
CircleFormation(PlayerbotAI* botAI) : MoveFormation(botAI, "circle") {}
WorldLocation GetLocation() override
{
float range = 2.0f;
Unit* target = AI_VALUE(Unit*, "current target");
Player* master = botAI->GetGroupMaster();
if (!target && target != bot)
target = master;
if (!target)
return Formation::NullLocation;
switch (bot->getClass())
{
case CLASS_HUNTER:
case CLASS_MAGE:
case CLASS_PRIEST:
case CLASS_WARLOCK:
range = botAI->GetRange("flee");
break;
case CLASS_DRUID:
if (!botAI->IsTank(bot))
range = botAI->GetRange("flee");
break;
case CLASS_SHAMAN:
if (botAI->IsHeal(bot))
range = botAI->GetRange("flee");
break;
}
float angle = GetFollowAngle();
float x = target->GetPositionX() + cos(angle) * range;
float y = target->GetPositionY() + sin(angle) * range;
float z = target->GetPositionZ();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = target->GetPositionX() + cos(angle) * range;
y = target->GetPositionY() + sin(angle) * range;
z = target->GetPositionZ() + target->GetHoverHeight();
z = master->GetMapHeight(x, y, z);
}
return WorldLocation(bot->GetMapId(), x, y, z);
}
};
class LineFormation : public MoveAheadFormation
{
public:
LineFormation(PlayerbotAI* botAI) : MoveAheadFormation(botAI, "line") {}
WorldLocation GetLocationInternal() override
{
Group* group = bot->GetGroup();
if (!group)
return Formation::NullLocation;
float range = 2.0f;
Player* master = botAI->GetGroupMaster();
if (!master)
return Formation::NullLocation;
float x = master->GetPositionX();
float y = master->GetPositionY();
float z = master->GetPositionZ();
float orientation = master->GetOrientation();
std::vector<Player*> players;
GroupReference* gref = group->GetFirstMember();
while (gref)
{
Player* member = gref->GetSource();
if (member != master)
players.push_back(member);
gref = gref->next();
}
players.insert(players.begin() + group->GetMembersCount() / 2, master);
return MoveLine(players, 0.0f, x, y, z, orientation, range);
}
};
class ShieldFormation : public MoveFormation
{
public:
ShieldFormation(PlayerbotAI* botAI) : MoveFormation(botAI, "shield") {}
WorldLocation GetLocation() override
{
Group* group = bot->GetGroup();
if (!group)
return Formation::NullLocation;
float range = sPlayerbotAIConfig->followDistance;
Player* master = GetMaster();
if (!master)
return Formation::NullLocation;
float x = master->GetPositionX();
float y = master->GetPositionY();
float z = master->GetPositionZ();
float orientation = master->GetOrientation();
std::vector<Player*> tanks;
std::vector<Player*> dps;
GroupReference* gref = group->GetFirstMember();
while (gref)
{
Player* member = gref->GetSource();
if (member != master)
{
if (botAI->IsTank(member))
tanks.push_back(member);
else
dps.push_back(member);
}
gref = gref->next();
}
if (botAI->IsTank(master))
tanks.insert(tanks.begin() + (tanks.size() + 1) / 2, master);
else
dps.insert(dps.begin() + (dps.size() + 1) / 2, master);
if (botAI->IsTank(bot) && botAI->IsTank(master))
{
return MoveLine(tanks, 0.0f, x, y, z, orientation, range);
}
if (!botAI->IsTank(bot) && !botAI->IsTank(master))
{
return MoveLine(dps, 0.0f, x, y, z, orientation, range);
}
if (botAI->IsTank(bot) && !botAI->IsTank(master))
{
float diff = tanks.size() % 2 == 0 ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
return MoveLine(tanks, diff, x + cos(orientation) * range, y + sin(orientation) * range, z, orientation,
range);
}
if (!botAI->IsTank(bot) && botAI->IsTank(master))
{
float diff = dps.size() % 2 == 0 ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
return MoveLine(dps, diff, x - cos(orientation) * range, y - sin(orientation) * range, z, orientation,
range);
}
return Formation::NullLocation;
}
};
class FarFormation : public FollowFormation
{
public:
FarFormation(PlayerbotAI* botAI) : FollowFormation(botAI, "far") {}
WorldLocation GetLocation() override
{
float range = sPlayerbotAIConfig->farDistance;
float followRange = sPlayerbotAIConfig->followDistance;
Player* master = GetMaster();
if (!master)
return Formation::NullLocation;
if (sServerFacade->GetDistance2d(bot, master) <= range)
return Formation::NullLocation;
float angle = master->GetAngle(bot);
float followAngle = GetFollowAngle();
float x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
float y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
float z = master->GetPositionZ();
float ground = master->GetMapHeight(x, y, z + 30.0f);
if (ground <= INVALID_HEIGHT)
{
float minDist = 0, minX = 0, minY = 0;
for (float angle = 0.0f; angle <= 2 * M_PI; angle += M_PI / 16.0f)
{
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
float dist = sServerFacade->GetDistance2d(bot, x, y);
float ground = master->GetMapHeight(x, y, z + 30.0f);
if (ground > INVALID_HEIGHT && (!minDist || minDist > dist))
{
minDist = dist;
minX = x;
minY = y;
}
}
if (minDist)
{
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
z = master->GetPositionZ() + master->GetHoverHeight();
z = master->GetMapHeight(x, y, z);
}
return WorldLocation(bot->GetMapId(), minX, minY, z);
}
return Formation::NullLocation;
}
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
z = master->GetPositionZ() + master->GetHoverHeight();
z = master->GetMapHeight(x, y, z);
}
return WorldLocation(bot->GetMapId(), x, y, z);
}
};
float Formation::GetFollowAngle()
{
Player* master = GetMaster();
Group* group = bot->GetGroup();
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
uint32 index = 1;
uint32 total = 1;
PlayerbotMgr* masterBotMgr = nullptr;
if (master)
masterBotMgr = GET_PLAYERBOT_MGR(master);
if (!group && master && !GET_PLAYERBOT_AI(master) && masterBotMgr)
{
for (PlayerBotMap::const_iterator i = masterBotMgr->GetPlayerBotsBegin(); i != masterBotMgr->GetPlayerBotsEnd();
++i)
{
if (i->second == bot)
index = total;
++total;
}
}
else if (group)
{
std::vector<Player*> roster;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
if (Player* member = ref->GetSource())
{
if (member != master && !botAI->IsTank(member) && !botAI->IsHeal(member))
{
roster.insert(roster.begin() + roster.size() / 2, member);
}
}
}
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
if (Player* member = ref->GetSource())
{
if (member != master && botAI->IsHeal(member))
{
roster.insert(roster.begin() + roster.size() / 2, member);
}
}
}
bool left = true;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
if (Player* member = ref->GetSource())
{
if (member != master && botAI->IsTank(member))
{
if (left)
roster.push_back(member);
else
roster.insert(roster.begin(), member);
left = !left;
}
}
}
for (Player* playerRoster : roster)
{
if (playerRoster == bot)
break;
++index;
}
total = roster.size() + 1;
}
float start = (master ? master->GetOrientation() : 0.0f);
return start + (0.125f + 1.75f * index / total + (total == 2 ? 0.125f : 0.0f)) * M_PI;
}
FormationValue::FormationValue(PlayerbotAI* botAI)
: ManualSetValue<Formation*>(botAI, new NearFormation(botAI), "formation")
{
}
FormationValue::~FormationValue()
{
if (value)
{
delete value;
value = nullptr;
}
}
std::string const FormationValue::Save() { return value ? value->getName() : "?"; }
bool FormationValue::Load(std::string const formation)
{
if (formation == "melee")
{
if (value)
delete value;
value = new MeleeFormation(botAI);
}
else if (formation == "queue")
{
if (value)
delete value;
value = new QueueFormation(botAI);
}
else if (formation == "chaos")
{
if (value)
delete value;
value = new ChaosFormation(botAI);
}
else if (formation == "circle")
{
if (value)
delete value;
value = new CircleFormation(botAI);
}
else if (formation == "line")
{
if (value)
delete value;
value = new LineFormation(botAI);
}
else if (formation == "shield")
{
if (value)
delete value;
value = new ShieldFormation(botAI);
}
else if (formation == "arrow")
{
if (value)
delete value;
value = new ArrowFormation(botAI);
}
else if (formation == "near" || formation == "default")
{
if (value)
delete value;
value = new NearFormation(botAI);
}
else if (formation == "far")
{
if (value)
delete value;
value = new FarFormation(botAI);
}
else
return false;
return true;
}
bool SetFormationAction::Execute(Event event)
{
std::string const formation = event.getParam();
FormationValue* value = (FormationValue*)context->GetValue<Formation*>("formation");
if (formation == "?" || formation.empty())
{
std::ostringstream str;
str << "Formation: |cff00ff00" << value->Get()->getName();
botAI->TellMaster(str);
return true;
}
if (formation == "show")
{
WorldLocation loc = value->Get()->GetLocation();
if (!Formation::IsNullLocation(loc))
botAI->Ping(loc.GetPositionX(), loc.GetPositionY());
return true;
}
if (!value->Load(formation))
{
std::ostringstream str;
str << "Invalid formation: |cffff0000" << formation;
botAI->TellMaster(str);
botAI->TellMaster(
"Please set to any of:|cffffffff near (default), queue, chaos, circle, line, shield, arrow, melee, far");
return false;
}
std::ostringstream str;
str << "Formation set to: " << formation;
botAI->TellMaster(str);
return true;
}
WorldLocation MoveFormation::MoveLine(std::vector<Player*> line, float diff, float cx, float cy, float cz,
float orientation, float range)
{
if (line.size() < 5)
{
return MoveSingleLine(line, diff, cx, cy, cz, orientation, range);
}
uint32 lines = ceil((double)line.size() / 5.0);
for (uint32 i = 0; i < lines; i++)
{
float radius = range * i;
float x = cx + cos(orientation) * radius;
float y = cy + sin(orientation) * radius;
std::vector<Player*> singleLine;
for (uint32 j = 0; j < 5 && !line.empty(); j++)
{
singleLine.push_back(line[line.size() - 1]);
line.pop_back();
}
WorldLocation loc = MoveSingleLine(singleLine, diff, x, y, cz, orientation, range);
if (!Formation::IsNullLocation(loc))
return loc;
}
return Formation::NullLocation;
}
WorldLocation MoveFormation::MoveSingleLine(std::vector<Player*> line, float diff, float cx, float cy, float cz,
float orientation, float range)
{
float count = line.size();
float angle = orientation - M_PI / 2.0f;
float x = cx + cos(angle) * (range * floor(count / 2.0f) + diff);
float y = cy + sin(angle) * (range * floor(count / 2.0f) + diff);
uint32 index = 0;
for (Player* member : line)
{
if (member == bot)
{
float angle = orientation + M_PI / 2.0f;
float radius = range * index;
float lx = x + cos(angle) * radius;
float ly = y + sin(angle) * radius;
float lz = cz;
Player* master = botAI->GetMaster();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), lx, ly, lz))
{
lx = x + cos(angle) * radius;
ly = y + sin(angle) * radius;
lz = cz;
}
return WorldLocation(bot->GetMapId(), lx, ly, lz);
}
++index;
}
return Formation::NullLocation;
}