supervisor-simulator/scripts/Core/GameSystems.cs
wjsjwr 9c1593e717 已把 TaskSystem/SynergySystem/EconomySystem 按设计文档的“核心规则”落地成可运行逻辑,并把回合结算从 GameManager 下沉到系统与事件流上。
- 任务推进与结算:执行阶段按任务类型用属性权重计算进度、考虑角色/学科匹配、状态衰减与难度缩放,回合结束统一扣 Deadline、判定完成/失败并派发事件,同时记录署名贡献度 scripts/Core/GameSystems.cs
  - 经济结算:接收任务事件发奖惩,学术探索根据难度生成论文卡;回合结束统一扣工资并结息(经济学学科触发基础利率)scripts/Core/GameSystems.cs
  - 羁绊层数与效果:统计人群画像/职能分工堆叠,激活层级并聚合为全局 ModifierBundle 给数值解析器使用 scripts/Core/GameSystems.cs, scripts/Models/GameState.cs
  - 事件与数值解析支撑:新增领域事件与统一属性合成器,兼容羁绊/学科/特质/装备的叠加 scripts/Core/DomainEvents.cs, scripts/Core/StatResolver.cs
  - 回合结算入口调整:Review 阶段改由系统处理并发布回合结束事件 scripts/GameManager.cs
2026-01-01 00:07:16 +08:00

