Compare commits

..

4 Commits

93 changed files with 5940 additions and 193 deletions

View File

@ -171,5 +171,3 @@
* **策略性:** 不只是堆数值,而是在“做学术”和“搞政治”之间做平衡。 * **策略性:** 不只是堆数值,而是在“做学术”和“搞政治”之间做平衡。
* **情感反馈:** 从初期的无助,到中期的纠结,再到后期的“屠龙少年终成恶龙”或“桃李满天下”的感动。 * **情感反馈:** 从初期的无助,到中期的纠结,再到后期的“屠龙少年终成恶龙”或“桃李满天下”的感动。
===

View File

@ -0,0 +1,183 @@
# 校园行为系统文档
本文档描述校园行为系统相关代码与配置,
覆盖 `scripts/Campus`、`scripts/Core`、`scripts/Models`
以及 `resources/definitions` 下的所有相关文件。
## 1. 系统概览
- 行为系统基于“优先级决策 + 有限状态机”。
- 决策优先级Critical(危机) -> AssignedTask(指令任务)
-> Needs(需求) -> Trait(特质偏好) -> Idle(闲置)。
- 行为数值由 `campus_behavior.json` 驱动,
动作效果按“每秒”变化。
- 每轮开始会预生成整轮行动计划,
计划包含多条动作与浮动时长。
- 所有行为通过 `GD.Print` 输出到控制台。
## 2. 代码文件说明
### 2.1 `scripts/Campus`
- `scripts/Campus/CampusBehaviorConfig.cs`
- 定义行为系统枚举与配置结构。
- 提供 `CampusBehaviorConfig` JSON 加载。
- 提供 `CampusLocationRegistry``CampusBehaviorWorld`
- 定义 `CampusTask``CampusAgentNeeds`
- `scripts/Campus/CampusBehaviorAgent.cs`
- 定义 `CampusAgentRuntime`、`CampusBehaviorIntent`、`CampusBehaviorContext`。
- 实现 Planner 与多级 Provider。
- 实现状态机Decision/Move/Action。
- `CampusBehaviorAgent` 负责每帧驱动与打断。
- `CampusTraitIds` 提供行为特质 Id 常量。
### 2.2 `scripts/Core`
- `scripts/Core/ContentCollectionResource.cs`
- 资源集合容器,可把多个定义资源打包到 `.tres`
- `scripts/Core/ContentRegistry.cs`
- 内容加载与合并入口,支持 Resource/JSON 源。
- `scripts/Core/ContentResources.cs`
- 内容资源接口:`IContentResource`/`IContentResourceCollection`。
- `scripts/Core/DisciplineDefinitionResource.cs`
- 学科定义 `.tres``DisciplineDefinition` 的映射。
- `scripts/Core/DomainEvents.cs`
- 领域事件:任务完成/失败、回合结束。
- `scripts/Core/EventBus.cs`
- 轻量事件总线 `DomainEventBus`
- `scripts/Core/GameController.cs`
- 回合阶段切换控制器。
- `scripts/Core/GameSession.cs`
- 会话入口,聚合 State/Content/Systems/Events/Localization。
- `CreateDefault()` 负责加载 `.tres` 与 JSON。
- `scripts/Core/GameSystems.cs`
- 系统集合Turn/Task/Economy/Synergy/Assignment。
- `scripts/Core/LocalizationService.cs`
- 多语言接口与 Godot 实现。
- `scripts/Core/ModManifest.cs`
- Mod 清单与包信息结构。
- `scripts/Core/Mvc.cs`
- MVC 接口与 `ModelView<TModel>` 基类。
- `scripts/Core/StatResolver.cs`
- 数值解析器(羁绊/学科/特质/装备叠加)。
### 2.3 `scripts/Models`
- `scripts/Models/CoreIds.cs`
- 核心内容 Id 常量(学科/羁绊/角色等)。
- `scripts/Models/DefinitionSupport.cs`
- 通用定义结构:`LocalizedText` 与 `DefinitionHeader`
- `scripts/Models/DisciplineDefinitions.cs`
- 学科定义 `DisciplineDefinition``DisciplineBuff`
- `scripts/Models/DomainEnums.cs`
- 统一枚举:`AttributeType`、`ResourceType`、`StatusType`。
- `scripts/Models/GameContentDatabase.cs`
- 定义数据汇总库(学科/羁绊/任务/装备等)。
- `scripts/Models/GameState.cs`
- 全局运行时状态:回合/经济/人员/任务/库存/羁绊/肉鸽。
- `scripts/Models/ItemDefinitions.cs`
- 装备/设施/消耗品定义与分类枚举。
- `scripts/Models/MentorModel.cs`
- 导师模型与资源组件(能量)。
- `scripts/Models/Modifiers.cs`
- 通用数值修饰结构(属性/状态/资源 + RuleIds
- `scripts/Models/PaperDefinitions.cs`
- 论文卡牌定义(等级与元数据)。
- `scripts/Models/PropertyValue.cs`
- 通用数值类型,带范围与运算符重载。
- `scripts/Models/RogueliteDefinitions.cs`
- 肉鸽继承定义(校友录/传承/职称保留)。
- `scripts/Models/StaffModel.cs`
- 雇员/合作者模型(博后/小老师)与动机组件。
- `scripts/Models/StatusValue.cs`
- 带阈值的状态值(压力/理智/忠诚等)。
- `scripts/Models/StudentModel.cs`
- 学生模型(类型、进度、贡献记录)。
- `scripts/Models/SynergyDefinitions.cs`
- 羁绊/职业/特质定义与叠层结构 `SynergyTier`
- `scripts/Models/Task.cs`
- 运行时任务模型 `TaskModel` 与进度数据结构。
- `scripts/Models/TaskDefinitions.cs`
- 任务定义与枚举(类型/难度/奖励/需求)。
- `scripts/Models/UnitComponents.cs`
- Unit 组件结构(身份/属性/状态/标签/分配/位置/装备)。
- `scripts/Models/UnitModel.cs`
- Unit 组合容器,聚合上述组件。
## 3. 配置文件说明(`resources/definitions`
### 3.1 `resources/definitions/campus_behavior.json`
行为系统核心数值配置,动作数值按秒生效。
全局阈值与衰减:
- `CriticalSanityThreshold`:理智过低触发人工湖。
- `CriticalStaminaThreshold`:体力过低触发强制休息。
- `CriticalStressThreshold`:压力过高触发人工湖。
- `HungerThreshold`:饥饿过低触发食堂。
- `EnergyThreshold`:精力过低触发睡觉/咖啡。
- `SocialThreshold`:社交过低触发社交行为。
- `LowMoodThreshold`:心情过低触发宿舍休息。
- `HungerDecayPerSecond`:基础饥饿衰减。
- `EnergyDecayPerSecond`:基础精力衰减。
- `StaminaDecayPerSecond`:基础体力衰减。
- `StressGrowthPerSecond`:基础压力增长。
- `SocialDecayPerSecond`:基础社交衰减。
- `DecisionIntervalSeconds`:打断/重评估间隔。
- `ActionDurationVariance`:动作时长浮动比例(±百分比)。
- `MinPlannedActionSeconds`:单次规划动作的最短时长。
ActionConfigs
- `ActionId`:动作枚举名。
- `LocationId`:地点枚举名。
- `DurationSeconds`:动作持续时间。
- `HungerDelta`/`EnergyDelta`/`StaminaDelta`。
- `StressDelta`/`MoodDelta`/`SocialDelta`。
- `SanityDelta`/`HealthDelta`。
### 3.2 `resources/definitions/archetypes.json`
- 数组形式的 `ArchetypeDefinition`
- `Header``Id`、`Name`、`Description`、`Tags`。
- `Tiers`:叠层配置列表。
- `RequiredCount`:触发层数。
- `Modifiers`:数值修饰与 `RuleIds`
### 3.3 `resources/definitions/roles.json`
- 数组形式的 `RoleDefinition`
- `Header`、`Tiers` 与 archetypes 一致。
- `AllowedDisciplineIds`:学科限制列表。
### 3.4 `resources/definitions/traits.json`
- 数组形式的 `TraitDefinition`
- `Header`Id 与名称说明。
- `Modifiers`:数值修饰与 `RuleIds`
### 3.5 `resources/definitions/disciplines.json`
- 数组形式的 `DisciplineDefinition`
- `Header`:学科 Id、名称、描述、Tags。
- `Buff`学科被动Name/Description/Modifiers
- `RolePoolIds`:角色池 Id 列表。
- `ItemPoolIds`:装备池 Id 列表。
- `TaskKeywordIds`:任务关键词 Id 列表。
### 3.6 `resources/definitions/discipline_biology.tres`
- Godot 资源版学科定义(`DisciplineDefinitionResource`)。
- 主要字段:
- `Id`/`NameKey`/`NameFallback`/`DescriptionKey`/`DescriptionFallback`。
- `IconPath`/`Tags`。
- `BuffNameKey`/`BuffNameFallback`。
- `BuffDescriptionKey`/`BuffDescriptionFallback`/`BuffRuleIds`。
- `RolePoolIds`/`ItemPoolIds`/`TaskKeywordIds`。
## 4. 规则扩展建议
- 新增地点:扩展 `CampusLocationId`,并添加 `Locations` 标记。
- 新增动作:扩展 `CampusActionId`,并添加 ActionConfig。
- 新增 AI 规则:新增 Provider 并加入 `BuildProviders()`

View File

@ -0,0 +1,132 @@
# 校园角色行为规则文档
**版本:** 1.0
**依据:** 基于 `design.md`, `任务与经济系统.md`, `学科与流派系统.md`, `装备与设施系统.md`, `角色与羁绊系统.md` 整合生成。
---
## 1. 行为逻辑总述 (General Behavior Logic)
在游戏的“自走阶段”30秒自由行动角色学生/导师/职工)并非仅仅是执行任务的机器,而是拥有**生理需求**、**心理状态**和**个人性格**的自主智能体。
角色的行为决策遵循以下**优先级队列**
1. **生存危机 (Critical State):** 压力爆表San值过低或 体力耗尽 $\rightarrow$ 强制中断当前行为,前往特定场所恢复。
2. **导师指令 (Assigned Task):** 玩家手动拖拽或分配的任务 $\rightarrow$ 前往对应的工作场所(实验室/图书馆/机房)。
3. **生理/心理需求 (Needs):** 饥饿、轻度疲劳、需要社交 $\rightarrow$ 前往食堂、寝室或咖啡店。
4. **特质驱动 (Trait-Driven):** 基于“卷王”、“摸鱼党”等标签的自发行为 $\rightarrow$ 加班或闲逛。
5. **闲置/游荡 (Idle):** 无事可做时在校园内随机移动或触发彩蛋交互。
---
## 2. 场所行为详解 (Location Behaviors)
校园分为8个核心区域每个区域对应特定的行为模式和属性变化。
### 2.1 实验室 (Laboratory)
* **功能定义:** 科研生产的核心区域,进行“学术探索”任务。
* **进入条件:**
* 被分配了【实验类】任务(如“产出论文草稿”)。
* 具有【卷王】特质的角色在闲置时会自动进入。
* 具有【炼金术士】、【实验狗】标签的角色偏好此地。
* **行为状态:**
* **做实验 (Experimenting):** 消耗体力,增加压力,产出任务进度。
* **仪器操作 (Operating):** 只有装备了特定道具(如移液枪)或特定职业角色才会触发的高效动作。
* **特殊事件:**
* *爆炸:* 化学/生化类角色有概率触发,导致周围角色停止动作并扣血。
* *甚至不是人AI成精* 不进入实验室,而是通过网络远程占用设备。
### 2.2 图书馆 (Library)
* **功能定义:** 知识获取与文本生产区域。
* **进入条件:**
* 被分配了【写作类】任务(如“论文润色”、“文献综述”)。
* 具有【笔杆子】、【思想者】、【写手】标签的角色偏好此地。
* 角色处于“查阅资料”状态(任务进度卡住时触发)。
* **行为状态:**
* **静默写作 (Silent Writing):** 持续产出,低噪音。
* **寻找灵感 (Seeking Inspiration):** 在书架间移动,不产出进度,但增加下一次产出的暴击率。
### 2.3 食堂 (Canteen)
* **功能定义:** 生理补给站。
* **进入条件:**
* 角色【饥饿值】低于阈值。
* 【大胃王】特质的角色会频繁进入。
* 午餐/晚餐时间(游戏内特定时间段)引发群体移动。
* **行为状态:**
* **进食 (Eating):** 消耗金钱(玩家资金),快速恢复体力。
* **吐槽 (Gossiping):** 进食时与邻座角色交互随机恢复心情或传播“摸鱼”Buff。
### 2.4 寝室 (Dormitory)
* **功能定义:** 休息与娱乐San值避风港。
* **进入条件:**
* 角色【体力】极低或【心情】较差。
* 具有【摸鱼党】特质的角色在工作间隙溜入。
* 【极客】职业的角色可能在寝室进行“远程办公”(如果有相关羁绊)。
* **行为状态:**
* **睡眠 (Sleeping):** 大幅恢复体力,时间较长。
* **打游戏/刷剧 (Gaming/Chilling):** 恢复心情,恢复少量体力。
* **甚至不回寝室:** 【卷王】(6层羁绊) 会无视寝室需求,直接在工位打地铺。
### 2.5 人工湖 (Artificial Lake)
* **功能定义:** 心理急救中心。
* **进入条件:**
* 角色处于**【崩溃 (Mental Breakdown)】**状态(压力满)。
* 角色处于**【顿悟 (Epiphany)】**前摇状态(常见于【思想者】职业)。
* **行为状态:**
* **发呆/看鸭子 (Staring):** 角色停止一切响应,缓慢降低压力值。若被打断,压力值回弹。
* **跳河 (Suicide):** 极低概率触发,因为长期处于崩溃状态导致角色退出。
### 2.6 咖啡店 (Coffee Shop)
* **功能定义:** 兴奋剂补给站Buff获取点。
* **进入条件:**
* 角色【精力】下降但仍有任务在身。
* 具有【咖啡因依赖】特质的角色必须定期进入否则通过Debuff降低效率。
* 【富家子弟】偏好的休息场所。
* **行为状态:**
* **购买饮料 (Buying):** 消耗金钱获得短时“攻速提升”Buff。
* **商务洽谈 (Business Talk):** 【大忽悠】或【经济学】角色在此可能触发“获得额外经费”的小事件。
### 2.7 行政楼 (Administration Building)
* **功能定义:** 财务报销与行政手续处理。
* **进入条件:**
* 任务阶段涉及到“报销”、“审批”环节。
* 触发【事务型任务】(如“年度报表”、“安全检查”)。
* 【管家】、【调研员】常驻区域。
* **行为状态:**
* **排队 (Queuing):** 极度消耗心情,无产出。
* **扯皮 (Arguing):** 【刺头】或【法学】角色可能触发,减少排队时间或免除惩罚。
* **盖章 (Stamping):** 任务完成的必要步骤。
### 2.8 足球场 (Football Field)
* **功能定义:** 锻炼身体,发泄精力。
* **进入条件:**
* 角色【健康值】较低,需要锻炼。
* 【环境科学】、【调研员】等高体力需求职业的训练场。
* 没有任务且不想回寝室时。
* **行为状态:**
* **跑步 (Running):** 恢复健康值,微量降低压力。
* **甚至在踢球:** 如果凑齐足够多的【摸鱼党】,可能会组织一场球赛,吸引周围人围观(降低周围人工作效率)。
---
## 3. 角色特质对行为的修正 (Trait Modifiers)
角色的特质Traits和羁绊Synergies会覆盖或修正上述默认行为规则。
| 特质/羁绊 | 修正规则 |
| :--- | :--- |
| **【卷王】 (The Grinder)** | **拒绝休息:** 当体力/心情低于阈值时,仍有 50% 概率拒绝前往寝室/湖边,而是去咖啡店买咖啡强撑。 |
| **【摸鱼党】 (The Slacker)** | **路径偏移:** 在前往实验室/图书馆的路上,有 30% 概率“迷路”进食堂或寝室逗留 5 秒。 |
| **【社恐】 (Social Phobia)** | **避开人群:** 动态检测人数。如果目标区域(如食堂)人数 > 3会转身去买自动贩卖机或饿着或者去无人的角落如人工湖死角。 |
| **【社牛/大忽悠】** | **聚集效应:** 倾向于前往人数最多的区域,且进入后会引发周围角色短暂停顿(听他说话)。 |
| **【洁癖/强迫症】** | **环境敏感:** 如果实验室发生过“爆炸”或有人呕吐,会拒绝进入该区域直到被清理。 |
| **【甚至不是人】** | **幽灵行为:** 不需要进食(不去食堂),不需要睡觉(不去寝室),只消耗电费(常驻机房/实验室)。 |
---
## 4. 寻路与交互逻辑 (Pathfinding & Interaction)
* **碰撞体积:** 角色之间存在碰撞体积,狭窄通道(如行政楼走廊)容易发生拥堵,增加焦躁感(压力上升)。
* **交互气泡:**
* 角色头顶会显示当前意图的气泡图标(如:🍔 = 去食堂,🧪 = 去实验室,💤 = 去寝室)。
* 当两个角色相遇时若羁绊相关如两个【二次元】会弹出“握手”或“爱心”表情并短暂交换Buff。

21
package-lock.json generated Normal file
View File

@ -0,0 +1,21 @@
{
"name": "super-mentor",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"yargs-parser": "^22.0.0"
}
},
"node_modules/yargs-parser": {
"version": "22.0.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
"integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
"license": "ISC",
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=23"
}
}
}
}

5
package.json Normal file
View File

@ -0,0 +1,5 @@
{
"dependencies": {
"yargs-parser": "^22.0.0"
}
}

View File

@ -8,7 +8,7 @@
}, },
"Description": { "Description": {
"Key": "archetype.grinder.desc", "Key": "archetype.grinder.desc",
"Fallback": "They live in the lab and push everything faster." "Fallback": "Obsessed with work; always pushing the lab faster."
}, },
"Tags": [ "archetype" ] "Tags": [ "archetype" ]
}, },
@ -17,18 +17,27 @@
"RequiredCount": 2, "RequiredCount": 2,
"Modifiers": { "Modifiers": {
"AttributeModifiers": [ "AttributeModifiers": [
{ "Type": "Activation", "Add": 5, "Multiplier": 1.1 } { "Type": "Activation", "Add": 0, "Multiplier": 1.15 }
], ],
"RuleIds": [ "rule:grinder_stress_up" ] "RuleIds": [ "rule:grinder_stress_growth_20" ]
} }
}, },
{ {
"RequiredCount": 4, "RequiredCount": 4,
"Modifiers": { "Modifiers": {
"AttributeModifiers": [ "AttributeModifiers": [
{ "Type": "Activation", "Add": 10, "Multiplier": 1.2 } { "Type": "Activation", "Add": 0, "Multiplier": 1.35 }
], ],
"RuleIds": [ "rule:grinder_overwork" ] "RuleIds": [ "rule:grinder_overwork_rage" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Activation", "Add": 0, "Multiplier": 1.60 }
],
"RuleIds": [ "rule:grinder_no_sleep_life_cost" ]
} }
} }
] ]
@ -42,7 +51,7 @@
}, },
"Description": { "Description": {
"Key": "archetype.slacker.desc", "Key": "archetype.slacker.desc",
"Fallback": "They recover morale but slow down the team." "Fallback": "Finds the best corners to rest and spread chill vibes."
}, },
"Tags": [ "archetype" ] "Tags": [ "archetype" ]
}, },
@ -51,18 +60,129 @@
"RequiredCount": 2, "RequiredCount": 2,
"Modifiers": { "Modifiers": {
"StatusModifiers": [ "StatusModifiers": [
{ "Type": "Mood", "Add": 5, "Multiplier": 1.05 } { "Type": "Mood", "Add": 0, "Multiplier": 1.50 }
], ],
"RuleIds": [ "rule:slacker_relax" ] "RuleIds": [ "rule:slacker_rest_recovery_50" ]
} }
}, },
{ {
"RequiredCount": 4, "RequiredCount": 4,
"Modifiers": { "Modifiers": {
"StatusModifiers": [ "StatusModifiers": [
{ "Type": "Stress", "Add": -5, "Multiplier": 0.9 } { "Type": "Stress", "Add": 0, "Multiplier": 1.30 }
], ],
"RuleIds": [ "rule:slacker_spread" ] "RuleIds": [ "rule:slacker_spread_breaks" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:slacker_no_reputation_loss_on_fail" ]
}
}
]
},
{
"Header": {
"Id": "core:archetype_elite",
"Name": {
"Key": "archetype.elite.name",
"Fallback": "Elite"
},
"Description": {
"Key": "archetype.elite.desc",
"Fallback": "Well-funded and well-connected; money solves problems."
},
"Tags": [ "archetype" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"ResourceModifiers": [
{ "Type": "Money", "Add": 0, "Multiplier": 1.10 }
],
"RuleIds": [ "rule:elite_interest_10" ]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"RuleIds": [ "rule:elite_shop_tier_up", "rule:elite_refund_50" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:elite_buy_progress" ]
}
}
]
},
{
"Header": {
"Id": "core:archetype_prodigy",
"Name": {
"Key": "archetype.prodigy.name",
"Fallback": "Prodigy"
},
"Description": {
"Key": "archetype.prodigy.desc",
"Fallback": "Brilliant but eccentric; thrives in extremes."
},
"Tags": [ "archetype" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Academic", "Add": 0, "Multiplier": 1.10 }
],
"RuleIds": [ "rule:prodigy_crit_15" ]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"RuleIds": [ "rule:prodigy_crit_50_on_s_tasks" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:prodigy_solitary_scaling" ]
}
}
]
},
{
"Header": {
"Id": "core:archetype_mascot",
"Name": {
"Key": "archetype.mascot.name",
"Fallback": "Mascot"
},
"Description": {
"Key": "archetype.mascot.desc",
"Fallback": "Low output, high morale and administrative favors."
},
"Tags": [ "archetype" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"StatusModifiers": [
{ "Type": "Mood", "Add": 2, "Multiplier": 1.0 }
],
"RuleIds": [ "rule:mascot_mood_regen" ]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"RuleIds": [ "rule:mascot_admin_fast", "rule:mascot_admin_immunity" ]
} }
} }
] ]

View File

@ -0,0 +1,142 @@
{
"CriticalSanityThreshold": 15,
"CriticalStaminaThreshold": 12,
"CriticalStressThreshold": 90,
"HungerThreshold": 30,
"EnergyThreshold": 25,
"SocialThreshold": 35,
"LowMoodThreshold": 25,
"HungerDecayPerSecond": 0.60,
"EnergyDecayPerSecond": 0.50,
"StaminaDecayPerSecond": 0.40,
"StressGrowthPerSecond": 0.45,
"SocialDecayPerSecond": 0.35,
"DecisionIntervalSeconds": 0.50,
"ActionDurationVariance": 0.25,
"MinPlannedActionSeconds": 2.0,
"ActionConfigs": [
{
"ActionId": "Experimenting",
"LocationId": "Laboratory",
"DurationSeconds": 6.0,
"HungerDelta": -0.60,
"EnergyDelta": -0.80,
"StaminaDelta": -1.00,
"StressDelta": 0.90,
"MoodDelta": -0.20,
"SocialDelta": -0.10
},
{
"ActionId": "Writing",
"LocationId": "Library",
"DurationSeconds": 6.0,
"HungerDelta": -0.40,
"EnergyDelta": -0.60,
"StaminaDelta": -0.60,
"StressDelta": 0.60,
"MoodDelta": -0.10,
"SocialDelta": -0.20
},
{
"ActionId": "Eating",
"LocationId": "Canteen",
"DurationSeconds": 4.5,
"HungerDelta": 3.00,
"EnergyDelta": 0.40,
"StaminaDelta": 0.80,
"StressDelta": -0.50,
"MoodDelta": 0.30,
"SocialDelta": 0.40
},
{
"ActionId": "Sleeping",
"LocationId": "Dormitory",
"DurationSeconds": 7.5,
"HungerDelta": -0.20,
"EnergyDelta": 2.40,
"StaminaDelta": 2.20,
"StressDelta": -0.90,
"MoodDelta": 0.50,
"SocialDelta": -0.20
},
{
"ActionId": "Chilling",
"LocationId": "Dormitory",
"DurationSeconds": 5.0,
"HungerDelta": -0.10,
"EnergyDelta": 0.80,
"StaminaDelta": 0.60,
"StressDelta": -0.50,
"MoodDelta": 0.80,
"SocialDelta": 0.10
},
{
"ActionId": "Staring",
"LocationId": "ArtificialLake",
"DurationSeconds": 5.5,
"HungerDelta": -0.20,
"EnergyDelta": 0.20,
"StaminaDelta": 0.20,
"StressDelta": -1.20,
"MoodDelta": 0.30,
"SocialDelta": -0.30,
"SanityDelta": 0.80
},
{
"ActionId": "CoffeeBreak",
"LocationId": "CoffeeShop",
"DurationSeconds": 4.0,
"HungerDelta": -0.10,
"EnergyDelta": 1.50,
"StaminaDelta": 0.30,
"StressDelta": -0.20,
"MoodDelta": 0.40,
"SocialDelta": 0.30
},
{
"ActionId": "Administration",
"LocationId": "AdministrationBuilding",
"DurationSeconds": 5.0,
"HungerDelta": -0.20,
"EnergyDelta": -0.40,
"StaminaDelta": -0.30,
"StressDelta": 0.80,
"MoodDelta": -0.60,
"SocialDelta": -0.10
},
{
"ActionId": "Running",
"LocationId": "FootballField",
"DurationSeconds": 4.5,
"HungerDelta": -0.30,
"EnergyDelta": -0.20,
"StaminaDelta": -0.20,
"StressDelta": -0.40,
"MoodDelta": 0.40,
"SocialDelta": 0.20,
"HealthDelta": 0.80
},
{
"ActionId": "Socializing",
"LocationId": "CoffeeShop",
"DurationSeconds": 4.5,
"HungerDelta": -0.10,
"EnergyDelta": 0.30,
"StaminaDelta": 0.10,
"StressDelta": -0.30,
"MoodDelta": 0.70,
"SocialDelta": 1.20
},
{
"ActionId": "Wandering",
"LocationId": "RandomWander",
"DurationSeconds": 4.0,
"HungerDelta": -0.10,
"EnergyDelta": -0.10,
"StaminaDelta": -0.10,
"StressDelta": -0.10,
"MoodDelta": 0.10,
"SocialDelta": -0.10
}
]
}

View File

