Add hero runtime
This commit is contained in:
248
godot/scripts/hero/rules/HeroRuntimeService.cs
Normal file
248
godot/scripts/hero/rules/HeroRuntimeService.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user