diff --git a/docs/角色与羁绊系统.md b/docs/角色与羁绊系统.md index abfb9b5..71d3253 100644 --- a/docs/角色与羁绊系统.md +++ b/docs/角色与羁绊系统.md @@ -238,4 +238,139 @@ * 你的战斗节奏是什么(平稳输出?憋大招爆发?赌博?) * 你的团队构成是什么。 -这完美解决了“文科生不需要做实验”的逻辑漏洞,同时也大大加深了游戏的策略池。 \ No newline at end of file +这完美解决了“文科生不需要做实验”的逻辑漏洞,同时也大大加深了游戏的策略池。 + +这是一个极具策略深度的改进点。将**个人怪癖 (Traits)** 与 **羁绊叠层 (Stacking)** 挂钩,可以让棋子的价值不再仅仅取决于面板数值,而是取决于他在“构筑 (Build)”中的位置。 + +这一类怪癖我们称为 **“催化剂特质” (Catalyst Traits)**。它们可以视为“万能牌”、“增幅器”或者“阻断剂”。 + +以下是针对“实验室生态(人群)”和“职能分工(职业)”设计的 20 个互动型怪癖: + +--- + +### 一、 增益型:为了凑羁绊而生 (Bond Boosters) +*这些棋子是构筑流派的核心插件,或者是前期的强力打工仔。* + +#### 1. 【学术变色龙】 (Academic Chameleon) +* **效果:** 该单位入场时,自动**模仿**队伍中当前层数最高的“人群画像”羁绊(如场上卷王最多,他自动视为卷王)。 +* **叠层互动:** 视为 1 个单位,但类型灵活。 +* **点评:** “为了混进圈子,他什么都能演。” + +#### 2. 【简历注水】 (Resume Padder) +* **效果:** 该单位在计算“职能分工”羁绊时,**视为 2 个单位**。但他本人的实际工作效率 -30%。 +* **叠层互动:** 快速凑齐 (4) 或 (6) 层羁绊的强力挂件。 +* **点评:** “虽然代码不是他写的,但项目组名单里他排第一个。” + +#### 3. 【跨学科天才】 (Interdisciplinary) +* **效果:** 同时拥有【炼金术士】和【极客】(或随机两个职业)的双重标签。 +* **叠层互动:** 可以同时为两个职业羁绊提供层数 +1。 +* **点评:** “生物里代码写得最好的,码农里最懂分子式的。” + +#### 4. 【精神股东】 (Fanatic) +* **效果:** 如果当前激活了【卷王】或【富家子弟】羁绊,该单位享受的羁绊加成效果 **翻倍**。 +* **叠层互动:** 单体享受双倍Buff。 +* **点评:** “明明拿的是实习生的工资,操的却是CEO的心。” + +#### 5. 【气氛组】 (Hype Man) +* **效果:** 只要他在场,当前激活的所有羁绊层数需求 **-1**(例如原本 4 人触发的效果,现在 3 人即可触发)。但全队压力增长 +10%。 +* **叠层互动:** 降低触发门槛(仅限第二层级以上)。 +* **点评:** “只要我不尴尬,这个组就是世界一流团队。” + +#### 6. 【领头羊】 (Alpha) +* **效果:** 必须作为队长(放在特定核心格子上)才能生效。使当前最高的职业羁绊等级 **+1**(突破上限,如创造出 7 层羁绊效果)。 +* **叠层互动:** 突破上限。 +* **点评:** “他重新定义了这个专业。” + +--- + +### 二、 减益/干扰型:副作用与高回报 (Disruptors) +*这些棋子通常数值极高,但会破坏团队化学反应,适合“独狼”或特定解法。* + +#### 7. 【害群之马】 (Black Sheep) +* **效果:** 该单位的全属性 +50%(极强),但在计算任何羁绊层数时,**视为 -1 个单位**。 +* **叠层互动:** 扣除层数。可能导致原本激活的 (4) 层掉回 (3) 层。 +* **点评:** “技术是大牛,但只要他在,团队就没法团结。” + +#### 8. 【独行侠】 (Lone Wolf) +* **效果:** 如果该单位**没有**激活任何羁绊(既不是卷王也不享受职业加成),攻击力 +100%。 +* **叠层互动:** 只有不叠层才强。 +* **点评:** “我不抱团,我就是大腿。” + +#### 9. 【熵增发生器】 (Entropy Generator) +* **效果:** 随机**无效化**场上的一个已激活羁绊,转化为全队的“吸血”效果(工作回心情)。 +* **叠层互动:** 献祭羁绊换取生存。 +* **点评:** “毁灭秩序,带来混乱的快乐。” + +#### 10. 【鄙视链顶端】 (Elitist) +* **效果:** 如果他在场,**除他以外**的所有同职业棋子(如其他写手)羁绊收益归零,但他本人获得所有被归零收益的总和。 +* **叠层互动:** 吸星大法,献祭队友养大哥。 +* **点评:** “文无第一,但我就是第一,你们都是垃圾。” + +#### 11. 【社恐患者】 (Social Phobia) +* **效果:** 如果激活了【人群画像】(如卷王/摸鱼党)这种涉及社交氛围的羁绊,他会立刻**逃跑**(离场或停止工作)。只有在“无阵营”状态下才能工作。 +* **叠层互动:** 互斥机制。 +* **点评:** “人一多我就想吐。” + +--- + +### 三、 特殊机制型:动态与随机 (Special Mechanics) +*增加Roguelike的不可预测性。* + +#### 12. 【墙头草】 (Bandwagoner) +* **效果:** 每回合开始时,随机变成场上人数最多的那个职业。 +* **叠层互动:** 动态补位。 +* **点评:** “这就是所谓的'追逐热点',哪个方向火我就搞哪个。” + +#### 13. 【卧底】 (Imposter) +* **效果:** 表面上显示为【卷王】,实际上在计算层数时视为【摸鱼党】。 +* **叠层互动:** 欺骗玩家(或者在PVP/对抗模式中欺骗对手)。 +* **点评:** “看他在疯狂敲键盘,其实是在和群友对喷。” + +#### 14. 【催化剂】 (Catalyst) +* **效果:** 他不提供层数,但能改变羁绊的**性质**。 + * 例如:让【卷王】羁绊的“攻速加成”变为“金钱产出”。 + * 例如:让【摸鱼党】羁绊的“心情恢复”变为“甚至能恢复压力(San值)”。 +* **叠层互动:** 质变。 +* **点评:** “他改变了这股风气的走向。” + +#### 15. 【内卷终结者】 (The Breaker) +* **效果:** 当【卷王】层数达到 (6) 时,他会自爆,清空所有卷王层数,并给予全队一个巨额的“解放”Buff(瞬间完成当前任务)。 +* **叠层互动:** 阈值触发型核弹。 +* **点评:** “同归于尽吧,该死的KPI!” + +#### 16. 【学术寄生虫】 (Parasite) +* **效果:** 只要场上有任何 (4) 层以上的羁绊激活,该单位每回合自动升级(星级/属性提升),但他自己不贡献层数。 +* **叠层互动:** 纯收益者。 +* **点评:** “大树底下好乘凉。” + +#### 17. 【教条主义者】 (Purist) +* **效果:** 只有当全队**所有单位**都是同一个职业(如全是代码工)时,全队属性 +50%。只要混入一个异类,加成消失。 +* **叠层互动:** 纯色队核心。 +* **点评:** “我们的血统必须纯正。” + +#### 18. 【客座教授】 (Visiting Scholar) +* **效果:** 每回合随机获得一个不同的职业标签(本回合是【笔杆子】,下回合是【大忽悠】)。 +* **叠层互动:** 随机性补位。 +* **点评:** “我在很多领域都有涉猎……虽然都不精。” + +#### 19. 【镜像】 (Mirror) +* **效果:** 复制站在他**左边**那个棋子的所有职业和人群标签。 +* **叠层互动:** 位置相关的复制。 +* **点评:** “导师,我也想做师兄那个课题。” + +#### 20. 【负重训练】 (Weight Trainer) +* **效果:** 他使所有羁绊的触发门槛 **+1**(例如需要 3 人才能触发 2 人效果),但一旦触发,效果提升 **150%**。 +* **叠层互动:** 提高门槛换取高强度的后期流派。 +* **点评:** “我们要走精英路线,不是什么阿猫阿狗都能进来的。” + +--- + +### 设计总结 + +通过引入这些对叠层机制进行**增益(Amplifiers)、伪装(Masqueraders)、破坏(Saboteurs)**的特质,游戏的策略空间被大幅拉伸了: + +* **初期:** 玩家可能会为了凑齐 (2) 层羁绊,不得不忍受一个带有【简历注水】但属性很差的棋子。 +* **中期:** 玩家可能会遇到一个【害群之马】的S级强卡,为了用他,不得不重构整个队伍,放弃某些羁绊。 +* **后期:** 玩家可能会利用【领头羊】或【负重训练】来打造一个极致的数值怪物团队。 + +这使得“特质”不再是单调的Buff,而是连接“单卡”与“团队”的粘合剂。 \ No newline at end of file diff --git a/scripts/Core/ContentRegistry.cs b/scripts/Core/ContentRegistry.cs new file mode 100644 index 0000000..c51d215 --- /dev/null +++ b/scripts/Core/ContentRegistry.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using Models; + +namespace Core; + +/// +/// 内容加载与合并(基础 + Mod) +/// 设计说明: +/// 1) 通过 IContentSource 抽象读取来源,支持 res:// 与 user://mods。 +/// 2) ContentRegistry 负责合并,同 Id 以后加载覆盖先加载。 +/// 注意事项: +/// - 真实加载逻辑应避免在主线程做大规模 IO。 +/// 未来扩展: +/// - 可加入“补丁合并策略”(例如列表合并/字段覆盖)。 +/// +public interface IContentSource +{ + int Priority { get; } + IEnumerable LoadAll() where T : class; +} + +public enum ContentMergeMode +{ + Override, + KeepFirst +} + +public sealed class ContentRegistry +{ + private readonly List _sources = new(); + public ContentMergeMode MergeMode { get; set; } = ContentMergeMode.Override; + + public void RegisterSource(IContentSource source) + { + _sources.Add(source); + _sources.Sort((a, b) => a.Priority.CompareTo(b.Priority)); + } + + public GameContentDatabase BuildDatabase() + { + var db = new GameContentDatabase(); + Merge(db.Disciplines, LoadAll(), d => d.Header.Id); + Merge(db.Archetypes, LoadAll(), d => d.Header.Id); + Merge(db.Roles, LoadAll(), d => d.Header.Id); + Merge(db.Traits, LoadAll(), d => d.Header.Id); + Merge(db.Tasks, LoadAll(), d => d.Header.Id); + Merge(db.Items, LoadAll(), d => d.Header.Id); + Merge(db.Papers, LoadAll(), d => d.Header.Id); + Merge(db.RoguelitePerks, LoadAll(), d => d.Header.Id); + return db; + } + + private IEnumerable LoadAll() where T : class + { + foreach (var source in _sources) + { + foreach (var item in source.LoadAll()) + { + yield return item; + } + } + } + + private void Merge(Dictionary target, IEnumerable items, Func idSelector) where T : class + { + foreach (var item in items) + { + var id = idSelector(item); + if (string.IsNullOrWhiteSpace(id)) + { + continue; + } + + if (target.ContainsKey(id)) + { + if (MergeMode == ContentMergeMode.Override) + { + target[id] = item; + } + } + else + { + target[id] = item; + } + } + } +} + +/// +/// 资源读取示例:res:// 中的 .tres/.res +/// 这里只给出接口骨架,具体解析留给后续实现。 +/// +public sealed class ResourceContentSource : IContentSource +{ + public int Priority { get; } + public List ResourcePaths { get; } = new(); + + public ResourceContentSource(int priority) + { + Priority = priority; + } + + public IEnumerable LoadAll() where T : class + { + yield break; + } +} + +/// +/// 资源读取示例:json/yaml/自定义格式(Mod 友好) +/// +public sealed class JsonContentSource : IContentSource +{ + public int Priority { get; } + public List DataPaths { get; } = new(); + + public JsonContentSource(int priority) + { + Priority = priority; + } + + public IEnumerable LoadAll() where T : class + { + yield break; + } +} + diff --git a/scripts/Core/EventBus.cs b/scripts/Core/EventBus.cs new file mode 100644 index 0000000..2ddc389 --- /dev/null +++ b/scripts/Core/EventBus.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; + +namespace Core; + +/// +/// 轻量事件总线(解耦系统之间的通信) +/// 设计说明: +/// 1) 系统层发布 DomainEvent,View/Controller 可订阅。 +/// 2) 不依赖 Godot Signal,便于纯逻辑测试。 +/// 注意事项: +/// - 事件为进程内同步调用,避免在事件里做耗时操作。 +/// 未来扩展: +/// - 可加入“事件队列”和“延迟派发”以支持回合结算。 +/// +public sealed class DomainEventBus +{ + private readonly Dictionary> _handlers = new(); + + public void Subscribe(Action handler) + { + var type = typeof(T); + if (!_handlers.TryGetValue(type, out var list)) + { + list = new List(); + _handlers[type] = list; + } + + list.Add(handler); + } + + public void Unsubscribe(Action handler) + { + var type = typeof(T); + if (_handlers.TryGetValue(type, out var list)) + { + list.Remove(handler); + } + } + + public void Publish(T evt) + { + var type = typeof(T); + if (!_handlers.TryGetValue(type, out var list)) + { + return; + } + + foreach (var handler in list) + { + if (handler is Action typedHandler) + { + typedHandler.Invoke(evt); + } + } + } +} + diff --git a/scripts/Core/GameController.cs b/scripts/Core/GameController.cs new file mode 100644 index 0000000..a8d5089 --- /dev/null +++ b/scripts/Core/GameController.cs @@ -0,0 +1,43 @@ +using Models; + +namespace Core; + +/// +/// 高层控制器(面向 UI/输入) +/// 设计说明: +/// 1) Controller 只负责“指令式操作”,不直接做数值计算。 +/// 2) 复杂结算交给系统层(GameSystems)。 +/// 注意事项: +/// - 需要可重入性,避免重复触发同一阶段切换。 +/// 未来扩展: +/// - 可加入“命令队列”,用于回放或网络同步。 +/// +public sealed class GameController : IController +{ + private GameSession _session; + + public void Initialize(GameSession session) + { + _session = session; + } + + public void StartExecution() + { + if (_session.State.Turn.Phase != GamePhase.Planning) return; + _session.State.Turn.Phase = GamePhase.Execution; + } + + public void EndExecution() + { + if (_session.State.Turn.Phase != GamePhase.Execution) return; + _session.State.Turn.Phase = GamePhase.Review; + } + + public void StartNextTurn() + { + if (_session.State.Turn.Phase != GamePhase.Review) return; + _session.State.Turn.CurrentTurn++; + _session.State.Turn.Phase = GamePhase.Planning; + } +} + diff --git a/scripts/Core/GameSession.cs b/scripts/Core/GameSession.cs new file mode 100644 index 0000000..d668f6c --- /dev/null +++ b/scripts/Core/GameSession.cs @@ -0,0 +1,47 @@ +using Models; + +namespace Core; + +/// +/// 游戏会话(运行时入口) +/// 设计说明: +/// 1) 将 State + Content + Systems + Services 组织为一个整体。 +/// 2) 便于在 Godot Node 中持有,避免散落的静态单例。 +/// 注意事项: +/// - 真实项目中应通过工厂或依赖注入创建。 +/// 未来扩展: +/// - 可加入“加载存档/保存存档/重置”接口。 +/// +public sealed class GameSession +{ + public GameState State { get; } + public GameContentDatabase Content { get; } + public DomainEventBus Events { get; } + public ILocalizationService Localization { get; } + public GameSystems Systems { get; } + + public GameSession(GameState state, GameContentDatabase content, ILocalizationService localization, DomainEventBus events) + { + State = state; + Content = content; + Localization = localization; + Events = events; + Systems = new GameSystems(); + Systems.Initialize(this); + } + + public static GameSession CreateDefault() + { + var registry = new ContentRegistry(); + var content = registry.BuildDatabase(); + var localization = new GodotLocalizationService(); + var events = new DomainEventBus(); + return new GameSession(new GameState(), content, localization, events); + } + + public void Tick(float delta) + { + Systems.Tick(delta); + } +} + diff --git a/scripts/Core/GameSystems.cs b/scripts/Core/GameSystems.cs new file mode 100644 index 0000000..81a1374 --- /dev/null +++ b/scripts/Core/GameSystems.cs @@ -0,0 +1,122 @@ +using Models; + +namespace Core; + +/// +/// 系统层骨架(负责模型变化,不直接处理输入/显示) +/// 设计说明: +/// 1) 每个系统只关注单一职责,形成高内聚低耦合结构。 +/// 2) 系统之间通过 GameSession 共享状态,通过事件总线通讯。 +/// 注意事项: +/// - 系统逻辑应尽量“幂等”,便于回合重算与调试。 +/// 未来扩展: +/// - 可加入“系统执行顺序配置”,支持 Mod 插入新系统。 +/// +public interface IGameSystem +{ + void Initialize(GameSession session); + void Tick(float delta); +} + +public sealed class GameSystems +{ + public TurnSystem Turn { get; } = new(); + public TaskSystem Task { get; } = new(); + public EconomySystem Economy { get; } = new(); + public SynergySystem Synergy { get; } = new(); + public AssignmentSystem Assignment { get; } = new(); + + public void Initialize(GameSession session) + { + Turn.Initialize(session); + Task.Initialize(session); + Economy.Initialize(session); + Synergy.Initialize(session); + Assignment.Initialize(session); + } + + public void Tick(float delta) + { + Turn.Tick(delta); + Task.Tick(delta); + Economy.Tick(delta); + Synergy.Tick(delta); + Assignment.Tick(delta); + } +} + +public sealed class TurnSystem : IGameSystem +{ + private GameSession _session; + + public void Initialize(GameSession session) + { + _session = session; + } + + public void Tick(float delta) + { + // 预留:回合推进计时器/阶段切换 + } +} + +public sealed class TaskSystem : IGameSystem +{ + private GameSession _session; + + public void Initialize(GameSession session) + { + _session = session; + } + + public void Tick(float delta) + { + // 预留:执行阶段推进任务进度 + } +} + +public sealed class EconomySystem : IGameSystem +{ + private GameSession _session; + + public void Initialize(GameSession session) + { + _session = session; + } + + public void Tick(float delta) + { + // 预留:利息结算、工资消耗等 + } +} + +public sealed class SynergySystem : IGameSystem +{ + private GameSession _session; + + public void Initialize(GameSession session) + { + _session = session; + } + + public void Tick(float delta) + { + // 预留:根据 roster 统计羁绊层数 + } +} + +public sealed class AssignmentSystem : IGameSystem +{ + private GameSession _session; + + public void Initialize(GameSession session) + { + _session = session; + } + + public void Tick(float delta) + { + // 预留:人员分配、交接惩罚等 + } +} + diff --git a/scripts/Core/LocalizationService.cs b/scripts/Core/LocalizationService.cs new file mode 100644 index 0000000..a8f8ba3 --- /dev/null +++ b/scripts/Core/LocalizationService.cs @@ -0,0 +1,46 @@ +using Godot; +using Models; + +namespace Core; + +/// +/// i18n 服务抽象 +/// 设计说明: +/// 1) Model 只持有 LocalizedText(Key/Fallback),具体翻译交给服务层。 +/// 2) 便于后续替换为自定义多语言系统或 Mod 词库。 +/// 注意事项: +/// - TranslationServer 的翻译表应通过 Godot Editor 管理。 +/// 未来扩展: +/// - 可加入“参数化翻译”和“运行时语言切换事件”。 +/// +public interface ILocalizationService +{ + string Translate(LocalizedText text); + string Translate(string key, string fallback = null); +} + +public sealed class GodotLocalizationService : ILocalizationService +{ + public string Translate(LocalizedText text) + { + if (text == null) return string.Empty; + return Translate(text.Key, text.Fallback); + } + + public string Translate(string key, string fallback = null) + { + if (string.IsNullOrWhiteSpace(key)) + { + return fallback ?? string.Empty; + } + + var translated = TranslationServer.Translate(key); + if (translated == key && !string.IsNullOrWhiteSpace(fallback)) + { + return fallback; + } + + return translated; + } +} + diff --git a/scripts/Core/ModManifest.cs b/scripts/Core/ModManifest.cs new file mode 100644 index 0000000..96d1a6a --- /dev/null +++ b/scripts/Core/ModManifest.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace Core; + +/// +/// Mod 清单与包信息 +/// 设计说明: +/// 1) 通过 Manifest 提供版本/依赖/内容路径。 +/// 2) 与 ContentRegistry 配合,实现热加载或启动时加载。 +/// 注意事项: +/// - 建议为每个 Mod 指定唯一 Id 与语义化版本。 +/// 未来扩展: +/// - 可加入“加载开关/冲突检测/签名校验”。 +/// +public sealed class ModManifest +{ + public string Id { get; set; } + public string Name { get; set; } + public string Version { get; set; } + public List Dependencies { get; } = new(); + public List ContentPaths { get; } = new(); +} + +public sealed class ModPackage +{ + public ModManifest Manifest { get; set; } = new(); + public string RootPath { get; set; } +} + diff --git a/scripts/Core/Mvc.cs b/scripts/Core/Mvc.cs new file mode 100644 index 0000000..db6f81c --- /dev/null +++ b/scripts/Core/Mvc.cs @@ -0,0 +1,39 @@ +using Godot; + +namespace Core; + +/// +/// MVC 基础接口 +/// 设计说明: +/// 1) View 只负责显示与输入,Controller 负责修改 Model。 +/// 2) View 与 Model 通过 Bind 绑定,避免静态单例。 +/// 注意事项: +/// - View 内不要直接写业务结算逻辑。 +/// 未来扩展: +/// - 可加入“脏标记刷新”与“差量更新”以减少 UI 开销。 +/// +public interface IView +{ + void Bind(TModel model); +} + +public interface IController +{ + void Initialize(GameSession session); +} + +public abstract partial class ModelView : Node, IView +{ + public TModel Model { get; private set; } + + public virtual void Bind(TModel model) + { + Model = model; + OnModelBound(); + } + + protected virtual void OnModelBound() + { + } +} + diff --git a/scripts/GameManager.cs b/scripts/GameManager.cs index 8f67441..d6e7473 100644 --- a/scripts/GameManager.cs +++ b/scripts/GameManager.cs @@ -2,7 +2,18 @@ using Godot; using System; using System.Collections.Generic; using Models; +using Core; +/// +/// 游戏主控制节点(MVC 的桥接层) +/// 设计说明: +/// 1) 负责初始化 GameSession,并把输入/信号转交给 Controller/System。 +/// 2) 仅做“流程调度”,不直接计算复杂数值,避免逻辑堆积。 +/// 注意事项: +/// - View 与 Model 的绑定应逐步迁移到独立 View 脚本,避免在此类膨胀。 +/// 未来扩展: +/// - 可拆分为 SceneRoot + UIController + DebugController,分层管理。 +/// public partial class GameManager : Node { /// @@ -12,25 +23,22 @@ public partial class GameManager : Node public static string NextScene { get; set; } = null; public static readonly Resource Arrow2X = ResourceLoader.Load("res://temp_res/kenney_ui-pack-space-expansion/PNG/Extra/Double/cursor_f.png"); - // --- Core Loop Definitions --- - public enum GamePhase - { - Planning, // 筹备阶段:时间暂停,分配任务,购买设施 - Execution, // 执行阶段:时间流动,学生工作 - Review, // 结算阶段:回合结束,发工资,结算成果 - } - [Export] public int MaxTurns = 30; // --- Global State --- - public static GamePhase CurrentPhase { get; private set; } = GamePhase.Planning; - public static int CurrentTurn { get; private set; } = 1; - + public GameSession Session { get; private set; } + public GameState State => Session?.State; + public GameController Controller { get; private set; } + + public GamePhase CurrentPhase => State?.Turn.Phase ?? GamePhase.Planning; + public int CurrentTurn => State?.Turn.CurrentTurn ?? 1; + // --- Domain Model --- - public MentorModel Mentor { get; private set; } - public List Students { get; private set; } = new List(); - public List ActiveTasks { get; private set; } = new List(); + public MentorModel Mentor => State?.Roster.Mentor; + public List Students => State?.Roster.Students; + public List ActiveTasks => State?.Tasks.ActiveTasks; + public EconomyState Economy => State?.Economy; // --- Signals --- [Signal] public delegate void PhaseChangedEventHandler(int phase); // int cast of GamePhase @@ -54,15 +62,21 @@ public partial class GameManager : Node private void InitializeGame() { - CurrentTurn = 1; - CurrentPhase = GamePhase.Planning; + Session = GameSession.CreateDefault(); + Controller = new GameController(); + Controller.Initialize(Session); + State.Turn.MaxTurns = MaxTurns; + State.Turn.CurrentTurn = 1; + State.Turn.Phase = GamePhase.Planning; // MVP: Add a test student var s1 = new StudentModel("张三"); Students.Add(s1); // MVP: Add a test task - var t1 = new Task("深度学习导论", TaskType.Paper, 1000f, 3); + var t1 = new TaskModel("深度学习导论", TaskKind.AcademicExploration, 1000f, 3); + t1.Reward.Money = 1000; + t1.Reward.Reputation = 5; ActiveTasks.Add(t1); GD.Print("Game Initialized. Phase: Planning, Turn: 1"); @@ -76,6 +90,8 @@ public partial class GameManager : Node // Update game logic (timers, etc.) // In a real implementation, this might manage the global timer for the day/week } + + Session?.Tick((float)delta); } /// @@ -85,7 +101,7 @@ public partial class GameManager : Node { if (CurrentPhase != GamePhase.Planning) return; - CurrentPhase = GamePhase.Execution; + Controller.StartExecution(); EmitSignal(SignalName.PhaseChanged, (int)CurrentPhase); GD.Print("Phase Changed: Execution"); @@ -99,7 +115,7 @@ public partial class GameManager : Node { if (CurrentPhase != GamePhase.Execution) return; - CurrentPhase = GamePhase.Review; + Controller.EndExecution(); EmitSignal(SignalName.PhaseChanged, (int)CurrentPhase); GD.Print("Phase Changed: Review"); @@ -114,17 +130,17 @@ public partial class GameManager : Node // 1. Task progress check foreach (var task in ActiveTasks) { - task.Deadline--; + task.Runtime.RemainingTurns--; if (task.IsCompleted) { GD.Print($"Task {task.Name} Completed!"); - Mentor.Reputation += task.RewardReputation; - Mentor.Money += task.RewardMoney; + Economy.Reputation += task.Reward.Reputation; + Economy.Money += task.Reward.Money; } else if (task.IsFailed) { GD.Print($"Task {task.Name} Failed!"); - Mentor.Reputation -= 10; // Penalty + Economy.Reputation -= 10; // Penalty } } @@ -144,20 +160,20 @@ public partial class GameManager : Node { if (CurrentPhase != GamePhase.Review) return; - CurrentTurn++; - if (CurrentTurn > MaxTurns) + Controller.StartNextTurn(); + if (CurrentTurn > State.Turn.MaxTurns) { GD.Print("Game Over!"); // Handle Game Over return; } - CurrentPhase = GamePhase.Planning; + State.Turn.Phase = GamePhase.Planning; EmitSignal(SignalName.TurnChanged, CurrentTurn); EmitSignal(SignalName.PhaseChanged, (int)CurrentPhase); // Refresh resources/AP - Mentor.Energy.Current = Mentor.Energy.UpperThreshold; + Mentor.Resources.Energy.Current.Value = Mentor.Resources.Energy.UpperThreshold; GD.Print($"Turn {CurrentTurn} Started. Phase: Planning"); } diff --git a/scripts/Models/CoreIds.cs b/scripts/Models/CoreIds.cs new file mode 100644 index 0000000..75f603e --- /dev/null +++ b/scripts/Models/CoreIds.cs @@ -0,0 +1,55 @@ +namespace Models; + +/// +/// 内置内容 Id(方便代码引用,避免魔法字符串) +/// 设计说明: +/// 1) 仅列出核心/内置内容,Mod 内容不应写在这里。 +/// 2) Id 使用 “namespace:name” 形式,默认 namespace 为 core。 +/// 注意事项: +/// - 更改 Id 会破坏存档兼容性,请谨慎修改。 +/// 未来扩展: +/// - 可拆分为多个文件(Disciplines/Roles/Items)以减少体积。 +/// +public static class CoreIds +{ + public const string Namespace = "core"; + + // Disciplines + public const string DisciplineBiology = "core:discipline_biology"; + public const string DisciplineChemistry = "core:discipline_chemistry"; + public const string DisciplineEnvironment = "core:discipline_environment"; + public const string DisciplineMaterials = "core:discipline_materials"; + public const string DisciplineMedicine = "core:discipline_medicine"; + public const string DisciplineComputer = "core:discipline_computer"; + public const string DisciplineMath = "core:discipline_math"; + public const string DisciplinePhysics = "core:discipline_physics"; + public const string DisciplineMechanical = "core:discipline_mechanical"; + public const string DisciplinePhilosophy = "core:discipline_philosophy"; + public const string DisciplineEconomics = "core:discipline_economics"; + public const string DisciplineLaw = "core:discipline_law"; + public const string DisciplineLiterature = "core:discipline_literature"; + public const string DisciplineAgriculture = "core:discipline_agriculture"; + public const string DisciplineManagement = "core:discipline_management"; + public const string DisciplineArt = "core:discipline_art"; + + // Archetypes + public const string ArchetypeGrinder = "core:archetype_grinder"; + public const string ArchetypeSlacker = "core:archetype_slacker"; + public const string ArchetypeElite = "core:archetype_elite"; + public const string ArchetypeProdigy = "core:archetype_prodigy"; + public const string ArchetypeMascot = "core:archetype_mascot"; + + // Roles + public const string RoleCoder = "core:role_coder"; + public const string RoleWriter = "core:role_writer"; + public const string RoleLabRat = "core:role_lab_rat"; + public const string RolePresenter = "core:role_presenter"; + public const string RoleScribe = "core:role_scribe"; + public const string RoleOrator = "core:role_orator"; + public const string RoleSteward = "core:role_steward"; + public const string RoleAlchemist = "core:role_alchemist"; + public const string RoleGeek = "core:role_geek"; + public const string RoleSurveyor = "core:role_surveyor"; + public const string RoleThinker = "core:role_thinker"; +} + diff --git a/scripts/Models/DefinitionSupport.cs b/scripts/Models/DefinitionSupport.cs new file mode 100644 index 0000000..fb95a65 --- /dev/null +++ b/scripts/Models/DefinitionSupport.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace Models; + +/// +/// 定义数据的通用结构(i18n + 标识 + 标签) +/// 设计说明: +/// 1) LocalizedText 只保存 Key/Fallback,不做翻译,翻译交给服务层。 +/// 2) DefinitionHeader 用组合而非继承,避免所有定义都挂在同一基类。 +/// 3) Tags 统一用 string Id,便于 Mod 增删与过滤。 +/// 注意事项: +/// - 资源路径统一使用 res:// 且小写下划线。 +/// - Id 建议使用 “namespace:name” 形式,例如 “core:discipline_biology”。 +/// 未来扩展: +/// - 可加入 Icon/Color/SortOrder 以支持 UI 排序与分组。 +/// - 可加入 Source/Version 字段用于 Mod 冲突排查。 +/// +public sealed class LocalizedText +{ + public string Key { get; set; } + public string Fallback { get; set; } +} + +public sealed class DefinitionHeader +{ + public string Id { get; set; } + public LocalizedText Name { get; set; } = new(); + public LocalizedText Description { get; set; } = new(); + public string IconPath { get; set; } + public List Tags { get; } = new(); +} + diff --git a/scripts/Models/DisciplineDefinitions.cs b/scripts/Models/DisciplineDefinitions.cs new file mode 100644 index 0000000..900aa28 --- /dev/null +++ b/scripts/Models/DisciplineDefinitions.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace Models; + +/// +/// 学科定义(决定开局流派、核心资源循环与卡池) +/// 设计说明: +/// 1) 学科只描述“规则与池子”,具体资源结算由系统层执行。 +/// 2) Buff 使用 ModifierBundle + RuleIds 组合,便于非数值机制扩展。 +/// 注意事项: +/// - DisciplineId 应稳定,用于存档与 Mod 兼容。 +/// 未来扩展: +/// - 可加入“学科子方向”与“跨学科解锁条件”。 +/// +public sealed class DisciplineDefinition +{ + public DefinitionHeader Header { get; set; } = new(); + public DisciplineBuff Buff { get; set; } = new(); + + public List RolePoolIds { get; } = new(); + public List ItemPoolIds { get; } = new(); + public List TaskKeywordIds { get; } = new(); +} + +public sealed class DisciplineBuff +{ + public LocalizedText Name { get; set; } = new(); + public LocalizedText Description { get; set; } = new(); + public ModifierBundle Modifiers { get; set; } = new(); +} + diff --git a/scripts/Models/DomainEnums.cs b/scripts/Models/DomainEnums.cs new file mode 100644 index 0000000..dc54745 --- /dev/null +++ b/scripts/Models/DomainEnums.cs @@ -0,0 +1,43 @@ +namespace Models; + +/// +/// 通用枚举(跨系统共享) +/// 设计说明: +/// 1) 这些枚举用于 Modifier 与系统逻辑,不直接绑定某个 UI。 +/// 2) 若未来需要 Mod 扩展新类型,可将枚举替换为 string Id。 +/// 注意事项: +/// - 如果确实需要开放扩展,请避免在存档里写枚举的数值。 +/// 未来扩展: +/// - 可加入“行动点/灵感/算力”等资源类型。 +/// +public enum AttributeType +{ + Academic, + Engineering, + Writing, + Financial, + Social, + Activation +} + +public enum ResourceType +{ + Money, + Reputation, + ResearchPoints, + Paper, + Inspiration, + Time +} + +public enum StatusType +{ + Stress, + Sanity, + Mood, + Stamina, + Loyalty, + Energy, + Health +} + diff --git a/scripts/Models/GameContentDatabase.cs b/scripts/Models/GameContentDatabase.cs new file mode 100644 index 0000000..608ff73 --- /dev/null +++ b/scripts/Models/GameContentDatabase.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Models; + +/// +/// 游戏内容数据库(Definitions 聚合) +/// 设计说明: +/// 1) 将所有配置型数据集中到一个对象,便于系统层按 Id 索引。 +/// 2) 由 ContentRegistry 构建,支持 Mod 覆盖/合并。 +/// 注意事项: +/// - 本对象只存配置,不存运行时状态。 +/// 未来扩展: +/// - 可加入“索引缓存”(如按学科/稀有度过滤)。 +/// +public sealed class GameContentDatabase +{ + public Dictionary Disciplines { get; } = new(); + public Dictionary Archetypes { get; } = new(); + public Dictionary Roles { get; } = new(); + public Dictionary Traits { get; } = new(); + public Dictionary Tasks { get; } = new(); + public Dictionary Items { get; } = new(); + public Dictionary Papers { get; } = new(); + public Dictionary RoguelitePerks { get; } = new(); +} + diff --git a/scripts/Models/GameState.cs b/scripts/Models/GameState.cs new file mode 100644 index 0000000..e3e2531 --- /dev/null +++ b/scripts/Models/GameState.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; + +namespace Models; + +/// +/// 运行时全局状态(GameState) +/// 设计说明: +/// 1) 将“局内状态”集中在一个聚合对象中,便于存档与调试快照。 +/// 2) 具体子状态按职责拆分:回合/经济/人员/任务/库存/羁绊。 +/// 3) Controller/System 只依赖这份数据,不直接操作 View。 +/// 注意事项: +/// - 这是运行时数据,不是配置数据;配置请看 *Definition 类。 +/// - 禁止在这里引用 Godot Node,保持纯数据。 +/// 未来扩展: +/// - 可加入“存档版本号”和“迁移器”,支持版本升级。 +/// - 可加入“局外 Roguelite 状态”,与局内共享接口。 +/// +public sealed class GameState +{ + public TurnState Turn { get; } = new(); + public EconomyState Economy { get; } = new(); + public RosterState Roster { get; } = new(); + public TaskState Tasks { get; } = new(); + public InventoryState Inventory { get; } = new(); + public SynergyState Synergy { get; } = new(); + public RogueliteState Roguelite { get; } = new(); +} + +public enum GamePhase +{ + Planning, + Execution, + Review +} + +public sealed class TurnState +{ + public int CurrentTurn { get; set; } = 1; + public int MaxTurns { get; set; } = 30; + public GamePhase Phase { get; set; } = GamePhase.Planning; +} + +public sealed class EconomyState +{ + public int Money { get; set; } = 50000; + public int Reputation { get; set; } + public int ResearchPoints { get; set; } + public float InterestRate { get; set; } = 0.0f; +} + +public sealed class RosterState +{ + public MentorModel Mentor { get; set; } = new("Player"); + public List Students { get; } = new(); + public List Staffs { get; } = new(); +} + +public sealed class TaskState +{ + public List ActiveTasks { get; } = new(); + public List CompletedTasks { get; } = new(); + public List FailedTasks { get; } = new(); +} + +public sealed class InventoryState +{ + public Dictionary ItemCounts { get; } = new(); + public Dictionary PaperCounts { get; } = new(); +} + +public sealed class SynergyState +{ + public Dictionary ArchetypeStacks { get; } = new(); + public Dictionary RoleStacks { get; } = new(); + public List ActiveSynergyIds { get; } = new(); +} + +public sealed class RogueliteState +{ + public List AlumniCardIds { get; } = new(); + public List LegacyUnlockIds { get; } = new(); + public int TitleRetentionLevel { get; set; } +} + diff --git a/scripts/Models/ItemDefinitions.cs b/scripts/Models/ItemDefinitions.cs new file mode 100644 index 0000000..6eeb83f --- /dev/null +++ b/scripts/Models/ItemDefinitions.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; + +namespace Models; + +/// +/// 装备/设施定义(通用 + 学科专用) +/// 设计说明: +/// 1) Facility 与 Equipment 统一为 ItemDefinition,不同类别通过枚举区分。 +/// 2) 效果使用 ModifierBundle + RuleIds 组合,便于特殊规则注入。 +/// 注意事项: +/// - Placement/Slot 为 UI 与摆放系统提供约束,不做逻辑判断。 +/// 未来扩展: +/// - 可加入“耐久度/维护成本/占地大小”等字段。 +/// +public enum ItemCategory +{ + Facility, + Equipment, + Consumable +} + +public enum FacilityPlacement +{ + Lab, + ServerRoom, + Admin, + Library, + RestArea, + Field, + Global +} + +public enum EquipmentSlot +{ + Tool, + Accessory, + Body, + Head, + Hand +} + +public sealed class ItemDefinition +{ + public DefinitionHeader Header { get; set; } = new(); + public ItemCategory Category { get; set; } + public string DisciplineId { get; set; } + public FacilityPlacement Placement { get; set; } + public EquipmentSlot Slot { get; set; } + public ItemEffect Effect { get; set; } = new(); + public bool IsUnique { get; set; } + public int MaxStack { get; set; } = 1; +} + +public sealed class ItemEffect +{ + public ModifierBundle Modifiers { get; set; } = new(); + public List RuleIds { get; } = new(); +} + diff --git a/scripts/Models/MentorModel.cs b/scripts/Models/MentorModel.cs index a1bf6d5..c84b24b 100644 --- a/scripts/Models/MentorModel.cs +++ b/scripts/Models/MentorModel.cs @@ -1,70 +1,51 @@ namespace Models; /// -/// 导师数据模型 (MentorModel) -/// 玩家的数值状态。 -/// 单例模式。 +/// 导师数据模型 (MentorModel) +/// 设计说明: +/// 1) 改为组合式结构,不再使用单例,避免全局隐式依赖。 +/// 2) 导师只保留“角色自身状态”,资金/声望等全局资源归 EconomyState 管理。 +/// 3) 这样便于后续扩展多导师/多人模式或 Mod 玩法。 +/// 注意事项: +/// - 构造函数不做复杂逻辑,具体初始数值交由 GameSession 初始化。 +/// 未来扩展: +/// - 可加入“导师技能树/天赋树”组件,与肉鸽继承系统对接。 +/// - 可加入“导师光环”组件,在系统层计算对学生的影响范围。 /// -public class MentorModel : UnitModel +public sealed class MentorModel { - private static MentorModel _instance; + public enum MentorModeType + { + Worker, + Manager + } - public static MentorModel Instance - { - get - { - if (_instance == null) - { - // 如果需要默认名称,这里可以硬编码或改为 Lazy 加载 - _instance = new MentorModel("Player"); - } - return _instance; - } - } + public UnitModel Core { get; } + public MentorResources Resources { get; } + public MentorModeType Mode { get; set; } = MentorModeType.Worker; - public enum MentorModeType - { - Worker, - Manager - } + public string Name + { + get => Core.Name; + set => Core.Name = value; + } - /// - /// 精力值 (Energy) - /// 用于释放技能(画饼、PUA、甚至亲自写代码)。 - /// 每回合恢复。 - /// - public StatusValue Energy { get; set; } + public MentorModel(string name) + { + Core = new UnitModel(name); + Resources = new MentorResources(); + } +} - /// - /// 经费 (Money/Funds) - /// 单位:元。用于发工资、买设备。 - /// - public int Money { get; set; } = 50000; +/// +/// 导师资源组件(角色自身能量池) +/// +public sealed class MentorResources +{ + public StatusValue Energy { get; set; } - /// - /// 学术声望 (Reputation) - /// 影响招生质量、项目申请成功率。 - /// - public int Reputation { get; set; } = 0; - - /// - /// 算力/数据资源 (ResearchPoints) - /// 用于攻克高难度 AI 课题。 - /// - public int ResearchPoints { get; set; } = 0; - - public MentorModeType Mode { get; set; } = MentorModeType.Worker; - - // 私有构造函数,防止外部实例化 - private MentorModel(string name) : base(name) - { - // 初始化 Energy,默认上限 10000 (100.00) - Energy = new StatusValue(new PropertyValue(10000), new PropertyValue(10000), new PropertyValue(0)); - } - - // 如果需要重置单例(例如新游戏),可以添加一个重置方法 - public static void Reset(string name = "Player") - { - _instance = new MentorModel(name); - } -} \ No newline at end of file + public MentorResources() + { + Energy = new StatusValue(100, 100, 0); + } +} diff --git a/scripts/Models/Modifiers.cs b/scripts/Models/Modifiers.cs new file mode 100644 index 0000000..44485a0 --- /dev/null +++ b/scripts/Models/Modifiers.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; + +namespace Models; + +/// +/// 统一的数值修饰结构(用于学科 Buff、羁绊、装备、特质) +/// 设计说明: +/// 1) 只表达“加成/倍率/规则”,不直接改变模型数值。 +/// 2) 计算由系统层统一处理,便于叠加与回滚。 +/// 注意事项: +/// - 这里的 Multiplier 默认 1.0f,不是百分比。 +/// 未来扩展: +/// - 可加入“条件触发”与“持续时间”字段。 +/// +public sealed class AttributeModifier +{ + public AttributeType Type { get; set; } + public float Add { get; set; } + public float Multiplier { get; set; } = 1.0f; +} + +public sealed class StatusModifier +{ + public StatusType Type { get; set; } + public float Add { get; set; } + public float Multiplier { get; set; } = 1.0f; +} + +public sealed class ResourceModifier +{ + public ResourceType Type { get; set; } + public int Add { get; set; } + public float Multiplier { get; set; } = 1.0f; +} + +public sealed class ModifierBundle +{ + public List AttributeModifiers { get; } = new(); + public List StatusModifiers { get; } = new(); + public List ResourceModifiers { get; } = new(); + public List RuleIds { get; } = new(); +} + diff --git a/scripts/Models/PaperDefinitions.cs b/scripts/Models/PaperDefinitions.cs new file mode 100644 index 0000000..3b1a66a --- /dev/null +++ b/scripts/Models/PaperDefinitions.cs @@ -0,0 +1,18 @@ +namespace Models; + +/// +/// 论文卡牌定义 +/// 设计说明: +/// 1) 论文作为“消耗品资源”,用于申请纵向基金。 +/// 2) 论文等级只是一种枚举,真正的效果由系统层决定。 +/// 注意事项: +/// - 若需要“引用率/影响因子”,可在此扩展字段。 +/// 未来扩展: +/// - 可加入“论文主题标签”,影响基金/任务匹配度。 +/// +public sealed class PaperDefinition +{ + public DefinitionHeader Header { get; set; } = new(); + public PaperRank Rank { get; set; } +} + diff --git a/scripts/Models/PropertyValue.cs b/scripts/Models/PropertyValue.cs index 55b9584..b4a128b 100644 --- a/scripts/Models/PropertyValue.cs +++ b/scripts/Models/PropertyValue.cs @@ -3,120 +3,129 @@ using System; namespace Models; /// -/// 属性值类型 -/// 封装了整型数值,提供最大值限制(100)和显示转换。 +/// 属性值类型(通用数值封装) +/// 设计说明: +/// 1) 过去固定使用 0-100 的范围,导致“精力/算力”等大数值被强行截断。 +/// 2) 现在改为“每个实例自带范围”,默认仍然是 0-100,兼顾直觉和可扩展性。 +/// 3) 这个类仅承担“数值与范围”的职责,不负责业务含义,便于在 MVC 中被复用。 +/// 注意事项: +/// - 运算符会保留左操作数的范围(或右操作数的范围),避免范围丢失。 +/// - 如果需要无上限/无下限,请显式传入更大的 Max/更小的 Min。 +/// 未来扩展: +/// - 可加入“软上限/软下限”和“可见范围”等 UI 表现字段。 +/// - 可增加数值变化事件,以便 View 层更高效地刷新。 /// -public class PropertyValue +public sealed class PropertyValue { - public const int Min = 0; - public const int Max = 100; - - private float _value; + public const float DefaultMin = 0f; + public const float DefaultMax = 100f; - /// - /// 获取或设置当前值。 - /// 设置时会自动限制在 [Min, Max] 范围内。 - /// - public float Value - { - get => _value; - set - { - _value = value; - if (_value > Max) _value = Max; - if (_value < Min) _value = Min; - } - } + public float Min { get; } + public float Max { get; } - public PropertyValue(int value) - { - Value = value; - } - - public PropertyValue(float value) - { - Value = value; - } + private float _value; - public PropertyValue() : this(0) { } + public float Value + { + get => _value; + set => _value = Clamp(value, Min, Max); + } - /// - /// 获取显示值 (Value / 100.0f) - /// - public int Display() => (int)_value; + public PropertyValue(float value = 0, float min = DefaultMin, float max = DefaultMax) + { + if (max < min) + { + // 兜底修正,避免传参错误导致全局异常 + (min, max) = (max, min); + } - public override string ToString() - { - return Display().ToString(); - } + Min = min; + Max = max; + Value = value; + } - // --- 隐式转换 (实现“重载等号”效果) --- + public PropertyValue(int value, float min = DefaultMin, float max = DefaultMax) + : this((float)value, min, max) + { + } - // 允许 int 直接赋值给 PropertyValue (创建新实例) - public static implicit operator PropertyValue(int value) - { - return new PropertyValue(value); - } + public int DisplayInt() => (int)_value; - // 允许 int 直接赋值给 PropertyValue (创建新实例) - public static implicit operator PropertyValue(float value) - { - return new PropertyValue(value); - } - - // 允许 PropertyValue 像 int 一样使用 - public static implicit operator int(PropertyValue p) - { - return (int)p._value; - } + public float Normalized + { + get + { + if (Max <= Min) return 0f; + return (Value - Min) / (Max - Min); + } + } - // 允许 PropertyValue 像 float 一样使用 - public static implicit operator float(PropertyValue p) - { - return p._value; - } + public PropertyValue Clone() => new(Value, Min, Max); - // --- 运算符重载 (支持 int 计算) --- + public void Add(float delta) => Value += delta; - public static PropertyValue operator +(PropertyValue a, int b) => new(a._value + b); - public static PropertyValue operator +(int a, PropertyValue b) => new(a + b._value); - - public static PropertyValue operator -(PropertyValue a, int b) => new(a._value - b); - public static PropertyValue operator -(int a, PropertyValue b) => new(a - b._value); + public void Subtract(float delta) => Value -= delta; - public static PropertyValue operator *(PropertyValue a, int b) => new(a._value * b); - public static PropertyValue operator *(int a, PropertyValue b) => new(a * b._value); + public override string ToString() => DisplayInt().ToString(); - public static PropertyValue operator /(PropertyValue a, int b) - { - return b == 0 ? throw new DivideByZeroException() : new PropertyValue(a._value / b); - } - public static PropertyValue operator /(int a, PropertyValue b) - { - return b._value == 0 ? throw new DivideByZeroException() : new PropertyValue(a / b._value); - } + // --- 隐式转换(用于简化调用,但不改变范围语义) --- + public static implicit operator PropertyValue(int value) + { + return new PropertyValue(value); + } - // --- 运算符重载 (支持 float 计算) --- + public static implicit operator PropertyValue(float value) + { + return new PropertyValue(value); + } - public static PropertyValue operator +(PropertyValue a, float b) => new(a._value + b); - public static PropertyValue operator +(float a, PropertyValue b) => new(a + b._value); + public static implicit operator int(PropertyValue p) + { + return (int)p._value; + } - public static PropertyValue operator -(PropertyValue a, float b) => new(a._value - b); - public static PropertyValue operator -(float a, PropertyValue b) => new(a - b._value); + public static implicit operator float(PropertyValue p) + { + return p._value; + } - public static PropertyValue operator *(PropertyValue a, float b) => new(a._value * b); - public static PropertyValue operator *(float a, PropertyValue b) => new(a * b._value); + // --- 运算符重载(保留范围) --- + public static PropertyValue operator +(PropertyValue a, int b) => new(a._value + b, a.Min, a.Max); + public static PropertyValue operator +(int a, PropertyValue b) => new(a + b._value, b.Min, b.Max); + public static PropertyValue operator -(PropertyValue a, int b) => new(a._value - b, a.Min, a.Max); + public static PropertyValue operator -(int a, PropertyValue b) => new(a - b._value, b.Min, b.Max); + public static PropertyValue operator *(PropertyValue a, int b) => new(a._value * b, a.Min, a.Max); + public static PropertyValue operator *(int a, PropertyValue b) => new(a * b._value, b.Min, b.Max); + public static PropertyValue operator /(PropertyValue a, int b) + { + return b == 0 ? throw new DivideByZeroException() : new PropertyValue(a._value / b, a.Min, a.Max); + } + public static PropertyValue operator /(int a, PropertyValue b) + { + return b._value == 0 ? throw new DivideByZeroException() : new PropertyValue(a / b._value, b.Min, b.Max); + } - public static PropertyValue operator /(PropertyValue a, float b) => new(a._value / b); - public static PropertyValue operator /(float a, PropertyValue b) => new(a / b._value); - - // --- 运算符重载 (PropertyValue 之间) --- - - public static PropertyValue operator +(PropertyValue a, PropertyValue b) => new(a._value + b._value); - public static PropertyValue operator -(PropertyValue a, PropertyValue b) => new(a._value - b._value); - public static PropertyValue operator *(PropertyValue a, PropertyValue b) => new(a._value * b._value); - public static PropertyValue operator /(PropertyValue a, PropertyValue b) - { - return b._value == 0 ? throw new DivideByZeroException() : new PropertyValue(a._value / b._value); - } -} \ No newline at end of file + public static PropertyValue operator +(PropertyValue a, float b) => new(a._value + b, a.Min, a.Max); + public static PropertyValue operator +(float a, PropertyValue b) => new(a + b._value, b.Min, b.Max); + public static PropertyValue operator -(PropertyValue a, float b) => new(a._value - b, a.Min, a.Max); + public static PropertyValue operator -(float a, PropertyValue b) => new(a - b._value, b.Min, b.Max); + public static PropertyValue operator *(PropertyValue a, float b) => new(a._value * b, a.Min, a.Max); + public static PropertyValue operator *(float a, PropertyValue b) => new(a * b._value, b.Min, b.Max); + public static PropertyValue operator /(PropertyValue a, float b) => new(a._value / b, a.Min, a.Max); + public static PropertyValue operator /(float a, PropertyValue b) => new(a / b._value, b.Min, b.Max); + + public static PropertyValue operator +(PropertyValue a, PropertyValue b) => new(a._value + b._value, a.Min, a.Max); + public static PropertyValue operator -(PropertyValue a, PropertyValue b) => new(a._value - b._value, a.Min, a.Max); + public static PropertyValue operator *(PropertyValue a, PropertyValue b) => new(a._value * b._value, a.Min, a.Max); + public static PropertyValue operator /(PropertyValue a, PropertyValue b) + { + return b._value == 0 ? throw new DivideByZeroException() : new PropertyValue(a._value / b._value, a.Min, a.Max); + } + + private static float Clamp(float value, float min, float max) + { + if (value < min) return min; + if (value > max) return max; + return value; + } +} diff --git a/scripts/Models/RogueliteDefinitions.cs b/scripts/Models/RogueliteDefinitions.cs new file mode 100644 index 0000000..160257a --- /dev/null +++ b/scripts/Models/RogueliteDefinitions.cs @@ -0,0 +1,27 @@ +namespace Models; + +/// +/// 肉鸽继承/局外成长定义 +/// 设计说明: +/// 1) 对应“校友录/祖传代码/职称保留”等局外系统。 +/// 2) 这里只描述解锁项,实际解锁逻辑由系统层处理。 +/// 注意事项: +/// - 与存档绑定的解锁项需要稳定 Id。 +/// 未来扩展: +/// - 可加入“局外科技树”与“多周目难度曲线”。 +/// +public enum RoguelitePerkType +{ + AlumniCard, + LegacyProgress, + TitleRetention +} + +public sealed class RoguelitePerkDefinition +{ + public DefinitionHeader Header { get; set; } = new(); + public RoguelitePerkType Type { get; set; } + public ModifierBundle Modifiers { get; set; } = new(); + public int MaxLevel { get; set; } = 1; +} + diff --git a/scripts/Models/StaffModel.cs b/scripts/Models/StaffModel.cs new file mode 100644 index 0000000..f7e737f --- /dev/null +++ b/scripts/Models/StaffModel.cs @@ -0,0 +1,47 @@ +using System; + +namespace Models; + +/// +/// 雇员/合作人数据模型 (StaffModel) +/// 设计说明: +/// 1) 覆盖“博后/小老师”等非学生角色,强调长期合作与野心值。 +/// 2) 继续复用 UnitModel 组件,保证与学生/导师统一的数据接口。 +/// 注意事项: +/// - Staff 行为逻辑应由系统层决定(例如背刺/跳槽)。 +/// 未来扩展: +/// - 可加入“合作者关系网”和“派系归属”。 +/// +public sealed class StaffModel +{ + public enum StaffType + { + PostDoc, + JuniorFaculty + } + + public UnitModel Core { get; } + public StaffType Type { get; private set; } + public StaffMotivation Motivation { get; } + + public string Name + { + get => Core.Name; + set => Core.Name = value; + } + + public StaffModel(string name, StaffType type, Random random = null) + { + Core = new UnitModel(name, random); + Type = type; + Motivation = new StaffMotivation(); + } +} + +public sealed class StaffMotivation +{ + public StatusValue Ambition { get; set; } = new(50, 100, 0); + public StatusValue Loyalty { get; set; } = new(70, 100, 0); + public StatusValue ContractTurns { get; set; } = new(6, 12, 0); +} + diff --git a/scripts/Models/StatusValue.cs b/scripts/Models/StatusValue.cs index 4722ad7..1c0b677 100644 --- a/scripts/Models/StatusValue.cs +++ b/scripts/Models/StatusValue.cs @@ -4,98 +4,81 @@ namespace Models; /// /// 状态值 (StatusValue) -/// 维护当前状态值以及上限和下限阈值。 +/// 设计说明: +/// 1) 这是“带阈值的数值”,用于压力、体力、忠诚等需要触发事件的状态。 +/// 2) 当前值由 PropertyValue 承担“范围约束”,阈值只负责触发行为。 +/// 3) 事件由 Model 层抛出,Controller/系统层订阅,View 不直接耦合。 +/// 注意事项: +/// - Upper/Lower 默认等于当前范围的上/下限,保持语义直观。 +/// - 如果需要“软阈值”,请在系统中根据 Normalized 手动判断。 +/// 未来扩展: +/// - 可增加“阈值触发冷却/抑制器”,避免频繁触发。 +/// - 可加入“变化速率”以支持回合/时间驱动的增减。 /// -public class StatusValue +public sealed class StatusValue { - private PropertyValue _current; - private PropertyValue _upperThreshold; - private PropertyValue _lowerThreshold; + private PropertyValue _current; - /// - /// 当 Current >= UpperThreshold 时触发此事件 - /// - public event Action OnUpperThresholdReached; + public event Action OnUpperThresholdReached; + public event Action OnLowerThresholdReached; - /// - /// 当 Current <= LowerThreshold 时触发此事件 - /// - public event Action OnLowerThresholdReached; + public PropertyValue Current + { + get => _current; + set + { + _current = value; + CheckThresholds(); + } + } - /// - /// 当前状态值 - /// 修改时会自动检测阈值。 - /// - public PropertyValue Current - { - get => _current; - set - { - _current = value; - CheckThresholds(); - } - } + public float UpperThreshold { get; set; } + public float LowerThreshold { get; set; } - /// - /// 上限阈值 - /// - public PropertyValue UpperThreshold - { - get => _upperThreshold; - set - { - _upperThreshold = value; - CheckThresholds(); - } - } + public StatusValue(int current = 0, int upper = 100, int lower = 0) + { + _current = new PropertyValue(current, lower, upper); + UpperThreshold = upper; + LowerThreshold = lower; + } - /// - /// 下限阈值 - /// - public PropertyValue LowerThreshold - { - get => _lowerThreshold; - set - { - _lowerThreshold = value; - CheckThresholds(); - } - } + public StatusValue(float current, float upper, float lower) + { + _current = new PropertyValue(current, lower, upper); + UpperThreshold = upper; + LowerThreshold = lower; + } - public StatusValue(int current = 0, int upper = 100, int lower = 0) - { - _current = new PropertyValue(current); - _upperThreshold = new PropertyValue(upper); - _lowerThreshold = new PropertyValue(lower); - } - - public StatusValue(PropertyValue current, PropertyValue upper, PropertyValue lower) - { - _current = current ?? new PropertyValue(0); - _upperThreshold = upper ?? new PropertyValue(100); - _lowerThreshold = lower ?? new PropertyValue(0); - } + public StatusValue(PropertyValue current, float upperThreshold, float lowerThreshold) + { + _current = current ?? new PropertyValue(0); + UpperThreshold = upperThreshold; + LowerThreshold = lowerThreshold; + CheckThresholds(); + } - private void CheckThresholds() - { - if ((int)_current >= (int)_upperThreshold) - { - OnUpperThresholdReached?.Invoke(); - } + private void CheckThresholds() + { + if (_current.Value >= UpperThreshold) + { + OnUpperThresholdReached?.Invoke(); + } - if ((int)_current <= (int)_lowerThreshold) - { - OnLowerThresholdReached?.Invoke(); - } - } + if (_current.Value <= LowerThreshold) + { + OnLowerThresholdReached?.Invoke(); + } + } - public void Add(int value) - { - Current += value; // 触发 setter -> CheckThresholds - } - - public void Subtract(int value) - { - Current -= value; // 触发 setter -> CheckThresholds - } -} \ No newline at end of file + public void Add(float value) + { + _current.Add(value); + CheckThresholds(); + } + + public void Subtract(float value) + { + _current.Subtract(value); + CheckThresholds(); + } +} diff --git a/scripts/Models/StudentModel.cs b/scripts/Models/StudentModel.cs index d475a6e..ca749a7 100644 --- a/scripts/Models/StudentModel.cs +++ b/scripts/Models/StudentModel.cs @@ -5,59 +5,70 @@ namespace Models; /// /// 学生数据模型 (StudentModel) -/// 包含学生的数值、状态和特质。 -/// 与 Godot 的 Node (View) 分离,便于序列化和逻辑处理。 +/// 设计说明: +/// 1) 不再继承 UnitModel,而是通过 Core 组合通用组件(属性/状态/标签/装备等)。 +/// 2) 学生特有内容(年级/体力/忠诚/署名贡献)放在独立组件中,保持高内聚。 +/// 3) Model 层仅负责数据,不做 Godot 行为调用,方便后续存档与 Mod 注入。 +/// 注意事项: +/// - Traits 实际上是标签(Tag),统一走 Core.Tags.TraitIds,避免双份真相。 +/// - Grade 仅表示“学年进度”,不是学术能力值。 +/// 未来扩展: +/// - 可新增“毕业/退学事件队列”,由系统层驱动。 +/// - 可新增“论文署名历史”用于复盘与羁绊叠层回溯。 /// -public class StudentModel: UnitModel +public sealed class StudentModel { - public enum StudentType - { - MasterCandidate, - DoctorCandidate - } - - // --- 静态属性 (Property) --- - /// - /// 学生类型 - /// - public StudentType Type { get; private set; } - - // --- 动态状态 (Status) --- - - /// - /// 体力 (Stamina) - /// 范围 0-100。工作消耗体力,休息恢复。体力过低效率下降。 - /// - public StatusValue Stamina { get; set; } - - /// - /// 忠诚度 (Loyalty) - /// 范围 0-100。过低可能跳槽或举报。 - /// - public StatusValue Loyalty { get; set; } - - /// - /// 年级 - /// - public StatusValue Grade { get; set; } - - /// - /// 记录对每个 Task 的贡献量,用于署名分配 - /// - public Dictionary Contributions { get; set; } + public enum StudentType + { + MasterCandidate, + DoctorCandidate + } - /// - /// 特质列表 (如 "DDL战士", "卷王") - /// - public List Traits { get; set; } = []; + public UnitModel Core { get; } + public StudentType Type { get; private set; } + public StudentProgress Progress { get; } + public StudentContributions Contributions { get; } - public StudentModel(string name) : base(name) - { - var random = new Random(); - Stamina = new StatusValue(random.Next(100),100, 0); - Loyalty = new StatusValue(80, 100, 0); - Type = random.Next(2) == 0 ? StudentType.MasterCandidate : StudentType.DoctorCandidate; - Grade = new StatusValue(0, Type == StudentType.DoctorCandidate ? 6 : 3); - } + public string Name + { + get => Core.Name; + set => Core.Name = value; + } + + public List Traits => Core.Tags.TraitIds; + + public StudentModel(string name, Random random = null) + { + var rng = random ?? Random.Shared; + Core = new UnitModel(name, rng); + Type = rng.Next(2) == 0 ? StudentType.MasterCandidate : StudentType.DoctorCandidate; + Progress = new StudentProgress(Type); + Contributions = new StudentContributions(); + } } +/// +/// 学生进度组件(只关心学生生命周期) +/// +public sealed class StudentProgress +{ + public StatusValue Stamina { get; set; } + public StatusValue Loyalty { get; set; } + public PropertyValue Grade { get; set; } + + public StudentProgress(StudentModel.StudentType type) + { + Stamina = new StatusValue(100, 100, 0); + Loyalty = new StatusValue(80, 100, 0); + var maxGrade = type == StudentModel.StudentType.DoctorCandidate ? 6 : 3; + Grade = new PropertyValue(0, 0, maxGrade); + } +} + +/// +/// 署名贡献记录(任务 -> 贡献值) +/// +public sealed class StudentContributions +{ + public Dictionary ByTask { get; } = new(); +} diff --git a/scripts/Models/SynergyDefinitions.cs b/scripts/Models/SynergyDefinitions.cs new file mode 100644 index 0000000..0d475e2 --- /dev/null +++ b/scripts/Models/SynergyDefinitions.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; + +namespace Models; + +/// +/// 羁绊/职业/特质定义(“人群画像 + 职能分工”) +/// 设计说明: +/// 1) Archetype/Role 采用分层 Tier 模式,满足 2/4/6 叠层设计。 +/// 2) Trait 为单体特质,不叠层,仅提供修饰与规则。 +/// 注意事项: +/// - Tier 的规则效果写在 RuleIds 中,由系统层解释执行。 +/// 未来扩展: +/// - 可加入“跨学科混合羁绊”条件与视觉特效引用。 +/// +public sealed class ArchetypeDefinition +{ + public DefinitionHeader Header { get; set; } = new(); + public List Tiers { get; } = new(); +} + +public sealed class RoleDefinition +{ + public DefinitionHeader Header { get; set; } = new(); + public List Tiers { get; } = new(); + + // 如果是学科限定角色(如炼金术士/极客),在这里配置允许的学科 Id。 + public List AllowedDisciplineIds { get; } = new(); +} + +public sealed class TraitDefinition +{ + public DefinitionHeader Header { get; set; } = new(); + public ModifierBundle Modifiers { get; set; } = new(); + public List RuleIds { get; } = new(); +} + +public sealed class SynergyTier +{ + public int RequiredCount { get; set; } + public ModifierBundle Modifiers { get; set; } = new(); + public List RuleIds { get; } = new(); +} + diff --git a/scripts/Models/Task.cs b/scripts/Models/Task.cs index c11e96b..b779315 100644 --- a/scripts/Models/Task.cs +++ b/scripts/Models/Task.cs @@ -4,94 +4,76 @@ using System.Collections.Generic; namespace Models; /// -/// 任务类型枚举 +/// 任务运行时模型 (TaskModel) +/// 设计说明: +/// 1) TaskDefinition 描述“配置”,TaskModel 描述“运行时进度”。二者分离利于 Mod 追加内容。 +/// 2) 任务状态只保存必要字段,展示文本统一由 Definition + i18n 输出。 +/// 3) 任务参与单位只记录 Id,避免模型直接引用 Node。 +/// 注意事项: +/// - 如果没有 DefinitionId,可使用 Name 作为临时演示数据。 +/// - Deadline 语义是“剩余回合数”,由 TurnSystem 递减。 +/// 未来扩展: +/// - 可加入阶段状态(灵感/写作/实验/审核),与 UI 进度条分段对应。 +/// - 可加入失败原因/完成评级,用于结算与肉鸽评分。 /// -public enum TaskType -{ - Paper, // 论文:重学术(Intelligence) + 表达(Expression) - Project, // 项目:重工程(Strength) + 学术(Intelligence) - Admin // 杂务:消耗体力,低收益,但必须做 +public sealed class TaskModel +{ + public Guid Id { get; } = Guid.NewGuid(); + public string DefinitionId { get; set; } + public string Name { get; set; } + public TaskTrack Track { get; set; } + public TaskKind Kind { get; set; } + public TaskDifficulty Difficulty { get; set; } + + public TaskRuntime Runtime { get; } + public TaskRewardSnapshot Reward { get; set; } = new(); + public List AssignedUnitIds { get; } = new(); + + public bool IsCompleted => Runtime.CurrentProgress >= Runtime.TotalWorkload; + public bool IsFailed => Runtime.RemainingTurns <= 0 && !IsCompleted; + + public TaskModel(string name, TaskKind kind, float workload, int deadline) + { + Name = name; + Kind = kind; + Track = TaskTrack.LabProject; + Difficulty = TaskDifficulty.Standard; + Runtime = new TaskRuntime(workload, deadline); + } + + public void AddProgress(float amount) + { + Runtime.CurrentProgress += amount; + if (Runtime.CurrentProgress > Runtime.TotalWorkload) + { + Runtime.CurrentProgress = Runtime.TotalWorkload; + } + } } /// -/// 任务 (Task) -/// 核心游戏对象之一,相当于地图上的“怪物”。 -/// 学生需要“攻击”任务(消耗工作量)来完成它。 +/// 任务运行参数(进度与截止) /// -public class Task +public sealed class TaskRuntime { - public Guid Id { get; private set; } = Guid.NewGuid(); - - /// - /// 任务名称 - /// - public string Name { get; set; } - - /// - /// 任务类型 - /// - public TaskType Type { get; set; } - - /// - /// 总工作量 (HP) - /// - public float TotalWorkload { get; set; } - - /// - /// 当前进度 - /// - public float CurrentProgress { get; set; } - - /// - /// 难度系数 (Defense) - /// 影响学生攻克该任务的效率。若学生能力低于难度,效率会大幅下降。 - /// - public float Difficulty { get; set; } - - /// - /// 截止日期 (剩余回合数) - /// 归零时如果未完成,触发失败惩罚。 - /// - public int Deadline { get; set; } + public float TotalWorkload { get; set; } + public float CurrentProgress { get; set; } + public float DifficultyScale { get; set; } = 1.0f; + public int RemainingTurns { get; set; } - /// - /// 奖励:经费 - /// - public int RewardMoney { get; set; } - - /// - /// 奖励:声望 - /// - public int RewardReputation { get; set; } - - /// - /// 任务是否已完成 - /// - public bool IsCompleted => CurrentProgress >= TotalWorkload; - - /// - /// 任务是否已失败(超时未完成) - /// - public bool IsFailed => Deadline <= 0 && !IsCompleted; - - public Task(string name, TaskType type, float workload, int deadline) - { - Name = name; - Type = type; - TotalWorkload = workload; - Deadline = deadline; - CurrentProgress = 0; - Difficulty = 1.0f; - } - - /// - /// 增加进度 - /// - /// 工作量数值 - public void AddProgress(float amount) - { - CurrentProgress += amount; - if (CurrentProgress > TotalWorkload) CurrentProgress = TotalWorkload; - } + public TaskRuntime(float totalWorkload, int remainingTurns) + { + TotalWorkload = totalWorkload; + RemainingTurns = remainingTurns; + } } +/// +/// 运行时奖励快照(避免 Definition 被动态修改) +/// +public sealed class TaskRewardSnapshot +{ + public int Money { get; set; } + public int Reputation { get; set; } + public List PaperIds { get; } = new(); +} diff --git a/scripts/Models/TaskDefinitions.cs b/scripts/Models/TaskDefinitions.cs new file mode 100644 index 0000000..9105193 --- /dev/null +++ b/scripts/Models/TaskDefinitions.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; + +namespace Models; + +/// +/// 任务定义与枚举(配置层) +/// 设计说明: +/// 1) Definition 描述“任务是什么”,TaskModel 描述“任务正在发生什么”。 +/// 2) 任务类别与难度与设计文档一致,方便 UI/系统层做分流。 +/// 3) Requirements/Rewards 以组合结构表示,便于 Mod 追加新条件。 +/// 注意事项: +/// - 不在此处写具体数值表,仅提供结构骨架。 +/// - 业务系统应优先读取 Definition,而不是硬编码逻辑。 +/// 未来扩展: +/// - 可加入“阶段式任务”与“事件触发任务”字段。 +/// - 可加入“动态难度曲线”,用于 Roguelite 调整强度。 +/// +public enum TaskTrack +{ + Tenure, // 生涯考核 + LabProject // 实验室项目 +} + +public enum TaskKind +{ + AcademicExploration, // 学术探索:产出论文 + GrantVertical, // 纵向基金:强制门票 + GrantHorizontal, // 横向项目:搞钱 + Administrative, // 行政事务:干扰项 + Milestone, // 目标型考核 + Conference // 会议大招 +} + +public enum TaskDifficulty +{ + Water, + Standard, + Hardcore, + BlackBox +} + +public enum PaperRank +{ + C, + B, + A, + S, + SPlus +} + +public sealed class TaskDefinition +{ + public DefinitionHeader Header { get; set; } = new(); + public TaskTrack Track { get; set; } + public TaskKind Kind { get; set; } + public TaskDifficulty Difficulty { get; set; } + public TaskRuntimeConfig Runtime { get; set; } = new(); + public TaskRequirements Requirements { get; set; } = new(); + public TaskRewards Rewards { get; set; } = new(); + + public List AllowedDisciplineIds { get; } = new(); + public List RecommendedRoleIds { get; } = new(); + public List RecommendedArchetypeIds { get; } = new(); +} + +public sealed class TaskRuntimeConfig +{ + public float Workload { get; set; } + public int DeadlineTurns { get; set; } + public float DifficultyScale { get; set; } = 1.0f; + public int MaxAssignedSlots { get; set; } = 3; +} + +public sealed class TaskRequirements +{ + public List PaperTickets { get; } = new(); + public List AttributeChecks { get; } = new(); + public List RequiredRoleIds { get; } = new(); +} + +public sealed class PaperRequirement +{ + public PaperRank Rank { get; set; } + public int Count { get; set; } +} + +public sealed class AttributeRequirement +{ + public AttributeType Type { get; set; } + public int MinValue { get; set; } +} + +public sealed class TaskRewards +{ + public int Money { get; set; } + public int Reputation { get; set; } + public List PaperIds { get; } = new(); + public List ItemIds { get; } = new(); + public List BuffIds { get; } = new(); +} + diff --git a/scripts/Models/UnitComponents.cs b/scripts/Models/UnitComponents.cs new file mode 100644 index 0000000..5c17418 --- /dev/null +++ b/scripts/Models/UnitComponents.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; + +namespace Models; + +/// +/// Unit 组件集合(组合优于继承) +/// 设计说明: +/// 1) UnitModel 只是“组件容器”,具体能力拆分到小组件中,便于复用与替换。 +/// 2) 组件保持低内聚小职责,避免“巨型基类”造成的隐式耦合。 +/// 3) 这些组件只描述数据,不包含具体行为,行为交给系统/控制器层。 +/// 注意事项: +/// - 不引用 Godot 类型,保持纯数据层,利于测试与序列化。 +/// - 标识/标签使用 string Id,方便 Mod 扩展新增内容。 +/// 未来扩展: +/// - 可以给组件增加“快照/脏标记”,用于高效 UI 刷新。 +/// - 需要网络联机时,可在这里加入序列化版本号。 +/// +public static class UnitComponents +{ + public readonly struct GridPosition + { + public int X { get; } + public int Y { get; } + + public GridPosition(int x, int y) + { + X = x; + Y = y; + } + + public static GridPosition Zero => new(0, 0); + } + + public sealed class UnitIdentity + { + public Guid Id { get; } = Guid.NewGuid(); + public string Name { get; set; } + public string NameKey { get; set; } + + public UnitIdentity(string name, string nameKey = null) + { + Name = name; + NameKey = nameKey; + } + } + + public sealed class UnitAttributes + { + public PropertyValue Academic { get; set; } + public PropertyValue Engineering { get; set; } + public PropertyValue Writing { get; set; } + public PropertyValue Financial { get; set; } + public PropertyValue Social { get; set; } + public PropertyValue Activation { get; set; } + + public UnitAttributes(Random random = null) + { + var rng = random ?? Random.Shared; + Academic = new PropertyValue(rng.Next(0, 100)); + Engineering = new PropertyValue(rng.Next(0, 100)); + Writing = new PropertyValue(rng.Next(0, 100)); + Financial = new PropertyValue(rng.Next(0, 100)); + Social = new PropertyValue(rng.Next(0, 100)); + Activation = new PropertyValue(rng.Next(0, 100)); + } + } + + public sealed class UnitStatuses + { + public StatusValue Stress { get; set; } + public StatusValue Sanity { get; set; } + public PropertyValue Mood { get; set; } + public PropertyValue MoveSpeed { get; set; } + + public UnitStatuses() + { + Stress = new StatusValue(0, 100, 0); + Sanity = new StatusValue(100, 100, 0); + Mood = new PropertyValue(50); + MoveSpeed = new PropertyValue(50, 0, 200); + } + } + + public sealed class UnitTags + { + public string DisciplineId { get; set; } + public List ArchetypeIds { get; } = new(); + public List RoleIds { get; } = new(); + public List TraitIds { get; } = new(); + + public bool HasTrait(string traitId) => TraitIds.Contains(traitId); + } + + public sealed class UnitAssignment + { + public Guid? CurrentTaskId { get; set; } + } + + public sealed class UnitPlacement + { + public GridPosition Current { get; set; } = GridPosition.Zero; + public GridPosition Target { get; set; } = GridPosition.Zero; + } + + public sealed class UnitEquipment + { + public List EquippedItemIds { get; } = new(); + } +} diff --git a/scripts/Models/UnitModel.cs b/scripts/Models/UnitModel.cs index 4e7c711..4ababc1 100644 --- a/scripts/Models/UnitModel.cs +++ b/scripts/Models/UnitModel.cs @@ -1,78 +1,44 @@ -using Godot; using System; namespace Models; /// -/// 所有角色的基类,无论是导师、学生还是博后。 +/// UnitModel(组合式数据容器) +/// 设计说明: +/// 1) 该类不再是“基类”,而是用于聚合组件的纯数据对象。 +/// 2) 这样 Student/Mentor/Staff 可以按需组合组件,避免继承链的耦合。 +/// 3) 任何业务逻辑应放在 System/Controller 层,Model 保持可序列化、可测试。 +/// 注意事项: +/// - 不引用 Godot 类型,保证与视图层隔离。 +/// - 通过 Name 属性提供便捷访问,便于旧代码过渡。 +/// 未来扩展: +/// - 可以增加“能力衰减/成长曲线”等组件,而不影响现有对象结构。 +/// - 可引入“只读快照”用于回放/结算等需求。 /// -public class UnitModel +public sealed class UnitModel { - public Guid Id { get; private set; } = Guid.NewGuid(); - public string Name { get; set; } - - // 基础属性 - - /// - /// 学术能力,影响科研进度 - /// - public PropertyValue Academic {get; set;} - /// - /// 工程能力,影响实验进度 - /// - public PropertyValue Engineering {get; set;} - /// - /// 写作能力,影响论文发表 - /// - public PropertyValue Writing {get; set;} - /// - /// 财务能力,影响资金相关任务 - /// - public PropertyValue Financial {get; set;} - /// - /// 社交能力 - /// - public PropertyValue Social {get; set;} - /// - /// 行动力,影响各种行为的效率和能动性 - /// - public PropertyValue Activation {get; set;} - - // 状态属性 + public UnitComponents.UnitIdentity Identity { get; } + public UnitComponents.UnitAttributes Attributes { get; } + public UnitComponents.UnitStatuses Statuses { get; } + public UnitComponents.UnitTags Tags { get; } + public UnitComponents.UnitAssignment Assignment { get; } + public UnitComponents.UnitPlacement Placement { get; } + public UnitComponents.UnitEquipment Equipment { get; } - /// - /// 压力值 - /// - public StatusValue Stress {get; set;} - /// - /// 精神值 (Sanity) / 抗压能力 - /// 范围 0-100。过低会导致崩溃(Breakdown)。 - /// - public StatusValue Sanity { get; set; } - /// - /// 情绪值,影响工作效率 - /// - public PropertyValue Mood {get; set;} - /// - /// 移动速度 - /// - public float MoveSpeed {get; set;} - - // 局内属性 - - public Vector2I TargetPosition {get; set;} - public Vector2I CurrentPosition {get; set;} - public Guid TargetTaskId {get; set;} + public string Name + { + get => Identity.Name; + set => Identity.Name = value; + } - protected UnitModel(string name) - { - Name = name; - var random = new Random(); - Academic = random.Next(PropertyValue.Min, PropertyValue.Max); - Engineering = random.Next(PropertyValue.Min, PropertyValue.Max); - Writing = random.Next(PropertyValue.Min, PropertyValue.Max); - Financial = random.Next(PropertyValue.Min, PropertyValue.Max); - Social = random.Next(PropertyValue.Min, PropertyValue.Max); - Activation = random.Next(PropertyValue.Min, PropertyValue.Max); - } -} \ No newline at end of file + public UnitModel(string name, Random random = null) + { + Identity = new UnitComponents.UnitIdentity(name); + Attributes = new UnitComponents.UnitAttributes(random); + Statuses = new UnitComponents.UnitStatuses(); + Tags = new UnitComponents.UnitTags(); + Assignment = new UnitComponents.UnitAssignment(); + Placement = new UnitComponents.UnitPlacement(); + Equipment = new UnitComponents.UnitEquipment(); + } +}