@ -0,0 +1,438 @@
[
{
"Header": {
"Id": "core:role_coder",
"Name": {
"Key": "role.coder.name",
"Fallback": "Coder"
},
"Description": {
"Key": "role.coder.desc",
"Fallback": "Specializes in engineering tasks and computer rooms."
},
"Tags": [ "role" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Engineering", "Add": 0, "Multiplier": 1.20 }
]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"RuleIds": [ "rule:coder_copy_paste" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:coder_remote_work" ]
}
}
]
},
{
"Header": {
"Id": "core:role_writer",
"Name": {
"Key": "role.writer.name",
"Fallback": "Writer"
},
"Description": {
"Key": "role.writer.desc",
"Fallback": "Turns ideas into papers with steady output."
},
"Tags": [ "role" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Writing", "Add": 0, "Multiplier": 1.20 }
]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"RuleIds": [ "rule:writer_citation_bonus" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:writer_auto_generation" ]
}
}
]
},
{
"Header": {
"Id": "core:role_lab_rat",
"Name": {
"Key": "role.lab_rat.name",
"Fallback": "Lab Rat"
},
"Description": {
"Key": "role.lab_rat.desc",
"Fallback": "Lives in the lab and keeps experiments running."
},
"Tags": [ "role" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Activation", "Add": 0, "Multiplier": 1.10 }
],
"RuleIds": [ "rule:lab_rat_move_speed_30" ]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"ResourceModifiers": [
{ "Type": "Money", "Add": 0, "Multiplier": 1.40 }
],
"RuleIds": [ "rule:lab_rat_extra_reagents" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:lab_rat_dual_equipment" ]
}
}
]
},
{
"Header": {
"Id": "core:role_presenter",
"Name": {
"Key": "role.presenter.name",
"Fallback": "Presenter"
},
"Description": {
"Key": "role.presenter.desc",
"Fallback": "Handles defenses, meetings, and funding pitches."
},
"Tags": [ "role" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Social", "Add": 0, "Multiplier": 1.20 }
],
"RuleIds": [ "rule:presenter_pitch_success_20" ]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"RuleIds": [ "rule:presenter_double_funding" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:presenter_hype_aura" ]
}
}
]
},
{
"Header": {
"Id": "core:role_scribe",
"Name": {
"Key": "role.scribe.name",
"Fallback": "Scribe"
},
"Description": {
"Key": "role.scribe.desc",
"Fallback": "A reliable writing role used by all disciplines."
},
"Tags": [ "role" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Writing", "Add": 0, "Multiplier": 1.15 }
]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"RuleIds": [ "rule:scribe_reputation_bonus" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:scribe_split_publication" ]
}
}
]
},
{
"Header": {
"Id": "core:role_orator",
"Name": {
"Key": "role.orator.name",
"Fallback": "Orator"
},
"Description": {
"Key": "role.orator.desc",
"Fallback": "Brings in resources and keeps morale afloat."
},
"Tags": [ "role" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Social", "Add": 0, "Multiplier": 1.15 }
]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"ResourceModifiers": [
{ "Type": "Money", "Add": 0, "Multiplier": 1.30 }
],
"RuleIds": [ "rule:orator_extra_funding" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:orator_morale_shield" ]
}
}
]
},
{
"Header": {
"Id": "core:role_steward",
"Name": {
"Key": "role.steward.name",
"Fallback": "Steward"
},
"Description": {
"Key": "role.steward.desc",
"Fallback": "Keeps admin tasks and logistics under control."
},
"Tags": [ "role" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Activation", "Add": 0, "Multiplier": 1.10 }
],
"RuleIds": [ "rule:steward_admin_speed_50" ]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"ResourceModifiers": [
{ "Type": "Money", "Add": 0, "Multiplier": 0.80 }
],
"RuleIds": [ "rule:steward_cost_reduction" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:steward_auto_supply" ]
}
}
]
},
{
"Header": {
"Id": "core:role_alchemist",
"Name": {
"Key": "role.alchemist.name",
"Fallback": "Alchemist"
},
"Description": {
"Key": "role.alchemist.desc",
"Fallback": "Consumes reagents for high-risk experiments."
},
"Tags": [ "role" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"ResourceModifiers": [
{ "Type": "Money", "Add": 0, "Multiplier": 0.80 }
],
"RuleIds": [ "rule:alchemist_reagent_discount" ]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"RuleIds": [ "rule:alchemist_explosion_5", "rule:alchemist_success_boost" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:alchemist_fail_progress_persist" ]
}
}
],
"AllowedDisciplineIds": [
"core:discipline_biology",
"core:discipline_chemistry",
"core:discipline_environment",
"core:discipline_materials",
"core:discipline_medicine",
"core:discipline_agriculture"
]
},
{
"Header": {
"Id": "core:role_geek",
"Name": {
"Key": "role.geek.name",
"Fallback": "Geek"
},
"Description": {
"Key": "role.geek.desc",
"Fallback": "Consumes compute power for engineering output."
},
"Tags": [ "role" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Engineering", "Add": 0, "Multiplier": 1.15 }
],
"RuleIds": [ "rule:geek_cooling_boost" ]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"RuleIds": [ "rule:geek_progress_inherit_30" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:geek_remote_work", "rule:geek_network_immunity" ]
}
}
],
"AllowedDisciplineIds": [
"core:discipline_computer",
"core:discipline_physics",
"core:discipline_math",
"core:discipline_mechanical"
]
},
{
"Header": {
"Id": "core:role_surveyor",
"Name": {
"Key": "role.surveyor.name",
"Fallback": "Surveyor"
},
"Description": {
"Key": "role.surveyor.desc",
"Fallback": "Collects field data and brings it back for analysis."
},
"Tags": [ "role" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Activation", "Add": 0, "Multiplier": 1.10 }
],
"RuleIds": [ "rule:surveyor_move_speed_30" ]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"RuleIds": [ "rule:surveyor_extra_leads" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:surveyor_reviewer_insight" ]
}
}
],
"AllowedDisciplineIds": [
"core:discipline_economics",
"core:discipline_management",
"core:discipline_law"
]
},
{
"Header": {
"Id": "core:role_thinker",
"Name": {
"Key": "role.thinker.name",
"Fallback": "Thinker"
},
"Description": {
"Key": "role.thinker.desc",
"Fallback": "Slow but capable of sudden leaps of insight."
},
"Tags": [ "role" ]
},
"Tiers": [
{
"RequiredCount": 2,
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Academic", "Add": 0, "Multiplier": 0.90 }
],
"RuleIds": [ "rule:thinker_epiphany_10" ]
}
},
{
"RequiredCount": 4,
"Modifiers": {
"RuleIds": [ "rule:thinker_low_mood_boost" ]
}
},
{
"RequiredCount": 6,
"Modifiers": {
"RuleIds": [ "rule:thinker_masterwork" ]
}
}
],
"AllowedDisciplineIds": [
"core:discipline_philosophy",
"core:discipline_literature",
"core:discipline_art"
]
}
]

View File

