[Zone] Implement zone player count sharding (#4536)

* [Zone] Implement zone player count sharding

* Update client.cpp

* Update database_instances.cpp

* You must request a shard change from the zone you are currently in.

* // zone sharding

* You cannot request a shard change while in combat.

* Query adjustment

* Use safe coords

* Changes

* Fixes to instance query

* Push

* Push

* Final push

* Update client.cpp

* Update eq_packet_structs.h

* Remove pick menu

* Comment

* Update character_data_repository.h

* Update zoning.cpp

---------

Co-authored-by: Kinglykrab <kinglykrab@gmail.com>
This commit is contained in:
Chris Miles
2025-01-08 17:41:16 -06:00
committed by GitHub
parent 15684567cf
commit c82f1b9afc
22 changed files with 443 additions and 22 deletions
+56 -8
View File
@@ -13037,19 +13037,19 @@ void Client::ClientToNpcAggroProcess()
const std::vector<int16>& Client::GetInventorySlots()
{
static const std::vector<std::pair<int16, int16>> slots = {
{ EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END },
{ EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END },
{ EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END },
{ EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END },
{ EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END },
{ EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END },
{ EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END },
{EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END},
{EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END},
{EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END},
{EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END},
{EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END},
{EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END},
{EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END},
};
static std::vector<int16> slot_ids;
if (slot_ids.empty()) {
for (const auto& [begin, end] : slots) {
for (const auto &[begin, end]: slots) {
for (int16 slot_id = begin; slot_id <= end; ++slot_id) {
slot_ids.emplace_back(slot_id);
}
@@ -13058,3 +13058,51 @@ const std::vector<int16>& Client::GetInventorySlots()
return slot_ids;
}
void Client::ShowZoneShardMenu()
{
auto z = GetZone(GetZoneID());
if (z && !z->shard_at_player_count) {
return;
}
auto results = CharacterDataRepository::GetInstanceZonePlayerCounts(database, GetZoneID());
LogZoning("Zone sharding results count [{}]", results.size());
if (results.empty()) {
Message(Chat::White, "No zone shards found.");
return;
}
if (!results.empty()) {
Message(Chat::White, "Available Zone Shards:");
}
int number = 1;
for (auto &e: results) {
std::string teleport_link = Saylink::Silent(
fmt::format("#zoneshard {} {}", e.zone_id, (e.instance_id == 0 ? -1 : e.instance_id)),
"Teleport"
);
std::string yours;
if (e.zone_id == GetZoneID() && e.instance_id == GetInstanceID()) {
teleport_link = "Teleport";
yours = " (Yours)";
}
Message(
Chat::White, fmt::format(
" --> [{}] #{} {} ({}) [{}/{}] players {}",
teleport_link,
number,
z->long_name,
e.instance_id,
e.player_count,
z->shard_at_player_count,
yours
).c_str()
);
number++;
}
}
+1
View File
@@ -2236,6 +2236,7 @@ private:
public:
const std::string &GetMailKeyFull() const;
const std::string &GetMailKey() const;
void ShowZoneShardMenu();
};
#endif
+17
View File
@@ -339,6 +339,7 @@ void MapOpcodes()
ConnectedOpcodes[OP_PlayerStateAdd] = &Client::Handle_OP_PlayerStateAdd;
ConnectedOpcodes[OP_PlayerStateRemove] = &Client::Handle_OP_PlayerStateRemove;
ConnectedOpcodes[OP_PickPocket] = &Client::Handle_OP_PickPocket;
ConnectedOpcodes[OP_PickZone] = &Client::Handle_OP_PickZone;
ConnectedOpcodes[OP_PopupResponse] = &Client::Handle_OP_PopupResponse;
ConnectedOpcodes[OP_PotionBelt] = &Client::Handle_OP_PotionBelt;
ConnectedOpcodes[OP_PurchaseLeadershipAA] = &Client::Handle_OP_PurchaseLeadershipAA;
@@ -941,6 +942,11 @@ void Client::CompleteConnect()
ShowDevToolsMenu();
}
auto z = GetZone(GetZoneID(), GetInstanceVersion());
if (z && z->shard_at_player_count > 0 && !RuleB(Zone, ZoneShardQuestMenuOnly)) {
ShowZoneShardMenu();
}
// shared tasks memberlist
if (RuleB(TaskSystem, EnableTaskSystem) && GetTaskState()->HasActiveSharedTask()) {
@@ -11839,6 +11845,17 @@ void Client::Handle_OP_PickPocket(const EQApplicationPacket *app)
SendPickPocketResponse(victim, 0, PickPocketFailed);
}
void Client::Handle_OP_PickZone(const EQApplicationPacket *app)
{
if (app->size != sizeof(PickZone_Struct)) {
LogDebug("Size mismatch in OP_PickZone expected [{}] got [{}]", sizeof(PickZone_Struct), app->size);
DumpPacket(app);
return;
}
// handle
}
void Client::Handle_OP_PopupResponse(const EQApplicationPacket *app)
{
+1
View File
@@ -242,6 +242,7 @@
void Handle_OP_PlayerStateAdd(const EQApplicationPacket *app);
void Handle_OP_PlayerStateRemove(const EQApplicationPacket *app);
void Handle_OP_PickPocket(const EQApplicationPacket *app);
void Handle_OP_PickZone(const EQApplicationPacket *app);
void Handle_OP_PopupResponse(const EQApplicationPacket *app);
void Handle_OP_PotionBelt(const EQApplicationPacket *app);
void Handle_OP_PurchaseLeadershipAA(const EQApplicationPacket *app);
+2
View File
@@ -243,6 +243,7 @@ int command_init(void)
command_add("zone", "[Zone ID|Zone Short Name] [X] [Y] [Z] - Teleport to specified Zone by ID or Short Name (coordinates are optional)", AccountStatus::Guide, command_zone) ||
command_add("zonebootup", "[ZoneServerID] [shortname] - Make a zone server boot a specific zone", AccountStatus::GMLeadAdmin, command_zonebootup) ||
command_add("zoneinstance", "[Instance ID] [X] [Y] [Z] - Teleport to specified Instance by ID (coordinates are optional)", AccountStatus::Guide, command_zone_instance) ||
command_add("zoneshard", "[zone] [instance_id] - Teleport explicitly to a zone shard", AccountStatus::Player, command_zone_shard) ||
command_add("zoneshutdown", "[shortname] - Shut down a zone server", AccountStatus::GMLeadAdmin, command_zoneshutdown) ||
command_add("zsave", " Saves zheader to the database", AccountStatus::QuestTroupe, command_zsave)
) {
@@ -936,4 +937,5 @@ void command_bot(Client *c, const Seperator *sep)
#include "gm_commands/zonebootup.cpp"
#include "gm_commands/zoneshutdown.cpp"
#include "gm_commands/zone_instance.cpp"
#include "gm_commands/zone_shard.cpp"
#include "gm_commands/zsave.cpp"
+1
View File
@@ -195,6 +195,7 @@ void command_wpadd(Client *c, const Seperator *sep);
void command_worldwide(Client *c, const Seperator *sep);
void command_zone(Client *c, const Seperator *sep);
void command_zone_instance(Client *c, const Seperator *sep);
void command_zone_shard(Client *c, const Seperator *sep);
void command_zonebootup(Client *c, const Seperator *sep);
void command_zoneshutdown(Client *c, const Seperator *sep);
void command_zopp(Client *c, const Seperator *sep);
+137
View File
@@ -0,0 +1,137 @@
#include "../client.h"
void command_zone_shard(Client *c, const Seperator *sep)
{
int arguments = sep->argnum;
if (!arguments || !sep->IsNumber(1)) {
if (!RuleB(Zone, ZoneShardQuestMenuOnly)) {
c->ShowZoneShardMenu();
}
return;
}
if (c->GetAggroCount() > 0) {
c->Message(Chat::White, "You cannot request a shard change while in combat.");
return;
}
std::string zone_input = sep->arg[1];
uint32 zone_id = 0;
// if input is id
if (Strings::IsNumber(zone_input)) {
zone_id = Strings::ToInt(zone_input);
// validate
if (zone_id != 0 && !GetZone(zone_id)) {
c->Message(Chat::White, fmt::format("Could not find zone by id [{}]", zone_id).c_str());
return;
}
}
else {
// validate
if (!zone_store.GetZone(zone_input)) {
c->Message(Chat::White, fmt::format("Could not find zone by short_name [{}]", zone_input).c_str());
return;
}
// validate we got id
zone_id = ZoneID(zone_input);
if (zone_id == 0) {
c->Message(Chat::White, fmt::format("Could not find zone id by short_name [{}]", zone_input).c_str());
return;
}
}
auto z = GetZone(zone_id);
if (z && z->shard_at_player_count == 0) {
c->Message(Chat::White, "Zone does not have sharding enabled.");
return;
}
auto instance_id = sep->arg[2] ? Strings::ToBigInt(sep->arg[2]) : 0;
if (instance_id < -1) {
c->Message(Chat::White, "You must enter a valid Instance ID.");
return;
}
if (zone_id == c->GetZoneID() && c->GetInstanceID() == instance_id) {
c->Message(Chat::White, "You are already in this shard.");
return;
}
if (zone_id != c->GetZoneID()) {
c->Message(Chat::White, "You must request a shard change from the zone you are currently in.");
return;
}
auto results = CharacterDataRepository::GetInstanceZonePlayerCounts(database, c->GetZoneID());
if (results.size() <= 1) {
c->Message(Chat::White, "No shards found.");
return;
}
if (instance_id > 0) {
if (!database.CheckInstanceExists(instance_id)) {
c->Message(
Chat::White,
fmt::format(
"Instance ID {} does not exist.",
instance_id
).c_str()
);
return;
}
auto instance_zone_id = database.GetInstanceZoneID(instance_id);
if (!instance_zone_id) {
c->Message(
Chat::White,
fmt::format(
"Instance ID {} not found or zone is set to null.",
instance_id
).c_str()
);
return;
}
if (instance_zone_id != zone_id) {
c->Message(
Chat::White,
fmt::format(
"Instance Zone ID {} does not match zone ID {}.",
instance_id,
zone_id
).c_str()
);
return;
}
if (!database.CheckInstanceByCharID(instance_id, c->CharacterID())) {
database.AddClientToInstance(instance_id, c->CharacterID());
}
if (!database.VerifyInstanceAlive(instance_id, c->CharacterID())) {
c->Message(
Chat::White,
fmt::format(
"Instance ID {} expired.",
instance_id
).c_str()
);
return;
}
}
c->MovePC(
zone_id,
instance_id,
c->GetX(),
c->GetY(),
c->GetZ(),
c->GetHeading(),
0,
ZoneSolicited
);
}
+12 -4
View File
@@ -3436,13 +3436,14 @@ void Lua_Client::AreaTaunt(float range, int bonus_hate)
entity_list.AETaunt(self, range, bonus_hate);
}
luabind::object Lua_Client::GetInventorySlots(lua_State* L) {
luabind::object Lua_Client::GetInventorySlots(lua_State* L)
{
auto lua_table = luabind::newtable(L);
if (d_) {
auto self = reinterpret_cast<NativeType*>(d_);
int index = 1;
for (const int16& slot_id : self->GetInventorySlots()) {
auto self = reinterpret_cast<NativeType *>(d_);
int index = 1;
for (const int16 &slot_id: self->GetInventorySlots()) {
lua_table[index] = slot_id;
index++;
}
@@ -3451,6 +3452,12 @@ luabind::object Lua_Client::GetInventorySlots(lua_State* L) {
return lua_table;
}
void Lua_Client::ShowZoneShardMenu()
{
Lua_Safe_Call_Void();
self->ShowZoneShardMenu();
}
luabind::scope lua_register_client() {
return luabind::class_<Lua_Client, Lua_Mob>("Client")
.def(luabind::constructor<>())
@@ -3970,6 +3977,7 @@ luabind::scope lua_register_client() {
.def("SetTint", (void(Lua_Client::*)(int,uint32))&Lua_Client::SetTint)
.def("SetTitleSuffix", (void(Lua_Client::*)(const char *))&Lua_Client::SetTitleSuffix)
.def("SetZoneFlag", (void(Lua_Client::*)(uint32))&Lua_Client::SetZoneFlag)
.def("ShowZoneShardMenu", (void(Lua_Client::*)(void))&Lua_Client::ShowZoneShardMenu)
.def("Signal", (void(Lua_Client::*)(int))&Lua_Client::Signal)
.def("Sit", (void(Lua_Client::*)(void))&Lua_Client::Sit)
.def("Stand", (void(Lua_Client::*)(void))&Lua_Client::Stand)
+1
View File
@@ -587,6 +587,7 @@ public:
void DialogueWindow(std::string markdown);
bool ReloadDataBuckets();
void ShowZoneShardMenu();
Lua_Expedition CreateExpedition(luabind::object expedition_info);
Lua_Expedition CreateExpedition(std::string zone_name, uint32 version, uint32 duration, std::string expedition_name, uint32 min_players, uint32 max_players);
+2 -1
View File
@@ -915,7 +915,8 @@ luabind::scope lua_register_packet_opcodes() {
luabind::value("Marquee", static_cast<int>(OP_Marquee)),
luabind::value("ClientTimeStamp", static_cast<int>(OP_ClientTimeStamp)),
luabind::value("GuildPromote", static_cast<int>(OP_GuildPromote)),
luabind::value("Fling", static_cast<int>(OP_Fling))
luabind::value("Fling", static_cast<int>(OP_Fling)),
luabind::value("PickZoneWindow", static_cast<int>(OP_PickZoneWindow))
)];
}
+6
View File
@@ -1756,6 +1756,11 @@ int Perl_Client_GetClientMaxLevel(Client* self)
return self->GetClientMaxLevel();
}
void Perl_Client_ShowZoneShardMenu(Client* self) // @categories Script Utility
{
self->ShowZoneShardMenu();
}
DynamicZoneLocation GetDynamicZoneLocationFromHash(perl::hash table)
{
// dynamic zone helper method, defaults invalid/missing keys to 0
@@ -3742,6 +3747,7 @@ void perl_register_client()
package.add("SetTitleSuffix", (void(*)(Client*, std::string))&Perl_Client_SetTitleSuffix);
package.add("SetTitleSuffix", (void(*)(Client*, std::string, bool))&Perl_Client_SetTitleSuffix);
package.add("SetZoneFlag", &Perl_Client_SetZoneFlag);
package.add("ShowZoneShardMenu", &Perl_Client_ShowZoneShardMenu);
package.add("Signal", &Perl_Client_Signal);
package.add("SignalClient", &Perl_Client_SignalClient);
package.add("SilentMessage", &Perl_Client_SilentMessage);
+60
View File
@@ -778,6 +778,66 @@ void Client::ZonePC(uint32 zoneID, uint32 instance_id, float x, float y, float z
}
}
// zone sharding
if (zoneID == zd->zoneidnumber &&
instance_id == 0 &&
zd->shard_at_player_count > 0) {
bool found_shard = false;
auto results = CharacterDataRepository::GetInstanceZonePlayerCounts(database, zoneID);
LogZoning("Zone sharding results count [{}]", results.size());
uint64_t shard_instance_duration = 3155760000;
for (auto &e: results) {
LogZoning(
"Zone sharding results [{}] ({}) instance_id [{}] player_count [{}]",
ZoneName(e.zone_id) ? ZoneName(e.zone_id) : "Unknown",
e.zone_id,
e.instance_id,
e.player_count
);
if (e.player_count < zd->shard_at_player_count) {
instance_id = e.instance_id;
database.AddClientToInstance(instance_id, CharacterID());
LogZoning(
"Client [{}] attempting zone to sharded zone > instance_id [{}] zone [{}] ({})",
GetCleanName(),
instance_id,
ZoneName(zoneID) ? ZoneName(zoneID) : "Unknown",
zoneID
);
found_shard = true;
break;
}
}
if (!found_shard) {
uint16 new_instance_id = 0;
database.GetUnusedInstanceID(new_instance_id);
database.CreateInstance(new_instance_id, zoneID, zd->version, shard_instance_duration);
database.AddClientToInstance(new_instance_id, CharacterID());
instance_id = new_instance_id;
LogZoning(
"Client [{}] creating new sharded zone > instance_id [{}] zone [{}] ({})",
GetCleanName(),
new_instance_id,
ZoneName(zoneID) ? ZoneName(zoneID) : "Unknown",
zoneID
);
}
}
// passed from zone shard request to normal zone
if (instance_id == -1) {
instance_id = 0;
}
LogInfo(
"Client [{}] zone_id [{}] instance_id [{}] x [{}] y [{}] z [{}] heading [{}] ignorerestrictions [{}] zone_mode [{}]",
GetCleanName(),