mirror of
https://github.com/mod-playerbots/mod-playerbots
synced 2025-11-29 15:58:20 +08:00
Merge branch 'master' into more-av-fixes
This commit is contained in:
@@ -104,6 +104,12 @@ AiPlayerbot.DeleteRandomBotAccounts = 0
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# Maximum number of bots added by one account
|
||||||
|
AiPlayerbot.MaxAddedBots = 40
|
||||||
|
|
||||||
|
# Maximum number of bots per class added by one account
|
||||||
|
AiPlayerbot.MaxAddedBotsPerClass = 10
|
||||||
|
|
||||||
# Enable/Disable create bot by addclass command (0 = GM only, 1 = enable)
|
# Enable/Disable create bot by addclass command (0 = GM only, 1 = enable)
|
||||||
# default: 1 (enable)
|
# default: 1 (enable)
|
||||||
AiPlayerbot.AddClassCommand = 1
|
AiPlayerbot.AddClassCommand = 1
|
||||||
@@ -331,7 +337,7 @@ AiPlayerbot.MediumMana = 40
|
|||||||
#
|
#
|
||||||
|
|
||||||
# Bots pick their quest reward (yes = picks first useful item, no = list all rewards, ask = pick useful item and lists if multiple)
|
# Bots pick their quest reward (yes = picks first useful item, no = list all rewards, ask = pick useful item and lists if multiple)
|
||||||
AiPlayerbot.AutoPickReward = no
|
AiPlayerbot.AutoPickReward = yes
|
||||||
|
|
||||||
# Sync quests with player (Bots will complete quests the moment you hand them in. Bots will ignore looting quest items.)
|
# Sync quests with player (Bots will complete quests the moment you hand them in. Bots will ignore looting quest items.)
|
||||||
# Default: 1 (enable)
|
# Default: 1 (enable)
|
||||||
@@ -656,7 +662,7 @@ AiPlayerbot.RandomBotArenaTeamCount = 20
|
|||||||
AiPlayerbot.DeleteRandomBotArenaTeams = 0
|
AiPlayerbot.DeleteRandomBotArenaTeams = 0
|
||||||
|
|
||||||
# PvP Restricted Zones (bots don't pvp)
|
# PvP Restricted Zones (bots don't pvp)
|
||||||
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298" # 33(stranglethorn vale),440(tanaris)
|
AiPlayerbot.PvpProhibitedZoneIds = "2255,656,2361,2362,2363,976,35,2268,3425,392,541,1446,3828,3712,3738,3565,3539,3623,4152,3988,4658,4284,4418,4436,4275,4323,4395,3703,4298"
|
||||||
|
|
||||||
# PvP Restricted Areas (bots don't pvp)
|
# PvP Restricted Areas (bots don't pvp)
|
||||||
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392"
|
AiPlayerbot.PvpProhibitedAreaIds = "976,35,392"
|
||||||
|
|||||||
@@ -50,16 +50,17 @@ void PerformanceMonitor::PrintStats(bool perTick, bool fullStack)
|
|||||||
if (data.empty())
|
if (data.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
uint32 total = 0;
|
|
||||||
|
|
||||||
if (!perTick)
|
if (!perTick)
|
||||||
{
|
{
|
||||||
|
float updateAITotalTime = 0;
|
||||||
for (auto& map : data[PERF_MON_TOTAL])
|
for (auto& map : data[PERF_MON_TOTAL])
|
||||||
if (map.first.find("PlayerbotAI::UpdateAIInternal") != std::string::npos)
|
if (map.first.find("PlayerbotAI::UpdateAIInternal") != std::string::npos)
|
||||||
total += map.second->totalTime;
|
updateAITotalTime += map.second->totalTime;
|
||||||
|
|
||||||
LOG_INFO("playerbots", "--------------------------------------[TOTAL BOT]------------------------------------------------------");
|
LOG_INFO("playerbots", "--------------------------------------[TOTAL BOT]------------------------------------------------------");
|
||||||
LOG_INFO("playerbots", "percentage time | min .. max ( avg of count) - type : name");
|
LOG_INFO("playerbots", "percentage time | min .. max ( avg of count) - type : name");
|
||||||
|
LOG_INFO("playerbots", "-------------------------------------------------------------------------------------------------------");
|
||||||
|
|
||||||
for (std::map<PerformanceMetric, std::map<std::string, PerformanceData*>>::iterator i = data.begin(); i != data.end(); ++i)
|
for (std::map<PerformanceMetric, std::map<std::string, PerformanceData*>>::iterator i = data.begin(); i != data.end(); ++i)
|
||||||
{
|
{
|
||||||
@@ -103,39 +104,66 @@ void PerformanceMonitor::PrintStats(bool perTick, bool fullStack)
|
|||||||
return pdMap.at(i)->totalTime < pdMap.at(j)->totalTime;
|
return pdMap.at(i)->totalTime < pdMap.at(j)->totalTime;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
uint64 typeTotalTime = 0;
|
||||||
|
uint64 typeMinTime = 0xffffffffu;
|
||||||
|
uint64 typeMaxTime = 0;
|
||||||
|
uint32 typeCount = 0;
|
||||||
for (auto& name : names)
|
for (auto& name : names)
|
||||||
{
|
{
|
||||||
PerformanceData* pd = pdMap[name];
|
PerformanceData* pd = pdMap[name];
|
||||||
float perc = (float)pd->totalTime / (float)total * 100.0f;
|
typeTotalTime += pd->totalTime;
|
||||||
float secs = (float)pd->totalTime / 1000.0f;
|
typeCount += pd->count;
|
||||||
float avg = (float)pd->totalTime / (float)pd->count;
|
if (typeMinTime > pd->minTime)
|
||||||
|
typeMinTime = pd->minTime;
|
||||||
|
if (typeMaxTime < pd->maxTime)
|
||||||
|
typeMaxTime = pd->maxTime;
|
||||||
|
float perc = (float)pd->totalTime / updateAITotalTime * 100.0f;
|
||||||
|
float time = (float)pd->totalTime / 1000000.0f;
|
||||||
|
float minTime = (float)pd->minTime / 1000.0f;
|
||||||
|
float maxTime = (float)pd->maxTime / 1000.0f;
|
||||||
|
float avg = (float)pd->totalTime / (float)pd->count / 1000.0f;
|
||||||
std::string disName = name;
|
std::string disName = name;
|
||||||
if (!fullStack && disName.find("|") != std::string::npos)
|
if (!fullStack && disName.find("|") != std::string::npos)
|
||||||
disName = disName.substr(0, disName.find("|")) + "]";
|
disName = disName.substr(0, disName.find("|")) + "]";
|
||||||
|
|
||||||
if (avg >= 0.5f || pd->maxTime > 10)
|
if (perc >= 0.1f || avg >= 0.25f || pd->maxTime > 1000)
|
||||||
{
|
{
|
||||||
LOG_INFO("playerbots", "{:7.3f}% {:10.3f}s | {:6d} .. {:6d} ({:10.3f} of {:10d}) - {:6} : {}"
|
LOG_INFO("playerbots", "{:7.3f}% {:10.3f}s | {:7.1f} .. {:7.1f} ({:10.3f} of {:10d}) - {:6} : {}"
|
||||||
, perc
|
, perc
|
||||||
, secs
|
, time
|
||||||
, pd->minTime
|
, minTime
|
||||||
, pd->maxTime
|
, maxTime
|
||||||
, avg
|
, avg
|
||||||
, pd->count
|
, pd->count
|
||||||
, key.c_str()
|
, key.c_str()
|
||||||
, disName.c_str());
|
, disName.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
float tPerc = (float)typeTotalTime / (float)updateAITotalTime * 100.0f;
|
||||||
|
float tTime = (float)typeTotalTime / 1000000.0f;
|
||||||
|
float tMinTime = (float)typeMinTime / 1000.0f;
|
||||||
|
float tMaxTime = (float)typeMaxTime / 1000.0f;
|
||||||
|
float tAvg = (float)typeTotalTime / (float)typeCount / 1000.0f;
|
||||||
|
LOG_INFO("playerbots", "{:7.3f}% {:10.3f}s | {:7.1f} .. {:7.1f} ({:10.3f} of {:10d}) - {:6} : {}"
|
||||||
|
, tPerc
|
||||||
|
, tTime
|
||||||
|
, tMinTime
|
||||||
|
, tMaxTime
|
||||||
|
, tAvg
|
||||||
|
, typeCount
|
||||||
|
, key.c_str()
|
||||||
|
, "Total");
|
||||||
LOG_INFO("playerbots", " ");
|
LOG_INFO("playerbots", " ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float totalCount = data[PERF_MON_TOTAL]["RandomPlayerbotMgr::FullTick"]->count;
|
float fullTickCount = data[PERF_MON_TOTAL]["RandomPlayerbotMgr::FullTick"]->count;
|
||||||
total = data[PERF_MON_TOTAL]["RandomPlayerbotMgr::FullTick"]->totalTime;
|
float fullTickTotalTime = data[PERF_MON_TOTAL]["RandomPlayerbotMgr::FullTick"]->totalTime;
|
||||||
|
|
||||||
LOG_INFO("playerbots", "---------------------------------------[PER TICK]------------------------------------------------------");
|
LOG_INFO("playerbots", "---------------------------------------[PER TICK]------------------------------------------------------");
|
||||||
LOG_INFO("playerbots", "percentage time | min .. max ( avg of count) - type : name");
|
LOG_INFO("playerbots", "percentage time | min .. max ( avg of count) - type : name");
|
||||||
|
LOG_INFO("playerbots", "-------------------------------------------------------------------------------------------------------");
|
||||||
|
|
||||||
for (std::map<PerformanceMetric, std::map<std::string, PerformanceData*>>::iterator i = data.begin(); i != data.end(); ++i)
|
for (std::map<PerformanceMetric, std::map<std::string, PerformanceData*>>::iterator i = data.begin(); i != data.end(); ++i)
|
||||||
{
|
{
|
||||||
@@ -175,30 +203,59 @@ void PerformanceMonitor::PrintStats(bool perTick, bool fullStack)
|
|||||||
return pdMap.at(i)->totalTime < pdMap.at(j)->totalTime;
|
return pdMap.at(i)->totalTime < pdMap.at(j)->totalTime;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
uint64 typeTotalTime = 0;
|
||||||
|
uint64 typeMinTime = 0xffffffffu;
|
||||||
|
uint64 typeMaxTime = 0;
|
||||||
|
uint32 typeCount = 0;
|
||||||
for (auto& name : names)
|
for (auto& name : names)
|
||||||
{
|
{
|
||||||
PerformanceData* pd = pdMap[name];
|
PerformanceData* pd = pdMap[name];
|
||||||
float perc = (float)pd->totalTime / (float)total * 100.0f;
|
typeTotalTime += pd->totalTime;
|
||||||
uint32 secs = pd->totalTime / totalCount;
|
typeCount += pd->count;
|
||||||
float avg = (float)pd->totalTime / (float)pd->count;
|
if (typeMinTime > pd->minTime)
|
||||||
float amount = (float)pd->count / (float)totalCount;
|
typeMinTime = pd->minTime;
|
||||||
|
if (typeMaxTime < pd->maxTime)
|
||||||
|
typeMaxTime = pd->maxTime;
|
||||||
|
float perc = (float)pd->totalTime / fullTickTotalTime * 100.0f;
|
||||||
|
float time = (float)pd->totalTime / fullTickCount / 1000.0f;
|
||||||
|
float minTime = (float)pd->minTime / 1000.0f;
|
||||||
|
float maxTime = (float)pd->maxTime / 1000.0f;
|
||||||
|
float avg = (float)pd->totalTime / (float)pd->count / 1000.0f;
|
||||||
|
float amount = (float)pd->count / fullTickCount;
|
||||||
std::string disName = name;
|
std::string disName = name;
|
||||||
if (!fullStack && disName.find("|") != std::string::npos)
|
if (!fullStack && disName.find("|") != std::string::npos)
|
||||||
disName = disName.substr(0, disName.find("|")) + "]";
|
disName = disName.substr(0, disName.find("|")) + "]";
|
||||||
|
if (perc >= 0.1f || avg >= 0.25f || pd->maxTime > 1000)
|
||||||
if (avg >= 0.5f || pd->maxTime > 10)
|
|
||||||
{
|
{
|
||||||
LOG_INFO("playerbots", "{:7.3f}% {:9d}ms | {:6d} .. {:6d} ({:10.3f} of {:10.3f}) - {:6} : {}"
|
LOG_INFO("playerbots", "{:7.3f}% {:9.3f}ms | {:7.1f} .. {:7.1f} ({:10.3f} of {:10.2f}) - {:6} : {}"
|
||||||
, perc
|
, perc
|
||||||
, secs
|
, time
|
||||||
, pd->minTime
|
, minTime
|
||||||
, pd->maxTime
|
, maxTime
|
||||||
, avg
|
, avg
|
||||||
, amount
|
, amount
|
||||||
, key.c_str()
|
, key.c_str()
|
||||||
, disName.c_str());
|
, disName.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (PERF_MON_TOTAL != i->first)
|
||||||
|
{
|
||||||
|
float tPerc = (float)typeTotalTime / (float)fullTickTotalTime * 100.0f;
|
||||||
|
float tTime = (float)typeTotalTime / fullTickCount / 1000.0f;
|
||||||
|
float tMinTime = (float)typeMinTime / 1000.0f;
|
||||||
|
float tMaxTime = (float)typeMaxTime / 1000.0f;
|
||||||
|
float tAvg = (float)typeTotalTime / (float)typeCount / 1000.0f;
|
||||||
|
float tAmount = (float)typeCount / fullTickCount;
|
||||||
|
LOG_INFO("playerbots", "{:7.3f}% {:9.3f}ms | {:7.1f} .. {:7.1f} ({:10.3f} of {:10.2f}) - {:6} : {}"
|
||||||
|
, tPerc
|
||||||
|
, tTime
|
||||||
|
, tMinTime
|
||||||
|
, tMaxTime
|
||||||
|
, tAvg
|
||||||
|
, tAmount
|
||||||
|
, key.c_str()
|
||||||
|
, "Total");
|
||||||
|
}
|
||||||
LOG_INFO("playerbots", " ");
|
LOG_INFO("playerbots", " ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,13 +280,13 @@ void PerformanceMonitor::Reset()
|
|||||||
|
|
||||||
PerformanceMonitorOperation::PerformanceMonitorOperation(PerformanceData* data, std::string const name, PerformanceStack* stack) : data(data), name(name), stack(stack)
|
PerformanceMonitorOperation::PerformanceMonitorOperation(PerformanceData* data, std::string const name, PerformanceStack* stack) : data(data), name(name), stack(stack)
|
||||||
{
|
{
|
||||||
started = (std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now())).time_since_epoch();
|
started = (std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now())).time_since_epoch();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PerformanceMonitorOperation::finish()
|
void PerformanceMonitorOperation::finish()
|
||||||
{
|
{
|
||||||
std::chrono::milliseconds finished = (std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now())).time_since_epoch();
|
std::chrono::microseconds finished = (std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now())).time_since_epoch();
|
||||||
uint32 elapsed = (finished - started).count();
|
uint64_t elapsed = (finished - started).count();
|
||||||
|
|
||||||
std::lock_guard<std::mutex> guard(data->lock);
|
std::lock_guard<std::mutex> guard(data->lock);
|
||||||
if (elapsed > 0)
|
if (elapsed > 0)
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ typedef std::vector<std::string> PerformanceStack;
|
|||||||
|
|
||||||
struct PerformanceData
|
struct PerformanceData
|
||||||
{
|
{
|
||||||
uint32 minTime;
|
uint64 minTime;
|
||||||
uint32 maxTime;
|
uint64 maxTime;
|
||||||
uint32 totalTime;
|
uint64 totalTime;
|
||||||
uint32 count;
|
uint32 count;
|
||||||
std::mutex lock;
|
std::mutex lock;
|
||||||
};
|
};
|
||||||
@@ -43,7 +43,7 @@ class PerformanceMonitorOperation
|
|||||||
PerformanceData* data;
|
PerformanceData* data;
|
||||||
std::string const name;
|
std::string const name;
|
||||||
PerformanceStack* stack;
|
PerformanceStack* stack;
|
||||||
std::chrono::milliseconds started;
|
std::chrono::microseconds started;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PerformanceMonitor
|
class PerformanceMonitor
|
||||||
|
|||||||
@@ -450,22 +450,28 @@ void PlayerbotAI::HandleTeleportAck()
|
|||||||
|
|
||||||
bot->GetMotionMaster()->Clear(true);
|
bot->GetMotionMaster()->Clear(true);
|
||||||
bot->StopMoving();
|
bot->StopMoving();
|
||||||
if (bot->IsBeingTeleportedNear())
|
if (bot->IsBeingTeleportedNear()) {
|
||||||
{
|
// Temporary fix for instance can not enter
|
||||||
WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 8 + 4 + 4);
|
if (!bot->IsInWorld()) {
|
||||||
p << bot->GetGUID().WriteAsPacked();
|
bot->GetMap()->AddPlayerToMap(bot);
|
||||||
|
}
|
||||||
|
while (bot->IsInWorld() && bot->IsBeingTeleportedNear()) {
|
||||||
|
Player* plMover = bot->m_mover->ToPlayer();
|
||||||
|
if (!plMover)
|
||||||
|
return;
|
||||||
|
WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 20);
|
||||||
|
p << plMover->GetPackGUID();
|
||||||
p << (uint32) 0; // supposed to be flags? not used currently
|
p << (uint32) 0; // supposed to be flags? not used currently
|
||||||
p << (uint32) time(nullptr); // time - not currently used
|
p << (uint32) 0; // time - not currently used
|
||||||
bot->GetSession()->HandleMoveTeleportAck(p);
|
bot->GetSession()->HandleMoveTeleportAck(p);
|
||||||
|
}
|
||||||
// add delay to simulate teleport delay
|
|
||||||
SetNextCheckDelay(urand(1000, 3000));
|
SetNextCheckDelay(urand(1000, 3000));
|
||||||
}
|
}
|
||||||
else if (bot->IsBeingTeleportedFar())
|
if (bot->IsBeingTeleportedFar())
|
||||||
{
|
{
|
||||||
|
while (bot->IsBeingTeleportedFar()) {
|
||||||
bot->GetSession()->HandleMoveWorldportAck();
|
bot->GetSession()->HandleMoveWorldportAck();
|
||||||
|
}
|
||||||
// add delay to simulate teleport delay
|
|
||||||
SetNextCheckDelay(urand(2000, 5000));
|
SetNextCheckDelay(urand(2000, 5000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -283,6 +283,9 @@ bool PlayerbotAIConfig::Initialize()
|
|||||||
botRepairWhenSummon = sConfigMgr->GetOption<bool>("AiPlayerbot.BotRepairWhenSummon", true);
|
botRepairWhenSummon = sConfigMgr->GetOption<bool>("AiPlayerbot.BotRepairWhenSummon", true);
|
||||||
autoInitOnly = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoInitOnly", false);
|
autoInitOnly = sConfigMgr->GetOption<bool>("AiPlayerbot.AutoInitOnly", false);
|
||||||
autoInitEquipLevelLimitRatio = sConfigMgr->GetOption<float>("AiPlayerbot.AutoInitEquipLevelLimitRatio", 1.0);
|
autoInitEquipLevelLimitRatio = sConfigMgr->GetOption<float>("AiPlayerbot.AutoInitEquipLevelLimitRatio", 1.0);
|
||||||
|
|
||||||
|
maxAddedBots = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxAddedBots", 40);
|
||||||
|
maxAddedBotsPerClass = sConfigMgr->GetOption<int32>("AiPlayerbot.MaxAddedBotsPerClass", 10);
|
||||||
addClassCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AddClassCommand", 1);
|
addClassCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AddClassCommand", 1);
|
||||||
maintenanceCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.MaintenanceCommand", 1);
|
maintenanceCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.MaintenanceCommand", 1);
|
||||||
autoGearCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearCommand", 1);
|
autoGearCommand = sConfigMgr->GetOption<int32>("AiPlayerbot.AutoGearCommand", 1);
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ class PlayerbotAIConfig
|
|||||||
bool botRepairWhenSummon;
|
bool botRepairWhenSummon;
|
||||||
bool autoInitOnly;
|
bool autoInitOnly;
|
||||||
float autoInitEquipLevelLimitRatio;
|
float autoInitEquipLevelLimitRatio;
|
||||||
|
int32 maxAddedBots, maxAddedBotsPerClass;
|
||||||
int32 addClassCommand;
|
int32 addClassCommand;
|
||||||
int32 maintenanceCommand;
|
int32 maintenanceCommand;
|
||||||
int32 autoGearCommand, autoGearQualityLimit, autoGearScoreLimit;
|
int32 autoGearCommand, autoGearQualityLimit, autoGearScoreLimit;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include "CharacterPackets.h"
|
#include "CharacterPackets.h"
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "Define.h"
|
#include "Define.h"
|
||||||
|
#include "Group.h"
|
||||||
|
#include "GroupMgr.h"
|
||||||
#include "ObjectAccessor.h"
|
#include "ObjectAccessor.h"
|
||||||
#include "ObjectMgr.h"
|
#include "ObjectMgr.h"
|
||||||
#include "PlayerbotAIConfig.h"
|
#include "PlayerbotAIConfig.h"
|
||||||
@@ -19,6 +21,7 @@
|
|||||||
#include "ChannelMgr.h"
|
#include "ChannelMgr.h"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <istream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false)
|
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false)
|
||||||
@@ -89,25 +92,42 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
|||||||
Player* bot = botSession->GetPlayer();
|
Player* bot = botSession->GetPlayer();
|
||||||
if (!bot)
|
if (!bot)
|
||||||
{
|
{
|
||||||
LogoutPlayerBot(holder.GetGuid());
|
botSession->LogoutPlayer(true);
|
||||||
|
delete botSession;
|
||||||
// LOG_ERROR("playerbots", "Error logging in bot {}, please try to reset all random bots", holder.GetGuid().ToString().c_str());
|
// LOG_ERROR("playerbots", "Error logging in bot {}, please try to reset all random bots", holder.GetGuid().ToString().c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sRandomPlayerbotMgr->OnPlayerLogin(bot);
|
|
||||||
|
|
||||||
uint32 masterAccount = holder.GetMasterAccountId();
|
uint32 masterAccount = holder.GetMasterAccountId();
|
||||||
WorldSession* masterSession = masterAccount ? sWorld->FindSession(masterAccount) : nullptr;
|
WorldSession* masterSession = masterAccount ? sWorld->FindSession(masterAccount) : nullptr;
|
||||||
|
std::ostringstream out;
|
||||||
bool allowed = false;
|
bool allowed = false;
|
||||||
if (botAccountId == masterAccount)
|
if (botAccountId == masterAccount) {
|
||||||
allowed = true;
|
allowed = true;
|
||||||
else if (masterSession && sPlayerbotAIConfig->allowGuildBots && bot->GetGuildId() != 0 && bot->GetGuildId() == masterSession->GetPlayer()->GetGuildId())
|
} else if (masterSession && sPlayerbotAIConfig->allowGuildBots && bot->GetGuildId() != 0 && bot->GetGuildId() == masterSession->GetPlayer()->GetGuildId()) {
|
||||||
allowed = true;
|
allowed = true;
|
||||||
else if (sPlayerbotAIConfig->IsInRandomAccountList(botAccountId))
|
} else if (sPlayerbotAIConfig->IsInRandomAccountList(botAccountId)) {
|
||||||
allowed = true;
|
allowed = true;
|
||||||
|
} else {
|
||||||
|
allowed = false;
|
||||||
|
out << "Failure: You are not allowed to control bot " << bot->GetName().c_str();
|
||||||
|
}
|
||||||
|
if (allowed && masterSession) {
|
||||||
|
Player* player = masterSession->GetPlayer();
|
||||||
|
PlayerbotMgr *mgr = GET_PLAYERBOT_MGR(player);
|
||||||
|
uint32 count = mgr->GetPlayerbotsCount();
|
||||||
|
uint32 cls_count = mgr->GetPlayerbotsCountByClass(bot->getClass());
|
||||||
|
if (count >= sPlayerbotAIConfig->maxAddedBots) {
|
||||||
|
allowed = false;
|
||||||
|
out << "Failure: You have added too many bots";
|
||||||
|
} else if (cls_count >= sPlayerbotAIConfig->maxAddedBotsPerClass) {
|
||||||
|
allowed = false;
|
||||||
|
out << "Failure: You have added too many bots for this class";
|
||||||
|
}
|
||||||
|
}
|
||||||
if (allowed)
|
if (allowed)
|
||||||
{
|
{
|
||||||
|
sRandomPlayerbotMgr->OnPlayerLogin(bot);
|
||||||
OnBotLogin(bot);
|
OnBotLogin(bot);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -115,10 +135,12 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
|
|||||||
if (masterSession)
|
if (masterSession)
|
||||||
{
|
{
|
||||||
ChatHandler ch(masterSession);
|
ChatHandler ch(masterSession);
|
||||||
ch.PSendSysMessage("You are not allowed to control bot %s", bot->GetName());
|
ch.SendSysMessage(out.str());
|
||||||
}
|
}
|
||||||
OnBotLogin(bot);
|
botSession->LogoutPlayer(true);
|
||||||
LogoutPlayerBot(bot->GetGUID());
|
delete botSession;
|
||||||
|
// OnBotLogin(bot);
|
||||||
|
// LogoutPlayerBot(bot->GetGUID());
|
||||||
|
|
||||||
// LOG_ERROR("playerbots", "Attempt to add not allowed bot {}, please try to reset all random bots", bot->GetName());
|
// LOG_ERROR("playerbots", "Attempt to add not allowed bot {}, please try to reset all random bots", bot->GetName());
|
||||||
}
|
}
|
||||||
@@ -453,7 +475,22 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
|
|||||||
botAI->TellMaster("Hello!", PLAYERBOT_SECURITY_TALK);
|
botAI->TellMaster("Hello!", PLAYERBOT_SECURITY_TALK);
|
||||||
|
|
||||||
if (master && master->GetGroup() && !group) {
|
if (master && master->GetGroup() && !group) {
|
||||||
master->GetGroup()->AddMember(bot);
|
Group* mgroup = master->GetGroup();
|
||||||
|
if (mgroup->GetMembersCount() >= 5) {
|
||||||
|
if (!mgroup->isRaidGroup() && !mgroup->isLFGGroup() && !mgroup->isBGGroup() && !mgroup->isBFGroup()) {
|
||||||
|
mgroup->ConvertToRaid();
|
||||||
|
}
|
||||||
|
if (mgroup->isRaidGroup()) {
|
||||||
|
mgroup->AddMember(bot);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mgroup->AddMember(bot);
|
||||||
|
}
|
||||||
|
} else if (master && !group) {
|
||||||
|
Group* newGroup = new Group();
|
||||||
|
newGroup->Create(master);
|
||||||
|
sGroupMgr->AddGroup(newGroup);
|
||||||
|
newGroup->AddMember(bot);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32 accountId = bot->GetSession()->GetAccountId();
|
uint32 accountId = bot->GetSession()->GetAccountId();
|
||||||
@@ -1157,6 +1194,19 @@ std::string const PlayerbotHolder::LookupBots(Player* master)
|
|||||||
return ret_msg;
|
return ret_msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls)
|
||||||
|
{
|
||||||
|
uint32 count = 0;
|
||||||
|
for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it)
|
||||||
|
{
|
||||||
|
Player* const bot = it->second;
|
||||||
|
if (bot->getClass() == cls) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
PlayerbotMgr::PlayerbotMgr(Player* const master) : PlayerbotHolder(), master(master), lastErrorTell(0)
|
PlayerbotMgr::PlayerbotMgr(Player* const master) : PlayerbotHolder(), master(master), lastErrorTell(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ class PlayerbotHolder : public PlayerbotAIBase
|
|||||||
uint32 GetAccountId(ObjectGuid guid);
|
uint32 GetAccountId(ObjectGuid guid);
|
||||||
std::string const ListBots(Player* master);
|
std::string const ListBots(Player* master);
|
||||||
std::string const LookupBots(Player* master);
|
std::string const LookupBots(Player* master);
|
||||||
|
uint32 GetPlayerbotsCount() { return playerBots.size(); }
|
||||||
|
uint32 GetPlayerbotsCountByClass(uint32 cls);
|
||||||
protected:
|
protected:
|
||||||
virtual void OnBotLoginInternal(Player* const bot) = 0;
|
virtual void OnBotLoginInternal(Player* const bot) = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -235,7 +235,80 @@ std::string const RandomPlayerbotFactory::CreateRandomBotName(uint8 gender)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOG_ERROR("playerbots", "No more names left for random bots. Simply random.");
|
|
||||||
|
//CONLANG NAME GENERATION
|
||||||
|
LOG_ERROR("playerbots", "No more names left for random bots. Attempting conlang name generation.");
|
||||||
|
const std::string groupCategory = "SCVKRU";
|
||||||
|
const std::string groupFormStart[2][4] = {
|
||||||
|
{"SV","SV","VK","RV"},
|
||||||
|
{"V" ,"SU","VS","RV"}
|
||||||
|
};
|
||||||
|
const std::string groupFormMid[2][6] = {
|
||||||
|
{"CV","CVC","CVC","CVK","VC","VK"},
|
||||||
|
{"CV","CVC","CVK","KVC","VC","KV"}
|
||||||
|
};
|
||||||
|
const std::string groupFormEnd[2][4] = {
|
||||||
|
{"CV","VC","VK","CV"},
|
||||||
|
{"RU","UR","VR","V" }
|
||||||
|
};
|
||||||
|
const std::string groupLetter[2][6] = {
|
||||||
|
//S C V K R U
|
||||||
|
{"dtspkThfS","bcCdfghjkmnNqqrrlsStTvwxyz","aaeeiouA" ,"ppttkkbdg","lmmnrr" ,"AEO" },
|
||||||
|
{"dtskThfS" ,"bcCdfghjkmmnNqrrlssStTvwyz","aaaeeiiuAAEIO","ppttkbbdg","lmmnrrr","AEOy"}
|
||||||
|
};
|
||||||
|
const std::string replaceRule[2][17] = {
|
||||||
|
{"ST" ,"ka","ko","ku","kr","S" ,"T" ,"C" ,"N" ,"jj","AA","AI" ,"A" ,"E" ,"O" ,"I" ,"aa"},
|
||||||
|
{"sth","ca","co","cu","cr","sh","th","ch","ng","dg","A" ,"ayu","ai","ei","ou","iu","ae"}
|
||||||
|
};
|
||||||
|
|
||||||
|
tries = 10;
|
||||||
|
while (--tries)
|
||||||
|
{
|
||||||
|
botName.clear();
|
||||||
|
//Build name from groupForms
|
||||||
|
//Pick random start group
|
||||||
|
botName = groupFormStart[gender][rand()%4];
|
||||||
|
//Pick up to 2 and then up to 1 additional middle group
|
||||||
|
for (int i = 0; i < rand()%3 + rand()%2; i++)
|
||||||
|
{
|
||||||
|
botName += groupFormMid[gender][rand()%6];
|
||||||
|
}
|
||||||
|
//Pick up to 1 end group
|
||||||
|
botName += rand()%2 ? groupFormEnd[gender][rand()%4] : "";
|
||||||
|
//If name is single letter add random end group
|
||||||
|
botName += (botName.size() < 2) ? groupFormEnd[gender][rand()%4] : "";
|
||||||
|
|
||||||
|
//Replace Catagory value with random Letter from that Catagory's Letter string for a given bot gender
|
||||||
|
for (int i=0; i < botName.size(); i++)
|
||||||
|
{
|
||||||
|
botName[i] = groupLetter[gender][groupCategory.find(botName[i])][rand()%groupLetter[gender][groupCategory.find(botName[i])].size()];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Itterate over replace rules
|
||||||
|
for (int i = 0; i < 17; i++)
|
||||||
|
{
|
||||||
|
int j = botName.find(replaceRule[0][i]);
|
||||||
|
while ( j > -1)
|
||||||
|
{
|
||||||
|
botName.replace(j,replaceRule[0][i].size(),replaceRule[1][i]);
|
||||||
|
j = botName.find(replaceRule[0][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Capitalize first letter
|
||||||
|
botName[0] -= 32;
|
||||||
|
|
||||||
|
if (ObjectMgr::CheckPlayerName(botName) != CHAR_NAME_SUCCESS ||
|
||||||
|
(sObjectMgr->IsReservedName(botName) || sObjectMgr->IsProfanityName(botName)))
|
||||||
|
{
|
||||||
|
botName.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return std::move(botName);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TRUE RANDOM NAME GENERATION
|
||||||
|
LOG_ERROR("playerbots", "Conlang name generation failed. True random name fallback.");
|
||||||
tries = 10;
|
tries = 10;
|
||||||
while(--tries) {
|
while(--tries) {
|
||||||
for (uint8 i = 0; i < 10; i++) {
|
for (uint8 i = 0; i < 10; i++) {
|
||||||
|
|||||||
@@ -95,6 +95,16 @@ bool AttackAction::Attack(Unit* target, bool with_pet /*true*/)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (target->IsPlayer() && !target->IsPvP() && !target->IsFFAPvP() && (!bot->duel || bot->duel->Opponent != target || bot->duel->StartTime))
|
||||||
|
{
|
||||||
|
if (verbose)
|
||||||
|
{
|
||||||
|
botAI->TellError(Acore::StringFormat("%s is not flagged for pvp", target->GetName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (bot->IsMounted() && bot->IsWithinLOSInMap(target))
|
if (bot->IsMounted() && bot->IsWithinLOSInMap(target))
|
||||||
{
|
{
|
||||||
WorldPacket emptyPacket;
|
WorldPacket emptyPacket;
|
||||||
|
|||||||
@@ -9,14 +9,14 @@
|
|||||||
#include "Playerbots.h"
|
#include "Playerbots.h"
|
||||||
#include "ServerFacade.h"
|
#include "ServerFacade.h"
|
||||||
|
|
||||||
uint32 FindLastSeparator(std::string const text, std::string const sep)
|
size_t FindLastSeparator(std::string const text, std::string const sep)
|
||||||
{
|
{
|
||||||
size_t pos = text.rfind(sep);
|
size_t pos = text.rfind(sep);
|
||||||
if (pos == std::string::npos)
|
if (pos == std::string::npos)
|
||||||
return pos;
|
return pos;
|
||||||
|
|
||||||
uint32 lastLinkBegin = text.rfind("|H");
|
size_t lastLinkBegin = text.rfind("|H");
|
||||||
uint32 lastLinkEnd = text.find("|h|r", lastLinkBegin + 1);
|
size_t lastLinkEnd = text.find("|h|r", lastLinkBegin + 1);
|
||||||
if (pos >= lastLinkBegin && pos <= lastLinkEnd)
|
if (pos >= lastLinkBegin && pos <= lastLinkEnd)
|
||||||
pos = text.find_last_of(sep, lastLinkBegin);
|
pos = text.find_last_of(sep, lastLinkBegin);
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,8 @@ std::string const QueryItemUsageAction::QueryItemUsage(ItemTemplate const* item)
|
|||||||
return "Auctionhouse";
|
return "Auctionhouse";
|
||||||
case ITEM_USAGE_AMMO:
|
case ITEM_USAGE_AMMO:
|
||||||
return "Ammunition";
|
return "Ammunition";
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -588,13 +588,14 @@ void ChatReplyAction::ChatReplyDo(Player* bot, uint32 type, uint32 guid1, uint32
|
|||||||
if (ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId()))
|
if (ChannelMgr* cMgr = ChannelMgr::forTeam(bot->GetTeamId()))
|
||||||
{
|
{
|
||||||
std::string worldChan = "World";
|
std::string worldChan = "World";
|
||||||
if (Channel* chn = cMgr->GetJoinChannel(worldChan.c_str(), 0))
|
if (Channel* chn = cMgr->GetJoinChannel(worldChan.c_str(), 0)) {
|
||||||
if (bot->GetTeamId() == TEAM_ALLIANCE)
|
if (bot->GetTeamId() == TEAM_ALLIANCE)
|
||||||
chn->Say(bot->GetGUID(), c, LANG_COMMON);
|
chn->Say(bot->GetGUID(), c, LANG_COMMON);
|
||||||
else
|
else
|
||||||
chn->Say(bot->GetGUID(), c, LANG_ORCISH);
|
chn->Say(bot->GetGUID(), c, LANG_ORCISH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (type == CHAT_MSG_WHISPER)
|
if (type == CHAT_MSG_WHISPER)
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ void TrainerAction::TellFooter(uint32 totalCost)
|
|||||||
bool MaintenanceAction::Execute(Event event)
|
bool MaintenanceAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
if (!sPlayerbotAIConfig->maintenanceCommand) {
|
if (!sPlayerbotAIConfig->maintenanceCommand) {
|
||||||
botAI->TellMaster("maintenance command is not allowed, please check the configuration.");
|
botAI->TellError("maintenance command is not allowed, please check the configuration.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
botAI->TellMaster("I'm maintaining");
|
botAI->TellMaster("I'm maintaining");
|
||||||
@@ -187,7 +187,7 @@ bool RemoveGlyphAction::Execute(Event event)
|
|||||||
bool AutoGearAction::Execute(Event event)
|
bool AutoGearAction::Execute(Event event)
|
||||||
{
|
{
|
||||||
if (!sPlayerbotAIConfig->autoGearCommand) {
|
if (!sPlayerbotAIConfig->autoGearCommand) {
|
||||||
botAI->TellMaster("autogear command is not allowed, please check the configuration.");
|
botAI->TellError("autogear command is not allowed, please check the configuration.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
botAI->TellMaster("I'm auto gearing");
|
botAI->TellMaster("I'm auto gearing");
|
||||||
|
|||||||
@@ -164,6 +164,15 @@ bool SummonAction::SummonUsingNpcs(Player* summoner, Player* player)
|
|||||||
bool SummonAction::Teleport(Player* summoner, Player* player)
|
bool SummonAction::Teleport(Player* summoner, Player* player)
|
||||||
{
|
{
|
||||||
Player* master = GetMaster();
|
Player* master = GetMaster();
|
||||||
|
if (master->GetMap() && master->GetMap()->IsDungeon()) {
|
||||||
|
InstanceMap* map = master->GetMap()->ToInstanceMap();
|
||||||
|
if (map) {
|
||||||
|
if (map->CannotEnter(player) == Map::CANNOT_ENTER_MAX_PLAYERS) {
|
||||||
|
botAI->TellError("I can not enter this dungeon");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!summoner->IsBeingTeleported() && !player->IsBeingTeleported())
|
if (!summoner->IsBeingTeleported() && !player->IsBeingTeleported())
|
||||||
{
|
{
|
||||||
float followAngle = GetFollowAngle();
|
float followAngle = GetFollowAngle();
|
||||||
|
|||||||
@@ -51,5 +51,5 @@ void GenericRogueNonCombatStrategy::InitTriggers(std::vector<TriggerNode*>& trig
|
|||||||
|
|
||||||
triggers.push_back(new TriggerNode(
|
triggers.push_back(new TriggerNode(
|
||||||
"often",
|
"often",
|
||||||
NextAction::array(0, new NextAction("unstealth", 10.0f), NULL)));
|
NextAction::array(0, new NextAction("unstealth", 30.0f), NULL)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class GenericShamanStrategyActionNodeFactory : public NamedObjectFactory<ActionN
|
|||||||
{
|
{
|
||||||
return new ActionNode ("flametongue weapon",
|
return new ActionNode ("flametongue weapon",
|
||||||
/*P*/ nullptr,
|
/*P*/ nullptr,
|
||||||
/*A*/ NextAction::array(0, new NextAction("flametongue weapon"), nullptr),
|
/*A*/ NextAction::array(0, new NextAction("rockbiter weapon"), nullptr),
|
||||||
/*C*/ nullptr);
|
/*C*/ nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ class GenericShamanStrategyActionNodeFactory : public NamedObjectFactory<ActionN
|
|||||||
{
|
{
|
||||||
return new ActionNode ("frostbrand weapon",
|
return new ActionNode ("frostbrand weapon",
|
||||||
/*P*/ nullptr,
|
/*P*/ nullptr,
|
||||||
/*A*/ NextAction::array(0, new NextAction("frostbrand weapon"), nullptr),
|
/*A*/ NextAction::array(0, new NextAction("flametongue weapon"), nullptr),
|
||||||
/*C*/ nullptr);
|
/*C*/ nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ class GenericShamanStrategyActionNodeFactory : public NamedObjectFactory<ActionN
|
|||||||
{
|
{
|
||||||
return new ActionNode ("windfury weapon",
|
return new ActionNode ("windfury weapon",
|
||||||
/*P*/ nullptr,
|
/*P*/ nullptr,
|
||||||
/*A*/ NextAction::array(0, new NextAction("windfury weapon"), nullptr),
|
/*A*/ NextAction::array(0, new NextAction("flametongue weapon"), nullptr),
|
||||||
/*C*/ nullptr);
|
/*C*/ nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,15 +34,15 @@ bool ShamanWeaponTrigger::IsActive()
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
bool MainHandWeaponNoImbueTrigger::IsActive() {
|
bool MainHandWeaponNoImbueTrigger::IsActive() {
|
||||||
Item* const itemForSpell = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND );
|
Item* const itemForSpell = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_MAINHAND);
|
||||||
if (!itemForSpell || itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
|
if (!itemForSpell || itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OffHandWeaponNoImbueTrigger::IsActive() {
|
bool OffHandWeaponNoImbueTrigger::IsActive() {
|
||||||
Item* const itemForSpell = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND );
|
Item* const itemForSpell = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
|
||||||
if (!itemForSpell || itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT))
|
if (!itemForSpell || itemForSpell->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT) || itemForSpell->GetTemplate()->InventoryType != INVTYPE_WEAPON)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class AreaDebuffValue : public CalculatedValue<Aura*>
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AreaDebuffValue(PlayerbotAI* botAI) :
|
AreaDebuffValue(PlayerbotAI* botAI) :
|
||||||
CalculatedValue<Aura*>(botAI, "area debuff", 1 * 1000) { }
|
CalculatedValue<Aura*>(botAI, "area debuff", 1) { }
|
||||||
|
|
||||||
Aura* Calculate() override;
|
Aura* Calculate() override;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user