mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 16:51:29 +00:00
[Performance] Auto Idle / AFK (#4903)
* [Performance] AFK Client Packet Filtering * Player feedback * Update client_packet.cpp * Fixes * Streamline updates to SetAFK * Decouple idling and AFK and manual AFK * Reset clock timer when we take AFK or idle off * Exclude bard songs in non combat zones from resetting timer * GM exclusion adjustments
This commit is contained in:
parent
53cc2de459
commit
567d46c3d6
@ -233,6 +233,12 @@ RULE_BOOL(Character, SneakAlwaysSucceedOver100, false, "When sneak skill is over
|
||||
RULE_INT(Character, BandolierSwapDelay, 0, "Bandolier swap delay in milliseconds, default is 0")
|
||||
RULE_BOOL(Character, EnableHackedFastCampForGM, false, "Enables hacked fast camp for GM clients, if the GM doesn't have a hacked client they'll camp like normal")
|
||||
RULE_BOOL(Character, AlwaysAllowNameChange, false, "Enable this option to allow /changename to work without enabling a name change via scripts.")
|
||||
RULE_BOOL(Character, EnableAutoAFK, true, "Enable or disable the auto AFK feature, cuts down on packet spam")
|
||||
RULE_BOOL(Character, AutoIdleFilterPackets, true, "Enable or disable filtering packets when auto AFK is enabled, heavily cuts down on packet spam in zones with lots of players")
|
||||
RULE_INT(Character, SecondsBeforeIdleCombatZone, 600, "Seconds before a player is considered idle in combat zones (600 = 10 minutes)")
|
||||
RULE_INT(Character, SecondsBeforeIdleNonCombatZone, 60, "Seconds before a player is considered idle in non-combat zones (60 = 1 minute)")
|
||||
RULE_INT(Character, SecondsBeforeAFKCombatZone, 1800, "Seconds before a player is considered AFK in combat zones (1800 = 30 minutes)")
|
||||
RULE_INT(Character, SecondsBeforeAFKNonCombatZone, 600, "Seconds before a player is considered AFK in non-combat zones (600 = 10 minutes)")
|
||||
RULE_CATEGORY_END()
|
||||
|
||||
RULE_CATEGORY(Mercs)
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
Aura::Aura(NPCType *type_data, Mob *owner, AuraRecord &record)
|
||||
: NPC(type_data, 0, owner->GetPosition(), GravityBehavior::Flying), spell_id(record.spell_id),
|
||||
distance(record.distance),
|
||||
remove_timer(record.duration), movement_timer(100), process_timer(1000), aura_id(-1)
|
||||
remove_timer(record.duration), movement_timer(1000), process_timer(1000), aura_id(-1)
|
||||
{
|
||||
GiveNPCTypeData(type_data); // we will delete this later on
|
||||
m_owner = owner->GetID();
|
||||
|
||||
@ -226,8 +226,8 @@ Client::Client() : Mob(
|
||||
last_reported_endurance_percent = 0;
|
||||
last_reported_mana_percent = 0;
|
||||
gm_hide_me = false;
|
||||
AFK = false;
|
||||
LFG = false;
|
||||
m_is_afk = false;
|
||||
LFG = false;
|
||||
LFGFromLevel = 0;
|
||||
LFGToLevel = 0;
|
||||
LFGMatchFilter = false;
|
||||
@ -536,8 +536,8 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
|
||||
last_reported_endurance_percent = 0;
|
||||
last_reported_mana_percent = 0;
|
||||
gm_hide_me = false;
|
||||
AFK = false;
|
||||
LFG = false;
|
||||
m_is_afk = false;
|
||||
LFG = false;
|
||||
LFGFromLevel = 0;
|
||||
LFGToLevel = 0;
|
||||
LFGMatchFilter = false;
|
||||
@ -1175,6 +1175,10 @@ void Client::QueuePacket(const EQApplicationPacket* app, bool ack_req, CLIENT_CO
|
||||
return;
|
||||
}
|
||||
|
||||
if (RuleB(Character, AutoIdleFilterPackets) && m_is_idle && IsFilteredAFKPacket(app)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (client_state != CLIENT_CONNECTED && required_state == CLIENT_CONNECTED) {
|
||||
AddPacket(app, ack_req);
|
||||
return;
|
||||
@ -2528,7 +2532,7 @@ void Client::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
|
||||
Mob::FillSpawnStruct(ns, ForWho);
|
||||
|
||||
// Populate client-specific spawn information
|
||||
ns->spawn.afk = AFK;
|
||||
ns->spawn.afk = m_is_afk;
|
||||
ns->spawn.lfg = LFG; // afk and lfg are cleared on zoning on live
|
||||
ns->spawn.anon = m_pp.anon;
|
||||
ns->spawn.gm = GetGM() ? 1 : 0;
|
||||
@ -10839,15 +10843,37 @@ void Client::SetAnon(uint8 anon_flag) {
|
||||
safe_delete(outapp);
|
||||
}
|
||||
|
||||
void Client::SetAFK(uint8 afk_flag) {
|
||||
AFK = afk_flag;
|
||||
auto outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
|
||||
SpawnAppearance_Struct* spawn_appearance = (SpawnAppearance_Struct*)outapp->pBuffer;
|
||||
spawn_appearance->spawn_id = GetID();
|
||||
spawn_appearance->type = AppearanceType::AFK;
|
||||
spawn_appearance->parameter = afk_flag;
|
||||
entity_list.QueueClients(this, outapp);
|
||||
safe_delete(outapp);
|
||||
void Client::SetAFK(uint8 afk_flag)
|
||||
{
|
||||
if (!afk_flag) {
|
||||
ResetAFKTimer();
|
||||
}
|
||||
|
||||
bool changed_afk_state = (m_is_afk && !afk_flag) || (!m_is_afk && afk_flag);
|
||||
|
||||
if (!changed_afk_state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set messaging based on the state
|
||||
std::string you_are = "You are no longer AFK.";
|
||||
if (!m_is_afk && afk_flag) {
|
||||
you_are = "You are now AFK.";
|
||||
}
|
||||
|
||||
// set the state
|
||||
m_is_afk = afk_flag;
|
||||
|
||||
// inform of state change
|
||||
Message(Chat::Yellow, you_are.c_str());
|
||||
|
||||
// send the spawn appearance packet to all clients
|
||||
static EQApplicationPacket p(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
|
||||
auto *s = (SpawnAppearance_Struct *) p.pBuffer;
|
||||
s->spawn_id = GetID();
|
||||
s->type = AppearanceType::AFK;
|
||||
s->parameter = afk_flag;
|
||||
entity_list.QueueClients(this, &p);
|
||||
}
|
||||
|
||||
void Client::SendToInstance(std::string instance_type, std::string zone_short_name, uint32 instance_version, float x, float y, float z, float heading, std::string instance_identifier, uint32 duration) {
|
||||
@ -12713,7 +12739,7 @@ void Client::SendTopLevelInventory()
|
||||
}
|
||||
}
|
||||
|
||||
void Client::CheckSendBulkNpcPositions()
|
||||
void Client::CheckSendBulkNpcPositions(bool force)
|
||||
{
|
||||
float distance_moved = DistanceNoZ(m_last_position_before_bulk_update, GetPosition());
|
||||
float update_range = RuleI(Range, MobCloseScanDistance);
|
||||
@ -12724,7 +12750,7 @@ void Client::CheckSendBulkNpcPositions()
|
||||
|
||||
int updated_count = 0;
|
||||
int skipped_count = 0;
|
||||
if (is_ready_to_update) {
|
||||
if (is_ready_to_update || force) {
|
||||
auto &mob_movement_manager = MobMovementManager::Get();
|
||||
|
||||
for (auto &e: entity_list.GetMobList()) {
|
||||
|
||||
@ -501,9 +501,19 @@ public:
|
||||
void Kick(const std::string &reason);
|
||||
void WorldKick();
|
||||
inline uint8 GetAnon() const { return m_pp.anon; }
|
||||
inline uint8 GetAFK() const { return AFK; }
|
||||
inline uint8 GetAFK() const { return m_is_afk; }
|
||||
void SetAnon(uint8 anon_flag);
|
||||
inline Client* ResetAFKTimer() {
|
||||
if (!RuleB(Character, EnableAutoAFK)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
m_afk_reset = true;
|
||||
m_last_moved = std::chrono::steady_clock::now();
|
||||
return this;
|
||||
};
|
||||
void SetAFK(uint8 afk_flag);
|
||||
inline bool IsIdle() { return m_is_idle; }
|
||||
inline PlayerProfile_Struct& GetPP() { return m_pp; }
|
||||
inline ExtendedProfile_Struct& GetEPP() { return m_epp; }
|
||||
inline EQ::InventoryProfile& GetInv() { return m_inv; }
|
||||
@ -2062,7 +2072,8 @@ private:
|
||||
uint8 LFGToLevel;
|
||||
bool LFGMatchFilter;
|
||||
char LFGComments[64];
|
||||
bool AFK;
|
||||
bool m_is_afk = false;
|
||||
bool m_is_manual_afk = false;
|
||||
bool auto_attack;
|
||||
bool auto_fire;
|
||||
bool runmode;
|
||||
@ -2216,7 +2227,12 @@ private:
|
||||
glm::vec4 m_last_position_before_bulk_update;
|
||||
Timer m_client_bulk_npc_pos_update_timer;
|
||||
Timer m_position_update_timer;
|
||||
void CheckSendBulkNpcPositions();
|
||||
void CheckSendBulkNpcPositions(bool force = false);
|
||||
|
||||
// afk
|
||||
bool m_is_idle = false;
|
||||
bool m_afk_reset = false; // used to trigger next-tic afk reset
|
||||
std::chrono::steady_clock::time_point m_last_moved = std::chrono::steady_clock::now();
|
||||
|
||||
void BulkSendInventoryItems();
|
||||
|
||||
@ -2410,6 +2426,9 @@ public:
|
||||
const std::string &GetMailKey() const;
|
||||
void ShowZoneShardMenu();
|
||||
void Handle_OP_ChangePetName(const EQApplicationPacket *app);
|
||||
bool IsFilteredAFKPacket(const EQApplicationPacket *p);
|
||||
void CheckAutoIdleAFK(PlayerPositionUpdateClient_Struct *p);
|
||||
void SyncWorldPositionsToClient(bool ignore_idle = false);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -4383,6 +4383,14 @@ void Client::Handle_OP_CastSpell(const EQApplicationPacket *app)
|
||||
|
||||
m_TargetRing = glm::vec3(castspell->x_pos, castspell->y_pos, castspell->z_pos);
|
||||
|
||||
if (castspell->spell_id && IsValidSpell(castspell->spell_id)) {
|
||||
bool is_non_combat_zone = !zone->CanDoCombat() || zone->BuffTimersSuspended();
|
||||
bool is_excluded_reset = is_non_combat_zone && IsBardSong(castspell->spell_id);
|
||||
if (!is_excluded_reset) {
|
||||
ResetAFKTimer();
|
||||
}
|
||||
}
|
||||
|
||||
LogSpells("OP CastSpell: slot [{}] spell [{}] target [{}] inv [{}]", castspell->slot, castspell->spell_id, castspell->target_id, (unsigned long)castspell->inventoryslot);
|
||||
CastingSlot slot = static_cast<CastingSlot>(castspell->slot);
|
||||
|
||||
@ -4566,6 +4574,12 @@ void Client::Handle_OP_ChannelMessage(const EQApplicationPacket *app)
|
||||
return;
|
||||
}
|
||||
|
||||
// reject automatic AFK messages from resetting /afk
|
||||
std::string message = cm->message;
|
||||
if (!Strings::Contains(message, "Sorry, I am A.F.K.")) {
|
||||
ResetAFKTimer();
|
||||
}
|
||||
|
||||
if (IsAIControlled() && !GetGM()) {
|
||||
Message(Chat::Red, "You try to speak but can't move your mouth!");
|
||||
return;
|
||||
@ -4992,6 +5006,10 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) {
|
||||
|
||||
SetMoving(!(cy == m_Position.y && cx == m_Position.x));
|
||||
|
||||
if (RuleB(Character, EnableAutoAFK)) {
|
||||
CheckAutoIdleAFK(ppu);
|
||||
}
|
||||
|
||||
CheckClientToNpcAggroTimer();
|
||||
|
||||
if (m_mob_check_moving_timer.Check()) {
|
||||
@ -10792,6 +10810,8 @@ void Client::Handle_OP_MoveItem(const EQApplicationPacket *app)
|
||||
return;
|
||||
}
|
||||
|
||||
ResetAFKTimer();
|
||||
|
||||
BenchTimer bench;
|
||||
|
||||
MoveItem_Struct* mi = (MoveItem_Struct*) app->pBuffer;
|
||||
@ -14732,7 +14752,9 @@ void Client::Handle_OP_SpawnAppearance(const EQApplicationPacket *app)
|
||||
}
|
||||
else if (sa->type == AppearanceType::AFK) {
|
||||
if (afk_toggle_timer.Check()) {
|
||||
AFK = (sa->parameter == 1);
|
||||
m_is_afk = (sa->parameter == 1);
|
||||
m_is_manual_afk = (sa->parameter == 1);
|
||||
ResetAFKTimer();
|
||||
entity_list.QueueClients(this, app, true);
|
||||
}
|
||||
}
|
||||
@ -15551,6 +15573,14 @@ void Client::Handle_OP_TradeRequest(const EQApplicationPacket *app)
|
||||
|
||||
// Pass trade request on to recipient
|
||||
if (tradee && tradee->IsClient()) {
|
||||
// if we are idling we need to sync client positions otherwise clients will not be aware of each other
|
||||
if (m_is_idle) {
|
||||
SyncWorldPositionsToClient(true);
|
||||
}
|
||||
if (tradee->CastToClient()->IsIdle()) {
|
||||
tradee->CastToClient()->SyncWorldPositionsToClient(true);
|
||||
}
|
||||
|
||||
tradee->CastToClient()->QueuePacket(app);
|
||||
}
|
||||
else if (tradee && (tradee->IsNPC() || tradee->IsBot())) {
|
||||
@ -15580,6 +15610,14 @@ void Client::Handle_OP_TradeRequestAck(const EQApplicationPacket *app)
|
||||
Mob* tradee = entity_list.GetMob(msg->to_mob_id);
|
||||
|
||||
if (tradee && tradee->IsClient()) {
|
||||
// if we are idling we need to sync client positions otherwise clients will not be aware of each other
|
||||
if (m_is_idle) {
|
||||
SyncWorldPositionsToClient(true);
|
||||
}
|
||||
if (tradee->CastToClient()->IsIdle()) {
|
||||
tradee->CastToClient()->SyncWorldPositionsToClient(true);
|
||||
}
|
||||
|
||||
trade->Start(msg->to_mob_id);
|
||||
tradee->CastToClient()->QueuePacket(app);
|
||||
}
|
||||
@ -17166,3 +17204,153 @@ void Client::Handle_OP_EvolveItem(const EQApplicationPacket *app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Client::IsFilteredAFKPacket(const EQApplicationPacket *p)
|
||||
{
|
||||
if (p->GetOpcode() == OP_ClientUpdate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Client::CheckAutoIdleAFK(PlayerPositionUpdateClient_Struct *p)
|
||||
{
|
||||
if (!RuleB(Character, EnableAutoAFK)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_non_combat_zone = !zone->CanDoCombat() || zone->BuffTimersSuspended();
|
||||
|
||||
int seconds_before_afk =
|
||||
is_non_combat_zone ?
|
||||
RuleI(Character, SecondsBeforeAFKNonCombatZone) :
|
||||
RuleI(Character, SecondsBeforeAFKCombatZone);
|
||||
|
||||
int seconds_before_idle =
|
||||
is_non_combat_zone ?
|
||||
RuleI(Character, SecondsBeforeIdleNonCombatZone) :
|
||||
RuleI(Character, SecondsBeforeIdleCombatZone);
|
||||
|
||||
// seconds_before_idle can't be greater than seconds_before_afk
|
||||
if (seconds_before_idle > seconds_before_afk) {
|
||||
seconds_before_idle = seconds_before_afk;
|
||||
}
|
||||
|
||||
bool has_moved =
|
||||
m_Position.x != p->x_pos ||
|
||||
m_Position.y != p->y_pos ||
|
||||
m_Position.z != p->z_pos ||
|
||||
m_Position.w != EQ12toFloat(p->heading);
|
||||
|
||||
bool triggered_reset = m_afk_reset;
|
||||
bool was_idle = m_is_idle;
|
||||
bool is_idle_or_afk = m_is_idle || m_is_afk;
|
||||
|
||||
if (!has_moved && (!m_is_idle || !m_is_afk)) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto since_last_moved = now - m_last_moved;
|
||||
|
||||
if (!m_is_manual_afk && !m_is_afk && since_last_moved > std::chrono::seconds(seconds_before_afk)) {
|
||||
bool is_client_excluded_from_afk = (IsBuyer() || IsTrader() || GetGM());
|
||||
if (is_client_excluded_from_afk) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogInfo(
|
||||
"Client [{}] has been AFK for [{}] seconds",
|
||||
GetCleanName(),
|
||||
std::chrono::duration_cast<std::chrono::seconds>(since_last_moved).count()
|
||||
);
|
||||
SetAFK(true);
|
||||
return;
|
||||
}
|
||||
else if (!m_is_idle && since_last_moved > std::chrono::seconds(seconds_before_idle)) {
|
||||
bool is_client_excluded_from_idle = GetGM() && !is_non_combat_zone;
|
||||
if (is_client_excluded_from_idle) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogInfo(
|
||||
"Client [{}] has been idle for [{}] seconds",
|
||||
GetCleanName(),
|
||||
std::chrono::duration_cast<std::chrono::seconds>(since_last_moved).count()
|
||||
);
|
||||
m_is_idle = true;
|
||||
Message(Chat::Yellow, "You are now idle. Updates will be sent to you less frequently.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if we triggered a reset, but didn't move, we are still idling but not AFK
|
||||
if (triggered_reset && was_idle) {
|
||||
m_is_idle = true;
|
||||
}
|
||||
|
||||
// if we moved or triggered reset through other actions, we are no longer AFK.
|
||||
// we could trigger resetting AFK status through actions like message, cast, attack etc but still by idle until we move
|
||||
if (!m_is_manual_afk && (has_moved || triggered_reset) && m_is_afk) {
|
||||
LogInfo("AFK [{}] is no longer idle, syncing positions", GetCleanName());
|
||||
SetAFK(false);
|
||||
ResetAFKTimer();
|
||||
}
|
||||
|
||||
// we could be not AFK and idle at the same time
|
||||
if (has_moved && m_is_idle) {
|
||||
LogInfo("Idle [{}] is no longer idle, syncing positions", GetCleanName());
|
||||
m_is_idle = false;
|
||||
Message(Chat::Yellow, "You are no longer idle.");
|
||||
SyncWorldPositionsToClient();
|
||||
ResetAFKTimer();
|
||||
}
|
||||
|
||||
m_afk_reset = false;
|
||||
}
|
||||
|
||||
void Client::SyncWorldPositionsToClient(bool ignore_idle)
|
||||
{
|
||||
// if we are idle currently, we need to force updates (which bypasses idle status) and reset idle status
|
||||
bool reset_idle = false;
|
||||
if (ignore_idle && m_is_idle) {
|
||||
m_is_idle = false;
|
||||
reset_idle = true;
|
||||
}
|
||||
|
||||
LogInfo("Syncing positions for client [{}]", GetCleanName());
|
||||
CheckSendBulkNpcPositions(true);
|
||||
|
||||
static EQApplicationPacket cu(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
|
||||
for (auto &e: entity_list.GetClientList()) {
|
||||
auto c = e.second;
|
||||
|
||||
// skip if not in range
|
||||
if (Distance(c->GetPosition(), GetPosition()) > RuleI(Range, ClientPositionUpdates)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip self
|
||||
if (c == this) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto *spu = (PlayerPositionUpdateServer_Struct *) cu.pBuffer;
|
||||
|
||||
memset(spu, 0x00, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
spu->spawn_id = c->GetID();
|
||||
spu->x_pos = FloatToEQ19(c->GetX());
|
||||
spu->y_pos = FloatToEQ19(c->GetY());
|
||||
spu->z_pos = FloatToEQ19(c->GetZ());
|
||||
spu->heading = FloatToEQ12(c->GetHeading());
|
||||
spu->delta_x = FloatToEQ13(0);
|
||||
spu->delta_y = FloatToEQ13(0);
|
||||
spu->delta_z = FloatToEQ13(0);
|
||||
spu->delta_heading = FloatToEQ10(0);
|
||||
spu->animation = 0;
|
||||
QueuePacket(&cu);
|
||||
}
|
||||
|
||||
if (ignore_idle && reset_idle) {
|
||||
m_is_idle = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -536,6 +536,10 @@ bool Client::Process() {
|
||||
DoEnduranceRegen();
|
||||
BuffProcess();
|
||||
|
||||
if (auto_attack) {
|
||||
ResetAFKTimer();
|
||||
}
|
||||
|
||||
if (tribute_timer.Check()) {
|
||||
ToggleTribute(true); //re-activate the tribute.
|
||||
}
|
||||
|
||||
@ -2646,7 +2646,7 @@ void Mob::SendStatsWindow(Client* c, bool use_window)
|
||||
Chat::White,
|
||||
fmt::format(
|
||||
" AFK: {} LFG: {} Anon: {} PVP: {} GM: {} Fly Mode: {} ({}) GM Speed: {} Hide Me: {} Invulnerability: {} LD: {} Client Version: {} Tells Off: {}",
|
||||
CastToClient()->AFK ? "Yes" : "No",
|
||||
CastToClient()->m_is_afk ? "Yes" : "No",
|
||||
CastToClient()->LFG ? "Yes" : "No",
|
||||
CastToClient()->GetAnon() ? "Yes" : "No",
|
||||
CastToClient()->GetPVP() ? "Yes" : "No",
|
||||
|
||||
@ -839,6 +839,10 @@ void MobMovementManager::SendCommandToClients(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c->IsIdle()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_impl->Stats.TotalSent++;
|
||||
|
||||
if (anim != 0) {
|
||||
@ -879,6 +883,10 @@ void MobMovementManager::SendCommandToClients(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c->IsIdle()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float distance = c->CalculateDistance(mob->GetX(), mob->GetY(), mob->GetZ());
|
||||
|
||||
bool match = false;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user