Files
zfxaction26_1/godot/scripts/hero/rules/HeroRuntimeService.cs
2026-04-21 22:54:14 +02:00

248 lines
7.4 KiB
C#

#nullable enable
using System;
using System.Linq;
using SideScrollerGame.Content;
using SideScrollerGame.Content.Definitions;
namespace SideScrollerGame.Hero.Rules;
public sealed class HeroRuntimeService
{
public HeroRuntimeService(ContentRegistry registry, HeroRuleConfig config, string difficultyId)
{
m_Registry = registry;
Config = config;
m_ActiveDifficulty = ResolveDifficulty(difficultyId);
State = new HeroRunState(m_ActiveDifficulty.Id, m_ActiveDifficulty.HeroStartingShieldCharges, m_ActiveDifficulty.HeroRetryCount, config);
}
public HeroRuleResult ApplyHit(bool invulnerable)
{
if (State.LifeState != HeroLifeState.Alive)
{
return Fail("Hero is not alive");
}
if (invulnerable)
{
return Succeed("Hero ignored hit while invulnerable");
}
if (State.ShieldCharges > 0)
{
State.ShieldCharges--;
return Succeed($"Hero hit: shield {State.ShieldCharges}");
}
return EnterDeathState("Hero killed");
}
public HeroRuleResult Kill()
{
if (State.LifeState != HeroLifeState.Alive)
{
return Fail("Hero is not alive");
}
State.ShieldCharges = 0;
return EnterDeathState("Hero killed");
}
public HeroRuleResult Rebirth()
{
if (State.LifeState != HeroLifeState.Dead)
{
return Fail("Hero is not waiting for rebirth");
}
if (State.RetryCount <= 0)
{
State.LifeState = HeroLifeState.GameOver;
return Succeed("Game over");
}
State.RetryCount--;
State.LifeState = HeroLifeState.Alive;
State.ShieldCharges = m_ActiveDifficulty.HeroStartingShieldCharges;
State.ResetInventory(Config);
return Succeed("Hero reborn");
}
public HeroRuleResult AddPoints(int points)
{
if (points <= 0)
{
return Fail($"Invalid point amount {points}");
}
State.Points += points;
int targetLevel = CalculateLevel(State.Points);
int gainedLevels = Math.Max(0, targetLevel - State.Level);
State.Level = targetLevel;
State.ShieldCharges += gainedLevels;
return Succeed(gainedLevels == 0 ? $"Points added: {points}" : $"Points added: {points}; level {State.Level}");
}
public HeroRuleResult SetLevel(int level)
{
if (level < 1)
{
return Fail($"Invalid hero level {level}");
}
State.Level = level;
return Succeed($"Hero level set to {level}");
}
public HeroRuleResult AddShieldCharge(int amount)
{
if (amount <= 0)
{
return Fail($"Invalid shield amount {amount}");
}
State.ShieldCharges += amount;
return Succeed($"Shield added: {State.ShieldCharges}");
}
public HeroRuleResult RemoveShieldCharge(int amount)
{
if (amount <= 0)
{
return Fail($"Invalid shield amount {amount}");
}
State.ShieldCharges = Math.Max(0, State.ShieldCharges - amount);
return Succeed($"Shield removed: {State.ShieldCharges}");
}
public HeroRuleResult SetRetryCount(int retryCount)
{
if (retryCount < 0)
{
return Fail($"Invalid retry count {retryCount}");
}
State.RetryCount = retryCount;
return Succeed($"Retries set to {retryCount}");
}
public HeroRuleResult TogglePrimaryWeaponSlot()
{
if (State.PrimaryWeaponSlots.Count == 0)
{
return Fail("No primary weapon slots");
}
State.SelectedPrimaryWeaponSlotIndex = (State.SelectedPrimaryWeaponSlotIndex + 1) % State.PrimaryWeaponSlots.Count;
return Succeed($"Primary slot {State.SelectedPrimaryWeaponSlotIndex}");
}
public HeroRuleResult ApplyPrimaryWeaponPickup(string weaponId)
{
if (!m_Registry.Weapons.ContainsKey(weaponId))
{
return Fail($"Unknown primary weapon '{weaponId}'");
}
int targetSlot = State.FindEmptyPrimarySlot();
if (targetSlot < 0)
{
targetSlot = State.SelectedPrimaryWeaponSlotIndex;
}
State.ReplacePrimaryWeaponSlot(targetSlot, weaponId);
return Succeed($"Primary weapon {weaponId} in slot {targetSlot}");
}
public HeroRuleResult ApplySecondaryWeaponPickup(string weaponId)
{
if (!m_Registry.Weapons.ContainsKey(weaponId))
{
return Fail($"Unknown secondary weapon '{weaponId}'");
}
State.CurrentSecondaryWeaponId = weaponId;
return Succeed($"Secondary weapon {weaponId}");
}
public HeroRuleResult AddSpecialAmmo(int amount)
{
State.SpecialAmmo = Math.Max(0, State.SpecialAmmo + amount);
return Succeed($"Special ammo {State.SpecialAmmo}");
}
public HeroRuleResult ApplySquadronMatePickup(string squadronMateTypeId)
{
if (!m_Registry.SquadronMateTypes.ContainsKey(squadronMateTypeId))
{
return Fail($"Unknown squadron mate '{squadronMateTypeId}'");
}
State.SquadronMateTypeId = squadronMateTypeId;
State.SquadronMateCount = Math.Min(Config.MaxSquadronMateCount, State.SquadronMateCount + 1);
return Succeed($"Squadron mates {State.SquadronMateCount} {squadronMateTypeId}");
}
public HeroRuleResult ClearInventory()
{
State.ResetInventory(Config);
return Succeed("Hero inventory cleared");
}
public HeroMissionSnapshot CreateMissionSnapshot()
{
return new HeroMissionSnapshot(State.ActiveDifficultyId, State.LifeState, State.Level, State.Points, State.ShieldCharges, State.RetryCount, State.PrimaryWeaponSlots.ToList(), State.SelectedPrimaryWeaponSlotIndex, State.CurrentSecondaryWeaponId, State.CurrentSpecialWeaponId, State.SpecialAmmo, State.SquadronMateTypeId, State.SquadronMateCount);
}
public int? NextPointThreshold
{
get { return Config.PointThresholds.Cast<int?>().FirstOrDefault(threshold => threshold > State.Points); }
}
public HeroRunState State { get; }
public HeroRuleConfig Config { get; }
public event Action<HeroRunState>? StateChanged;
private HeroRuleResult EnterDeathState(string message)
{
State.ShieldCharges = 0;
State.ResetInventory(Config);
State.LifeState = State.RetryCount > 0 ? HeroLifeState.Dead : HeroLifeState.GameOver;
return Succeed(State.LifeState == HeroLifeState.GameOver ? "Game over" : message);
}
private HeroRuleResult Succeed(string message)
{
State.LastStateChange = message;
StateChanged?.Invoke(State);
return HeroRuleResult.Success(message, State);
}
private HeroRuleResult Fail(string message)
{
State.LastStateChange = message;
return HeroRuleResult.Failure(message, State);
}
private int CalculateLevel(int points)
{
return 1 + Config.PointThresholds.Count(threshold => points >= threshold);
}
private DifficultyDefinition ResolveDifficulty(string difficultyId)
{
if (m_Registry.Difficulties.TryGetValue(difficultyId, out DifficultyDefinition? difficulty))
{
return difficulty;
}
return m_Registry.DifficultyDefinitions.First();
}
private readonly ContentRegistry m_Registry;
private readonly DifficultyDefinition m_ActiveDifficulty;
}