Compare commits
4 Commits
39682f14fe
...
541ba1695d
| Author | SHA1 | Date | |
|---|---|---|---|
| 541ba1695d | |||
| 3a3b09c2c5 | |||
| 206e5622bb | |||
| fc5db6496f |
@ -171,5 +171,3 @@
|
||||
* **策略性:** 不只是堆数值,而是在“做学术”和“搞政治”之间做平衡。
|
||||
* **情感反馈:** 从初期的无助,到中期的纠结,再到后期的“屠龙少年终成恶龙”或“桃李满天下”的感动。
|
||||
|
||||
===
|
||||
|
||||
|
||||
183
docs/校园行为系统文档.md
Normal file
183
docs/校园行为系统文档.md
Normal 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()`。
|
||||
132
docs/角色与行为规则.md
Normal file
132
docs/角色与行为规则.md
Normal 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
21
package-lock.json
generated
Normal 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
5
package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"yargs-parser": "^22.0.0"
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,7 @@
|
||||
},
|
||||
"Description": {
|
||||
"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" ]
|
||||
},
|
||||
@ -17,18 +17,27 @@
|
||||
"RequiredCount": 2,
|
||||
"Modifiers": {
|
||||
"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,
|
||||
"Modifiers": {
|
||||
"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": {
|
||||
"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" ]
|
||||
},
|
||||
@ -51,18 +60,129 @@
|
||||
"RequiredCount": 2,
|
||||
"Modifiers": {
|
||||
"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,
|
||||
"Modifiers": {
|
||||
"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" ]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
142
resources/definitions/campus_behavior.json
Normal file
142
resources/definitions/campus_behavior.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
438
resources/definitions/roles.json
Normal file
438
resources/definitions/roles.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
||||
283
resources/definitions/traits.json
Normal file
283
resources/definitions/traits.json
Normal 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" ]
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -1,6 +1,8 @@
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Models;
|
||||
|
||||
public partial class CampusController : Node2D
|
||||
{
|
||||
@ -10,13 +12,33 @@ public partial class CampusController : Node2D
|
||||
private Button _logToggle;
|
||||
|
||||
[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 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 Node2D _studentsRoot;
|
||||
private TopBar _topBar;
|
||||
private RichTextLabel _logLabel;
|
||||
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 _navBakePending = false;
|
||||
private bool _navBakeReady = false;
|
||||
@ -40,6 +62,20 @@ public partial class CampusController : Node2D
|
||||
_taskToggle.Toggled += OnTaskToggled;
|
||||
_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");
|
||||
_studentsRoot = GetNodeOrNull<Node2D>("Students");
|
||||
@ -60,6 +96,151 @@ public partial class CampusController : Node2D
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
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)
|
||||
@ -176,16 +357,140 @@ public partial class CampusController : Node2D
|
||||
}
|
||||
|
||||
_studentsRoot.AddChild(student);
|
||||
student.Name = $"CampusStudent_{i + 1}";
|
||||
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.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()
|
||||
{
|
||||
var points = new List<Vector2>();
|
||||
|
||||
@ -7,10 +7,9 @@
|
||||
[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"]
|
||||
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)
|
||||
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)])
|
||||
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)])
|
||||
parsed_geometry_type = 1
|
||||
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(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, 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_collision_mask = 4294967294
|
||||
agent_radius = 4.0
|
||||
|
||||
@ -2010,6 +2009,7 @@ centered = false
|
||||
|
||||
[node name="NavigationRegion2D" type="NavigationRegion2D" parent="Sprite2D"]
|
||||
navigation_polygon = SubResource("NavigationPolygon_8u8vn")
|
||||
enter_cost = 1.0
|
||||
|
||||
[node name="Log" parent="." instance=ExtResource("1_hi2p7")]
|
||||
visible = false
|
||||
@ -2019,7 +2019,6 @@ offset_right = 955.0
|
||||
offset_bottom = 535.0
|
||||
|
||||
[node name="TopBar" parent="." instance=ExtResource("2_p4tmp")]
|
||||
visible = false
|
||||
offset_bottom = 55.0
|
||||
|
||||
[node name="Task" parent="." instance=ExtResource("3_4gjr3")]
|
||||
@ -2032,3 +2031,29 @@ offset_bottom = 455.0
|
||||
[node name="TileMapLayer" type="TileMapLayer" parent="."]
|
||||
visible = false
|
||||
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)
|
||||
|
||||
@ -877,6 +877,7 @@ _data = {
|
||||
[node name="Student" type="CharacterBody2D"]
|
||||
z_index = 2
|
||||
script = ExtResource("1_oesea")
|
||||
DebugDrawPath = true
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
shape = SubResource("RectangleShape2D_opr6h")
|
||||
@ -935,9 +936,8 @@ autoplay = "RESET"
|
||||
script = ExtResource("8_kvqca")
|
||||
|
||||
[node name="NavigationAgent2D" type="NavigationAgent2D" parent="."]
|
||||
path_desired_distance = 4.0
|
||||
path_desired_distance = 16.0
|
||||
target_desired_distance = 4.0
|
||||
path_max_distance = 5000.0
|
||||
path_postprocessing = 1
|
||||
radius = 8.0
|
||||
debug_enabled = true
|
||||
|
||||
@ -10,9 +10,19 @@ public partial class TopBar : PanelContainer
|
||||
_progressBar = GetNode<ProgressBar>("HBox/YearProgress/ProgressBar");
|
||||
}
|
||||
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
public override void _Process(double delta)
|
||||
public void ResetRound(float durationSeconds)
|
||||
{
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
||||
1099
scripts/Campus/CampusBehaviorAgent.cs
Normal file
1099
scripts/Campus/CampusBehaviorAgent.cs
Normal file
File diff suppressed because it is too large
Load Diff
1
scripts/Campus/CampusBehaviorAgent.cs.uid
Normal file
1
scripts/Campus/CampusBehaviorAgent.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bd1bjq6vrs4hh
|
||||
271
scripts/Campus/CampusBehaviorConfig.cs
Normal file
271
scripts/Campus/CampusBehaviorConfig.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
1
scripts/Campus/CampusBehaviorConfig.cs.uid
Normal file
1
scripts/Campus/CampusBehaviorConfig.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dbp5g1aqtlqhi
|
||||
File diff suppressed because it is too large
Load Diff
@ -10,8 +10,15 @@ namespace Core;
|
||||
[GlobalClass]
|
||||
public partial class ContentCollectionResource : Resource, IContentResourceCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源列表
|
||||
/// </summary>
|
||||
[Export] public Array<Resource> Items { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源项
|
||||
/// </summary>
|
||||
/// <returns>资源项集合</returns>
|
||||
public IEnumerable<IContentResource> GetItems()
|
||||
{
|
||||
foreach (var item in Items)
|
||||
@ -22,5 +29,4 @@ public partial class ContentCollectionResource : Resource, IContentResourceColle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Core/ContentCollectionResource.cs.uid
Normal file
1
scripts/Core/ContentCollectionResource.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://ctbmcynvl8ffm
|
||||
@ -20,27 +20,52 @@ namespace Core;
|
||||
/// </summary>
|
||||
public interface IContentSource
|
||||
{
|
||||
/// <summary>
|
||||
/// 优先级
|
||||
/// </summary>
|
||||
int Priority { get; }
|
||||
/// <summary>
|
||||
/// 加载所有指定类型的对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T">对象类型</typeparam>
|
||||
/// <returns>对象集合</returns>
|
||||
IEnumerable<T> LoadAll<T>() where T : class;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内容合并模式
|
||||
/// </summary>
|
||||
public enum ContentMergeMode
|
||||
{
|
||||
Override,
|
||||
KeepFirst
|
||||
Override, // 覆盖
|
||||
KeepFirst // 保留首次
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内容注册表
|
||||
/// </summary>
|
||||
public sealed class ContentRegistry
|
||||
{
|
||||
private readonly List<IContentSource> _sources = new();
|
||||
/// <summary>
|
||||
/// 合并模式
|
||||
/// </summary>
|
||||
public ContentMergeMode MergeMode { get; set; } = ContentMergeMode.Override;
|
||||
|
||||
/// <summary>
|
||||
/// 注册内容源
|
||||
/// </summary>
|
||||
/// <param name="source">内容源</param>
|
||||
public void RegisterSource(IContentSource source)
|
||||
{
|
||||
_sources.Add(source);
|
||||
_sources.Sort((a, b) => a.Priority.CompareTo(b.Priority));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建游戏内容数据库
|
||||
/// </summary>
|
||||
/// <returns>游戏内容数据库</returns>
|
||||
public GameContentDatabase BuildDatabase()
|
||||
{
|
||||
var db = new GameContentDatabase();
|
||||
@ -55,6 +80,11 @@ public sealed class ContentRegistry
|
||||
return db;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从所有源加载指定类型的所有对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T">对象类型</typeparam>
|
||||
/// <returns>对象集合</returns>
|
||||
private IEnumerable<T> LoadAll<T>() where T : class
|
||||
{
|
||||
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
|
||||
{
|
||||
foreach (var item in items)
|
||||
@ -97,14 +134,29 @@ public sealed class ContentRegistry
|
||||
/// </summary>
|
||||
public sealed class ResourceContentSource : IContentSource
|
||||
{
|
||||
/// <summary>
|
||||
/// 优先级
|
||||
/// </summary>
|
||||
public int Priority { get; }
|
||||
/// <summary>
|
||||
/// 资源路径列表
|
||||
/// </summary>
|
||||
public List<string> ResourcePaths { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="priority">优先级</param>
|
||||
public ResourceContentSource(int priority)
|
||||
{
|
||||
Priority = priority;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载所有指定类型的对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T">对象类型</typeparam>
|
||||
/// <returns>对象集合</returns>
|
||||
public IEnumerable<T> LoadAll<T>() where T : class
|
||||
{
|
||||
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
|
||||
{
|
||||
if (resource is IContentResource content)
|
||||
@ -163,10 +221,20 @@ public sealed class ResourceContentSource : IContentSource
|
||||
/// </summary>
|
||||
public sealed class JsonContentSource : IContentSource
|
||||
{
|
||||
/// <summary>
|
||||
/// 优先级
|
||||
/// </summary>
|
||||
public int Priority { get; }
|
||||
/// <summary>
|
||||
/// 数据路径列表
|
||||
/// </summary>
|
||||
public List<string> DataPaths { get; } = new();
|
||||
private readonly JsonSerializerOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="priority">优先级</param>
|
||||
public JsonContentSource(int priority)
|
||||
{
|
||||
Priority = priority;
|
||||
@ -177,6 +245,11 @@ public sealed class JsonContentSource : IContentSource
|
||||
_options.Converters.Add(new JsonStringEnumConverter());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载所有指定类型的对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T">对象类型</typeparam>
|
||||
/// <returns>对象集合</returns>
|
||||
public IEnumerable<T> LoadAll<T>() where T : class
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (path.StartsWith("res://") || path.StartsWith("user://"))
|
||||
@ -242,6 +320,9 @@ public sealed class JsonContentSource : IContentSource
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试反序列化列表
|
||||
/// </summary>
|
||||
private bool TryDeserializeList<T>(string json, out List<T> list) where T : class
|
||||
{
|
||||
try
|
||||
@ -256,6 +337,9 @@ public sealed class JsonContentSource : IContentSource
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试反序列化单个对象
|
||||
/// </summary>
|
||||
private bool TryDeserializeSingle<T>(string json, out T item) where T : class
|
||||
{
|
||||
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
|
||||
{
|
||||
try
|
||||
@ -295,5 +382,4 @@ public sealed class JsonContentSource : IContentSource
|
||||
public List<T> Items { get; set; }
|
||||
public T Item { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Core/ContentRegistry.cs.uid
Normal file
1
scripts/Core/ContentRegistry.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://fpwckd80v5nv
|
||||
@ -15,13 +15,26 @@ namespace Core;
|
||||
/// </summary>
|
||||
public interface IContentResource
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取定义类型
|
||||
/// </summary>
|
||||
/// <returns>类型</returns>
|
||||
Type GetDefinitionType();
|
||||
/// <summary>
|
||||
/// 转换为定义对象
|
||||
/// </summary>
|
||||
/// <returns>定义对象</returns>
|
||||
object ToDefinition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 资源集合接口
|
||||
/// </summary>
|
||||
public interface IContentResourceCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取资源项集合
|
||||
/// </summary>
|
||||
/// <returns>资源项集合</returns>
|
||||
IEnumerable<IContentResource> GetItems();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
1
scripts/Core/ContentResources.cs.uid
Normal file
1
scripts/Core/ContentResources.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bkgwb8oeer5ri
|
||||
@ -20,28 +20,81 @@ namespace Core;
|
||||
public partial class DisciplineDefinitionResource : Resource, IContentResource
|
||||
{
|
||||
// --- Header ---
|
||||
/// <summary>
|
||||
/// 学科ID
|
||||
/// </summary>
|
||||
[Export] public string Id { get; set; }
|
||||
/// <summary>
|
||||
/// 名称键值
|
||||
/// </summary>
|
||||
[Export] public string NameKey { get; set; }
|
||||
/// <summary>
|
||||
/// 名称默认值
|
||||
/// </summary>
|
||||
[Export] public string NameFallback { get; set; }
|
||||
/// <summary>
|
||||
/// 描述键值
|
||||
/// </summary>
|
||||
[Export] public string DescriptionKey { get; set; }
|
||||
/// <summary>
|
||||
/// 描述默认值
|
||||
/// </summary>
|
||||
[Export] public string DescriptionFallback { get; set; }
|
||||
/// <summary>
|
||||
/// 图标路径
|
||||
/// </summary>
|
||||
[Export] public string IconPath { get; set; }
|
||||
/// <summary>
|
||||
/// 标签列表
|
||||
/// </summary>
|
||||
[Export] public Array<string> Tags { get; set; } = new();
|
||||
|
||||
// --- Buff ---
|
||||
/// <summary>
|
||||
/// Buff名称键值
|
||||
/// </summary>
|
||||
[Export] public string BuffNameKey { get; set; }
|
||||
/// <summary>
|
||||
/// Buff名称默认值
|
||||
/// </summary>
|
||||
[Export] public string BuffNameFallback { get; set; }
|
||||
/// <summary>
|
||||
/// Buff描述键值
|
||||
/// </summary>
|
||||
[Export] public string BuffDescriptionKey { get; set; }
|
||||
/// <summary>
|
||||
/// Buff描述默认值
|
||||
/// </summary>
|
||||
[Export] public string BuffDescriptionFallback { get; set; }
|
||||
/// <summary>
|
||||
/// Buff规则ID列表
|
||||
/// </summary>
|
||||
[Export] public Array<string> BuffRuleIds { get; set; } = new();
|
||||
|
||||
// --- Pools ---
|
||||
/// <summary>
|
||||
/// 角色池ID列表
|
||||
/// </summary>
|
||||
[Export] public Array<string> RolePoolIds { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 物品池ID列表
|
||||
/// </summary>
|
||||
[Export] public Array<string> ItemPoolIds { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 任务关键词ID列表
|
||||
/// </summary>
|
||||
[Export] public Array<string> TaskKeywordIds { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取定义类型
|
||||
/// </summary>
|
||||
/// <returns>类型</returns>
|
||||
public Type GetDefinitionType() => typeof(DisciplineDefinition);
|
||||
|
||||
/// <summary>
|
||||
/// 转换为定义对象
|
||||
/// </summary>
|
||||
/// <returns>定义对象</returns>
|
||||
public object ToDefinition()
|
||||
{
|
||||
var header = new DefinitionHeader
|
||||
@ -98,6 +151,11 @@ public partial class DisciplineDefinitionResource : Resource, IContentResource
|
||||
return definition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加范围
|
||||
/// </summary>
|
||||
/// <param name="source">源数组</param>
|
||||
/// <param name="target">目标列表</param>
|
||||
private static void AddRange(Array<string> source, List<string> target)
|
||||
{
|
||||
foreach (var value in source)
|
||||
@ -105,5 +163,4 @@ public partial class DisciplineDefinitionResource : Resource, IContentResource
|
||||
target.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Core/DisciplineDefinitionResource.cs.uid
Normal file
1
scripts/Core/DisciplineDefinitionResource.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bfehg7ijcybie
|
||||
@ -14,31 +14,57 @@ namespace Core;
|
||||
/// </summary>
|
||||
public readonly struct TaskCompletedEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 完成的任务
|
||||
/// </summary>
|
||||
public TaskModel Task { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="task">任务</param>
|
||||
public TaskCompletedEvent(TaskModel task)
|
||||
{
|
||||
Task = task;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务失败事件
|
||||
/// </summary>
|
||||
public readonly struct TaskFailedEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 失败的任务
|
||||
/// </summary>
|
||||
public TaskModel Task { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="task">任务</param>
|
||||
public TaskFailedEvent(TaskModel task)
|
||||
{
|
||||
Task = task;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回合结束事件
|
||||
/// </summary>
|
||||
public readonly struct TurnEndedEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 结束的回合数
|
||||
/// </summary>
|
||||
public int Turn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="turn">回合数</param>
|
||||
public TurnEndedEvent(int turn)
|
||||
{
|
||||
Turn = turn;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Core/DomainEvents.cs.uid
Normal file
1
scripts/Core/DomainEvents.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://drbjfxehidfrn
|
||||
@ -17,6 +17,11 @@ public sealed class DomainEventBus
|
||||
{
|
||||
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)
|
||||
{
|
||||
var type = typeof(T);
|
||||
@ -29,6 +34,11 @@ public sealed class DomainEventBus
|
||||
list.Add(handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消订阅事件
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型</typeparam>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
public void Unsubscribe<T>(Action<T> handler)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var type = typeof(T);
|
||||
@ -54,5 +69,4 @@ public sealed class DomainEventBus
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Core/EventBus.cs.uid
Normal file
1
scripts/Core/EventBus.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dmhvn0hu7qlne
|
||||
@ -16,28 +16,40 @@ public sealed class GameController : IController
|
||||
{
|
||||
private GameSession _session;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化控制器
|
||||
/// </summary>
|
||||
/// <param name="session">游戏会话</param>
|
||||
public void Initialize(GameSession session)
|
||||
{
|
||||
_session = session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始执行阶段
|
||||
/// </summary>
|
||||
public void StartExecution()
|
||||
{
|
||||
if (_session.State.Turn.Phase != GamePhase.Planning) return;
|
||||
_session.State.Turn.Phase = GamePhase.Execution;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束执行阶段
|
||||
/// </summary>
|
||||
public void EndExecution()
|
||||
{
|
||||
if (_session.State.Turn.Phase != GamePhase.Execution) return;
|
||||
_session.State.Turn.Phase = GamePhase.Review;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始下一回合
|
||||
/// </summary>
|
||||
public void StartNextTurn()
|
||||
{
|
||||
if (_session.State.Turn.Phase != GamePhase.Review) return;
|
||||
_session.State.Turn.CurrentTurn++;
|
||||
_session.State.Turn.Phase = GamePhase.Planning;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Core/GameController.cs.uid
Normal file
1
scripts/Core/GameController.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cqw1q6qv873he
|
||||
@ -14,12 +14,34 @@ namespace Core;
|
||||
/// </summary>
|
||||
public sealed class GameSession
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏状态
|
||||
/// </summary>
|
||||
public GameState State { get; }
|
||||
/// <summary>
|
||||
/// 游戏内容数据库
|
||||
/// </summary>
|
||||
public GameContentDatabase Content { get; }
|
||||
/// <summary>
|
||||
/// 领域事件总线
|
||||
/// </summary>
|
||||
public DomainEventBus Events { get; }
|
||||
/// <summary>
|
||||
/// 本地化服务
|
||||
/// </summary>
|
||||
public ILocalizationService Localization { get; }
|
||||
/// <summary>
|
||||
/// 游戏系统集合
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
State = state;
|
||||
@ -30,6 +52,10 @@ public sealed class GameSession
|
||||
Systems.Initialize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建默认游戏会话
|
||||
/// </summary>
|
||||
/// <returns>游戏会话实例</returns>
|
||||
public static GameSession CreateDefault()
|
||||
{
|
||||
var registry = new ContentRegistry();
|
||||
@ -40,6 +66,8 @@ public sealed class GameSession
|
||||
var jsonSource = new JsonContentSource(10);
|
||||
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);
|
||||
|
||||
var content = registry.BuildDatabase();
|
||||
@ -48,9 +76,12 @@ public sealed class GameSession
|
||||
return new GameSession(new GameState(), content, localization, events);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 每帧更新
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
public void Tick(float delta)
|
||||
{
|
||||
Systems.Tick(delta);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Core/GameSession.cs.uid
Normal file
1
scripts/Core/GameSession.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cltmymdid63wi
|
||||
@ -16,18 +16,48 @@ namespace Core;
|
||||
/// </summary>
|
||||
public interface IGameSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化系统
|
||||
/// </summary>
|
||||
/// <param name="session">游戏会话</param>
|
||||
void Initialize(GameSession session);
|
||||
/// <summary>
|
||||
/// 每帧更新
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
void Tick(float delta);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 游戏系统管理器
|
||||
/// </summary>
|
||||
public sealed class GameSystems
|
||||
{
|
||||
/// <summary>
|
||||
/// 回合系统
|
||||
/// </summary>
|
||||
public TurnSystem Turn { get; } = new();
|
||||
/// <summary>
|
||||
/// 任务系统
|
||||
/// </summary>
|
||||
public TaskSystem Task { get; } = new();
|
||||
/// <summary>
|
||||
/// 经济系统
|
||||
/// </summary>
|
||||
public EconomySystem Economy { get; } = new();
|
||||
/// <summary>
|
||||
/// 羁绊/协同系统
|
||||
/// </summary>
|
||||
public SynergySystem Synergy { get; } = new();
|
||||
/// <summary>
|
||||
/// 分配系统
|
||||
/// </summary>
|
||||
public AssignmentSystem Assignment { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化所有系统
|
||||
/// </summary>
|
||||
/// <param name="session">游戏会话</param>
|
||||
public void Initialize(GameSession session)
|
||||
{
|
||||
Turn.Initialize(session);
|
||||
@ -37,6 +67,10 @@ public sealed class GameSystems
|
||||
Assignment.Initialize(session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新所有系统
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
public void Tick(float delta)
|
||||
{
|
||||
Turn.Tick(delta);
|
||||
@ -47,32 +81,54 @@ public sealed class GameSystems
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回合系统
|
||||
/// </summary>
|
||||
public sealed class TurnSystem : IGameSystem
|
||||
{
|
||||
private GameSession _session;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
/// <param name="session">游戏会话</param>
|
||||
public void Initialize(GameSession session)
|
||||
{
|
||||
_session = session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
public void Tick(float delta)
|
||||
{
|
||||
// 预留:回合推进计时器/阶段切换
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务系统
|
||||
/// </summary>
|
||||
public sealed class TaskSystem : IGameSystem
|
||||
{
|
||||
private GameSession _session;
|
||||
private StatResolver _statResolver;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
/// <param name="session">游戏会话</param>
|
||||
public void Initialize(GameSession session)
|
||||
{
|
||||
_session = session;
|
||||
_statResolver = new StatResolver(session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
public void Tick(float delta)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var unit = entry.Unit;
|
||||
@ -173,6 +236,9 @@ public sealed class TaskSystem : IGameSystem
|
||||
return effectivePower;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取任务基础效率
|
||||
/// </summary>
|
||||
private float GetTaskBasePower(UnitModel unit, TaskKind kind)
|
||||
{
|
||||
var academic = _statResolver.GetAttribute(unit, AttributeType.Academic);
|
||||
@ -194,6 +260,9 @@ public sealed class TaskSystem : IGameSystem
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取状态乘数
|
||||
/// </summary>
|
||||
private float GetStatusMultiplier(UnitEntry entry)
|
||||
{
|
||||
var mood = entry.Unit.Statuses.Mood.Normalized;
|
||||
@ -213,6 +282,9 @@ public sealed class TaskSystem : IGameSystem
|
||||
return Math.Clamp(multiplier, 0.3f, 1.2f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取角色乘数
|
||||
/// </summary>
|
||||
private float GetRoleMultiplier(UnitModel unit, TaskDefinition taskDef)
|
||||
{
|
||||
if (taskDef == null) return 1.0f;
|
||||
@ -246,6 +318,9 @@ public sealed class TaskSystem : IGameSystem
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取需求乘数
|
||||
/// </summary>
|
||||
private float GetRequirementMultiplier(UnitModel unit, TaskDefinition taskDef)
|
||||
{
|
||||
if (taskDef == null) return 1.0f;
|
||||
@ -263,6 +338,9 @@ public sealed class TaskSystem : IGameSystem
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取学科乘数
|
||||
/// </summary>
|
||||
private float GetDisciplineMultiplier(UnitModel unit, TaskDefinition taskDef)
|
||||
{
|
||||
if (taskDef == null) return 1.0f;
|
||||
@ -280,6 +358,9 @@ public sealed class TaskSystem : IGameSystem
|
||||
return 0.7f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取难度系数
|
||||
/// </summary>
|
||||
private float GetDifficultyScale(TaskDifficulty difficulty)
|
||||
{
|
||||
return difficulty switch
|
||||
@ -292,12 +373,18 @@ public sealed class TaskSystem : IGameSystem
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取任务定义
|
||||
/// </summary>
|
||||
private TaskDefinition GetTaskDefinition(TaskModel task)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(task.DefinitionId)) return null;
|
||||
return _session.Content.Tasks.TryGetValue(task.DefinitionId, out var definition) ? definition : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建单位索引
|
||||
/// </summary>
|
||||
private Dictionary<Guid, UnitEntry> BuildUnitIndex()
|
||||
{
|
||||
var index = new Dictionary<Guid, UnitEntry>();
|
||||
@ -324,6 +411,9 @@ public sealed class TaskSystem : IGameSystem
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 追踪贡献
|
||||
/// </summary>
|
||||
private void TrackContribution(UnitEntry entry, TaskModel task, float deltaContribution)
|
||||
{
|
||||
if (entry.Student == null) return;
|
||||
@ -355,6 +445,9 @@ public sealed class TaskSystem : IGameSystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 经济系统
|
||||
/// </summary>
|
||||
public sealed class EconomySystem : IGameSystem
|
||||
{
|
||||
private GameSession _session;
|
||||
@ -363,6 +456,10 @@ public sealed class EconomySystem : IGameSystem
|
||||
private const int PostDocSalary = 1200;
|
||||
private const int JuniorFacultySalary = 2000;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
/// <param name="session">游戏会话</param>
|
||||
public void Initialize(GameSession session)
|
||||
{
|
||||
_session = session;
|
||||
@ -371,11 +468,18 @@ public sealed class EconomySystem : IGameSystem
|
||||
_session.Events.Subscribe<TurnEndedEvent>(OnTurnEnded);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
public void Tick(float delta)
|
||||
{
|
||||
// 当前为回合驱动,不在 Tick 中结算
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务完成回调
|
||||
/// </summary>
|
||||
private void OnTaskCompleted(TaskCompletedEvent evt)
|
||||
{
|
||||
var task = evt.Task;
|
||||
@ -404,6 +508,9 @@ public sealed class EconomySystem : IGameSystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务失败回调
|
||||
/// </summary>
|
||||
private void OnTaskFailed(TaskFailedEvent evt)
|
||||
{
|
||||
var task = evt.Task;
|
||||
@ -412,12 +519,18 @@ public sealed class EconomySystem : IGameSystem
|
||||
_session.State.Economy.Reputation -= penalty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回合结束回调
|
||||
/// </summary>
|
||||
private void OnTurnEnded(TurnEndedEvent evt)
|
||||
{
|
||||
ApplySalaries();
|
||||
ApplyInterest();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 支付薪水
|
||||
/// </summary>
|
||||
private void ApplySalaries()
|
||||
{
|
||||
var economy = _session.State.Economy;
|
||||
@ -436,6 +549,9 @@ public sealed class EconomySystem : IGameSystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算利息
|
||||
/// </summary>
|
||||
private void ApplyInterest()
|
||||
{
|
||||
var economy = _session.State.Economy;
|
||||
@ -446,6 +562,9 @@ public sealed class EconomySystem : IGameSystem
|
||||
economy.Money += interest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新利率
|
||||
/// </summary>
|
||||
private void UpdateInterestRate()
|
||||
{
|
||||
var economy = _session.State.Economy;
|
||||
@ -456,6 +575,9 @@ public sealed class EconomySystem : IGameSystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加论文
|
||||
/// </summary>
|
||||
private void AddPaper(PaperRank rank)
|
||||
{
|
||||
var inventory = _session.State.Inventory;
|
||||
@ -467,6 +589,9 @@ public sealed class EconomySystem : IGameSystem
|
||||
inventory.PaperCounts[rank] += 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加物品
|
||||
/// </summary>
|
||||
private void AddItem(string itemId, int count)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(itemId)) return;
|
||||
@ -479,6 +604,9 @@ public sealed class EconomySystem : IGameSystem
|
||||
inventory.ItemCounts[itemId] += count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据难度获取论文等级
|
||||
/// </summary>
|
||||
private PaperRank GetPaperRankByDifficulty(TaskDifficulty difficulty)
|
||||
{
|
||||
return difficulty switch
|
||||
@ -492,20 +620,34 @@ public sealed class EconomySystem : IGameSystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 羁绊/协同系统
|
||||
/// </summary>
|
||||
public sealed class SynergySystem : IGameSystem
|
||||
{
|
||||
private GameSession _session;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
/// <param name="session">游戏会话</param>
|
||||
public void Initialize(GameSession session)
|
||||
{
|
||||
_session = session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
public void Tick(float delta)
|
||||
{
|
||||
RecalculateSynergy();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新计算协同
|
||||
/// </summary>
|
||||
private void RecalculateSynergy()
|
||||
{
|
||||
var state = _session.State.Synergy;
|
||||
@ -518,6 +660,9 @@ public sealed class SynergySystem : IGameSystem
|
||||
ApplySynergyDefinitions(state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统计单位标签
|
||||
/// </summary>
|
||||
private void CountUnitTags(SynergyState synergy)
|
||||
{
|
||||
var roster = _session.State.Roster;
|
||||
@ -534,6 +679,9 @@ public sealed class SynergySystem : IGameSystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加单位标签
|
||||
/// </summary>
|
||||
private void AddUnitTags(SynergyState synergy, UnitModel unit)
|
||||
{
|
||||
if (unit == null) return;
|
||||
@ -548,6 +696,9 @@ public sealed class SynergySystem : IGameSystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加堆叠计数
|
||||
/// </summary>
|
||||
private void AddStack(Dictionary<string, int> stacks, string id)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id)) return;
|
||||
@ -559,6 +710,9 @@ public sealed class SynergySystem : IGameSystem
|
||||
stacks[id] += 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用协同定义
|
||||
/// </summary>
|
||||
private void ApplySynergyDefinitions(SynergyState synergy)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id)) return;
|
||||
@ -586,6 +743,9 @@ public sealed class SynergySystem : IGameSystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 合并修正
|
||||
/// </summary>
|
||||
private void MergeModifiers(ModifierBundle target, ModifierBundle source)
|
||||
{
|
||||
if (target == null || source == null) return;
|
||||
@ -595,6 +755,9 @@ public sealed class SynergySystem : IGameSystem
|
||||
target.RuleIds.AddRange(source.RuleIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除修正
|
||||
/// </summary>
|
||||
private void ClearModifiers(ModifierBundle bundle)
|
||||
{
|
||||
bundle.AttributeModifiers.Clear();
|
||||
@ -604,18 +767,28 @@ public sealed class SynergySystem : IGameSystem
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分配系统
|
||||
/// </summary>
|
||||
public sealed class AssignmentSystem : IGameSystem
|
||||
{
|
||||
private GameSession _session;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
/// <param name="session">游戏会话</param>
|
||||
public void Initialize(GameSession session)
|
||||
{
|
||||
_session = session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
public void Tick(float delta)
|
||||
{
|
||||
// 预留:人员分配、交接惩罚等
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Core/GameSystems.cs.uid
Normal file
1
scripts/Core/GameSystems.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dg4tya2t3wgel
|
||||
@ -15,18 +15,43 @@ namespace Core;
|
||||
/// </summary>
|
||||
public interface ILocalizationService
|
||||
{
|
||||
/// <summary>
|
||||
/// 翻译本地化文本对象
|
||||
/// </summary>
|
||||
/// <param name="text">文本对象</param>
|
||||
/// <returns>翻译后的字符串</returns>
|
||||
string Translate(LocalizedText text);
|
||||
/// <summary>
|
||||
/// 翻译键值
|
||||
/// </summary>
|
||||
/// <param name="key">键值</param>
|
||||
/// <param name="fallback">默认值</param>
|
||||
/// <returns>翻译后的字符串</returns>
|
||||
string Translate(string key, string fallback = null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Godot 本地化服务实现
|
||||
/// </summary>
|
||||
public sealed class GodotLocalizationService : ILocalizationService
|
||||
{
|
||||
/// <summary>
|
||||
/// 翻译本地化文本对象
|
||||
/// </summary>
|
||||
/// <param name="text">文本对象</param>
|
||||
/// <returns>翻译后的字符串</returns>
|
||||
public string Translate(LocalizedText text)
|
||||
{
|
||||
if (text == null) return string.Empty;
|
||||
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)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
@ -42,5 +67,4 @@ public sealed class GodotLocalizationService : ILocalizationService
|
||||
|
||||
return translated;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Core/LocalizationService.cs.uid
Normal file
1
scripts/Core/LocalizationService.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bi61o586eqdyu
|
||||
@ -14,16 +14,39 @@ namespace Core;
|
||||
/// </summary>
|
||||
public sealed class ModManifest
|
||||
{
|
||||
/// <summary>
|
||||
/// Mod ID
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
/// <summary>
|
||||
/// Mod 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// 版本
|
||||
/// </summary>
|
||||
public string Version { get; set; }
|
||||
/// <summary>
|
||||
/// 依赖列表
|
||||
/// </summary>
|
||||
public List<string> Dependencies { get; } = new();
|
||||
/// <summary>
|
||||
/// 内容路径列表
|
||||
/// </summary>
|
||||
public List<string> ContentPaths { get; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mod 包
|
||||
/// </summary>
|
||||
public sealed class ModPackage
|
||||
{
|
||||
/// <summary>
|
||||
/// 清单
|
||||
/// </summary>
|
||||
public ModManifest Manifest { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 根路径
|
||||
/// </summary>
|
||||
public string RootPath { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Core/ModManifest.cs.uid
Normal file
1
scripts/Core/ModManifest.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://c7ng0xpd2iul1
|
||||
@ -14,26 +14,50 @@ namespace Core;
|
||||
/// </summary>
|
||||
public interface IView<TModel>
|
||||
{
|
||||
/// <summary>
|
||||
/// 绑定数据模型
|
||||
/// </summary>
|
||||
/// <param name="model">数据模型</param>
|
||||
void Bind(TModel model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控制器接口
|
||||
/// </summary>
|
||||
public interface IController
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化控制器
|
||||
/// </summary>
|
||||
/// <param name="session">游戏会话</param>
|
||||
void Initialize(GameSession session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模型视图基类
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">模型类型</typeparam>
|
||||
public abstract partial class ModelView<TModel> : Node, IView<TModel>
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据模型
|
||||
/// </summary>
|
||||
public TModel Model { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 绑定数据模型
|
||||
/// </summary>
|
||||
/// <param name="model">数据模型</param>
|
||||
public virtual void Bind(TModel model)
|
||||
{
|
||||
Model = model;
|
||||
OnModelBound();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当模型绑定时调用
|
||||
/// </summary>
|
||||
protected virtual void OnModelBound()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Core/Mvc.cs.uid
Normal file
1
scripts/Core/Mvc.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bqconwrqysw5b
|
||||
@ -17,11 +17,21 @@ public sealed class StatResolver
|
||||
{
|
||||
private readonly GameSession _session;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="session">游戏会话</param>
|
||||
public StatResolver(GameSession session)
|
||||
{
|
||||
_session = session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取计算后的属性值
|
||||
/// </summary>
|
||||
/// <param name="unit">单位</param>
|
||||
/// <param name="type">属性类型</param>
|
||||
/// <returns>属性值</returns>
|
||||
public float GetAttribute(UnitModel unit, AttributeType type)
|
||||
{
|
||||
var value = GetBaseAttribute(unit, type);
|
||||
@ -32,6 +42,12 @@ public sealed class StatResolver
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取基础属性值
|
||||
/// </summary>
|
||||
/// <param name="unit">单位</param>
|
||||
/// <param name="type">属性类型</param>
|
||||
/// <returns>基础值</returns>
|
||||
private float GetBaseAttribute(UnitModel unit, AttributeType type)
|
||||
{
|
||||
return type switch
|
||||
@ -46,6 +62,9 @@ public sealed class StatResolver
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用学科加成
|
||||
/// </summary>
|
||||
private void ApplyDiscipline(string disciplineId, AttributeType type, ref float value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(disciplineId)) return;
|
||||
@ -53,6 +72,9 @@ public sealed class StatResolver
|
||||
ApplyBundle(discipline.Buff?.Modifiers, type, ref value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用特质加成
|
||||
/// </summary>
|
||||
private void ApplyTraits(List<string> traitIds, AttributeType type, ref float value)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (bundle == null) return;
|
||||
@ -86,5 +114,4 @@ public sealed class StatResolver
|
||||
value = (value + modifier.Add) * modifier.Multiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Core/StatResolver.cs.uid
Normal file
1
scripts/Core/StatResolver.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://l1exgbqpbayh
|
||||
@ -4,14 +4,20 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
|
||||
/// <summary>
|
||||
/// 办公格子/图块对象
|
||||
/// </summary>
|
||||
public partial class Cube : StaticBody2D, ITileDraggable
|
||||
{
|
||||
// 定义各种家具的占用区域
|
||||
private static readonly Rect2I tableRect = new(0, 0, 3, 2);
|
||||
private static readonly Rect2I table2Rect = new(0, -1, 3, 1);
|
||||
private static readonly Rect2I chairRect = new(1, 1, 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 equip2Rect = new(0, -1, 3, 1);
|
||||
|
||||
// 定义桌子的主题映射
|
||||
private static readonly TileMapping[] tableThemes = {
|
||||
new(new Vector2I(1, 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(10, 28), tableRect),
|
||||
};
|
||||
// 定义第二种桌子的主题映射
|
||||
private static readonly TileMapping[] table2Themes = {
|
||||
new(new Vector2I(1, 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(10, 29), table2Rect),
|
||||
};
|
||||
// 定义椅子的主题映射
|
||||
private static readonly TileMapping[] chairThemes = {
|
||||
new(new Vector2I(-1, 7), chairRect),
|
||||
new(new Vector2I(0, 7), chairRect),
|
||||
new(new Vector2I(-1, 9), chairRect),
|
||||
new(new Vector2I(0, 9), chairRect),
|
||||
};
|
||||
// 定义第二种椅子的主题映射
|
||||
private static readonly TileMapping[] chair2Themes = {
|
||||
new(new Vector2I(-1, 10), chair2Rect),
|
||||
new(new Vector2I(0, 10), chair2Rect),
|
||||
@ -39,12 +48,13 @@ public partial class Cube : StaticBody2D, ITileDraggable
|
||||
new(new Vector2I(0, 12), chair2Rect),
|
||||
};
|
||||
|
||||
// 定义设备的主题映射
|
||||
private static readonly TileMapping[] equipThemes = {
|
||||
// one laptop
|
||||
// 一台笔记本电脑
|
||||
new(new Vector2I(13, 26), equipRect),
|
||||
// one laptop with one monitor
|
||||
// 一台笔记本电脑加一台显示器
|
||||
new(new Vector2I(7, 26), equipRect),
|
||||
// one desktop PC
|
||||
// 一台台式机
|
||||
new(new Dictionary<Vector2I, Vector2I>() {
|
||||
[new Vector2I(0, 0)] = new Vector2I(11, 17),
|
||||
[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(2, 1)] = new Vector2I(9, 33),
|
||||
}),
|
||||
// one desktop PC with one monitors
|
||||
// 一台台式机加一台显示器
|
||||
new(new Vector2I(7, 32), equipRect),
|
||||
};
|
||||
|
||||
// 定义第二种设备的主题映射
|
||||
private static readonly TileMapping[] equip2Themes = {
|
||||
// one laptop
|
||||
// 一台笔记本电脑
|
||||
new(new Vector2I(13, 31), equip2Rect),
|
||||
// one laptop with one monitor
|
||||
// 一台笔记本电脑加一台显示器
|
||||
new(new Dictionary<Vector2I, Vector2I>() {
|
||||
[new Vector2I(0, -1)] = new Vector2I(13, 32),
|
||||
[new Vector2I(1, -1)] = new Vector2I(10, 7),
|
||||
[new Vector2I(2, -1)] = new Vector2I(15, 32),
|
||||
}),
|
||||
// one desktop PC
|
||||
// 一台台式机
|
||||
new(new Dictionary<Vector2I, Vector2I>() {
|
||||
[new Vector2I(0, -1)] = new Vector2I(13, 32),
|
||||
[new Vector2I(1, -1)] = new Vector2I(14, 32),
|
||||
[new Vector2I(2, -1)] = new Vector2I(15, 30),
|
||||
}),
|
||||
// one desktop PC with one monitors
|
||||
// 一台台式机加一台显示器
|
||||
new(new Vector2I(13, 33), equip2Rect),
|
||||
};
|
||||
|
||||
private bool _draggable;
|
||||
/// <summary>
|
||||
/// 是否可拖拽
|
||||
/// </summary>
|
||||
public bool Draggable {
|
||||
get => _draggable;
|
||||
set {
|
||||
@ -86,10 +100,20 @@ public partial class Cube : StaticBody2D, ITileDraggable
|
||||
}
|
||||
|
||||
private readonly Guid _id = Guid.NewGuid();
|
||||
/// <summary>
|
||||
/// 唯一标识符
|
||||
/// </summary>
|
||||
public Guid Id => _id;
|
||||
|
||||
/// <summary>
|
||||
/// 在网格中的位置
|
||||
/// </summary>
|
||||
public Vector2I TilePosition { get; set; } = new Vector2I(5,5);
|
||||
|
||||
private bool _isCollided = true;
|
||||
/// <summary>
|
||||
/// 是否处于碰撞状态(显示红色背景)
|
||||
/// </summary>
|
||||
public bool IsCollided {
|
||||
get => _isCollided;
|
||||
set {
|
||||
@ -103,9 +127,15 @@ public partial class Cube : StaticBody2D, ITileDraggable
|
||||
}
|
||||
}
|
||||
private static readonly Rect2I tileRect = new(-1, -2, 4, 5);
|
||||
/// <summary>
|
||||
/// 占用矩形区域
|
||||
/// </summary>
|
||||
public Rect2I TileRect => tileRect;
|
||||
|
||||
private Vector2I _mouseOffset;
|
||||
/// <summary>
|
||||
/// 鼠标拖拽偏移
|
||||
/// </summary>
|
||||
public Vector2I MouseOffset => _mouseOffset;
|
||||
|
||||
private static readonly ITileDraggable.SpecialTile[] specialTiles = {
|
||||
@ -114,15 +144,27 @@ public partial class Cube : StaticBody2D, ITileDraggable
|
||||
};
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 特殊图块(如座位)
|
||||
/// </summary>
|
||||
public ITileDraggable.SpecialTile[] SpecialTiles => specialTiles;
|
||||
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
/// <summary>
|
||||
/// 节点进入场景树时调用
|
||||
/// </summary>
|
||||
public override void _Ready()
|
||||
{
|
||||
RandomChangeTheme();
|
||||
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)
|
||||
{
|
||||
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.
|
||||
/// <summary>
|
||||
/// 每帧处理
|
||||
/// </summary>
|
||||
/// <param name="delta">时间间隔</param>
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
GlobalPosition = TilePosition * 48;
|
||||
@ -153,6 +199,10 @@ public partial class Cube : StaticBody2D, ITileDraggable
|
||||
private int _chair2ThemeIdx;
|
||||
private int _equipThemeIdx;
|
||||
private int _equip2ThemeIdx;
|
||||
|
||||
/// <summary>
|
||||
/// 随机更换主题样式
|
||||
/// </summary>
|
||||
public void RandomChangeTheme() {
|
||||
_tableThemeIdx = GD.RandRange(0, tableThemes.Length-1);
|
||||
_chairThemeIdx = GD.RandRange(0, chairThemes.Length-1);
|
||||
@ -177,6 +227,12 @@ public partial class Cube : StaticBody2D, ITileDraggable
|
||||
new(1,1),
|
||||
new(2,1),
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定位置的图块类型
|
||||
/// </summary>
|
||||
/// <param name="pos">相对位置</param>
|
||||
/// <returns>图块类型</returns>
|
||||
public Lab.MapNodeType GetTileType(Vector2I pos)
|
||||
{
|
||||
GD.Print($"query position of {pos}");
|
||||
@ -189,6 +245,11 @@ public partial class Cube : StaticBody2D, ITileDraggable
|
||||
return Lab.MapNodeType.Walkable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置椅子朝向(待实现)
|
||||
/// </summary>
|
||||
/// <param name="target">目标位置</param>
|
||||
/// <param name="idx">索引</param>
|
||||
public void ChairFaceTo(Vector2I target, int idx) {
|
||||
if (idx == 0) {
|
||||
var theme = chairThemes[_chairThemeIdx];
|
||||
@ -196,4 +257,4 @@ public partial class Cube : StaticBody2D, ITileDraggable
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,41 +17,105 @@ using Core;
|
||||
public partial class GameManager : Node
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates if the game is currently in tutorial mode.
|
||||
/// 指示当前是否处于教程模式
|
||||
/// </summary>
|
||||
public static bool IsTutorial { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 下一个场景的路径
|
||||
/// </summary>
|
||||
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");
|
||||
|
||||
/// <summary>
|
||||
/// 最大回合数
|
||||
/// </summary>
|
||||
[Export]
|
||||
public int MaxTurns = 30;
|
||||
|
||||
// --- Global State ---
|
||||
|
||||
/// <summary>
|
||||
/// 游戏会话实例
|
||||
/// </summary>
|
||||
public GameSession Session { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前游戏状态
|
||||
/// </summary>
|
||||
public GameState State => Session?.State;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏控制器
|
||||
/// </summary>
|
||||
public GameController Controller { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前游戏阶段
|
||||
/// </summary>
|
||||
public GamePhase CurrentPhase => State?.Turn.Phase ?? GamePhase.Planning;
|
||||
|
||||
/// <summary>
|
||||
/// 当前回合数
|
||||
/// </summary>
|
||||
public int CurrentTurn => State?.Turn.CurrentTurn ?? 1;
|
||||
|
||||
// --- Domain Model ---
|
||||
|
||||
/// <summary>
|
||||
/// 导师模型
|
||||
/// </summary>
|
||||
public MentorModel Mentor => State?.Roster.Mentor;
|
||||
|
||||
/// <summary>
|
||||
/// 学生列表
|
||||
/// </summary>
|
||||
public List<StudentModel> Students => State?.Roster.Students;
|
||||
|
||||
/// <summary>
|
||||
/// 当前活动任务列表
|
||||
/// </summary>
|
||||
public List<TaskModel> ActiveTasks => State?.Tasks.ActiveTasks;
|
||||
|
||||
/// <summary>
|
||||
/// 经济状态
|
||||
/// </summary>
|
||||
public EconomyState Economy => State?.Economy;
|
||||
|
||||
// --- Signals ---
|
||||
/// <summary>
|
||||
/// 阶段变更信号
|
||||
/// </summary>
|
||||
/// <param name="phase">阶段枚举的整数值</param>
|
||||
[Signal] public delegate void PhaseChangedEventHandler(int phase); // int cast of GamePhase
|
||||
|
||||
/// <summary>
|
||||
/// 回合变更信号
|
||||
/// </summary>
|
||||
/// <param name="turn">当前回合数</param>
|
||||
[Signal] public delegate void TurnChangedEventHandler(int turn);
|
||||
|
||||
// Singleton instance access (if needed, though Godot uses node paths)
|
||||
/// <summary>
|
||||
/// 单例实例
|
||||
/// </summary>
|
||||
public static GameManager Instance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 进入场景树时调用
|
||||
/// </summary>
|
||||
public override void _EnterTree()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 准备就绪时调用
|
||||
/// </summary>
|
||||
public override void _Ready()
|
||||
{
|
||||
Input.SetCustomMouseCursor(Arrow2X);
|
||||
@ -60,6 +124,9 @@ public partial class GameManager : Node
|
||||
InitializeGame();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化游戏数据
|
||||
/// </summary>
|
||||
private void InitializeGame()
|
||||
{
|
||||
Session = GameSession.CreateDefault();
|
||||
@ -83,6 +150,10 @@ public partial class GameManager : Node
|
||||
}
|
||||
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
/// <summary>
|
||||
/// 每帧调用
|
||||
/// </summary>
|
||||
/// <param name="delta">距离上一帧的时间间隔</param>
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (CurrentPhase == GamePhase.Execution)
|
||||
@ -157,4 +228,4 @@ public partial class GameManager : Node
|
||||
|
||||
GD.Print($"Turn {CurrentTurn} Started. Phase: Planning");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,15 @@
|
||||
using Godot;
|
||||
|
||||
/// <summary>
|
||||
/// 辅助工具类
|
||||
/// </summary>
|
||||
public class H {
|
||||
/// <summary>
|
||||
/// 判断矩形是否包含点(包含边界)
|
||||
/// </summary>
|
||||
/// <param name="r">矩形区域</param>
|
||||
/// <param name="p">点坐标</param>
|
||||
/// <returns>如果点在矩形内(包括边界)则返回true</returns>
|
||||
public static bool RectHasPointInclusive(Rect2I r, Vector2I p) {
|
||||
if (r.HasPoint(p)) {
|
||||
return true;
|
||||
@ -13,4 +22,4 @@ public class H {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,25 +3,73 @@ using System.Diagnostics.SymbolStore;
|
||||
using System.Dynamic;
|
||||
using Godot;
|
||||
|
||||
/// <summary>
|
||||
/// 可拖拽图块接口
|
||||
/// </summary>
|
||||
public interface ITileDraggable {
|
||||
/// <summary>
|
||||
/// 图块在网格中的位置
|
||||
/// </summary>
|
||||
Vector2I TilePosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否允许拖拽
|
||||
/// </summary>
|
||||
bool Draggable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否处于碰撞状态(例如位置无效或重叠)
|
||||
/// </summary>
|
||||
bool IsCollided { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图块占据的矩形区域(相对于 TilePosition)
|
||||
/// </summary>
|
||||
Rect2I TileRect { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 拖拽时鼠标相对于图块原点的偏移量
|
||||
/// </summary>
|
||||
Vector2I MouseOffset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 特殊功能图块列表(如座位、交互点等)
|
||||
/// </summary>
|
||||
SpecialTile[] SpecialTiles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 特殊图块定义
|
||||
/// </summary>
|
||||
class SpecialTile {
|
||||
/// <summary>
|
||||
/// 相对位置
|
||||
/// </summary>
|
||||
public readonly Vector2I Position;
|
||||
/// <summary>
|
||||
/// 节点类型(如可行走、作为座位等)
|
||||
/// </summary>
|
||||
public readonly Lab.MapNodeType NodeType;
|
||||
|
||||
/// <summary>
|
||||
/// 构造特殊图块
|
||||
/// </summary>
|
||||
/// <param name="pos">相对位置</param>
|
||||
/// <param name="node">节点类型</param>
|
||||
public SpecialTile(Vector2I pos, Lab.MapNodeType node) {
|
||||
Position = pos;
|
||||
NodeType = node;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定相对坐标处的图块类型
|
||||
/// </summary>
|
||||
/// <param name="pos">相对坐标</param>
|
||||
/// <returns>地图节点类型</returns>
|
||||
Lab.MapNodeType GetTileType(Vector2I pos);
|
||||
|
||||
/// <summary>
|
||||
/// 唯一标识符
|
||||
/// </summary>
|
||||
Guid Id { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,18 +3,26 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// 实验室场景主控脚本
|
||||
/// </summary>
|
||||
public partial class Lab : Node2D
|
||||
{
|
||||
/// <summary>
|
||||
/// 地图节点类型标志位
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum MapNodeType
|
||||
{
|
||||
Invalid = 0,
|
||||
Walkable = 1,
|
||||
Wall = 2,
|
||||
Blocker = 4,
|
||||
SeatUp = 8,
|
||||
SeatDown = 16,
|
||||
Invalid = 0, // 无效
|
||||
Walkable = 1, // 可行走
|
||||
Wall = 2, // 墙壁
|
||||
Blocker = 4, // 阻挡物
|
||||
SeatUp = 8, // 上方座位
|
||||
SeatDown = 16, // 下方座位
|
||||
}
|
||||
|
||||
// 墙壁矩形区域列表
|
||||
private static readonly Rect2I[] wallRectangles = {
|
||||
new(0,0,40,2),
|
||||
new(0,5,1, 15),
|
||||
@ -23,9 +31,15 @@ public partial class Lab : Node2D
|
||||
};
|
||||
|
||||
private readonly Dictionary<Guid, ITileDraggable> _furnitureIDs = new();
|
||||
/// <summary>
|
||||
/// 家具字典,通过ID索引
|
||||
/// </summary>
|
||||
public Dictionary<Guid, ITileDraggable> Furniture => _furnitureIDs;
|
||||
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
/// <summary>
|
||||
/// 场景加载完成时调用
|
||||
/// </summary>
|
||||
public override void _Ready()
|
||||
{
|
||||
var ticker = GetNode<Timer>("/root/GameManager/OneSecondTicker");
|
||||
@ -57,6 +71,10 @@ public partial class Lab : Node2D
|
||||
|
||||
private Label _moneyLabel;
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
/// <summary>
|
||||
/// 每帧更新
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
switch(GD.RandRange(0,2)) {
|
||||
@ -82,12 +100,21 @@ public partial class Lab : Node2D
|
||||
}
|
||||
|
||||
private bool _isDragging;
|
||||
|
||||
/// <summary>
|
||||
/// 拾起物品
|
||||
/// </summary>
|
||||
/// <param name="target">目标物品</param>
|
||||
public void Pickup(ITileDraggable target) {
|
||||
if (target == null) return;
|
||||
_isDragging = true;
|
||||
DraggingTarget = target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 放下物品
|
||||
/// </summary>
|
||||
/// <param name="target">目标物品</param>
|
||||
public void PutDown(ITileDraggable target) {
|
||||
if (target == null) return;
|
||||
_isDragging = false;
|
||||
@ -98,6 +125,10 @@ public partial class Lab : Node2D
|
||||
DraggingTarget = null;
|
||||
UpdateMap();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前正在拖拽的目标
|
||||
/// </summary>
|
||||
public ITileDraggable DraggingTarget { get; set; }
|
||||
|
||||
public const int MapWidth = 40;
|
||||
@ -109,6 +140,9 @@ public partial class Lab : Node2D
|
||||
|
||||
private TileMapLayer _tileMap;
|
||||
|
||||
/// <summary>
|
||||
/// 更新地图数据
|
||||
/// </summary>
|
||||
public void UpdateMap()
|
||||
{
|
||||
for (int i = 0; i < MapWidth; i++) {
|
||||
@ -132,8 +166,6 @@ public partial class Lab : Node2D
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static bool IsValidPosition(Vector2I pos)
|
||||
{
|
||||
int x = pos.X;
|
||||
@ -143,6 +175,7 @@ public partial class Lab : Node2D
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<Vector2I> GetNeighbors(Vector2I pos) {
|
||||
int x = pos.X;
|
||||
int y = pos.Y;
|
||||
@ -166,6 +199,12 @@ public partial class Lab : Node2D
|
||||
return neighbor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取最短路径
|
||||
/// </summary>
|
||||
/// <param name="start">起点</param>
|
||||
/// <param name="end">终点</param>
|
||||
/// <returns>路径点列表</returns>
|
||||
public List<Vector2I> GetShortestPath(Vector2I start, Vector2I end)
|
||||
{
|
||||
for (int j = 0; j < MapHeight; j++) {
|
||||
@ -258,19 +297,35 @@ public partial class Lab : Node2D
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定位置的地图节点类型
|
||||
/// </summary>
|
||||
/// <param name="pos">位置</param>
|
||||
/// <returns>节点类型</returns>
|
||||
public MapNodeType GetMapNodeTypeOfPosition(Vector2I pos) {
|
||||
if (!IsValidPosition(pos)) return MapNodeType.Invalid;
|
||||
return _blocks[pos.X, pos.Y];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 世界坐标转网格坐标
|
||||
/// </summary>
|
||||
/// <param name="pos">世界坐标</param>
|
||||
/// <returns>网格坐标</returns>
|
||||
public Vector2I Point2Coord(Vector2 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) {
|
||||
if (!_furnitureIDs.ContainsKey(fId)) return Vector2I.Zero;
|
||||
if (idx < 0 || idx > _furnitureIDs[fId].SpecialTiles.Length) return Vector2I.Zero;
|
||||
return _furnitureIDs[fId].SpecialTiles[idx].Position + _furnitureIDs[fId].TilePosition;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,9 @@ using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// 场景加载器
|
||||
/// </summary>
|
||||
public partial class Loader : Control
|
||||
{
|
||||
private ProgressBar _progressBar;
|
||||
@ -12,6 +15,9 @@ public partial class Loader : Control
|
||||
};
|
||||
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
/// <summary>
|
||||
/// 场景加载完成时调用
|
||||
/// </summary>
|
||||
public override void _Ready()
|
||||
{
|
||||
_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.
|
||||
/// <summary>
|
||||
/// 每帧更新
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
Godot.Collections.Array progress = new();
|
||||
@ -53,4 +63,4 @@ public partial class Loader : Control
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,25 @@
|
||||
using Godot;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Logo场景
|
||||
/// </summary>
|
||||
public partial class LogoScene : Node2D
|
||||
{
|
||||
/// <summary>
|
||||
/// 场景加载完成时调用
|
||||
/// </summary>
|
||||
public override void _Ready() {
|
||||
GetNode<AnimationPlayer>("AnimationPlayer").AnimationFinished += OnAnimationPlayerAnimationFinished;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 动画播放结束回调
|
||||
/// </summary>
|
||||
/// <param name="animationName">动画名称</param>
|
||||
public void OnAnimationPlayerAnimationFinished(StringName animationName)
|
||||
{
|
||||
GD.Print("FFF");
|
||||
GetTree().ChangeSceneToFile("res://scenes/loader.tscn");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,44 +12,142 @@ namespace Models;
|
||||
/// </summary>
|
||||
public static class CoreIds
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认命名空间
|
||||
/// </summary>
|
||||
public const string Namespace = "core";
|
||||
|
||||
// Disciplines
|
||||
/// <summary>
|
||||
/// 生物学
|
||||
/// </summary>
|
||||
public const string DisciplineBiology = "core:discipline_biology";
|
||||
/// <summary>
|
||||
/// 化学
|
||||
/// </summary>
|
||||
public const string DisciplineChemistry = "core:discipline_chemistry";
|
||||
/// <summary>
|
||||
/// 环境科学
|
||||
/// </summary>
|
||||
public const string DisciplineEnvironment = "core:discipline_environment";
|
||||
/// <summary>
|
||||
/// 材料学
|
||||
/// </summary>
|
||||
public const string DisciplineMaterials = "core:discipline_materials";
|
||||
/// <summary>
|
||||
/// 医学
|
||||
/// </summary>
|
||||
public const string DisciplineMedicine = "core:discipline_medicine";
|
||||
/// <summary>
|
||||
/// 计算机
|
||||
/// </summary>
|
||||
public const string DisciplineComputer = "core:discipline_computer";
|
||||
/// <summary>
|
||||
/// 数学
|
||||
/// </summary>
|
||||
public const string DisciplineMath = "core:discipline_math";
|
||||
/// <summary>
|
||||
/// 物理学
|
||||
/// </summary>
|
||||
public const string DisciplinePhysics = "core:discipline_physics";
|
||||
/// <summary>
|
||||
/// 机械工程
|
||||
/// </summary>
|
||||
public const string DisciplineMechanical = "core:discipline_mechanical";
|
||||
/// <summary>
|
||||
/// 哲学
|
||||
/// </summary>
|
||||
public const string DisciplinePhilosophy = "core:discipline_philosophy";
|
||||
/// <summary>
|
||||
/// 经济学
|
||||
/// </summary>
|
||||
public const string DisciplineEconomics = "core:discipline_economics";
|
||||
/// <summary>
|
||||
/// 法学
|
||||
/// </summary>
|
||||
public const string DisciplineLaw = "core:discipline_law";
|
||||
/// <summary>
|
||||
/// 文学
|
||||
/// </summary>
|
||||
public const string DisciplineLiterature = "core:discipline_literature";
|
||||
/// <summary>
|
||||
/// 农学
|
||||
/// </summary>
|
||||
public const string DisciplineAgriculture = "core:discipline_agriculture";
|
||||
/// <summary>
|
||||
/// 管理学
|
||||
/// </summary>
|
||||
public const string DisciplineManagement = "core:discipline_management";
|
||||
/// <summary>
|
||||
/// 艺术
|
||||
/// </summary>
|
||||
public const string DisciplineArt = "core:discipline_art";
|
||||
|
||||
// Archetypes
|
||||
/// <summary>
|
||||
/// 卷王
|
||||
/// </summary>
|
||||
public const string ArchetypeGrinder = "core:archetype_grinder";
|
||||
/// <summary>
|
||||
/// 摸鱼
|
||||
/// </summary>
|
||||
public const string ArchetypeSlacker = "core:archetype_slacker";
|
||||
/// <summary>
|
||||
/// 精英
|
||||
/// </summary>
|
||||
public const string ArchetypeElite = "core:archetype_elite";
|
||||
/// <summary>
|
||||
/// 天才
|
||||
/// </summary>
|
||||
public const string ArchetypeProdigy = "core:archetype_prodigy";
|
||||
/// <summary>
|
||||
/// 吉祥物
|
||||
/// </summary>
|
||||
public const string ArchetypeMascot = "core:archetype_mascot";
|
||||
|
||||
// Roles
|
||||
/// <summary>
|
||||
/// 码农
|
||||
/// </summary>
|
||||
public const string RoleCoder = "core:role_coder";
|
||||
/// <summary>
|
||||
/// 写手
|
||||
/// </summary>
|
||||
public const string RoleWriter = "core:role_writer";
|
||||
/// <summary>
|
||||
/// 实验员
|
||||
/// </summary>
|
||||
public const string RoleLabRat = "core:role_lab_rat";
|
||||
/// <summary>
|
||||
/// 讲演者
|
||||
/// </summary>
|
||||
public const string RolePresenter = "core:role_presenter";
|
||||
/// <summary>
|
||||
/// 记录员
|
||||
/// </summary>
|
||||
public const string RoleScribe = "core:role_scribe";
|
||||
/// <summary>
|
||||
/// 辩论者
|
||||
/// </summary>
|
||||
public const string RoleOrator = "core:role_orator";
|
||||
/// <summary>
|
||||
/// 管家
|
||||
/// </summary>
|
||||
public const string RoleSteward = "core:role_steward";
|
||||
/// <summary>
|
||||
/// 炼金术士
|
||||
/// </summary>
|
||||
public const string RoleAlchemist = "core:role_alchemist";
|
||||
/// <summary>
|
||||
/// 极客
|
||||
/// </summary>
|
||||
public const string RoleGeek = "core:role_geek";
|
||||
/// <summary>
|
||||
/// 勘测员
|
||||
/// </summary>
|
||||
public const string RoleSurveyor = "core:role_surveyor";
|
||||
/// <summary>
|
||||
/// 思考者
|
||||
/// </summary>
|
||||
public const string RoleThinker = "core:role_thinker";
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Models/CoreIds.cs.uid
Normal file
1
scripts/Models/CoreIds.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://b8dobhu11y8kt
|
||||
@ -17,16 +17,36 @@ namespace Models;
|
||||
/// </summary>
|
||||
public sealed class LocalizedText
|
||||
{
|
||||
/// <summary>
|
||||
/// 键值
|
||||
/// </summary>
|
||||
public string Key { get; set; }
|
||||
/// <summary>
|
||||
/// 默认值
|
||||
/// </summary>
|
||||
public string Fallback { get; set; }
|
||||
}
|
||||
|
||||
public sealed class DefinitionHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义ID
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public LocalizedText Name { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public LocalizedText Description { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 图标路径
|
||||
/// </summary>
|
||||
public string IconPath { get; set; }
|
||||
/// <summary>
|
||||
/// 标签列表
|
||||
/// </summary>
|
||||
public List<string> Tags { get; } = new();
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Models/DefinitionSupport.cs.uid
Normal file
1
scripts/Models/DefinitionSupport.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bpvgbmxh1gq6u
|
||||
@ -14,18 +14,44 @@ namespace Models;
|
||||
/// </summary>
|
||||
public sealed class DisciplineDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// 基础头部信息
|
||||
/// </summary>
|
||||
public DefinitionHeader Header { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 学科Buff
|
||||
/// </summary>
|
||||
public DisciplineBuff Buff { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 角色池ID列表
|
||||
/// </summary>
|
||||
public List<string> RolePoolIds { get; } = new();
|
||||
/// <summary>
|
||||
/// 物品池ID列表
|
||||
/// </summary>
|
||||
public List<string> ItemPoolIds { get; } = new();
|
||||
/// <summary>
|
||||
/// 任务关键词ID列表
|
||||
/// </summary>
|
||||
public List<string> TaskKeywordIds { get; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 学科Buff定义
|
||||
/// </summary>
|
||||
public sealed class DisciplineBuff
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public LocalizedText Name { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public LocalizedText Description { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 修正包
|
||||
/// </summary>
|
||||
public ModifierBundle Modifiers { get; set; } = new();
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Models/DisciplineDefinitions.cs.uid
Normal file
1
scripts/Models/DisciplineDefinitions.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cdec5vh0h2md
|
||||
@ -12,32 +12,31 @@ namespace Models;
|
||||
/// </summary>
|
||||
public enum AttributeType
|
||||
{
|
||||
Academic,
|
||||
Engineering,
|
||||
Writing,
|
||||
Financial,
|
||||
Social,
|
||||
Activation
|
||||
Academic, // 学术
|
||||
Engineering, // 工程
|
||||
Writing, // 写作
|
||||
Financial, // 财务
|
||||
Social, // 社交
|
||||
Activation // 活跃/执行
|
||||
}
|
||||
|
||||
public enum ResourceType
|
||||
{
|
||||
Money,
|
||||
Reputation,
|
||||
ResearchPoints,
|
||||
Paper,
|
||||
Inspiration,
|
||||
Time
|
||||
Money, // 金钱
|
||||
Reputation, // 声望
|
||||
ResearchPoints, // 科研点
|
||||
Paper, // 论文
|
||||
Inspiration, // 灵感
|
||||
Time // 时间
|
||||
}
|
||||
|
||||
public enum StatusType
|
||||
{
|
||||
Stress,
|
||||
Sanity,
|
||||
Mood,
|
||||
Stamina,
|
||||
Loyalty,
|
||||
Energy,
|
||||
Health
|
||||
}
|
||||
|
||||
Stress, // 压力
|
||||
Sanity, // 理智
|
||||
Mood, // 心情
|
||||
Stamina, // 体力
|
||||
Loyalty, // 忠诚度
|
||||
Energy, // 精力
|
||||
Health // 健康
|
||||
}
|
||||
1
scripts/Models/DomainEnums.cs.uid
Normal file
1
scripts/Models/DomainEnums.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dco1nttd0pgqp
|
||||
@ -14,13 +14,36 @@ namespace Models;
|
||||
/// </summary>
|
||||
public sealed class GameContentDatabase
|
||||
{
|
||||
/// <summary>
|
||||
/// 学科定义字典
|
||||
/// </summary>
|
||||
public Dictionary<string, DisciplineDefinition> Disciplines { get; } = new();
|
||||
/// <summary>
|
||||
/// 原型定义字典
|
||||
/// </summary>
|
||||
public Dictionary<string, ArchetypeDefinition> Archetypes { get; } = new();
|
||||
/// <summary>
|
||||
/// 角色定义字典
|
||||
/// </summary>
|
||||
public Dictionary<string, RoleDefinition> Roles { get; } = new();
|
||||
/// <summary>
|
||||
/// 特质定义字典
|
||||
/// </summary>
|
||||
public Dictionary<string, TraitDefinition> Traits { get; } = new();
|
||||
/// <summary>
|
||||
/// 任务定义字典
|
||||
/// </summary>
|
||||
public Dictionary<string, TaskDefinition> Tasks { get; } = new();
|
||||
/// <summary>
|
||||
/// 物品定义字典
|
||||
/// </summary>
|
||||
public Dictionary<string, ItemDefinition> Items { get; } = new();
|
||||
/// <summary>
|
||||
/// 论文定义字典
|
||||
/// </summary>
|
||||
public Dictionary<string, PaperDefinition> Papers { get; } = new();
|
||||
/// <summary>
|
||||
/// 肉鸽天赋定义字典
|
||||
/// </summary>
|
||||
public Dictionary<string, RoguelitePerkDefinition> RoguelitePerks { get; } = new();
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Models/GameContentDatabase.cs.uid
Normal file
1
scripts/Models/GameContentDatabase.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://i6eakpcujm0q
|
||||
@ -17,69 +17,179 @@ namespace Models;
|
||||
/// </summary>
|
||||
public sealed class GameState
|
||||
{
|
||||
/// <summary>
|
||||
/// 回合状态
|
||||
/// </summary>
|
||||
public TurnState Turn { get; } = new();
|
||||
/// <summary>
|
||||
/// 经济状态
|
||||
/// </summary>
|
||||
public EconomyState Economy { get; } = new();
|
||||
/// <summary>
|
||||
/// 人员状态
|
||||
/// </summary>
|
||||
public RosterState Roster { get; } = new();
|
||||
/// <summary>
|
||||
/// 任务状态
|
||||
/// </summary>
|
||||
public TaskState Tasks { get; } = new();
|
||||
/// <summary>
|
||||
/// 库存状态
|
||||
/// </summary>
|
||||
public InventoryState Inventory { get; } = new();
|
||||
/// <summary>
|
||||
/// 协同状态
|
||||
/// </summary>
|
||||
public SynergyState Synergy { get; } = new();
|
||||
/// <summary>
|
||||
/// 肉鸽状态
|
||||
/// </summary>
|
||||
public RogueliteState Roguelite { get; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 游戏阶段
|
||||
/// </summary>
|
||||
public enum GamePhase
|
||||
{
|
||||
Planning,
|
||||
Execution,
|
||||
Review
|
||||
Planning, // 筹备阶段
|
||||
Execution, // 执行阶段
|
||||
Review // 结算阶段
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回合状态数据
|
||||
/// </summary>
|
||||
public sealed class TurnState
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前回合
|
||||
/// </summary>
|
||||
public int CurrentTurn { get; set; } = 1;
|
||||
/// <summary>
|
||||
/// 最大回合
|
||||
/// </summary>
|
||||
public int MaxTurns { get; set; } = 30;
|
||||
/// <summary>
|
||||
/// 当前阶段
|
||||
/// </summary>
|
||||
public GamePhase Phase { get; set; } = GamePhase.Planning;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 经济状态数据
|
||||
/// </summary>
|
||||
public sealed class EconomyState
|
||||
{
|
||||
/// <summary>
|
||||
/// 资金
|
||||
/// </summary>
|
||||
public int Money { get; set; } = 50000;
|
||||
/// <summary>
|
||||
/// 声望
|
||||
/// </summary>
|
||||
public int Reputation { get; set; }
|
||||
/// <summary>
|
||||
/// 科研点数
|
||||
/// </summary>
|
||||
public int ResearchPoints { get; set; }
|
||||
/// <summary>
|
||||
/// 利率
|
||||
/// </summary>
|
||||
public float InterestRate { get; set; } = 0.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 人员状态数据
|
||||
/// </summary>
|
||||
public sealed class RosterState
|
||||
{
|
||||
/// <summary>
|
||||
/// 导师模型
|
||||
/// </summary>
|
||||
public MentorModel Mentor { get; set; } = new("Player");
|
||||
/// <summary>
|
||||
/// 学生列表
|
||||
/// </summary>
|
||||
public List<StudentModel> Students { get; } = new();
|
||||
/// <summary>
|
||||
/// 职工列表
|
||||
/// </summary>
|
||||
public List<StaffModel> Staffs { get; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务状态数据
|
||||
/// </summary>
|
||||
public sealed class TaskState
|
||||
{
|
||||
/// <summary>
|
||||
/// 活动任务列表
|
||||
/// </summary>
|
||||
public List<TaskModel> ActiveTasks { get; } = new();
|
||||
/// <summary>
|
||||
/// 已完成任务列表
|
||||
/// </summary>
|
||||
public List<TaskModel> CompletedTasks { get; } = new();
|
||||
/// <summary>
|
||||
/// 失败任务列表
|
||||
/// </summary>
|
||||
public List<TaskModel> FailedTasks { get; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 库存状态数据
|
||||
/// </summary>
|
||||
public sealed class InventoryState
|
||||
{
|
||||
/// <summary>
|
||||
/// 物品计数
|
||||
/// </summary>
|
||||
public Dictionary<string, int> ItemCounts { get; } = new();
|
||||
/// <summary>
|
||||
/// 论文计数
|
||||
/// </summary>
|
||||
public Dictionary<PaperRank, int> PaperCounts { get; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 协同状态数据
|
||||
/// </summary>
|
||||
public sealed class SynergyState
|
||||
{
|
||||
/// <summary>
|
||||
/// 原型堆叠数
|
||||
/// </summary>
|
||||
public Dictionary<string, int> ArchetypeStacks { get; } = new();
|
||||
/// <summary>
|
||||
/// 角色堆叠数
|
||||
/// </summary>
|
||||
public Dictionary<string, int> RoleStacks { get; } = new();
|
||||
/// <summary>
|
||||
/// 激活的协同ID列表
|
||||
/// </summary>
|
||||
public List<string> ActiveSynergyIds { get; } = new();
|
||||
/// <summary>
|
||||
/// 激活的修正包
|
||||
/// </summary>
|
||||
public ModifierBundle ActiveModifiers { get; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 肉鸽状态数据
|
||||
/// </summary>
|
||||
public sealed class RogueliteState
|
||||
{
|
||||
/// <summary>
|
||||
/// 校友卡ID列表
|
||||
/// </summary>
|
||||
public List<string> AlumniCardIds { get; } = new();
|
||||
/// <summary>
|
||||
/// 遗产解锁ID列表
|
||||
/// </summary>
|
||||
public List<string> LegacyUnlockIds { get; } = new();
|
||||
/// <summary>
|
||||
/// 头衔保留等级
|
||||
/// </summary>
|
||||
public int TitleRetentionLevel { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Models/GameState.cs.uid
Normal file
1
scripts/Models/GameState.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bm4fbpk2hmxkj
|
||||
@ -14,29 +14,29 @@ namespace Models;
|
||||
/// </summary>
|
||||
public enum ItemCategory
|
||||
{
|
||||
Facility,
|
||||
Equipment,
|
||||
Consumable
|
||||
Facility, // 设施
|
||||
Equipment, // 装备
|
||||
Consumable // 消耗品
|
||||
}
|
||||
|
||||
public enum FacilityPlacement
|
||||
{
|
||||
Lab,
|
||||
ServerRoom,
|
||||
Admin,
|
||||
Library,
|
||||
RestArea,
|
||||
Field,
|
||||
Global
|
||||
Lab, // 实验室
|
||||
ServerRoom, // 机房
|
||||
Admin, // 行政区
|
||||
Library, // 图书馆
|
||||
RestArea, // 休息区
|
||||
Field, // 场地
|
||||
Global // 全局
|
||||
}
|
||||
|
||||
public enum EquipmentSlot
|
||||
{
|
||||
Tool,
|
||||
Accessory,
|
||||
Body,
|
||||
Head,
|
||||
Hand
|
||||
Tool, // 工具
|
||||
Accessory, // 饰品
|
||||
Body, // 身体
|
||||
Head, // 头部
|
||||
Hand // 手部
|
||||
}
|
||||
|
||||
public sealed class ItemDefinition
|
||||
@ -55,5 +55,4 @@ public sealed class ItemEffect
|
||||
{
|
||||
public ModifierBundle Modifiers { get; set; } = new();
|
||||
public List<string> RuleIds { get; } = new();
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Models/ItemDefinitions.cs.uid
Normal file
1
scripts/Models/ItemDefinitions.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://buicivgjyfews
|
||||
@ -14,22 +14,41 @@ namespace Models;
|
||||
/// </summary>
|
||||
public sealed class MentorModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 导师模式
|
||||
/// </summary>
|
||||
public enum MentorModeType
|
||||
{
|
||||
Worker,
|
||||
Manager
|
||||
Worker, // 打工人
|
||||
Manager // 管理者
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 核心单位数据
|
||||
/// </summary>
|
||||
public UnitModel Core { get; }
|
||||
/// <summary>
|
||||
/// 导师特有资源
|
||||
/// </summary>
|
||||
public MentorResources Resources { get; }
|
||||
/// <summary>
|
||||
/// 当前模式
|
||||
/// </summary>
|
||||
public MentorModeType Mode { get; set; } = MentorModeType.Worker;
|
||||
|
||||
/// <summary>
|
||||
/// 名字(便捷访问)
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => Core.Name;
|
||||
set => Core.Name = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="name">名字</param>
|
||||
public MentorModel(string name)
|
||||
{
|
||||
Core = new UnitModel(name);
|
||||
@ -42,10 +61,13 @@ public sealed class MentorModel
|
||||
/// </summary>
|
||||
public sealed class MentorResources
|
||||
{
|
||||
/// <summary>
|
||||
/// 精力值
|
||||
/// </summary>
|
||||
public StatusValue Energy { get; set; }
|
||||
|
||||
public MentorResources()
|
||||
{
|
||||
Energy = new StatusValue(100, 100, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,30 +14,68 @@ namespace Models;
|
||||
/// </summary>
|
||||
public sealed class AttributeModifier
|
||||
{
|
||||
/// <summary>
|
||||
/// 属性类型
|
||||
/// </summary>
|
||||
public AttributeType Type { get; set; }
|
||||
/// <summary>
|
||||
/// 加值
|
||||
/// </summary>
|
||||
public float Add { get; set; }
|
||||
/// <summary>
|
||||
/// 乘数
|
||||
/// </summary>
|
||||
public float Multiplier { get; set; } = 1.0f;
|
||||
}
|
||||
|
||||
public sealed class StatusModifier
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态类型
|
||||
/// </summary>
|
||||
public StatusType Type { get; set; }
|
||||
/// <summary>
|
||||
/// 加值
|
||||
/// </summary>
|
||||
public float Add { get; set; }
|
||||
/// <summary>
|
||||
/// 乘数
|
||||
/// </summary>
|
||||
public float Multiplier { get; set; } = 1.0f;
|
||||
}
|
||||
|
||||
public sealed class ResourceModifier
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源类型
|
||||
/// </summary>
|
||||
public ResourceType Type { get; set; }
|
||||
/// <summary>
|
||||
/// 加值
|
||||
/// </summary>
|
||||
public int Add { get; set; }
|
||||
/// <summary>
|
||||
/// 乘数
|
||||
/// </summary>
|
||||
public float Multiplier { get; set; } = 1.0f;
|
||||
}
|
||||
|
||||
public sealed class ModifierBundle
|
||||
{
|
||||
/// <summary>
|
||||
/// 属性修正列表
|
||||
/// </summary>
|
||||
public List<AttributeModifier> AttributeModifiers { get; } = new();
|
||||
/// <summary>
|
||||
/// 状态修正列表
|
||||
/// </summary>
|
||||
public List<StatusModifier> StatusModifiers { get; } = new();
|
||||
/// <summary>
|
||||
/// 资源修正列表
|
||||
/// </summary>
|
||||
public List<ResourceModifier> ResourceModifiers { get; } = new();
|
||||
/// <summary>
|
||||
/// 规则ID列表
|
||||
/// </summary>
|
||||
public List<string> RuleIds { get; } = new();
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Models/Modifiers.cs.uid
Normal file
1
scripts/Models/Modifiers.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bpjfyyhudrq8b
|
||||
@ -14,5 +14,4 @@ public sealed class PaperDefinition
|
||||
{
|
||||
public DefinitionHeader Header { get; set; } = new();
|
||||
public PaperRank Rank { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Models/PaperDefinitions.cs.uid
Normal file
1
scripts/Models/PaperDefinitions.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://mtm1ypht0ynd
|
||||
@ -20,17 +20,32 @@ public sealed class PropertyValue
|
||||
public const float DefaultMin = 0f;
|
||||
public const float DefaultMax = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// 最小值
|
||||
/// </summary>
|
||||
public float Min { get; }
|
||||
/// <summary>
|
||||
/// 最大值
|
||||
/// </summary>
|
||||
public float Max { get; }
|
||||
|
||||
private float _value;
|
||||
|
||||
/// <summary>
|
||||
/// 当前值(自动限制在 Min/Max 范围内)
|
||||
/// </summary>
|
||||
public float Value
|
||||
{
|
||||
get => _value;
|
||||
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)
|
||||
{
|
||||
if (max < min)
|
||||
@ -44,13 +59,26 @@ public sealed class PropertyValue
|
||||
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)
|
||||
: this((float)value, min, max)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取显示的整数值
|
||||
/// </summary>
|
||||
/// <returns>整数值</returns>
|
||||
public int DisplayInt() => (int)_value;
|
||||
|
||||
/// <summary>
|
||||
/// 获取归一化值 (0.0 - 1.0)
|
||||
/// </summary>
|
||||
public float Normalized
|
||||
{
|
||||
get
|
||||
@ -60,10 +88,22 @@ public sealed class PropertyValue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 克隆
|
||||
/// </summary>
|
||||
/// <returns>新实例</returns>
|
||||
public PropertyValue Clone() => new(Value, Min, Max);
|
||||
|
||||
/// <summary>
|
||||
/// 增加值
|
||||
/// </summary>
|
||||
/// <param name="delta">增量</param>
|
||||
public void Add(float delta) => Value += delta;
|
||||
|
||||
/// <summary>
|
||||
/// 减少值
|
||||
/// </summary>
|
||||
/// <param name="delta">减量</param>
|
||||
public void Subtract(float delta) => Value -= delta;
|
||||
|
||||
public override string ToString() => DisplayInt().ToString();
|
||||
@ -128,4 +168,4 @@ public sealed class PropertyValue
|
||||
if (value > max) return max;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,16 +12,27 @@ namespace Models;
|
||||
/// </summary>
|
||||
public enum RoguelitePerkType
|
||||
{
|
||||
AlumniCard,
|
||||
LegacyProgress,
|
||||
TitleRetention
|
||||
AlumniCard, // 校友卡
|
||||
LegacyProgress, // 遗产进度
|
||||
TitleRetention // 头衔保留
|
||||
}
|
||||
|
||||
public sealed class RoguelitePerkDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// 基础头部信息
|
||||
/// </summary>
|
||||
public DefinitionHeader Header { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 天赋类型
|
||||
/// </summary>
|
||||
public RoguelitePerkType Type { get; set; }
|
||||
/// <summary>
|
||||
/// 修正包
|
||||
/// </summary>
|
||||
public ModifierBundle Modifiers { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 最大等级
|
||||
/// </summary>
|
||||
public int MaxLevel { get; set; } = 1;
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Models/RogueliteDefinitions.cs.uid
Normal file
1
scripts/Models/RogueliteDefinitions.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cjcfah7uglbad
|
||||
@ -14,22 +14,43 @@ namespace Models;
|
||||
/// </summary>
|
||||
public sealed class StaffModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 职员类型
|
||||
/// </summary>
|
||||
public enum StaffType
|
||||
{
|
||||
PostDoc,
|
||||
JuniorFaculty
|
||||
PostDoc, // 博士后
|
||||
JuniorFaculty // 青年教师
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 核心单位数据
|
||||
/// </summary>
|
||||
public UnitModel Core { get; }
|
||||
/// <summary>
|
||||
/// 职员类型
|
||||
/// </summary>
|
||||
public StaffType Type { get; private set; }
|
||||
/// <summary>
|
||||
/// 动机数据
|
||||
/// </summary>
|
||||
public StaffMotivation Motivation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 名字(便捷访问)
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => Core.Name;
|
||||
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)
|
||||
{
|
||||
Core = new UnitModel(name, random);
|
||||
@ -38,10 +59,21 @@ public sealed class StaffModel
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 职员动机组件
|
||||
/// </summary>
|
||||
public sealed class StaffMotivation
|
||||
{
|
||||
/// <summary>
|
||||
/// 野心
|
||||
/// </summary>
|
||||
public StatusValue Ambition { get; set; } = new(50, 100, 0);
|
||||
/// <summary>
|
||||
/// 忠诚度
|
||||
/// </summary>
|
||||
public StatusValue Loyalty { get; set; } = new(70, 100, 0);
|
||||
/// <summary>
|
||||
/// 合同剩余回合
|
||||
/// </summary>
|
||||
public StatusValue ContractTurns { get; set; } = new(6, 12, 0);
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Models/StaffModel.cs.uid
Normal file
1
scripts/Models/StaffModel.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://cx1jd2ydj5nof
|
||||
@ -19,9 +19,18 @@ public sealed class StatusValue
|
||||
{
|
||||
private PropertyValue _current;
|
||||
|
||||
/// <summary>
|
||||
/// 达到上限事件
|
||||
/// </summary>
|
||||
public event Action OnUpperThresholdReached;
|
||||
/// <summary>
|
||||
/// 达到下限事件
|
||||
/// </summary>
|
||||
public event Action OnLowerThresholdReached;
|
||||
|
||||
/// <summary>
|
||||
/// 当前值
|
||||
/// </summary>
|
||||
public PropertyValue Current
|
||||
{
|
||||
get => _current;
|
||||
@ -32,9 +41,18 @@ public sealed class StatusValue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上限阈值
|
||||
/// </summary>
|
||||
public float UpperThreshold { get; set; }
|
||||
/// <summary>
|
||||
/// 下限阈值
|
||||
/// </summary>
|
||||
public float LowerThreshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数(整数)
|
||||
/// </summary>
|
||||
public StatusValue(int current = 0, int upper = 100, int lower = 0)
|
||||
{
|
||||
_current = new PropertyValue(current, lower, upper);
|
||||
@ -42,6 +60,9 @@ public sealed class StatusValue
|
||||
LowerThreshold = lower;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数(浮点数)
|
||||
/// </summary>
|
||||
public StatusValue(float current, float upper, float lower)
|
||||
{
|
||||
_current = new PropertyValue(current, lower, upper);
|
||||
@ -49,6 +70,9 @@ public sealed class StatusValue
|
||||
LowerThreshold = lower;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数(PropertyValue)
|
||||
/// </summary>
|
||||
public StatusValue(PropertyValue current, float upperThreshold, float lowerThreshold)
|
||||
{
|
||||
_current = current ?? new PropertyValue(0);
|
||||
@ -70,15 +94,21 @@ public sealed class StatusValue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加值
|
||||
/// </summary>
|
||||
public void Add(float value)
|
||||
{
|
||||
_current.Add(value);
|
||||
CheckThresholds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 减少值
|
||||
/// </summary>
|
||||
public void Subtract(float value)
|
||||
{
|
||||
_current.Subtract(value);
|
||||
CheckThresholds();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,25 +18,51 @@ namespace Models;
|
||||
/// </summary>
|
||||
public sealed class StudentModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 学生类型
|
||||
/// </summary>
|
||||
public enum StudentType
|
||||
{
|
||||
MasterCandidate,
|
||||
DoctorCandidate
|
||||
MasterCandidate, // 硕士生
|
||||
DoctorCandidate // 博士生
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 核心单位数据
|
||||
/// </summary>
|
||||
public UnitModel Core { get; }
|
||||
/// <summary>
|
||||
/// 学生类型
|
||||
/// </summary>
|
||||
public StudentType Type { get; private set; }
|
||||
/// <summary>
|
||||
/// 进度数据
|
||||
/// </summary>
|
||||
public StudentProgress Progress { get; }
|
||||
/// <summary>
|
||||
/// 贡献记录
|
||||
/// </summary>
|
||||
public StudentContributions Contributions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 名字(便捷访问)
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => Core.Name;
|
||||
set => Core.Name = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 特质列表(便捷访问)
|
||||
/// </summary>
|
||||
public List<string> Traits => Core.Tags.TraitIds;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="name">名字</param>
|
||||
/// <param name="random">随机数生成器</param>
|
||||
public StudentModel(string name, Random random = null)
|
||||
{
|
||||
var rng = random ?? Random.Shared;
|
||||
@ -52,8 +78,17 @@ public sealed class StudentModel
|
||||
/// </summary>
|
||||
public sealed class StudentProgress
|
||||
{
|
||||
/// <summary>
|
||||
/// 体力
|
||||
/// </summary>
|
||||
public StatusValue Stamina { get; set; }
|
||||
/// <summary>
|
||||
/// 忠诚度
|
||||
/// </summary>
|
||||
public StatusValue Loyalty { get; set; }
|
||||
/// <summary>
|
||||
/// 年级
|
||||
/// </summary>
|
||||
public PropertyValue Grade { get; set; }
|
||||
|
||||
public StudentProgress(StudentModel.StudentType type)
|
||||
@ -70,5 +105,8 @@ public sealed class StudentProgress
|
||||
/// </summary>
|
||||
public sealed class StudentContributions
|
||||
{
|
||||
/// <summary>
|
||||
/// 按任务ID索引的贡献值
|
||||
/// </summary>
|
||||
public Dictionary<Guid, PropertyValue> ByTask { get; } = new();
|
||||
}
|
||||
}
|
||||
@ -14,30 +14,62 @@ namespace Models;
|
||||
/// </summary>
|
||||
public sealed class ArchetypeDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// 基础头部信息
|
||||
/// </summary>
|
||||
public DefinitionHeader Header { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 羁绊层级列表
|
||||
/// </summary>
|
||||
public List<SynergyTier> Tiers { get; } = new();
|
||||
}
|
||||
|
||||
public sealed class RoleDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// 基础头部信息
|
||||
/// </summary>
|
||||
public DefinitionHeader Header { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 羁绊层级列表
|
||||
/// </summary>
|
||||
public List<SynergyTier> Tiers { get; } = new();
|
||||
|
||||
// 如果是学科限定角色(如炼金术士/极客),在这里配置允许的学科 Id。
|
||||
/// <summary>
|
||||
/// 允许的学科ID列表
|
||||
/// </summary>
|
||||
public List<string> AllowedDisciplineIds { get; } = new();
|
||||
}
|
||||
|
||||
public sealed class TraitDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// 基础头部信息
|
||||
/// </summary>
|
||||
public DefinitionHeader Header { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 修正包
|
||||
/// </summary>
|
||||
public ModifierBundle Modifiers { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 规则ID列表
|
||||
/// </summary>
|
||||
public List<string> RuleIds { get; } = new();
|
||||
}
|
||||
|
||||
public sealed class SynergyTier
|
||||
{
|
||||
/// <summary>
|
||||
/// 需求数量
|
||||
/// </summary>
|
||||
public int RequiredCount { get; set; }
|
||||
/// <summary>
|
||||
/// 修正包
|
||||
/// </summary>
|
||||
public ModifierBundle Modifiers { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 规则ID列表
|
||||
/// </summary>
|
||||
public List<string> RuleIds { get; } = new();
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Models/SynergyDefinitions.cs.uid
Normal file
1
scripts/Models/SynergyDefinitions.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://vqos5kin02p1
|
||||
@ -77,4 +77,4 @@ public sealed class TaskRewardSnapshot
|
||||
public int Reputation { get; set; }
|
||||
public List<string> PaperIds { get; } = new();
|
||||
public List<string> ItemIds { get; set; }
|
||||
}
|
||||
}
|
||||
@ -33,10 +33,10 @@ public enum TaskKind
|
||||
|
||||
public enum TaskDifficulty
|
||||
{
|
||||
Water,
|
||||
Standard,
|
||||
Hardcore,
|
||||
BlackBox
|
||||
Water, // 水
|
||||
Standard, // 标准
|
||||
Hardcore, // 硬核
|
||||
BlackBox // 黑箱
|
||||
}
|
||||
|
||||
public enum PaperRank
|
||||
@ -97,5 +97,4 @@ public sealed class TaskRewards
|
||||
public List<string> PaperIds { get; } = new();
|
||||
public List<string> ItemIds { get; } = new();
|
||||
public List<string> BuffIds { get; } = new();
|
||||
}
|
||||
|
||||
}
|
||||
1
scripts/Models/TaskDefinitions.cs.uid
Normal file
1
scripts/Models/TaskDefinitions.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bth3oambl7pnk
|
||||
@ -18,6 +18,9 @@ namespace Models;
|
||||
/// </summary>
|
||||
public static class UnitComponents
|
||||
{
|
||||
/// <summary>
|
||||
/// 网格位置组件
|
||||
/// </summary>
|
||||
public readonly struct GridPosition
|
||||
{
|
||||
public int X { get; }
|
||||
@ -32,6 +35,9 @@ public static class UnitComponents
|
||||
public static GridPosition Zero => new(0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单位身份组件
|
||||
/// </summary>
|
||||
public sealed class UnitIdentity
|
||||
{
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
@ -45,6 +51,9 @@ public static class UnitComponents
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单位属性组件
|
||||
/// </summary>
|
||||
public sealed class UnitAttributes
|
||||
{
|
||||
public PropertyValue Academic { get; set; }
|
||||
@ -66,6 +75,9 @@ public static class UnitComponents
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单位状态组件
|
||||
/// </summary>
|
||||
public sealed class UnitStatuses
|
||||
{
|
||||
public StatusValue Stress { get; set; }
|
||||
@ -82,6 +94,9 @@ public static class UnitComponents
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单位标签组件
|
||||
/// </summary>
|
||||
public sealed class UnitTags
|
||||
{
|
||||
public string DisciplineId { get; set; }
|
||||
@ -92,19 +107,28 @@ public static class UnitComponents
|
||||
public bool HasTrait(string traitId) => TraitIds.Contains(traitId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单位指派组件
|
||||
/// </summary>
|
||||
public sealed class UnitAssignment
|
||||
{
|
||||
public Guid? CurrentTaskId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单位位置组件
|
||||
/// </summary>
|
||||
public sealed class UnitPlacement
|
||||
{
|
||||
public GridPosition Current { get; set; } = GridPosition.Zero;
|
||||
public GridPosition Target { get; set; } = GridPosition.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单位装备组件
|
||||
/// </summary>
|
||||
public sealed class UnitEquipment
|
||||
{
|
||||
public List<string> EquippedItemIds { get; } = new();
|
||||
}
|
||||
}
|
||||
}
|
||||
1
scripts/Models/UnitComponents.cs.uid
Normal file
1
scripts/Models/UnitComponents.cs.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://huerc661rxe7
|
||||
@ -17,20 +17,49 @@ namespace Models;
|
||||
/// </summary>
|
||||
public sealed class UnitModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 身份组件
|
||||
/// </summary>
|
||||
public UnitComponents.UnitIdentity Identity { get; }
|
||||
/// <summary>
|
||||
/// 属性组件
|
||||
/// </summary>
|
||||
public UnitComponents.UnitAttributes Attributes { get; }
|
||||
/// <summary>
|
||||
/// 状态组件
|
||||
/// </summary>
|
||||
public UnitComponents.UnitStatuses Statuses { get; }
|
||||
/// <summary>
|
||||
/// 标签组件
|
||||
/// </summary>
|
||||
public UnitComponents.UnitTags Tags { get; }
|
||||
/// <summary>
|
||||
/// 指派组件
|
||||
/// </summary>
|
||||
public UnitComponents.UnitAssignment Assignment { get; }
|
||||
/// <summary>
|
||||
/// 位置组件
|
||||
/// </summary>
|
||||
public UnitComponents.UnitPlacement Placement { get; }
|
||||
/// <summary>
|
||||
/// 装备组件
|
||||
/// </summary>
|
||||
public UnitComponents.UnitEquipment Equipment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 名字(便捷访问)
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => Identity.Name;
|
||||
set => Identity.Name = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="name">名字</param>
|
||||
/// <param name="random">随机数生成器</param>
|
||||
public UnitModel(string name, Random random = null)
|
||||
{
|
||||
Identity = new UnitComponents.UnitIdentity(name);
|
||||
@ -41,4 +70,4 @@ public sealed class UnitModel
|
||||
Placement = new UnitComponents.UnitPlacement();
|
||||
Equipment = new UnitComponents.UnitEquipment();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,9 @@ using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家数据管理类
|
||||
/// </summary>
|
||||
public partial class Player : Node
|
||||
{
|
||||
/// <summary>
|
||||
@ -37,12 +40,23 @@ public partial class Player : Node
|
||||
public int Total => Facility + Operational + Labor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间轴类型
|
||||
/// </summary>
|
||||
public class TimelineType
|
||||
{
|
||||
/// <summary>
|
||||
/// 内部日期
|
||||
/// </summary>
|
||||
public DateOnly InternalDate { get; set; }
|
||||
|
||||
private Dictionary<DateOnly, HashSet<Guid>> _events;
|
||||
|
||||
/// <summary>
|
||||
/// 订阅事件
|
||||
/// </summary>
|
||||
/// <param name="date">日期</param>
|
||||
/// <param name="eventUuid">事件UUID</param>
|
||||
public void Subscribe(DateOnly date, Guid eventUuid)
|
||||
{
|
||||
_events ??= new Dictionary<DateOnly, HashSet<Guid>>();
|
||||
@ -50,6 +64,11 @@ public partial class Player : Node
|
||||
_events[date].Add(eventUuid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消订阅事件
|
||||
/// </summary>
|
||||
/// <param name="date">日期</param>
|
||||
/// <param name="eventUuid">事件UUID</param>
|
||||
public void Unsubscribe(DateOnly date, Guid eventUuid)
|
||||
{
|
||||
if (_events == null) return;
|
||||
@ -57,6 +76,9 @@ public partial class Player : Node
|
||||
_events[date].Remove(eventUuid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进入下一天
|
||||
/// </summary>
|
||||
private void NextDay()
|
||||
{
|
||||
if (_events == null) return;
|
||||
@ -78,27 +100,53 @@ public partial class Player : Node
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 附加计时器
|
||||
/// </summary>
|
||||
/// <param name="ticker">计时器</param>
|
||||
public void Attach(Timer ticker)
|
||||
{
|
||||
ticker.Timeout += NextDay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="startDate">开始日期</param>
|
||||
public TimelineType(DateOnly startDate)
|
||||
{
|
||||
InternalDate = startDate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 事件触发委托
|
||||
/// </summary>
|
||||
public delegate void EventTriggeredEventHandler(DateOnly date, Guid eventUuid);
|
||||
|
||||
/// <summary>
|
||||
/// 事件触发事件
|
||||
/// </summary>
|
||||
public event EventTriggeredEventHandler OnEventTriggered;
|
||||
|
||||
/// <summary>
|
||||
/// 日期变更委托
|
||||
/// </summary>
|
||||
public delegate void DayChangedEventHandler(DateOnly date);
|
||||
|
||||
/// <summary>
|
||||
/// 日期变更事件
|
||||
/// </summary>
|
||||
public event DayChangedEventHandler OnDayChanged;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 预算实例
|
||||
/// </summary>
|
||||
public static BudgetType Budget { get; set; } = new();
|
||||
/// <summary>
|
||||
/// 时间轴实例
|
||||
/// </summary>
|
||||
public static readonly TimelineType Timeline = new(DateOnly.Parse("2024/11/17"));
|
||||
|
||||
/// <summary>
|
||||
@ -117,13 +165,20 @@ public partial class Player : Node
|
||||
public static DateOnly Date { get; set; }
|
||||
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
/// <summary>
|
||||
/// 场景加载完成时调用
|
||||
/// </summary>
|
||||
public override void _Ready()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
/// <summary>
|
||||
/// 每帧更新
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,31 @@
|
||||
using Godot;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// 资源管理类
|
||||
/// </summary>
|
||||
public partial class Res : Node
|
||||
{
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
/// <summary>
|
||||
/// 场景加载完成时调用
|
||||
/// </summary>
|
||||
public override void _Ready()
|
||||
{
|
||||
}
|
||||
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
/// <summary>
|
||||
/// 每帧更新
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 资源类型枚举
|
||||
/// </summary>
|
||||
public enum Type
|
||||
{
|
||||
Accessory,
|
||||
@ -23,16 +36,32 @@ public partial class Res : Node
|
||||
Phone
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取随机资源路径
|
||||
/// </summary>
|
||||
/// <param name="resType">资源类型</param>
|
||||
/// <returns>资源路径</returns>
|
||||
public static string GetRandom(Type resType)
|
||||
{
|
||||
return GetRandom(resType, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为16x16精灵路径
|
||||
/// </summary>
|
||||
/// <param name="path">原路径</param>
|
||||
/// <returns>16x16路径</returns>
|
||||
private static string To16Path(string path)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var resources = allResources[(int)resType];
|
||||
@ -48,6 +77,9 @@ public partial class Res : Node
|
||||
return ResourceLoader.Exists(path16) ? path16 : path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 身体资源列表
|
||||
/// </summary>
|
||||
public static readonly string[] Body =
|
||||
[
|
||||
"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"
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// 饰品资源列表
|
||||
/// </summary>
|
||||
public static readonly string[] Accessory =
|
||||
[
|
||||
"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"
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// 眼睛资源列表
|
||||
/// </summary>
|
||||
public static readonly string[] Eye =
|
||||
[
|
||||
"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"
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// 发型资源列表
|
||||
/// </summary>
|
||||
public static readonly string[] Hair =
|
||||
[
|
||||
"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"
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// 服装资源列表
|
||||
/// </summary>
|
||||
public static readonly string[] Outfit =
|
||||
[
|
||||
"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"
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// 手机资源列表
|
||||
/// </summary>
|
||||
public static readonly string[] Smartphone =
|
||||
[
|
||||
"res://resources/characters/smartphones/Smartphone_48x48_1.png",
|
||||
@ -515,4 +562,4 @@ public partial class Res : Node
|
||||
Smartphone
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,15 @@
|
||||
using Godot;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// 场景过渡控制器
|
||||
/// </summary>
|
||||
public partial class SceneTransit : CanvasLayer
|
||||
{
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
/// <summary>
|
||||
/// 场景加载完成时调用
|
||||
/// </summary>
|
||||
public override void _Ready()
|
||||
{
|
||||
Layer = -1;
|
||||
@ -11,10 +17,21 @@ public partial class SceneTransit : CanvasLayer
|
||||
}
|
||||
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
/// <summary>
|
||||
/// 每帧更新
|
||||
/// </summary>
|
||||
/// <param name="delta">帧间隔</param>
|
||||
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)
|
||||
{
|
||||
SetProcess(true);
|
||||
@ -39,4 +56,4 @@ public partial class SceneTransit : CanvasLayer
|
||||
SetProcess(false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -5,15 +5,34 @@ using System.Linq;
|
||||
using Models;
|
||||
// ReSharper disable CheckNamespace
|
||||
|
||||
/// <summary>
|
||||
/// 学生角色控制器
|
||||
/// </summary>
|
||||
public partial class Student : CharacterBody2D
|
||||
{
|
||||
/// <summary>
|
||||
/// 移动速度
|
||||
/// </summary>
|
||||
public float Speed { get; set; } = 8.0f;
|
||||
/// <summary>
|
||||
/// 跳跃速度
|
||||
/// </summary>
|
||||
public const float JumpVelocity = -400.0f;
|
||||
/// <summary>
|
||||
/// 是否使用16x16精灵
|
||||
/// </summary>
|
||||
[Export] public bool Use16X16Sprites { get; set; }
|
||||
|
||||
// --- MVP: Model Binding ---
|
||||
/// <summary>
|
||||
/// 学生数据模型
|
||||
/// </summary>
|
||||
public StudentModel Model { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 绑定数据模型
|
||||
/// </summary>
|
||||
/// <param name="model">学生模型</param>
|
||||
public void BindData(StudentModel model)
|
||||
{
|
||||
Model = model;
|
||||
@ -21,15 +40,35 @@ public partial class Student : CharacterBody2D
|
||||
}
|
||||
// --------------------------
|
||||
|
||||
/// <summary>
|
||||
/// 下一个状态类型(未使用)
|
||||
/// </summary>
|
||||
public int NextType = -1;
|
||||
|
||||
private Queue<Vector2I> _pathToGo = new();
|
||||
private AnimationPlayer _animationPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// 角色状态枚举
|
||||
/// </summary>
|
||||
public enum CharacterState { Idle, Walking, Sitting, SittingDown, StandingUp }
|
||||
/// <summary>
|
||||
/// 当前状态
|
||||
/// </summary>
|
||||
public CharacterState State { get; set; } = CharacterState.Idle;
|
||||
|
||||
/// <summary>
|
||||
/// 方向枚举
|
||||
/// </summary>
|
||||
public enum Direction { Up, Down, Left, Right }
|
||||
/// <summary>
|
||||
/// 目标方向
|
||||
/// </summary>
|
||||
public Direction TargetDirection { get; set; } = Direction.Up;
|
||||
|
||||
/// <summary>
|
||||
/// 状态队列
|
||||
/// </summary>
|
||||
public Queue<CharacterState> StateQueue { get; set; } = new();
|
||||
|
||||
private Vector2I _targetSpecialPosition;
|
||||
@ -54,6 +93,11 @@ public partial class Student : CharacterBody2D
|
||||
State = StateQueue.Dequeue();
|
||||
GlobalPosition = _lastWalkablePosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 物理处理
|
||||
/// </summary>
|
||||
/// <param name="delta">时间间隔</param>
|
||||
public override void _PhysicsProcess(double delta)
|
||||
{
|
||||
if (StateQueue.Count > 0) {
|
||||
@ -141,6 +185,9 @@ public partial class Student : CharacterBody2D
|
||||
// MoveAndSlide();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 准备就绪时调用
|
||||
/// </summary>
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
@ -163,6 +210,10 @@ public partial class Student : CharacterBody2D
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移动跟随路径
|
||||
/// </summary>
|
||||
/// <param name="path">路径点列表</param>
|
||||
public void MoveFollowPath(List<Vector2I> path)
|
||||
{
|
||||
foreach (var p in path)
|
||||
@ -171,8 +222,14 @@ public partial class Student : CharacterBody2D
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关联的格子ID
|
||||
/// </summary>
|
||||
public Guid CubeId;
|
||||
|
||||
/// <summary>
|
||||
/// 随机前往某处
|
||||
/// </summary>
|
||||
public void GoTo() {
|
||||
if (State == CharacterState.SittingDown || State == CharacterState.StandingUp || State == CharacterState.Walking)
|
||||
{
|
||||
@ -201,6 +258,9 @@ public partial class Student : CharacterBody2D
|
||||
RandomChangeBody();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前往座位
|
||||
/// </summary>
|
||||
public void GoToSeat() {
|
||||
if (State == CharacterState.SittingDown || State == CharacterState.StandingUp || State == CharacterState.Walking)
|
||||
{
|
||||
@ -248,6 +308,9 @@ public partial class Student : CharacterBody2D
|
||||
RandomChangeBody();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 随机更换外观
|
||||
/// </summary>
|
||||
private void RandomChangeBody()
|
||||
{
|
||||
GetNode<Sprite2D>("parts/body").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Body, Use16X16Sprites));
|
||||
@ -257,4 +320,4 @@ public partial class Student : CharacterBody2D
|
||||
GetNode<Sprite2D>("parts/accessory").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Accessory, Use16X16Sprites));
|
||||
GetNode<Sprite2D>("parts/smartphone").Texture = ResourceLoader.Load<Texture2D>(Res.GetRandom(Res.Type.Phone, Use16X16Sprites));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,20 +2,33 @@ using Godot;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// 学生姓名生成器
|
||||
/// </summary>
|
||||
public partial class StudentName : Node
|
||||
{
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
/// <summary>
|
||||
/// 场景加载完成时调用
|
||||
/// </summary>
|
||||
public override void _Ready()
|
||||
{
|
||||
}
|
||||
|
||||
// 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 string GenerateName()
|
||||
{
|
||||
string[] 姓氏 = { "大", "小", "飞", "傻", "呆", "雷", "东", "西", "王", "赵", "李", "张", "刘", "周" ,"赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈", "褚", "卫", "蒋", "沈", "韩", "杨", "朱", "秦", "尤", "许", "何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏", "陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章", "云", "苏", "潘", "葛", "奚", "范", "彭", "郎", "鲁", "韦", "昌", "马", "苗", "凤", "花", "方", "俞", "任", "袁", "柳", "酆", "鲍", "史", "唐", "费", "廉", "岑", "薛", "雷", "贺", "倪", "汤", "滕", "殷", "罗", "毕", "郝", "邬", "安", "常", "乐", "于", "时", "傅", "皮", "卞", "齐", "康", "伍", "余", "元", "卜", "顾", "孟", "平", "黄", "和", "穆", "萧", "尹", "姚", "邵", "湛", "汪", "祁", "毛", "禹", "狄", "米", "贝", "明", "臧", "计", "伏", "成", "戴", "谈", "宋", "茅", "庞", "熊", "纪", "舒", "屈", "项", "祝", "董", "梁", "杜", "阮", "蓝", "闵", "席", "季", "麻", "强", "贾", "路", "娄", "危", "江", "童", "颜", "郭", "梅", "盛", "林", "刁", "钟", "徐", "邱", "骆", "高", "夏", "蔡", "田", "樊", "胡", "凌", "霍", "虞", "万", "支", "柯", "昝", "管", "卢", "莫", "经", "房", "裘", "缪", "干", "解", "应", "宗", "丁", "宣", "贲", "邓", "郁", "单", "杭", "洪", "包", "诸", "左", "石", "崔", "吉", "钮", "龚", "程", "嵇", "邢", "滑", "裴", "陆", "荣", "翁", "荀", "羊", "於", "惠", "甄", "麴", "家", "封", "芮", "羿", "储", "靳", "汲", "邴", "糜", "松", "井", "段", "富", "巫", "乌", "焦", "巴", "弓", "牧", "隗", "山", "谷", "车", "侯", "宓", "蓬", "全", "郗", "班", "仰", "秋", "仲", "伊", "宫", "宁", "仇", "栾", "暴", "甘", "钭", "厉", "戎", "祖", "武", "符", "刘", "景", "詹", "束", "龙", "叶", "幸", "司", "韶", "郜", "黎", "蓟", "薄", "印", "宿", "白", "怀", "蒲", "邰", "从", "鄂", "索", "咸", "籍", "赖", "卓", "蔺", "屠", "蒙", "池", "乔", "阴", "欎", "胥", "能", "苍", "双", "闻", "莘", "党", "翟", "谭", "贡", "劳", "逄", "姬", "申", "扶", "堵", "冉", "宰", "郦", "雍", "舄", "璩", "桑", "桂", "濮", "牛", "寿", "通", "边", "扈", "燕", "冀", "郏", "浦", "尚", "农", "温", "别", "庄", "晏", "柴", "瞿", "阎", "充", "慕", "连", "茹", "习", "宦", "艾", "鱼", "容", "向", "古", "易", "慎", "戈", "廖", "庾", "终", "暨", "居", "衡", "步", "都", "耿", "满", "弘", "匡", "国", "文", "寇", "广", "禄", "阙", "东", "殴", "殳", "沃", "利", "蔚", "越", "夔", "隆", "师", "巩", "厍", "聂", "晁", "勾", "敖", "融", "冷", "訾", "辛", "阚", "那", "简", "饶", "空", "曾", "毋", "沙", "乜", "养", "鞠", "须", "丰", "巢", "关", "蒯", "相", "查", "後", "荆", "红", "游", "竺", "权", "逯", "盖", "益", "桓", "公", "万俟", "司马", "上官", "欧阳", "夏侯", "诸葛", "闻人", "东方", "赫连", "皇甫", "尉迟", "公羊", "澹台", "公冶", "宗政", "濮阳", "淳于", "单于", "太叔", "申屠", "公孙", "仲孙", "轩辕", "令狐", "钟离", "宇文", "长孙", "慕容", "鲜于", "闾丘", "司徒", "司空", "亓官", "司寇", "仉", "督", "子车", "颛孙", "端木", "巫马", "公西", "漆雕", "乐正", "壤驷", "公良", "拓跋", "夹谷", "宰父", "谷梁", "晋", "楚", "闫", "法", "汝", "鄢", "涂", "钦", "段干", "百里", "东郭", "南门", "呼延", "归", "海", "羊舌", "微生", "岳", "帅", "缑", "亢", "况", "后", "有", "琴", "梁丘", "左丘", "东门", "西门", "商", "牟", "佘", "佴", "伯", "赏", "南宫", "墨", "哈", "谯", "笪", "年", "爱", "阳", "佟", "第五", "言", "福", "百", "家", "姓", "终"};
|
||||
/// <summary>
|
||||
/// 生成随机姓名
|
||||
/// </summary>
|
||||
/// <returns>随机姓名</returns>
|
||||
public string GenerateName()
|
||||
{ string[] 姓氏 = { "大", "小", "飞", "傻", "呆", "雷", "东", "西", "王", "赵", "李", "张", "刘", "周" ,"赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈", "褚", "卫", "蒋", "沈", "韩", "杨", "朱", "秦", "尤", "许", "何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏", "陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章", "云", "苏", "潘", "葛", "奚", "范", "彭", "郎", "鲁", "韦", "昌", "马", "苗", "凤", "花", "方", "俞", "任", "袁", "柳", "酆", "鲍", "史", "唐", "费", "廉", "岑", "薛", "雷", "贺", "倪", "汤", "滕", "殷", "罗", "毕", "郝", "邬", "安", "常", "乐", "于", "时", "傅", "皮", "卞", "齐", "康", "伍", "余", "元", "卜", "顾", "孟", "平", "黄", "和", "穆", "萧", "尹", "姚", "邵", "湛", "汪", "祁", "毛", "禹", "狄", "米", "贝", "明", "臧", "计", "伏", "成", "戴", "谈", "宋", "茅", "庞", "熊", "纪", "舒", "屈", "项", "祝", "董", "梁", "杜", "阮", "蓝", "闵", "席", "季", "麻", "强", "贾", "路", "娄", "危", "江", "童", "颜", "郭", "梅", "盛", "林", "刁", "钟", "徐", "邱", "骆", "高", "夏", "蔡", "田", "樊", "胡", "凌", "霍", "虞", "万", "支", "柯", "昝", "管", "卢", "莫", "经", "房", "裘", "缪", "干", "解", "应", "宗", "丁", "宣", "贲", "邓", "郁", "单", "杭", "洪", "包", "诸", "左", "石", "崔", "吉", "钮", "龚", "程", "嵇", "邢", "滑", "裴", "陆", "荣", "翁", "荀", "羊", "於", "惠", "甄", "麴", "家", "封", "芮", "羿", "储", "靳", "汲", "邴", "糜", "松", "井", "段", "富", "巫", "乌", "焦", "巴", "弓", "牧", "隗", "山", "谷", "车", "侯", "宓", "蓬", "全", "郗", "班", "仰", "秋", "仲", "伊", "宫", "宁", "仇", "栾", "暴", "甘", "钭", "厉", "戎", "祖", "武", "符", "刘", "景", "詹", "束", "龙", "叶", "幸", "司", "韶", "郜", "黎", "蓟", "薄", "印", "宿", "白", "怀", "蒲", "邰", "从", "鄂", "索", "咸", "籍", "赖", "卓", "蔺", "屠", "蒙", "池", "乔", "阴", "欎", "胥", "能", "苍", "双", "闻", "莘", "党", "翟", "谭", "贡", "劳", "逄", "姬", "申", "扶", "堵", "冉", "宰", "郦", "雍", "舄", "璩", "桑", "桂", "濮", "牛", "寿", "通", "边", "扈", "燕", "冀", "郏", "浦", "尚", "农", "温", "别", "庄", "晏", "柴", "瞿", "阎", "充", "慕", "连", "茹", "习", "宦", "艾", "鱼", "容", "向", "古", "易", "慎", "戈", "廖", "庾", "终", "暨", "居", "衡", "步", "都", "耿", "满", "弘", "匡", "国", "文", "寇", "广", "禄", "阙", "东", "殴", "殳", "沃", "利", "蔚", "越", "夔", "隆", "师", "巩", "厍", "聂", "晁", "勾", "敖", "融", "冷", "訾", "辛", "阚", "那", "简", "饶", "空", "曾", "毋", "沙", "乜", "养", "鞠", "须", "丰", "巢", "关", "蒯", "相", "查", "後", "荆", "红", "游", "竺", "权", "逯", "盖", "益", "桓", "公", "万俟", "司马", "上官", "欧阳", "夏侯", "诸葛", "闻人", "东方", "赫连", "皇甫", "尉迟", "公羊", "澹台", "公冶", "宗政", "濮阳", "淳于", "单于", "太叔", "申屠", "公孙", "仲孙", "轩辕", "令狐", "钟离", "宇文", "长孙", "慕容", "鲜于", "闾丘", "司徒", "司空", "亓官", "司寇", "仉", "督", "子车", "颛孙", "端木", "巫马", "公西", "漆雕", "乐正", "壤驷", "公良", "拓跋", "夹谷", "宰父", "谷梁", "晋", "楚", "闫", "法", "汝", "鄢", "涂", "钦", "段干", "百里", "东郭", "南门", "呼延", "归", "海", "羊舌", "微生", "岳", "帅", "缑", "亢", "况", "后", "有", "琴", "梁丘", "左丘", "东门", "西门", "商", "牟", "佘", "佴", "伯", "赏", "南宫", "墨", "哈", "谯", "笪", "年", "爱", "阳", "佟", "第五", "言", "福", "百", "家", "姓", "终"};
|
||||
string[] 名字前缀 = { "金", "猪", "兔", "猫", "鱼", "蛋", "胖", "大", "傻", "酷", "蠢", "聪", "萌", "暴","靖", "铭", "琛", "川", "承", "司", "斯", "宗", "骁", "聪", "在", "钩", "锦", "铎", "楚", "铮", "钦", "则", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "清", "澈", "泫", "浚", "润", "泽", "向", "凡", "文", "浦", "洲", "珩", "玄", "洋", "淮", "雨", "子", "云", "卓", "昱", "南", "晨", "知", "宁", "年", "易", "晗", "炎", "焕", "哲", "煦", "旭", "明", "阳", "朗", "典", "辰", "宸", "野", "安", "为", "亦", "岚", "也", "围", "以", "延", "允", "容", "恩", "衡", "宇", "硕", "已", "意", "也", "坤", "辰", "伊", "米", "安", "恩", "以", "容", "宛", "岚", "又", "衣", "亚", "悠", "允", "画", "灿", "夏", "珞", "煊", "晴", "彤", "诺", "宁", "恬", "钧", "灵", "昭", "琉", "晨", "曦", "南", "毓", "冉", "妍", "澜", "淇", "沐", "潆", "盈", "雨", "文", "冰", "雯", "溪", "子", "云", "汐", "潞", "淇", "妙", "涵", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "真", "心", "新", "悦", "西", "兮", "楚", "初", "千", "锐", "素", "锦", "静", "镜", "斯", "舒", "瑜", "童" };
|
||||
string[] 名字后缀 = { "子", "郎", "妹", "宝", "儿", "汉", "君", "爷", "娃", "猪", "鬼", "鸟", "仔", "蛋","靖", "铭", "琛", "川", "承", "司", "斯", "宗", "骁", "聪", "在", "钩", "锦", "铎", "楚", "铮", "钦", "则", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "清", "澈", "泫", "浚", "润", "泽", "向", "凡", "文", "浦", "洲", "珩", "玄", "洋", "淮", "雨", "子", "云", "卓", "昱", "南", "晨", "知", "宁", "年", "易", "晗", "炎", "焕", "哲", "煦", "旭", "明", "阳", "朗", "典", "辰", "宸", "野", "安", "为", "亦", "岚", "也", "围", "以", "延", "允", "容", "恩", "衡", "宇", "硕", "已", "意", "也", "坤", "辰", "伊", "米", "安", "恩", "以", "容", "宛", "岚", "又", "衣", "亚", "悠", "允", "画", "灿", "夏", "珞", "煊", "晴", "彤", "诺", "宁", "恬", "钧", "灵", "昭", "琉", "晨", "曦", "南", "毓", "冉", "妍", "澜", "淇", "沐", "潆", "盈", "雨", "文", "冰", "雯", "溪", "子", "云", "汐", "潞", "淇", "妙", "涵", "楠", "景", "茗", "聿", "启", "尧", "言", "嘉", "桉", "桐", "筒", "竹", "林", "乔", "栋", "家", "翊", "松", "真", "心", "新", "悦", "西", "兮", "楚", "初", "千", "锐", "素", "锦", "静", "镜", "斯", "舒", "瑜", "童" };
|
||||
|
||||
|
||||
@ -1,26 +1,32 @@
|
||||
using Godot;
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 测试地图层
|
||||
/// </summary>
|
||||
public partial class TestMap : TileMapLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// The last cell that was highlighted. Used to avoid unnecessary updates.
|
||||
/// 上一次高亮的单元格。用于避免不必要的更新。
|
||||
/// </summary>
|
||||
private Vector2I _lastHighlightCell;
|
||||
private readonly Vector2I _highlightTileCoord = new(0, 5);
|
||||
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()
|
||||
{
|
||||
// Initialize the lastHighlightCell to an invalid value.
|
||||
// 初始化最后高亮单元格为无效值。
|
||||
_lastHighlightCell = _vector2INegOne;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every frame.
|
||||
/// 每帧更新。
|
||||
/// </summary>
|
||||
/// <param name="delta">the elapsed time since the previous frame.</param>
|
||||
/// <param name="delta">距离上一帧的时间间隔。</param>
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
Vector2 mousePos = GetLocalMousePosition();
|
||||
@ -36,5 +42,4 @@ public partial class TestMap : TileMapLayer
|
||||
SetCell(cell, 0, _highlightTileCoord);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -3,23 +3,39 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// 图块映射辅助类
|
||||
/// </summary>
|
||||
public partial class TileMapping : Node
|
||||
{
|
||||
/// <summary>
|
||||
/// From tile coordinates to pixel coordinates. If null, use the offset instead.
|
||||
/// 从图块坐标到像素坐标的映射。如果不为空,则使用此映射。
|
||||
/// </summary>
|
||||
private Dictionary<Vector2I, Vector2I> _pixelMap;
|
||||
/// <summary>
|
||||
/// If pixelMap is null, use this offset to convert tile coordinates to pixel coordinates.
|
||||
/// 如果 pixelMap 为空,使用此偏移量将图块坐标转换为像素坐标。
|
||||
/// </summary>
|
||||
private Vector2I _pixelOffset;
|
||||
|
||||
/// <summary>
|
||||
/// 图块区域
|
||||
/// </summary>
|
||||
private Rect2I _tileRect;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,使用坐标映射字典
|
||||
/// </summary>
|
||||
/// <param name="map">坐标映射字典</param>
|
||||
public TileMapping(Dictionary<Vector2I, Vector2I> map) {
|
||||
_pixelMap = map;
|
||||
_pixelOffset = new Vector2I(-int.MaxValue, -int.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数,使用偏移量和区域
|
||||
/// </summary>
|
||||
/// <param name="offset">偏移量</param>
|
||||
/// <param name="rect">区域</param>
|
||||
public TileMapping(Vector2I offset, Rect2I rect) {
|
||||
_pixelMap = null;
|
||||
_pixelOffset = offset;
|
||||
@ -28,6 +44,10 @@ public partial class TileMapping : Node
|
||||
|
||||
public TileMapping() {}
|
||||
|
||||
/// <summary>
|
||||
/// 应用映射到TileMapLayer
|
||||
/// </summary>
|
||||
/// <param name="layer">TileMapLayer</param>
|
||||
public void Apply(TileMapLayer layer) {
|
||||
// if pixelMap is not null, use all the pairs in pixelMap to set the tiles.
|
||||
if (_pixelMap != null) {
|
||||
@ -46,4 +66,4 @@ public partial class TileMapping : Node
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,14 +6,19 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<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="README.md" />
|
||||
<Content Include="resources\definitions\archetypes.json" />
|
||||
<Content Include="resources\definitions\campus_behavior.json" />
|
||||
<Content Include="resources\definitions\disciplines.json" />
|
||||
<Content Include="resources\definitions\discipline_biology.tres" />
|
||||
<Content Include="resources\definitions\roles.json" />
|
||||
<Content Include="resources\definitions\traits.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="addons\" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user