diff --git a/common/content/world_content_service.cpp b/common/content/world_content_service.cpp index 8669902ef..1e9b98000 100644 --- a/common/content/world_content_service.cpp +++ b/common/content/world_content_service.cpp @@ -1,28 +1,11 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - #include "world_content_service.h" + +#include +#include #include "../database.h" #include "../rulesys.h" #include "../eqemu_logsys.h" -#include "../repositories/content_flags_repository.h" +#include "../repositories/instance_list_repository.h" WorldContentService::WorldContentService() @@ -119,7 +102,7 @@ std::vector WorldContentService::GetContentFlagsDisabled() /** * @param content_flags */ -void WorldContentService::SetContentFlags(const std::vector& content_flags) +void WorldContentService::SetContentFlags(const std::vector &content_flags) { WorldContentService::content_flags = content_flags; } @@ -167,14 +150,14 @@ bool WorldContentService::DoesPassContentFiltering(const ContentFlags &f) } // if we don't have any enabled flag in enabled flags, we fail - for (const auto& flag: Strings::Split(f.content_flags)) { + for (const auto &flag: Strings::Split(f.content_flags)) { if (!Strings::Contains(GetContentFlagsEnabled(), flag)) { return false; } } // if we don't have any disabled flag in disabled flags, we fail - for (const auto& flag: Strings::Split(f.content_flags_disabled)) { + for (const auto &flag: Strings::Split(f.content_flags_disabled)) { if (!Strings::Contains(GetContentFlagsDisabled(), flag)) { return false; } @@ -200,6 +183,7 @@ void WorldContentService::ReloadContentFlags() } SetContentFlags(set_content_flags); + SetContentZones(ZoneRepository::All(*m_content_database)); } Database *WorldContentService::GetDatabase() const @@ -214,6 +198,18 @@ WorldContentService *WorldContentService::SetDatabase(Database *database) return this; } +Database *WorldContentService::GetContentDatabase() const +{ + return m_content_database; +} + +WorldContentService *WorldContentService::SetContentDatabase(Database *database) +{ + WorldContentService::m_content_database = database; + + return this; +} + void WorldContentService::SetContentFlag(const std::string &content_flag_name, bool enabled) { auto flags = ContentFlagsRepository::GetWhere( @@ -238,3 +234,85 @@ 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 +// lavastorm (pre-don) version 0 (classic) +// lavastorm (don) version 1 +// we want to route players to the correct version of lavastorm based on the current server side expansion +// in order to do that the simplest and cleanest way we intercept the zoning process and route players to an "instance" of the zone +// the reason why we're doing this is because all of the zoning logic already is handled by two keys "zone_id" and "instance_id" +// we can leverage static, never expires instances to handle this but to the client they don't see it any other way than a public normal zone +// 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) { + return; + } + + for (auto &z: m_zones) { + if (z.zoneidnumber == zc->zoneID) { + auto f = ContentFlags{ + .min_expansion = z.min_expansion, + .max_expansion = z.max_expansion, + .content_flags = z.content_flags, + .content_flags_disabled = z.content_flags_disabled + }; + + if (DoesPassContentFiltering(f)) { + LogInfo( + "Attempting to route player to zone [{}] ({}) version [{}] long_name [{}]", + z.short_name, + z.zoneidnumber, + z.version, + 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 + ) + ); + + 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; + } + } + } + } +} + diff --git a/common/content/world_content_service.h b/common/content/world_content_service.h index fb336da02..66d28556f 100644 --- a/common/content/world_content_service.h +++ b/common/content/world_content_service.h @@ -1,29 +1,10 @@ -/** - * EQEmulator: Everquest Server Emulator - * Copyright (C) 2001-2019 EQEmulator Development Team (https://github.com/EQEmu/Server) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY except by those people which sell it, which - * are required to give you total support for your newly bought product; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - #ifndef EQEMU_WORLD_CONTENT_SERVICE_H #define EQEMU_WORLD_CONTENT_SERVICE_H #include #include #include "../repositories/content_flags_repository.h" +#include "../repositories/zone_repository.h" class Database; @@ -182,14 +163,23 @@ public: WorldContentService * SetDatabase(Database *database); Database *GetDatabase() const; + WorldContentService * SetContentDatabase(Database *database); + Database *GetContentDatabase() const; + void SetContentFlag(const std::string &content_flag_name, bool enabled); + void HandleZoneRoutingMiddleware(ZoneChange_Struct *zc); + WorldContentService * SetContentZones(const std::vector& zones); private: int current_expansion{}; std::vector content_flags; // reference to database Database *m_database; + Database *m_content_database; + + // holds a record of the zone table from the database + std::vector m_zones = {}; }; extern WorldContentService content_service; diff --git a/common/database/database_update_manifest.cpp b/common/database/database_update_manifest.cpp index 7a02e28ac..8af76823d 100644 --- a/common/database/database_update_manifest.cpp +++ b/common/database/database_update_manifest.cpp @@ -5365,6 +5365,33 @@ ALTER TABLE `character_corpses` MODIFY COLUMN `time_of_death` datetime NOT NULL .sql = R"( ALTER TABLE `object_contents` MODIFY COLUMN `droptime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP; )" + }, + ManifestEntry{ + .version = 9263, + .description = "2024_02_16_rearrange_zone_columns.sql", + .check = "show columns from zone like 'note'", + .condition = "missing", + .match = "varchar(200)", + .sql = R"( +ALTER TABLE `zone` +MODIFY COLUMN `id` int(10) NOT NULL AUTO_INCREMENT FIRST, +MODIFY COLUMN `zoneidnumber` int(4) NOT NULL DEFAULT 0 AFTER `id`, +MODIFY COLUMN `version` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `zoneidnumber`, +MODIFY COLUMN `short_name` varchar(32) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL AFTER `version`, +MODIFY COLUMN `long_name` text CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL AFTER `short_name`, +MODIFY COLUMN `min_status` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 AFTER `long_name`, +MODIFY COLUMN `note` varchar(200) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL AFTER `map_file_name`, +MODIFY COLUMN `min_expansion` tinyint(4) NOT NULL DEFAULT -1 AFTER `note`, +MODIFY COLUMN `max_expansion` tinyint(4) NOT NULL DEFAULT -1 AFTER `min_expansion`, +MODIFY COLUMN `content_flags` varchar(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL AFTER `max_expansion`, +MODIFY COLUMN `content_flags_disabled` varchar(100) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL AFTER `content_flags`, +MODIFY COLUMN `expansion` tinyint(3) NOT NULL DEFAULT 0 AFTER `content_flags_disabled`, +MODIFY COLUMN `file_name` varchar(16) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL AFTER `expansion`, +MODIFY COLUMN `safe_x` float NOT NULL DEFAULT 0 AFTER `file_name`, +MODIFY COLUMN `safe_y` float NOT NULL DEFAULT 0 AFTER `safe_x`, +MODIFY COLUMN `safe_z` float NOT NULL DEFAULT 0 AFTER `safe_y`, +MODIFY COLUMN `safe_heading` float NOT NULL DEFAULT 0 AFTER `safe_z`; + )" } // -- template; copy/paste this when you need to create a new entry // ManifestEntry{ diff --git a/common/ruletypes.h b/common/ruletypes.h index 33094544d..a4e181326 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -890,7 +890,7 @@ RULE_INT(Expansion, AutoGrantAAExpansion, -1, "Expansion to auto grant AAs up to RULE_CATEGORY_END() RULE_CATEGORY(Instances) -RULE_INT(Instances, ReservedInstances, 30, "Number of instance IDs which are reserved for globals. This value should not be changed while a server is running") +RULE_INT(Instances, ReservedInstances, 100, "Number of instance IDs which are reserved for globals. This value should not be changed while a server is running") RULE_BOOL(Instances, RecycleInstanceIds, true, "Setting whether free instance IDs should be recycled to prevent them from gradually running out at 32k") RULE_INT(Instances, GuildHallExpirationDays, 90, "Amount of days before a Guild Hall instance expires") RULE_CATEGORY_END() diff --git a/common/version.h b/common/version.h index 9130b7042..d6e1b873f 100644 --- a/common/version.h +++ b/common/version.h @@ -42,7 +42,7 @@ * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9262 +#define CURRENT_BINARY_DATABASE_VERSION 9263 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9042 #endif diff --git a/shared_memory/main.cpp b/shared_memory/main.cpp index 58ebaa444..9c4e84f40 100644 --- a/shared_memory/main.cpp +++ b/shared_memory/main.cpp @@ -168,6 +168,7 @@ int main(int argc, char **argv) content_service.SetCurrentExpansion(RuleI(Expansion, CurrentExpansion)); content_service.SetDatabase(&database) + ->SetContentDatabase(&content_db) ->SetExpansionContext() ->ReloadContentFlags(); diff --git a/world/world_boot.cpp b/world/world_boot.cpp index b05e3abc7..86ae466ec 100644 --- a/world/world_boot.cpp +++ b/world/world_boot.cpp @@ -381,6 +381,7 @@ bool WorldBoot::DatabaseLoadRoutines(int argc, char **argv) LogInfo("Initializing [WorldContentService]"); content_service.SetDatabase(&database) + ->SetContentDatabase(&content_db) ->SetExpansionContext() ->ReloadContentFlags(); diff --git a/zone/client.cpp b/zone/client.cpp index 43763defd..5782130bd 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -71,6 +71,7 @@ extern volatile bool RunLoops; #include "../common/events/player_events.h" #include "../common/events/player_event_logs.h" #include "dialogue_window.h" +#include "../common/zone_store.h" extern QueryServ* QServ; @@ -9108,6 +9109,7 @@ void Client::ShowDevToolsMenu() */ menu_show += Saylink::Silent("#showzonepoints", "Zone Points"); menu_show += " | " + Saylink::Silent("#showzonegloballoot", "Zone Global Loot"); + menu_show += " | " + Saylink::Silent("#show content_flags", "Content Flags"); /** * Reload @@ -9165,14 +9167,6 @@ void Client::ShowDevToolsMenu() Message(Chat::White, "Developer Tools Menu"); - Message( - Chat::White, - fmt::format( - "Current Expansion | {}", - content_service.GetCurrentExpansionName() - ).c_str() - ); - Message( Chat::White, fmt::format( @@ -9288,6 +9282,36 @@ void Client::ShowDevToolsMenu() ); SendChatLineBreak(); + + Message( + Chat::White, + fmt::format( + "Current Expansion | {} ({})", + content_service.GetCurrentExpansionName(), + content_service.GetCurrentExpansion() + ).c_str() + ); + + + auto z = GetZoneVersionWithFallback(zone->GetZoneID(), zone->GetInstanceVersion()); + + if (z) { + Message( + Chat::White, + fmt::format( + "Current Zone | [{}] ({}) version [{}] instance_id [{}] min/max expansion ({}/{}) content_flags [{}]", + z->short_name, + z->long_name, + z->version, + zone->GetInstanceID(), + z->min_expansion, + z->max_expansion, + z->content_flags + ).c_str() + ); + } + + SendChatLineBreak(); } void Client::SendChatLineBreak(uint16 color) { diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index bd556ccd7..8f1355f52 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -954,7 +954,6 @@ void Client::CompleteConnect() heroforge_wearchange_timer.Start(250); RecordStats(); - AutoGrantAAPoints(); // enforce some rules.. @@ -17138,3 +17137,4 @@ void Client::Handle_OP_GuildTributeDonatePlat(const EQApplicationPacket *app) RequestGuildFavorAndTimer(GuildID()); } } + diff --git a/zone/client_process.cpp b/zone/client_process.cpp index a35be86f0..c900add67 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -1072,7 +1072,7 @@ void Client::OPRezzAnswer(uint32 Action, uint32 SpellID, uint16 ZoneID, uint16 I SetMana(GetMaxMana() / 20); SetEndurance(GetMaxEndurance() / 20); } - + if(spells[SpellID].base_value[0] < 100 && spells[SpellID].base_value[0] > 0 && PendingRezzXP > 0) { SetEXP(((int)(GetEXP()+((float)((PendingRezzXP / 100) * spells[SpellID].base_value[0])))), GetAAXP(),true); diff --git a/zone/gm_commands/show.cpp b/zone/gm_commands/show.cpp index b1aab35cd..5676be69b 100755 --- a/zone/gm_commands/show.cpp +++ b/zone/gm_commands/show.cpp @@ -5,6 +5,7 @@ #include "show/buffs.cpp" #include "show/buried_corpse_count.cpp" #include "show/client_version_summary.cpp" +#include "show/content_flags.cpp" #include "show/currencies.cpp" #include "show/distance.cpp" #include "show/emotes.cpp" @@ -64,6 +65,7 @@ void command_show(Client *c, const Seperator *sep) Cmd{.cmd = "buffs", .u = "buffs", .fn = ShowBuffs, .a = {"#showbuffs"}}, Cmd{.cmd = "buried_corpse_count", .u = "buried_corpse_count", .fn = ShowBuriedCorpseCount, .a = {"#getplayerburiedcorpsecount"}}, Cmd{.cmd = "client_version_summary", .u = "client_version_summary", .fn = ShowClientVersionSummary, .a = {"#cvs"}}, + Cmd{.cmd = "content_flags", .u = "content_flags", .fn = ShowContentFlags, .a = {"#showcontentflags"}}, Cmd{.cmd = "currencies", .u = "currencies", .fn = ShowCurrencies, .a = {"#viewcurrencies"}}, Cmd{.cmd = "distance", .u = "distance", .fn = ShowDistance, .a = {"#distance"}}, Cmd{.cmd = "emotes", .u = "emotes", .fn = ShowEmotes, .a = {"#emoteview"}}, diff --git a/zone/gm_commands/show/content_flags.cpp b/zone/gm_commands/show/content_flags.cpp new file mode 100644 index 000000000..bba89ce18 --- /dev/null +++ b/zone/gm_commands/show/content_flags.cpp @@ -0,0 +1,30 @@ +#include "../../client.h" +#include "../../dialogue_window.h" + +void ShowContentFlags(Client *c, const Seperator *sep) +{ + Client *t = c; + if (c->GetTarget() && c->GetTarget()->IsClient()) { + t = c->GetTarget()->CastToClient(); + } + + std::string flags = DialogueWindow::TableRow( + DialogueWindow::TableCell("id") + + DialogueWindow::TableCell("flag_name") + + DialogueWindow::TableCell("enabled") + ); + + for (auto &f: ContentFlagsRepository::All(database)) { + flags += DialogueWindow::TableRow( + DialogueWindow::TableCell(std::to_string(f.id)) + + DialogueWindow::TableCell(f.flag_name) + + DialogueWindow::TableCell( + f.enabled ? + DialogueWindow::ColorMessage("forest_green", "yes") : + DialogueWindow::ColorMessage("red", "no") + ) + ); + } + + c->SendPopupToClient("Server Content Flag Settings", DialogueWindow::Table(flags).c_str()); +} diff --git a/zone/main.cpp b/zone/main.cpp index 88ba232a9..8fd770842 100644 --- a/zone/main.cpp +++ b/zone/main.cpp @@ -402,6 +402,8 @@ int main(int argc, char **argv) } content_service.SetDatabase(&database) + ->SetContentDatabase(&content_db) + ->SetContentZones(zone_store.GetZones()) ->SetExpansionContext() ->ReloadContentFlags(); diff --git a/zone/quest_parser_collection.cpp b/zone/quest_parser_collection.cpp index c14a8cf4c..1d7177c9b 100644 --- a/zone/quest_parser_collection.cpp +++ b/zone/quest_parser_collection.cpp @@ -830,11 +830,21 @@ QuestInterface* QuestParserCollection::GetQIByNPCQuest(uint32 npc_id, std::strin zone->GetShortName() ); + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}", + path.GetQuestsPath(), + zone->GetShortName(), + zone->GetInstanceVersion() + ); + std::vector file_names = { + fmt::format("{}/{}", zone_versioned_path, npc_id), // Local versioned by NPC ID ./quests/zone/v0/10.ext + fmt::format("{}/{}", zone_versioned_path, npc_name), // Local versioned by NPC Name ./quests/zone/v0/npc.ext fmt::format("{}/{}", zone_path, npc_id), // Local by NPC ID fmt::format("{}/{}", zone_path, npc_name), // Local by NPC Name fmt::format("{}/{}", global_path, npc_id), // Global by NPC ID fmt::format("{}/{}", global_path, npc_name), // Global by NPC ID + fmt::format("{}/default", zone_versioned_path), // Zone Default ./quests/zone/v0/default.ext fmt::format("{}/default", zone_path), // Zone Default fmt::format("{}/default", global_path), // Global Default }; @@ -877,7 +887,15 @@ QuestInterface* QuestParserCollection::GetQIByPlayerQuest(std::string& filename) zone->GetShortName() ); + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}", + path.GetQuestsPath(), + zone->GetShortName(), + zone->GetInstanceVersion() + ); + std::vector file_names = { + fmt::format("{}/player", zone_versioned_path), // Local by Instance Version ./quests/zone/v0/player.ext fmt::format("{}/player_v{}", zone_path, zone->GetInstanceVersion()), // Local by Instance Version fmt::format("{}/player", zone_path), // Local fmt::format("{}/player", global_path) // Global @@ -969,7 +987,15 @@ QuestInterface* QuestParserCollection::GetQIBySpellQuest(uint32 spell_id, std::s zone->GetShortName() ); + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}/spells", + path.GetQuestsPath(), + zone->GetShortName(), + zone->GetInstanceVersion() + ); + std::vector file_names = { + fmt::format("{}/{}", zone_versioned_path, spell_id), // Local versioned by Spell ID ./quests/zone/v0/spells/10.ext fmt::format("{}/{}", zone_path, spell_id), // Local fmt::format("{}/{}", global_path, spell_id), // Global fmt::format("{}/default", zone_path), // Local Default @@ -1013,7 +1039,15 @@ QuestInterface* QuestParserCollection::GetQIByItemQuest(std::string item_script, zone->GetShortName() ); + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}/items", + path.GetQuestsPath(), + zone->GetShortName(), + zone->GetInstanceVersion() + ); + std::vector file_names = { + fmt::format("{}/{}", zone_versioned_path, item_script), // Local versioned by Item Script ./quests/zone/v0/items/10.ext fmt::format("{}/{}", zone_path, item_script), // Local fmt::format("{}/{}", global_path, item_script), // Global fmt::format("{}/default", zone_path), // Local Default @@ -1057,7 +1091,15 @@ QuestInterface* QuestParserCollection::GetQIByEncounterQuest(std::string encount zone->GetShortName() ); + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}/encounters", + path.GetQuestsPath(), + zone->GetShortName(), + zone->GetInstanceVersion() + ); + std::vector file_names = { + fmt::format("{}/{}", zone_versioned_path, encounter_name), // Local versioned ./quests/zone/v0/encounters/name.ext fmt::format("{}/{}", zone_path, encounter_name), // Local fmt::format("{}/{}", global_path, encounter_name) // Global }; @@ -1099,7 +1141,15 @@ QuestInterface* QuestParserCollection::GetQIByBotQuest(std::string& filename) zone->GetShortName() ); + const std::string& zone_versioned_path = fmt::format( + "{}/{}/v{}", + path.GetQuestsPath(), + zone->GetShortName(), + zone->GetInstanceVersion() + ); + std::vector file_names = { + fmt::format("{}/bot", zone_versioned_path), // Local versioned by Instance Version ./quests/zone/v0/bot.ext fmt::format("{}/bot_v{}", zone_path, zone->GetInstanceVersion()), // Local by Instance Version fmt::format("{}/bot", zone_path), // Local fmt::format("{}/bot", global_path) // Global diff --git a/zone/zoning.cpp b/zone/zoning.cpp index d1a988940..9456293a2 100644 --- a/zone/zoning.cpp +++ b/zone/zoning.cpp @@ -72,6 +72,8 @@ void Client::Handle_OP_ZoneChange(const EQApplicationPacket *app) { int(zone_mode) ); + content_service.HandleZoneRoutingMiddleware(zc); + uint16 target_zone_id = 0; auto target_instance_id = zc->instanceID; ZonePoint* zone_point = nullptr;