[Zone] Zone Routing Improvements (#4142)

* Routing changes

* Update world_content_service.cpp

* Cleanup routing logic

* Tweaks
This commit is contained in:
Chris Miles 2024-03-02 15:19:42 -06:00 committed by GitHub
parent 70ee95efc0
commit b6c3e549da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 185 additions and 52 deletions

View File

@ -178,12 +178,13 @@ void WorldContentService::ReloadContentFlags()
LogInfo(
"Loaded content flag [{}] [{}]",
f.flag_name,
(f.enabled ? "Enabled" : "Disabled")
(f.enabled ? "enabled" : "disabled")
);
}
SetContentFlags(set_content_flags);
SetContentZones(ZoneRepository::All(*m_content_database));
LoadZones();
LoadStaticGlobalZoneInstances();
}
Database *WorldContentService::GetDatabase() const
@ -235,19 +236,6 @@ void WorldContentService::SetContentFlag(const std::string &content_flag_name, b
ReloadContentFlags();
}
// SetZones sets the zones for the world content service
// this is used for zone routing middleware
// we pull the zone list from the zone repository and feed from the zone store for now
// we're holding a copy in the content service - but we're talking 250kb of data in memory to handle routing of zoning
WorldContentService *WorldContentService::SetContentZones(const std::vector<BaseZoneRepository::Zone>& zones)
{
m_zones = zones;
LogInfo("Loaded [{}] zones", m_zones.size());
return this;
}
// HandleZoneRoutingMiddleware is meant to handle content and context aware zone routing
//
// example # 1
@ -260,16 +248,52 @@ WorldContentService *WorldContentService::SetContentZones(const std::vector<Base
// scripts handle all the same way, you don't have to think about instances, the middleware will handle the magic
// the versions of zones are represented by two zone entries that have potentially different min/max expansion and/or different content flags
// we decide to route the client to the correct version of the zone based on the current server side expansion
// example # 2
void WorldContentService::HandleZoneRoutingMiddleware(ZoneChange_Struct *zc)
{
// if we're already in an instance, we don't want to route the player to another instance
if (zc->instanceID > 0) {
auto r = FindZone(zc->zoneID, zc->instanceID);
if (r.zone_id == 0) {
return;
}
zc->instanceID = r.instance.id;
}
// LoadStaticGlobalZoneInstances loads all static global zone instances
// these are zones that are never set to expire and are global
// these are used commonly in v1/v2/v3 versions of the same zone for expansion routing
WorldContentService * WorldContentService::LoadStaticGlobalZoneInstances()
{
m_zone_instances = InstanceListRepository::GetWhere(*GetDatabase(), fmt::format("never_expires = 1 AND is_global = 1"));
LogInfo("Loaded [{}] zone_instances", m_zone_instances.size());
return this;
}
// LoadZones sets the zones for the world content service
// this is used for zone routing middleware
// we pull the zone list from the zone repository and feed from the zone store for now
// we're holding a copy in the content service - but we're talking 250kb of data in memory to handle routing of zoning
WorldContentService * WorldContentService::LoadZones()
{
m_zones = ZoneRepository::All(*GetContentDatabase());
LogInfo("Loaded [{}] zones", m_zones.size());
return this;
}
// FindZone is critical to the zone routing middleware and any logic that needs to route players to the correct zone
// era contextual routing, multiple version of zones, etc
WorldContentService::FindZoneResult WorldContentService::FindZone(uint32 zone_id, uint32 instance_id)
{
// if we're already in a regular instance, we don't want to route the player to another instance
if (instance_id > RuleI(Instances, ReservedInstances)) {
return WorldContentService::FindZoneResult{};
}
for (auto &z: m_zones) {
if (z.zoneidnumber == zc->zoneID) {
if (z.zoneidnumber == zone_id) {
auto f = ContentFlags{
.min_expansion = z.min_expansion,
.max_expansion = z.max_expansion,
@ -286,33 +310,45 @@ void WorldContentService::HandleZoneRoutingMiddleware(ZoneChange_Struct *zc)
z.long_name
);
auto instances = InstanceListRepository::GetWhere(
*GetDatabase(),
fmt::format(
"zone = {} AND version = {} AND never_expires = 1 AND is_global = 1",
z.zoneidnumber,
z.version
)
// first pass, explicit match on public static global zone instances
for (auto &i: m_zone_instances) {
if (i.zone == zone_id && i.version == z.version) {
LogInfo(
"Routed player to instance [{}] of zone [{}] ({}) version [{}] long_name [{}] notes [{}]",
i.id,
z.short_name,
z.zoneidnumber,
z.version,
z.long_name,
i.notes
);
return WorldContentService::FindZoneResult{
.zone_id = static_cast<uint32>(z.zoneidnumber),
.instance = i,
.zone = z
};
}
}
LogInfo(
"Routed player to non-instance zone [{}] ({}) version [{}] long_name [{}] notes [{}]",
z.short_name,
z.zoneidnumber,
z.version,
z.long_name,
z.note
);
if (!instances.empty()) {
auto instance = instances.front();
zc->instanceID = instance.id;
LogInfo(
"Routed player to instance [{}] of zone [{}] ({}) version [{}] long_name [{}] notes [{}]",
instance.id,
z.short_name,
z.zoneidnumber,
z.version,
z.long_name,
instance.notes
);
break;
}
return WorldContentService::FindZoneResult{
.zone_id = static_cast<uint32>(z.zoneidnumber),
.instance = InstanceListRepository::NewEntity(),
.zone = z
};
}
}
}
return WorldContentService::FindZoneResult{};
}

View File

@ -5,6 +5,7 @@
#include <vector>
#include "../repositories/content_flags_repository.h"
#include "../repositories/zone_repository.h"
#include "../repositories/instance_list_repository.h"
class Database;
@ -169,7 +170,14 @@ public:
void SetContentFlag(const std::string &content_flag_name, bool enabled);
void HandleZoneRoutingMiddleware(ZoneChange_Struct *zc);
WorldContentService * SetContentZones(const std::vector<ZoneRepository::Zone>& zones);
struct FindZoneResult {
uint32 zone_id = 0;
InstanceListRepository::InstanceList instance;
ZoneRepository::Zone zone;
};
FindZoneResult FindZone(uint32 zone_id, uint32 instance_id);
private:
int current_expansion{};
std::vector<ContentFlagsRepository::ContentFlags> content_flags;
@ -180,6 +188,9 @@ private:
// holds a record of the zone table from the database
std::vector<ZoneRepository::Zone> m_zones = {};
WorldContentService *LoadStaticGlobalZoneInstances();
std::vector<InstanceListRepository::InstanceList> m_zone_instances;
WorldContentService * LoadZones();
};
extern WorldContentService content_service;

View File

@ -52,6 +52,7 @@
#include "../common/repositories/player_event_logs_repository.h"
#include "../common/repositories/inventory_repository.h"
#include "../common/events/player_event_logs.h"
#include "../common/content/world_content_service.h"
#include <iostream>
#include <iomanip>
@ -771,6 +772,18 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) {
return true;
}
auto r = content_service.FindZone(zone_id, instance_id);
if (r.zone_id && r.instance.id != instance_id) {
LogInfo(
"Zone [{}] has been remapped to instance_id [{}] from instance_id [{}] for client [{}]",
r.zone.short_name,
r.instance.id,
instance_id,
char_name
);
instance_id = r.instance.id;
}
// Make sure this account owns this character
if (temporary_account_id != account_id) {
LogInfo("Account [{}] does not own the character named [{}] from account [{}]", account_id, char_name, temporary_account_id);

View File

@ -142,6 +142,7 @@ std::vector<Reload> reload_types = {
Reload{.command = "base_data", .opcode = ServerOP_ReloadBaseData, .desc = "Base Data"},
Reload{.command = "blocked_spells", .opcode = ServerOP_ReloadBlockedSpells, .desc = "Blocked Spells"},
Reload{.command = "commands", .opcode = ServerOP_ReloadCommands, .desc = "Commands"},
Reload{.command = "content_flags", .opcode = ServerOP_ReloadContentFlags, .desc = "Content Flags"},
Reload{.command = "data_buckets_cache", .opcode = ServerOP_ReloadDataBucketsCache, .desc = "Data Buckets Cache"},
Reload{.command = "doors", .opcode = ServerOP_ReloadDoors, .desc = "Doors"},
Reload{.command = "dztemplates", .opcode = ServerOP_ReloadDzTemplates, .desc = "Dynamic Zone Templates"},

View File

@ -188,6 +188,11 @@ int main(int argc, char **argv)
RegisterConsoleFunctions(console);
}
content_service.SetDatabase(&database)
->SetContentDatabase(&content_db)
->SetExpansionContext()
->ReloadContentFlags();
std::unique_ptr<EQ::Net::ServertalkServer> server_connection;
server_connection = std::make_unique<EQ::Net::ServertalkServer>();

View File

@ -6285,7 +6285,17 @@ void Client::SendZonePoints()
zp->zpe[i].z = data->target_z;
zp->zpe[i].heading = data->target_heading;
zp->zpe[i].zoneid = data->target_zone_id;
zp->zpe[i].zoneinstance = data->target_zone_instance;
// if the target zone is the same as the current zone, use the instance of the current zone
// if we don't use the same instance_id that the client was sent, the client will forcefully
// issue a zone change request when they should be simply moving to a different point in the same zone
// because the client will think the zone point target is different from the current instance
auto target_instance = data->target_zone_instance;
if (data->target_zone_id == zone->GetZoneID() && data->target_zone_instance == 0) {
target_instance = zone->GetInstanceID();
}
zp->zpe[i].zoneinstance = target_instance;
i++;
}
iterator.Advance();

View File

@ -73,6 +73,15 @@ Doors::Doors(const DoorsRepository::Doors &door) :
m_door_param = door.door_param;
m_size = door.size;
m_invert_state = door.invert_state;
// if the target zone is the same as the current zone, use the instance of the current zone
// if we don't use the same instance_id that the client was sent, the client will forcefully
// issue a zone change request when they should be simply moving to a different point in the same zone
// because the client will think the zone point target is different from the current instance
if (door.dest_zone == zone->GetShortName() && m_destination_instance_id == 0) {
m_destination_instance_id = zone->GetInstanceID();
}
m_destination_instance_id = door.dest_instance;
m_is_ldon_door = door.is_ldon_door;
m_dz_switch_id = door.dz_switch_id;

View File

@ -403,7 +403,6 @@ int main(int argc, char **argv)
content_service.SetDatabase(&database)
->SetContentDatabase(&content_db)
->SetContentZones(zone_store.GetZones())
->SetExpansionContext()
->ReloadContentFlags();

View File

@ -405,11 +405,23 @@ void Client::SendZoneCancel(ZoneChange_Struct *zc) {
zc->success
);
strcpy(zc2->char_name, zc->char_name);
strn0cpy(zc2->char_name, zc->char_name, 64);
zc2->zoneID = zone->GetZoneID();
zc2->success = 1;
outapp->priority = 6;
FastQueuePacket(&outapp);
zc2->instanceID = zone->GetInstanceID();
// this fixes an issue where when we do a zone cancel what often ends up happening is we are sending
// the client the wrong coordinates to zone back to. Often times it is the x,y,z of the destination zone
// because we saved the destination x,y,z on the client profile before we rejected the zone request.
// we're using rewind location because it should be where the client relatively was before we rejected the zone request.
// it also prevents the client from getting caught up in a zone loop because if we sent them exactly back to where they
// originated the request we could end up in a situation where the client is caught in a zone loop.
m_Position.x = m_RewindLocation.x;
m_Position.y = m_RewindLocation.y;
m_Position.z = m_RewindLocation.z;
zc2->x = m_Position.x;
zc2->y = m_Position.y;
zc2->z = m_Position.z;
LogZoning(
"(zc2) Client [{}] char_name [{}] zoning to [{}] ({}) cancelled instance_id [{}] x [{}] y [{}] z [{}] zone_reason [{}] success [{}]",
@ -425,6 +437,9 @@ void Client::SendZoneCancel(ZoneChange_Struct *zc) {
zc2->success
);
outapp->priority = 6;
FastQueuePacket(&outapp);
//reset to unsolicited.
zone_mode = ZoneUnsolicited;
// reset since we're not zoning anymore
@ -740,10 +755,34 @@ void Client::ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z
pZoneName = strcpy(new char[zd->long_name.length() + 1], zd->long_name.c_str());
}
// If we are zoning to the same zone, we need to use the current instance ID if it is not specified.
if (zoneID == zone->GetZoneID() && instance_id == 0) {
instance_id = zone->GetInstanceID();
}
auto r = content_service.FindZone(zoneID, instance_id);
if (r.zone_id) {
zoneID = r.zone_id;
instance_id = r.instance.id;
LogZoning(
"Client caught HandleZoneRoutingMiddleware [{}] zone_id [{}] instance_id [{}] x [{}] y [{}] z [{}] heading [{}] ignorerestrictions [{}] zone_mode [{}]",
GetCleanName(),
zoneID,
instance_id,
x,
y,
z,
heading,
ignorerestrictions,
static_cast<int>(zm)
);
}
LogInfo(
"Client [{}] zone_id [{}] x [{}] y [{}] z [{}] heading [{}] ignorerestrictions [{}] zone_mode [{}]",
"Client [{}] zone_id [{}] instance_id [{}] x [{}] y [{}] z [{}] heading [{}] ignorerestrictions [{}] zone_mode [{}]",
GetCleanName(),
zoneID,
instance_id,
x,
y,
z,
@ -867,9 +906,8 @@ void Client::ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z
outapp->priority = 6;
FastQueuePacket(&outapp);
}
else if(zm == ZoneSolicited || zm == ZoneToSafeCoords) {
auto outapp =
new EQApplicationPacket(OP_RequestClientZoneChange, sizeof(RequestClientZoneChange_Struct));
else if (zm == ZoneSolicited || zm == ZoneToSafeCoords) {
auto outapp = new EQApplicationPacket(OP_RequestClientZoneChange, sizeof(RequestClientZoneChange_Struct));
RequestClientZoneChange_Struct* gmg = (RequestClientZoneChange_Struct*) outapp->pBuffer;
gmg->zone_id = zoneID;
@ -880,6 +918,17 @@ void Client::ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z
gmg->instance_id = instance_id;
gmg->type = 0x01; //an observed value, not sure of meaning
LogZoning(
"Player [{}] has requested zoning to zone_id [{}] instance_id [{}] x [{}] y [{}] z [{}] heading [{}]",
GetCleanName(),
zoneID,
instance_id,
x,
y,
z,
heading
);
outapp->priority = 6;
FastQueuePacket(&outapp);
}