eqemu-server/world/dynamic_zone_manager.cpp
Chris Miles d9f545a5ec
[Logging] Implement Player Event Logging system (#2833)
* Plumbing

* Batch processing in world

* Cleanup

* Cleanup

* Update player_event_logs.cpp

* Add player zoning event

* Use generics

* Comments

* Add events

* Add more events

* AA_GAIN, AA_PURCHASE, FORAGE_SUCCESS, FORAGE_FAILURE

* FISH_SUCCESS, FISH_FAILURE, ITEM_DESTROY

* Add charges to ITEM_DESTROY

* WENT_ONLINE, WENT_OFFLINE

* LEVEL_GAIN, LEVEL_LOSS

* LOOT_ITEM

* MERCHANT_PURCHASE

* MERCHANT_SELL

* SKILL_UP

* Add events

* Add more events

* TASK_ACCEPT, TASK_COMPLETE, and TASK_UPDATE

* GROUNDSPAWN_PICKUP

* SAY

* REZ_ACCEPTED

* COMBINE_FAILURE and COMBINE_SUCCESS

* DROPPED_ITEM

* DEATH

* SPLIT_MONEY

* TRADER_PURCHASE and TRADER_SELL

* DISCOVER_ITEM

* Convert GM_COMMAND to use new macro

* Convert ZONING event to use macro

* Revert some code changes

* Revert "Revert some code changes"

This reverts commit d53682f997e89a053a660761085913245db91e9d.

* Add cereal generation support to repositories

* TRADE

* Formatting

* Cleanup

* Relocate discord_manager to discord folder

* Discord sending plumbing

* Rename UCS's Database class to UCSDatabase to be more specific and not collide with base Database class for repository usage

* More discord sending plumbing

* More discord message formatting work

* More discord formatting work

* Discord formatting of events

* Format WENT_ONLINE, WENT_OFFLINE

* Add merchant purchase event

* Handle Discord MERCHANT_SELL formatter

* Update player_event_discord_formatter.cpp

* Tweaks

* Implement retention truncation

* Put mutex locking on batch queue, put processor on its own thread

* Process on initial bootup

* Implement optional QS processing, implement keepalive from world to QS

* Reload player event settings when logs are reloaded in game

* Set settings defaults

* Update player_event_logs.cpp

* Update player_event_logs.cpp

* Set retention days on boot

* Update player_event_logs.cpp

* Player Handin Event Testing.

Testing player handin stuff.

* Cleanup.

* Finish NPC Handin.

* set a reference to the client inside of the trade object as well for plugins to process

* Fix for windows _inline

* Bump to cpp20 default, ignore excessive warnings on windows

* Bump FMT to 6.1.2 for cpp20 compat and swap fmt::join for Strings::Join

* Windows compile fixes

* Update CMakeLists.txt

* Update CMakeLists.txt

* Update CMakeLists.txt

* Create 2022_12_19_player_events_tables.sql

* [Formatters] Work on Discord Formatters

* Handin money.

* Format header

* [Formatters] Work on Discord Formatters

* Format

* Format

* [Formatters] More Formatter work, need to test further.

* [Formatters] More Work on Formatters.

* Add missing #endif

* [Formatters] Work on Formatters, fix Bot formatting in ^create help

* NPC Handin Discord Formatter

* Update player_event_logs.cpp

* Discover Item Discord Formatter

* Dropped Item Discord Formatter

* Split Money Discord Formatter

* Trader Discord Formatters

* Cleanup.

* Trade Event Discord Formatter Groundwork

* SAY don't record GM commands

* GM_Command don't record #help

* Update player_event_logs.cpp

* Fill in more event data

* Post rebase fixes

* Post rebase fix

* Discord formatting adjustments

* Add event deprecation or unimplemented tag support

* Trade events

* Add return money and sanity checks.

* Update schema

* Update ucs.cpp

* Update client.cpp

* Update 2022_12_19_player_events_tables.sql

* Implement archive single line

* Replace hackers table and functions with PossibleHack player event

* Replace very old eventlog table since the same events are covered by player event logs

* Update bot_command.cpp

* Record NPC kill events ALL / Named / Raid

* Add BatchEventProcessIntervalSeconds rule

* Naming

* Update CMakeLists.txt

* Update database_schema.h

* Remove logging function and methods

* DB version

* Cleanup SendPlayerHandinEvent

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
Co-authored-by: Aeadoin <109764533+Aeadoin@users.noreply.github.com>
2023-02-12 21:31:01 -06:00

177 lines
5.4 KiB
C++

#include "dynamic_zone_manager.h"
#include "dynamic_zone.h"
#include "worlddb.h"
#include "zonelist.h"
#include "zoneserver.h"
#include "../common/rulesys.h"
#include "../common/repositories/expeditions_repository.h"
#include "../common/repositories/expedition_lockouts_repository.h"
extern ZSList zoneserver_list;
DynamicZoneManager dynamic_zone_manager;
DynamicZoneManager::DynamicZoneManager() :
m_process_throttle_timer{ static_cast<uint32_t>(RuleI(DynamicZone, WorldProcessRate)) }
{
}
void DynamicZoneManager::PurgeExpiredDynamicZones()
{
// purge when no members, instance is expired, or instance doesn't exist.
// this prevents characters remaining members of dzs that expired while
// server was offline but delayed instance purging hasn't cleaned yet
auto dz_ids = DynamicZonesRepository::GetStaleIDs(database);
if (!dz_ids.empty())
{
LogDynamicZones("Purging [{}] dynamic zone(s)", dz_ids.size());
DynamicZoneMembersRepository::DeleteWhere(database,
fmt::format("dynamic_zone_id IN ({})", Strings::Join(dz_ids, ",")));
DynamicZonesRepository::DeleteWhere(database,
fmt::format("id IN ({})", Strings::Join(dz_ids, ",")));
}
}
DynamicZone* DynamicZoneManager::CreateNew(
DynamicZone& dz_request, const std::vector<DynamicZoneMember>& members)
{
// this creates a new dz instance and saves it to both db and cache
uint32_t dz_id = dz_request.Create();
if (dz_id == 0)
{
LogDynamicZones("Failed to create dynamic zone for zone [{}]", dz_request.GetZoneID());
return nullptr;
}
auto dz = std::make_unique<DynamicZone>(dz_request);
if (!members.empty())
{
dz->SaveMembers(members);
dz->CacheMemberStatuses();
}
LogDynamicZones("Created new dz [{}] for zone [{}]", dz_id, dz_request.GetZoneID());
auto pack = dz->CreateServerDzCreatePacket(0, 0);
zoneserver_list.SendPacket(pack.get());
auto inserted = dynamic_zone_cache.emplace(dz_id, std::move(dz));
return inserted.first->second.get();
}
void DynamicZoneManager::CacheNewDynamicZone(ServerPacket* pack)
{
auto buf = reinterpret_cast<ServerDzCreateSerialized_Struct*>(pack->pBuffer);
auto new_dz = std::make_unique<DynamicZone>();
new_dz->LoadSerializedDzPacket(buf->cereal_data, buf->cereal_size);
new_dz->CacheMemberStatuses();
// reserialize with member statuses cached before forwarding (restore origin zone)
auto repack = new_dz->CreateServerDzCreatePacket(buf->origin_zone_id, buf->origin_instance_id);
uint32_t dz_id = new_dz->GetID();
dynamic_zone_cache.emplace(dz_id, std::move(new_dz));
LogDynamicZones("Cached new dynamic zone [{}]", dz_id);
zoneserver_list.SendPacket(repack.get());
}
void DynamicZoneManager::CacheAllFromDatabase()
{
BenchTimer bench;
auto dynamic_zones = DynamicZonesRepository::AllWithInstanceNotExpired(database);
auto dynamic_zone_members = DynamicZoneMembersRepository::GetAllWithNames(database);
dynamic_zone_cache.clear();
dynamic_zone_cache.reserve(dynamic_zones.size());
for (auto& entry : dynamic_zones)
{
uint32_t dz_id = entry.id;
auto dz = std::make_unique<DynamicZone>(std::move(entry));
for (auto& member : dynamic_zone_members)
{
if (member.dynamic_zone_id == dz_id)
{
dz->AddMemberFromRepositoryResult(std::move(member));
}
}
// note leader status won't be updated here until leader is set by owning system (expeditions)
dz->CacheMemberStatuses();
dynamic_zone_cache.emplace(dz_id, std::move(dz));
}
LogDynamicZones("Caching [{}] dynamic zone(s) took [{}s]", dynamic_zone_cache.size(), bench.elapsed());
}
void DynamicZoneManager::Process()
{
if (!m_process_throttle_timer.Check())
{
return;
}
std::vector<uint32_t> dynamic_zone_ids;
for (const auto& dz_iter : dynamic_zone_cache)
{
DynamicZone* dz = dz_iter.second.get();
// dynamic zone is not deleted until its zone has no clients to prevent exploits
// clients should be removed by zone-based kick timers if expired but not empty
DynamicZoneStatus status = dz->Process();
if (status == DynamicZoneStatus::ExpiredEmpty)
{
LogDynamicZones("[{}] expired with [{}] members, notifying zones and deleting", dz->GetID(), dz->GetMemberCount());
dynamic_zone_ids.emplace_back(dz->GetID());
dz->SendZonesDynamicZoneDeleted(); // delete dz from zone caches
}
}
if (!dynamic_zone_ids.empty())
{
for (const auto& dz_id : dynamic_zone_ids)
{
dynamic_zone_cache.erase(dz_id);
}
// need to look up expedition ids until lockouts are moved to dynamic zones
std::vector<uint32_t> expedition_ids;
auto expeditions = ExpeditionsRepository::GetWhere(database,
fmt::format("dynamic_zone_id IN ({})", Strings::Join(dynamic_zone_ids, ",")));
if (!expeditions.empty())
{
for (const auto& expedition : expeditions)
{
expedition_ids.emplace_back(expedition.id);
}
ExpeditionLockoutsRepository::DeleteWhere(database,
fmt::format("expedition_id IN ({})", Strings::Join(expedition_ids, ",")));
}
ExpeditionsRepository::DeleteWhere(database,
fmt::format("dynamic_zone_id IN ({})", Strings::Join(dynamic_zone_ids, ",")));
DynamicZoneMembersRepository::RemoveAllMembers(database, dynamic_zone_ids);
DynamicZonesRepository::DeleteWhere(database,
fmt::format("id IN ({})", Strings::Join(dynamic_zone_ids, ",")));
}
}
void DynamicZoneManager::LoadTemplates()
{
m_dz_templates.clear();
auto dz_templates = DynamicZoneTemplatesRepository::All(content_db);
for (const auto& dz_template : dz_templates)
{
m_dz_templates[dz_template.id] = dz_template;
}
}