diff --git a/resources/definitions/archetypes.json b/resources/definitions/archetypes.json
new file mode 100644
index 0000000..52d0573
--- /dev/null
+++ b/resources/definitions/archetypes.json
@@ -0,0 +1,70 @@
+[
+ {
+ "Header": {
+ "Id": "core:archetype_grinder",
+ "Name": {
+ "Key": "archetype.grinder.name",
+ "Fallback": "Grinder"
+ },
+ "Description": {
+ "Key": "archetype.grinder.desc",
+ "Fallback": "They live in the lab and push everything faster."
+ },
+ "Tags": [ "archetype" ]
+ },
+ "Tiers": [
+ {
+ "RequiredCount": 2,
+ "Modifiers": {
+ "AttributeModifiers": [
+ { "Type": "Activation", "Add": 5, "Multiplier": 1.1 }
+ ],
+ "RuleIds": [ "rule:grinder_stress_up" ]
+ }
+ },
+ {
+ "RequiredCount": 4,
+ "Modifiers": {
+ "AttributeModifiers": [
+ { "Type": "Activation", "Add": 10, "Multiplier": 1.2 }
+ ],
+ "RuleIds": [ "rule:grinder_overwork" ]
+ }
+ }
+ ]
+ },
+ {
+ "Header": {
+ "Id": "core:archetype_slacker",
+ "Name": {
+ "Key": "archetype.slacker.name",
+ "Fallback": "Slacker"
+ },
+ "Description": {
+ "Key": "archetype.slacker.desc",
+ "Fallback": "They recover morale but slow down the team."
+ },
+ "Tags": [ "archetype" ]
+ },
+ "Tiers": [
+ {
+ "RequiredCount": 2,
+ "Modifiers": {
+ "StatusModifiers": [
+ { "Type": "Mood", "Add": 5, "Multiplier": 1.05 }
+ ],
+ "RuleIds": [ "rule:slacker_relax" ]
+ }
+ },
+ {
+ "RequiredCount": 4,
+ "Modifiers": {
+ "StatusModifiers": [
+ { "Type": "Stress", "Add": -5, "Multiplier": 0.9 }
+ ],
+ "RuleIds": [ "rule:slacker_spread" ]
+ }
+ }
+ ]
+ }
+]
diff --git a/resources/definitions/discipline_biology.tres b/resources/definitions/discipline_biology.tres
new file mode 100644
index 0000000..34e5133
--- /dev/null
+++ b/resources/definitions/discipline_biology.tres
@@ -0,0 +1,21 @@
+[gd_resource type="Resource" script_class="DisciplineDefinitionResource" load_steps=2 format=3]
+
+[ext_resource type="Script" path="res://scripts/Core/DisciplineDefinitionResource.cs" id=1]
+
+[resource]
+script = ExtResource("1")
+Id = "core:discipline_biology_tres"
+NameKey = "discipline.biology.name"
+NameFallback = "Biology"
+DescriptionKey = "discipline.biology.desc"
+DescriptionFallback = "Lab-intensive discipline focused on experiments."
+IconPath = ""
+Tags = [ "discipline" ]
+BuffNameKey = "buff.pipette_master.name"
+BuffNameFallback = "Pipette Master"
+BuffDescriptionKey = "buff.pipette_master.desc"
+BuffDescriptionFallback = "Higher lab success, higher stamina cost."
+BuffRuleIds = [ "rule:discipline_biology_pipette_master" ]
+RolePoolIds = [ "core:role_alchemist", "core:role_lab_rat" ]
+ItemPoolIds = [ "core:item_pipette" ]
+TaskKeywordIds = [ "task_keyword_lab" ]
diff --git a/resources/definitions/disciplines.json b/resources/definitions/disciplines.json
new file mode 100644
index 0000000..f28dda7
--- /dev/null
+++ b/resources/definitions/disciplines.json
@@ -0,0 +1,68 @@
+[
+ {
+ "Header": {
+ "Id": "core:discipline_economics",
+ "Name": {
+ "Key": "discipline.economics.name",
+ "Fallback": "Economics"
+ },
+ "Description": {
+ "Key": "discipline.economics.desc",
+ "Fallback": "Money drives everything; interest becomes a core loop."
+ },
+ "Tags": [ "discipline" ]
+ },
+ "Buff": {
+ "Name": {
+ "Key": "buff.capital_flow.name",
+ "Fallback": "Capital Flow"
+ },
+ "Description": {
+ "Key": "buff.capital_flow.desc",
+ "Fallback": "Idle funds generate interest each turn."
+ },
+ "Modifiers": {
+ "ResourceModifiers": [
+ { "Type": "Money", "Add": 0, "Multiplier": 1.0 }
+ ],
+ "RuleIds": [ "rule:discipline_economics_interest" ]
+ }
+ },
+ "RolePoolIds": [ "core:role_surveyor", "core:role_orator" ],
+ "ItemPoolIds": [ "core:item_bloomberg_terminal" ],
+ "TaskKeywordIds": [ "task_keyword_finance" ]
+ },
+ {
+ "Header": {
+ "Id": "core:discipline_computer",
+ "Name": {
+ "Key": "discipline.computer.name",
+ "Fallback": "Computer Science"
+ },
+ "Description": {
+ "Key": "discipline.computer.desc",
+ "Fallback": "Compute-heavy discipline with strong tech output."
+ },
+ "Tags": [ "discipline" ]
+ },
+ "Buff": {
+ "Name": {
+ "Key": "buff.overclock.name",
+ "Fallback": "Overclock"
+ },
+ "Description": {
+ "Key": "buff.overclock.desc",
+ "Fallback": "Server power is amplified for AI tasks."
+ },
+ "Modifiers": {
+ "AttributeModifiers": [
+ { "Type": "Engineering", "Add": 5, "Multiplier": 1.05 }
+ ],
+ "RuleIds": [ "rule:discipline_computer_overclock" ]
+ }
+ },
+ "RolePoolIds": [ "core:role_geek", "core:role_coder" ],
+ "ItemPoolIds": [ "core:item_rtx_cluster" ],
+ "TaskKeywordIds": [ "task_keyword_ai" ]
+ }
+]
diff --git a/scripts/Core/ContentCollectionResource.cs b/scripts/Core/ContentCollectionResource.cs
new file mode 100644
index 0000000..f9624ff
--- /dev/null
+++ b/scripts/Core/ContentCollectionResource.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using Godot;
+using Godot.Collections;
+
+namespace Core;
+
+///
+/// 资源集合(便于打包多个定义到一个 .tres)
+///
+[GlobalClass]
+public partial class ContentCollectionResource : Resource, IContentResourceCollection
+{
+ [Export] public Array Items { get; set; } = new();
+
+ public IEnumerable GetItems()
+ {
+ foreach (var item in Items)
+ {
+ if (item is IContentResource content)
+ {
+ yield return content;
+ }
+ }
+ }
+}
+
diff --git a/scripts/Core/ContentRegistry.cs b/scripts/Core/ContentRegistry.cs
index c51d215..2144d80 100644
--- a/scripts/Core/ContentRegistry.cs
+++ b/scripts/Core/ContentRegistry.cs
@@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Godot;
using Models;
namespace Core;
@@ -103,7 +107,54 @@ public sealed class ResourceContentSource : IContentSource
public IEnumerable LoadAll() where T : class
{
- yield break;
+ foreach (var path in ResourcePaths)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ continue;
+ }
+
+ var resource = ResourceLoader.Load(path);
+ if (resource == null)
+ {
+ continue;
+ }
+
+ foreach (var item in ExtractResources(resource))
+ {
+ yield return item;
+ }
+ }
+ }
+
+ private IEnumerable ExtractResources(Resource resource) where T : class
+ {
+ if (resource is IContentResource content)
+ {
+ if (content.GetDefinitionType() == typeof(T))
+ {
+ if (content.ToDefinition() is T typed)
+ {
+ yield return typed;
+ }
+ }
+
+ yield break;
+ }
+
+ if (resource is IContentResourceCollection collection)
+ {
+ foreach (var item in collection.GetItems())
+ {
+ if (item.GetDefinitionType() == typeof(T))
+ {
+ if (item.ToDefinition() is T typed)
+ {
+ yield return typed;
+ }
+ }
+ }
+ }
}
}
@@ -114,15 +165,135 @@ public sealed class JsonContentSource : IContentSource
{
public int Priority { get; }
public List DataPaths { get; } = new();
+ private readonly JsonSerializerOptions _options;
public JsonContentSource(int priority)
{
Priority = priority;
+ _options = new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true
+ };
+ _options.Converters.Add(new JsonStringEnumConverter());
}
public IEnumerable LoadAll() where T : class
{
- yield break;
+ foreach (var path in DataPaths)
+ {
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ continue;
+ }
+
+ var resolvedPath = ResolvePath(path);
+ if (!File.Exists(resolvedPath))
+ {
+ continue;
+ }
+
+ var json = File.ReadAllText(resolvedPath);
+ if (string.IsNullOrWhiteSpace(json))
+ {
+ continue;
+ }
+
+ if (TryDeserializeList(json, out List list))
+ {
+ foreach (var item in list)
+ {
+ yield return item;
+ }
+
+ continue;
+ }
+
+ if (TryDeserializeEnvelope(json, out JsonContentEnvelope envelope))
+ {
+ if (envelope.Items != null)
+ {
+ foreach (var item in envelope.Items)
+ {
+ yield return item;
+ }
+ }
+ else if (envelope.Item != null)
+ {
+ yield return envelope.Item;
+ }
+
+ continue;
+ }
+
+ if (TryDeserializeSingle(json, out T single))
+ {
+ yield return single;
+ }
+ }
+ }
+
+ private string ResolvePath(string path)
+ {
+ if (path.StartsWith("res://") || path.StartsWith("user://"))
+ {
+ return ProjectSettings.GlobalizePath(path);
+ }
+
+ return path;
+ }
+
+ private bool TryDeserializeList(string json, out List list) where T : class
+ {
+ try
+ {
+ list = JsonSerializer.Deserialize>(json, _options);
+ return list != null;
+ }
+ catch
+ {
+ list = null;
+ return false;
+ }
+ }
+
+ private bool TryDeserializeSingle(string json, out T item) where T : class
+ {
+ try
+ {
+ item = JsonSerializer.Deserialize(json, _options);
+ return item != null;
+ }
+ catch
+ {
+ item = null;
+ return false;
+ }
+ }
+
+ private bool TryDeserializeEnvelope(string json, out JsonContentEnvelope envelope) where T : class
+ {
+ try
+ {
+ envelope = JsonSerializer.Deserialize>(json, _options);
+ if (envelope == null)
+ {
+ return false;
+ }
+
+ return envelope.Items != null || envelope.Item != null;
+ }
+ catch
+ {
+ envelope = null;
+ return false;
+ }
+ }
+
+ private sealed class JsonContentEnvelope where T : class
+ {
+ public string Type { get; set; }
+ public List Items { get; set; }
+ public T Item { get; set; }
}
}
diff --git a/scripts/Core/ContentResources.cs b/scripts/Core/ContentResources.cs
new file mode 100644
index 0000000..d711ddf
--- /dev/null
+++ b/scripts/Core/ContentResources.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+
+namespace Core;
+
+///
+/// 资源定义接口(用于 .tres/.res 的内容加载)
+/// 设计说明:
+/// 1) 资源负责“序列化友好”,模型负责“运行时友好”。
+/// 2) 通过 ToDefinition 映射到纯数据模型,保持解耦。
+/// 注意事项:
+/// - 资源字段尽量使用 Godot 可序列化的基础类型与 Array。
+/// 未来扩展:
+/// - 可加入验证器,确保 Id/路径等关键字段符合规范。
+///
+public interface IContentResource
+{
+ Type GetDefinitionType();
+ object ToDefinition();
+}
+
+public interface IContentResourceCollection
+{
+ IEnumerable GetItems();
+}
+
+
diff --git a/scripts/Core/DisciplineDefinitionResource.cs b/scripts/Core/DisciplineDefinitionResource.cs
new file mode 100644
index 0000000..bf78b7c
--- /dev/null
+++ b/scripts/Core/DisciplineDefinitionResource.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using Godot;
+using Godot.Collections;
+using Models;
+
+namespace Core;
+
+///
+/// 学科定义资源(先落地一个完整示例)
+/// 设计说明:
+/// 1) 为避免 .tres 过度嵌套,字段保持扁平化。
+/// 2) Buff 规则通过 RuleIds 存储,数值效果可后续用 JSON 增补。
+/// 注意事项:
+/// - Id 与资源路径应稳定且小写下划线。
+/// 未来扩展:
+/// - 可补充 Modifiers/AllowedRoles 等字段,进一步丰富配置能力。
+///
+[GlobalClass]
+public partial class DisciplineDefinitionResource : Resource, IContentResource
+{
+ // --- Header ---
+ [Export] public string Id { get; set; }
+ [Export] public string NameKey { get; set; }
+ [Export] public string NameFallback { get; set; }
+ [Export] public string DescriptionKey { get; set; }
+ [Export] public string DescriptionFallback { get; set; }
+ [Export] public string IconPath { get; set; }
+ [Export] public Array Tags { get; set; } = new();
+
+ // --- Buff ---
+ [Export] public string BuffNameKey { get; set; }
+ [Export] public string BuffNameFallback { get; set; }
+ [Export] public string BuffDescriptionKey { get; set; }
+ [Export] public string BuffDescriptionFallback { get; set; }
+ [Export] public Array BuffRuleIds { get; set; } = new();
+
+ // --- Pools ---
+ [Export] public Array RolePoolIds { get; set; } = new();
+ [Export] public Array ItemPoolIds { get; set; } = new();
+ [Export] public Array TaskKeywordIds { get; set; } = new();
+
+ public Type GetDefinitionType() => typeof(DisciplineDefinition);
+
+ public object ToDefinition()
+ {
+ var header = new DefinitionHeader
+ {
+ Id = Id,
+ IconPath = IconPath,
+ Name = new LocalizedText
+ {
+ Key = NameKey,
+ Fallback = NameFallback
+ },
+ Description = new LocalizedText
+ {
+ Key = DescriptionKey,
+ Fallback = DescriptionFallback
+ }
+ };
+
+ foreach (var tag in Tags)
+ {
+ header.Tags.Add(tag);
+ }
+
+ var buff = new DisciplineBuff
+ {
+ Name = new LocalizedText
+ {
+ Key = BuffNameKey,
+ Fallback = BuffNameFallback
+ },
+ Description = new LocalizedText
+ {
+ Key = BuffDescriptionKey,
+ Fallback = BuffDescriptionFallback
+ },
+ Modifiers = new ModifierBundle()
+ };
+
+ foreach (var ruleId in BuffRuleIds)
+ {
+ buff.Modifiers.RuleIds.Add(ruleId);
+ }
+
+ var definition = new DisciplineDefinition
+ {
+ Header = header,
+ Buff = buff
+ };
+
+ AddRange(RolePoolIds, definition.RolePoolIds);
+ AddRange(ItemPoolIds, definition.ItemPoolIds);
+ AddRange(TaskKeywordIds, definition.TaskKeywordIds);
+
+ return definition;
+ }
+
+ private static void AddRange(Array source, List target)
+ {
+ foreach (var value in source)
+ {
+ target.Add(value);
+ }
+ }
+}
+
diff --git a/scripts/Core/GameSession.cs b/scripts/Core/GameSession.cs
index d668f6c..9840d00 100644
--- a/scripts/Core/GameSession.cs
+++ b/scripts/Core/GameSession.cs
@@ -33,6 +33,15 @@ public sealed class GameSession
public static GameSession CreateDefault()
{
var registry = new ContentRegistry();
+ var resourceSource = new ResourceContentSource(0);
+ resourceSource.ResourcePaths.Add("res://resources/definitions/discipline_biology.tres");
+ registry.RegisterSource(resourceSource);
+
+ var jsonSource = new JsonContentSource(10);
+ jsonSource.DataPaths.Add("res://resources/definitions/disciplines.json");
+ jsonSource.DataPaths.Add("res://resources/definitions/archetypes.json");
+ registry.RegisterSource(jsonSource);
+
var content = registry.BuildDatabase();
var localization = new GodotLocalizationService();
var events = new DomainEventBus();
diff --git a/最强导师.csproj b/最强导师.csproj
index 3e43820..00ed44d 100644
--- a/最强导师.csproj
+++ b/最强导师.csproj
@@ -11,6 +11,9 @@
+
+
+