From 7fe9fc94c02f9d46834f61da658262e3f3de6015 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 6 Dec 2024 20:00:09 -0500 Subject: [PATCH 1/4] feat: Implement a task scheduler Implemented a light weight mechanism for scheduling recurring tasks. This can be used to implement, hourly, daily or weekly resets. There is also an intention to use the mechanism to check for seasonal events and turn on/off depending on the date. --- .../Arrowgene.Ddon.Database.csproj | 3 + .../Database/Script/migration_scheduling.sql | 7 ++ .../Files/Database/Script/schema_sqlite.sql | 8 ++ Arrowgene.Ddon.Database/IDatabase.cs | 7 +- ...donSqlDbEpitaphRoadClaimedWeeklyRewards.cs | 2 +- .../Sql/Core/DdonSqlDbScheduleNext.cs | 49 ++++++++++ .../00000026_TaskSchedulerMigration.cs | 25 ++++++ Arrowgene.Ddon.GameServer/DdonGameServer.cs | 3 + Arrowgene.Ddon.GameServer/ScheduleManager.cs | 89 +++++++++++++++++++ Arrowgene.Ddon.GameServer/Tasks/DailyTask.cs | 23 +++++ .../Tasks/EpitaphSchedulerTask.cs | 36 ++++++++ Arrowgene.Ddon.GameServer/Tasks/HourlyTask.cs | 17 ++++ .../Tasks/SchedulerTask.cs | 24 +++++ Arrowgene.Ddon.GameServer/Tasks/WeeklyTask.cs | 36 ++++++++ Arrowgene.Ddon.GameServer/Utils/DateUtils.cs | 14 +++ .../Model/Scheduler/ScheduleInterval.cs | 11 +++ .../Model/Scheduler/SchedulerTaskEntry.cs | 14 +++ .../Model/Scheduler/TaskType.cs | 34 +++++++ .../Database/DatabaseMigratorTest.cs | 6 +- 19 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 Arrowgene.Ddon.Database/Files/Database/Script/migration_scheduling.sql create mode 100644 Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbScheduleNext.cs create mode 100644 Arrowgene.Ddon.Database/Sql/Core/Migration/00000026_TaskSchedulerMigration.cs create mode 100644 Arrowgene.Ddon.GameServer/ScheduleManager.cs create mode 100644 Arrowgene.Ddon.GameServer/Tasks/DailyTask.cs create mode 100644 Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs create mode 100644 Arrowgene.Ddon.GameServer/Tasks/HourlyTask.cs create mode 100644 Arrowgene.Ddon.GameServer/Tasks/SchedulerTask.cs create mode 100644 Arrowgene.Ddon.GameServer/Tasks/WeeklyTask.cs create mode 100644 Arrowgene.Ddon.GameServer/Utils/DateUtils.cs create mode 100644 Arrowgene.Ddon.Shared/Model/Scheduler/ScheduleInterval.cs create mode 100644 Arrowgene.Ddon.Shared/Model/Scheduler/SchedulerTaskEntry.cs create mode 100644 Arrowgene.Ddon.Shared/Model/Scheduler/TaskType.cs diff --git a/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj b/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj index 204769318..f10daa3bb 100644 --- a/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj +++ b/Arrowgene.Ddon.Database/Arrowgene.Ddon.Database.csproj @@ -41,5 +41,8 @@ PreserveNewest + + PreserveNewest + diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/migration_scheduling.sql b/Arrowgene.Ddon.Database/Files/Database/Script/migration_scheduling.sql new file mode 100644 index 000000000..c39ec37ca --- /dev/null +++ b/Arrowgene.Ddon.Database/Files/Database/Script/migration_scheduling.sql @@ -0,0 +1,7 @@ +CREATE TABLE ddon_schedule_next ( + "type" INTEGER NOT NULL, + "timestamp" BIGINT NOT NULL, + PRIMARY KEY("type") +); + +INSERT INTO ddon_schedule_next(type, timestamp) VALUES (19, 0); diff --git a/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql b/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql index 99e5ba454..1a898d2bb 100644 --- a/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql +++ b/Arrowgene.Ddon.Database/Files/Database/Script/schema_sqlite.sql @@ -782,3 +782,11 @@ CREATE TABLE IF NOT EXISTS ddon_epitaph_claimed_weekly_rewards ( CONSTRAINT "pk_ddon_epitaph_claimed_weekly_rewards" PRIMARY KEY ("character_id", "epitaph_id"), CONSTRAINT "fk_ddon_epitaph_claimed_weekly_rewards_character_id" FOREIGN KEY ("character_id") REFERENCES "ddon_character"("character_id") ON DELETE CASCADE ); + + +CREATE TABLE IF NOT EXISTS ddon_schedule_next ( + "type" INTEGER NOT NULL, + "timestamp" BIGINT NOT NULL, + PRIMARY KEY("type") +); +INSERT INTO ddon_schedule_next(type, timestamp) VALUES (19, 0); diff --git a/Arrowgene.Ddon.Database/IDatabase.cs b/Arrowgene.Ddon.Database/IDatabase.cs index cdac143c0..a932be218 100644 --- a/Arrowgene.Ddon.Database/IDatabase.cs +++ b/Arrowgene.Ddon.Database/IDatabase.cs @@ -11,6 +11,7 @@ using Arrowgene.Ddon.Shared.Model.BattleContent; using Arrowgene.Ddon.Shared.Model.Clan; using Arrowgene.Ddon.Shared.Model.Quest; +using Arrowgene.Ddon.Shared.Model.Scheduler; namespace Arrowgene.Ddon.Database { @@ -590,6 +591,10 @@ bool InsertBBMContentTreasure( bool InsertEpitaphWeeklyReward(uint characterId, uint epitaphId, DbConnection? connectionIn = null); HashSet GetEpitaphClaimedWeeklyRewards(uint characterId, DbConnection? connectionIn = null); - void DeleteWeeklyRewards(DbConnection? connectionIn = null); + void DeleteWeeklyEpitaphClaimedRewards(DbConnection? connectionIn = null); + + // Scheduler + Dictionary SelectAllTaskEntries(); + bool UpdateScheduleInfo(TaskType type, long timestamp); } } diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEpitaphRoadClaimedWeeklyRewards.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEpitaphRoadClaimedWeeklyRewards.cs index c9626a4b9..ce715f5bd 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEpitaphRoadClaimedWeeklyRewards.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEpitaphRoadClaimedWeeklyRewards.cs @@ -49,7 +49,7 @@ public HashSet GetEpitaphClaimedWeeklyRewards(uint characterId, DbConnecti return results; } - public void DeleteWeeklyRewards(DbConnection? connectionIn = null) + public void DeleteWeeklyEpitaphClaimedRewards(DbConnection? connectionIn = null) { ExecuteQuerySafe(connectionIn, (connection) => { diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbScheduleNext.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbScheduleNext.cs new file mode 100644 index 000000000..677d655e3 --- /dev/null +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbScheduleNext.cs @@ -0,0 +1,49 @@ +using Arrowgene.Ddon.Shared.Model.Scheduler; +using System.Collections.Generic; +using System.Data.Common; + +namespace Arrowgene.Ddon.Database.Sql.Core +{ + public abstract partial class DdonSqlDb : SqlDb + where TCon : DbConnection + where TCom : DbCommand + where TReader : DbDataReader + { + protected static readonly string[] ScheduleNextFields = new string[] + { + "type", "timestamp" + }; + + private static readonly string SqlUpdateScheduleNext = $"UPDATE \"ddon_schedule_next\" SET \"timestamp\"=@timestamp WHERE \"type\"=@type;"; + private static readonly string SqlSelectScheduleNext = $"SELECT {BuildQueryField(ScheduleNextFields)} FROM \"ddon_schedule_next\";"; + + + public Dictionary SelectAllTaskEntries() + { + Dictionary results = new Dictionary(); + ExecuteReader(SqlSelectScheduleNext, command => { }, reader => + { + while (reader.Read()) + { + TaskType type = (TaskType) GetUInt32(reader, "type"); + results[type] = new SchedulerTaskEntry() + { + Type = type, + Timestamp = GetInt64(reader, "timestamp") + }; + } + }); + return results; + } + + public bool UpdateScheduleInfo(TaskType type, long timestamp) + { + return ExecuteNonQuery(SqlUpdateScheduleNext, command => + { + AddParameter(command, "@type", (uint) type); + AddParameter(command, "@timestamp", timestamp); + }) == 1; + } + } +} + diff --git a/Arrowgene.Ddon.Database/Sql/Core/Migration/00000026_TaskSchedulerMigration.cs b/Arrowgene.Ddon.Database/Sql/Core/Migration/00000026_TaskSchedulerMigration.cs new file mode 100644 index 000000000..1324344e2 --- /dev/null +++ b/Arrowgene.Ddon.Database/Sql/Core/Migration/00000026_TaskSchedulerMigration.cs @@ -0,0 +1,25 @@ +using System.Data.Common; + +namespace Arrowgene.Ddon.Database.Sql.Core.Migration +{ + public class TaskSchedulerMigration : IMigrationStrategy + { + public uint From => 25; + public uint To => 26; + + private readonly DatabaseSetting DatabaseSetting; + + public TaskSchedulerMigration(DatabaseSetting databaseSetting) + { + DatabaseSetting = databaseSetting; + } + + public bool Migrate(IDatabase db, DbConnection conn) + { + string adaptedSchema = DdonDatabaseBuilder.GetAdaptedSchema(DatabaseSetting, "Script/migration_scheduling.sql"); + db.Execute(conn, adaptedSchema); + return true; + } + } +} + diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index 15d99dacb..494bb9ace 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -79,6 +79,7 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi ClanManager = new ClanManager(this); RpcManager = new RpcManager(this); EpitaphRoadManager = new EpitaphRoadManager(this); + ScheduleManager = new ScheduleManager(this); // Orb Management is slightly complex and requires updating fields across multiple systems OrbUnlockManager = new OrbUnlockManager(database, WalletManager, JobManager, CharacterManager); @@ -115,6 +116,7 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi public ClanManager ClanManager { get; } public RpcManager RpcManager { get; } public EpitaphRoadManager EpitaphRoadManager { get; } + private ScheduleManager ScheduleManager { get; } public ChatLogHandler ChatLogHandler { get; } @@ -128,6 +130,7 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi public override void Start() { QuestManager.LoadQuests(this); + ScheduleManager.Start(); GpCourseManager.EvaluateCourses(); LoadChatHandler(); LoadPacketHandler(); diff --git a/Arrowgene.Ddon.GameServer/ScheduleManager.cs b/Arrowgene.Ddon.GameServer/ScheduleManager.cs new file mode 100644 index 000000000..8f5eede75 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/ScheduleManager.cs @@ -0,0 +1,89 @@ +using Arrowgene.Ddon.GameServer.Tasks; +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Model.Scheduler; +using Arrowgene.Logging; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Arrowgene.Ddon.GameServer +{ + public class ScheduleManager + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ScheduleManager)); + + private List Tasks; + private DdonGameServer Server; + + private static readonly int TIMER_TICK_HOURLY = 1 * 1000; // 1 second + private static readonly int TIMER_TICK_DAILY = 10 * 1000; // 10 seconds + private static readonly int TIMER_TICK_WEEKLY = 30 * 1000; // 30 seconds + + public ScheduleManager(DdonGameServer server) + { + Server = server; + + // TODO: Load from server config + Tasks = new List() + { + new EpitaphSchedulerTask(DayOfWeek.Monday, 5, 0) + }; + } + + private int GetTimerTick(ScheduleInterval interval) + { + switch (interval) + { + case ScheduleInterval.Hourly: + return TIMER_TICK_HOURLY; + case ScheduleInterval.Daily: + return TIMER_TICK_DAILY; + case ScheduleInterval.Weekly: + return TIMER_TICK_WEEKLY; + default: + return TIMER_TICK_HOURLY; + } + } + + public void Start() + { + Dictionary entries = Server.Database.SelectAllTaskEntries(); + + var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + foreach (var task in Tasks) + { + if (!entries.ContainsKey(task.Type)) + { + Logger.Error($"Task '{task.Type}' has no record in the database. Skipping."); + continue; + } + + if (!task.IsEnabled(Server)) + { + // This task is not enabled so skip it + continue; + } + + long nextAction = entries[task.Type].Timestamp; + if (now >= nextAction) + { + task.RunTask(Server); + entries[task.Type].Timestamp = task.NextTimestamp(); + Server.Database.UpdateScheduleInfo(task.Type, entries[task.Type].Timestamp); + } + + var timerTick = GetTimerTick(task.Interval); + var Timer = new Timer(state => + { + long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + if (now >= entries[task.Type].Timestamp) + { + task.RunTask(Server); + entries[task.Type].Timestamp = task.NextTimestamp(); + Server.Database.UpdateScheduleInfo(task.Type, entries[task.Type].Timestamp); + } + }, null, timerTick, timerTick); + } + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Tasks/DailyTask.cs b/Arrowgene.Ddon.GameServer/Tasks/DailyTask.cs new file mode 100644 index 000000000..e0a671a37 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Tasks/DailyTask.cs @@ -0,0 +1,23 @@ +using Arrowgene.Ddon.Shared.Model.Scheduler; +using System; + +namespace Arrowgene.Ddon.GameServer.Tasks +{ + public abstract class DailyTask : SchedulerTask + { + public uint Hour { get; } + public uint Minute { get; } + + public DailyTask(TaskType scheduleType, uint hour, uint minute) : base(ScheduleInterval.Daily, scheduleType) + { + Hour = hour; + Minute = minute; + } + + public override long NextTimestamp() + { + var tomorrow = DateTime.Today.AddDays(1); + return new DateTimeOffset(new DateTime(tomorrow.Year, tomorrow.Month, tomorrow.Day, (int)Hour, (int)Minute, 0)).ToUnixTimeSeconds(); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs b/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs new file mode 100644 index 000000000..f1cc73491 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs @@ -0,0 +1,36 @@ +using Arrowgene.Ddon.Server; +using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Model.Scheduler; +using Arrowgene.Logging; +using System; + +namespace Arrowgene.Ddon.GameServer.Tasks +{ + public class EpitaphSchedulerTask : WeeklyTask + { + private static readonly ServerLogger Logger = LogProvider.Logger(typeof(EpitaphSchedulerTask)); + + public EpitaphSchedulerTask(DayOfWeek day, uint hour, uint minute) : base(TaskType.EpitaphRoadRewardsReset, day, hour, minute) + { + + } + + public override bool IsEnabled(DdonGameServer server) + { + return server.Setting.GameLogicSetting.EnableEpitaphWeeklyRewards; + } + + public override void RunTask(DdonGameServer server) + { + Logger.Info("Performing weekly epitaph reset"); + server.ChatManager.SendMessage("Performing weekly epitaph reset", string.Empty, string.Empty, LobbyChatMsgType.ManagementAlertN, server.ClientLookup.GetAll()); + + server.Database.DeleteWeeklyEpitaphClaimedRewards(); + + foreach (var client in server.ClientLookup.GetAll()) + { + client.Character.EpitaphRoadState.WeeklyRewardsClaimed.Clear(); + } + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Tasks/HourlyTask.cs b/Arrowgene.Ddon.GameServer/Tasks/HourlyTask.cs new file mode 100644 index 000000000..5c7358651 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Tasks/HourlyTask.cs @@ -0,0 +1,17 @@ +using Arrowgene.Ddon.Shared.Model.Scheduler; +using System; + +namespace Arrowgene.Ddon.GameServer.Tasks +{ + public abstract class HourlyTask : SchedulerTask + { + public HourlyTask(TaskType type) : base(ScheduleInterval.Hourly, type) + { + } + + public override long NextTimestamp() + { + return new DateTimeOffset(DateTime.Now.AddHours(1)).ToUnixTimeSeconds(); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Tasks/SchedulerTask.cs b/Arrowgene.Ddon.GameServer/Tasks/SchedulerTask.cs new file mode 100644 index 000000000..55805cb8c --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Tasks/SchedulerTask.cs @@ -0,0 +1,24 @@ +using Arrowgene.Ddon.Shared.Model.Scheduler; + +namespace Arrowgene.Ddon.GameServer.Tasks +{ + public abstract class SchedulerTask + { + public TaskType Type { get; } + public ScheduleInterval Interval { get; } + + public SchedulerTask(ScheduleInterval interval, TaskType type) + { + Type = type; + Interval = interval; + } + + public abstract void RunTask(DdonGameServer server); + public abstract long NextTimestamp(); + + public virtual bool IsEnabled(DdonGameServer server) + { + return true; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Tasks/WeeklyTask.cs b/Arrowgene.Ddon.GameServer/Tasks/WeeklyTask.cs new file mode 100644 index 000000000..54eb38115 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Tasks/WeeklyTask.cs @@ -0,0 +1,36 @@ +using Arrowgene.Ddon.GameServer.Utils; +using Arrowgene.Ddon.Shared.Model.Scheduler; +using System; + +namespace Arrowgene.Ddon.GameServer.Tasks +{ + public abstract class WeeklyTask : SchedulerTask + { + public DayOfWeek Day { get; } + public uint Hour { get; } + public uint Minute { get; } + + /// + /// Creates a task which runs on a weekly cadence. + /// + /// The type of event this is associated with + /// The day during the week the reset should occur. + /// The hour the reset should occur in a 24 hour format + public WeeklyTask(TaskType scheduleType, DayOfWeek day, uint hour, uint minute) : base(ScheduleInterval.Weekly, scheduleType) + { + Day = day; + Hour = hour; + Minute = minute; + } + + /// + /// Calculates the next timestamp for this task in unix seconds + /// + /// Returns the next timestamp in unix seconds + public override long NextTimestamp() + { + var nextDate = DateUtils.GetNextWeekday(DateTime.Today.AddDays(1), Day); + return new DateTimeOffset(new DateTime(nextDate.Year, nextDate.Month, nextDate.Day, (int)Hour, (int)Minute, 0)).ToUnixTimeSeconds(); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Utils/DateUtils.cs b/Arrowgene.Ddon.GameServer/Utils/DateUtils.cs new file mode 100644 index 000000000..d6bd94908 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Utils/DateUtils.cs @@ -0,0 +1,14 @@ +using System; + +namespace Arrowgene.Ddon.GameServer.Utils +{ + public class DateUtils + { + public static DateTime GetNextWeekday(DateTime start, DayOfWeek day) + { + // The (... + 7) % 7 ensures we end up with a value in the range [0, 6] + int daysToAdd = ((int)day - (int)start.DayOfWeek + 7) % 7; + return start.AddDays(daysToAdd); + } + } +} diff --git a/Arrowgene.Ddon.Shared/Model/Scheduler/ScheduleInterval.cs b/Arrowgene.Ddon.Shared/Model/Scheduler/ScheduleInterval.cs new file mode 100644 index 000000000..e2a1cfefb --- /dev/null +++ b/Arrowgene.Ddon.Shared/Model/Scheduler/ScheduleInterval.cs @@ -0,0 +1,11 @@ +namespace Arrowgene.Ddon.Shared.Model.Scheduler +{ + public enum ScheduleInterval : uint + { + Unknown = 0, + Hourly = 1, + Daily = 2, + Weekly = 3, + Monthly = 4 + } +} diff --git a/Arrowgene.Ddon.Shared/Model/Scheduler/SchedulerTaskEntry.cs b/Arrowgene.Ddon.Shared/Model/Scheduler/SchedulerTaskEntry.cs new file mode 100644 index 000000000..0e5da0cfa --- /dev/null +++ b/Arrowgene.Ddon.Shared/Model/Scheduler/SchedulerTaskEntry.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Arrowgene.Ddon.Shared.Model.Scheduler +{ + public class SchedulerTaskEntry + { + public TaskType Type { get; set; } + public long Timestamp { get; set; } + } +} diff --git a/Arrowgene.Ddon.Shared/Model/Scheduler/TaskType.cs b/Arrowgene.Ddon.Shared/Model/Scheduler/TaskType.cs new file mode 100644 index 000000000..7844968ba --- /dev/null +++ b/Arrowgene.Ddon.Shared/Model/Scheduler/TaskType.cs @@ -0,0 +1,34 @@ +namespace Arrowgene.Ddon.Shared.Model.Scheduler +{ + public enum TaskType : uint + { + // Types derived from http://ddon.wikidot.com/gameplay:home + // Possible to add new types other than from this list + // Try not to change values as this will confuse the scheduler + // in an existing database (unless they are not being used yet) + Unknown = 0, + RevivalGreenGemstones = 1, + LoginStamps = 2, + WeakenedStatusRecovery = 3, + AreaPointReset = 4, + AreaMasterSupportItems = 5, + BoardQuestRotation = 6, + SpecialBoardQuestRotation = 7, + WorldQuestRotation = 8, + ClanQuestRotation = 9, + SubstoryQuestRotation = 10, + ExtremeMissionRewardUpdate = 11, + PawnExpedition = 12, + PawnAffectionIncreaseInteraction = 13, + PawnTrainingExperiencePoints = 14, + ClanDungeonReset = 15, + MandragoraGrowth = 16, + GoldenTreasureChestReset = 17, + VioletTreasureChestReset = 18, + EpitaphRoadRewardsReset = 19, + AwardBitterblackMazeResetTickets = 20, + TimeLockedDungeons = 21, + // Others not from above webpage + SeasonalEventSchedule = 22 + } +} diff --git a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs index ae3d08073..722d59602 100644 --- a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs +++ b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs @@ -8,6 +8,7 @@ using Arrowgene.Ddon.Shared.Model.BattleContent; using Arrowgene.Ddon.Shared.Model.Clan; using Arrowgene.Ddon.Shared.Model.Quest; +using Arrowgene.Ddon.Shared.Model.Scheduler; using System; using System.Collections.Generic; using System.Data; @@ -419,6 +420,9 @@ public bool UpdateRentalPawnSlot(uint characterId, uint num) public bool InsertEpitaphWeeklyReward(uint characterId, uint epitaphId, DbConnection? connectionIn = null) { return true; } public HashSet GetEpitaphClaimedWeeklyRewards(uint characterId, DbConnection? connectionIn = null) { return new(); } + public Dictionary SelectAllTaskEntries() { return new(); } + public bool UpdateScheduleInfo(TaskType type, long timestamp) { return true; } + public void AddParameter(DbCommand command, string name, object? value, DbType type) { } public void AddParameter(DbCommand command, string name, string value) { } public void AddParameter(DbCommand command, string name, Int32 value) { } @@ -443,7 +447,7 @@ public void AddParameter(DbCommand command, string name, bool value) { } public byte[] GetBytes(DbDataReader reader, string column, int size) { return null; } public List SelectRegisteredPawns(Character searchingCharacter, CDataPawnSearchParameter searchParams) { return new List(); } public List SelectRegisteredPawns(DbConnection conn, Character searchingCharacter, CDataPawnSearchParameter searchParams) { return new List(); } - public void DeleteWeeklyRewards(DbConnection? connectionIn = null) { } + public void DeleteWeeklyEpitaphClaimedRewards(DbConnection? connectionIn = null) { } } class MockMigrationStrategy : IMigrationStrategy From 09d29997769161bcfe8dd582e20ce8b4db364cca Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 12 Dec 2024 16:21:28 -0500 Subject: [PATCH 2/4] Introduce concept of a head server - Treat the lowest server id as the head server. - Head server runs the timer task - When timer task fires, if individual channel actions are required, use the RPC manager to send an internal message. - Update the EpitaphWeeklyReward task to send a message to all channels. - Update migration values --- .../DdonDatabaseBuilder.cs | 2 +- .../00000026_TaskSchedulerMigration.cs | 4 ++-- .../Characters/EpitaphRoadManager.cs | 17 ++++++++++++++++ Arrowgene.Ddon.GameServer/Chat/ChatManager.cs | 20 ++++++++++++------- Arrowgene.Ddon.GameServer/DdonGameServer.cs | 7 ++++++- Arrowgene.Ddon.GameServer/RpcManager.cs | 12 +++++++++++ Arrowgene.Ddon.GameServer/ScheduleManager.cs | 2 +- Arrowgene.Ddon.GameServer/ServerUtils.cs | 12 +++++++++++ .../Tasks/EpitaphSchedulerTask.cs | 8 ++------ .../Tasks/SchedulerTask.cs | 6 ++++++ Arrowgene.Ddon.GameServer/Tasks/WeeklyTask.cs | 1 + .../Route/Internal/PacketRoute.cs | 3 +++ .../Model/Rpc/RpcInternalCommand.cs | 2 ++ 13 files changed, 78 insertions(+), 18 deletions(-) create mode 100644 Arrowgene.Ddon.GameServer/ServerUtils.cs diff --git a/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs b/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs index 11f96110e..507c5bd39 100644 --- a/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs +++ b/Arrowgene.Ddon.Database/DdonDatabaseBuilder.cs @@ -14,7 +14,7 @@ public static class DdonDatabaseBuilder private static readonly ILogger Logger = LogProvider.Logger(typeof(DdonDatabaseBuilder)); private const string DefaultSchemaFile = "Script/schema_sqlite.sql"; - public const uint Version = 26; + public const uint Version = 27; public static IDatabase Build(DatabaseSetting settings) { diff --git a/Arrowgene.Ddon.Database/Sql/Core/Migration/00000026_TaskSchedulerMigration.cs b/Arrowgene.Ddon.Database/Sql/Core/Migration/00000026_TaskSchedulerMigration.cs index 1324344e2..d99366b89 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/Migration/00000026_TaskSchedulerMigration.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/Migration/00000026_TaskSchedulerMigration.cs @@ -4,8 +4,8 @@ namespace Arrowgene.Ddon.Database.Sql.Core.Migration { public class TaskSchedulerMigration : IMigrationStrategy { - public uint From => 25; - public uint To => 26; + public uint From => 26; + public uint To => 27; private readonly DatabaseSetting DatabaseSetting; diff --git a/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs b/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs index cb280f5e8..41bce2482 100644 --- a/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs @@ -1321,5 +1321,22 @@ public List RollGatheringLoot(GameClient client, Charact return results; } + + /// + /// Called by the task manager. The main task will signal all channels + /// to flush the cached information queried by the player when first + /// logging in and send a notification to all players that the action + /// occurred. + /// + public void PerformWeeklyReset() + { + _Server.ChatManager.BroadcastMessage(LobbyChatMsgType.ManagementAlertN, "Epitaph Road Weekly Rewards Reset"); + + // Clear out cached data related to epitaph weekly rewards + foreach (var client in _Server.ClientLookup.GetAll()) + { + client.Character.EpitaphRoadState.WeeklyRewardsClaimed.Clear(); + } + } } } diff --git a/Arrowgene.Ddon.GameServer/Chat/ChatManager.cs b/Arrowgene.Ddon.GameServer/Chat/ChatManager.cs index 37d0ac28f..94f2e82ec 100644 --- a/Arrowgene.Ddon.GameServer/Chat/ChatManager.cs +++ b/Arrowgene.Ddon.GameServer/Chat/ChatManager.cs @@ -4,6 +4,7 @@ using Arrowgene.Ddon.Shared.Entity.Structure; using Arrowgene.Ddon.Shared.Model; using Arrowgene.Logging; +using System; using System.Collections.Generic; using System.Linq; @@ -14,11 +15,11 @@ public class ChatManager private static readonly ServerLogger Logger = LogProvider.Logger(typeof(ChatManager)); private readonly List _handler; - private readonly DdonGameServer _server; + private readonly DdonGameServer _Server; public ChatManager(DdonGameServer server) { - _server = server; + _Server = server; _handler = new List(); } @@ -42,7 +43,7 @@ public void SendMessage(string message, string firstName, string lastName, Lobby response.PhrasesIndex = 0; foreach (uint characterId in characterIds) { - GameClient client = _server.ClientLookup.GetClientByCharacterId(characterId); + GameClient client = _Server.ClientLookup.GetClientByCharacterId(characterId); if (client == null) { continue; @@ -73,6 +74,11 @@ public void SendMessage(string message, string firstName, string lastName, Lobby response.Recipients.AddRange(recipients); Send(response); } + + public void BroadcastMessage(LobbyChatMsgType type, string message) + { + SendMessage(message, string.Empty, string.Empty, type, _Server.ClientLookup.GetAll()); + } public void SendTellMessage(GameClient sender, GameClient receiver, C2SChatSendTellMsgReq request) { @@ -89,7 +95,7 @@ public void SendTellMessage(GameClient sender, GameClient receiver, C2SChatSendT public void SendTellMessageForeign(GameClient client, C2SChatSendTellMsgReq request) { - _server.RpcManager.AnnounceTellChat(client, request); + _Server.RpcManager.AnnounceTellChat(client, request); ChatResponse senderChatResponse = new ChatResponse { @@ -155,7 +161,7 @@ private void Deliver(GameClient client, ChatResponse response) { case LobbyChatMsgType.Say: case LobbyChatMsgType.Shout: - response.Recipients.AddRange(_server.ClientLookup.GetAll()); + response.Recipients.AddRange(_Server.ClientLookup.GetAll()); break; case LobbyChatMsgType.Party: PartyGroup party = client.Party; @@ -171,13 +177,13 @@ private void Deliver(GameClient client, ChatResponse response) break; } - response.Recipients.AddRange(_server.ClientLookup.GetAll().Where( + response.Recipients.AddRange(_Server.ClientLookup.GetAll().Where( x => x.Character != null && client.Character != null && x.Character.ClanId == client.Character.ClanId) ); - _server.RpcManager.AnnounceClanChat(client, response); + _Server.RpcManager.AnnounceClanChat(client, response); break; default: response.Recipients.Add(client); diff --git a/Arrowgene.Ddon.GameServer/DdonGameServer.cs b/Arrowgene.Ddon.GameServer/DdonGameServer.cs index 494bb9ace..a1b4bea1e 100644 --- a/Arrowgene.Ddon.GameServer/DdonGameServer.cs +++ b/Arrowgene.Ddon.GameServer/DdonGameServer.cs @@ -130,8 +130,13 @@ public DdonGameServer(GameServerSetting setting, IDatabase database, AssetReposi public override void Start() { QuestManager.LoadQuests(this); - ScheduleManager.Start(); GpCourseManager.EvaluateCourses(); + + if (ServerUtils.IsHeadServer(this)) + { + ScheduleManager.StartServerTasks(); + } + LoadChatHandler(); LoadPacketHandler(); base.Start(); diff --git a/Arrowgene.Ddon.GameServer/RpcManager.cs b/Arrowgene.Ddon.GameServer/RpcManager.cs index 19afd8d7b..0e47f642a 100644 --- a/Arrowgene.Ddon.GameServer/RpcManager.cs +++ b/Arrowgene.Ddon.GameServer/RpcManager.cs @@ -9,9 +9,11 @@ using Arrowgene.Logging; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Net.Http; using System.Net.Http.Json; +using System.Security.Claims; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; @@ -142,6 +144,11 @@ public List ServerListInfo() return ChannelInfo.Keys.Select(x => ServerListInfo(x)).ToList(); } + public ServerInfo HeadServer() + { + return ChannelInfo.Values.ToList().OrderBy(x => x.Id).ToList()[0]; + } + public CDataGameServerListInfo ServerListInfo(ushort channelId) { var info = ChannelInfo[channelId].ToCDataGameServerListInfo(); @@ -412,5 +419,10 @@ public void AnnounceClanPacket(uint clanId, T packet, uint characterId = 0) AnnounceClan(clanId, "internal/packet", RpcInternalCommand.AnnouncePacketClan, data); } } + + public void AnnounceEpitaphWeeklyReset() + { + AnnounceAll("internal/packet", RpcInternalCommand.EpitaphRoadWeeklyReset, null); + } } } diff --git a/Arrowgene.Ddon.GameServer/ScheduleManager.cs b/Arrowgene.Ddon.GameServer/ScheduleManager.cs index 8f5eede75..24224ea3f 100644 --- a/Arrowgene.Ddon.GameServer/ScheduleManager.cs +++ b/Arrowgene.Ddon.GameServer/ScheduleManager.cs @@ -45,7 +45,7 @@ private int GetTimerTick(ScheduleInterval interval) } } - public void Start() + public void StartServerTasks() { Dictionary entries = Server.Database.SelectAllTaskEntries(); diff --git a/Arrowgene.Ddon.GameServer/ServerUtils.cs b/Arrowgene.Ddon.GameServer/ServerUtils.cs new file mode 100644 index 000000000..01cffea96 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/ServerUtils.cs @@ -0,0 +1,12 @@ +using System.Linq; + +namespace Arrowgene.Ddon.GameServer +{ + public class ServerUtils + { + public static bool IsHeadServer(DdonGameServer server) + { + return server.RpcManager.HeadServer().Id == server.Id; + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs b/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs index f1cc73491..169bc15bf 100644 --- a/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs +++ b/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs @@ -1,5 +1,6 @@ using Arrowgene.Ddon.Server; using Arrowgene.Ddon.Shared.Model; +using Arrowgene.Ddon.Shared.Model.Rpc; using Arrowgene.Ddon.Shared.Model.Scheduler; using Arrowgene.Logging; using System; @@ -23,14 +24,9 @@ public override bool IsEnabled(DdonGameServer server) public override void RunTask(DdonGameServer server) { Logger.Info("Performing weekly epitaph reset"); - server.ChatManager.SendMessage("Performing weekly epitaph reset", string.Empty, string.Empty, LobbyChatMsgType.ManagementAlertN, server.ClientLookup.GetAll()); - server.Database.DeleteWeeklyEpitaphClaimedRewards(); - foreach (var client in server.ClientLookup.GetAll()) - { - client.Character.EpitaphRoadState.WeeklyRewardsClaimed.Clear(); - } + server.RpcManager.AnnounceEpitaphWeeklyReset(); } } } diff --git a/Arrowgene.Ddon.GameServer/Tasks/SchedulerTask.cs b/Arrowgene.Ddon.GameServer/Tasks/SchedulerTask.cs index 55805cb8c..f4a673705 100644 --- a/Arrowgene.Ddon.GameServer/Tasks/SchedulerTask.cs +++ b/Arrowgene.Ddon.GameServer/Tasks/SchedulerTask.cs @@ -13,6 +13,12 @@ public SchedulerTask(ScheduleInterval interval, TaskType type) Interval = interval; } + /// + /// Runs on the head server. Should deal with things like modifying the database. + /// Should use the RPC manage if it is required to update clients on different channels + /// or send annoucements to players. + /// + /// public abstract void RunTask(DdonGameServer server); public abstract long NextTimestamp(); diff --git a/Arrowgene.Ddon.GameServer/Tasks/WeeklyTask.cs b/Arrowgene.Ddon.GameServer/Tasks/WeeklyTask.cs index 54eb38115..713a863ec 100644 --- a/Arrowgene.Ddon.GameServer/Tasks/WeeklyTask.cs +++ b/Arrowgene.Ddon.GameServer/Tasks/WeeklyTask.cs @@ -12,6 +12,7 @@ public abstract class WeeklyTask : SchedulerTask /// /// Creates a task which runs on a weekly cadence. + /// Uses the timezone of the head server when calculating times. /// /// The type of event this is associated with /// The day during the week the reset should occur. diff --git a/Arrowgene.Ddon.Rpc.Web/Route/Internal/PacketRoute.cs b/Arrowgene.Ddon.Rpc.Web/Route/Internal/PacketRoute.cs index b6897c934..89d0d44b7 100644 --- a/Arrowgene.Ddon.Rpc.Web/Route/Internal/PacketRoute.cs +++ b/Arrowgene.Ddon.Rpc.Web/Route/Internal/PacketRoute.cs @@ -168,6 +168,9 @@ public override RpcCommandResult Execute(DdonGameServer gameServer) Message = $"AnnouncePacketClan Ch.{_entry.Origin} ClanID {data.ClanId} -> {packet.Id}" }; } + case RpcInternalCommand.EpitaphRoadWeeklyReset: + gameServer.EpitaphRoadManager.PerformWeeklyReset(); + return new RpcCommandResult(this, true); default: return new RpcCommandResult(this, false); } diff --git a/Arrowgene.Ddon.Shared/Model/Rpc/RpcInternalCommand.cs b/Arrowgene.Ddon.Shared/Model/Rpc/RpcInternalCommand.cs index 4589e334c..b4a8a1359 100644 --- a/Arrowgene.Ddon.Shared/Model/Rpc/RpcInternalCommand.cs +++ b/Arrowgene.Ddon.Shared/Model/Rpc/RpcInternalCommand.cs @@ -10,5 +10,7 @@ public enum RpcInternalCommand AnnouncePacketAll, // RpcPacketData AnnouncePacketClan, // RpcPacketData + + EpitaphRoadWeeklyReset } } From ccb4ebdebacbb95b90efc1398894f2f688cdbd9a Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 13 Dec 2024 19:18:35 -0500 Subject: [PATCH 3/4] Adjust hourly task - Move current hourly task implementation to NextTimeAmountTask - Implemented a new hourly task which occurs wgen transitioning from x:59 to y:00. --- Arrowgene.Ddon.GameServer/Tasks/HourlyTask.cs | 9 +++++++- .../Tasks/NextTimeAmountTask.cs | 22 +++++++++++++++++++ .../Tasks/SchedulerTask.cs | 21 +++++++++++++++++- 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 Arrowgene.Ddon.GameServer/Tasks/NextTimeAmountTask.cs diff --git a/Arrowgene.Ddon.GameServer/Tasks/HourlyTask.cs b/Arrowgene.Ddon.GameServer/Tasks/HourlyTask.cs index 5c7358651..b4360803f 100644 --- a/Arrowgene.Ddon.GameServer/Tasks/HourlyTask.cs +++ b/Arrowgene.Ddon.GameServer/Tasks/HourlyTask.cs @@ -5,13 +5,20 @@ namespace Arrowgene.Ddon.GameServer.Tasks { public abstract class HourlyTask : SchedulerTask { + /// + /// Task which is always scheduled to run on the hour in the timezone of the server currently running. + /// + /// The task type associated with this task public HourlyTask(TaskType type) : base(ScheduleInterval.Hourly, type) { } public override long NextTimestamp() { - return new DateTimeOffset(DateTime.Now.AddHours(1)).ToUnixTimeSeconds(); + var now = DateTime.Now; + var next = now.AddHours(1); + var nextTime = new DateTime(next.Year, next.Month, next.Day, next.Hour, 0, 0); + return new DateTimeOffset(now.Add(nextTime - now)).ToUnixTimeSeconds(); } } } diff --git a/Arrowgene.Ddon.GameServer/Tasks/NextTimeAmountTask.cs b/Arrowgene.Ddon.GameServer/Tasks/NextTimeAmountTask.cs new file mode 100644 index 000000000..ad030fd08 --- /dev/null +++ b/Arrowgene.Ddon.GameServer/Tasks/NextTimeAmountTask.cs @@ -0,0 +1,22 @@ +using Arrowgene.Ddon.Shared.Model.Scheduler; +using System; + +namespace Arrowgene.Ddon.GameServer.Tasks +{ + public abstract class NextTimeAmountTask : SchedulerTask + { + public uint Hours { get; } + public uint Minutes { get; } + + public NextTimeAmountTask(TaskType type, uint hours, uint minutes = 0) : base(ScheduleInterval.Hourly, type) + { + Hours = hours; + Minutes = minutes; + } + + public override long NextTimestamp() + { + return new DateTimeOffset(DateTime.Now.AddHours(Hours).AddMinutes(Minutes)).ToUnixTimeSeconds(); + } + } +} diff --git a/Arrowgene.Ddon.GameServer/Tasks/SchedulerTask.cs b/Arrowgene.Ddon.GameServer/Tasks/SchedulerTask.cs index f4a673705..fcd76a083 100644 --- a/Arrowgene.Ddon.GameServer/Tasks/SchedulerTask.cs +++ b/Arrowgene.Ddon.GameServer/Tasks/SchedulerTask.cs @@ -7,6 +7,14 @@ public abstract class SchedulerTask public TaskType Type { get; } public ScheduleInterval Interval { get; } + /// + /// Constructor for SchedulerTask. + /// + /// Hint for the type of interval this task is expected to occur at. + /// + /// The task type which is stored in the DB and used to resume the scheduler + /// timer when the head server starts. + /// public SchedulerTask(ScheduleInterval interval, TaskType type) { Type = type; @@ -18,10 +26,21 @@ public SchedulerTask(ScheduleInterval interval, TaskType type) /// Should use the RPC manage if it is required to update clients on different channels /// or send annoucements to players. /// - /// + /// The head server object public abstract void RunTask(DdonGameServer server); + + /// + /// Generates the next unix timestamp to store in the database for the task. + /// + /// Returns the unix timestamp which represents the next time this task should activate. public abstract long NextTimestamp(); + /// + /// By default, all tasks will return that they are enabled. A child class can override + /// this function to provide custom checks for enablement. + /// + /// The head server object + /// Returns true if this task is enabled, otherwise false. public virtual bool IsEnabled(DdonGameServer server) { return true; From d1f75c8a85faa3623be29b40e50b132e2574f9ef Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 16 Dec 2024 08:56:37 -0500 Subject: [PATCH 4/4] Enable Epitaph Road weekly reset by default Enabled weekly reset as the default setting. --- Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs | 2 +- Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs | 2 +- Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs | 2 +- Arrowgene.Ddon.Server/GameLogicSetting.cs | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs b/Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs index 247655845..cb3e1c3c8 100644 --- a/Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/CharacterManager.cs @@ -61,7 +61,7 @@ public Character SelectCharacter(uint characterId, DbConnection? connectionIn = character.EpitaphRoadState.UnlockedContent = _Server.Database.GetEpitaphRoadUnlocks(character.CharacterId, connectionIn); - if (_Server.Setting.GameLogicSetting.EnableEpitaphWeeklyRewards) + if (_Server.Setting.GameLogicSetting.EnableEpitaphWeeklyRewards.Value) { character.EpitaphRoadState.WeeklyRewardsClaimed = _Server.Database.GetEpitaphClaimedWeeklyRewards(character.CharacterId, connectionIn); } diff --git a/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs b/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs index 41bce2482..34c959d00 100644 --- a/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/EpitaphRoadManager.cs @@ -1290,7 +1290,7 @@ public List RollGatheringLoot(GameClient client, Charact { results.AddRange(RollWeeklyChestReward(dungeonInfo, reward)); - if (_Server.Setting.GameLogicSetting.EnableEpitaphWeeklyRewards) + if (_Server.Setting.GameLogicSetting.EnableEpitaphWeeklyRewards.Value) { character.EpitaphRoadState.WeeklyRewardsClaimed.Add(reward.EpitaphId); _Server.Database.InsertEpitaphWeeklyReward(character.CharacterId, reward.EpitaphId); diff --git a/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs b/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs index 169bc15bf..ff835cb9f 100644 --- a/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs +++ b/Arrowgene.Ddon.GameServer/Tasks/EpitaphSchedulerTask.cs @@ -18,7 +18,7 @@ public EpitaphSchedulerTask(DayOfWeek day, uint hour, uint minute) : base(TaskTy public override bool IsEnabled(DdonGameServer server) { - return server.Setting.GameLogicSetting.EnableEpitaphWeeklyRewards; + return server.Setting.GameLogicSetting.EnableEpitaphWeeklyRewards.Value; } public override void RunTask(DdonGameServer server) diff --git a/Arrowgene.Ddon.Server/GameLogicSetting.cs b/Arrowgene.Ddon.Server/GameLogicSetting.cs index 936aa137e..201e1b75f 100644 --- a/Arrowgene.Ddon.Server/GameLogicSetting.cs +++ b/Arrowgene.Ddon.Server/GameLogicSetting.cs @@ -240,7 +240,7 @@ public class GameLogicSetting /// /// Configures if epitaph rewards are limited once per weekly reset. /// - [DataMember(Order = 37)] public bool EnableEpitaphWeeklyRewards { get; set; } + [DataMember(Order = 37)] public bool? EnableEpitaphWeeklyRewards { get; set; } = true; /// Enables main pawns in party to gain EXP and JP from quests /// Original game apparantly did not have pawns share quest reward, so will set to false for default, @@ -450,6 +450,8 @@ void OnDeserialized(StreamingContext context) UrlChargeB ??= string.Empty; UrlCompanionImage ??= string.Empty; + EnableEpitaphWeeklyRewards ??= true; + EnemyExpModifier ??= 1; QuestExpModifier ??= 1; PpModifier ??= 1;