diff --git a/src/common/cbasetypes.h b/src/common/cbasetypes.h index c827424fe1..372d88ad56 100644 --- a/src/common/cbasetypes.h +++ b/src/common/cbasetypes.h @@ -48,9 +48,11 @@ using uint64 = std::uint64_t; #include using namespace std::literals::chrono_literals; -using server_clock = std::chrono::system_clock; -using time_point = server_clock::time_point; -using duration = server_clock::duration; +using server_clock = std::chrono::system_clock; +using highres_clock = std::chrono::high_resolution_clock; +using time_point = server_clock::time_point; +using duration = server_clock::duration; +using hr_time_point = highres_clock::time_point; #include "tracy.h" diff --git a/src/map/ai/controllers/mob_controller.cpp b/src/map/ai/controllers/mob_controller.cpp index 889ac48340..23b46033f7 100644 --- a/src/map/ai/controllers/mob_controller.cpp +++ b/src/map/ai/controllers/mob_controller.cpp @@ -51,6 +51,8 @@ void CMobController::Tick(time_point tick) if (PMob->isAlive()) { + ResolveClaim(); + if (PMob->PAI->IsEngaged()) { DoCombatTick(tick); @@ -62,6 +64,30 @@ void CMobController::Tick(time_point tick) } } +void CMobController::ResolveClaim() +{ + auto& list = PMob->m_EntitiesTryingToClaim; + if (list.empty()) + { + return; + } + + // Sort the list, earliest first + std::sort(list.begin(), list.end(), [](CBattleEntity* a, CBattleEntity* b) + { + return a->lastActionTime < b->lastActionTime; + }); + + // Try the claims in the order they arrived + for (auto* entity : list) + { + battleutils::ClaimMobFinal(PMob, entity); + } + + // Empty the list + list.clear(); +} + bool CMobController::TryDeaggro() { TracyZoneScoped; diff --git a/src/map/ai/controllers/mob_controller.h b/src/map/ai/controllers/mob_controller.h index 03fe6075c5..942bbab6e0 100644 --- a/src/map/ai/controllers/mob_controller.h +++ b/src/map/ai/controllers/mob_controller.h @@ -51,6 +51,7 @@ class CMobController : public CController virtual bool Cast(uint16 targid, SpellID spellid) override; protected: + void ResolveClaim(); virtual bool TryDeaggro(); virtual void TryLink(); diff --git a/src/map/entities/battleentity.h b/src/map/entities/battleentity.h index 98fc148586..84a1faebd3 100644 --- a/src/map/entities/battleentity.h +++ b/src/map/entities/battleentity.h @@ -674,6 +674,9 @@ class CBattleEntity : public CBaseEntity virtual void Tick(time_point) override; virtual void PostTick() override; + // High resolution timestamp of the last action time + hr_time_point lastActionTime{ hr_time_point::max() }; + health_t health; // hp,mp,tp stats_t stats; // атрибуты STR,DEX,VIT,AGI,INT,MND,CHR skills_t WorkingSkills; // структура всех доступных сущности умений, ограниченных уровнем diff --git a/src/map/entities/mobentity.h b/src/map/entities/mobentity.h index 5acdea0f18..97d96d3f60 100644 --- a/src/map/entities/mobentity.h +++ b/src/map/entities/mobentity.h @@ -254,6 +254,8 @@ class CMobEntity : public CBattleEntity static constexpr float sound_range{ 8.f }; static constexpr float sight_range{ 15.f }; + std::vector m_EntitiesTryingToClaim; + protected: void DistributeRewards(); void DropItems(CCharEntity* PChar); diff --git a/src/map/map.cpp b/src/map/map.cpp index 5f1cb1dd95..a32190142b 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -139,7 +139,7 @@ map_session_data_t* mapsession_createsession(uint32 ip, uint16 port) map_session_data->server_packet_data = new int8[map_config.buffer_size + 20]; - map_session_data->last_update = time(nullptr); + map_session_data->last_update = highres_clock::now(); map_session_data->client_addr = ip; map_session_data->client_port = port; @@ -405,7 +405,7 @@ int32 do_sockets(fd_set* rfd, duration next) } } - map_session_data->last_update = time(nullptr); + map_session_data->last_update = highres_clock::now(); size_t size = ret; if (recv_parse(g_PBuff, &size, &from, map_session_data) != -1) @@ -852,7 +852,9 @@ int32 map_cleanup(time_point tick, CTaskMgr::CTask* PTask) CCharEntity* PChar = map_session_data->PChar; - if ((time(nullptr) - map_session_data->last_update) > 5) + auto last_update_time_sec = std::chrono::duration_cast(highres_clock::now() - map_session_data->last_update).count(); + + if (last_update_time_sec > 5) { if (PChar != nullptr && !(PChar->nameflags.flags & FLAG_DC)) { @@ -863,7 +865,8 @@ int32 map_cleanup(time_point tick, CTaskMgr::CTask* PTask) PChar->loc.zone->SpawnPCs(PChar); } } - if ((time(nullptr) - map_session_data->last_update) > map_config.max_time_lastupdate) + + if (last_update_time_sec > map_config.max_time_lastupdate) { if (PChar != nullptr) { diff --git a/src/map/map.h b/src/map/map.h index 85afe63ddc..2b44081106 100644 --- a/src/map/map.h +++ b/src/map/map.h @@ -159,16 +159,16 @@ struct map_config_t struct map_session_data_t { - uint32 client_addr; - uint16 client_port; - uint16 client_packet_id; // id последнего пакета, пришедшего от клиента - uint16 server_packet_id; // id последнего пакета, отправленного сервером - int8* server_packet_data; // указатель на собранный пакет, который был ранее отправлен клиенту - size_t server_packet_size; // размер пакета, который был ранее отправлен клиенту - time_t last_update; // time of last packet recv - blowfish_t blowfish; // unique decypher keys - CCharEntity* PChar; // game char - uint8 shuttingDown; // prevents double session closing + uint32 client_addr; + uint16 client_port; + uint16 client_packet_id; // id последнего пакета, пришедшего от клиента + uint16 server_packet_id; // id последнего пакета, отправленного сервером + int8* server_packet_data; // указатель на собранный пакет, который был ранее отправлен клиенту + size_t server_packet_size; // размер пакета, который был ранее отправлен клиенту + hr_time_point last_update; // time of last packet recv + blowfish_t blowfish; // unique decypher keys + CCharEntity* PChar; // game char + uint8 shuttingDown; // prevents double session closing map_session_data_t() { diff --git a/src/map/packet_system.cpp b/src/map/packet_system.cpp index 4763f29cee..d0ae0dfdc1 100644 --- a/src/map/packet_system.cpp +++ b/src/map/packet_system.cpp @@ -646,9 +646,11 @@ void SmallPacket0x01A(map_session_data_t* const PSession, CCharEntity* const PCh TracyZoneScoped; TracyZoneCString("Player Action"); - // uint32 ID = data.ref(0x04); - uint16 TargID = data.ref(0x08); - uint8 action = data.ref(0x0A); + uint16 TargID = data.ref(0x08); + uint8 action = data.ref(0x0A); + + // Used later for claim resolution + PChar->lastActionTime = PSession->last_update; switch (action) { diff --git a/src/map/utils/battleutils.cpp b/src/map/utils/battleutils.cpp index 8e3d89346d..79e8e23af2 100644 --- a/src/map/utils/battleutils.cpp +++ b/src/map/utils/battleutils.cpp @@ -4764,80 +4764,100 @@ namespace battleutils void ClaimMob(CBattleEntity* PDefender, CBattleEntity* PAttacker, bool passing) { - TracyZoneScoped; if (PDefender->objtype == TYPE_MOB) { - CBattleEntity* original = PAttacker; - if (PAttacker->objtype != TYPE_PC) + auto* PMob = static_cast(PDefender); + PMob->m_EntitiesTryingToClaim.emplace_back(PAttacker); + } + } + + void ClaimMobFinal(CBattleEntity* PDefender, CBattleEntity* PAttacker, bool passing) + { + TracyZoneScoped; + + if (PDefender->objtype != TYPE_MOB) + { + return; + } + + CBattleEntity* original = PAttacker; + if (PAttacker->objtype != TYPE_PC) + { + if (PAttacker->PMaster && PAttacker->PMaster->objtype == TYPE_PC) + { // claim by master + PAttacker = PAttacker->PMaster; + } + else { - if (PAttacker->PMaster && PAttacker->PMaster->objtype == TYPE_PC) - { // claim by master - PAttacker = PAttacker->PMaster; - } - else - { - return; - } + return; } - CBattleEntity* battleTarget = original->GetBattleTarget(); - CMobEntity* mob = static_cast(PDefender); + } + + CBattleEntity* battleTarget = original->GetBattleTarget(); + CMobEntity* mob = static_cast(PDefender); + + if (!passing) + { + mob->PEnmityContainer->UpdateEnmity(original, 0, 0, true, true); + } + + if (PAttacker) + { + CCharEntity* attacker = static_cast(PAttacker); if (!passing) { - mob->PEnmityContainer->UpdateEnmity(original, 0, 0, true, true); + battleutils::DirtyExp(PDefender, PAttacker); } - if (PAttacker) + + if (!battleTarget || battleTarget == PDefender || battleTarget != attacker->PClaimedMob || PDefender->isDead()) { - CCharEntity* attacker = static_cast(PAttacker); - if (!passing) - { - battleutils::DirtyExp(PDefender, PAttacker); + if (PDefender->isAlive() && attacker->PClaimedMob && attacker->PClaimedMob != PDefender && attacker->PClaimedMob->isAlive() && + attacker->PClaimedMob->m_OwnerID.id == attacker->id) + { // unclaim any other living mobs owned by attacker + static_cast(attacker->PClaimedMob->PAI->GetController())->TapDeclaimTime(); + attacker->PClaimedMob = nullptr; } - if (!battleTarget || battleTarget == PDefender || battleTarget != attacker->PClaimedMob || PDefender->isDead()) + + if (!mob->CalledForHelp()) { - if (PDefender->isAlive() && attacker->PClaimedMob && attacker->PClaimedMob != PDefender && attacker->PClaimedMob->isAlive() && - attacker->PClaimedMob->m_OwnerID.id == attacker->id) - { // unclaim any other living mobs owned by attacker - static_cast(attacker->PClaimedMob->PAI->GetController())->TapDeclaimTime(); - attacker->PClaimedMob = nullptr; + if (battleutils::HasClaim(PAttacker, PDefender)) + { // mob is currently claimed by your alliance, update ownership + mob->m_OwnerID.id = PAttacker->id; + mob->m_OwnerID.targid = PAttacker->targid; + if (PDefender->isAlive()) + { // ignore killing blow + mob->updatemask |= UPDATE_STATUS; + attacker->PClaimedMob = PDefender; + } } - if (!mob->CalledForHelp()) - { - if (battleutils::HasClaim(PAttacker, PDefender)) - { // mob is currently claimed by your alliance, update ownership + else + { // mob is unclaimed + if (PDefender->isDead()) + { // always give rewards on the killing blow mob->m_OwnerID.id = PAttacker->id; mob->m_OwnerID.targid = PAttacker->targid; - if (PDefender->isAlive()) - { // ignore killing blow - mob->updatemask |= UPDATE_STATUS; - attacker->PClaimedMob = PDefender; - } + return; } - else - { // mob is unclaimed - if (PDefender->isDead()) - { // always give rewards on the killing blow + + CBattleEntity* highestClaim = mob->PEnmityContainer->GetHighestEnmity(); + if (highestClaim && highestClaim->objtype == TYPE_TRUST) + { + highestClaim = static_cast(highestClaim)->PMaster; + } + + PAttacker->ForAlliance([&](CBattleEntity* PMember) { + if (!highestClaim || highestClaim == PMember || highestClaim == PMember->PPet) + { // someone in your alliance is top of hate list, claim for your alliance mob->m_OwnerID.id = PAttacker->id; mob->m_OwnerID.targid = PAttacker->targid; - return; - } - CBattleEntity* highestClaim = mob->PEnmityContainer->GetHighestEnmity(); - if (highestClaim && highestClaim->objtype == TYPE_TRUST) - { - highestClaim = static_cast(highestClaim)->PMaster; - } - PAttacker->ForAlliance([&](CBattleEntity* PMember) { - if (!highestClaim || highestClaim == PMember || highestClaim == PMember->PPet) - { // someone in your alliance is top of hate list, claim for your alliance - mob->m_OwnerID.id = PAttacker->id; - mob->m_OwnerID.targid = PAttacker->targid; - if (PDefender->isAlive()) - { // ignore killing blow - mob->updatemask |= UPDATE_STATUS; - attacker->PClaimedMob = PDefender; - } + + if (PDefender->isAlive()) + { // ignore killing blow + mob->updatemask |= UPDATE_STATUS; + attacker->PClaimedMob = PDefender; } - }); - } + } + }); } } } diff --git a/src/map/utils/battleutils.h b/src/map/utils/battleutils.h index 526ffe6a8d..07912ca2e3 100644 --- a/src/map/utils/battleutils.h +++ b/src/map/utils/battleutils.h @@ -198,6 +198,7 @@ namespace battleutils uint8 getStoreTPbonusFromMerit(CBattleEntity* PEntity); void ClaimMob(CBattleEntity* PDefender, CBattleEntity* PAttacker, bool passing = false); + void ClaimMobFinal(CBattleEntity* PDefender, CBattleEntity* PAttacker, bool passing = false); void DirtyExp(CBattleEntity* PDefender, CBattleEntity* PAttacker); void RelinquishClaim(CCharEntity* PDefender);