From a6d8b718292c54b689eba99f9790ea0b6db23cb4 Mon Sep 17 00:00:00 2001
From: milimoe <110188673+milimoe@users.noreply.github.com>
Date: Sun, 15 Dec 2024 16:37:57 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=BB=E5=8A=A1=E8=AE=A1?=
=?UTF-8?q?=E5=88=92=EF=BC=9B=E6=B7=BB=E5=8A=A0=E5=A4=8D=E5=88=B6=E7=89=A9?=
=?UTF-8?q?=E5=93=81=E3=80=81=E6=8A=80=E8=83=BD=E6=96=B0=E5=A2=9E=E5=A4=8D?=
=?UTF-8?q?=E5=88=B6=E9=80=89=E9=A1=B9=20(#102)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 添加任务计划
* 复制物品、技能新增复制选项;修改游戏平衡常数
* 更新游戏平衡常数
---------
Co-authored-by: yeziuku <53083103+yeziuku@users.noreply.github.com>
---
Api/Utility/TaskScheduler.cs | 129 +++++++++++++++++++++++++++
Entity/Character/Character.cs | 8 +-
Entity/Character/CharacterBuilder.cs | 35 +++++---
Entity/Item/Item.cs | 56 ++++++------
Entity/Skill/Skill.cs | 38 +++++---
Model/ActionQueue.cs | 3 +-
Model/EquilibriumConstant.cs | 4 +-
Model/RecurringTask.cs | 35 ++++++++
Model/ScheduledTask.cs | 35 ++++++++
9 files changed, 280 insertions(+), 63 deletions(-)
create mode 100644 Api/Utility/TaskScheduler.cs
create mode 100644 Model/RecurringTask.cs
create mode 100644 Model/ScheduledTask.cs
diff --git a/Api/Utility/TaskScheduler.cs b/Api/Utility/TaskScheduler.cs
new file mode 100644
index 0000000..d2c710d
--- /dev/null
+++ b/Api/Utility/TaskScheduler.cs
@@ -0,0 +1,129 @@
+using Milimoe.FunGame.Core.Model;
+
+namespace Milimoe.FunGame.Core.Api.Utility
+{
+ public class TaskScheduler
+ {
+ ///
+ /// 任务计划管理器实例,可以直接使用
+ ///
+ public static TaskScheduler Shared { get; } = new();
+
+ private readonly List _tasks = [];
+ private readonly List _recurringTasks = [];
+ private readonly Timer _timer;
+ private readonly Lock _lock = new();
+
+ ///
+ /// 创建一个轻量级的任务计划管理器
+ ///
+ public TaskScheduler()
+ {
+ _timer = new Timer(CheckAndRunTasks, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ _timer.Change(TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ }
+
+ ///
+ /// 添加一个任务计划
+ ///
+ ///
+ ///
+ ///
+ public void AddTask(string name, TimeSpan timeOfDay, Action action)
+ {
+ lock (_lock)
+ {
+ _tasks.Add(new ScheduledTask(name, timeOfDay, action));
+ }
+ }
+
+ ///
+ /// 添加一个循环任务
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void AddRecurringTask(string name, TimeSpan interval, Action action, bool startNow = false)
+ {
+ lock (_lock)
+ {
+ DateTime now = DateTime.Now;
+ now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0);
+ DateTime nextRun = startNow ? now : now.Add(interval);
+ RecurringTask recurringTask = new(name, interval, action)
+ {
+ NextRun = nextRun
+ };
+ _recurringTasks.Add(recurringTask);
+ }
+ }
+
+ ///
+ /// 移除任务计划
+ ///
+ ///
+ public void RemoveTask(string name)
+ {
+ lock (_lock)
+ {
+ int removeTasks = _tasks.RemoveAll(t => t.Name == name);
+ int removeRecurringTasks = _recurringTasks.RemoveAll(t => t.Name == name);
+ }
+ }
+
+ ///
+ /// 执行任务
+ ///
+ ///
+ private void CheckAndRunTasks(object? state)
+ {
+ lock (_lock)
+ {
+ DateTime now = DateTime.Now;
+
+ foreach (ScheduledTask task in _tasks)
+ {
+ if (!task.IsTodayRun)
+ {
+ if (now.TimeOfDay >= task.TimeOfDay && now.TimeOfDay < task.TimeOfDay.Add(TimeSpan.FromSeconds(10)))
+ {
+ task.LastRun = now;
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ try
+ {
+ task.Action();
+ }
+ catch (Exception ex)
+ {
+ task.Error = ex;
+ }
+ });
+ }
+ }
+ }
+
+ foreach (RecurringTask recurringTask in _recurringTasks)
+ {
+ if (now >= recurringTask.NextRun)
+ {
+ recurringTask.LastRun = now;
+ recurringTask.NextRun = recurringTask.NextRun.Add(recurringTask.Interval);
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ try
+ {
+ recurringTask.Action();
+ }
+ catch (Exception ex)
+ {
+ recurringTask.Error = ex;
+ }
+ });
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Entity/Character/Character.cs b/Entity/Character/Character.cs
index 8c9b9e2..dc1596a 100644
--- a/Entity/Character/Character.cs
+++ b/Entity/Character/Character.cs
@@ -1239,8 +1239,8 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"力量:{STR:0.##}" + (ExSTR != 0 ? $" [{BaseSTR:0.##} {(ExSTR >= 0 ? "+" : "-")} {Math.Abs(ExSTR):0.##}]" : "") + (showGrowth ? $"({(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv)" : ""));
builder.AppendLine($"敏捷:{AGI:0.##}" + (ExAGI != 0 ? $" [{BaseAGI:0.##} {(ExAGI >= 0 ? "+" : "-")} {Math.Abs(ExAGI):0.##}]" : "") + (showGrowth ? $"({(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv)" : ""));
builder.AppendLine($"智力:{INT:0.##}" + (ExINT != 0 ? $" [{BaseINT:0.##} {(ExINT >= 0 ? "+" : "-")} {Math.Abs(ExINT):0.##}]" : "") + (showGrowth ? $"({(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv)" : ""));
- builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * 0.25:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
- builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * 0.1:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
+ builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * General.GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
+ builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * General.GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
builder.AppendLine($"暴击率:{CritRate * 100:0.##}%");
builder.AppendLine($"暴击伤害:{CritDMG * 100:0.##}%");
builder.AppendLine($"闪避率:{EvadeRate * 100:0.##}%");
@@ -1362,8 +1362,8 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"力量:{STR:0.##}" + (ExSTR != 0 ? $" [{BaseSTR:0.##} {(ExSTR >= 0 ? "+" : "-")} {Math.Abs(ExSTR):0.##}]" : "") + (showGrowth ? $"({(STRGrowth >= 0 ? "+" : "-")}{Math.Abs(STRGrowth)}/Lv)" : ""));
builder.AppendLine($"敏捷:{AGI:0.##}" + (ExAGI != 0 ? $" [{BaseAGI:0.##} {(ExAGI >= 0 ? "+" : "-")} {Math.Abs(ExAGI):0.##}]" : "") + (showGrowth ? $"({(AGIGrowth >= 0 ? "+" : "-")}{Math.Abs(AGIGrowth)}/Lv)" : ""));
builder.AppendLine($"智力:{INT:0.##}" + (ExINT != 0 ? $" [{BaseINT:0.##} {(ExINT >= 0 ? "+" : "-")} {Math.Abs(ExINT):0.##}]" : "") + (showGrowth ? $"({(INTGrowth >= 0 ? "+" : "-")}{Math.Abs(INTGrowth)}/Lv)" : ""));
- builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * 0.25:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
- builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * 0.1:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
+ builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * General.GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
+ builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * General.GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
if (CharacterState != CharacterState.Actionable)
{
diff --git a/Entity/Character/CharacterBuilder.cs b/Entity/Character/CharacterBuilder.cs
index b864e6b..777743e 100644
--- a/Entity/Character/CharacterBuilder.cs
+++ b/Entity/Character/CharacterBuilder.cs
@@ -102,8 +102,10 @@ namespace Milimoe.FunGame.Core.Entity
///
///
///
+ ///
+ ///
/// 构建的新角色
- public Character Build(int level, IEnumerable skills, IEnumerable- items, bool newItemGuid = true, EquipSlot? equips = null)
+ public Character Build(int level, IEnumerable skills, IEnumerable
- items, bool newItemGuid = true, EquipSlot? equips = null, IEnumerable
- ? itemsDefined = null, IEnumerable? skillsDefined = null)
{
Character character = Factory.GetCharacter();
character.Id = Id;
@@ -133,7 +135,7 @@ namespace Milimoe.FunGame.Core.Entity
// 主动技能的Guid表示与其关联的物品
if (skill.Guid == Guid.Empty)
{
- Skill newskill = skill.Copy();
+ Skill newskill = skill.Copy(true, skillsDefined);
newskill.Character = character;
newskill.Level = skill.Level;
if (skill.CurrentCD > 0 && !skill.Enable)
@@ -146,7 +148,16 @@ namespace Milimoe.FunGame.Core.Entity
}
foreach (Item item in items)
{
- Item newitem = item.Copy(true, !newItemGuid);
+ Item newitem;
+ if (itemsDefined != null && itemsDefined.FirstOrDefault(i => i.GetIdName() == item.GetIdName()) is Item itemDefined)
+ {
+ newitem = itemDefined.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
+ item.SetPropertyToItemModuleNew(newitem);
+ }
+ else
+ {
+ newitem = item.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
+ }
newitem.Character = character;
character.Items.Add(newitem);
}
@@ -160,32 +171,32 @@ namespace Milimoe.FunGame.Core.Entity
Item? ac2 = equips.Accessory2;
if (mcp != null)
{
- mcp = mcp.Copy(true, !newItemGuid);
+ mcp = mcp.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
character.Equip(mcp);
}
if (w != null)
{
- w = w.Copy(true, !newItemGuid);
+ w = w.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
character.Equip(w);
}
if (a != null)
{
- a = a.Copy(true, !newItemGuid);
+ a = a.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
character.Equip(a);
}
if (s != null)
{
- s = s.Copy(true, !newItemGuid);
+ s = s.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
character.Equip(s);
}
if (ac1 != null)
{
- ac1 = ac1.Copy(true, !newItemGuid);
+ ac1 = ac1.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
character.Equip(ac1);
}
if (ac2 != null)
{
- ac2 = ac2.Copy(true, !newItemGuid);
+ ac2 = ac2.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
character.Equip(ac2);
}
}
@@ -199,10 +210,12 @@ namespace Milimoe.FunGame.Core.Entity
///
///
///
+ /// 对于动态扩展的物品而言,传入已定义的物品表,不使用被复制物品的数据
+ /// 对于动态扩展的技能而言,传入已定义的技能表,不使用被复制技能的数据
/// 构建的新角色
- public static Character Build(Character reference, bool newItemGuid = true)
+ public static Character Build(Character reference, bool newItemGuid = true, IEnumerable
- ? itemsDefined = null, IEnumerable? skillsDefined = null)
{
- Character character = new CharacterBuilder(reference).Build(reference.Level, reference.Skills, reference.Items, newItemGuid, reference.EquipSlot);
+ Character character = new CharacterBuilder(reference).Build(reference.Level, reference.Skills, reference.Items, newItemGuid, reference.EquipSlot, itemsDefined, skillsDefined);
character.NormalAttack.Level = reference.Level;
character.NormalAttack.SetMagicType(reference.NormalAttack.IsMagic, reference.NormalAttack.MagicType);
return character;
diff --git a/Entity/Item/Item.cs b/Entity/Item/Item.cs
index 870506d..b584869 100644
--- a/Entity/Item/Item.cs
+++ b/Entity/Item/Item.cs
@@ -549,48 +549,46 @@ namespace Milimoe.FunGame.Core.Entity
newbyFactory.WeaponType = WeaponType;
newbyFactory.EquipSlotType = EquipSlotType;
newbyFactory.Equipable = Equipable;
+ newbyFactory.Unequipable = Unequipable;
newbyFactory.IsPurchasable = IsPurchasable;
newbyFactory.Price = Price;
newbyFactory.IsSellable = IsSellable;
newbyFactory.NextSellableTime = NextSellableTime;
newbyFactory.IsTradable = IsTradable;
newbyFactory.NextTradableTime = NextTradableTime;
+ newbyFactory.RemainUseTimes = RemainUseTimes;
}
///
/// 复制一个物品
///
///
- public Item Copy(bool copyLevel = false, bool copyGuid = false)
+ public Item Copy(bool copyLevel = false, bool copyGuid = false, bool copyProperty = true, IEnumerable
- ? itemsDefined = null, IEnumerable? skillsDefined = null)
{
Item item = Factory.OpenFactory.GetInstance
- (Id, Name, []);
- SetPropertyToItemModuleNew(item);
- item.Id = Id;
- item.Name = Name;
+ Item? itemDefined = null;
+ if (itemsDefined != null && itemsDefined.FirstOrDefault(i => i.GetIdName() == item.GetIdName()) is Item temp)
+ {
+ itemDefined = temp;
+ }
+ if (copyProperty) SetPropertyToItemModuleNew(item);
if (copyGuid) item.Guid = Guid;
- item.Description = Description;
- item.GeneralDescription = GeneralDescription;
- item.BackgroundStory = BackgroundStory;
- item.ItemType = ItemType;
- item.Equipable = Equipable;
- item.Unequipable = Unequipable;
- item.WeaponType = WeaponType;
- item.QualityType = QualityType;
- item.RarityType = RarityType;
- item.RankType = RankType;
- item.Key = Key;
- item.Enable = Enable;
- item.IsInGameItem = IsInGameItem;
- item.IsPurchasable = IsPurchasable;
- item.Price = Price;
- item.IsSellable = IsSellable;
- item.NextSellableTime = NextSellableTime;
- item.IsTradable = IsTradable;
- item.NextTradableTime = NextTradableTime;
- item.RemainUseTimes = RemainUseTimes;
+ itemDefined ??= this;
+ item.Id = itemDefined.Id;
+ item.Name = itemDefined.Name;
+ item.Description = itemDefined.Description;
+ item.GeneralDescription = itemDefined.GeneralDescription;
+ item.BackgroundStory = itemDefined.BackgroundStory;
+ item.ItemType = itemDefined.ItemType;
+ item.QualityType = itemDefined.QualityType;
+ item.RarityType = itemDefined.RarityType;
+ item.RankType = itemDefined.RankType;
+ item.Key = itemDefined.Key;
+ item.Enable = itemDefined.Enable;
+ item.IsInGameItem = itemDefined.IsInGameItem;
if (item is OpenItem)
{
- item.Skills.Active = Skills.Active?.Copy();
+ item.Skills.Active = Skills.Active?.Copy(true, skillsDefined);
if (item.Skills.Active != null)
{
item.Skills.Active.Level = copyLevel ? (Skills.Active?.Level ?? 1) : 1;
@@ -598,7 +596,7 @@ namespace Milimoe.FunGame.Core.Entity
}
foreach (Skill skill in Skills.Passives)
{
- Skill newskill = skill.Copy();
+ Skill newskill = skill.Copy(true, skillsDefined);
newskill.Item = item;
newskill.Level = copyLevel ? skill.Level : 1;
newskill.Guid = item.Guid;
@@ -606,7 +604,7 @@ namespace Milimoe.FunGame.Core.Entity
}
foreach (Skill skill in Skills.Magics)
{
- Skill newskill = skill.Copy();
+ Skill newskill = skill.Copy(true, skillsDefined);
newskill.Item = item;
newskill.Level = copyLevel ? skill.Level : 1;
newskill.Guid = item.Guid;
@@ -638,10 +636,6 @@ namespace Milimoe.FunGame.Core.Entity
///
public void SetMagicsLevel(int level)
{
- if (Skills.Active != null)
- {
- Skills.Active.Level = level;
- }
foreach (Skill skill in Skills.Magics)
{
skill.Level = level;
diff --git a/Entity/Skill/Skill.cs b/Entity/Skill/Skill.cs
index 5b3e048..3907085 100644
--- a/Entity/Skill/Skill.cs
+++ b/Entity/Skill/Skill.cs
@@ -240,6 +240,7 @@ namespace Milimoe.FunGame.Core.Entity
///
public void SetPropertyToItemModuleNew(Skill newbyFactory)
{
+ newbyFactory.GamingQueue = GamingQueue;
newbyFactory.Enable = Enable;
newbyFactory.IsInEffect = IsInEffect;
newbyFactory.CurrentCD = CurrentCD;
@@ -452,26 +453,35 @@ namespace Milimoe.FunGame.Core.Entity
/// 复制一个技能
///
///
- public Skill Copy()
+ public Skill Copy(bool copyProperty = true, IEnumerable? skillsDefined = null)
{
Dictionary args = new()
{
{ "values", Values }
};
+ Skill? skillDefined = null;
+ if (skillsDefined != null && skillsDefined.FirstOrDefault(i => i.GetIdName() == GetIdName()) is Skill temp)
+ {
+ skillDefined = temp;
+ }
+ if (skillDefined != null)
+ {
+ args["values"] = skillDefined.Values;
+ }
Skill skill = Factory.OpenFactory.GetInstance(Id, Name, args);
- SetPropertyToItemModuleNew(skill);
- skill.Id = Id;
- skill.Name = Name;
- skill.Description = Description;
- skill.GeneralDescription = GeneralDescription;
- skill.SkillType = SkillType;
- skill.MPCost = MPCost;
- skill.CastTime = CastTime;
- skill.EPCost = EPCost;
- skill.CD = CD;
- skill.CurrentCD = CurrentCD;
- skill.HardnessTime = HardnessTime;
- skill.GamingQueue = GamingQueue;
+ skillDefined ??= skill;
+ if (copyProperty) SetPropertyToItemModuleNew(skill);
+ skill.Id = skillDefined.Id;
+ skill.Name = skillDefined.Name;
+ skill.Description = skillDefined.Description;
+ skill.GeneralDescription = skillDefined.GeneralDescription;
+ skill.SkillType = skillDefined.SkillType;
+ skill.MPCost = skillDefined.MPCost;
+ skill.CastTime = skillDefined.CastTime;
+ skill.EPCost = skillDefined.EPCost;
+ skill.CD = skillDefined.CD;
+ skill.HardnessTime = skillDefined.HardnessTime;
+ skill.GamingQueue = skillDefined.GamingQueue;
if (skill is OpenSkill)
{
foreach (Effect e in Effects)
diff --git a/Model/ActionQueue.cs b/Model/ActionQueue.cs
index 449938a..d3ddbce 100644
--- a/Model/ActionQueue.cs
+++ b/Model/ActionQueue.cs
@@ -684,6 +684,7 @@ namespace Milimoe.FunGame.Core.Model
}
else
{
+ decided = true;
WriteLine("[ " + character + $" ] 完全行动不能!");
type = CharacterActionType.None;
}
@@ -1639,7 +1640,7 @@ namespace Milimoe.FunGame.Core.Model
foreach (Character caster in castingSkills)
{
SkillTarget st = _castingSkills[caster];
- if (st.Targets.Remove(death) && st.Targets.Count == 0)
+ if (st.Targets.Remove(death))
{
_castingSkills.Remove(caster);
if (caster.CharacterState == CharacterState.Casting)
diff --git a/Model/EquilibriumConstant.cs b/Model/EquilibriumConstant.cs
index b386046..d76d3b9 100644
--- a/Model/EquilibriumConstant.cs
+++ b/Model/EquilibriumConstant.cs
@@ -203,7 +203,7 @@
///
/// 每 1 点力量增加生命回复力
///
- public double STRtoHRFactor { get; set; } = 0.25;
+ public double STRtoHRFactor { get; set; } = 0.025;
///
/// 每 1 点力量增加物理护甲
@@ -228,7 +228,7 @@
///
/// 每 1 点智力增加魔法回复力
///
- public double INTtoMRFactor { get; set; } = 0.1;
+ public double INTtoMRFactor { get; set; } = 0.01;
///
/// 每 1 点智力减少魔法消耗
diff --git a/Model/RecurringTask.cs b/Model/RecurringTask.cs
new file mode 100644
index 0000000..61ab15d
--- /dev/null
+++ b/Model/RecurringTask.cs
@@ -0,0 +1,35 @@
+namespace Milimoe.FunGame.Core.Model
+{
+ public class RecurringTask(string name, TimeSpan interval, Action action)
+ {
+ ///
+ /// 任务名称
+ ///
+ public string Name { get; set; } = name;
+
+ ///
+ /// 循环执行间隔
+ ///
+ public TimeSpan Interval { get; set; } = interval;
+
+ ///
+ /// 任务执行逻辑
+ ///
+ public Action Action { get; set; } = action;
+
+ ///
+ /// 记录上一次运行时间
+ ///
+ public DateTime? LastRun { get; set; } = null;
+
+ ///
+ /// 记录下一次运行时间
+ ///
+ public DateTime NextRun { get; set; } = DateTime.MaxValue;
+
+ ///
+ /// 最后一次运行时发生的错误
+ ///
+ public Exception? Error { get; set; }
+ }
+}
diff --git a/Model/ScheduledTask.cs b/Model/ScheduledTask.cs
new file mode 100644
index 0000000..880e994
--- /dev/null
+++ b/Model/ScheduledTask.cs
@@ -0,0 +1,35 @@
+namespace Milimoe.FunGame.Core.Model
+{
+ public class ScheduledTask(string name, TimeSpan timeSpan, Action action)
+ {
+ ///
+ /// 任务名称
+ ///
+ public string Name { get; set; } = name;
+
+ ///
+ /// 每天的目标时间
+ ///
+ public TimeSpan TimeOfDay { get; set; } = timeSpan;
+
+ ///
+ /// 任务执行逻辑
+ ///
+ public Action Action { get; set; } = action;
+
+ ///
+ /// 记录上一次运行时间
+ ///
+ public DateTime? LastRun { get; set; } = null;
+
+ ///
+ /// 当天是否已经运行
+ ///
+ public bool IsTodayRun => LastRun.HasValue && LastRun.Value.Date == DateTime.Today;
+
+ ///
+ /// 最后一次运行时发生的错误
+ ///
+ public Exception? Error { get; set; }
+ }
+}