diff --git a/data/json/events.json b/data/json/events.json index f9692bae75a..4dcfff18ed1 100644 --- a/data/json/events.json +++ b/data/json/events.json @@ -1,49 +1,178 @@ { "events": [ { - "name": "Otservbr example 1", - "startdate": "11/03/2020", - "enddate": "12/30/2025", - "script": "first-example.lua", + "name": "Forge Time", + "startdate": "08/11/2024", + "enddate": "11/11/2024", + "script": "forge_time.lua", + "description": "Increases the success rate of forges by 20%.", + "colors": { + "colordark": "#2b3e50", + "colorlight": "#3d5a73" + }, + "details": { + "displaypriority": 5, + "isseasonal": 0, + "specialevent": 1 + } + }, + { + "name": "Double Bestiary", + "startdate": "08/11/2024", + "enddate": "11/11/2024", + "script": "double_bestiary.lua", + "description": "Doubles the bestiary counter when defeating monsters.", + "colors": { + "colordark": "#3a4f2a", + "colorlight": "#4f713a" + }, + "details": { + "displaypriority": 5, + "isseasonal": 0, + "specialevent": 1 + } + }, + { + "name": "Fast Exercise", + "startdate": "08/11/2024", + "enddate": "11/11/2024", + "script": "fast_exercise.lua", + "description": "Exercise weapons are faster, doubling their speed.", + "colors": { + "colordark": "#5a3a2a", + "colorlight": "#7b4f3a" + }, + "details": { + "displaypriority": 5, + "isseasonal": 0, + "specialevent": 1 + } + }, + { + "name": "50% Loot Bonus", + "startdate": "08/11/2024", + "enddate": "11/11/2024", + "script": "", "ingame": { - "exprate": 100, - "lootrate": 100, - "bosslootrate": 100, - "spawnrate": 100, - "skillrate": 100 + "lootrate": 150 }, - "description": "Otserver br example 1 description double exp and a half, double loot !chance!, regular spawn and double skill", + "description": "Increases loot by 50%.", "colors": { - "colordark": "#235c00", - "colorlight": "#2d7400" + "colordark": "#2b1e10", + "colorlight": "#4a2e18" }, "details": { "displaypriority": 6, "isseasonal": 0, - "specialevent": 0 + "specialevent": 1 } }, { - "name": "Otservbr example 2", - "startdate": "2/2/2022", - "enddate": "12/31/2025", - "script": "second-example.lua", + "name": "50% Exp Bonus", + "startdate": "08/11/2024", + "enddate": "11/11/2024", + "script": "", "ingame": { - "exprate": 100, - "lootrate": 100, - "bosslootrate": 100, - "spawnrate": 100, - "skillrate": 100 + "exprate": 150 + }, + "description": "Increases experience by 50%.", + "colors": { + "colordark": "#234d00", + "colorlight": "#3a7500" }, - "description": "Otserver br example 2 description 50% less exp, triple loot !chance!, 50% faster spawn and regular skill", + "details": { + "displaypriority": 6, + "isseasonal": 0, + "specialevent": 1 + } + }, + { + "name": "Boss Cooldown Reduction", + "startdate": "08/11/2024", + "enddate": "11/11/2024", + "script": "boss_cooldown.lua", + "description": "Reduces boss cooldown time by 50%.", "colors": { - "colordark": "#735D10", - "colorlight": "#8B6D05" + "colordark": "#2d3c5a", + "colorlight": "#4a5f7d" }, "details": { "displaypriority": 6, "isseasonal": 0, - "specialevent": 0 + "specialevent": 1 + } + }, + { + "name": "Double Exp", + "startdate": "08/11/2024", + "enddate": "11/11/2024", + "script": "", + "ingame": { + "exprate": 200 + }, + "description": "Double experience when hunting monsters.", + "colors": { + "colordark": "#002d00", + "colorlight": "#004400" + }, + "details": { + "displaypriority": 6, + "isseasonal": 1, + "specialevent": 1 + } + }, + { + "name": "Double Loot", + "startdate": "08/11/2024", + "enddate": "11/11/2024", + "script": "", + "ingame": { + "lootrate": 200 + }, + "description": "Doubles the amount of loot obtained from monsters.", + "colors": { + "colordark": "#2a1d00", + "colorlight": "#4c3300" + }, + "details": { + "displaypriority": 6, + "isseasonal": 1, + "specialevent": 1 + } + }, + { + "name": "Double Bosstiary", + "startdate": "08/11/2024", + "enddate": "11/11/2024", + "script": "double_bosstiary.lua", + "description": "Doubles the bestiary counter for bosses.", + "colors": { + "colordark": "#3a005a", + "colorlight": "#4e0075" + }, + "details": { + "displaypriority": 5, + "isseasonal": 0, + "specialevent": 1 + } + }, + { + "name": "Fast Respawn", + "startdate": "11/01/2024", + "enddate": "11/15/2024", + "script": "", + "ingame": { + "spawnrate": 200 + }, + "description": "Monsters respawn twice as fast.", + "colors": { + "colordark": "#4d2f00", + "colorlight": "#6e3e00" + }, + "details": { + "displaypriority": 6, + "isseasonal": 1, + "specialevent": 1 } } ] diff --git a/data/json/scripts/boss_cooldown.lua b/data/json/scripts/boss_cooldown.lua new file mode 100644 index 00000000000..e69de29bb2d diff --git a/data/json/scripts/double_bestiary.lua b/data/json/scripts/double_bestiary.lua new file mode 100644 index 00000000000..b267b076f49 --- /dev/null +++ b/data/json/scripts/double_bestiary.lua @@ -0,0 +1,6 @@ +local globalEvent = GlobalEvent("EventScheduleDoubleBestiaryKV") +function globalEvent.onStartup() + KV.scoped("eventscheduler"):set("double-bestiary", true) +end + +globalEvent:register() diff --git a/data/json/scripts/double_bosstiary.lua b/data/json/scripts/double_bosstiary.lua new file mode 100644 index 00000000000..749e0d09a64 --- /dev/null +++ b/data/json/scripts/double_bosstiary.lua @@ -0,0 +1,6 @@ +local globalEvent = GlobalEvent("EventScheduleDoubleBosstiaryKV") +function globalEvent.onStartup() + KV.scoped("eventscheduler"):set("double-bosstiary", true) +end + +globalEvent:register() diff --git a/data/json/scripts/fast_exercise.lua b/data/json/scripts/fast_exercise.lua new file mode 100644 index 00000000000..b376f679c4c --- /dev/null +++ b/data/json/scripts/fast_exercise.lua @@ -0,0 +1,6 @@ +local globalEvent = GlobalEvent("EventScheduleFastExerciseKV") +function globalEvent.onStartup() + KV.scoped("eventscheduler"):set("fast-exercise", true) +end + +globalEvent:register() diff --git a/data/json/scripts/first-example.lua b/data/json/scripts/first-example.lua deleted file mode 100644 index fad6d69d71a..00000000000 --- a/data/json/scripts/first-example.lua +++ /dev/null @@ -1,17 +0,0 @@ --- [OtServerBr] --- Event scheduler lua scripts, on this file is possible to load any kind --- of global values, create functions or create and register GlobalEvents using the revscript system. --- For example you can load a 'local Example = GlobalEvent("example")' and register it with 'Example:register()', --- adding the 'Example.onStartup()' or 'Example.onThink(interval)' with 'Example:interval(time)'. --- With 'onStartup()' you can load any raid, for example loading a entire map/hunt and the choseen spawns. - --- Examples: --- Loading map: Game.loadMap(DATA_DIRECTORY.. '/world/myMapFolder/myMapFile.otbm') --- Loading spawn: addEvent(function() Game.loadSpawnFile(DATA_DIRECTORY.. '/world/mySpawnFolder/mySpawnFile.xml) end, 30 * 1000) - -local globalEvent = GlobalEvent("ExampleOne") -function globalEvent.onStartup() - logger.info("Example one started") -end - -globalEvent:register() diff --git a/data/json/scripts/forge_time.lua b/data/json/scripts/forge_time.lua new file mode 100644 index 00000000000..f628f52f0b4 --- /dev/null +++ b/data/json/scripts/forge_time.lua @@ -0,0 +1,6 @@ +local globalEvent = GlobalEvent("EventScheduleForgeTimeKV") +function globalEvent.onStartup() + KV.scoped("eventscheduler"):set("forge-chance", 20) +end + +globalEvent:register() diff --git a/data/json/scripts/second-example.lua b/data/json/scripts/second-example.lua deleted file mode 100644 index 12675b80894..00000000000 --- a/data/json/scripts/second-example.lua +++ /dev/null @@ -1,17 +0,0 @@ --- [OtServerBr] --- Event scheduler lua scripts, on this file is possible to load any kind --- of global values, create functions or create and register GlobalEvents using the revscript system. --- For example you can load a 'local Example = GlobalEvent("example")' and register it with 'Example:register()', --- adding the 'Example.onStartup()' or 'Example.onThink(interval)' with 'Example:interval(time)'. --- With 'onStartup()' you can load any raid, for example loading a entire map/hunt and the choseen spawns. - --- Examples: --- Loading map: Game.loadMap(DATA_DIRECTORY.. '/world/myMapFolder/myMapFile.otbm') --- Loading spawn: addEvent(function() Game.loadSpawnFile(DATA_DIRECTORY.. '/world/mySpawnFolder/mySpawnFile.xml) end, 30 * 1000) - -local globalEvent = GlobalEvent("ExampleTwo") -function globalEvent.onStartup() - logger.info("Example one started") -end - -globalEvent:register() diff --git a/data/scripts/actions/items/exercise_training_weapons.lua b/data/scripts/actions/items/exercise_training_weapons.lua index 3c62d7c1183..a95e0fbe075 100644 --- a/data/scripts/actions/items/exercise_training_weapons.lua +++ b/data/scripts/actions/items/exercise_training_weapons.lua @@ -121,8 +121,19 @@ local function exerciseTrainingEvent(playerId, tilePosition, weaponId, dummyId) return false end + local eventSpeedMultiplier = 1 + local scopedFastExercise = KV.scoped("eventscheduler"):get("fast-exercise") + if scopedFastExercise then + eventSpeedMultiplier = 0.5 + logger.debug("Fast exercise is enabled.") + end + local vocation = player:getVocation() - _G.OnExerciseTraining[playerId].event = addEvent(exerciseTrainingEvent, vocation:getBaseAttackSpeed() / configManager.getFloat(configKeys.RATE_EXERCISE_TRAINING_SPEED), playerId, tilePosition, weaponId, dummyId) + _G.OnExerciseTraining[playerId].event = addEvent( + exerciseTrainingEvent, + (vocation:getBaseAttackSpeed() / configManager.getFloat(configKeys.RATE_EXERCISE_TRAINING_SPEED)) * eventSpeedMultiplier, + playerId, tilePosition, weaponId, dummyId + ) return true end diff --git a/src/creatures/players/player.cpp b/src/creatures/players/player.cpp index 923b153a99a..e5b40487706 100644 --- a/src/creatures/players/player.cpp +++ b/src/creatures/players/player.cpp @@ -5693,6 +5693,14 @@ void Player::addBestiaryKill(const std::shared_ptr &mType) { return; } uint32_t kills = g_configManager().getNumber(BESTIARY_KILL_MULTIPLIER); + + auto scopedDoubleBestiary = g_kv().scoped("eventscheduler")->get("double-bestiary"); + bool doubleBestiaryEnabled = scopedDoubleBestiary && scopedDoubleBestiary->get(); + if (doubleBestiaryEnabled) { + kills *= 2; + g_logger().info("[{}] double bestiary is enabled.", std::source_location::current().function_name()); + } + if (isConcoctionActive(Concoction_t::BestiaryBetterment)) { kills *= 2; } @@ -5704,6 +5712,14 @@ void Player::addBosstiaryKill(const std::shared_ptr &mType) { return; } uint32_t kills = g_configManager().getNumber(BOSSTIARY_KILL_MULTIPLIER); + + auto scopedDoubleBosstiary = g_kv().scoped("eventscheduler")->get("double-bosstiary"); + bool doubleBosstiaryEnabled = scopedDoubleBosstiary && scopedDoubleBosstiary->get(); + if (doubleBosstiaryEnabled) { + kills *= 2; + g_logger().info("[{}] double bosstiary is enabled.", std::source_location::current().function_name()); + } + if (g_ioBosstiary().getBoostedBossId() == mType->info.raceid) { kills *= g_configManager().getNumber(BOOSTED_BOSS_KILL_BONUS); } diff --git a/src/game/game.cpp b/src/game/game.cpp index 780ce5b7303..87040418279 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -9498,6 +9498,11 @@ void Game::playerForgeFuseItems(uint32_t playerId, ForgeAction_t actionType, uin uint8_t coreCount = (usedCore ? 1 : 0) + (reduceTierLoss ? 1 : 0); auto baseSuccess = static_cast(g_configManager().getNumber(FORGE_BASE_SUCCESS_RATE)); + if (const auto scopedForgeChance = g_kv().scoped("eventscheduler")->get("forge-chance")) { + auto forgeChance = static_cast(scopedForgeChance->getNumber()); + g_logger().info("Base success: {}, forge chance: {}", baseSuccess, forgeChance); + baseSuccess += forgeChance; + } auto coreSuccess = usedCore ? g_configManager().getNumber(FORGE_BONUS_SUCCESS_RATE) : 0; auto finalRate = baseSuccess + coreSuccess; auto roll = static_cast(uniform_random(1, 100)) <= finalRate; diff --git a/src/game/scheduling/events_scheduler.cpp b/src/game/scheduling/events_scheduler.cpp index 08c23c92aef..01790789408 100644 --- a/src/game/scheduling/events_scheduler.cpp +++ b/src/game/scheduling/events_scheduler.cpp @@ -14,9 +14,13 @@ #include -using json = nlohmann::json; - bool EventsScheduler::loadScheduleEventFromJson() { + g_kv().scoped("eventscheduler")->remove("forge-chance"); + g_kv().scoped("eventscheduler")->remove("double-bestiary"); + g_kv().scoped("eventscheduler")->remove("double-bosstiary"); + g_kv().scoped("eventscheduler")->remove("fast-exercise"); + + using json = nlohmann::json; auto coreFolder = g_configManager().getString(CORE_DIRECTORY); auto folder = coreFolder + "/json/events.json"; std::ifstream file(folder); @@ -43,17 +47,20 @@ bool EventsScheduler::loadScheduleEventFromJson() { std::map eventsOnSameDay; for (const auto &event : eventsJson["events"]) { - std::string eventScript = event.value("script", ""); + std::string eventScript = event.contains("script") && !event["script"].is_null() ? event["script"].get() : ""; std::string eventName = event.value("name", ""); - if (eventScript.empty()) { - g_logger().warn("{} - Event script is empty for event '{}'", __FUNCTION__, eventName); + if (!event.contains("startdate") || !event.contains("enddate")) { + g_logger().warn("{} - Missing 'startdate' or 'enddate' for event '{}'", __FUNCTION__, eventName); continue; } int startYear, startMonth, startDay, endYear, endMonth, endDay; - sscanf(event["startdate"].get().c_str(), "%d/%d/%d", &startMonth, &startDay, &startYear); - sscanf(event["enddate"].get().c_str(), "%d/%d/%d", &endMonth, &endDay, &endYear); + if (sscanf(event["startdate"].get().c_str(), "%d/%d/%d", &startMonth, &startDay, &startYear) != 3 || + sscanf(event["enddate"].get().c_str(), "%d/%d/%d", &endMonth, &endDay, &endYear) != 3) { + g_logger().warn("{} - Invalid date format for event '{}'", __FUNCTION__, eventName); + continue; + } int startDays = (startYear * 365) + (startMonth * 30) + startDay; int endDays = (endYear * 365) + (endMonth * 30) + endDay; @@ -62,30 +69,32 @@ bool EventsScheduler::loadScheduleEventFromJson() { continue; } - if (!eventScript.empty() && loadedScripts.contains(eventScript)) { - g_logger().warn("{} - Script declaration '{}' is duplicated in '{}'", __FUNCTION__, eventScript, folder); - continue; - } + if (!eventScript.empty()) { + if (loadedScripts.contains(eventScript)) { + g_logger().warn("{} - Script declaration '{}' is duplicated in '{}'", __FUNCTION__, eventScript, folder); + continue; + } - loadedScripts.insert(eventScript); - std::filesystem::path filePath = std::filesystem::current_path() / coreFolder / "json" / "scripts" / eventScript; + loadedScripts.insert(eventScript); + std::filesystem::path filePath = std::filesystem::current_path() / coreFolder / "json" / "scripts" / eventScript; - if (!std::filesystem::exists(filePath) || !std::filesystem::is_regular_file(filePath)) { - g_logger().warn("{} - Cannot find script file '{}'", __FUNCTION__, filePath.string()); - return false; - } + if (!std::filesystem::exists(filePath) || !std::filesystem::is_regular_file(filePath)) { + g_logger().warn("{} - Cannot find script file '{}'", __FUNCTION__, filePath.string()); + return false; + } - if (!g_scripts().loadEventSchedulerScripts(filePath)) { - g_logger().warn("{} - Cannot load the file '{}' on '{}/scripts/'", __FUNCTION__, eventScript, coreFolder); - return false; + if (!g_scripts().loadEventSchedulerScripts(filePath)) { + g_logger().warn("{} - Cannot load the file '{}' on '{}/scripts/'", __FUNCTION__, eventScript, coreFolder); + return false; + } } EventRates currentEventRates = { - static_cast(event["ingame"].value("exprate", 100)), - static_cast(event["ingame"].value("lootrate", 100)), - static_cast(event["ingame"].value("bosslootrate", 100)), - static_cast(event["ingame"].value("spawnrate", 100)), - static_cast(event["ingame"].value("skillrate", 100)) + static_cast(event.contains("ingame") && event["ingame"].contains("exprate") ? event["ingame"].value("exprate", 100) : 100), + static_cast(event.contains("ingame") && event["ingame"].contains("lootrate") ? event["ingame"].value("lootrate", 100) : 100), + static_cast(event.contains("ingame") && event["ingame"].contains("bosslootrate") ? event["ingame"].value("bosslootrate", 100) : 100), + static_cast(event.contains("ingame") && event["ingame"].contains("spawnrate") ? event["ingame"].value("spawnrate", 100) : 100), + static_cast(event.contains("ingame") && event["ingame"].contains("skillrate") ? event["ingame"].value("skillrate", 100) : 100) }; for (const auto &[existingEventName, rates] : eventsOnSameDay) {