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, // 咖啡店
AdminBuilding, // 行政楼
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 {
private readonly Dictionary _actionLookup = new();
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();
public CampusActionConfig GetActionConfig(CampusActionId id) {
if (_actionLookup.Count == 0) BuildLookup();
return _actionLookup.GetValueOrDefault(id);
}
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 CampusTask(CampusTaskType type, float remainingSeconds) {
Type = type;
RemainingSeconds = Mathf.Max(0f, remainingSeconds);
}
public CampusTaskType Type { get; }
public float RemainingSeconds { get; private set; }
public bool IsComplete => RemainingSeconds <= 0f;
public void Advance(float delta) {
RemainingSeconds = Mathf.Max(0f, RemainingSeconds - delta);
}
}
///
/// 自定义需求,尚未成为核心 UnitModel 的一部分(饥饿/社交/精力)。
/// 使用 PropertyValue 以便接入现有的数值系统。
///
public sealed class CampusAgentNeeds {
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);
}
public PropertyValue Hunger { get; }
public PropertyValue Energy { get; }
public PropertyValue Social { get; }
public PropertyValue Health { get; }
}