@ -0,0 +1,283 @@
[
{
"Header": {
"Id": "core:trait_caffeine_dependence",
"Name": {
"Key": "trait.caffeine.name",
"Fallback": "Caffeine Dependence"
},
"Description": {
"Key": "trait.caffeine.desc",
"Fallback": "Needs coffee to work at full speed."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"RuleIds": [ "rule:trait_requires_coffee" ]
}
},
{
"Header": {
"Id": "core:trait_night_owl",
"Name": {
"Key": "trait.night_owl.name",
"Fallback": "Night Owl"
},
"Description": {
"Key": "trait.night_owl.desc",
"Fallback": "Shines after 18:00 and drifts in the daytime."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"RuleIds": [ "rule:trait_night_owl" ]
}
},
{
"Header": {
"Id": "core:trait_ocd",
"Name": {
"Key": "trait.ocd.name",
"Fallback": "OCD"
},
"Description": {
"Key": "trait.ocd.desc",
"Fallback": "Must push tasks to 100% before stopping."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"RuleIds": [ "rule:trait_ocd_full_completion" ]
}
},
{
"Header": {
"Id": "core:trait_otaku",
"Name": {
"Key": "trait.otaku.name",
"Fallback": "Otaku"
},
"Description": {
"Key": "trait.otaku.desc",
"Fallback": "Mood never drops near figurine decorations."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"RuleIds": [ "rule:trait_otaku_figurine" ]
}
},
{
"Header": {
"Id": "core:trait_glass_heart",
"Name": {
"Key": "trait.glass_heart.name",
"Fallback": "Glass Heart"
},
"Description": {
"Key": "trait.glass_heart.desc",
"Fallback": "Faints when scolded or after paper rejection."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"RuleIds": [ "rule:trait_glass_heart" ]
}
},
{
"Header": {
"Id": "core:trait_big_eater",
"Name": {
"Key": "trait.big_eater.name",
"Fallback": "Big Eater"
},
"Description": {
"Key": "trait.big_eater.desc",
"Fallback": "Consumes double salary but has double stamina cap."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"StatusModifiers": [
{ "Type": "Stamina", "Add": 0, "Multiplier": 2.0 }
],
"RuleIds": [ "rule:trait_big_eater_salary" ]
}
},
{
"Header": {
"Id": "core:trait_social_phobia",
"Name": {
"Key": "trait.social_phobia.name",
"Fallback": "Social Phobia"
},
"Description": {
"Key": "trait.social_phobia.desc",
"Fallback": "Works better alone and avoids crowds."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"RuleIds": [ "rule:trait_social_phobia" ]
}
},
{
"Header": {
"Id": "core:trait_social_butterfly",
"Name": {
"Key": "trait.social_butterfly.name",
"Fallback": "Social Butterfly"
},
"Description": {
"Key": "trait.social_butterfly.desc",
"Fallback": "Boosts nearby teammates when chatting."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Social", "Add": 5, "Multiplier": 1.05 }
],
"RuleIds": [ "rule:trait_social_butterfly" ]
}
},
{
"Header": {
"Id": "core:trait_lucky",
"Name": {
"Key": "trait.lucky.name",
"Fallback": "Lucky"
},
"Description": {
"Key": "trait.lucky.desc",
"Fallback": "Bad events never trigger when participating."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"RuleIds": [ "rule:trait_lucky" ]
}
},
{
"Header": {
"Id": "core:trait_unlucky",
"Name": {
"Key": "trait.unlucky.name",
"Fallback": "Unlucky"
},
"Description": {
"Key": "trait.unlucky.desc",
"Fallback": "Bad events are more frequent but growth is faster."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Activation", "Add": 5, "Multiplier": 1.05 }
],
"RuleIds": [ "rule:trait_unlucky" ]
}
},
{
"Header": {
"Id": "core:trait_keyboard_warrior",
"Name": {
"Key": "trait.keyboard_warrior.name",
"Fallback": "Keyboard Warrior"
},
"Description": {
"Key": "trait.keyboard_warrior.desc",
"Fallback": "Excels in online PR or debate tasks."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Writing", "Add": 5, "Multiplier": 1.05 }
],
"RuleIds": [ "rule:trait_keyboard_warrior" ]
}
},
{
"Header": {
"Id": "core:trait_looks_matter",
"Name": {
"Key": "trait.looks_matter.name",
"Fallback": "Looks Matter"
},
"Description": {
"Key": "trait.looks_matter.desc",
"Fallback": "Loyalty barely drops with attractive teammates."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"StatusModifiers": [
{ "Type": "Loyalty", "Add": 5, "Multiplier": 1.0 }
],
"RuleIds": [ "rule:trait_looks_matter" ]
}
},
{
"Header": {
"Id": "core:trait_nice_guy",
"Name": {
"Key": "trait.nice_guy.name",
"Fallback": "Nice Guy"
},
"Description": {
"Key": "trait.nice_guy.desc",
"Fallback": "Auto-helps others but overworks easily."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"StatusModifiers": [
{ "Type": "Mood", "Add": 5, "Multiplier": 1.0 }
],
"RuleIds": [ "rule:trait_nice_guy" ]
}
},
{
"Header": {
"Id": "core:trait_rebel",
"Name": {
"Key": "trait.rebel.name",
"Fallback": "Rebel"
},
"Description": {
"Key": "trait.rebel.desc",
"Fallback": "Cannot be commanded directly but has high stats."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"AttributeModifiers": [
{ "Type": "Academic", "Add": 10, "Multiplier": 1.0 },
{ "Type": "Engineering", "Add": 10, "Multiplier": 1.0 },
{ "Type": "Writing", "Add": 10, "Multiplier": 1.0 },
{ "Type": "Financial", "Add": 10, "Multiplier": 1.0 },
{ "Type": "Social", "Add": 10, "Multiplier": 1.0 },
{ "Type": "Activation", "Add": 10, "Multiplier": 1.0 }
],
"RuleIds": [ "rule:trait_rebel" ]
}
},
{
"Header": {
"Id": "core:trait_not_human",
"Name": {
"Key": "trait.not_human.name",
"Fallback": "Not Human"
},
"Description": {
"Key": "trait.not_human.desc",
"Fallback": "Needs no food or sleep but burns compute costs."
},
"Tags": [ "trait" ]
},
"Modifiers": {
"RuleIds": [ "rule:trait_not_human" ]
}
}
]

View File

@ -1,6 +1,8 @@
using Godot; using Godot;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Core;
using Models;
public partial class CampusController : Node2D public partial class CampusController : Node2D
{ {
@ -10,13 +12,33 @@ public partial class CampusController : Node2D
private Button _logToggle; private Button _logToggle;
[Export] public PackedScene StudentScene { get; set; } [Export] public PackedScene StudentScene { get; set; }
[Export] public int StudentCount { get; set; } = 6; [Export] public int StudentCount { get; set; } = 5;
[Export] public float CoverageStep { get; set; } = 48.0f; [Export] public float CoverageStep { get; set; } = 48.0f;
[Export] public int MaxCoveragePoints { get; set; } = 200; [Export] public int MaxCoveragePoints { get; set; } = 200;
[Export] public string BehaviorConfigPath { get; set; } = "res://resources/definitions/campus_behavior.json";
[Export] public int RandomSeed { get; set; } = 0;
[Export] public int AssignedTaskChancePercent { get; set; } = 60;
[Export] public float RoundDurationSeconds { get; set; } = 30.0f;
[Export] public float AgentMoveSpeed { get; set; } = 90.0f;
private NavigationRegion2D _navigationRegion; private NavigationRegion2D _navigationRegion;
private Node2D _studentsRoot; private Node2D _studentsRoot;
private TopBar _topBar;
private RichTextLabel _logLabel;
private readonly List<Vector2> _coveragePoints = new(); private readonly List<Vector2> _coveragePoints = new();
private readonly List<CampusBehaviorAgent> _behaviorAgents = new();
private readonly CampusBehaviorWorld _behaviorWorld = new();
private readonly CampusLocationRegistry _locationRegistry = new();
private CampusBehaviorConfig _behaviorConfig;
private GameContentDatabase _contentDatabase;
private List<string> _archetypeIds = new();
private List<string> _roleIds = new();
private List<string> _traitIds = new();
private List<string> _disciplineIds = new();
private Random _random;
private float _roundElapsed;
private int _roundIndex;
private bool _roundActive;
private bool _spawnPending = true; private bool _spawnPending = true;
private bool _navBakePending = false; private bool _navBakePending = false;
private bool _navBakeReady = false; private bool _navBakeReady = false;
@ -40,6 +62,20 @@ public partial class CampusController : Node2D
_taskToggle.Toggled += OnTaskToggled; _taskToggle.Toggled += OnTaskToggled;
_logToggle.Toggled += OnLogToggled; _logToggle.Toggled += OnLogToggled;
_topBar = GetNodeOrNull<TopBar>("TopBar");
if (_topBar != null)
{
_topBar.ResetRound(RoundDurationSeconds);
}
_logLabel = GetNodeOrNull<RichTextLabel>("Log/VBoxContainer/RichTextLabel");
if (_logLabel != null)
{
_logLabel.Text = string.Empty;
}
InitializeBehaviorAssets();
// 导航区域与学生容器初始化 // 导航区域与学生容器初始化
_navigationRegion = GetNodeOrNull<NavigationRegion2D>("Sprite2D/NavigationRegion2D"); _navigationRegion = GetNodeOrNull<NavigationRegion2D>("Sprite2D/NavigationRegion2D");
_studentsRoot = GetNodeOrNull<Node2D>("Students"); _studentsRoot = GetNodeOrNull<Node2D>("Students");
@ -60,6 +96,151 @@ public partial class CampusController : Node2D
public override void _Process(double delta) public override void _Process(double delta)
{ {
TrySpawnStudents(); TrySpawnStudents();
UpdateRoundTimer((float)delta);
UpdateBehaviorAgents((float)delta);
}
private void InitializeBehaviorAssets()
{
_behaviorConfig = CampusBehaviorConfig.Load(BehaviorConfigPath);
_random = RandomSeed == 0 ? new Random() : new Random(RandomSeed);
LoadContentDatabase();
CacheLocations();
}
private void LoadContentDatabase()
{
var registry = new ContentRegistry();
var jsonSource = new JsonContentSource(0);
jsonSource.DataPaths.Add("res://resources/definitions/disciplines.json");
jsonSource.DataPaths.Add("res://resources/definitions/archetypes.json");
jsonSource.DataPaths.Add("res://resources/definitions/roles.json");
jsonSource.DataPaths.Add("res://resources/definitions/traits.json");
registry.RegisterSource(jsonSource);
_contentDatabase = registry.BuildDatabase();
_archetypeIds = new List<string>(_contentDatabase.Archetypes.Keys);
_roleIds = new List<string>(_contentDatabase.Roles.Keys);
_traitIds = new List<string>(_contentDatabase.Traits.Keys);
_disciplineIds = new List<string>(_contentDatabase.Disciplines.Keys);
if (_archetypeIds.Count == 0 || _roleIds.Count == 0 || _traitIds.Count == 0)
{
GD.PushWarning("Behavior content definitions are missing; random tags will be limited.");
}
}
private void CacheLocations()
{
var locationsRoot = GetNodeOrNull<Node2D>("Locations");
if (locationsRoot == null)
{
GD.PushWarning("Campus scene is missing Locations root; agents will wander only.");
return;
}
RegisterLocation(locationsRoot, "Location_Lab", CampusLocationId.Laboratory);
RegisterLocation(locationsRoot, "Location_Library", CampusLocationId.Library);
RegisterLocation(locationsRoot, "Location_Canteen", CampusLocationId.Canteen);
RegisterLocation(locationsRoot, "Location_Dorm", CampusLocationId.Dormitory);
RegisterLocation(locationsRoot, "Location_Lake", CampusLocationId.ArtificialLake);
RegisterLocation(locationsRoot, "Location_Coffee", CampusLocationId.CoffeeShop);
RegisterLocation(locationsRoot, "Location_Admin", CampusLocationId.AdministrationBuilding);
RegisterLocation(locationsRoot, "Location_Field", CampusLocationId.FootballField);
}
private void RegisterLocation(Node2D root, string nodeName, CampusLocationId id)
{
var node = root.GetNodeOrNull<Node2D>(nodeName);
if (node == null)
{
GD.PushWarning($"Campus location marker not found: {nodeName}");
return;
}
_locationRegistry.Register(id, node.GlobalPosition);
}
private void UpdateBehaviorAgents(float delta)
{
if (_behaviorAgents.Count == 0) return;
_behaviorWorld.Clear();
foreach (var agent in _behaviorAgents)
{
_behaviorWorld.AddOccupant(agent.Runtime.CurrentLocationId);
}
foreach (var agent in _behaviorAgents)
{
agent.Tick(delta);
}
}
private void UpdateRoundTimer(float delta)
{
if (_behaviorAgents.Count == 0 || _spawnPending) return;
if (!_roundActive)
{
StartRound();
}
if (!_roundActive) return;
_roundElapsed += delta;
_topBar?.UpdateRoundProgress(_roundElapsed, RoundDurationSeconds);
if (_roundElapsed >= RoundDurationSeconds)
{
EndRound();
}
}
private void StartRound()
{
_roundActive = true;
_roundElapsed = 0.0f;
_roundIndex += 1;
_topBar?.ResetRound(RoundDurationSeconds);
AppendLog($"第{_roundIndex}轮开始");
foreach (var agent in _behaviorAgents)
{
EnsureRoundTask(agent.Runtime);
agent.StartRound(RoundDurationSeconds);
}
}
private void EndRound()
{
_roundActive = false;
foreach (var agent in _behaviorAgents)
{
agent.EndRound();
}
AppendLog($"第{_roundIndex}轮结束");
}
private void EnsureRoundTask(CampusAgentRuntime runtime)
{
if (runtime.AssignedTask != null) return;
if (TaskTypePool.Length == 0) return;
var rng = _random ?? Random.Shared;
if (rng.Next(0, 100) >= AssignedTaskChancePercent) return;
var taskType = TaskTypePool[rng.Next(0, TaskTypePool.Length)];
var maxDuration = Math.Max(10, (int)RoundDurationSeconds - 5);
var duration = rng.Next(8, Math.Max(9, maxDuration));
runtime.AssignedTask = new CampusTask(taskType, duration);
}
private void AppendLog(string message)
{
if (_logLabel == null || string.IsNullOrWhiteSpace(message)) return;
_logLabel.AppendText(message + "\n");
} }
private void OnTaskToggled(bool pressed) private void OnTaskToggled(bool pressed)
@ -176,16 +357,140 @@ public partial class CampusController : Node2D
} }
_studentsRoot.AddChild(student); _studentsRoot.AddChild(student);
student.Name = $"CampusStudent_{i + 1}";
student.SetNavigationMap(map); student.SetNavigationMap(map);
student.MoveSpeed = AgentMoveSpeed;
// 随机放置在可行走区域,并设置不同的巡游起点 // 随机放置在可行走区域,并交给行为系统控制
var randomIndex = GD.RandRange(0, _coveragePoints.Count - 1); var randomIndex = _random != null
? _random.Next(0, _coveragePoints.Count)
: (int)GD.RandRange(0, _coveragePoints.Count - 1);
student.GlobalPosition = _coveragePoints[randomIndex]; student.GlobalPosition = _coveragePoints[randomIndex];
student.ConfigurePatrol(_coveragePoints, i * 7); student.ApplyRandomTheme();
var runtime = BuildRandomAgentRuntime(i);
student.Name = runtime.Name;
var agent = new CampusBehaviorAgent(
student,
runtime,
_behaviorConfig,
_locationRegistry,
_behaviorWorld,
_random,
_coveragePoints,
AppendLog);
_behaviorAgents.Add(agent);
LogSpawn(runtime);
} }
} }
private static readonly CampusTaskType[] TaskTypePool =
{
CampusTaskType.Experiment,
CampusTaskType.Writing,
CampusTaskType.Administration,
CampusTaskType.Exercise,
CampusTaskType.Coding,
CampusTaskType.Social
};
private CampusAgentRuntime BuildRandomAgentRuntime(int index)
{
var rng = _random ?? Random.Shared;
var name = $"Agent_{index + 1}";
var student = new StudentModel(name, rng);
AssignRandomTags(student.Core, rng);
ApplyRandomStatus(student, rng);
var needs = new CampusAgentNeeds(
rng.Next(20, 100),
rng.Next(20, 100),
rng.Next(20, 100),
rng.Next(60, 100));
var runtime = new CampusAgentRuntime(student, needs);
if (rng.Next(0, 100) < AssignedTaskChancePercent && TaskTypePool.Length > 0)
{
var taskType = TaskTypePool[rng.Next(0, TaskTypePool.Length)];
var duration = rng.Next(8, 16);
runtime.AssignedTask = new CampusTask(taskType, duration);
}
return runtime;
}
private void AssignRandomTags(UnitModel unit, Random rng)
{
unit.Tags.ArchetypeIds.Clear();
unit.Tags.RoleIds.Clear();
unit.Tags.TraitIds.Clear();
var archetypeId = PickRandomId(_archetypeIds, rng);
if (!string.IsNullOrWhiteSpace(archetypeId))
{
unit.Tags.ArchetypeIds.Add(archetypeId);
}
var roleId = PickRandomId(_roleIds, rng);
if (!string.IsNullOrWhiteSpace(roleId))
{
unit.Tags.RoleIds.Add(roleId);
}
var traitCount = _traitIds.Count == 0 ? 0 : rng.Next(1, Math.Min(3, _traitIds.Count + 1));
for (var i = 0; i < traitCount; i++)
{
var traitId = PickRandomId(_traitIds, rng);
if (!string.IsNullOrWhiteSpace(traitId) && !unit.Tags.TraitIds.Contains(traitId))
{
unit.Tags.TraitIds.Add(traitId);
}
}
var disciplineId = PickRandomId(_disciplineIds, rng);
if (!string.IsNullOrWhiteSpace(disciplineId))
{
unit.Tags.DisciplineId = disciplineId;
}
}
private void ApplyRandomStatus(StudentModel student, Random rng)
{
var unit = student.Core;
unit.Statuses.Stress.Current.Value = rng.Next(0, 60);
unit.Statuses.Sanity.Current.Value = rng.Next(50, 100);
unit.Statuses.Mood.Value = rng.Next(30, 90);
student.Progress.Stamina.Current.Value = rng.Next(25, 100);
}
private static string PickRandomId(List<string> ids, Random rng)
{
if (ids == null || ids.Count == 0) return null;
return ids[rng.Next(0, ids.Count)];
}
private void LogSpawn(CampusAgentRuntime runtime)
{
var unit = runtime.Unit;
var archetypes = unit.Tags.ArchetypeIds.Count == 0 ? "none" : string.Join(",", unit.Tags.ArchetypeIds);
var roles = unit.Tags.RoleIds.Count == 0 ? "none" : string.Join(",", unit.Tags.RoleIds);
var traits = unit.Tags.TraitIds.Count == 0 ? "none" : string.Join(",", unit.Tags.TraitIds);
var taskInfo = runtime.AssignedTask != null
? $"{runtime.AssignedTask.Type} ({runtime.AssignedTask.RemainingSeconds:0}s)"
: "none";
var attributes =
$"A:{unit.Attributes.Academic.DisplayInt()} " +
$"E:{unit.Attributes.Engineering.DisplayInt()} " +
$"W:{unit.Attributes.Writing.DisplayInt()} " +
$"F:{unit.Attributes.Financial.DisplayInt()} " +
$"S:{unit.Attributes.Social.DisplayInt()} " +
$"Act:{unit.Attributes.Activation.DisplayInt()}";
GD.Print($"[CampusAI] Spawned {runtime.Name} archetype={archetypes} role={roles} traits={traits} task={taskInfo} attrs={attributes}");
}
private List<Vector2> BuildCoveragePoints() private List<Vector2> BuildCoveragePoints()
{ {
var points = new List<Vector2>(); var points = new List<Vector2>();

View File

@ -7,10 +7,9 @@
[ext_resource type="PackedScene" uid="uid://drmjsqoy8htc8" path="res://scenes/ui-elements/task_list.tscn" id="3_4gjr3"] [ext_resource type="PackedScene" uid="uid://drmjsqoy8htc8" path="res://scenes/ui-elements/task_list.tscn" id="3_4gjr3"]
[sub_resource type="NavigationPolygon" id="NavigationPolygon_8u8vn"] [sub_resource type="NavigationPolygon" id="NavigationPolygon_8u8vn"]
vertices = PackedVector2Array(956, 540, 4, 540, 420, 516, 460, 516, 380, 516, 204, 356, 204, 260, 228, 284, 228, 468, 4, 500, 100, 500, 380, 468, 300, 468, 148, 212, 148, 196, 156, 196, 156, 212, 244, 212, 244, 20, 268, 20, 268, 236, 436, 236, 436, 196, 444, 196, 444, 236, 612, 236, 612, 20, 636, 20, 636, 244, 708, 244, 708, 196, 732, 196, 732, 172, 756, 172, 756, 196, 772, 196, 772, 20, 796, 20, 796, 228, 844, 228, 844, 196, 852, 196, 852, 228, 860, 228, 860, 284, 828, 284, 804, 284, 916, 308, 916, 292, 940, 292, 948, 332, 940, 188, 948, 188, 828, 332, 828, 308, 828, 516, 804, 516, 572, 284, 572, 300, 548, 300, 548, 284, 956, 516, 116, 364, 124, 364, 124, 396, 196, 396, 196, 356, 180, 436, 180, 412, 140, 412, 292, 468, 292, 452, 300, 452, 140, 436, 4, 284, 4, 4, 28, 4, 28, 212, 100, 284, 116, 260, 460, 284, 420, 284) vertices = PackedVector2Array(956, 540, 4, 540, 420, 516, 460, 516, 380, 516, 204, 356, 204, 260, 228, 284, 228, 468, 4, 500, 100, 500, 380, 468, 300, 468, 148, 212, 148, 196, 156, 196, 156, 212, 244, 212, 244, 20, 268, 20, 268, 236, 436, 236, 436, 196, 444, 196, 444, 236, 612, 236, 612, 20, 636, 20, 636, 244, 708, 244, 708, 196, 732, 196, 732, 172, 756, 172, 756, 196, 772, 196, 772, 20, 796, 20, 796, 228, 844, 228, 844, 196, 852, 196, 852, 228, 860, 228, 860, 284, 828, 284, 804, 284, 932, 308, 932, 180, 956, 180, 956, 332, 828, 332, 828, 308, 828, 516, 804, 516, 572, 284, 572, 300, 548, 300, 548, 284, 956, 516, 116, 364, 124, 364, 124, 396, 196, 396, 196, 356, 180, 436, 180, 412, 140, 412, 292, 468, 292, 452, 300, 452, 140, 436, 4, 284, 4, 4, 28, 4, 28, 212, 100, 284, 116, 260, 460, 284, 420, 284)
polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(4, 2, 1), PackedInt32Array(5, 6, 7, 8), PackedInt32Array(4, 1, 9, 10), PackedInt32Array(11, 4, 10, 12), PackedInt32Array(13, 14, 15, 16), PackedInt32Array(17, 18, 19, 20), PackedInt32Array(21, 22, 23, 24), PackedInt32Array(25, 26, 27, 28), PackedInt32Array(29, 30, 31), PackedInt32Array(31, 32, 33, 34), PackedInt32Array(35, 36, 37, 38), PackedInt32Array(39, 40, 41, 42), PackedInt32Array(42, 43, 44, 45), PackedInt32Array(39, 42, 45), PackedInt32Array(38, 39, 45, 46), PackedInt32Array(47, 48, 49, 50), PackedInt32Array(49, 51, 52, 50), PackedInt32Array(47, 50, 53, 54), PackedInt32Array(53, 55, 56, 46), PackedInt32Array(54, 53, 46), PackedInt32Array(45, 54, 46), PackedInt32Array(38, 46, 57, 29), PackedInt32Array(57, 58, 59, 60), PackedInt32Array(55, 61, 0), PackedInt32Array(62, 63, 64, 10), PackedInt32Array(65, 66, 5, 8), PackedInt32Array(65, 8, 67, 68), PackedInt32Array(64, 65, 68, 69), PackedInt32Array(70, 71, 72, 12), PackedInt32Array(70, 12, 10), PackedInt32Array(8, 70, 10), PackedInt32Array(67, 8, 10, 73), PackedInt32Array(74, 75, 76, 77), PackedInt32Array(78, 74, 77, 79), PackedInt32Array(29, 31, 34, 38), PackedInt32Array(34, 35, 38), PackedInt32Array(28, 29, 57), PackedInt32Array(24, 25, 28, 57, 60), PackedInt32Array(24, 60, 80, 21), PackedInt32Array(20, 21, 80, 81, 6), PackedInt32Array(64, 69, 73, 10), PackedInt32Array(16, 17, 20, 6), PackedInt32Array(81, 7, 6), PackedInt32Array(62, 10, 78, 79), PackedInt32Array(79, 77, 13, 6), PackedInt32Array(6, 13, 16), PackedInt32Array(81, 80, 3, 2), PackedInt32Array(0, 3, 56), PackedInt32Array(0, 56, 55)]) polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(4, 2, 1), PackedInt32Array(5, 6, 7, 8), PackedInt32Array(4, 1, 9, 10), PackedInt32Array(11, 4, 10, 12), PackedInt32Array(13, 14, 15, 16), PackedInt32Array(17, 18, 19, 20), PackedInt32Array(21, 22, 23, 24), PackedInt32Array(25, 26, 27, 28), PackedInt32Array(29, 30, 31), PackedInt32Array(31, 32, 33, 34), PackedInt32Array(35, 36, 37, 38), PackedInt32Array(39, 40, 41, 42), PackedInt32Array(42, 43, 44, 45), PackedInt32Array(39, 42, 45), PackedInt32Array(38, 39, 45, 46), PackedInt32Array(47, 48, 49, 50), PackedInt32Array(47, 50, 51, 52), PackedInt32Array(51, 53, 54, 46), PackedInt32Array(52, 51, 46), PackedInt32Array(45, 52, 46), PackedInt32Array(38, 46, 55, 29), PackedInt32Array(55, 56, 57, 58), PackedInt32Array(53, 59, 0), PackedInt32Array(60, 61, 62, 10), PackedInt32Array(63, 64, 5, 8), PackedInt32Array(63, 8, 65, 66), PackedInt32Array(62, 63, 66, 67), PackedInt32Array(68, 69, 70, 12), PackedInt32Array(68, 12, 10), PackedInt32Array(8, 68, 10), PackedInt32Array(65, 8, 10, 71), PackedInt32Array(72, 73, 74, 75), PackedInt32Array(76, 72, 75, 77), PackedInt32Array(29, 31, 34, 38), PackedInt32Array(34, 35, 38), PackedInt32Array(28, 29, 55), PackedInt32Array(24, 25, 28, 55, 58), PackedInt32Array(24, 58, 78, 21), PackedInt32Array(20, 21, 78, 79, 6), PackedInt32Array(62, 67, 71, 10), PackedInt32Array(16, 17, 20, 6), PackedInt32Array(79, 7, 6), PackedInt32Array(60, 10, 76, 77), PackedInt32Array(77, 75, 13, 6), PackedInt32Array(6, 13, 16), PackedInt32Array(79, 78, 3, 2), PackedInt32Array(0, 3, 54), PackedInt32Array(0, 54, 53)])
outlines = Array[PackedVector2Array]([PackedVector2Array(0, 0, 32, 0, 32, 208, 144, 208, 144, 192, 160, 192, 160, 208, 240, 208, 240, 16, 272, 16, 272, 232, 432, 232, 432, 192, 448, 192, 448, 232, 608, 232, 608, 16, 640, 16, 640, 240, 704, 240, 704, 192, 728, 192, 728, 168, 760, 168, 760, 192, 768, 192, 768, 16, 800, 16, 800, 224, 832, 224, 840, 224, 840, 192, 856, 192, 856, 224, 864, 224, 864, 288, 832, 288, 832, 304, 912, 304, 912, 288, 936, 288, 936, 184, 952, 184, 952, 336, 832, 336, 832, 512, 960, 512, 960, 544, 0, 544, 0, 496, 96, 496, 96, 288, 0, 288), PackedVector2Array(144, 432, 176, 432, 176, 416, 144, 416), PackedVector2Array(128, 392, 192, 392, 192, 352, 200, 352, 200, 264, 120, 264, 120, 360, 128, 360), PackedVector2Array(232, 304, 232, 464, 288, 464, 288, 448, 304, 448, 304, 464, 384, 464, 384, 512, 416, 512, 416, 288, 232, 288), PackedVector2Array(464, 288, 464, 512, 800, 512, 800, 288, 576, 288, 576, 304, 544, 304, 544, 288), PackedVector2Array(144, 464, 176, 464, 176, 480, 144, 480)]) outlines = Array[PackedVector2Array]([PackedVector2Array(0, 0, 32, 0, 32, 208, 144, 208, 144, 192, 160, 192, 160, 208, 240, 208, 240, 16, 272, 16, 272, 232, 432, 232, 432, 192, 448, 192, 448, 232, 608, 232, 608, 16, 640, 16, 640, 240, 704, 240, 704, 192, 728, 192, 728, 168, 760, 168, 760, 192, 768, 192, 768, 16, 800, 16, 800, 224, 832, 224, 840, 224, 840, 192, 856, 192, 856, 224, 864, 224, 864, 288, 832, 288, 832, 304, 912, 304, 928, 304, 928, 288, 928, 176, 960, 176, 960, 336, 832, 336, 832, 512, 960, 512, 960, 544, 0, 544, 0, 496, 96, 496, 96, 288, 0, 288), PackedVector2Array(144, 432, 176, 432, 176, 416, 144, 416), PackedVector2Array(128, 392, 192, 392, 192, 352, 200, 352, 200, 264, 120, 264, 120, 360, 128, 360), PackedVector2Array(232, 304, 232, 464, 288, 464, 288, 448, 304, 448, 304, 464, 384, 464, 384, 512, 416, 512, 416, 288, 232, 288), PackedVector2Array(464, 288, 464, 512, 800, 512, 800, 288, 576, 288, 576, 304, 544, 304, 544, 288), PackedVector2Array(144, 464, 176, 464, 176, 480, 144, 480)])
parsed_geometry_type = 1
parsed_collision_mask = 4294967294 parsed_collision_mask = 4294967294
agent_radius = 4.0 agent_radius = 4.0
@ -2010,6 +2009,7 @@ centered = false
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="Sprite2D"] [node name="NavigationRegion2D" type="NavigationRegion2D" parent="Sprite2D"]
navigation_polygon = SubResource("NavigationPolygon_8u8vn") navigation_polygon = SubResource("NavigationPolygon_8u8vn")
enter_cost = 1.0
[node name="Log" parent="." instance=ExtResource("1_hi2p7")] [node name="Log" parent="." instance=ExtResource("1_hi2p7")]
visible = false visible = false
@ -2019,7 +2019,6 @@ offset_right = 955.0
offset_bottom = 535.0 offset_bottom = 535.0
[node name="TopBar" parent="." instance=ExtResource("2_p4tmp")] [node name="TopBar" parent="." instance=ExtResource("2_p4tmp")]
visible = false
offset_bottom = 55.0 offset_bottom = 55.0
[node name="Task" parent="." instance=ExtResource("3_4gjr3")] [node name="Task" parent="." instance=ExtResource("3_4gjr3")]
@ -2032,3 +2031,29 @@ offset_bottom = 455.0
[node name="TileMapLayer" type="TileMapLayer" parent="."] [node name="TileMapLayer" type="TileMapLayer" parent="."]
visible = false visible = false
tile_set = SubResource("TileSet_74kl0") tile_set = SubResource("TileSet_74kl0")
[node name="Locations" type="Node2D" parent="."]
[node name="Location_Lab" type="Node2D" parent="Locations"]
position = Vector2(150, 196)
[node name="Location_Library" type="Node2D" parent="Locations"]
position = Vector2(440, 196)
[node name="Location_Canteen" type="Node2D" parent="Locations"]
position = Vector2(745, 166)
[node name="Location_Dorm" type="Node2D" parent="Locations"]
position = Vector2(848, 192)
[node name="Location_Lake" type="Node2D" parent="Locations"]
position = Vector2(943, 179)
[node name="Location_Coffee" type="Node2D" parent="Locations"]
position = Vector2(160, 395)
[node name="Location_Admin" type="Node2D" parent="Locations"]
position = Vector2(296, 452)
[node name="Location_Field" type="Node2D" parent="Locations"]
position = Vector2(560, 300)

View File

@ -877,6 +877,7 @@ _data = {
[node name="Student" type="CharacterBody2D"] [node name="Student" type="CharacterBody2D"]
z_index = 2 z_index = 2
script = ExtResource("1_oesea") script = ExtResource("1_oesea")
DebugDrawPath = true
[node name="CollisionShape2D" type="CollisionShape2D" parent="."] [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("RectangleShape2D_opr6h") shape = SubResource("RectangleShape2D_opr6h")
@ -935,9 +936,8 @@ autoplay = "RESET"
script = ExtResource("8_kvqca") script = ExtResource("8_kvqca")
[node name="NavigationAgent2D" type="NavigationAgent2D" parent="."] [node name="NavigationAgent2D" type="NavigationAgent2D" parent="."]
path_desired_distance = 4.0 path_desired_distance = 16.0
target_desired_distance = 4.0 target_desired_distance = 4.0
path_max_distance = 5000.0
path_postprocessing = 1 path_postprocessing = 1
radius = 8.0 radius = 8.0
debug_enabled = true debug_enabled = true

View File

@ -10,9 +10,19 @@ public partial class TopBar : PanelContainer
_progressBar = GetNode<ProgressBar>("HBox/YearProgress/ProgressBar"); _progressBar = GetNode<ProgressBar>("HBox/YearProgress/ProgressBar");
} }
// Called every frame. 'delta' is the elapsed time since the previous frame. public void ResetRound(float durationSeconds)
public override void _Process(double delta)
{ {
_progressBar.Value = (_progressBar.Value + 1) % _progressBar.MaxValue; if (_progressBar == null) return;
_progressBar.MinValue = 0;
_progressBar.MaxValue = durationSeconds;
_progressBar.Value = 0;
}
public void UpdateRoundProgress(float elapsedSeconds, float durationSeconds)
{
if (_progressBar == null) return;
_progressBar.MinValue = 0;
_progressBar.MaxValue = durationSeconds;
_progressBar.Value = Mathf.Clamp(elapsedSeconds, 0, durationSeconds);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
uid://bd1bjq6vrs4hh

View File

@ -0,0 +1,271 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using Godot;
using Models;
/// <summary>
/// 校园行为系统使用的位置标识符。
/// 这些标识符映射到 campus.tscn 中的 Node2D 标记点,以便 AI 可以通过名称选择目标。
/// </summary>
public enum CampusLocationId
{
None,
Laboratory, // 实验室
Library, // 图书馆
Canteen, // 食堂
Dormitory, // 宿舍
ArtificialLake, // 人工湖
CoffeeShop, // 咖啡店
AdministrationBuilding, // 行政楼
FootballField, // 足球场
RandomWander // 随机漫游
}
/// <summary>
/// 行为规划器和状态机使用的动作标识符。
/// 每个动作通过 campus_behavior.json 配置持续时间和属性变化。
/// </summary>
public enum CampusActionId
{
None,
Experimenting, // 做实验
Writing, // 写作
Eating, // 吃饭
Sleeping, // 睡觉
Chilling, // 放松/闲逛
Staring, // 发呆
CoffeeBreak, // 喝咖啡
Administration, // 行政工作
Running, // 跑步
Socializing, // 社交
Wandering // 漫步
}
/// <summary>
/// 优先级级别,对应设计文档中的顺序:值越小优先级越高。
/// </summary>
public enum CampusBehaviorPriority
{
Critical = 0, // 紧急状态(崩溃/力竭)
AssignedTask = 1, // 指派任务
Needs = 2, // 基础需求(饿/累/社交)
Trait = 3, // 特质驱动(性格偏好)
Idle = 4 // 闲置
}
/// <summary>
/// 校园演示的最小化任务类型。这些不是完整的游戏任务,
/// 仅用于驱动 AI 的“指派任务”优先级。
/// </summary>
public enum CampusTaskType
{
Experiment, // 实验
Writing, // 写作
Administration, // 行政
Exercise, // 锻炼
Coding, // 编程
Social // 社交
}
/// <summary>
/// 从 JSON 加载的动作配置。
/// 变化量Delta在动作运行时按秒应用因此动作越长积累的效果越多。
/// </summary>
public sealed class CampusActionConfig
{
public CampusActionId ActionId { get; set; }
public CampusLocationId LocationId { get; set; }
public float DurationSeconds { get; set; }
public float HungerDelta { get; set; }
public float EnergyDelta { get; set; }
public float StaminaDelta { get; set; }
public float StressDelta { get; set; }
public float MoodDelta { get; set; }
public float SocialDelta { get; set; }
public float SanityDelta { get; set; }
public float HealthDelta { get; set; }
}
/// <summary>
/// 校园 AI 的全局行为配置。
/// 这是数据驱动的,以便在不修改代码的情况下通过 JSON 进行平衡调整。
/// </summary>
public sealed class CampusBehaviorConfig
{
public float CriticalSanityThreshold { get; set; } = 15f;
public float CriticalStaminaThreshold { get; set; } = 12f;
public float CriticalStressThreshold { get; set; } = 90f;
public float HungerThreshold { get; set; } = 30f;
public float EnergyThreshold { get; set; } = 25f;
public float SocialThreshold { get; set; } = 35f;
public float LowMoodThreshold { get; set; } = 25f;
public float HungerDecayPerSecond { get; set; } = 0.6f;
public float EnergyDecayPerSecond { get; set; } = 0.5f;
public float StaminaDecayPerSecond { get; set; } = 0.4f;
public float StressGrowthPerSecond { get; set; } = 0.45f;
public float SocialDecayPerSecond { get; set; } = 0.35f;
public float DecisionIntervalSeconds { get; set; } = 0.5f;
public float ActionDurationVariance { get; set; } = 0.25f;
public float MinPlannedActionSeconds { get; set; } = 2.0f;
public List<CampusActionConfig> ActionConfigs { get; set; } = new();
private readonly Dictionary<CampusActionId, CampusActionConfig> _actionLookup = new();
public CampusActionConfig GetActionConfig(CampusActionId id)
{
if (_actionLookup.Count == 0)
{
BuildLookup();
}
return _actionLookup.TryGetValue(id, out var config) ? config : null;
}
private void BuildLookup()
{
_actionLookup.Clear();
if (ActionConfigs == null) return;
foreach (var config in ActionConfigs)
{
_actionLookup[config.ActionId] = config;
}
}
public static CampusBehaviorConfig Load(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
GD.PushWarning("Campus behavior config path is empty; using defaults.");
return new CampusBehaviorConfig();
}
var resolvedPath = path.StartsWith("res://") || path.StartsWith("user://")
? ProjectSettings.GlobalizePath(path)
: path;
if (!File.Exists(resolvedPath))
{
GD.PushWarning($"Campus behavior config not found at {resolvedPath}; using defaults.");
return new CampusBehaviorConfig();
}
var json = File.ReadAllText(resolvedPath);
if (string.IsNullOrWhiteSpace(json))
{
GD.PushWarning($"Campus behavior config is empty at {resolvedPath}; using defaults.");
return new CampusBehaviorConfig();
}
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
options.Converters.Add(new JsonStringEnumConverter());
try
{
var config = JsonSerializer.Deserialize<CampusBehaviorConfig>(json, options);
return config ?? new CampusBehaviorConfig();
}
catch (Exception ex)
{
GD.PushWarning($"Failed to parse campus behavior config: {ex.Message}");
return new CampusBehaviorConfig();
}
}
}
/// <summary>
/// 简单的位置注册表,将逻辑位置 ID 映射到场景位置。
/// 保持行为系统独立于场景树细节。
/// </summary>
public sealed class CampusLocationRegistry
{
private readonly Dictionary<CampusLocationId, Vector2> _locations = new();
public void Register(CampusLocationId id, Vector2 position)
{
if (id == CampusLocationId.None) return;
_locations[id] = position;
}
public bool TryGetPosition(CampusLocationId id, out Vector2 position)
{
return _locations.TryGetValue(id, out position);
}
}
/// <summary>
/// 跟踪每个位置的当前占用情况,以便像社交恐惧症这样的特质可以根据人群规模做出反应,
/// 而无需硬编码场景知识。
/// </summary>
public sealed class CampusBehaviorWorld
{
private readonly Dictionary<CampusLocationId, int> _occupancy = new();
public void Clear()
{
_occupancy.Clear();
}
public void AddOccupant(CampusLocationId id)
{
if (id == CampusLocationId.None || id == CampusLocationId.RandomWander) return;
if (!_occupancy.ContainsKey(id))
{
_occupancy[id] = 0;
}
_occupancy[id] += 1;
}
public int GetOccupancy(CampusLocationId id)
{
return _occupancy.TryGetValue(id, out var count) ? count : 0;
}
}
/// <summary>
/// 校园演示的轻量级任务容器;它只跟踪剩余工作量。
/// </summary>
public sealed class CampusTask
{
public CampusTaskType Type { get; }
public float RemainingSeconds { get; private set; }
public CampusTask(CampusTaskType type, float remainingSeconds)
{
Type = type;
RemainingSeconds = Mathf.Max(0f, remainingSeconds);
}
public void Advance(float delta)
{
RemainingSeconds = Mathf.Max(0f, RemainingSeconds - delta);
}
public bool IsComplete => RemainingSeconds <= 0f;
}
/// <summary>
/// 自定义需求,尚未成为核心 UnitModel 的一部分(饥饿/社交/精力)。
/// 使用 PropertyValue 以便接入现有的数值系统。
/// </summary>
public sealed class CampusAgentNeeds
{
public PropertyValue Hunger { get; }
public PropertyValue Energy { get; }
public PropertyValue Social { get; }
public PropertyValue Health { get; }
public CampusAgentNeeds(float hunger, float energy, float social, float health)
{
Hunger = new PropertyValue(hunger);
Energy = new PropertyValue(energy);
Social = new PropertyValue(social);
Health = new PropertyValue(health);
}
}

View File

@ -0,0 +1 @@
uid://dbp5g1aqtlqhi

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,15 @@ namespace Core;
[GlobalClass] [GlobalClass]
public partial class ContentCollectionResource : Resource, IContentResourceCollection public partial class ContentCollectionResource : Resource, IContentResourceCollection
{ {
/// <summary>
/// 资源列表
/// </summary>
[Export] public Array<Resource> Items { get; set; } = new(); [Export] public Array<Resource> Items { get; set; } = new();
/// <summary>
/// 获取资源项
/// </summary>
/// <returns>资源项集合</returns>
public IEnumerable<IContentResource> GetItems() public IEnumerable<IContentResource> GetItems()
{ {
foreach (var item in Items) foreach (var item in Items)
@ -23,4 +30,3 @@ public partial class ContentCollectionResource : Resource, IContentResourceColle
} }
} }
} }

View File

@ -0,0 +1 @@
uid://ctbmcynvl8ffm

View File

@ -20,27 +20,52 @@ namespace Core;
/// </summary> /// </summary>
public interface IContentSource public interface IContentSource
{ {
/// <summary>
/// 优先级
/// </summary>
int Priority { get; } int Priority { get; }
/// <summary>
/// 加载所有指定类型的对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <returns>对象集合</returns>
IEnumerable<T> LoadAll<T>() where T : class; IEnumerable<T> LoadAll<T>() where T : class;
} }
/// <summary>
/// 内容合并模式
/// </summary>
public enum ContentMergeMode public enum ContentMergeMode
{ {
Override, Override, // 覆盖
KeepFirst KeepFirst // 保留首次
} }
/// <summary>
/// 内容注册表
/// </summary>
public sealed class ContentRegistry public sealed class ContentRegistry
{ {
private readonly List<IContentSource> _sources = new(); private readonly List<IContentSource> _sources = new();
/// <summary>
/// 合并模式
/// </summary>
public ContentMergeMode MergeMode { get; set; } = ContentMergeMode.Override; public ContentMergeMode MergeMode { get; set; } = ContentMergeMode.Override;
/// <summary>
/// 注册内容源
/// </summary>
/// <param name="source">内容源</param>
public void RegisterSource(IContentSource source) public void RegisterSource(IContentSource source)
{ {
_sources.Add(source); _sources.Add(source);
_sources.Sort((a, b) => a.Priority.CompareTo(b.Priority)); _sources.Sort((a, b) => a.Priority.CompareTo(b.Priority));
} }
/// <summary>
/// 构建游戏内容数据库
/// </summary>
/// <returns>游戏内容数据库</returns>
public GameContentDatabase BuildDatabase() public GameContentDatabase BuildDatabase()
{ {
var db = new GameContentDatabase(); var db = new GameContentDatabase();
@ -55,6 +80,11 @@ public sealed class ContentRegistry
return db; return db;
} }
/// <summary>
/// 从所有源加载指定类型的所有对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <returns>对象集合</returns>
private IEnumerable<T> LoadAll<T>() where T : class private IEnumerable<T> LoadAll<T>() where T : class
{ {
foreach (var source in _sources) foreach (var source in _sources)
@ -66,6 +96,13 @@ public sealed class ContentRegistry
} }
} }
/// <summary>
/// 合并对象到目标字典
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="target">目标字典</param>
/// <param name="items">对象集合</param>
/// <param name="idSelector">ID选择器</param>
private void Merge<T>(Dictionary<string, T> target, IEnumerable<T> items, Func<T, string> idSelector) where T : class private void Merge<T>(Dictionary<string, T> target, IEnumerable<T> items, Func<T, string> idSelector) where T : class
{ {
foreach (var item in items) foreach (var item in items)
@ -97,14 +134,29 @@ public sealed class ContentRegistry
/// </summary> /// </summary>
public sealed class ResourceContentSource : IContentSource public sealed class ResourceContentSource : IContentSource
{ {
/// <summary>
/// 优先级
/// </summary>
public int Priority { get; } public int Priority { get; }
/// <summary>
/// 资源路径列表
/// </summary>
public List<string> ResourcePaths { get; } = new(); public List<string> ResourcePaths { get; } = new();
/// <summary>
/// 构造函数
/// </summary>
/// <param name="priority">优先级</param>
public ResourceContentSource(int priority) public ResourceContentSource(int priority)
{ {
Priority = priority; Priority = priority;
} }
/// <summary>
/// 加载所有指定类型的对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <returns>对象集合</returns>
public IEnumerable<T> LoadAll<T>() where T : class public IEnumerable<T> LoadAll<T>() where T : class
{ {
foreach (var path in ResourcePaths) foreach (var path in ResourcePaths)
@ -127,6 +179,12 @@ public sealed class ResourceContentSource : IContentSource
} }
} }
/// <summary>
/// 从资源中提取对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="resource">资源</param>
/// <returns>对象集合</returns>
private IEnumerable<T> ExtractResources<T>(Resource resource) where T : class private IEnumerable<T> ExtractResources<T>(Resource resource) where T : class
{ {
if (resource is IContentResource content) if (resource is IContentResource content)
@ -163,10 +221,20 @@ public sealed class ResourceContentSource : IContentSource
/// </summary> /// </summary>
public sealed class JsonContentSource : IContentSource public sealed class JsonContentSource : IContentSource
{ {
/// <summary>
/// 优先级
/// </summary>
public int Priority { get; } public int Priority { get; }
/// <summary>
/// 数据路径列表
/// </summary>
public List<string> DataPaths { get; } = new(); public List<string> DataPaths { get; } = new();
private readonly JsonSerializerOptions _options; private readonly JsonSerializerOptions _options;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="priority">优先级</param>
public JsonContentSource(int priority) public JsonContentSource(int priority)
{ {
Priority = priority; Priority = priority;
@ -177,6 +245,11 @@ public sealed class JsonContentSource : IContentSource
_options.Converters.Add(new JsonStringEnumConverter()); _options.Converters.Add(new JsonStringEnumConverter());
} }
/// <summary>
/// 加载所有指定类型的对象
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <returns>对象集合</returns>
public IEnumerable<T> LoadAll<T>() where T : class public IEnumerable<T> LoadAll<T>() where T : class
{ {
foreach (var path in DataPaths) foreach (var path in DataPaths)
@ -232,6 +305,11 @@ public sealed class JsonContentSource : IContentSource
} }
} }
/// <summary>
/// 解析路径
/// </summary>
/// <param name="path">路径</param>
/// <returns>解析后的绝对路径</returns>
private string ResolvePath(string path) private string ResolvePath(string path)
{ {
if (path.StartsWith("res://") || path.StartsWith("user://")) if (path.StartsWith("res://") || path.StartsWith("user://"))
@ -242,6 +320,9 @@ public sealed class JsonContentSource : IContentSource
return path; return path;
} }
/// <summary>
/// 尝试反序列化列表
/// </summary>
private bool TryDeserializeList<T>(string json, out List<T> list) where T : class private bool TryDeserializeList<T>(string json, out List<T> list) where T : class
{ {
try try
@ -256,6 +337,9 @@ public sealed class JsonContentSource : IContentSource
} }
} }
/// <summary>
/// 尝试反序列化单个对象
/// </summary>
private bool TryDeserializeSingle<T>(string json, out T item) where T : class private bool TryDeserializeSingle<T>(string json, out T item) where T : class
{ {
try try
@ -270,6 +354,9 @@ public sealed class JsonContentSource : IContentSource
} }
} }
/// <summary>
/// 尝试反序列化信封结构
/// </summary>
private bool TryDeserializeEnvelope<T>(string json, out JsonContentEnvelope<T> envelope) where T : class private bool TryDeserializeEnvelope<T>(string json, out JsonContentEnvelope<T> envelope) where T : class
{ {
try try
@ -296,4 +383,3 @@ public sealed class JsonContentSource : IContentSource
public T Item { get; set; } public T Item { get; set; }
} }
} }

View File

@ -0,0 +1 @@
uid://fpwckd80v5nv

View File

@ -15,13 +15,26 @@ namespace Core;
/// </summary> /// </summary>
public interface IContentResource public interface IContentResource
{ {
/// <summary>
/// 获取定义类型
/// </summary>
/// <returns>类型</returns>
Type GetDefinitionType(); Type GetDefinitionType();
/// <summary>
/// 转换为定义对象
/// </summary>
/// <returns>定义对象</returns>
object ToDefinition(); object ToDefinition();
} }
/// <summary>
/// 资源集合接口
/// </summary>
public interface IContentResourceCollection public interface IContentResourceCollection
{ {
/// <summary>
/// 获取资源项集合
/// </summary>
/// <returns>资源项集合</returns>
IEnumerable<IContentResource> GetItems(); IEnumerable<IContentResource> GetItems();
} }

View File

@ -0,0 +1 @@
uid://bkgwb8oeer5ri

View File

@ -20,28 +20,81 @@ namespace Core;
public partial class DisciplineDefinitionResource : Resource, IContentResource public partial class DisciplineDefinitionResource : Resource, IContentResource
{ {
// --- Header --- // --- Header ---
/// <summary>
/// 学科ID
/// </summary>
[Export] public string Id { get; set; } [Export] public string Id { get; set; }
/// <summary>
/// 名称键值
/// </summary>
[Export] public string NameKey { get; set; } [Export] public string NameKey { get; set; }
/// <summary>
/// 名称默认值
/// </summary>
[Export] public string NameFallback { get; set; } [Export] public string NameFallback { get; set; }
/// <summary>
/// 描述键值
/// </summary>
[Export] public string DescriptionKey { get; set; } [Export] public string DescriptionKey { get; set; }
/// <summary>
/// 描述默认值
/// </summary>
[Export] public string DescriptionFallback { get; set; } [Export] public string DescriptionFallback { get; set; }
/// <summary>
/// 图标路径
/// </summary>
[Export] public string IconPath { get; set; } [Export] public string IconPath { get; set; }
/// <summary>
/// 标签列表
/// </summary>
[Export] public Array<string> Tags { get; set; } = new(); [Export] public Array<string> Tags { get; set; } = new();
// --- Buff --- // --- Buff ---
/// <summary>
/// Buff名称键值
/// </summary>
[Export] public string BuffNameKey { get; set; } [Export] public string BuffNameKey { get; set; }
/// <summary>
/// Buff名称默认值
/// </summary>
[Export] public string BuffNameFallback { get; set; } [Export] public string BuffNameFallback { get; set; }
/// <summary>
/// Buff描述键值
/// </summary>
[Export] public string BuffDescriptionKey { get; set; } [Export] public string BuffDescriptionKey { get; set; }
/// <summary>
/// Buff描述默认值
/// </summary>
[Export] public string BuffDescriptionFallback { get; set; } [Export] public string BuffDescriptionFallback { get; set; }
/// <summary>
/// Buff规则ID列表
/// </summary>
[Export] public Array<string> BuffRuleIds { get; set; } = new(); [Export] public Array<string> BuffRuleIds { get; set; } = new();
// --- Pools --- // --- Pools ---
/// <summary>
/// 角色池ID列表
/// </summary>
[Export] public Array<string> RolePoolIds { get; set; } = new(); [Export] public Array<string> RolePoolIds { get; set; } = new();
/// <summary>
/// 物品池ID列表
/// </summary>
[Export] public Array<string> ItemPoolIds { get; set; } = new(); [Export] public Array<string> ItemPoolIds { get; set; } = new();
/// <summary>
/// 任务关键词ID列表
/// </summary>
[Export] public Array<string> TaskKeywordIds { get; set; } = new(); [Export] public Array<string> TaskKeywordIds { get; set; } = new();
/// <summary>
/// 获取定义类型
/// </summary>
/// <returns>类型</returns>
public Type GetDefinitionType() => typeof(DisciplineDefinition); public Type GetDefinitionType() => typeof(DisciplineDefinition);
/// <summary>
/// 转换为定义对象
/// </summary>
/// <returns>定义对象</returns>
public object ToDefinition() public object ToDefinition()
{ {
var header = new DefinitionHeader var header = new DefinitionHeader
@ -98,6 +151,11 @@ public partial class DisciplineDefinitionResource : Resource, IContentResource
return definition; return definition;
} }
/// <summary>
/// 添加范围
/// </summary>
/// <param name="source">源数组</param>
/// <param name="target">目标列表</param>
private static void AddRange(Array<string> source, List<string> target) private static void AddRange(Array<string> source, List<string> target)
{ {
foreach (var value in source) foreach (var value in source)
@ -106,4 +164,3 @@ public partial class DisciplineDefinitionResource : Resource, IContentResource
} }
} }
} }

