diff --git a/dDatabase/CDClientDatabase/CDClientManager.cpp b/dDatabase/CDClientDatabase/CDClientManager.cpp index 7d720b9ac..426a4f621 100644 --- a/dDatabase/CDClientDatabase/CDClientManager.cpp +++ b/dDatabase/CDClientDatabase/CDClientManager.cpp @@ -23,6 +23,7 @@ #include "CDPhysicsComponentTable.h" #include "CDRebuildComponentTable.h" #include "CDCollectibleComponentTable.h" +#include "CDEventGatingTable.h" #include "CDScriptComponentTable.h" #include "CDSkillBehaviorTable.h" #include "CDZoneTableTable.h" @@ -102,6 +103,7 @@ DEFINE_TABLE_STORAGE(CDRailActivatorComponentTable); DEFINE_TABLE_STORAGE(CDRarityTableTable); DEFINE_TABLE_STORAGE(CDRebuildComponentTable); DEFINE_TABLE_STORAGE(CDCollectibleComponentTable); +DEFINE_TABLE_STORAGE(CDEventGatingTable); DEFINE_TABLE_STORAGE(CDRewardCodesTable); DEFINE_TABLE_STORAGE(CDRewardsTable); DEFINE_TABLE_STORAGE(CDScriptComponentTable); @@ -126,6 +128,7 @@ void CDClientManager::LoadValuesFromDatabase() { CDDeletionRestrictionsTable::Instance().LoadValuesFromDatabase(); CDDestructibleComponentTable::Instance().LoadValuesFromDatabase(); CDEmoteTableTable::Instance().LoadValuesFromDatabase(); + CDEventGatingTable::Instance().LoadValuesFromDatabase(); CDFeatureGatingTable::Instance().LoadValuesFromDatabase(); CDInventoryComponentTable::Instance().LoadValuesFromDatabase(); CDCLIENT_DONT_CACHE_TABLE(CDItemComponentTable::Instance().LoadValuesFromDatabase()); diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDEventGatingTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDEventGatingTable.cpp new file mode 100644 index 000000000..9c41b8397 --- /dev/null +++ b/dDatabase/CDClientDatabase/CDClientTables/CDEventGatingTable.cpp @@ -0,0 +1,22 @@ +#include "CDEventGatingTable.h" + +void CDEventGatingTable::LoadValuesFromDatabase() { + auto& entries = GetEntriesMutable(); + auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM EventGating"); + while (!tableData.eof()) { + CDEventGating entry; + entry.eventName = tableData.getStringField("eventName", ""); + entry.dateStart = tableData.getInt64Field("date_start", 0); + entry.dateEnd = tableData.getInt64Field("date_end", 0); + entries.push_back(entry); + tableData.nextRow(); + } + tableData.finalize(); +} + +bool CDEventGatingTable::IsEventActive(const std::string_view eventName, const int64_t unixTime) const { + for (const auto& entry : GetEntries()) { + if (entry.eventName == eventName && entry.dateStart <= unixTime && unixTime <= entry.dateEnd) return true; + } + return false; +} diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDEventGatingTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDEventGatingTable.h new file mode 100644 index 000000000..4c53a7ca3 --- /dev/null +++ b/dDatabase/CDClientDatabase/CDClientTables/CDEventGatingTable.h @@ -0,0 +1,28 @@ +#pragma once + +#include "CDTable.h" + +#include +#include +#include +#include + +/** + * EventGating: read only by the server (the 1.10.64 client has no string for the table). A holiday event and the + * Unix times (UTC) it ran between, inclusive: pirateDay 2011-09-19 00:00 to 2011-09-20 23:59:59, buildNexusTower, + * frostburgh2010_notused and test rows. Server Lua asked for an event with GetHolidayEvent{eventToCheck = name}.isValid + * (the Crux Prime random spawners switch to their pirateDay loads). + */ +struct CDEventGating { + std::string eventName; + int64_t dateStart{}; + int64_t dateEnd{}; +}; + +class CDEventGatingTable : public CDTable> { +public: + void LoadValuesFromDatabase(); + + // Whether the event has a row whose dates include the time + [[nodiscard]] bool IsEventActive(std::string_view eventName, int64_t unixTime) const; +}; diff --git a/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt index 0767ab663..56d057c10 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt +++ b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt @@ -9,6 +9,7 @@ set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp" "CDCurrencyTableTable.cpp" "CDDestructibleComponentTable.cpp" "CDEmoteTable.cpp" + "CDEventGatingTable.cpp" "CDFeatureGatingTable.cpp" "CDInventoryComponentTable.cpp" "CDItemComponentTable.cpp" diff --git a/dGame/dUtilities/CMakeLists.txt b/dGame/dUtilities/CMakeLists.txt index 5596d72e5..05115b9ba 100644 --- a/dGame/dUtilities/CMakeLists.txt +++ b/dGame/dUtilities/CMakeLists.txt @@ -7,6 +7,7 @@ set(DGAME_DUTILITIES_SOURCES "BrickDatabase.cpp" "DashboardNotify.cpp" "GUID.cpp" "LiveEvents.cpp" + "HolidayEvents.cpp" "Contraband.cpp" "PropertyBuilders.cpp" "PropertyRent.cpp" diff --git a/dGame/dUtilities/HolidayEvents.cpp b/dGame/dUtilities/HolidayEvents.cpp new file mode 100644 index 000000000..df2639684 --- /dev/null +++ b/dGame/dUtilities/HolidayEvents.cpp @@ -0,0 +1,29 @@ +#include "HolidayEvents.h" + +#include "CDClientManager.h" +#include "CDEventGatingTable.h" +#include "dConfig.h" +#include "Game.h" + +#include +#include +#include + +bool HolidayEvents::IsActive(const std::string_view eventName, const int64_t unixTime, const std::string_view* enabledEvents, const size_t enabledCount) { + if (eventName.empty()) return false; + for (size_t i = 0; i < enabledCount; i++) { + if (enabledEvents[i] == eventName) return true; + } + return CDClientManager::GetTable()->IsEventActive(eventName, unixTime); +} + +bool HolidayEvents::IsActive(const std::string_view eventName) { + static constexpr std::array KEYS = { "event_1", "event_2", "event_3", "event_4", "event_5", "event_6", "event_7", "event_8" }; + std::array settings; + std::array enabled; + for (size_t i = 0; i < KEYS.size(); i++) { + settings[i] = Game::config ? Game::config->GetValue(KEYS[i]) : ""; + enabled[i] = settings[i]; + } + return IsActive(eventName, static_cast(std::time(nullptr)), enabled.data(), enabled.size()); +} diff --git a/dGame/dUtilities/HolidayEvents.h b/dGame/dUtilities/HolidayEvents.h new file mode 100644 index 000000000..85377cd47 --- /dev/null +++ b/dGame/dUtilities/HolidayEvents.h @@ -0,0 +1,22 @@ +#ifndef HOLIDAYEVENTS_H +#define HOLIDAYEVENTS_H + +#include +#include + +/** + * Holiday events from the server-only EventGating table, as server scripts asked for them + * (GetHolidayEvent{eventToCheck}.isValid in the live Lua). + */ +namespace HolidayEvents { + /** + * Whether an event runs: its EventGating dates include now, or the server's event_1..event_8 setting names it (the + * live dates are all in 2010 and 2011, so this is how a server turns one on) + */ + bool IsActive(std::string_view eventName); + + // The same at a given Unix time, with the event_N settings passed in + bool IsActive(std::string_view eventName, int64_t unixTime, const std::string_view* enabledEvents, size_t enabledCount); +} + +#endif // HOLIDAYEVENTS_H diff --git a/dScripts/BaseRandomServer.cpp b/dScripts/BaseRandomServer.cpp index 4b67a5e35..39e973276 100644 --- a/dScripts/BaseRandomServer.cpp +++ b/dScripts/BaseRandomServer.cpp @@ -3,6 +3,7 @@ #include "Spawner.h" #include "Logger.h" #include "Entity.h" +#include "HolidayEvents.h" void BaseRandomServer::BaseStartup(Entity* self) { self->SetVar(u"SpawnState", "min"); @@ -13,7 +14,20 @@ void BaseRandomServer::BaseStartup(Entity* self) { } void BaseRandomServer::CheckEvents(Entity* self) { - // TODO: Add events? + // L_BASE_RANDOM_SPAWNER.lua: while a holiday event runs, its loads replace the spawner's. The only event the Crux + // Prime spawners check is pirateDay, with loads for the str and zip areas (each spawner's Lua carries both tables; + // each is used here for the area it names) + if (zoneName != "str" && zoneName != "zip") return; + if (!HolidayEvents::IsActive("pirateDay")) return; + + constexpr LOT pirate = 11215; + constexpr LOT admiral = 11216; + zones = { + { { { pirate, 5, "type1" }, { pirate, 5, "type2" }, { pirate, 5, "type3" } }, 80 }, + { { { admiral, 5, "type1" }, { admiral, 5, "type2" }, { admiral, 5, "type3" } }, 20 }, + }; + if (zoneName == "str") sectionMultipliers = { { "secA", 1.0f }, { "secB", 1.0f }, { "secC", 1.2f } }; + else sectionMultipliers = { { "secA", 1.0f }, { "secB", 1.0f } }; } void BaseRandomServer::SpawnMapZones(Entity* self) { diff --git a/resources/sharedconfig.ini b/resources/sharedconfig.ini index 03bdbaadb..53b5b9b5d 100644 --- a/resources/sharedconfig.ini +++ b/resources/sharedconfig.ini @@ -57,7 +57,8 @@ client_net_version=171022 # This is used in both Chat and World servers. default_team_loot=0 -# event gating for login response and luz gating +# event gating for login response and luz gating, and holiday events from the EventGating table +# (e.g. pirateDay: pirates and admirals in the Crux Prime random spawners) event_1=Talk_Like_A_Pirate event_2= event_3= diff --git a/tests/dGameTests/CMakeLists.txt b/tests/dGameTests/CMakeLists.txt index 02960de08..5ee37e527 100644 --- a/tests/dGameTests/CMakeLists.txt +++ b/tests/dGameTests/CMakeLists.txt @@ -17,6 +17,7 @@ set(DGAMETEST_SOURCES "ModularBuildTableTests.cpp" "UgcManifestTests.cpp" "SceneGhostingTests.cpp" + "HolidayEventsTests.cpp" ) add_subdirectory(dComponentsTests) diff --git a/tests/dGameTests/HolidayEventsTests.cpp b/tests/dGameTests/HolidayEventsTests.cpp new file mode 100644 index 000000000..78f73d8a9 --- /dev/null +++ b/tests/dGameTests/HolidayEventsTests.cpp @@ -0,0 +1,49 @@ +// EventGating (a server-only table) as GetHolidayEvent answered it: an event runs while its dates include the time; +// a server can also name it in event_1..event_8. +#include "GameDependencies.h" + +#include "CDClientManager.h" +#include "CDEventGatingTable.h" +#include "HolidayEvents.h" + +#include +#include + +#include + +class HolidayEventsTests : public GameDependenciesTest { +protected: + void SetUp() override { + SetUpDependencies(); + // Rows as in the 1.10.64 CDClient + auto& rows = CDClientManager::GetEntriesMutable(); + rows.push_back({ "pirateDay", 1316390400, 1316563199 }); + rows.push_back({ "buildNexusTower", 1268092800, 1299801600 }); + } + + void TearDown() override { + CDClientManager::GetEntriesMutable().clear(); + TearDownDependencies(); + } +}; + +TEST_F(HolidayEventsTests, DatesIncludeBothEnds) { + const auto* const table = CDClientManager::GetTable(); + EXPECT_FALSE(table->IsEventActive("pirateDay", 1316390399)); + EXPECT_TRUE(table->IsEventActive("pirateDay", 1316390400)); // 2011-09-19 00:00 UTC + EXPECT_TRUE(table->IsEventActive("pirateDay", 1316563199)); // 2011-09-20 23:59:59 UTC + EXPECT_FALSE(table->IsEventActive("pirateDay", 1316563200)); + EXPECT_FALSE(table->IsEventActive("noSuchEvent", 1316390400)); +} + +TEST_F(HolidayEventsTests, ServerSettingTurnsAnEventOn) { + constexpr int64_t now = 1790000000; // 2026: no live dates match + const std::array none{}; + EXPECT_FALSE(HolidayEvents::IsActive("pirateDay", now, none.data(), none.size())); + + const std::array pirates{ "Talk_Like_A_Pirate", "pirateDay" }; + EXPECT_TRUE(HolidayEvents::IsActive("pirateDay", now, pirates.data(), pirates.size())); + EXPECT_FALSE(HolidayEvents::IsActive("buildNexusTower", now, pirates.data(), pirates.size())); + EXPECT_FALSE(HolidayEvents::IsActive("", now, none.data(), none.size())); // empty event_N settings don't match + EXPECT_TRUE(HolidayEvents::IsActive("buildNexusTower", 1268092800, none.data(), none.size())); +}