622 lines
15 KiB
C#
Raw 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 Models;
namespace Core;
/// <summary>
/// 系统层骨架(负责模型变化,不直接处理输入/显示)
/// 设计说明:
/// 1) 每个系统只关注单一职责,形成高内聚低耦合结构。
/// 2) 系统之间通过 GameSession 共享状态,通过事件总线通讯。
/// 注意事项:
/// - 系统逻辑应尽量“幂等”,便于回合重算与调试。
/// 未来扩展:
/// - 可加入“系统执行顺序配置”,支持 Mod 插入新系统。
/// </summary>
public interface IGameSystem
{
void Initialize(GameSession session);
void Tick(float delta);
}
public sealed class GameSystems
{
public TurnSystem Turn { get; } = new();
public TaskSystem Task { get; } = new();
public EconomySystem Economy { get; } = new();
public SynergySystem Synergy { get; } = new();
public AssignmentSystem Assignment { get; } = new();
public void Initialize(GameSession session)
{
Turn.Initialize(session);
Task.Initialize(session);
Economy.Initialize(session);
Synergy.Initialize(session);
Assignment.Initialize(session);
}
public void Tick(float delta)
{
Turn.Tick(delta);
Synergy.Tick(delta);
Task.Tick(delta);
Economy.Tick(delta);
Assignment.Tick(delta);
}
}
public sealed class TurnSystem : IGameSystem
{
private GameSession _session;
public void Initialize(GameSession session)
{
_session = session;
}
public void Tick(float delta)
{
// 预留:回合推进计时器/阶段切换
}
}
public sealed class TaskSystem : IGameSystem
{
private GameSession _session;
private StatResolver _statResolver;
public void Initialize(GameSession session)
{
_session = session;
_statResolver = new StatResolver(session);
}
public void Tick(float delta)
{
if (_session.State.Turn.Phase != GamePhase.Execution)
{
return;
}
AdvanceTasks(delta);
}
/// <summary>
/// 回合结算(扣除 Deadline判定完成与失败
/// </summary>
public void ResolveEndOfTurn()
{
var state = _session.State;
var completed = new List<TaskModel>();
var failed = new List<TaskModel>();
foreach (var task in state.Tasks.ActiveTasks)
{
if (task.Runtime.RemainingTurns > 0)
{
task.Runtime.RemainingTurns--;
}
if (task.IsCompleted)
{
completed.Add(task);
}
else if (task.IsFailed)
{
failed.Add(task);
}
}
foreach (var task in completed)
{
state.Tasks.ActiveTasks.Remove(task);
state.Tasks.CompletedTasks.Add(task);
_session.Events.Publish(new TaskCompletedEvent(task));
}
foreach (var task in failed)
{
state.Tasks.ActiveTasks.Remove(task);
state.Tasks.FailedTasks.Add(task);
_session.Events.Publish(new TaskFailedEvent(task));
}
}
private void AdvanceTasks(float delta)
{
var state = _session.State;
if (state.Tasks.ActiveTasks.Count == 0) return;
var unitIndex = BuildUnitIndex();
foreach (var task in state.Tasks.ActiveTasks)
{
if (task.IsCompleted || task.IsFailed) continue;
if (task.Kind == TaskKind.Milestone) continue;
var taskDef = GetTaskDefinition(task);
var totalPower = 0f;
foreach (var unitId in task.AssignedUnitIds)
{
if (!unitIndex.TryGetValue(unitId, out var entry))
{
continue;
}
var contribution = GetUnitContribution(entry, task, taskDef, delta);
totalPower += contribution;
}
if (totalPower <= 0f) continue;
var difficultyScale = task.Runtime.DifficultyScale * GetDifficultyScale(task.Difficulty);
task.AddProgress(totalPower * delta / difficultyScale);
}
}
private float GetUnitContribution(UnitEntry entry, TaskModel task, TaskDefinition taskDef, float delta)
{
var unit = entry.Unit;
var basePower = GetTaskBasePower(unit, task.Kind);
if (basePower <= 0f) return 0f;
var roleMultiplier = GetRoleMultiplier(unit, taskDef);
var disciplineMultiplier = GetDisciplineMultiplier(unit, taskDef);
var statusMultiplier = GetStatusMultiplier(entry);
var requirementMultiplier = GetRequirementMultiplier(unit, taskDef);
var effectivePower = basePower * roleMultiplier * disciplineMultiplier * statusMultiplier * requirementMultiplier;
TrackContribution(entry, task, effectivePower * delta);
return effectivePower;
}
private float GetTaskBasePower(UnitModel unit, TaskKind kind)
{
var academic = _statResolver.GetAttribute(unit, AttributeType.Academic);
var engineering = _statResolver.GetAttribute(unit, AttributeType.Engineering);
var writing = _statResolver.GetAttribute(unit, AttributeType.Writing);
var financial = _statResolver.GetAttribute(unit, AttributeType.Financial);
var social = _statResolver.GetAttribute(unit, AttributeType.Social);
var activation = _statResolver.GetAttribute(unit, AttributeType.Activation);
return kind switch
{
TaskKind.AcademicExploration => academic * 0.6f + writing * 0.4f,
TaskKind.GrantVertical => writing * 0.4f + social * 0.4f + financial * 0.2f,
TaskKind.GrantHorizontal => engineering * 0.5f + activation * 0.3f + academic * 0.2f,
TaskKind.Administrative => activation * 0.6f + social * 0.4f,
TaskKind.Conference => social * 0.5f + writing * 0.3f + financial * 0.2f,
TaskKind.Milestone => 0f,
_ => academic
};
}
private float GetStatusMultiplier(UnitEntry entry)
{
var mood = entry.Unit.Statuses.Mood.Normalized;
var stress = entry.Unit.Statuses.Stress.Current.Normalized;
var moodMultiplier = 0.5f + mood * 0.5f;
var stressMultiplier = Math.Clamp(1.0f - 0.3f * stress, 0.7f, 1.0f);
var multiplier = moodMultiplier * stressMultiplier;
if (entry.Student != null)
{
var stamina = entry.Student.Progress.Stamina.Current.Normalized;
if (stamina < 0.1f) multiplier *= 0.4f;
else if (stamina < 0.2f) multiplier *= 0.7f;
}
return Math.Clamp(multiplier, 0.3f, 1.2f);
}
private float GetRoleMultiplier(UnitModel unit, TaskDefinition taskDef)
{
if (taskDef == null) return 1.0f;
if (taskDef.Requirements.RequiredRoleIds.Count > 0)
{
foreach (var roleId in taskDef.Requirements.RequiredRoleIds)
{
if (unit.Tags.RoleIds.Contains(roleId))
{
return 1.0f;
}
}
return 0.5f;
}
if (taskDef.RecommendedRoleIds.Count > 0)
{
foreach (var roleId in taskDef.RecommendedRoleIds)
{
if (unit.Tags.RoleIds.Contains(roleId))
{
return 1.1f;
}
}
return 0.9f;
}
return 1.0f;
}
private float GetRequirementMultiplier(UnitModel unit, TaskDefinition taskDef)
{
if (taskDef == null) return 1.0f;
var multiplier = 1.0f;
foreach (var requirement in taskDef.Requirements.AttributeChecks)
{
var value = _statResolver.GetAttribute(unit, requirement.Type);
if (value < requirement.MinValue)
{
multiplier *= 0.85f;
}
}
return multiplier;
}
private float GetDisciplineMultiplier(UnitModel unit, TaskDefinition taskDef)
{
if (taskDef == null) return 1.0f;
if (taskDef.AllowedDisciplineIds.Count == 0) return 1.0f;
if (string.IsNullOrWhiteSpace(unit.Tags.DisciplineId)) return 0.8f;
foreach (var disciplineId in taskDef.AllowedDisciplineIds)
{
if (disciplineId == unit.Tags.DisciplineId)
{
return 1.0f;
}
}
return 0.7f;
}
private float GetDifficultyScale(TaskDifficulty difficulty)
{
return difficulty switch
{
TaskDifficulty.Water => 0.8f,
TaskDifficulty.Standard => 1.0f,
TaskDifficulty.Hardcore => 1.3f,
TaskDifficulty.BlackBox => 1.6f,
_ => 1.0f
};
}
private TaskDefinition GetTaskDefinition(TaskModel task)
{
if (string.IsNullOrWhiteSpace(task.DefinitionId)) return null;
return _session.Content.Tasks.TryGetValue(task.DefinitionId, out var definition) ? definition : null;
}
private Dictionary<Guid, UnitEntry> BuildUnitIndex()
{
var index = new Dictionary<Guid, UnitEntry>();
var roster = _session.State.Roster;
if (roster.Mentor != null)
{
var unit = roster.Mentor.Core;
index[unit.Identity.Id] = new UnitEntry(unit, roster.Mentor, null, null);
}
foreach (var student in roster.Students)
{
var unit = student.Core;
index[unit.Identity.Id] = new UnitEntry(unit, null, student, null);
}
foreach (var staff in roster.Staffs)
{
var unit = staff.Core;
index[unit.Identity.Id] = new UnitEntry(unit, null, null, staff);
}
return index;
}
private void TrackContribution(UnitEntry entry, TaskModel task, float deltaContribution)
{
if (entry.Student == null) return;
var contributions = entry.Student.Contributions.ByTask;
if (!contributions.TryGetValue(task.Id, out var value))
{
value = new PropertyValue(0, 0, 1000000);
contributions[task.Id] = value;
}
value.Add(deltaContribution);
}
private readonly struct UnitEntry
{
public UnitModel Unit { get; }
public MentorModel Mentor { get; }
public StudentModel Student { get; }
public StaffModel Staff { get; }
public UnitEntry(UnitModel unit, MentorModel mentor, StudentModel student, StaffModel staff)
{
Unit = unit;
Mentor = mentor;
Student = student;
Staff = staff;
}
}
}
public sealed class EconomySystem : IGameSystem
{
private GameSession _session;
private const int MasterSalary = 500;
private const int DoctorSalary = 800;
private const int PostDocSalary = 1200;
private const int JuniorFacultySalary = 2000;
public void Initialize(GameSession session)
{
_session = session;
_session.Events.Subscribe<TaskCompletedEvent>(OnTaskCompleted);
_session.Events.Subscribe<TaskFailedEvent>(OnTaskFailed);
_session.Events.Subscribe<TurnEndedEvent>(OnTurnEnded);
}
public void Tick(float delta)
{
// 当前为回合驱动,不在 Tick 中结算
}
private void OnTaskCompleted(TaskCompletedEvent evt)
{
var task = evt.Task;
var economy = _session.State.Economy;
economy.Money += task.Reward.Money;
economy.Reputation += task.Reward.Reputation;
if (task.Reward.PaperIds.Count > 0)
{
foreach (var paperId in task.Reward.PaperIds)
{
if (_session.Content.Papers.TryGetValue(paperId, out var paper))
{
AddPaper(paper.Rank);
}
}
}
else if (task.Kind == TaskKind.AcademicExploration)
{
AddPaper(GetPaperRankByDifficulty(task.Difficulty));
}
foreach (var itemId in task.Reward.ItemIds)
{
AddItem(itemId, 1);
}
}
private void OnTaskFailed(TaskFailedEvent evt)
{
var task = evt.Task;
var penalty = task.Track == TaskTrack.Tenure ? 20 : 10;
if (task.Kind == TaskKind.GrantVertical) penalty += 10;
_session.State.Economy.Reputation -= penalty;
}
private void OnTurnEnded(TurnEndedEvent evt)
{
ApplySalaries();
ApplyInterest();
}
private void ApplySalaries()
{
var economy = _session.State.Economy;
foreach (var student in _session.State.Roster.Students)
{
economy.Money -= student.Type == StudentModel.StudentType.MasterCandidate
? MasterSalary
: DoctorSalary;
}
foreach (var staff in _session.State.Roster.Staffs)
{
economy.Money -= staff.Type == StaffModel.StaffType.PostDoc
? PostDocSalary
: JuniorFacultySalary;
}
}
private void ApplyInterest()
{
var economy = _session.State.Economy;
UpdateInterestRate();
if (economy.Money <= 0 || economy.InterestRate <= 0) return;
var interest = (int)(economy.Money * economy.InterestRate);
economy.Money += interest;
}
private void UpdateInterestRate()
{
var economy = _session.State.Economy;
var mentor = _session.State.Roster.Mentor;
if (mentor?.Core.Tags.DisciplineId == CoreIds.DisciplineEconomics)
{
economy.InterestRate = Math.Max(economy.InterestRate, 0.1f);
}
}
private void AddPaper(PaperRank rank)
{
var inventory = _session.State.Inventory;
if (!inventory.PaperCounts.ContainsKey(rank))
{
inventory.PaperCounts[rank] = 0;
}
inventory.PaperCounts[rank] += 1;
}
private void AddItem(string itemId, int count)
{
if (string.IsNullOrWhiteSpace(itemId)) return;
var inventory = _session.State.Inventory;
if (!inventory.ItemCounts.ContainsKey(itemId))
{
inventory.ItemCounts[itemId] = 0;
}
inventory.ItemCounts[itemId] += count;
}
private PaperRank GetPaperRankByDifficulty(TaskDifficulty difficulty)
{
return difficulty switch
{
TaskDifficulty.Water => PaperRank.C,
TaskDifficulty.Standard => PaperRank.B,
TaskDifficulty.Hardcore => PaperRank.A,
TaskDifficulty.BlackBox => PaperRank.S,
_ => PaperRank.C
};
}
}
public sealed class SynergySystem : IGameSystem
{
private GameSession _session;
public void Initialize(GameSession session)
{
_session = session;
}
public void Tick(float delta)
{
RecalculateSynergy();
}
private void RecalculateSynergy()
{
var state = _session.State.Synergy;
state.ArchetypeStacks.Clear();
state.RoleStacks.Clear();
state.ActiveSynergyIds.Clear();
ClearModifiers(state.ActiveModifiers);
CountUnitTags(state);
ApplySynergyDefinitions(state);
}
private void CountUnitTags(SynergyState synergy)
{
var roster = _session.State.Roster;
AddUnitTags(synergy, roster.Mentor?.Core);
foreach (var student in roster.Students)
{
AddUnitTags(synergy, student.Core);
}
foreach (var staff in roster.Staffs)
{
AddUnitTags(synergy, staff.Core);
}
}
private void AddUnitTags(SynergyState synergy, UnitModel unit)
{
if (unit == null) return;
foreach (var archetypeId in unit.Tags.ArchetypeIds)
{
AddStack(synergy.ArchetypeStacks, archetypeId);
}
foreach (var roleId in unit.Tags.RoleIds)
{
AddStack(synergy.RoleStacks, roleId);
}
}
private void AddStack(Dictionary<string, int> stacks, string id)
{
if (string.IsNullOrWhiteSpace(id)) return;
if (!stacks.ContainsKey(id))
{
stacks[id] = 0;
}
stacks[id] += 1;
}
private void ApplySynergyDefinitions(SynergyState synergy)
{
foreach (var archetype in _session.Content.Archetypes.Values)
{
ApplySynergyTier(archetype.Header.Id, archetype.Tiers, synergy.ArchetypeStacks, synergy);
}
foreach (var role in _session.Content.Roles.Values)
{
ApplySynergyTier(role.Header.Id, role.Tiers, synergy.RoleStacks, synergy);
}
}
private void ApplySynergyTier(string id, List<SynergyTier> tiers, Dictionary<string, int> stacks, SynergyState synergy)
{
if (string.IsNullOrWhiteSpace(id)) return;
stacks.TryGetValue(id, out var count);
foreach (var tier in tiers)
{
if (count < tier.RequiredCount) continue;
var synergyId = $"{id}@{tier.RequiredCount}";
synergy.ActiveSynergyIds.Add(synergyId);
MergeModifiers(synergy.ActiveModifiers, tier.Modifiers);
}
}
private void MergeModifiers(ModifierBundle target, ModifierBundle source)
{
if (target == null || source == null) return;
target.AttributeModifiers.AddRange(source.AttributeModifiers);
target.StatusModifiers.AddRange(source.StatusModifiers);
target.ResourceModifiers.AddRange(source.ResourceModifiers);
target.RuleIds.AddRange(source.RuleIds);
}
private void ClearModifiers(ModifierBundle bundle)
{
bundle.AttributeModifiers.Clear();
bundle.StatusModifiers.Clear();
bundle.ResourceModifiers.Clear();
bundle.RuleIds.Clear();
}
}
public sealed class AssignmentSystem : IGameSystem
{
private GameSession _session;
public void Initialize(GameSession session)
{
_session = session;
}
public void Tick(float delta)
{
// 预留:人员分配、交接惩罚等
}
}