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 @@ + + +