View File

@ -0,0 +1 @@
uid://bfehg7ijcybie

View File

@ -14,31 +14,57 @@ namespace Core;
/// </summary> /// </summary>
public readonly struct TaskCompletedEvent public readonly struct TaskCompletedEvent
{ {
/// <summary>
/// 完成的任务
/// </summary>
public TaskModel Task { get; } public TaskModel Task { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="task">任务</param>
public TaskCompletedEvent(TaskModel task) public TaskCompletedEvent(TaskModel task)
{ {
Task = task; Task = task;
} }
} }
/// <summary>
/// 任务失败事件
/// </summary>
public readonly struct TaskFailedEvent public readonly struct TaskFailedEvent
{ {
/// <summary>
/// 失败的任务
/// </summary>
public TaskModel Task { get; } public TaskModel Task { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="task">任务</param>
public TaskFailedEvent(TaskModel task) public TaskFailedEvent(TaskModel task)
{ {
Task = task; Task = task;
} }
} }
/// <summary>
/// 回合结束事件
/// </summary>
public readonly struct TurnEndedEvent public readonly struct TurnEndedEvent
{ {
/// <summary>
/// 结束的回合数
/// </summary>
public int Turn { get; } public int Turn { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="turn">回合数</param>
public TurnEndedEvent(int turn) public TurnEndedEvent(int turn)
{ {
Turn = turn; Turn = turn;
} }
} }

View File

@ -0,0 +1 @@
uid://drbjfxehidfrn

View File

@ -17,6 +17,11 @@ public sealed class DomainEventBus
{ {
private readonly Dictionary<Type, List<Delegate>> _handlers = new(); private readonly Dictionary<Type, List<Delegate>> _handlers = new();
/// <summary>
/// 订阅事件
/// </summary>
/// <typeparam name="T">事件类型</typeparam>
/// <param name="handler">事件处理器</param>
public void Subscribe<T>(Action<T> handler) public void Subscribe<T>(Action<T> handler)
{ {
var type = typeof(T); var type = typeof(T);
@ -29,6 +34,11 @@ public sealed class DomainEventBus
list.Add(handler); list.Add(handler);
} }
/// <summary>
/// 取消订阅事件
/// </summary>
/// <typeparam name="T">事件类型</typeparam>
/// <param name="handler">事件处理器</param>
public void Unsubscribe<T>(Action<T> handler) public void Unsubscribe<T>(Action<T> handler)
{ {
var type = typeof(T); var type = typeof(T);
@ -38,6 +48,11 @@ public sealed class DomainEventBus
} }
} }
/// <summary>
/// 发布事件
/// </summary>
/// <typeparam name="T">事件类型</typeparam>
/// <param name="evt">事件对象</param>
public void Publish<T>(T evt) public void Publish<T>(T evt)
{ {
var type = typeof(T); var type = typeof(T);
@ -55,4 +70,3 @@ public sealed class DomainEventBus
} }
} }
} }

View File

@ -0,0 +1 @@
uid://dmhvn0hu7qlne

View File

@ -16,23 +16,36 @@ public sealed class GameController : IController
{ {
private GameSession _session; private GameSession _session;
/// <summary>
/// 初始化控制器
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session) public void Initialize(GameSession session)
{ {
_session = session; _session = session;
} }
/// <summary>
/// 开始执行阶段
/// </summary>
public void StartExecution() public void StartExecution()
{ {
if (_session.State.Turn.Phase != GamePhase.Planning) return; if (_session.State.Turn.Phase != GamePhase.Planning) return;
_session.State.Turn.Phase = GamePhase.Execution; _session.State.Turn.Phase = GamePhase.Execution;
} }
/// <summary>
/// 结束执行阶段
/// </summary>
public void EndExecution() public void EndExecution()
{ {
if (_session.State.Turn.Phase != GamePhase.Execution) return; if (_session.State.Turn.Phase != GamePhase.Execution) return;
_session.State.Turn.Phase = GamePhase.Review; _session.State.Turn.Phase = GamePhase.Review;
} }
/// <summary>
/// 开始下一回合
/// </summary>
public void StartNextTurn() public void StartNextTurn()
{ {
if (_session.State.Turn.Phase != GamePhase.Review) return; if (_session.State.Turn.Phase != GamePhase.Review) return;
@ -40,4 +53,3 @@ public sealed class GameController : IController
_session.State.Turn.Phase = GamePhase.Planning; _session.State.Turn.Phase = GamePhase.Planning;
} }
} }

View File

@ -0,0 +1 @@
uid://cqw1q6qv873he

View File

@ -14,12 +14,34 @@ namespace Core;
/// </summary> /// </summary>
public sealed class GameSession public sealed class GameSession
{ {
/// <summary>
/// 游戏状态
/// </summary>
public GameState State { get; } public GameState State { get; }
/// <summary>
/// 游戏内容数据库
/// </summary>
public GameContentDatabase Content { get; } public GameContentDatabase Content { get; }
/// <summary>
/// 领域事件总线
/// </summary>
public DomainEventBus Events { get; } public DomainEventBus Events { get; }
/// <summary>
/// 本地化服务
/// </summary>
public ILocalizationService Localization { get; } public ILocalizationService Localization { get; }
/// <summary>
/// 游戏系统集合
/// </summary>
public GameSystems Systems { get; } public GameSystems Systems { get; }
/// <summary>
/// 构造游戏会话
/// </summary>
/// <param name="state">游戏状态</param>
/// <param name="content">内容数据库</param>
/// <param name="localization">本地化服务</param>
/// <param name="events">事件总线</param>
public GameSession(GameState state, GameContentDatabase content, ILocalizationService localization, DomainEventBus events) public GameSession(GameState state, GameContentDatabase content, ILocalizationService localization, DomainEventBus events)
{ {
State = state; State = state;
@ -30,6 +52,10 @@ public sealed class GameSession
Systems.Initialize(this); Systems.Initialize(this);
} }
/// <summary>
/// 创建默认游戏会话
/// </summary>
/// <returns>游戏会话实例</returns>
public static GameSession CreateDefault() public static GameSession CreateDefault()
{ {
var registry = new ContentRegistry(); var registry = new ContentRegistry();
@ -40,6 +66,8 @@ public sealed class GameSession
var jsonSource = new JsonContentSource(10); var jsonSource = new JsonContentSource(10);
jsonSource.DataPaths.Add("res://resources/definitions/disciplines.json"); jsonSource.DataPaths.Add("res://resources/definitions/disciplines.json");
jsonSource.DataPaths.Add("res://resources/definitions/archetypes.json"); jsonSource.DataPaths.Add("res://resources/definitions/archetypes.json");
jsonSource.DataPaths.Add("res://resources/definitions/roles.json");
jsonSource.DataPaths.Add("res://resources/definitions/traits.json");
registry.RegisterSource(jsonSource); registry.RegisterSource(jsonSource);
var content = registry.BuildDatabase(); var content = registry.BuildDatabase();
@ -48,9 +76,12 @@ public sealed class GameSession
return new GameSession(new GameState(), content, localization, events); return new GameSession(new GameState(), content, localization, events);
} }
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta) public void Tick(float delta)
{ {
Systems.Tick(delta); Systems.Tick(delta);
} }
} }

View File

@ -0,0 +1 @@
uid://cltmymdid63wi

View File

@ -16,18 +16,48 @@ namespace Core;
/// </summary> /// </summary>
public interface IGameSystem public interface IGameSystem
{ {
/// <summary>
/// 初始化系统
/// </summary>
/// <param name="session">游戏会话</param>
void Initialize(GameSession session); void Initialize(GameSession session);
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
void Tick(float delta); void Tick(float delta);
} }
/// <summary>
/// 游戏系统管理器
/// </summary>
public sealed class GameSystems public sealed class GameSystems
{ {
/// <summary>
/// 回合系统
/// </summary>
public TurnSystem Turn { get; } = new(); public TurnSystem Turn { get; } = new();
/// <summary>
/// 任务系统
/// </summary>
public TaskSystem Task { get; } = new(); public TaskSystem Task { get; } = new();
/// <summary>
/// 经济系统
/// </summary>
public EconomySystem Economy { get; } = new(); public EconomySystem Economy { get; } = new();
/// <summary>
/// 羁绊/协同系统
/// </summary>
public SynergySystem Synergy { get; } = new(); public SynergySystem Synergy { get; } = new();
/// <summary>
/// 分配系统
/// </summary>
public AssignmentSystem Assignment { get; } = new(); public AssignmentSystem Assignment { get; } = new();
/// <summary>
/// 初始化所有系统
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session) public void Initialize(GameSession session)
{ {
Turn.Initialize(session); Turn.Initialize(session);
@ -37,6 +67,10 @@ public sealed class GameSystems
Assignment.Initialize(session); Assignment.Initialize(session);
} }
/// <summary>
/// 更新所有系统
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta) public void Tick(float delta)
{ {
Turn.Tick(delta); Turn.Tick(delta);
@ -47,32 +81,54 @@ public sealed class GameSystems
} }
} }
/// <summary>
/// 回合系统
/// </summary>
public sealed class TurnSystem : IGameSystem public sealed class TurnSystem : IGameSystem
{ {
private GameSession _session; private GameSession _session;
/// <summary>
/// 初始化
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session) public void Initialize(GameSession session)
{ {
_session = session; _session = session;
} }
/// <summary>
/// 更新
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta) public void Tick(float delta)
{ {
// 预留:回合推进计时器/阶段切换 // 预留:回合推进计时器/阶段切换
} }
} }
/// <summary>
/// 任务系统
/// </summary>
public sealed class TaskSystem : IGameSystem public sealed class TaskSystem : IGameSystem
{ {
private GameSession _session; private GameSession _session;
private StatResolver _statResolver; private StatResolver _statResolver;
/// <summary>
/// 初始化
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session) public void Initialize(GameSession session)
{ {
_session = session; _session = session;
_statResolver = new StatResolver(session); _statResolver = new StatResolver(session);
} }
/// <summary>
/// 更新
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta) public void Tick(float delta)
{ {
if (_session.State.Turn.Phase != GamePhase.Execution) if (_session.State.Turn.Phase != GamePhase.Execution)
@ -124,6 +180,10 @@ public sealed class TaskSystem : IGameSystem
} }
} }
/// <summary>
/// 推进任务进度
/// </summary>
/// <param name="delta">时间间隔</param>
private void AdvanceTasks(float delta) private void AdvanceTasks(float delta)
{ {
var state = _session.State; var state = _session.State;
@ -157,6 +217,9 @@ public sealed class TaskSystem : IGameSystem
} }
} }
/// <summary>
/// 获取单位对任务的贡献
/// </summary>
private float GetUnitContribution(UnitEntry entry, TaskModel task, TaskDefinition taskDef, float delta) private float GetUnitContribution(UnitEntry entry, TaskModel task, TaskDefinition taskDef, float delta)
{ {
var unit = entry.Unit; var unit = entry.Unit;
@ -173,6 +236,9 @@ public sealed class TaskSystem : IGameSystem
return effectivePower; return effectivePower;
} }
/// <summary>
/// 获取任务基础效率
/// </summary>
private float GetTaskBasePower(UnitModel unit, TaskKind kind) private float GetTaskBasePower(UnitModel unit, TaskKind kind)
{ {
var academic = _statResolver.GetAttribute(unit, AttributeType.Academic); var academic = _statResolver.GetAttribute(unit, AttributeType.Academic);
@ -194,6 +260,9 @@ public sealed class TaskSystem : IGameSystem
}; };
} }
/// <summary>
/// 获取状态乘数
/// </summary>
private float GetStatusMultiplier(UnitEntry entry) private float GetStatusMultiplier(UnitEntry entry)
{ {
var mood = entry.Unit.Statuses.Mood.Normalized; var mood = entry.Unit.Statuses.Mood.Normalized;
@ -213,6 +282,9 @@ public sealed class TaskSystem : IGameSystem
return Math.Clamp(multiplier, 0.3f, 1.2f); return Math.Clamp(multiplier, 0.3f, 1.2f);
} }
/// <summary>
/// 获取角色乘数
/// </summary>
private float GetRoleMultiplier(UnitModel unit, TaskDefinition taskDef) private float GetRoleMultiplier(UnitModel unit, TaskDefinition taskDef)
{ {
if (taskDef == null) return 1.0f; if (taskDef == null) return 1.0f;
@ -246,6 +318,9 @@ public sealed class TaskSystem : IGameSystem
return 1.0f; return 1.0f;
} }
/// <summary>
/// 获取需求乘数
/// </summary>
private float GetRequirementMultiplier(UnitModel unit, TaskDefinition taskDef) private float GetRequirementMultiplier(UnitModel unit, TaskDefinition taskDef)
{ {
if (taskDef == null) return 1.0f; if (taskDef == null) return 1.0f;
@ -263,6 +338,9 @@ public sealed class TaskSystem : IGameSystem
return multiplier; return multiplier;
} }
/// <summary>
/// 获取学科乘数
/// </summary>
private float GetDisciplineMultiplier(UnitModel unit, TaskDefinition taskDef) private float GetDisciplineMultiplier(UnitModel unit, TaskDefinition taskDef)
{ {
if (taskDef == null) return 1.0f; if (taskDef == null) return 1.0f;
@ -280,6 +358,9 @@ public sealed class TaskSystem : IGameSystem
return 0.7f; return 0.7f;
} }
/// <summary>
/// 获取难度系数
/// </summary>
private float GetDifficultyScale(TaskDifficulty difficulty) private float GetDifficultyScale(TaskDifficulty difficulty)
{ {
return difficulty switch return difficulty switch
@ -292,12 +373,18 @@ public sealed class TaskSystem : IGameSystem
}; };
} }
/// <summary>
/// 获取任务定义
/// </summary>
private TaskDefinition GetTaskDefinition(TaskModel task) private TaskDefinition GetTaskDefinition(TaskModel task)
{ {
if (string.IsNullOrWhiteSpace(task.DefinitionId)) return null; if (string.IsNullOrWhiteSpace(task.DefinitionId)) return null;
return _session.Content.Tasks.TryGetValue(task.DefinitionId, out var definition) ? definition : null; return _session.Content.Tasks.TryGetValue(task.DefinitionId, out var definition) ? definition : null;
} }
/// <summary>
/// 构建单位索引
/// </summary>
private Dictionary<Guid, UnitEntry> BuildUnitIndex() private Dictionary<Guid, UnitEntry> BuildUnitIndex()
{ {
var index = new Dictionary<Guid, UnitEntry>(); var index = new Dictionary<Guid, UnitEntry>();
@ -324,6 +411,9 @@ public sealed class TaskSystem : IGameSystem
return index; return index;
} }
/// <summary>
/// 追踪贡献
/// </summary>
private void TrackContribution(UnitEntry entry, TaskModel task, float deltaContribution) private void TrackContribution(UnitEntry entry, TaskModel task, float deltaContribution)
{ {
if (entry.Student == null) return; if (entry.Student == null) return;
@ -355,6 +445,9 @@ public sealed class TaskSystem : IGameSystem
} }
} }
/// <summary>
/// 经济系统
/// </summary>
public sealed class EconomySystem : IGameSystem public sealed class EconomySystem : IGameSystem
{ {
private GameSession _session; private GameSession _session;
@ -363,6 +456,10 @@ public sealed class EconomySystem : IGameSystem
private const int PostDocSalary = 1200; private const int PostDocSalary = 1200;
private const int JuniorFacultySalary = 2000; private const int JuniorFacultySalary = 2000;
/// <summary>
/// 初始化
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session) public void Initialize(GameSession session)
{ {
_session = session; _session = session;
@ -371,11 +468,18 @@ public sealed class EconomySystem : IGameSystem
_session.Events.Subscribe<TurnEndedEvent>(OnTurnEnded); _session.Events.Subscribe<TurnEndedEvent>(OnTurnEnded);
} }
/// <summary>
/// 更新
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta) public void Tick(float delta)
{ {
// 当前为回合驱动,不在 Tick 中结算 // 当前为回合驱动,不在 Tick 中结算
} }
/// <summary>
/// 任务完成回调
/// </summary>
private void OnTaskCompleted(TaskCompletedEvent evt) private void OnTaskCompleted(TaskCompletedEvent evt)
{ {
var task = evt.Task; var task = evt.Task;
@ -404,6 +508,9 @@ public sealed class EconomySystem : IGameSystem
} }
} }
/// <summary>
/// 任务失败回调
/// </summary>
private void OnTaskFailed(TaskFailedEvent evt) private void OnTaskFailed(TaskFailedEvent evt)
{ {
var task = evt.Task; var task = evt.Task;
@ -412,12 +519,18 @@ public sealed class EconomySystem : IGameSystem
_session.State.Economy.Reputation -= penalty; _session.State.Economy.Reputation -= penalty;
} }
/// <summary>
/// 回合结束回调
/// </summary>
private void OnTurnEnded(TurnEndedEvent evt) private void OnTurnEnded(TurnEndedEvent evt)
{ {
ApplySalaries(); ApplySalaries();
ApplyInterest(); ApplyInterest();
} }
/// <summary>
/// 支付薪水
/// </summary>
private void ApplySalaries() private void ApplySalaries()
{ {
var economy = _session.State.Economy; var economy = _session.State.Economy;
@ -436,6 +549,9 @@ public sealed class EconomySystem : IGameSystem
} }
} }
/// <summary>
/// 计算利息
/// </summary>
private void ApplyInterest() private void ApplyInterest()
{ {
var economy = _session.State.Economy; var economy = _session.State.Economy;
@ -446,6 +562,9 @@ public sealed class EconomySystem : IGameSystem
economy.Money += interest; economy.Money += interest;
} }
/// <summary>
/// 更新利率
/// </summary>
private void UpdateInterestRate() private void UpdateInterestRate()
{ {
var economy = _session.State.Economy; var economy = _session.State.Economy;
@ -456,6 +575,9 @@ public sealed class EconomySystem : IGameSystem
} }
} }
/// <summary>
/// 添加论文
/// </summary>
private void AddPaper(PaperRank rank) private void AddPaper(PaperRank rank)
{ {
var inventory = _session.State.Inventory; var inventory = _session.State.Inventory;
@ -467,6 +589,9 @@ public sealed class EconomySystem : IGameSystem
inventory.PaperCounts[rank] += 1; inventory.PaperCounts[rank] += 1;
} }
/// <summary>
/// 添加物品
/// </summary>
private void AddItem(string itemId, int count) private void AddItem(string itemId, int count)
{ {
if (string.IsNullOrWhiteSpace(itemId)) return; if (string.IsNullOrWhiteSpace(itemId)) return;
@ -479,6 +604,9 @@ public sealed class EconomySystem : IGameSystem
inventory.ItemCounts[itemId] += count; inventory.ItemCounts[itemId] += count;
} }
/// <summary>
/// 根据难度获取论文等级
/// </summary>
private PaperRank GetPaperRankByDifficulty(TaskDifficulty difficulty) private PaperRank GetPaperRankByDifficulty(TaskDifficulty difficulty)
{ {
return difficulty switch return difficulty switch
@ -492,20 +620,34 @@ public sealed class EconomySystem : IGameSystem
} }
} }
/// <summary>
/// 羁绊/协同系统
/// </summary>
public sealed class SynergySystem : IGameSystem public sealed class SynergySystem : IGameSystem
{ {
private GameSession _session; private GameSession _session;
/// <summary>
/// 初始化
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session) public void Initialize(GameSession session)
{ {
_session = session; _session = session;
} }
/// <summary>
/// 更新
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta) public void Tick(float delta)
{ {
RecalculateSynergy(); RecalculateSynergy();
} }
/// <summary>
/// 重新计算协同
/// </summary>
private void RecalculateSynergy() private void RecalculateSynergy()
{ {
var state = _session.State.Synergy; var state = _session.State.Synergy;
@ -518,6 +660,9 @@ public sealed class SynergySystem : IGameSystem
ApplySynergyDefinitions(state); ApplySynergyDefinitions(state);
} }
/// <summary>
/// 统计单位标签
/// </summary>
private void CountUnitTags(SynergyState synergy) private void CountUnitTags(SynergyState synergy)
{ {
var roster = _session.State.Roster; var roster = _session.State.Roster;
@ -534,6 +679,9 @@ public sealed class SynergySystem : IGameSystem
} }
} }
/// <summary>
/// 添加单位标签
/// </summary>
private void AddUnitTags(SynergyState synergy, UnitModel unit) private void AddUnitTags(SynergyState synergy, UnitModel unit)
{ {
if (unit == null) return; if (unit == null) return;
@ -548,6 +696,9 @@ public sealed class SynergySystem : IGameSystem
} }
} }
/// <summary>
/// 增加堆叠计数
/// </summary>
private void AddStack(Dictionary<string, int> stacks, string id) private void AddStack(Dictionary<string, int> stacks, string id)
{ {
if (string.IsNullOrWhiteSpace(id)) return; if (string.IsNullOrWhiteSpace(id)) return;
@ -559,6 +710,9 @@ public sealed class SynergySystem : IGameSystem
stacks[id] += 1; stacks[id] += 1;
} }
/// <summary>
/// 应用协同定义
/// </summary>
private void ApplySynergyDefinitions(SynergyState synergy) private void ApplySynergyDefinitions(SynergyState synergy)
{ {
foreach (var archetype in _session.Content.Archetypes.Values) foreach (var archetype in _session.Content.Archetypes.Values)
@ -572,6 +726,9 @@ public sealed class SynergySystem : IGameSystem
} }
} }
/// <summary>
/// 应用协同层级
/// </summary>
private void ApplySynergyTier(string id, List<SynergyTier> tiers, Dictionary<string, int> stacks, SynergyState synergy) private void ApplySynergyTier(string id, List<SynergyTier> tiers, Dictionary<string, int> stacks, SynergyState synergy)
{ {
if (string.IsNullOrWhiteSpace(id)) return; if (string.IsNullOrWhiteSpace(id)) return;
@ -586,6 +743,9 @@ public sealed class SynergySystem : IGameSystem
} }
} }
/// <summary>
/// 合并修正
/// </summary>
private void MergeModifiers(ModifierBundle target, ModifierBundle source) private void MergeModifiers(ModifierBundle target, ModifierBundle source)
{ {
if (target == null || source == null) return; if (target == null || source == null) return;
@ -595,6 +755,9 @@ public sealed class SynergySystem : IGameSystem
target.RuleIds.AddRange(source.RuleIds); target.RuleIds.AddRange(source.RuleIds);
} }
/// <summary>
/// 清除修正
/// </summary>
private void ClearModifiers(ModifierBundle bundle) private void ClearModifiers(ModifierBundle bundle)
{ {
bundle.AttributeModifiers.Clear(); bundle.AttributeModifiers.Clear();
@ -604,18 +767,28 @@ public sealed class SynergySystem : IGameSystem
} }
} }
/// <summary>
/// 分配系统
/// </summary>
public sealed class AssignmentSystem : IGameSystem public sealed class AssignmentSystem : IGameSystem
{ {
private GameSession _session; private GameSession _session;
/// <summary>
/// 初始化
/// </summary>
/// <param name="session">游戏会话</param>
public void Initialize(GameSession session) public void Initialize(GameSession session)
{ {
_session = session; _session = session;
} }
/// <summary>
/// 更新
/// </summary>
/// <param name="delta">帧间隔</param>
public void Tick(float delta) public void Tick(float delta)
{ {
// 预留:人员分配、交接惩罚等 // 预留:人员分配、交接惩罚等
} }
} }

