supervisor-simulator/scripts/Campus/CampusBehaviorConfig.cs

234 lines
8.0 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, // 咖啡店
AdminBuilding, // 行政楼
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 {
private readonly Dictionary<CampusActionId, CampusActionConfig> _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<CampusActionConfig> 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<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 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);
}
}
/// <summary>
/// 自定义需求,尚未成为核心 UnitModel 的一部分(饥饿/社交/精力)。
/// 使用 PropertyValue 以便接入现有的数值系统。
/// </summary>
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; }
}