using System; using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using Godot; using Models; /// /// 校园行为系统使用的位置标识符。 /// 这些标识符映射到 campus.tscn 中的 Node2D 标记点,以便 AI 可以通过名称选择目标。 /// public enum CampusLocationId { None, Laboratory, // 实验室 Library, // 图书馆 Canteen, // 食堂 Dormitory, // 宿舍 ArtificialLake, // 人工湖 CoffeeShop, // 咖啡店 AdministrationBuilding, // 行政楼 FootballField, // 足球场 RandomWander // 随机漫游 } /// /// 行为规划器和状态机使用的动作标识符。 /// 每个动作通过 campus_behavior.json 配置持续时间和属性变化。 /// public enum CampusActionId { None, Experimenting, // 做实验 Writing, // 写作 Eating, // 吃饭 Sleeping, // 睡觉 Chilling, // 放松/闲逛 Staring, // 发呆 CoffeeBreak, // 喝咖啡 Administration, // 行政工作 Running, // 跑步 Socializing, // 社交 Wandering // 漫步 } /// /// 优先级级别,对应设计文档中的顺序:值越小优先级越高。 /// public enum CampusBehaviorPriority { Critical = 0, // 紧急状态(崩溃/力竭) AssignedTask = 1, // 指派任务 Needs = 2, // 基础需求(饿/累/社交) Trait = 3, // 特质驱动(性格偏好) Idle = 4 // 闲置 } /// /// 校园演示的最小化任务类型。这些不是完整的游戏任务, /// 仅用于驱动 AI 的“指派任务”优先级。 /// public enum CampusTaskType { Experiment, // 实验 Writing, // 写作 Administration, // 行政 Exercise, // 锻炼 Coding, // 编程 Social // 社交 } /// /// 从 JSON 加载的动作配置。 /// 变化量(Delta)在动作运行时按秒应用,因此动作越长积累的效果越多。 /// 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; } } /// /// 校园 AI 的全局行为配置。 /// 这是数据驱动的,以便在不修改代码的情况下通过 JSON 进行平衡调整。 /// 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 ActionConfigs { get; set; } = new(); private readonly Dictionary _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(json, options); return config ?? new CampusBehaviorConfig(); } catch (Exception ex) { GD.PushWarning($"Failed to parse campus behavior config: {ex.Message}"); return new CampusBehaviorConfig(); } } } /// /// 简单的位置注册表,将逻辑位置 ID 映射到场景位置。 /// 保持行为系统独立于场景树细节。 /// public sealed class CampusLocationRegistry { private readonly Dictionary _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); } } /// /// 跟踪每个位置的当前占用情况,以便像社交恐惧症这样的特质可以根据人群规模做出反应, /// 而无需硬编码场景知识。 /// public sealed class CampusBehaviorWorld { private readonly Dictionary _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; } } /// /// 校园演示的轻量级任务容器;它只跟踪剩余工作量。 /// 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; } /// /// 自定义需求,尚未成为核心 UnitModel 的一部分(饥饿/社交/精力)。 /// 使用 PropertyValue 以便接入现有的数值系统。 /// 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); } }