View File

@ -0,0 +1 @@
uid://dg4tya2t3wgel

View File

@ -15,18 +15,43 @@ namespace Core;
/// </summary> /// </summary>
public interface ILocalizationService public interface ILocalizationService
{ {
/// <summary>
/// 翻译本地化文本对象
/// </summary>
/// <param name="text">文本对象</param>
/// <returns>翻译后的字符串</returns>
string Translate(LocalizedText text); string Translate(LocalizedText text);
/// <summary>
/// 翻译键值
/// </summary>
/// <param name="key">键值</param>
/// <param name="fallback">默认值</param>
/// <returns>翻译后的字符串</returns>
string Translate(string key, string fallback = null); string Translate(string key, string fallback = null);
} }
/// <summary>
/// Godot 本地化服务实现
/// </summary>
public sealed class GodotLocalizationService : ILocalizationService public sealed class GodotLocalizationService : ILocalizationService
{ {
/// <summary>
/// 翻译本地化文本对象
/// </summary>
/// <param name="text">文本对象</param>
/// <returns>翻译后的字符串</returns>
public string Translate(LocalizedText text) public string Translate(LocalizedText text)
{ {
if (text == null) return string.Empty; if (text == null) return string.Empty;
return Translate(text.Key, text.Fallback); return Translate(text.Key, text.Fallback);
} }
/// <summary>
/// 翻译键值
/// </summary>
/// <param name="key">键值</param>
/// <param name="fallback">默认值</param>
/// <returns>翻译后的字符串</returns>
public string Translate(string key, string fallback = null) public string Translate(string key, string fallback = null)
{ {
if (string.IsNullOrWhiteSpace(key)) if (string.IsNullOrWhiteSpace(key))
@ -43,4 +68,3 @@ public sealed class GodotLocalizationService : ILocalizationService
return translated; return translated;
} }
} }

View File

@ -0,0 +1 @@
uid://bi61o586eqdyu

View File

@ -14,16 +14,39 @@ namespace Core;
/// </summary> /// </summary>
public sealed class ModManifest public sealed class ModManifest
{ {
/// <summary>
/// Mod ID
/// </summary>
public string Id { get; set; } public string Id { get; set; }
/// <summary>
/// Mod 名称
/// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary>
/// 版本
/// </summary>
public string Version { get; set; } public string Version { get; set; }
/// <summary>
/// 依赖列表
/// </summary>
public List<string> Dependencies { get; } = new(); public List<string> Dependencies { get; } = new();
/// <summary>
/// 内容路径列表
/// </summary>
public List<string> ContentPaths { get; } = new(); public List<string> ContentPaths { get; } = new();
} }
/// <summary>
/// Mod 包
/// </summary>
public sealed class ModPackage public sealed class ModPackage
{ {
/// <summary>
/// 清单
/// </summary>
public ModManifest Manifest { get; set; } = new(); public ModManifest Manifest { get; set; } = new();
/// <summary>
/// 根路径
/// </summary>
public string RootPath { get; set; } public string RootPath { get; set; }
} }

View File

@ -0,0 +1 @@
uid://c7ng0xpd2iul1

View File

@ -14,26 +14,50 @@ namespace Core;
/// </summary> /// </summary>
public interface IView<TModel> public interface IView<TModel>
{ {
/// <summary>
/// 绑定数据模型
/// </summary>
/// <param name="model">数据模型</param>
void Bind(TModel model); void Bind(TModel model);
} }
/// <summary>
/// 控制器接口
/// </summary>
public interface IController public interface IController
{ {
/// <summary>
/// 初始化控制器
/// </summary>
/// <param name="session">游戏会话</param>
void Initialize(GameSession session); void Initialize(GameSession session);
} }
/// <summary>
/// 模型视图基类
/// </summary>
/// <typeparam name="TModel">模型类型</typeparam>
public abstract partial class ModelView<TModel> : Node, IView<TModel> public abstract partial class ModelView<TModel> : Node, IView<TModel>
{ {
/// <summary>
/// 数据模型
/// </summary>
public TModel Model { get; private set; } public TModel Model { get; private set; }
/// <summary>
/// 绑定数据模型
/// </summary>
/// <param name="model">数据模型</param>
public virtual void Bind(TModel model) public virtual void Bind(TModel model)
{ {
Model = model; Model = model;
OnModelBound(); OnModelBound();
} }
/// <summary>
/// 当模型绑定时调用
/// </summary>
protected virtual void OnModelBound() protected virtual void OnModelBound()
{ {
} }
} }

1
scripts/Core/Mvc.cs.uid Normal file
View File

@ -0,0 +1 @@
uid://bqconwrqysw5b

View File

@ -17,11 +17,21 @@ public sealed class StatResolver
{ {
private readonly GameSession _session; private readonly GameSession _session;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="session">游戏会话</param>
public StatResolver(GameSession session) public StatResolver(GameSession session)
{ {
_session = session; _session = session;
} }
/// <summary>
/// 获取计算后的属性值
/// </summary>
/// <param name="unit">单位</param>
/// <param name="type">属性类型</param>
/// <returns>属性值</returns>
public float GetAttribute(UnitModel unit, AttributeType type) public float GetAttribute(UnitModel unit, AttributeType type)
{ {
var value = GetBaseAttribute(unit, type); var value = GetBaseAttribute(unit, type);
@ -32,6 +42,12 @@ public sealed class StatResolver
return value; return value;
} }
/// <summary>
/// 获取基础属性值
/// </summary>
/// <param name="unit">单位</param>
/// <param name="type">属性类型</param>
/// <returns>基础值</returns>
private float GetBaseAttribute(UnitModel unit, AttributeType type) private float GetBaseAttribute(UnitModel unit, AttributeType type)
{ {
return type switch return type switch
@ -46,6 +62,9 @@ public sealed class StatResolver
}; };
} }
/// <summary>
/// 应用学科加成
/// </summary>
private void ApplyDiscipline(string disciplineId, AttributeType type, ref float value) private void ApplyDiscipline(string disciplineId, AttributeType type, ref float value)
{ {
if (string.IsNullOrWhiteSpace(disciplineId)) return; if (string.IsNullOrWhiteSpace(disciplineId)) return;
@ -53,6 +72,9 @@ public sealed class StatResolver
ApplyBundle(discipline.Buff?.Modifiers, type, ref value); ApplyBundle(discipline.Buff?.Modifiers, type, ref value);
} }
/// <summary>
/// 应用特质加成
/// </summary>
private void ApplyTraits(List<string> traitIds, AttributeType type, ref float value) private void ApplyTraits(List<string> traitIds, AttributeType type, ref float value)
{ {
if (traitIds == null || traitIds.Count == 0) return; if (traitIds == null || traitIds.Count == 0) return;
@ -65,6 +87,9 @@ public sealed class StatResolver
} }
} }
/// <summary>
/// 应用装备加成
/// </summary>
private void ApplyItems(List<string> itemIds, AttributeType type, ref float value) private void ApplyItems(List<string> itemIds, AttributeType type, ref float value)
{ {
if (itemIds == null || itemIds.Count == 0) return; if (itemIds == null || itemIds.Count == 0) return;
@ -77,6 +102,9 @@ public sealed class StatResolver
} }
} }
/// <summary>
/// 应用修正包
/// </summary>
private void ApplyBundle(ModifierBundle bundle, AttributeType type, ref float value) private void ApplyBundle(ModifierBundle bundle, AttributeType type, ref float value)
{ {
if (bundle == null) return; if (bundle == null) return;
@ -87,4 +115,3 @@ public sealed class StatResolver
} }
} }
} }

View File

@ -0,0 +1 @@
uid://l1exgbqpbayh

View File

