234 lines
8.1 KiB
C#
234 lines
8.1 KiB
C#
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, // 咖啡店
|
||
AdministrationBuilding, // 行政楼
|
||
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.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<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; }
|
||
} |