已把 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
This commit is contained in:
parent
b8fecd080f
commit
9c1593e717
44
scripts/Core/DomainEvents.cs
Normal file
44
scripts/Core/DomainEvents.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using Models;
|
||||
|
||||
namespace Core;
|
||||
|
||||
/// <summary>
|
||||
/// 领域事件(用于系统解耦)
|
||||
/// 设计说明:
|
||||
/// 1) Task/Economy/Turn 等系统通过事件通信,避免直接依赖。
|
||||
/// 2) 事件只携带最小必要信息,避免模型被过度暴露。
|
||||
/// 注意事项:
|
||||
/// - 事件是同步派发,请避免在处理器中做耗时操作。
|
||||
/// 未来扩展:
|
||||
/// - 可加入“事件上下文/来源系统”等字段,便于调试。
|
||||
/// </summary>
|
||||
public readonly struct TaskCompletedEvent
|
||||
{
|
||||
public TaskModel Task { get; }
|
||||
|
||||
public TaskCompletedEvent(TaskModel task)
|
||||
{
|
||||
Task = task;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct TaskFailedEvent
|
||||
{
|
||||
public TaskModel Task { get; }
|
||||
|
||||
public TaskFailedEvent(TaskModel task)
|
||||
{
|
||||
Task = task;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct TurnEndedEvent
|
||||
{
|
||||
public int Turn { get; }
|
||||
|
||||
public TurnEndedEvent(int turn)
|
||||
{
|
||||
Turn = turn;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Models;
|
||||
|
||||
namespace Core;
|
||||
@ -38,9 +40,9 @@ public sealed class GameSystems
|
||||
public void Tick(float delta)
|
||||
{
|
||||
Turn.Tick(delta);
|
||||
Synergy.Tick(delta);
|
||||
Task.Tick(delta);
|
||||
Economy.Tick(delta);
|
||||
Synergy.Tick(delta);
|
||||
Assignment.Tick(delta);
|
||||
}
|
||||
}
|
||||
@ -63,30 +65,430 @@ public sealed class TurnSystem : IGameSystem
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +503,104 @@ public sealed class SynergySystem : IGameSystem
|
||||
|
||||
public void Tick(float delta)
|
||||
{
|
||||
// 预留:根据 roster 统计羁绊层数
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
90
scripts/Core/StatResolver.cs
Normal file
90
scripts/Core/StatResolver.cs
Normal file
@ -0,0 +1,90 @@
|
||||
using System.Collections.Generic;
|
||||
using Models;
|
||||
|
||||
namespace Core;
|
||||
|
||||
/// <summary>
|
||||
/// 数值解析器(用于统一计算有效属性)
|
||||
/// 设计说明:
|
||||
/// 1) 只做“读+合成”,不直接修改 Model。
|
||||
/// 2) 合成来源包含:羁绊、学科 Buff、特质、装备。
|
||||
/// 注意事项:
|
||||
/// - 规则型效果(RuleIds)暂不在此处理,由系统层扩展。
|
||||
/// 未来扩展:
|
||||
/// - 可引入“上下文参数”(任务类型/场景)以处理条件加成。
|
||||
/// </summary>
|
||||
public sealed class StatResolver
|
||||
{
|
||||
private readonly GameSession _session;
|
||||
|
||||
public StatResolver(GameSession session)
|
||||
{
|
||||
_session = session;
|
||||
}
|
||||
|
||||
public float GetAttribute(UnitModel unit, AttributeType type)
|
||||
{
|
||||
var value = GetBaseAttribute(unit, type);
|
||||
ApplyBundle(_session.State.Synergy.ActiveModifiers, type, ref value);
|
||||
ApplyDiscipline(unit.Tags.DisciplineId, type, ref value);
|
||||
ApplyTraits(unit.Tags.TraitIds, type, ref value);
|
||||
ApplyItems(unit.Equipment.EquippedItemIds, type, ref value);
|
||||
return value;
|
||||
}
|
||||
|
||||
private float GetBaseAttribute(UnitModel unit, AttributeType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
AttributeType.Academic => unit.Attributes.Academic.Value,
|
||||
AttributeType.Engineering => unit.Attributes.Engineering.Value,
|
||||
AttributeType.Writing => unit.Attributes.Writing.Value,
|
||||
AttributeType.Financial => unit.Attributes.Financial.Value,
|
||||
AttributeType.Social => unit.Attributes.Social.Value,
|
||||
AttributeType.Activation => unit.Attributes.Activation.Value,
|
||||
_ => 0f
|
||||
};
|
||||
}
|
||||
|
||||
private void ApplyDiscipline(string disciplineId, AttributeType type, ref float value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(disciplineId)) return;
|
||||
if (!_session.Content.Disciplines.TryGetValue(disciplineId, out var discipline)) return;
|
||||
ApplyBundle(discipline.Buff?.Modifiers, type, ref value);
|
||||
}
|
||||
|
||||
private void ApplyTraits(List<string> traitIds, AttributeType type, ref float value)
|
||||
{
|
||||
if (traitIds == null || traitIds.Count == 0) return;
|
||||
foreach (var traitId in traitIds)
|
||||
{
|
||||
if (_session.Content.Traits.TryGetValue(traitId, out var trait))
|
||||
{
|
||||
ApplyBundle(trait.Modifiers, type, ref value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyItems(List<string> itemIds, AttributeType type, ref float value)
|
||||
{
|
||||
if (itemIds == null || itemIds.Count == 0) return;
|
||||
foreach (var itemId in itemIds)
|
||||
{
|
||||
if (_session.Content.Items.TryGetValue(itemId, out var item))
|
||||
{
|
||||
ApplyBundle(item.Effect?.Modifiers, type, ref value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyBundle(ModifierBundle bundle, AttributeType type, ref float value)
|
||||
{
|
||||
if (bundle == null) return;
|
||||
foreach (var modifier in bundle.AttributeModifiers)
|
||||
{
|
||||
if (modifier.Type != type) continue;
|
||||
value = (value + modifier.Add) * modifier.Multiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,28 +127,8 @@ public partial class GameManager : Node
|
||||
/// </summary>
|
||||
private void PerformReview()
|
||||
{
|
||||
// 1. Task progress check
|
||||
foreach (var task in ActiveTasks)
|
||||
{
|
||||
task.Runtime.RemainingTurns--;
|
||||
if (task.IsCompleted)
|
||||
{
|
||||
GD.Print($"Task {task.Name} Completed!");
|
||||
Economy.Reputation += task.Reward.Reputation;
|
||||
Economy.Money += task.Reward.Money;
|
||||
}
|
||||
else if (task.IsFailed)
|
||||
{
|
||||
GD.Print($"Task {task.Name} Failed!");
|
||||
Economy.Reputation -= 10; // Penalty
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Student status update (Salary, etc.)
|
||||
foreach (var student in Students)
|
||||
{
|
||||
// Deduct salary? Restore some stamina?
|
||||
}
|
||||
Session.Systems.Task.ResolveEndOfTurn();
|
||||
Session.Events.Publish(new TurnEndedEvent(CurrentTurn));
|
||||
|
||||
GD.Print("Review Complete. Waiting for Next Turn confirmation.");
|
||||
}
|
||||
|
||||
@ -73,6 +73,7 @@ public sealed class SynergyState
|
||||
public Dictionary<string, int> ArchetypeStacks { get; } = new();
|
||||
public Dictionary<string, int> RoleStacks { get; } = new();
|
||||
public List<string> ActiveSynergyIds { get; } = new();
|
||||
public ModifierBundle ActiveModifiers { get; } = new();
|
||||
}
|
||||
|
||||
public sealed class RogueliteState
|
||||
|
||||
@ -76,4 +76,5 @@ public sealed class TaskRewardSnapshot
|
||||
public int Money { get; set; }
|
||||
public int Reputation { get; set; }
|
||||
public List<string> PaperIds { get; } = new();
|
||||
public List<string> ItemIds { get; set; }
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user