using System; using System.Collections.Generic; using Models; namespace Core; /// /// 系统层骨架(负责模型变化,不直接处理输入/显示) /// 设计说明: /// 1) 每个系统只关注单一职责,形成高内聚低耦合结构。 /// 2) 系统之间通过 GameSession 共享状态,通过事件总线通讯。 /// 注意事项: /// - 系统逻辑应尽量“幂等”,便于回合重算与调试。 /// 未来扩展: /// - 可加入“系统执行顺序配置”,支持 Mod 插入新系统。 /// 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); } /// /// 回合结算(扣除 Deadline,判定完成与失败) /// public void ResolveEndOfTurn() { var state = _session.State; var completed = new List(); var failed = new List(); 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 BuildUnitIndex() { var index = new Dictionary(); 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(OnTaskCompleted); _session.Events.Subscribe(OnTaskFailed); _session.Events.Subscribe(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 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 tiers, Dictionary 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) { // 预留:人员分配、交接惩罚等 } }