diff --git a/common/content/world_content_service.cpp b/common/content/world_content_service.cpp index 1e9b98000..c584d0644 100644 --- a/common/content/world_content_service.cpp +++ b/common/content/world_content_service.cpp @@ -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& 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::vectorinstanceID > 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(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(z.zoneidnumber), + .instance = InstanceListRepository::NewEntity(), + .zone = z + }; } } } + + return WorldContentService::FindZoneResult{}; } diff --git a/common/content/world_content_service.h b/common/content/world_content_service.h index 66d28556f..9d8ce82af 100644 --- a/common/content/world_content_service.h +++ b/common/content/world_content_service.h @@ -5,6 +5,7 @@ #include #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& 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 content_flags; @@ -180,6 +188,9 @@ private: // holds a record of the zone table from the database std::vector m_zones = {}; + WorldContentService *LoadStaticGlobalZoneInstances(); + std::vector m_zone_instances; + WorldContentService * LoadZones(); }; extern WorldContentService content_service; diff --git a/world/client.cpp b/world/client.cpp index 30c0c266d..90937fac4 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -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 #include @@ -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); diff --git a/world/eqemu_api_world_data_service.cpp b/world/eqemu_api_world_data_service.cpp index 02944e53b..c3013a4de 100644 --- a/world/eqemu_api_world_data_service.cpp +++ b/world/eqemu_api_world_data_service.cpp @@ -142,6 +142,7 @@ std::vector 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"}, diff --git a/world/main.cpp b/world/main.cpp index 0cff3fc20..a2a908128 100644 --- a/world/main.cpp +++ b/world/main.cpp @@ -188,6 +188,11 @@ int main(int argc, char **argv) RegisterConsoleFunctions(console); } + content_service.SetDatabase(&database) + ->SetContentDatabase(&content_db) + ->SetExpansionContext() + ->ReloadContentFlags(); + std::unique_ptr server_connection; server_connection = std::make_unique(); diff --git a/zone/client.cpp b/zone/client.cpp index 1bc6a8d96..f82688080 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -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(); diff --git a/zone/doors.cpp b/zone/doors.cpp index 98cbccb06..ab07509f3 100644 --- a/zone/doors.cpp +++ b/zone/doors.cpp @@ -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; diff --git a/zone/main.cpp b/zone/main.cpp index 8fd770842..7d6a17ac7 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -403,7 +403,6 @@ int main(int argc, char **argv) content_service.SetDatabase(&database) ->SetContentDatabase(&content_db) - ->SetContentZones(zone_store.GetZones()) ->SetExpansionContext() ->ReloadContentFlags(); diff --git a/zone/zoning.cpp b/zone/zoning.cpp index 9456293a2..c095f7ad0 100644 --- a/zone/zoning.cpp +++ b/zone/zoning.cpp @@ -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(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); }