@ -4,14 +4,20 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml; using System.Xml;
/// <summary>
/// 办公格子/图块对象
/// </summary>
public partial class Cube : StaticBody2D, ITileDraggable public partial class Cube : StaticBody2D, ITileDraggable
{ {
// 定义各种家具的占用区域
private static readonly Rect2I tableRect = new(0, 0, 3, 2); private static readonly Rect2I tableRect = new(0, 0, 3, 2);
private static readonly Rect2I table2Rect = new(0, -1, 3, 1); private static readonly Rect2I table2Rect = new(0, -1, 3, 1);
private static readonly Rect2I chairRect = new(1, 1, 1, 2); private static readonly Rect2I chairRect = new(1, 1, 1, 2);
private static readonly Rect2I chair2Rect = new(1, -2, 1, 2); private static readonly Rect2I chair2Rect = new(1, -2, 1, 2);
private static readonly Rect2I equipRect = new(0, 0, 3, 2); private static readonly Rect2I equipRect = new(0, 0, 3, 2);
private static readonly Rect2I equip2Rect = new(0, -1, 3, 1); private static readonly Rect2I equip2Rect = new(0, -1, 3, 1);
// 定义桌子的主题映射
private static readonly TileMapping[] tableThemes = { private static readonly TileMapping[] tableThemes = {
new(new Vector2I(1, 30), tableRect), new(new Vector2I(1, 30), tableRect),
new(new Vector2I(4, 30), tableRect), new(new Vector2I(4, 30), tableRect),
@ -19,6 +25,7 @@ public partial class Cube : StaticBody2D, ITileDraggable
new(new Vector2I(7, 28), tableRect), new(new Vector2I(7, 28), tableRect),
new(new Vector2I(10, 28), tableRect), new(new Vector2I(10, 28), tableRect),
}; };
// 定义第二种桌子的主题映射
private static readonly TileMapping[] table2Themes = { private static readonly TileMapping[] table2Themes = {
new(new Vector2I(1, 31), table2Rect), new(new Vector2I(1, 31), table2Rect),
new(new Vector2I(4, 31), table2Rect), new(new Vector2I(4, 31), table2Rect),
@ -26,12 +33,14 @@ public partial class Cube : StaticBody2D, ITileDraggable
new(new Vector2I(7, 29), table2Rect), new(new Vector2I(7, 29), table2Rect),
new(new Vector2I(10, 29), table2Rect), new(new Vector2I(10, 29), table2Rect),
}; };
// 定义椅子的主题映射
private static readonly TileMapping[] chairThemes = { private static readonly TileMapping[] chairThemes = {
new(new Vector2I(-1, 7), chairRect), new(new Vector2I(-1, 7), chairRect),
new(new Vector2I(0, 7), chairRect), new(new Vector2I(0, 7), chairRect),
new(new Vector2I(-1, 9), chairRect), new(new Vector2I(-1, 9), chairRect),
new(new Vector2I(0, 9), chairRect), new(new Vector2I(0, 9), chairRect),
}; };
// 定义第二种椅子的主题映射
private static readonly TileMapping[] chair2Themes = { private static readonly TileMapping[] chair2Themes = {
new(new Vector2I(-1, 10), chair2Rect), new(new Vector2I(-1, 10), chair2Rect),
new(new Vector2I(0, 10), chair2Rect), new(new Vector2I(0, 10), chair2Rect),
@ -39,12 +48,13 @@ public partial class Cube : StaticBody2D, ITileDraggable
new(new Vector2I(0, 12), chair2Rect), new(new Vector2I(0, 12), chair2Rect),
}; };
// 定义设备的主题映射
private static readonly TileMapping[] equipThemes = { private static readonly TileMapping[] equipThemes = {
// one laptop // 一台笔记本电脑
new(new Vector2I(13, 26), equipRect), new(new Vector2I(13, 26), equipRect),
// one laptop with one monitor // 一台笔记本电脑加一台显示器
new(new Vector2I(7, 26), equipRect), new(new Vector2I(7, 26), equipRect),
// one desktop PC // 一台台式机
new(new Dictionary<Vector2I, Vector2I>() { new(new Dictionary<Vector2I, Vector2I>() {
[new Vector2I(0, 0)] = new Vector2I(11, 17), [new Vector2I(0, 0)] = new Vector2I(11, 17),
[new Vector2I(1, 0)] = new Vector2I(8, 32), [new Vector2I(1, 0)] = new Vector2I(8, 32),
@ -53,30 +63,34 @@ public partial class Cube : StaticBody2D, ITileDraggable
[new Vector2I(1, 1)] = new Vector2I(8, 33), [new Vector2I(1, 1)] = new Vector2I(8, 33),
[new Vector2I(2, 1)] = new Vector2I(9, 33), [new Vector2I(2, 1)] = new Vector2I(9, 33),
}), }),
// one desktop PC with one monitors // 一台台式机加一台显示器
new(new Vector2I(7, 32), equipRect), new(new Vector2I(7, 32), equipRect),
}; };
// 定义第二种设备的主题映射
private static readonly TileMapping[] equip2Themes = { private static readonly TileMapping[] equip2Themes = {
// one laptop // 一台笔记本电脑
new(new Vector2I(13, 31), equip2Rect), new(new Vector2I(13, 31), equip2Rect),
// one laptop with one monitor // 一台笔记本电脑加一台显示器
new(new Dictionary<Vector2I, Vector2I>() { new(new Dictionary<Vector2I, Vector2I>() {
[new Vector2I(0, -1)] = new Vector2I(13, 32), [new Vector2I(0, -1)] = new Vector2I(13, 32),
[new Vector2I(1, -1)] = new Vector2I(10, 7), [new Vector2I(1, -1)] = new Vector2I(10, 7),
[new Vector2I(2, -1)] = new Vector2I(15, 32), [new Vector2I(2, -1)] = new Vector2I(15, 32),
}), }),
// one desktop PC // 一台台式机
new(new Dictionary<Vector2I, Vector2I>() { new(new Dictionary<Vector2I, Vector2I>() {
[new Vector2I(0, -1)] = new Vector2I(13, 32), [new Vector2I(0, -1)] = new Vector2I(13, 32),
[new Vector2I(1, -1)] = new Vector2I(14, 32), [new Vector2I(1, -1)] = new Vector2I(14, 32),
[new Vector2I(2, -1)] = new Vector2I(15, 30), [new Vector2I(2, -1)] = new Vector2I(15, 30),
}), }),
// one desktop PC with one monitors // 一台台式机加一台显示器
new(new Vector2I(13, 33), equip2Rect), new(new Vector2I(13, 33), equip2Rect),
}; };
private bool _draggable; private bool _draggable;
/// <summary>
/// 是否可拖拽
/// </summary>
public bool Draggable { public bool Draggable {
get => _draggable; get => _draggable;
set { set {
@ -86,10 +100,20 @@ public partial class Cube : StaticBody2D, ITileDraggable
} }
private readonly Guid _id = Guid.NewGuid(); private readonly Guid _id = Guid.NewGuid();
/// <summary>
/// 唯一标识符
/// </summary>
public Guid Id => _id; public Guid Id => _id;
/// <summary>
/// 在网格中的位置
/// </summary>
public Vector2I TilePosition { get; set; } = new Vector2I(5,5); public Vector2I TilePosition { get; set; } = new Vector2I(5,5);
private bool _isCollided = true; private bool _isCollided = true;
/// <summary>
/// 是否处于碰撞状态(显示红色背景)
/// </summary>
public bool IsCollided { public bool IsCollided {
get => _isCollided; get => _isCollided;
set { set {
@ -103,9 +127,15 @@ public partial class Cube : StaticBody2D, ITileDraggable
} }
} }
private static readonly Rect2I tileRect = new(-1, -2, 4, 5); private static readonly Rect2I tileRect = new(-1, -2, 4, 5);
/// <summary>
/// 占用矩形区域
/// </summary>
public Rect2I TileRect => tileRect; public Rect2I TileRect => tileRect;
private Vector2I _mouseOffset; private Vector2I _mouseOffset;
/// <summary>
/// 鼠标拖拽偏移
/// </summary>
public Vector2I MouseOffset => _mouseOffset; public Vector2I MouseOffset => _mouseOffset;
private static readonly ITileDraggable.SpecialTile[] specialTiles = { private static readonly ITileDraggable.SpecialTile[] specialTiles = {
@ -114,15 +144,27 @@ public partial class Cube : StaticBody2D, ITileDraggable
}; };
/// <summary>
/// 特殊图块(如座位)
/// </summary>
public ITileDraggable.SpecialTile[] SpecialTiles => specialTiles; public ITileDraggable.SpecialTile[] SpecialTiles => specialTiles;
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
/// <summary>
/// 节点进入场景树时调用
/// </summary>
public override void _Ready() public override void _Ready()
{ {
RandomChangeTheme(); RandomChangeTheme();
IsCollided = false; IsCollided = false;
} }
/// <summary>
/// 处理输入事件
/// </summary>
/// <param name="viewport">视口</param>
/// <param name="event">输入事件</param>
/// <param name="shapeIdx">形状索引</param>
public override void _InputEvent(Viewport viewport, InputEvent @event, int shapeIdx) public override void _InputEvent(Viewport viewport, InputEvent @event, int shapeIdx)
{ {
if (@event.IsActionPressed("mouse_left_press")) { if (@event.IsActionPressed("mouse_left_press")) {
@ -142,6 +184,10 @@ public partial class Cube : StaticBody2D, ITileDraggable
} }
// Called every frame. 'delta' is the elapsed time since the previous frame. // Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧处理
/// </summary>
/// <param name="delta">时间间隔</param>
public override void _Process(double delta) public override void _Process(double delta)
{ {
GlobalPosition = TilePosition * 48; GlobalPosition = TilePosition * 48;
@ -153,6 +199,10 @@ public partial class Cube : StaticBody2D, ITileDraggable
private int _chair2ThemeIdx; private int _chair2ThemeIdx;
private int _equipThemeIdx; private int _equipThemeIdx;
private int _equip2ThemeIdx; private int _equip2ThemeIdx;
/// <summary>
/// 随机更换主题样式
/// </summary>
public void RandomChangeTheme() { public void RandomChangeTheme() {
_tableThemeIdx = GD.RandRange(0, tableThemes.Length-1); _tableThemeIdx = GD.RandRange(0, tableThemes.Length-1);
_chairThemeIdx = GD.RandRange(0, chairThemes.Length-1); _chairThemeIdx = GD.RandRange(0, chairThemes.Length-1);
@ -177,6 +227,12 @@ public partial class Cube : StaticBody2D, ITileDraggable
new(1,1), new(1,1),
new(2,1), new(2,1),
}); });
/// <summary>
/// 获取指定位置的图块类型
/// </summary>
/// <param name="pos">相对位置</param>
/// <returns>图块类型</returns>
public Lab.MapNodeType GetTileType(Vector2I pos) public Lab.MapNodeType GetTileType(Vector2I pos)
{ {
GD.Print($"query position of {pos}"); GD.Print($"query position of {pos}");
@ -189,6 +245,11 @@ public partial class Cube : StaticBody2D, ITileDraggable
return Lab.MapNodeType.Walkable; return Lab.MapNodeType.Walkable;
} }
/// <summary>
/// 设置椅子朝向(待实现)
/// </summary>
/// <param name="target">目标位置</param>
/// <param name="idx">索引</param>
public void ChairFaceTo(Vector2I target, int idx) { public void ChairFaceTo(Vector2I target, int idx) {
if (idx == 0) { if (idx == 0) {
var theme = chairThemes[_chairThemeIdx]; var theme = chairThemes[_chairThemeIdx];

View File

@ -17,41 +17,105 @@ using Core;
public partial class GameManager : Node public partial class GameManager : Node
{ {
/// <summary> /// <summary>
/// Indicates if the game is currently in tutorial mode. /// 指示当前是否处于教程模式
/// </summary> /// </summary>
public static bool IsTutorial { get; private set; } public static bool IsTutorial { get; private set; }
/// <summary>
/// 下一个场景的路径
/// </summary>
public static string NextScene { get; set; } = null; public static string NextScene { get; set; } = null;
/// <summary>
/// 双倍大小的箭头光标资源
/// </summary>
public static readonly Resource Arrow2X = ResourceLoader.Load("res://temp_res/kenney_ui-pack-space-expansion/PNG/Extra/Double/cursor_f.png"); public static readonly Resource Arrow2X = ResourceLoader.Load("res://temp_res/kenney_ui-pack-space-expansion/PNG/Extra/Double/cursor_f.png");
/// <summary>
/// 最大回合数
/// </summary>
[Export] [Export]
public int MaxTurns = 30; public int MaxTurns = 30;
// --- Global State --- // --- Global State ---
/// <summary>
/// 游戏会话实例
/// </summary>
public GameSession Session { get; private set; } public GameSession Session { get; private set; }
/// <summary>
/// 当前游戏状态
/// </summary>
public GameState State => Session?.State; public GameState State => Session?.State;
/// <summary>
/// 游戏控制器
/// </summary>
public GameController Controller { get; private set; } public GameController Controller { get; private set; }
/// <summary>
/// 当前游戏阶段
/// </summary>
public GamePhase CurrentPhase => State?.Turn.Phase ?? GamePhase.Planning; public GamePhase CurrentPhase => State?.Turn.Phase ?? GamePhase.Planning;
/// <summary>
/// 当前回合数
/// </summary>
public int CurrentTurn => State?.Turn.CurrentTurn ?? 1; public int CurrentTurn => State?.Turn.CurrentTurn ?? 1;
// --- Domain Model --- // --- Domain Model ---
/// <summary>
/// 导师模型
/// </summary>
public MentorModel Mentor => State?.Roster.Mentor; public MentorModel Mentor => State?.Roster.Mentor;
/// <summary>
/// 学生列表
/// </summary>
public List<StudentModel> Students => State?.Roster.Students; public List<StudentModel> Students => State?.Roster.Students;
/// <summary>
/// 当前活动任务列表
/// </summary>
public List<TaskModel> ActiveTasks => State?.Tasks.ActiveTasks; public List<TaskModel> ActiveTasks => State?.Tasks.ActiveTasks;
/// <summary>
/// 经济状态
/// </summary>
public EconomyState Economy => State?.Economy; public EconomyState Economy => State?.Economy;
// --- Signals --- // --- Signals ---
/// <summary>
/// 阶段变更信号
/// </summary>
/// <param name="phase">阶段枚举的整数值</param>
[Signal] public delegate void PhaseChangedEventHandler(int phase); // int cast of GamePhase [Signal] public delegate void PhaseChangedEventHandler(int phase); // int cast of GamePhase
/// <summary>
/// 回合变更信号
/// </summary>
/// <param name="turn">当前回合数</param>
[Signal] public delegate void TurnChangedEventHandler(int turn); [Signal] public delegate void TurnChangedEventHandler(int turn);
// Singleton instance access (if needed, though Godot uses node paths) // Singleton instance access (if needed, though Godot uses node paths)
/// <summary>
/// 单例实例
/// </summary>
public static GameManager Instance { get; private set; } public static GameManager Instance { get; private set; }
/// <summary>
/// 进入场景树时调用
/// </summary>
public override void _EnterTree() public override void _EnterTree()
{ {
Instance = this; Instance = this;
} }
/// <summary>
/// 准备就绪时调用
/// </summary>
public override void _Ready() public override void _Ready()
{ {
Input.SetCustomMouseCursor(Arrow2X); Input.SetCustomMouseCursor(Arrow2X);
@ -60,6 +124,9 @@ public partial class GameManager : Node
InitializeGame(); InitializeGame();
} }
/// <summary>
/// 初始化游戏数据
/// </summary>
private void InitializeGame() private void InitializeGame()
{ {
Session = GameSession.CreateDefault(); Session = GameSession.CreateDefault();
@ -83,6 +150,10 @@ public partial class GameManager : Node
} }
// Called every frame. 'delta' is the elapsed time since the previous frame. // Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧调用
/// </summary>
/// <param name="delta">距离上一帧的时间间隔</param>
public override void _Process(double delta) public override void _Process(double delta)
{ {
if (CurrentPhase == GamePhase.Execution) if (CurrentPhase == GamePhase.Execution)

View File

@ -1,6 +1,15 @@
using Godot; using Godot;
/// <summary>
/// 辅助工具类
/// </summary>
public class H { public class H {
/// <summary>
/// 判断矩形是否包含点(包含边界)
/// </summary>
/// <param name="r">矩形区域</param>
/// <param name="p">点坐标</param>
/// <returns>如果点在矩形内包括边界则返回true</returns>
public static bool RectHasPointInclusive(Rect2I r, Vector2I p) { public static bool RectHasPointInclusive(Rect2I r, Vector2I p) {
if (r.HasPoint(p)) { if (r.HasPoint(p)) {
return true; return true;

View File

@ -3,25 +3,73 @@ using System.Diagnostics.SymbolStore;
using System.Dynamic; using System.Dynamic;
using Godot; using Godot;
/// <summary>
/// 可拖拽图块接口
/// </summary>
public interface ITileDraggable { public interface ITileDraggable {
/// <summary>
/// 图块在网格中的位置
/// </summary>
Vector2I TilePosition { get; set; } Vector2I TilePosition { get; set; }
/// <summary>
/// 是否允许拖拽
/// </summary>
bool Draggable { get; set; } bool Draggable { get; set; }
/// <summary>
/// 是否处于碰撞状态(例如位置无效或重叠)
/// </summary>
bool IsCollided { get; set; } bool IsCollided { get; set; }
/// <summary>
/// 图块占据的矩形区域(相对于 TilePosition
/// </summary>
Rect2I TileRect { get; } Rect2I TileRect { get; }
/// <summary>
/// 拖拽时鼠标相对于图块原点的偏移量
/// </summary>
Vector2I MouseOffset { get; } Vector2I MouseOffset { get; }
/// <summary>
/// 特殊功能图块列表(如座位、交互点等)
/// </summary>
SpecialTile[] SpecialTiles { get; } SpecialTile[] SpecialTiles { get; }
/// <summary>
/// 特殊图块定义
/// </summary>
class SpecialTile { class SpecialTile {
/// <summary>
/// 相对位置
/// </summary>
public readonly Vector2I Position; public readonly Vector2I Position;
/// <summary>
/// 节点类型(如可行走、作为座位等)
/// </summary>
public readonly Lab.MapNodeType NodeType; public readonly Lab.MapNodeType NodeType;
/// <summary>
/// 构造特殊图块
/// </summary>
/// <param name="pos">相对位置</param>
/// <param name="node">节点类型</param>
public SpecialTile(Vector2I pos, Lab.MapNodeType node) { public SpecialTile(Vector2I pos, Lab.MapNodeType node) {
Position = pos; Position = pos;
NodeType = node; NodeType = node;
} }
} }
/// <summary>
/// 获取指定相对坐标处的图块类型
/// </summary>
/// <param name="pos">相对坐标</param>
/// <returns>地图节点类型</returns>
Lab.MapNodeType GetTileType(Vector2I pos); Lab.MapNodeType GetTileType(Vector2I pos);
/// <summary>
/// 唯一标识符
/// </summary>
Guid Id { get; } Guid Id { get; }
} }

View File

@ -3,18 +3,26 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
/// <summary>
/// 实验室场景主控脚本
/// </summary>
public partial class Lab : Node2D public partial class Lab : Node2D
{ {
/// <summary>
/// 地图节点类型标志位
/// </summary>
[Flags] [Flags]
public enum MapNodeType public enum MapNodeType
{ {
Invalid = 0, Invalid = 0, // 无效
Walkable = 1, Walkable = 1, // 可行走
Wall = 2, Wall = 2, // 墙壁
Blocker = 4, Blocker = 4, // 阻挡物
SeatUp = 8, SeatUp = 8, // 上方座位
SeatDown = 16, SeatDown = 16, // 下方座位
} }
// 墙壁矩形区域列表
private static readonly Rect2I[] wallRectangles = { private static readonly Rect2I[] wallRectangles = {
new(0,0,40,2), new(0,0,40,2),
new(0,5,1, 15), new(0,5,1, 15),
@ -23,9 +31,15 @@ public partial class Lab : Node2D
}; };
private readonly Dictionary<Guid, ITileDraggable> _furnitureIDs = new(); private readonly Dictionary<Guid, ITileDraggable> _furnitureIDs = new();
/// <summary>
/// 家具字典通过ID索引
/// </summary>
public Dictionary<Guid, ITileDraggable> Furniture => _furnitureIDs; public Dictionary<Guid, ITileDraggable> Furniture => _furnitureIDs;
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready() public override void _Ready()
{ {
var ticker = GetNode<Timer>("/root/GameManager/OneSecondTicker"); var ticker = GetNode<Timer>("/root/GameManager/OneSecondTicker");
@ -57,6 +71,10 @@ public partial class Lab : Node2D
private Label _moneyLabel; private Label _moneyLabel;
// Called every frame. 'delta' is the elapsed time since the previous frame. // Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public override void _Process(double delta) public override void _Process(double delta)
{ {
switch(GD.RandRange(0,2)) { switch(GD.RandRange(0,2)) {
@ -82,12 +100,21 @@ public partial class Lab : Node2D
} }
private bool _isDragging; private bool _isDragging;
/// <summary>
/// 拾起物品
/// </summary>
/// <param name="target">目标物品</param>
public void Pickup(ITileDraggable target) { public void Pickup(ITileDraggable target) {
if (target == null) return; if (target == null) return;
_isDragging = true; _isDragging = true;
DraggingTarget = target; DraggingTarget = target;
} }
/// <summary>
/// 放下物品
/// </summary>
/// <param name="target">目标物品</param>
public void PutDown(ITileDraggable target) { public void PutDown(ITileDraggable target) {
if (target == null) return; if (target == null) return;
_isDragging = false; _isDragging = false;
@ -98,6 +125,10 @@ public partial class Lab : Node2D
DraggingTarget = null; DraggingTarget = null;
UpdateMap(); UpdateMap();
} }
/// <summary>
/// 当前正在拖拽的目标
/// </summary>
public ITileDraggable DraggingTarget { get; set; } public ITileDraggable DraggingTarget { get; set; }
public const int MapWidth = 40; public const int MapWidth = 40;
@ -109,6 +140,9 @@ public partial class Lab : Node2D
private TileMapLayer _tileMap; private TileMapLayer _tileMap;
/// <summary>
/// 更新地图数据
/// </summary>
public void UpdateMap() public void UpdateMap()
{ {
for (int i = 0; i < MapWidth; i++) { for (int i = 0; i < MapWidth; i++) {
@ -132,8 +166,6 @@ public partial class Lab : Node2D
} }
} }
private static bool IsValidPosition(Vector2I pos) private static bool IsValidPosition(Vector2I pos)
{ {
int x = pos.X; int x = pos.X;
@ -143,6 +175,7 @@ public partial class Lab : Node2D
} }
return true; return true;
} }
private List<Vector2I> GetNeighbors(Vector2I pos) { private List<Vector2I> GetNeighbors(Vector2I pos) {
int x = pos.X; int x = pos.X;
int y = pos.Y; int y = pos.Y;
@ -166,6 +199,12 @@ public partial class Lab : Node2D
return neighbor; return neighbor;
} }
/// <summary>
/// 获取最短路径
/// </summary>
/// <param name="start">起点</param>
/// <param name="end">终点</param>
/// <returns>路径点列表</returns>
public List<Vector2I> GetShortestPath(Vector2I start, Vector2I end) public List<Vector2I> GetShortestPath(Vector2I start, Vector2I end)
{ {
for (int j = 0; j < MapHeight; j++) { for (int j = 0; j < MapHeight; j++) {
@ -258,15 +297,31 @@ public partial class Lab : Node2D
} }
} }
/// <summary>
/// 获取指定位置的地图节点类型
/// </summary>
/// <param name="pos">位置</param>
/// <returns>节点类型</returns>
public MapNodeType GetMapNodeTypeOfPosition(Vector2I pos) { public MapNodeType GetMapNodeTypeOfPosition(Vector2I pos) {
if (!IsValidPosition(pos)) return MapNodeType.Invalid; if (!IsValidPosition(pos)) return MapNodeType.Invalid;
return _blocks[pos.X, pos.Y]; return _blocks[pos.X, pos.Y];
} }
/// <summary>
/// 世界坐标转网格坐标
/// </summary>
/// <param name="pos">世界坐标</param>
/// <returns>网格坐标</returns>
public Vector2I Point2Coord(Vector2 pos) { public Vector2I Point2Coord(Vector2 pos) {
return _tileMap.LocalToMap(_tileMap.ToLocal(pos)); return _tileMap.LocalToMap(_tileMap.ToLocal(pos));
} }
/// <summary>
/// 获取家具的特殊位置(如座位)
/// </summary>
/// <param name="fId">家具ID</param>
/// <param name="idx">索引</param>
/// <returns>特殊位置坐标</returns>
public Vector2I GetFurSpecialPosition(Guid fId, int idx) { public Vector2I GetFurSpecialPosition(Guid fId, int idx) {
if (!_furnitureIDs.ContainsKey(fId)) return Vector2I.Zero; if (!_furnitureIDs.ContainsKey(fId)) return Vector2I.Zero;
if (idx < 0 || idx > _furnitureIDs[fId].SpecialTiles.Length) return Vector2I.Zero; if (idx < 0 || idx > _furnitureIDs[fId].SpecialTiles.Length) return Vector2I.Zero;

View File

@ -2,6 +2,9 @@ using Godot;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
/// <summary>
/// 场景加载器
/// </summary>
public partial class Loader : Control public partial class Loader : Control
{ {
private ProgressBar _progressBar; private ProgressBar _progressBar;
@ -12,6 +15,9 @@ public partial class Loader : Control
}; };
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready() public override void _Ready()
{ {
_progressBar = GetNode<ProgressBar>("ProgressBar"); _progressBar = GetNode<ProgressBar>("ProgressBar");
@ -31,6 +37,10 @@ public partial class Loader : Control
} }
// Called every frame. 'delta' is the elapsed time since the previous frame. // Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public override void _Process(double delta) public override void _Process(double delta)
{ {
Godot.Collections.Array progress = new(); Godot.Collections.Array progress = new();

View File

@ -1,12 +1,22 @@
using Godot; using Godot;
using System; using System;
/// <summary>
/// Logo场景
/// </summary>
public partial class LogoScene : Node2D public partial class LogoScene : Node2D
{ {
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready() { public override void _Ready() {
GetNode<AnimationPlayer>("AnimationPlayer").AnimationFinished += OnAnimationPlayerAnimationFinished; GetNode<AnimationPlayer>("AnimationPlayer").AnimationFinished += OnAnimationPlayerAnimationFinished;
} }
/// <summary>
/// 动画播放结束回调
/// </summary>
/// <param name="animationName">动画名称</param>
public void OnAnimationPlayerAnimationFinished(StringName animationName) public void OnAnimationPlayerAnimationFinished(StringName animationName)
{ {
GD.Print("FFF"); GD.Print("FFF");

View File

@ -12,44 +12,142 @@ namespace Models;
/// </summary> /// </summary>
public static class CoreIds public static class CoreIds
{ {
/// <summary>
/// 默认命名空间
/// </summary>
public const string Namespace = "core"; public const string Namespace = "core";
// Disciplines // Disciplines
/// <summary>
/// 生物学
/// </summary>
public const string DisciplineBiology = "core:discipline_biology"; public const string DisciplineBiology = "core:discipline_biology";
/// <summary>
/// 化学
/// </summary>
public const string DisciplineChemistry = "core:discipline_chemistry"; public const string DisciplineChemistry = "core:discipline_chemistry";
/// <summary>
/// 环境科学
/// </summary>
public const string DisciplineEnvironment = "core:discipline_environment"; public const string DisciplineEnvironment = "core:discipline_environment";
/// <summary>
/// 材料学
/// </summary>
public const string DisciplineMaterials = "core:discipline_materials"; public const string DisciplineMaterials = "core:discipline_materials";
/// <summary>
/// 医学
/// </summary>
public const string DisciplineMedicine = "core:discipline_medicine"; public const string DisciplineMedicine = "core:discipline_medicine";
/// <summary>
/// 计算机
/// </summary>
public const string DisciplineComputer = "core:discipline_computer"; public const string DisciplineComputer = "core:discipline_computer";
/// <summary>
/// 数学
/// </summary>
public const string DisciplineMath = "core:discipline_math"; public const string DisciplineMath = "core:discipline_math";
/// <summary>
/// 物理学
/// </summary>
public const string DisciplinePhysics = "core:discipline_physics"; public const string DisciplinePhysics = "core:discipline_physics";
/// <summary>
/// 机械工程
/// </summary>
public const string DisciplineMechanical = "core:discipline_mechanical"; public const string DisciplineMechanical = "core:discipline_mechanical";
/// <summary>
/// 哲学
/// </summary>
public const string DisciplinePhilosophy = "core:discipline_philosophy"; public const string DisciplinePhilosophy = "core:discipline_philosophy";
/// <summary>
/// 经济学
/// </summary>
public const string DisciplineEconomics = "core:discipline_economics"; public const string DisciplineEconomics = "core:discipline_economics";
/// <summary>
/// 法学
/// </summary>
public const string DisciplineLaw = "core:discipline_law"; public const string DisciplineLaw = "core:discipline_law";
/// <summary>
/// 文学
/// </summary>
public const string DisciplineLiterature = "core:discipline_literature"; public const string DisciplineLiterature = "core:discipline_literature";
/// <summary>
/// 农学
/// </summary>
public const string DisciplineAgriculture = "core:discipline_agriculture"; public const string DisciplineAgriculture = "core:discipline_agriculture";
/// <summary>
/// 管理学
/// </summary>
public const string DisciplineManagement = "core:discipline_management"; public const string DisciplineManagement = "core:discipline_management";
/// <summary>
/// 艺术
/// </summary>
public const string DisciplineArt = "core:discipline_art"; public const string DisciplineArt = "core:discipline_art";
// Archetypes // Archetypes
/// <summary>
/// 卷王
/// </summary>
public const string ArchetypeGrinder = "core:archetype_grinder"; public const string ArchetypeGrinder = "core:archetype_grinder";
/// <summary>
/// 摸鱼
/// </summary>
public const string ArchetypeSlacker = "core:archetype_slacker"; public const string ArchetypeSlacker = "core:archetype_slacker";
/// <summary>
/// 精英
/// </summary>
public const string ArchetypeElite = "core:archetype_elite"; public const string ArchetypeElite = "core:archetype_elite";
/// <summary>
/// 天才
/// </summary>
public const string ArchetypeProdigy = "core:archetype_prodigy"; public const string ArchetypeProdigy = "core:archetype_prodigy";
/// <summary>
/// 吉祥物
/// </summary>
public const string ArchetypeMascot = "core:archetype_mascot"; public const string ArchetypeMascot = "core:archetype_mascot";
// Roles // Roles
/// <summary>
/// 码农
/// </summary>
public const string RoleCoder = "core:role_coder"; public const string RoleCoder = "core:role_coder";
/// <summary>
/// 写手
/// </summary>
public const string RoleWriter = "core:role_writer"; public const string RoleWriter = "core:role_writer";
/// <summary>
/// 实验员
/// </summary>
public const string RoleLabRat = "core:role_lab_rat"; public const string RoleLabRat = "core:role_lab_rat";
/// <summary>
/// 讲演者
/// </summary>
public const string RolePresenter = "core:role_presenter"; public const string RolePresenter = "core:role_presenter";
/// <summary>
/// 记录员
/// </summary>
public const string RoleScribe = "core:role_scribe"; public const string RoleScribe = "core:role_scribe";
/// <summary>
/// 辩论者
/// </summary>
public const string RoleOrator = "core:role_orator"; public const string RoleOrator = "core:role_orator";
/// <summary>
/// 管家
/// </summary>
public const string RoleSteward = "core:role_steward"; public const string RoleSteward = "core:role_steward";
/// <summary>
/// 炼金术士
/// </summary>
public const string RoleAlchemist = "core:role_alchemist"; public const string RoleAlchemist = "core:role_alchemist";
/// <summary>
/// 极客
/// </summary>
public const string RoleGeek = "core:role_geek"; public const string RoleGeek = "core:role_geek";
/// <summary>
/// 勘测员
/// </summary>
public const string RoleSurveyor = "core:role_surveyor"; public const string RoleSurveyor = "core:role_surveyor";
/// <summary>
/// 思考者
/// </summary>
public const string RoleThinker = "core:role_thinker"; public const string RoleThinker = "core:role_thinker";
} }

View File

@ -0,0 +1 @@
uid://b8dobhu11y8kt

View File

@ -17,16 +17,36 @@ namespace Models;
/// </summary> /// </summary>
public sealed class LocalizedText public sealed class LocalizedText
{ {
/// <summary>
/// 键值
/// </summary>
public string Key { get; set; } public string Key { get; set; }
/// <summary>
/// 默认值
/// </summary>
public string Fallback { get; set; } public string Fallback { get; set; }
} }
public sealed class DefinitionHeader public sealed class DefinitionHeader
{ {
/// <summary>
/// 定义ID
/// </summary>
public string Id { get; set; } public string Id { get; set; }
/// <summary>
/// 名称
/// </summary>
public LocalizedText Name { get; set; } = new(); public LocalizedText Name { get; set; } = new();
/// <summary>
/// 描述
/// </summary>
public LocalizedText Description { get; set; } = new(); public LocalizedText Description { get; set; } = new();
/// <summary>
/// 图标路径
/// </summary>
public string IconPath { get; set; } public string IconPath { get; set; }
/// <summary>
/// 标签列表
/// </summary>
public List<string> Tags { get; } = new(); public List<string> Tags { get; } = new();
} }

View File

@ -0,0 +1 @@
uid://bpvgbmxh1gq6u

View File

@ -14,18 +14,44 @@ namespace Models;
/// </summary> /// </summary>
public sealed class DisciplineDefinition public sealed class DisciplineDefinition
{ {
/// <summary>
/// 基础头部信息
/// </summary>
public DefinitionHeader Header { get; set; } = new(); public DefinitionHeader Header { get; set; } = new();
/// <summary>
/// 学科Buff
/// </summary>
public DisciplineBuff Buff { get; set; } = new(); public DisciplineBuff Buff { get; set; } = new();
/// <summary>
/// 角色池ID列表
/// </summary>
public List<string> RolePoolIds { get; } = new(); public List<string> RolePoolIds { get; } = new();
/// <summary>
/// 物品池ID列表
/// </summary>
public List<string> ItemPoolIds { get; } = new(); public List<string> ItemPoolIds { get; } = new();
/// <summary>
/// 任务关键词ID列表
/// </summary>
public List<string> TaskKeywordIds { get; } = new(); public List<string> TaskKeywordIds { get; } = new();
} }
/// <summary>
/// 学科Buff定义
/// </summary>
public sealed class DisciplineBuff public sealed class DisciplineBuff
{ {
/// <summary>
/// 名称
/// </summary>
public LocalizedText Name { get; set; } = new(); public LocalizedText Name { get; set; } = new();
/// <summary>
/// 描述
/// </summary>
public LocalizedText Description { get; set; } = new(); public LocalizedText Description { get; set; } = new();
/// <summary>
/// 修正包
/// </summary>
public ModifierBundle Modifiers { get; set; } = new(); public ModifierBundle Modifiers { get; set; } = new();
} }

View File

@ -0,0 +1 @@
uid://cdec5vh0h2md

View File

@ -12,32 +12,31 @@ namespace Models;
/// </summary> /// </summary>
public enum AttributeType public enum AttributeType
{ {
Academic, Academic, // 学术
Engineering, Engineering, // 工程
Writing, Writing, // 写作
Financial, Financial, // 财务
Social, Social, // 社交
Activation Activation // 活跃/执行
} }
public enum ResourceType public enum ResourceType
{ {
Money, Money, // 金钱
Reputation, Reputation, // 声望
ResearchPoints, ResearchPoints, // 科研点
Paper, Paper, // 论文
Inspiration, Inspiration, // 灵感
Time Time // 时间
} }
public enum StatusType public enum StatusType
{ {
Stress, Stress, // 压力
Sanity, Sanity, // 理智
Mood, Mood, // 心情
Stamina, Stamina, // 体力
Loyalty, Loyalty, // 忠诚度
Energy, Energy, // 精力
Health Health // 健康
} }

View File

@ -0,0 +1 @@
uid://dco1nttd0pgqp

View File

@ -14,13 +14,36 @@ namespace Models;
/// </summary> /// </summary>
public sealed class GameContentDatabase public sealed class GameContentDatabase
{ {
/// <summary>
/// 学科定义字典
/// </summary>
public Dictionary<string, DisciplineDefinition> Disciplines { get; } = new(); public Dictionary<string, DisciplineDefinition> Disciplines { get; } = new();
/// <summary>
/// 原型定义字典
/// </summary>
public Dictionary<string, ArchetypeDefinition> Archetypes { get; } = new(); public Dictionary<string, ArchetypeDefinition> Archetypes { get; } = new();
/// <summary>
/// 角色定义字典
/// </summary>
public Dictionary<string, RoleDefinition> Roles { get; } = new(); public Dictionary<string, RoleDefinition> Roles { get; } = new();
/// <summary>
/// 特质定义字典
/// </summary>
public Dictionary<string, TraitDefinition> Traits { get; } = new(); public Dictionary<string, TraitDefinition> Traits { get; } = new();
/// <summary>
/// 任务定义字典
/// </summary>
public Dictionary<string, TaskDefinition> Tasks { get; } = new(); public Dictionary<string, TaskDefinition> Tasks { get; } = new();
/// <summary>
/// 物品定义字典
/// </summary>
public Dictionary<string, ItemDefinition> Items { get; } = new(); public Dictionary<string, ItemDefinition> Items { get; } = new();
/// <summary>
/// 论文定义字典
/// </summary>
public Dictionary<string, PaperDefinition> Papers { get; } = new(); public Dictionary<string, PaperDefinition> Papers { get; } = new();
/// <summary>
/// 肉鸽天赋定义字典
/// </summary>
public Dictionary<string, RoguelitePerkDefinition> RoguelitePerks { get; } = new(); public Dictionary<string, RoguelitePerkDefinition> RoguelitePerks { get; } = new();
} }

View File

@ -0,0 +1 @@
uid://i6eakpcujm0q

View File

@ -17,69 +17,179 @@ namespace Models;
/// </summary> /// </summary>
public sealed class GameState public sealed class GameState
{ {
/// <summary>
/// 回合状态
/// </summary>
public TurnState Turn { get; } = new(); public TurnState Turn { get; } = new();
/// <summary>
/// 经济状态
/// </summary>
public EconomyState Economy { get; } = new(); public EconomyState Economy { get; } = new();
/// <summary>
/// 人员状态
/// </summary>
public RosterState Roster { get; } = new(); public RosterState Roster { get; } = new();
/// <summary>
/// 任务状态
/// </summary>
public TaskState Tasks { get; } = new(); public TaskState Tasks { get; } = new();
/// <summary>
/// 库存状态
/// </summary>
public InventoryState Inventory { get; } = new(); public InventoryState Inventory { get; } = new();
/// <summary>
/// 协同状态
/// </summary>
public SynergyState Synergy { get; } = new(); public SynergyState Synergy { get; } = new();
/// <summary>
/// 肉鸽状态
/// </summary>
public RogueliteState Roguelite { get; } = new(); public RogueliteState Roguelite { get; } = new();
} }
/// <summary>
/// 游戏阶段
/// </summary>
public enum GamePhase public enum GamePhase
{ {
Planning, Planning, // 筹备阶段
Execution, Execution, // 执行阶段
Review Review // 结算阶段
} }
/// <summary>
/// 回合状态数据
/// </summary>
public sealed class TurnState public sealed class TurnState
{ {
/// <summary>
/// 当前回合
/// </summary>
public int CurrentTurn { get; set; } = 1; public int CurrentTurn { get; set; } = 1;
/// <summary>
/// 最大回合
/// </summary>
public int MaxTurns { get; set; } = 30; public int MaxTurns { get; set; } = 30;
/// <summary>
/// 当前阶段
/// </summary>
public GamePhase Phase { get; set; } = GamePhase.Planning; public GamePhase Phase { get; set; } = GamePhase.Planning;
} }
/// <summary>
/// 经济状态数据
/// </summary>
public sealed class EconomyState public sealed class EconomyState
{ {
/// <summary>
/// 资金
/// </summary>
public int Money { get; set; } = 50000; public int Money { get; set; } = 50000;
/// <summary>
/// 声望
/// </summary>
public int Reputation { get; set; } public int Reputation { get; set; }
/// <summary>
/// 科研点数
/// </summary>
public int ResearchPoints { get; set; } public int ResearchPoints { get; set; }
/// <summary>
/// 利率
/// </summary>
public float InterestRate { get; set; } = 0.0f; public float InterestRate { get; set; } = 0.0f;
} }
/// <summary>
/// 人员状态数据
/// </summary>
public sealed class RosterState public sealed class RosterState
{ {
/// <summary>
/// 导师模型
/// </summary>
public MentorModel Mentor { get; set; } = new("Player"); public MentorModel Mentor { get; set; } = new("Player");
/// <summary>
/// 学生列表
/// </summary>
public List<StudentModel> Students { get; } = new(); public List<StudentModel> Students { get; } = new();
/// <summary>
/// 职工列表
/// </summary>
public List<StaffModel> Staffs { get; } = new(); public List<StaffModel> Staffs { get; } = new();
} }
/// <summary>
/// 任务状态数据
/// </summary>
public sealed class TaskState public sealed class TaskState
{ {
/// <summary>
/// 活动任务列表
/// </summary>
public List<TaskModel> ActiveTasks { get; } = new(); public List<TaskModel> ActiveTasks { get; } = new();
/// <summary>
/// 已完成任务列表
/// </summary>
public List<TaskModel> CompletedTasks { get; } = new(); public List<TaskModel> CompletedTasks { get; } = new();
/// <summary>
/// 失败任务列表
/// </summary>
public List<TaskModel> FailedTasks { get; } = new(); public List<TaskModel> FailedTasks { get; } = new();
} }
/// <summary>
/// 库存状态数据
/// </summary>
public sealed class InventoryState public sealed class InventoryState
{ {
/// <summary>
/// 物品计数
/// </summary>
public Dictionary<string, int> ItemCounts { get; } = new(); public Dictionary<string, int> ItemCounts { get; } = new();
/// <summary>
/// 论文计数
/// </summary>
public Dictionary<PaperRank, int> PaperCounts { get; } = new(); public Dictionary<PaperRank, int> PaperCounts { get; } = new();
} }
/// <summary>
/// 协同状态数据
/// </summary>
public sealed class SynergyState public sealed class SynergyState
{ {
/// <summary>
/// 原型堆叠数
/// </summary>
public Dictionary<string, int> ArchetypeStacks { get; } = new(); public Dictionary<string, int> ArchetypeStacks { get; } = new();
/// <summary>
/// 角色堆叠数
/// </summary>
public Dictionary<string, int> RoleStacks { get; } = new(); public Dictionary<string, int> RoleStacks { get; } = new();
/// <summary>
/// 激活的协同ID列表
/// </summary>
public List<string> ActiveSynergyIds { get; } = new(); public List<string> ActiveSynergyIds { get; } = new();
/// <summary>
/// 激活的修正包
/// </summary>
public ModifierBundle ActiveModifiers { get; } = new(); public ModifierBundle ActiveModifiers { get; } = new();
} }
/// <summary>
/// 肉鸽状态数据
/// </summary>
public sealed class RogueliteState public sealed class RogueliteState
{ {
/// <summary>
/// 校友卡ID列表
/// </summary>
public List<string> AlumniCardIds { get; } = new(); public List<string> AlumniCardIds { get; } = new();
/// <summary>
/// 遗产解锁ID列表
/// </summary>
public List<string> LegacyUnlockIds { get; } = new(); public List<string> LegacyUnlockIds { get; } = new();
/// <summary>
/// 头衔保留等级
/// </summary>
public int TitleRetentionLevel { get; set; } public int TitleRetentionLevel { get; set; }
} }

View File

@ -0,0 +1 @@
uid://bm4fbpk2hmxkj

View File

@ -14,29 +14,29 @@ namespace Models;
/// </summary> /// </summary>
public enum ItemCategory public enum ItemCategory
{ {
Facility, Facility, // 设施
Equipment, Equipment, // 装备
Consumable Consumable // 消耗品
} }
public enum FacilityPlacement public enum FacilityPlacement
{ {
Lab, Lab, // 实验室
ServerRoom, ServerRoom, // 机房
Admin, Admin, // 行政区
Library, Library, // 图书馆
RestArea, RestArea, // 休息区
Field, Field, // 场地
Global Global // 全局
} }
public enum EquipmentSlot public enum EquipmentSlot
{ {
Tool, Tool, // 工具
Accessory, Accessory, // 饰品
Body, Body, // 身体
Head, Head, // 头部
Hand Hand // 手部
} }
public sealed class ItemDefinition public sealed class ItemDefinition
@ -56,4 +56,3 @@ public sealed class ItemEffect
public ModifierBundle Modifiers { get; set; } = new(); public ModifierBundle Modifiers { get; set; } = new();
public List<string> RuleIds { get; } = new(); public List<string> RuleIds { get; } = new();
} }

View File

@ -0,0 +1 @@
uid://buicivgjyfews

View File

@ -14,22 +14,41 @@ namespace Models;
/// </summary> /// </summary>
public sealed class MentorModel public sealed class MentorModel
{ {
/// <summary>
/// 导师模式
/// </summary>
public enum MentorModeType public enum MentorModeType
{ {
Worker, Worker, // 打工人
Manager Manager // 管理者
} }
/// <summary>
/// 核心单位数据
/// </summary>
public UnitModel Core { get; } public UnitModel Core { get; }
/// <summary>
/// 导师特有资源
/// </summary>
public MentorResources Resources { get; } public MentorResources Resources { get; }
/// <summary>
/// 当前模式
/// </summary>
public MentorModeType Mode { get; set; } = MentorModeType.Worker; public MentorModeType Mode { get; set; } = MentorModeType.Worker;
/// <summary>
/// 名字(便捷访问)
/// </summary>
public string Name public string Name
{ {
get => Core.Name; get => Core.Name;
set => Core.Name = value; set => Core.Name = value;
} }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="name">名字</param>
public MentorModel(string name) public MentorModel(string name)
{ {
Core = new UnitModel(name); Core = new UnitModel(name);
@ -42,6 +61,9 @@ public sealed class MentorModel
/// </summary> /// </summary>
public sealed class MentorResources public sealed class MentorResources
{ {
/// <summary>
/// 精力值
/// </summary>
public StatusValue Energy { get; set; } public StatusValue Energy { get; set; }
public MentorResources() public MentorResources()

View File

@ -14,30 +14,68 @@ namespace Models;
/// </summary> /// </summary>
public sealed class AttributeModifier public sealed class AttributeModifier
{ {
/// <summary>
/// 属性类型
/// </summary>
public AttributeType Type { get; set; } public AttributeType Type { get; set; }
/// <summary>
/// 加值
/// </summary>
public float Add { get; set; } public float Add { get; set; }
/// <summary>
/// 乘数
/// </summary>
public float Multiplier { get; set; } = 1.0f; public float Multiplier { get; set; } = 1.0f;
} }
public sealed class StatusModifier public sealed class StatusModifier
{ {
/// <summary>
/// 状态类型
/// </summary>
public StatusType Type { get; set; } public StatusType Type { get; set; }
/// <summary>
/// 加值
/// </summary>
public float Add { get; set; } public float Add { get; set; }
/// <summary>
/// 乘数
/// </summary>
public float Multiplier { get; set; } = 1.0f; public float Multiplier { get; set; } = 1.0f;
} }
public sealed class ResourceModifier public sealed class ResourceModifier
{ {
/// <summary>
/// 资源类型
/// </summary>
public ResourceType Type { get; set; } public ResourceType Type { get; set; }
/// <summary>
/// 加值
/// </summary>
public int Add { get; set; } public int Add { get; set; }
/// <summary>
/// 乘数
/// </summary>
public float Multiplier { get; set; } = 1.0f; public float Multiplier { get; set; } = 1.0f;
} }
public sealed class ModifierBundle public sealed class ModifierBundle
{ {
/// <summary>
/// 属性修正列表
/// </summary>
public List<AttributeModifier> AttributeModifiers { get; } = new(); public List<AttributeModifier> AttributeModifiers { get; } = new();
/// <summary>
/// 状态修正列表
/// </summary>
public List<StatusModifier> StatusModifiers { get; } = new(); public List<StatusModifier> StatusModifiers { get; } = new();
/// <summary>
/// 资源修正列表
/// </summary>
public List<ResourceModifier> ResourceModifiers { get; } = new(); public List<ResourceModifier> ResourceModifiers { get; } = new();
/// <summary>
/// 规则ID列表
/// </summary>
public List<string> RuleIds { get; } = new(); public List<string> RuleIds { get; } = new();
} }

View File

@ -0,0 +1 @@
uid://bpjfyyhudrq8b

View File

@ -15,4 +15,3 @@ public sealed class PaperDefinition
public DefinitionHeader Header { get; set; } = new(); public DefinitionHeader Header { get; set; } = new();
public PaperRank Rank { get; set; } public PaperRank Rank { get; set; }
} }

View File

@ -0,0 +1 @@
uid://mtm1ypht0ynd

View File

@ -20,17 +20,32 @@ public sealed class PropertyValue
public const float DefaultMin = 0f; public const float DefaultMin = 0f;
public const float DefaultMax = 100f; public const float DefaultMax = 100f;
/// <summary>
/// 最小值
/// </summary>
public float Min { get; } public float Min { get; }
/// <summary>
/// 最大值
/// </summary>
public float Max { get; } public float Max { get; }
private float _value; private float _value;
/// <summary>
/// 当前值(自动限制在 Min/Max 范围内)
/// </summary>
public float Value public float Value
{ {
get => _value; get => _value;
set => _value = Clamp(value, Min, Max); set => _value = Clamp(value, Min, Max);
} }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="value">初始值</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public PropertyValue(float value = 0, float min = DefaultMin, float max = DefaultMax) public PropertyValue(float value = 0, float min = DefaultMin, float max = DefaultMax)
{ {
if (max < min) if (max < min)
@ -44,13 +59,26 @@ public sealed class PropertyValue
Value = value; Value = value;
} }
/// <summary>
/// 构造函数(整数)
/// </summary>
/// <param name="value">初始值</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public PropertyValue(int value, float min = DefaultMin, float max = DefaultMax) public PropertyValue(int value, float min = DefaultMin, float max = DefaultMax)
: this((float)value, min, max) : this((float)value, min, max)
{ {
} }
/// <summary>
/// 获取显示的整数值
/// </summary>
/// <returns>整数值</returns>
public int DisplayInt() => (int)_value; public int DisplayInt() => (int)_value;
/// <summary>
/// 获取归一化值 (0.0 - 1.0)
/// </summary>
public float Normalized public float Normalized
{ {
get get
@ -60,10 +88,22 @@ public sealed class PropertyValue
} }
} }
/// <summary>
/// 克隆
/// </summary>
/// <returns>新实例</returns>
public PropertyValue Clone() => new(Value, Min, Max); public PropertyValue Clone() => new(Value, Min, Max);
/// <summary>
/// 增加值
/// </summary>
/// <param name="delta">增量</param>
public void Add(float delta) => Value += delta; public void Add(float delta) => Value += delta;
/// <summary>
/// 减少值
/// </summary>
/// <param name="delta">减量</param>
public void Subtract(float delta) => Value -= delta; public void Subtract(float delta) => Value -= delta;
public override string ToString() => DisplayInt().ToString(); public override string ToString() => DisplayInt().ToString();

View File

@ -12,16 +12,27 @@ namespace Models;
/// </summary> /// </summary>
public enum RoguelitePerkType public enum RoguelitePerkType
{ {
AlumniCard, AlumniCard, // 校友卡
LegacyProgress, LegacyProgress, // 遗产进度
TitleRetention TitleRetention // 头衔保留
} }
public sealed class RoguelitePerkDefinition public sealed class RoguelitePerkDefinition
{ {
/// <summary>
/// 基础头部信息
/// </summary>
public DefinitionHeader Header { get; set; } = new(); public DefinitionHeader Header { get; set; } = new();
/// <summary>
/// 天赋类型
/// </summary>
public RoguelitePerkType Type { get; set; } public RoguelitePerkType Type { get; set; }
/// <summary>
/// 修正包
/// </summary>
public ModifierBundle Modifiers { get; set; } = new(); public ModifierBundle Modifiers { get; set; } = new();
/// <summary>
/// 最大等级
/// </summary>
public int MaxLevel { get; set; } = 1; public int MaxLevel { get; set; } = 1;
} }

View File

@ -0,0 +1 @@
uid://cjcfah7uglbad

View File

@ -14,22 +14,43 @@ namespace Models;
/// </summary> /// </summary>
public sealed class StaffModel public sealed class StaffModel
{ {
/// <summary>
/// 职员类型
/// </summary>
public enum StaffType public enum StaffType
{ {
PostDoc, PostDoc, // 博士后
JuniorFaculty JuniorFaculty // 青年教师
} }
/// <summary>
/// 核心单位数据
/// </summary>
public UnitModel Core { get; } public UnitModel Core { get; }
/// <summary>
/// 职员类型
/// </summary>
public StaffType Type { get; private set; } public StaffType Type { get; private set; }
/// <summary>
/// 动机数据
/// </summary>
public StaffMotivation Motivation { get; } public StaffMotivation Motivation { get; }
/// <summary>
/// 名字(便捷访问)
/// </summary>
public string Name public string Name
{ {
get => Core.Name; get => Core.Name;
set => Core.Name = value; set => Core.Name = value;
} }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="name">名字</param>
/// <param name="type">类型</param>
/// <param name="random">随机数生成器</param>
public StaffModel(string name, StaffType type, Random random = null) public StaffModel(string name, StaffType type, Random random = null)
{ {
Core = new UnitModel(name, random); Core = new UnitModel(name, random);
@ -38,10 +59,21 @@ public sealed class StaffModel
} }
} }
/// <summary>
/// 职员动机组件
/// </summary>
public sealed class StaffMotivation public sealed class StaffMotivation
{ {
/// <summary>
/// 野心
/// </summary>
public StatusValue Ambition { get; set; } = new(50, 100, 0); public StatusValue Ambition { get; set; } = new(50, 100, 0);
/// <summary>
/// 忠诚度
/// </summary>
public StatusValue Loyalty { get; set; } = new(70, 100, 0); public StatusValue Loyalty { get; set; } = new(70, 100, 0);
/// <summary>
/// 合同剩余回合
/// </summary>
public StatusValue ContractTurns { get; set; } = new(6, 12, 0); public StatusValue ContractTurns { get; set; } = new(6, 12, 0);
} }

View File

@ -0,0 +1 @@
uid://cx1jd2ydj5nof

View File

@ -19,9 +19,18 @@ public sealed class StatusValue
{ {
private PropertyValue _current; private PropertyValue _current;
/// <summary>
/// 达到上限事件
/// </summary>
public event Action OnUpperThresholdReached; public event Action OnUpperThresholdReached;
/// <summary>
/// 达到下限事件
/// </summary>
public event Action OnLowerThresholdReached; public event Action OnLowerThresholdReached;
/// <summary>
/// 当前值
/// </summary>
public PropertyValue Current public PropertyValue Current
{ {
get => _current; get => _current;
@ -32,9 +41,18 @@ public sealed class StatusValue
} }
} }
/// <summary>
/// 上限阈值
/// </summary>
public float UpperThreshold { get; set; } public float UpperThreshold { get; set; }
/// <summary>
/// 下限阈值
/// </summary>
public float LowerThreshold { get; set; } public float LowerThreshold { get; set; }
/// <summary>
/// 构造函数(整数)
/// </summary>
public StatusValue(int current = 0, int upper = 100, int lower = 0) public StatusValue(int current = 0, int upper = 100, int lower = 0)
{ {
_current = new PropertyValue(current, lower, upper); _current = new PropertyValue(current, lower, upper);
@ -42,6 +60,9 @@ public sealed class StatusValue
LowerThreshold = lower; LowerThreshold = lower;
} }
/// <summary>
/// 构造函数(浮点数)
/// </summary>
public StatusValue(float current, float upper, float lower) public StatusValue(float current, float upper, float lower)
{ {
_current = new PropertyValue(current, lower, upper); _current = new PropertyValue(current, lower, upper);
@ -49,6 +70,9 @@ public sealed class StatusValue
LowerThreshold = lower; LowerThreshold = lower;
} }
/// <summary>
/// 构造函数PropertyValue
/// </summary>
public StatusValue(PropertyValue current, float upperThreshold, float lowerThreshold) public StatusValue(PropertyValue current, float upperThreshold, float lowerThreshold)
{ {
_current = current ?? new PropertyValue(0); _current = current ?? new PropertyValue(0);
@ -70,12 +94,18 @@ public sealed class StatusValue
} }
} }
/// <summary>
/// 增加值
/// </summary>
public void Add(float value) public void Add(float value)
{ {
_current.Add(value); _current.Add(value);
CheckThresholds(); CheckThresholds();
} }
/// <summary>
/// 减少值
/// </summary>
public void Subtract(float value) public void Subtract(float value)
{ {
_current.Subtract(value); _current.Subtract(value);

View File

@ -18,25 +18,51 @@ namespace Models;
/// </summary> /// </summary>
public sealed class StudentModel public sealed class StudentModel
{ {
/// <summary>
/// 学生类型
/// </summary>
public enum StudentType public enum StudentType
{ {
MasterCandidate, MasterCandidate, // 硕士生
DoctorCandidate DoctorCandidate // 博士生
} }
/// <summary>
/// 核心单位数据
/// </summary>
public UnitModel Core { get; } public UnitModel Core { get; }
/// <summary>
/// 学生类型
/// </summary>
public StudentType Type { get; private set; } public StudentType Type { get; private set; }
/// <summary>
/// 进度数据
/// </summary>
public StudentProgress Progress { get; } public StudentProgress Progress { get; }
/// <summary>
/// 贡献记录
/// </summary>
public StudentContributions Contributions { get; } public StudentContributions Contributions { get; }
/// <summary>
/// 名字(便捷访问)
/// </summary>
public string Name public string Name
{ {
get => Core.Name; get => Core.Name;
set => Core.Name = value; set => Core.Name = value;
} }
/// <summary>
/// 特质列表(便捷访问)
/// </summary>
public List<string> Traits => Core.Tags.TraitIds; public List<string> Traits => Core.Tags.TraitIds;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="name">名字</param>
/// <param name="random">随机数生成器</param>
public StudentModel(string name, Random random = null) public StudentModel(string name, Random random = null)
{ {
var rng = random ?? Random.Shared; var rng = random ?? Random.Shared;
@ -52,8 +78,17 @@ public sealed class StudentModel
/// </summary> /// </summary>
public sealed class StudentProgress public sealed class StudentProgress
{ {
/// <summary>
/// 体力
/// </summary>
public StatusValue Stamina { get; set; } public StatusValue Stamina { get; set; }
/// <summary>
/// 忠诚度
/// </summary>
public StatusValue Loyalty { get; set; } public StatusValue Loyalty { get; set; }
/// <summary>
/// 年级
/// </summary>
public PropertyValue Grade { get; set; } public PropertyValue Grade { get; set; }
public StudentProgress(StudentModel.StudentType type) public StudentProgress(StudentModel.StudentType type)
@ -70,5 +105,8 @@ public sealed class StudentProgress
/// </summary> /// </summary>
public sealed class StudentContributions public sealed class StudentContributions
{ {
/// <summary>
/// 按任务ID索引的贡献值
/// </summary>
public Dictionary<Guid, PropertyValue> ByTask { get; } = new(); public Dictionary<Guid, PropertyValue> ByTask { get; } = new();
} }

View File

@ -14,30 +14,62 @@ namespace Models;
/// </summary> /// </summary>
public sealed class ArchetypeDefinition public sealed class ArchetypeDefinition
{ {
/// <summary>
/// 基础头部信息
/// </summary>
public DefinitionHeader Header { get; set; } = new(); public DefinitionHeader Header { get; set; } = new();
/// <summary>
/// 羁绊层级列表
/// </summary>
public List<SynergyTier> Tiers { get; } = new(); public List<SynergyTier> Tiers { get; } = new();
} }
public sealed class RoleDefinition public sealed class RoleDefinition
{ {
/// <summary>
/// 基础头部信息
/// </summary>
public DefinitionHeader Header { get; set; } = new(); public DefinitionHeader Header { get; set; } = new();
/// <summary>
/// 羁绊层级列表
/// </summary>
public List<SynergyTier> Tiers { get; } = new(); public List<SynergyTier> Tiers { get; } = new();
// 如果是学科限定角色(如炼金术士/极客),在这里配置允许的学科 Id。 // 如果是学科限定角色(如炼金术士/极客),在这里配置允许的学科 Id。
/// <summary>
/// 允许的学科ID列表
/// </summary>
public List<string> AllowedDisciplineIds { get; } = new(); public List<string> AllowedDisciplineIds { get; } = new();
} }
public sealed class TraitDefinition public sealed class TraitDefinition
{ {
/// <summary>
/// 基础头部信息
/// </summary>
public DefinitionHeader Header { get; set; } = new(); public DefinitionHeader Header { get; set; } = new();
/// <summary>
/// 修正包
/// </summary>
public ModifierBundle Modifiers { get; set; } = new(); public ModifierBundle Modifiers { get; set; } = new();
/// <summary>
/// 规则ID列表
/// </summary>
public List<string> RuleIds { get; } = new(); public List<string> RuleIds { get; } = new();
} }
public sealed class SynergyTier public sealed class SynergyTier
{ {
/// <summary>
/// 需求数量
/// </summary>
public int RequiredCount { get; set; } public int RequiredCount { get; set; }
/// <summary>
/// 修正包
/// </summary>
public ModifierBundle Modifiers { get; set; } = new(); public ModifierBundle Modifiers { get; set; } = new();
/// <summary>
/// 规则ID列表
/// </summary>
public List<string> RuleIds { get; } = new(); public List<string> RuleIds { get; } = new();
} }

View File

@ -0,0 +1 @@
uid://vqos5kin02p1

View File

@ -33,10 +33,10 @@ public enum TaskKind
public enum TaskDifficulty public enum TaskDifficulty
{ {
Water, Water, // 水
Standard, Standard, // 标准
Hardcore, Hardcore, // 硬核
BlackBox BlackBox // 黑箱
} }
public enum PaperRank public enum PaperRank
@ -98,4 +98,3 @@ public sealed class TaskRewards
public List<string> ItemIds { get; } = new(); public List<string> ItemIds { get; } = new();
public List<string> BuffIds { get; } = new(); public List<string> BuffIds { get; } = new();
} }

View File

@ -0,0 +1 @@
uid://bth3oambl7pnk

View File

@ -18,6 +18,9 @@ namespace Models;
/// </summary> /// </summary>
public static class UnitComponents public static class UnitComponents
{ {
/// <summary>
/// 网格位置组件
/// </summary>
public readonly struct GridPosition public readonly struct GridPosition
{ {
public int X { get; } public int X { get; }
@ -32,6 +35,9 @@ public static class UnitComponents
public static GridPosition Zero => new(0, 0); public static GridPosition Zero => new(0, 0);
} }
/// <summary>
/// 单位身份组件
/// </summary>
public sealed class UnitIdentity public sealed class UnitIdentity
{ {
public Guid Id { get; } = Guid.NewGuid(); public Guid Id { get; } = Guid.NewGuid();
@ -45,6 +51,9 @@ public static class UnitComponents
} }
} }
/// <summary>
/// 单位属性组件
/// </summary>
public sealed class UnitAttributes public sealed class UnitAttributes
{ {
public PropertyValue Academic { get; set; } public PropertyValue Academic { get; set; }
@ -66,6 +75,9 @@ public static class UnitComponents
} }
} }
/// <summary>
/// 单位状态组件
/// </summary>
public sealed class UnitStatuses public sealed class UnitStatuses
{ {
public StatusValue Stress { get; set; } public StatusValue Stress { get; set; }
@ -82,6 +94,9 @@ public static class UnitComponents
} }
} }
/// <summary>
/// 单位标签组件
/// </summary>
public sealed class UnitTags public sealed class UnitTags
{ {
public string DisciplineId { get; set; } public string DisciplineId { get; set; }
@ -92,17 +107,26 @@ public static class UnitComponents
public bool HasTrait(string traitId) => TraitIds.Contains(traitId); public bool HasTrait(string traitId) => TraitIds.Contains(traitId);
} }
/// <summary>
/// 单位指派组件
/// </summary>
public sealed class UnitAssignment public sealed class UnitAssignment
{ {
public Guid? CurrentTaskId { get; set; } public Guid? CurrentTaskId { get; set; }
} }
/// <summary>
/// 单位位置组件
/// </summary>
public sealed class UnitPlacement public sealed class UnitPlacement
{ {
public GridPosition Current { get; set; } = GridPosition.Zero; public GridPosition Current { get; set; } = GridPosition.Zero;
public GridPosition Target { get; set; } = GridPosition.Zero; public GridPosition Target { get; set; } = GridPosition.Zero;
} }
/// <summary>
/// 单位装备组件
/// </summary>
public sealed class UnitEquipment public sealed class UnitEquipment
{ {
public List<string> EquippedItemIds { get; } = new(); public List<string> EquippedItemIds { get; } = new();

View File

@ -0,0 +1 @@
uid://huerc661rxe7

View File

@ -17,20 +17,49 @@ namespace Models;
/// </summary> /// </summary>
public sealed class UnitModel public sealed class UnitModel
{ {
/// <summary>
/// 身份组件
/// </summary>
public UnitComponents.UnitIdentity Identity { get; } public UnitComponents.UnitIdentity Identity { get; }
/// <summary>
/// 属性组件
/// </summary>
public UnitComponents.UnitAttributes Attributes { get; } public UnitComponents.UnitAttributes Attributes { get; }
/// <summary>
/// 状态组件
/// </summary>
public UnitComponents.UnitStatuses Statuses { get; } public UnitComponents.UnitStatuses Statuses { get; }
/// <summary>
/// 标签组件
/// </summary>
public UnitComponents.UnitTags Tags { get; } public UnitComponents.UnitTags Tags { get; }
/// <summary>
/// 指派组件
/// </summary>
public UnitComponents.UnitAssignment Assignment { get; } public UnitComponents.UnitAssignment Assignment { get; }
/// <summary>
/// 位置组件
/// </summary>
public UnitComponents.UnitPlacement Placement { get; } public UnitComponents.UnitPlacement Placement { get; }
/// <summary>
/// 装备组件
/// </summary>
public UnitComponents.UnitEquipment Equipment { get; } public UnitComponents.UnitEquipment Equipment { get; }
/// <summary>
/// 名字(便捷访问)
/// </summary>
public string Name public string Name
{ {
get => Identity.Name; get => Identity.Name;
set => Identity.Name = value; set => Identity.Name = value;
} }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="name">名字</param>
/// <param name="random">随机数生成器</param>
public UnitModel(string name, Random random = null) public UnitModel(string name, Random random = null)
{ {
Identity = new UnitComponents.UnitIdentity(name); Identity = new UnitComponents.UnitIdentity(name);

View File

@ -2,6 +2,9 @@ using Godot;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
/// <summary>
/// 玩家数据管理类
/// </summary>
public partial class Player : Node public partial class Player : Node
{ {
/// <summary> /// <summary>
@ -37,12 +40,23 @@ public partial class Player : Node
public int Total => Facility + Operational + Labor; public int Total => Facility + Operational + Labor;
} }
/// <summary>
/// 时间轴类型
/// </summary>
public class TimelineType public class TimelineType
{ {
/// <summary>
/// 内部日期
/// </summary>
public DateOnly InternalDate { get; set; } public DateOnly InternalDate { get; set; }
private Dictionary<DateOnly, HashSet<Guid>> _events; private Dictionary<DateOnly, HashSet<Guid>> _events;
/// <summary>
/// 订阅事件
/// </summary>
/// <param name="date">日期</param>
/// <param name="eventUuid">事件UUID</param>
public void Subscribe(DateOnly date, Guid eventUuid) public void Subscribe(DateOnly date, Guid eventUuid)
{ {
_events ??= new Dictionary<DateOnly, HashSet<Guid>>(); _events ??= new Dictionary<DateOnly, HashSet<Guid>>();
@ -50,6 +64,11 @@ public partial class Player : Node
_events[date].Add(eventUuid); _events[date].Add(eventUuid);
} }
/// <summary>
/// 取消订阅事件
/// </summary>
/// <param name="date">日期</param>
/// <param name="eventUuid">事件UUID</param>
public void Unsubscribe(DateOnly date, Guid eventUuid) public void Unsubscribe(DateOnly date, Guid eventUuid)
{ {
if (_events == null) return; if (_events == null) return;
@ -57,6 +76,9 @@ public partial class Player : Node
_events[date].Remove(eventUuid); _events[date].Remove(eventUuid);
} }
/// <summary>
/// 进入下一天
/// </summary>
private void NextDay() private void NextDay()
{ {
if (_events == null) return; if (_events == null) return;
@ -78,27 +100,53 @@ public partial class Player : Node
} }
} }
/// <summary>
/// 附加计时器
/// </summary>
/// <param name="ticker">计时器</param>
public void Attach(Timer ticker) public void Attach(Timer ticker)
{ {
ticker.Timeout += NextDay; ticker.Timeout += NextDay;
} }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="startDate">开始日期</param>
public TimelineType(DateOnly startDate) public TimelineType(DateOnly startDate)
{ {
InternalDate = startDate; InternalDate = startDate;
} }
/// <summary>
/// 事件触发委托
/// </summary>
public delegate void EventTriggeredEventHandler(DateOnly date, Guid eventUuid); public delegate void EventTriggeredEventHandler(DateOnly date, Guid eventUuid);
/// <summary>
/// 事件触发事件
/// </summary>
public event EventTriggeredEventHandler OnEventTriggered; public event EventTriggeredEventHandler OnEventTriggered;
/// <summary>
/// 日期变更委托
/// </summary>
public delegate void DayChangedEventHandler(DateOnly date); public delegate void DayChangedEventHandler(DateOnly date);
/// <summary>
/// 日期变更事件
/// </summary>
public event DayChangedEventHandler OnDayChanged; public event DayChangedEventHandler OnDayChanged;
} }
/// <summary>
/// 预算实例
/// </summary>
public static BudgetType Budget { get; set; } = new(); public static BudgetType Budget { get; set; } = new();
/// <summary>
/// 时间轴实例
/// </summary>
public static readonly TimelineType Timeline = new(DateOnly.Parse("2024/11/17")); public static readonly TimelineType Timeline = new(DateOnly.Parse("2024/11/17"));
/// <summary> /// <summary>
@ -117,12 +165,19 @@ public partial class Player : Node
public static DateOnly Date { get; set; } public static DateOnly Date { get; set; }
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready() public override void _Ready()
{ {
} }
// Called every frame. 'delta' is the elapsed time since the previous frame. // Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public override void _Process(double delta) public override void _Process(double delta)
{ {
} }

View File

@ -1,18 +1,31 @@
using Godot; using Godot;
using System; using System;
/// <summary>
/// 资源管理类
/// </summary>
public partial class Res : Node public partial class Res : Node
{ {
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready() public override void _Ready()
{ {
} }
// Called every frame. 'delta' is the elapsed time since the previous frame. // Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public override void _Process(double delta) public override void _Process(double delta)
{ {
} }
/// <summary>
/// 资源类型枚举
/// </summary>
public enum Type public enum Type
{ {
Accessory, Accessory,
@ -23,16 +36,32 @@ public partial class Res : Node
Phone Phone
} }
/// <summary>
/// 获取随机资源路径
/// </summary>
/// <param name="resType">资源类型</param>
/// <returns>资源路径</returns>
public static string GetRandom(Type resType) public static string GetRandom(Type resType)
{ {
return GetRandom(resType, false); return GetRandom(resType, false);
} }
/// <summary>
/// 转换为16x16精灵路径
/// </summary>
/// <param name="path">原路径</param>
/// <returns>16x16路径</returns>
private static string To16Path(string path) private static string To16Path(string path)
{ {
return path.Replace("_48x48_", "_").Replace("_48x48", ""); return path.Replace("_48x48_", "_").Replace("_48x48", "");
} }
/// <summary>
/// 获取随机资源路径可选16x16
/// </summary>
/// <param name="resType">资源类型</param>
/// <param name="use16X16Sprites">是否使用16x16精灵</param>
/// <returns>资源路径</returns>
public static string GetRandom(Type resType, bool use16X16Sprites) public static string GetRandom(Type resType, bool use16X16Sprites)
{ {
var resources = allResources[(int)resType]; var resources = allResources[(int)resType];
@ -48,6 +77,9 @@ public partial class Res : Node
return ResourceLoader.Exists(path16) ? path16 : path; return ResourceLoader.Exists(path16) ? path16 : path;
} }
/// <summary>
/// 身体资源列表
/// </summary>
public static readonly string[] Body = public static readonly string[] Body =
[ [
"res://resources/characters/bodies/Body_48x48_01.png", "res://resources/characters/bodies/Body_48x48_01.png",
@ -60,6 +92,9 @@ public partial class Res : Node
"res://resources/characters/bodies/Body_48x48_08.png" "res://resources/characters/bodies/Body_48x48_08.png"
]; ];
/// <summary>
/// 饰品资源列表
/// </summary>
public static readonly string[] Accessory = public static readonly string[] Accessory =
[ [
"res://resources/characters/accessories/Accessory_01_Ladybug_48x48_01.png", "res://resources/characters/accessories/Accessory_01_Ladybug_48x48_01.png",
@ -145,6 +180,9 @@ public partial class Res : Node
"res://resources/characters/accessories/Accessory_19_Party_Cone_48x48_04.png" "res://resources/characters/accessories/Accessory_19_Party_Cone_48x48_04.png"
]; ];
/// <summary>
/// 眼睛资源列表
/// </summary>
public static readonly string[] Eye = public static readonly string[] Eye =
[ [
"res://resources/characters/eyes/Eyes_48x48_01.png", "res://resources/characters/eyes/Eyes_48x48_01.png",
@ -156,6 +194,9 @@ public partial class Res : Node
"res://resources/characters/eyes/Eyes_48x48_07.png" "res://resources/characters/eyes/Eyes_48x48_07.png"
]; ];
/// <summary>
/// 发型资源列表
/// </summary>
public static readonly string[] Hair = public static readonly string[] Hair =
[ [
"res://resources/characters/hairstyles/Hairstyle_01_48x48_01.png", "res://resources/characters/hairstyles/Hairstyle_01_48x48_01.png",
@ -360,6 +401,9 @@ public partial class Res : Node
"res://resources/characters/hairstyles/Hairstyle_29_48x48_06.png" "res://resources/characters/hairstyles/Hairstyle_29_48x48_06.png"
]; ];
/// <summary>
/// 服装资源列表
/// </summary>
public static readonly string[] Outfit = public static readonly string[] Outfit =
[ [
"res://resources/characters/outfits/Outfit_01_48x48_01.png", "res://resources/characters/outfits/Outfit_01_48x48_01.png",
@ -496,6 +540,9 @@ public partial class Res : Node
"res://resources/characters/outfits/Outfit_33_48x48_03.png" "res://resources/characters/outfits/Outfit_33_48x48_03.png"
]; ];
/// <summary>
/// 手机资源列表
/// </summary>
public static readonly string[] Smartphone = public static readonly string[] Smartphone =
[ [
"res://resources/characters/smartphones/Smartphone_48x48_1.png", "res://resources/characters/smartphones/Smartphone_48x48_1.png",

View File

@ -1,9 +1,15 @@
using Godot; using Godot;
using System; using System;
/// <summary>
/// 场景过渡控制器
/// </summary>
public partial class SceneTransit : CanvasLayer public partial class SceneTransit : CanvasLayer
{ {
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready() public override void _Ready()
{ {
Layer = -1; Layer = -1;
@ -11,10 +17,21 @@ public partial class SceneTransit : CanvasLayer
} }
// Called every frame. 'delta' is the elapsed time since the previous frame. // Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public override void _Process(double delta) public override void _Process(double delta)
{ {
} }
/// <summary>
/// 切换场景
/// </summary>
/// <param name="nextScene">下一场景路径</param>
/// <param name="needLoadResources">是否需要加载资源</param>
/// <param name="animation">过渡动画名称</param>
/// <param name="waitSecond">等待时间</param>
public async void Transit(string nextScene, bool needLoadResources = true, string animation = "transit", float waitSecond = 0) public async void Transit(string nextScene, bool needLoadResources = true, string animation = "transit", float waitSecond = 0)
{ {
SetProcess(true); SetProcess(true);

View File

@ -5,15 +5,34 @@ using System.Linq;
using Models; using Models;
// ReSharper disable CheckNamespace // ReSharper disable CheckNamespace
/// <summary>
/// 学生角色控制器
/// </summary>
public partial class Student : CharacterBody2D public partial class Student : CharacterBody2D
{ {
/// <summary>
/// 移动速度
/// </summary>
public float Speed { get; set; } = 8.0f; public float Speed { get; set; } = 8.0f;
/// <summary>
/// 跳跃速度
/// </summary>
public const float JumpVelocity = -400.0f; public const float JumpVelocity = -400.0f;
/// <summary>
/// 是否使用16x16精灵
/// </summary>
[Export] public bool Use16X16Sprites { get; set; } [Export] public bool Use16X16Sprites { get; set; }
// --- MVP: Model Binding --- // --- MVP: Model Binding ---
/// <summary>
/// 学生数据模型
/// </summary>
public StudentModel Model { get; private set; } public StudentModel Model { get; private set; }
/// <summary>
/// 绑定数据模型
/// </summary>
/// <param name="model">学生模型</param>
public void BindData(StudentModel model) public void BindData(StudentModel model)
{ {
Model = model; Model = model;
@ -21,15 +40,35 @@ public partial class Student : CharacterBody2D
} }
// -------------------------- // --------------------------
/// <summary>
/// 下一个状态类型(未使用)
/// </summary>
public int NextType = -1; public int NextType = -1;
private Queue<Vector2I> _pathToGo = new(); private Queue<Vector2I> _pathToGo = new();
private AnimationPlayer _animationPlayer; private AnimationPlayer _animationPlayer;
/// <summary>
/// 角色状态枚举
/// </summary>
public enum CharacterState { Idle, Walking, Sitting, SittingDown, StandingUp } public enum CharacterState { Idle, Walking, Sitting, SittingDown, StandingUp }
/// <summary>
/// 当前状态
/// </summary>
public CharacterState State { get; set; } = CharacterState.Idle; public CharacterState State { get; set; } = CharacterState.Idle;
/// <summary>
/// 方向枚举
/// </summary>
public enum Direction { Up, Down, Left, Right } public enum Direction { Up, Down, Left, Right }
/// <summary>
/// 目标方向
/// </summary>
public Direction TargetDirection { get; set; } = Direction.Up; public Direction TargetDirection { get; set; } = Direction.Up;
/// <summary>
/// 状态队列
/// </summary>
public Queue<CharacterState> StateQueue { get; set; } = new(); public Queue<CharacterState> StateQueue { get; set; } = new();
private Vector2I _targetSpecialPosition; private Vector2I _targetSpecialPosition;
@ -54,6 +93,11 @@ public partial class Student : CharacterBody2D
State = StateQueue.Dequeue(); State = StateQueue.Dequeue();
GlobalPosition = _lastWalkablePosition; GlobalPosition = _lastWalkablePosition;
} }
/// <summary>
/// 物理处理
/// </summary>
/// <param name="delta">时间间隔</param>
public override void _PhysicsProcess(double delta) public override void _PhysicsProcess(double delta)
{ {
if (StateQueue.Count > 0) { if (StateQueue.Count > 0) {
@ -141,6 +185,9 @@ public partial class Student : CharacterBody2D
// MoveAndSlide(); // MoveAndSlide();
} }
/// <summary>
/// 准备就绪时调用
/// </summary>
public override void _Ready() public override void _Ready()
{ {
base._Ready(); base._Ready();
@ -163,6 +210,10 @@ public partial class Student : CharacterBody2D
} }
} }
/// <summary>
/// 移动跟随路径
/// </summary>
/// <param name="path">路径点列表</param>
public void MoveFollowPath(List<Vector2I> path) public void MoveFollowPath(List<Vector2I> path)
{ {
foreach (var p in path) foreach (var p in path)
@ -171,8 +222,14 @@ public partial class Student : CharacterBody2D
} }
} }
/// <summary>
/// 关联的格子ID
/// </summary>
public Guid CubeId; public Guid CubeId;
/// <summary>
/// 随机前往某处
/// </summary>
public void GoTo() { public void GoTo() {
if (State == CharacterState.SittingDown || State == CharacterState.StandingUp || State == CharacterState.Walking) if (State == CharacterState.SittingDown || State == CharacterState.StandingUp || State == CharacterState.Walking)
{ {
@ -201,6 +258,9 @@ public partial class Student : CharacterBody2D
RandomChangeBody(); RandomChangeBody();
} }
/// <summary>
/// 前往座位
/// </summary>
public void GoToSeat() { public void GoToSeat() {
if (State == CharacterState.SittingDown || State == CharacterState.StandingUp || State == CharacterState.Walking) if (State == CharacterState.SittingDown || State == CharacterState.StandingUp || State == CharacterState.Walking)
{ {
@ -248,6 +308,9 @@ public partial class Student : CharacterBody2D
RandomChangeBody(); RandomChangeBody();
} }
/// <summary>
/// 随机更换外观
/// </summary>
private void RandomChangeBody() private void RandomChangeBody()
{ {
GetNode<Sprite2D>("parts/body").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Body, Use16X16Sprites)); GetNode<Sprite2D>("parts/body").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Body, Use16X16Sprites));

View File

@ -2,20 +2,33 @@ using Godot;
using System; using System;
using System.IO; using System.IO;
/// <summary>
/// 学生姓名生成器
/// </summary>
public partial class StudentName : Node public partial class StudentName : Node
{ {
// Called when the node enters the scene tree for the first time. // Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready() public override void _Ready()
{ {
} }
// Called every frame. 'delta' is the elapsed time since the previous frame. // Called every frame. 'delta' is the elapsed time since the previous frame.
/// <summary>
/// 每帧更新
/// </summary>
/// <param name="delta">帧间隔</param>
public override void _Process(double delta) public override void _Process(double delta)
{ {
} }
public string GenerateName() /// <summary>
{ /// 生成随机姓名
string[] = { "大", "小", "飞", "傻", "呆", "雷", "东", "西", "王", "赵", "李", "张", "刘", "周" ,"赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈", "褚", "卫", "蒋", "沈", "韩", "杨", "朱", "秦", "尤", "许", "何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏", "陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章", "云", "苏", "潘", "葛", "奚", "范", "彭", "郎", "鲁", "韦", "昌", "马", "苗", "凤", "花", "方", "俞", "任", "袁", "柳", "酆", "鲍", "史", "唐", "费", "廉", "岑", "薛", "雷", "贺", "倪", "汤", "滕", "殷", "罗", "毕", "郝", "邬", "安", "常", "乐", "于", "时", "傅", "皮", "卞", "齐", "康", "伍", "余", "元", "卜", "顾", "孟", "平", "黄", "和", "穆", "萧", "尹", "姚", "邵", "湛", "汪", "祁", "毛", "禹", "狄", "米", "贝", "明", "臧", "计", "伏", "成", "戴", "谈", "宋", "茅", "庞", "熊", "纪", "舒", "屈", "项", "祝", "董", "梁", "杜", "阮", "蓝", "闵", "席", "季", "麻", "强", "贾", "路", "娄", "危", "江", "童", "颜", "郭", "梅", "盛", "林", "刁", "钟", "徐", "邱", "骆", "高", "夏", "蔡", "田", "樊", "胡", "凌", "霍", "虞", "万", "支", "柯", "昝", "管", "卢", "莫", "经", "房", "裘", "缪", "干", "解", "应", "宗", "丁", "宣", "贲", "邓", "郁", "单", "杭", "洪", "包", "诸", "左", "石", "崔", "吉", "钮", "龚", "程", "嵇", "邢", "滑", "裴", "陆", "荣", "翁", "荀", "羊", "於", "惠", "甄", "麴", "家", "封", "芮", "羿", "储", "靳", "汲", "邴", "糜", "松", "井", "段", "富", "巫", "乌", "焦", "巴", "弓", "牧", "隗", "山", "谷", "车", "侯", "宓", "蓬", "全", "郗", "班", "仰", "秋", "仲", "伊", "宫", "宁", "仇", "栾", "暴", "甘", "钭", "厉", "戎", "祖", "武", "符", "刘", "景", "詹", "束", "龙", "叶", "幸", "司", "韶", "郜", "黎", "蓟", "薄", "印", "宿", "白", "怀", "蒲", "邰", "从", "鄂", "索", "咸", "籍", "赖", "卓", "蔺", "屠", "蒙", "池", "乔", "阴", "欎", "胥", "能", "苍", "双", "闻", "莘", "党", "翟", "谭", "贡", "劳", "逄", "姬", "申", "扶", "堵", "冉", "宰", "郦", "雍", "舄", "璩", "桑", "桂", "濮", "牛", "寿", "通", "边", "扈", "燕", "冀", "郏", "浦", "尚", "农", "温", "别", "庄", "晏", "柴", "瞿", "阎", "充", "慕", "连", "茹", "习", "宦", "艾", "鱼", "容", "向", "古", "易", "慎", "戈", "廖", "庾", "终", "暨", "居", "衡", "步", "都", "耿", "满", "弘", "匡", "国", "文", "寇", "广", "禄", "阙", "东", "殴", "殳", "沃", "利", "蔚", "越", "夔", "隆", "师", "巩", "厍", "聂", "晁", "勾", "敖", "融", "冷", "訾", "辛", "阚", "那", "简", "饶", "空", "曾", "毋", "沙", "乜", "养", "鞠", "须", "丰", "巢", "关", "蒯", "相", "查", "後", "荆", "红", "游", "竺", "权", "逯", "盖", "益", "桓", "公", "万俟", "司马", "上官", "欧阳", "夏侯", "诸葛", "闻人", "东方", "赫连", "皇甫", "尉迟", "公羊", "澹台", "公冶", "宗政", "濮阳", "淳于", "单于", "太叔", "申屠", "公孙", "仲孙", "轩辕", "令狐", "钟离", "宇文", "长孙", "慕容", "鲜于", "闾丘", "司徒", "司空", "亓官", "司寇", "仉", "督", "子车", "颛孙", "端木", "巫马", "公西", "漆雕", "乐正", "壤驷", "公良", "拓跋", "夹谷", "宰父", "谷梁", "晋", "楚", "闫", "法", "汝", "鄢", "涂", "钦", "段干", "百里", "东郭", "南门", "呼延", "归", "海", "羊舌", "微生", "岳", "帅", "缑", "亢", "况", "后", "有", "琴", "梁丘", "左丘", "东门", "西门", "商", "牟", "佘", "佴", "伯", "赏", "南宫", "墨", "哈", "谯", "笪", "年", "爱", "阳", "佟", "第五", "言", "福", "百", "家", "姓", "终"}; /// </summary>
/// <returns>随机姓名</returns>
public string GenerateName()
{ string[] = { "大", "小", "飞", "傻", "呆", "雷", "东", "西", "王", "赵", "李", "张", "刘", "周" ,"赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈", "褚", "卫", "蒋", "沈", "韩", "杨", "朱", "秦", "尤", "许", "何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏", "陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章", "云", "苏", "潘", "葛", "奚", "范", "彭", "郎", "鲁", "韦", "昌", "马", "苗", "凤", "花", "方", "俞", "任", "袁", "柳", "酆", "鲍", "史", "唐", "费", "廉", "岑", "薛", "雷", "贺", "倪", "汤", "滕", "殷", "罗", "毕", "郝", "邬", "安", "常", "乐", "于", "时", "傅", "皮", "卞", "齐", "康", "伍", "余", "元", "卜", "顾", "孟", "平", "黄", "和", "穆", "萧", "尹", "姚", "邵", "湛", "汪", "祁", "毛", "禹", "狄", "米", "贝", "明", "臧", "计", "伏", "成", "戴", "谈", "宋", "茅", "庞", "熊", "纪", "舒", "屈", "项", "祝", "董", "梁", "杜", "阮", "蓝", "闵", "席", "季", "麻", "强", "贾", "路", "娄", "危", "江", "童", "颜", "郭", "梅", "盛", "林", "刁", "钟", "徐", "邱", "骆", "高", "夏", "蔡", "田", "樊", "胡", "凌", "霍", "虞", "万", "支", "柯", "昝", "管", "卢", "莫", "经", "房", "裘", "缪", "干", "解", "应", "宗", "丁", "宣", "贲", "邓", "郁", "单", "杭", "洪", "包", "诸", "左", "石", "崔", "吉", "钮", "龚", "程", "嵇", "邢", "滑", "裴", "陆", "荣", "翁", "荀", "羊", "於", "惠", "甄", "麴", "家", "封", "芮", "羿", "储", "靳", "汲", "邴", "糜", "松", "井", "段", "富", "巫", "乌", "焦", "巴", "弓", "牧", "隗", "山", "谷", "车", "侯", "宓", "蓬", "全", "郗", "班", "仰", "秋", "仲", "伊", "宫", "宁", "仇", "栾", "暴", "甘", "钭", "厉", "戎", "祖", "武", "符", "刘", "景", "詹", "束", "龙", "叶", "幸", "司", "韶", "郜", "黎", "蓟", "薄", "印", "宿", "白", "怀", "蒲", "邰", "从", "鄂", "索", "咸", "籍", "赖", "卓", "蔺", "屠", "蒙", "池", "乔", "阴", "欎", "胥", "能", "苍", "双", "闻", "莘", "党", "翟", "谭", "贡", "劳", "逄", "姬", "申", "扶", "堵", "冉", "宰", "郦", "雍", "舄", "璩", "桑", "桂", "濮", "牛", "寿", "通", "边", "扈", "燕", "冀", "郏", "浦", "尚", "农", "温", "别", "庄", "晏", "柴", "瞿", "阎", "充", "慕", "连", "茹", "习", "宦", "艾", "鱼", "容", "向", "古", "易", "慎", "戈", "廖", "庾", "终", "暨", "居", "衡", "步", "都", "耿", "满", "弘", "匡", "国", "文", "寇", "广", "禄", "阙", "东", "殴", "殳", "沃", "利", "蔚", "越", "夔", "隆", "师", "巩", "厍", "聂", "晁", "勾", "敖", "融", "冷", "訾", "辛", "阚", "那", "简", "饶", "空", "曾", "毋", "沙", "乜", "养", "鞠", "须", "丰", "巢", "关", "蒯", "相", "查", "後", "荆", "红", "游", "竺", "权", "逯", "盖", "益", "桓", "公", "万俟", "司马", "上官", "欧阳", "夏侯", "诸葛", "闻人", "东方", "赫连", "皇甫", "尉迟", "公羊", "澹台", "公冶", "宗政", "濮阳", "淳于", "单于", "太叔", "申屠", "公孙", "仲孙", "轩辕", "令狐", "钟离", "宇文", "长孙", "慕容", "鲜于", "闾丘", "司徒", "司空", "亓官", "司寇", "仉", "督", "子车", "颛孙", "端木", "巫马", "公西", "漆雕", "乐正", "壤驷", "公良", "拓跋", "夹谷", "宰父", "谷梁", "晋", "楚", "闫", "法", "汝", "鄢", "涂", "钦", "段干", "百里", "东郭", "南门", "呼延", "归", "海", "羊舌", "微生", "岳", "帅", "缑", "亢", "况", "后", "有", "琴", "梁丘", "左丘", "东门", "西门", "商", "牟", "佘", "佴", "伯", "赏", "南宫", "墨", "哈", "谯", "笪", "年", "爱", "阳", "佟", "第五", "言", "福", "百", "家", "姓", "终"};
string[] = { "金", "猪", "兔", "猫", "鱼", "蛋", "胖", "大", "傻", "酷", "蠢", "聪", "萌", "暴","靖", "铭", "琛", "川", "承", "司", "斯", "宗", "骁", "聪", "在", "钩", "锦", "铎", "楚", "铮", "钦", "则", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "清", "澈", "泫", "浚", "润", "泽", "向", "凡", "文", "浦", "洲", "珩", "玄", "洋", "淮", "雨", "子", "云", "卓", "昱", "南", "晨", "知", "宁", "年", "易", "晗", "炎", "焕", "哲", "煦", "旭", "明", "阳", "朗", "典", "辰", "宸", "野", "安", "为", "亦", "岚", "也", "围", "以", "延", "允", "容", "恩", "衡", "宇", "硕", "已", "意", "也", "坤", "辰", "伊", "米", "安", "恩", "以", "容", "宛", "岚", "又", "衣", "亚", "悠", "允", "画", "灿", "夏", "珞", "煊", "晴", "彤", "诺", "宁", "恬", "钧", "灵", "昭", "琉", "晨", "曦", "南", "毓", "冉", "妍", "澜", "淇", "沐", "潆", "盈", "雨", "文", "冰", "雯", "溪", "子", "云", "汐", "潞", "淇", "妙", "涵", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "真", "心", "新", "悦", "西", "兮", "楚", "初", "千", "锐", "素", "锦", "静", "镜", "斯", "舒", "瑜", "童" }; string[] = { "金", "猪", "兔", "猫", "鱼", "蛋", "胖", "大", "傻", "酷", "蠢", "聪", "萌", "暴","靖", "铭", "琛", "川", "承", "司", "斯", "宗", "骁", "聪", "在", "钩", "锦", "铎", "楚", "铮", "钦", "则", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "清", "澈", "泫", "浚", "润", "泽", "向", "凡", "文", "浦", "洲", "珩", "玄", "洋", "淮", "雨", "子", "云", "卓", "昱", "南", "晨", "知", "宁", "年", "易", "晗", "炎", "焕", "哲", "煦", "旭", "明", "阳", "朗", "典", "辰", "宸", "野", "安", "为", "亦", "岚", "也", "围", "以", "延", "允", "容", "恩", "衡", "宇", "硕", "已", "意", "也", "坤", "辰", "伊", "米", "安", "恩", "以", "容", "宛", "岚", "又", "衣", "亚", "悠", "允", "画", "灿", "夏", "珞", "煊", "晴", "彤", "诺", "宁", "恬", "钧", "灵", "昭", "琉", "晨", "曦", "南", "毓", "冉", "妍", "澜", "淇", "沐", "潆", "盈", "雨", "文", "冰", "雯", "溪", "子", "云", "汐", "潞", "淇", "妙", "涵", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "真", "心", "新", "悦", "西", "兮", "楚", "初", "千", "锐", "素", "锦", "静", "镜", "斯", "舒", "瑜", "童" };
string[] = { "子", "郎", "妹", "宝", "儿", "汉", "君", "爷", "娃", "猪", "鬼", "鸟", "仔", "蛋","靖", "铭", "琛", "川", "承", "司", "斯", "宗", "骁", "聪", "在", "钩", "锦", "铎", "楚", "铮", "钦", "则", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "清", "澈", "泫", "浚", "润", "泽", "向", "凡", "文", "浦", "洲", "珩", "玄", "洋", "淮", "雨", "子", "云", "卓", "昱", "南", "晨", "知", "宁", "年", "易", "晗", "炎", "焕", "哲", "煦", "旭", "明", "阳", "朗", "典", "辰", "宸", "野", "安", "为", "亦", "岚", "也", "围", "以", "延", "允", "容", "恩", "衡", "宇", "硕", "已", "意", "也", "坤", "辰", "伊", "米", "安", "恩", "以", "容", "宛", "岚", "又", "衣", "亚", "悠", "允", "画", "灿", "夏", "珞", "煊", "晴", "彤", "诺", "宁", "恬", "钧", "灵", "昭", "琉", "晨", "曦", "南", "毓", "冉", "妍", "澜", "淇", "沐", "潆", "盈", "雨", "文", "冰", "雯", "溪", "子", "云", "汐", "潞", "淇", "妙", "涵", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "真", "心", "新", "悦", "西", "兮", "楚", "初", "千", "锐", "素", "锦", "静", "镜", "斯", "舒", "瑜", "童" }; string[] = { "子", "郎", "妹", "宝", "儿", "汉", "君", "爷", "娃", "猪", "鬼", "鸟", "仔", "蛋","靖", "铭", "琛", "川", "承", "司", "斯", "宗", "骁", "聪", "在", "钩", "锦", "铎", "楚", "铮", "钦", "则", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "清", "澈", "泫", "浚", "润", "泽", "向", "凡", "文", "浦", "洲", "珩", "玄", "洋", "淮", "雨", "子", "云", "卓", "昱", "南", "晨", "知", "宁", "年", "易", "晗", "炎", "焕", "哲", "煦", "旭", "明", "阳", "朗", "典", "辰", "宸", "野", "安", "为", "亦", "岚", "也", "围", "以", "延", "允", "容", "恩", "衡", "宇", "硕", "已", "意", "也", "坤", "辰", "伊", "米", "安", "恩", "以", "容", "宛", "岚", "又", "衣", "亚", "悠", "允", "画", "灿", "夏", "珞", "煊", "晴", "彤", "诺", "宁", "恬", "钧", "灵", "昭", "琉", "晨", "曦", "南", "毓", "冉", "妍", "澜", "淇", "沐", "潆", "盈", "雨", "文", "冰", "雯", "溪", "子", "云", "汐", "潞", "淇", "妙", "涵", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "真", "心", "新", "悦", "西", "兮", "楚", "初", "千", "锐", "素", "锦", "静", "镜", "斯", "舒", "瑜", "童" };

View File

@ -1,26 +1,32 @@
using Godot; using Godot;
using System; using System;
/// <summary>
/// 测试地图层
/// </summary>
public partial class TestMap : TileMapLayer public partial class TestMap : TileMapLayer
{ {
/// <summary> /// <summary>
/// The last cell that was highlighted. Used to avoid unnecessary updates. /// 上一次高亮的单元格。用于避免不必要的更新。
/// </summary> /// </summary>
private Vector2I _lastHighlightCell; private Vector2I _lastHighlightCell;
private readonly Vector2I _highlightTileCoord = new(0, 5); private readonly Vector2I _highlightTileCoord = new(0, 5);
private readonly Vector2I _vector2INegOne = new(-1, -1); private readonly Vector2I _vector2INegOne = new(-1, -1);
// Called when the node enters the scene tree for the first time.
// Called when the node enters the scene tree for the first time.
/// <summary>
/// 场景加载完成时调用
/// </summary>
public override void _Ready() public override void _Ready()
{ {
// Initialize the lastHighlightCell to an invalid value. // 初始化最后高亮单元格为无效值。
_lastHighlightCell = _vector2INegOne; _lastHighlightCell = _vector2INegOne;
} }
/// <summary> /// <summary>
/// Called every frame. /// 每帧更新。
/// </summary> /// </summary>
/// <param name="delta">the elapsed time since the previous frame.</param> /// <param name="delta">距离上一帧的时间间隔。</param>
public override void _Process(double delta) public override void _Process(double delta)
{ {
Vector2 mousePos = GetLocalMousePosition(); Vector2 mousePos = GetLocalMousePosition();
@ -36,5 +42,4 @@ public partial class TestMap : TileMapLayer
SetCell(cell, 0, _highlightTileCoord); SetCell(cell, 0, _highlightTileCoord);
} }
} }
} }

View File

@ -3,23 +3,39 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
/// <summary>
/// 图块映射辅助类
/// </summary>
public partial class TileMapping : Node public partial class TileMapping : Node
{ {
/// <summary> /// <summary>
/// From tile coordinates to pixel coordinates. If null, use the offset instead. /// 从图块坐标到像素坐标的映射。如果不为空,则使用此映射。
/// </summary> /// </summary>
private Dictionary<Vector2I, Vector2I> _pixelMap; private Dictionary<Vector2I, Vector2I> _pixelMap;
/// <summary> /// <summary>
/// If pixelMap is null, use this offset to convert tile coordinates to pixel coordinates. /// 如果 pixelMap 为空,使用此偏移量将图块坐标转换为像素坐标。
/// </summary> /// </summary>
private Vector2I _pixelOffset; private Vector2I _pixelOffset;
/// <summary>
/// 图块区域
/// </summary>
private Rect2I _tileRect; private Rect2I _tileRect;
/// <summary>
/// 构造函数,使用坐标映射字典
/// </summary>
/// <param name="map">坐标映射字典</param>
public TileMapping(Dictionary<Vector2I, Vector2I> map) { public TileMapping(Dictionary<Vector2I, Vector2I> map) {
_pixelMap = map; _pixelMap = map;
_pixelOffset = new Vector2I(-int.MaxValue, -int.MaxValue); _pixelOffset = new Vector2I(-int.MaxValue, -int.MaxValue);
} }
/// <summary>
/// 构造函数,使用偏移量和区域
/// </summary>
/// <param name="offset">偏移量</param>
/// <param name="rect">区域</param>
public TileMapping(Vector2I offset, Rect2I rect) { public TileMapping(Vector2I offset, Rect2I rect) {
_pixelMap = null; _pixelMap = null;
_pixelOffset = offset; _pixelOffset = offset;
@ -28,6 +44,10 @@ public partial class TileMapping : Node
public TileMapping() {} public TileMapping() {}
/// <summary>
/// 应用映射到TileMapLayer
/// </summary>
/// <param name="layer">TileMapLayer</param>
public void Apply(TileMapLayer layer) { public void Apply(TileMapLayer layer) {
// if pixelMap is not null, use all the pairs in pixelMap to set the tiles. // if pixelMap is not null, use all the pairs in pixelMap to set the tiles.
if (_pixelMap != null) { if (_pixelMap != null) {

View File

@ -6,14 +6,19 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="docs\design.md" /> <Content Include="docs\design.md" />
<Content Include="docs\校园行为系统文档.md" />
<Content Include="docs\装备与设施系统.md" /> <Content Include="docs\装备与设施系统.md" />
<Content Include="docs\学科与流派系统.md" /> <Content Include="docs\学科与流派系统.md" />
<Content Include="docs\任务与经济系统.md" /> <Content Include="docs\任务与经济系统.md" />
<Content Include="docs\角色与羁绊系统.md" /> <Content Include="docs\角色与羁绊系统.md" />
<Content Include="docs\角色与行为规则.md" />
<Content Include="README.md" /> <Content Include="README.md" />
<Content Include="resources\definitions\archetypes.json" /> <Content Include="resources\definitions\archetypes.json" />
<Content Include="resources\definitions\campus_behavior.json" />
<Content Include="resources\definitions\disciplines.json" /> <Content Include="resources\definitions\disciplines.json" />
<Content Include="resources\definitions\discipline_biology.tres" /> <Content Include="resources\definitions\discipline_biology.tres" />
<Content Include="resources\definitions\roles.json" />
<Content Include="resources\definitions\traits.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="addons\" /> <Folder Include="addons\" />