Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Bearded.TD.Game.Simulation.GameObjects;
using Bearded.TD.Game.Simulation.StatusDisplays;
using Bearded.TD.Utilities.Collections;
using Bearded.Utilities.SpaceTime;

namespace Bearded.TD.Game.Simulation.Elements;

abstract class ElementalPhenomenonScopeBase<TEffect> : IElementalPhenomenon.IScope<TEffect> where TEffect : IElementalEffect
abstract class ElementalPhenomenonScopeBase<TEffect>
: ElementalPhenomenonScopeBase, IElementalEffect<TEffect>.IScope
where TEffect : struct, IElementalEffect<TEffect>, IEquatable<TEffect>
{
private readonly GameObject target;
private readonly IStatusTracker? statusDisplay;
private readonly List<EffectWithExpiry> activeEffects = [];
private ActiveEffect? activeEffect;

protected IEnumerable<TEffect> ActiveEffects => activeEffects.Select(e => e.Effect);
protected GameObject Target { get; private set; }

protected ElementalPhenomenonScopeBase(GameObject target)
{
this.target = target;
Target = target;
target.TryGetSingleComponent(out statusDisplay);
}

Expand All @@ -28,57 +32,61 @@ public void Adopt(TEffect effect, Instant now)

public void ApplyTick(Instant now)
{
activeEffects.RemoveAll(e => e.Expiry <= now);
activeEffects.RemoveAll(now, static (t, e) => e.Expiry <= t);

if (TryChooseEffect(out var effect))
if (ChooseEffect(CollectionsMarshal.AsSpan(activeEffects)) is { } effect)
{
transitionToEffect(effect);
ApplyEffectTick(target, effect);
ApplyEffectTick(effect);
}
else
{
endEffectIfPreviouslyActive();
}

if (activeEffect?.StatusIcon is not null)
if (activeEffect?.StatusReceipt is { } icon)
{
updateStatusIconExpiry(activeEffect.StatusIcon);
updateStatusIconExpiry(icon);
}
}

private void transitionToEffect(TEffect effect)
{
// null -> effect A
if (activeEffect is null)
if (activeEffect is not { } current)
{
StartScope(target, out var createStatus);
startEffect(effect, out var createOverrideStatus);
createStatus = createOverrideStatus ?? createStatus;
var receipt = createStatus is null ? null : reportStatus(createStatus);
var statusChange = EffectChangeResult.NoChange;
StartScope(ref statusChange);
StartEffect(effect, ref statusChange);

var receipt = statusChange.Type == EffectChangeType.ShowStatusIcon
? reportStatus(statusChange.NewStatus!.Value)
: null;

activeEffect = new ActiveEffect(effect, receipt);
return;
}

// effect A -> effect A
if (activeEffect.Effect.Equals(effect)) return;
if (current.Effect.Equals(effect))
return;

// effect A -> effect B
var statusIcon = activeEffect.StatusIcon;
EndEffect(target, effect);
startEffect(effect, out var createNewStatus);
if (createNewStatus is not null)
{
statusIcon?.DeleteImmediately();
statusIcon = reportStatus(createNewStatus);
}
activeEffect = new ActiveEffect(effect, statusIcon);
}
var statusChange = EffectChangeResult.NoChange;
EndEffect();
StartEffect(effect, ref statusChange);

private void startEffect(TEffect effect, out ElementalStatus? newStatus)
{
var ctx = new EffectStartContext();
StartEffect(target, effect, ctx);
newStatus = ctx.NewStatus;
var receipt = current.StatusReceipt;

if (statusChange.Type == EffectChangeType.ShowStatusIcon)
{
receipt?.DeleteImmediately();
receipt = reportStatus(statusChange.NewStatus!.Value);
}

activeEffect = new ActiveEffect(effect, receipt);
}
}

private IStatusReceipt? reportStatus(ElementalStatus status)
Expand All @@ -92,36 +100,55 @@ private void startEffect(TEffect effect, out ElementalStatus? newStatus)

private void endEffectIfPreviouslyActive()
{
if (activeEffect is null) return;
if (activeEffect is not { } current) return;

EndScope(target);
activeEffect.StatusIcon?.DeleteImmediately();
EndEffect();
EndScope();
current.StatusReceipt?.DeleteImmediately();
activeEffect = null;
}

private void updateStatusIconExpiry(IStatusReceipt statusIcon)
{
var latestExpiry = activeEffects.Select(e => e.Expiry).Max();
var latestExpiry = activeEffects.Max(static e => e.Expiry);
statusIcon.SetExpiryTime(latestExpiry);
}

protected abstract bool TryChooseEffect(out TEffect effect);
protected abstract void StartScope(GameObject target, out ElementalStatus? status);
protected abstract void StartEffect(GameObject target, TEffect effect, EffectStartContext context);
protected abstract void ApplyEffectTick(GameObject target, TEffect effect);
protected abstract void EndEffect(GameObject target, TEffect effect);
protected abstract void EndScope(GameObject target);
protected abstract TEffect? ChooseEffect(ReadOnlySpan<EffectWithExpiry> activeEffects);
protected abstract void StartScope(ref EffectChangeResult statusChange);
protected abstract void StartEffect(TEffect effect, ref EffectChangeResult statusChange);
protected abstract void ApplyEffectTick(TEffect effect);
protected abstract void EndEffect();
protected abstract void EndScope();

private readonly record struct EffectWithExpiry(TEffect Effect, Instant Expiry);
private sealed record ActiveEffect(TEffect Effect, IStatusReceipt? StatusIcon);
protected readonly record struct EffectWithExpiry(TEffect Effect, Instant Expiry);
private readonly record struct ActiveEffect(TEffect Effect, IStatusReceipt? StatusReceipt);

protected class EffectStartContext
}

// Base class for non-generic members
abstract class ElementalPhenomenonScopeBase
{
protected enum EffectChangeType
{
Retain = 0,
ShowStatusIcon = 1,
}

protected readonly struct EffectChangeResult
{
public ElementalStatus? NewStatus { get; private set; }
public EffectChangeType Type { get; private init; }

public ElementalStatus? NewStatus { get; private init; }

public static EffectChangeResult NoChange => default;

public void ChangeStatus(ElementalStatus status)
public static EffectChangeResult ShowStatus(ElementalStatus status) => new()
{
NewStatus = status;
}
Type = EffectChangeType.ShowStatusIcon,
NewStatus = status,
};

public static implicit operator EffectChangeResult(ElementalStatus newStatus) => ShowStatus(newStatus);
}
}
2 changes: 1 addition & 1 deletion src/Bearded.TD/Game/Simulation/Elements/ElementalStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

namespace Bearded.TD.Game.Simulation.Elements;

sealed record ElementalStatus(ModAwareSpriteId Sprite);
readonly record struct ElementalStatus(ModAwareSpriteId Sprite);
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Bearded.TD.Game.Simulation.Elements;

static class ElementalSystemExtensions
{
public static bool TryApplyEffect<T>(this GameObject obj, T effect) where T : IElementalEffect
public static bool TryApplyEffect<T>(this GameObject obj, T effect) where T : IElementalEffect<T>
{
if (!obj.TryGetSingleComponent<IElementSystemEntity>(out var entity))
{
Expand Down
18 changes: 17 additions & 1 deletion src/Bearded.TD/Game/Simulation/Elements/IElementalEffect.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
using Bearded.TD.Game.Simulation.GameObjects;
using Bearded.Utilities.SpaceTime;

namespace Bearded.TD.Game.Simulation.Elements;

interface IElementalEffect
{
IElementalPhenomenon Phenomenon { get; }
TimeSpan Duration { get; }

interface IScope
{
void ApplyTick(Instant now);
}
}

interface IElementalEffect<in TEffect> : IElementalEffect
where TEffect : IElementalEffect<TEffect>
{
new interface IScope : IElementalEffect.IScope
{
void Adopt(TEffect effect, Instant now);
}

IScope NewScope(GameObject target);
}
21 changes: 0 additions & 21 deletions src/Bearded.TD/Game/Simulation/Elements/IElementalPhenomenon.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,70 +1,55 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using Bearded.TD.Game.Simulation.GameObjects;
using Bearded.TD.Game.Simulation.StatusDisplays;
using Bearded.TD.Game.Simulation.Upgrades;
using Bearded.TD.Shared.TechEffects;
using Bearded.TD.Utilities.Collections;
using TimeSpan = Bearded.Utilities.SpaceTime.TimeSpan;

namespace Bearded.TD.Game.Simulation.Elements.Phenomena;

static class LowerWeaponRange
{
public readonly record struct Effect(double Factor, TimeSpan Duration) : IElementalEffect
public readonly record struct Effect(double Factor, TimeSpan Duration) : IElementalEffect<Effect>
{
IElementalPhenomenon IElementalEffect.Phenomenon => phenomenon;
}

private static readonly IElementalPhenomenon phenomenon = new Phenomenon();

private sealed class Phenomenon : IElementalPhenomenon
{
public Type EffectType => typeof(Effect);

public IElementalPhenomenon.IScope NewScope(GameObject target) => new Scope(target);
public IElementalEffect<Effect>.IScope NewScope(GameObject target) => new Scope(target);
}

private sealed class Scope(GameObject target) : ElementalPhenomenonScopeBase<Effect>(target)
{
private IUpgradeReceipt? receipt;

protected override bool TryChooseEffect(out Effect effect)
protected override Effect? ChooseEffect(ReadOnlySpan<EffectWithExpiry> activeEffects)
{
var effects = ActiveEffects.ToImmutableArray();
if (effects.IsEmpty)
{
effect = default;
return false;
}

effect = effects.MinBy(e => e.Factor);
return true;
return activeEffects.MinByOrDefault(e => e.Effect.Factor)?.Effect;
}

protected override void StartScope(GameObject target, out ElementalStatus? status)
protected override void StartScope(ref EffectChangeResult statusChange)
{
status = new ElementalStatus("eye-disabled".ToStatusIconSpriteId());
statusChange = new ElementalStatus("eye-disabled".ToStatusIconSpriteId());

}

protected override void StartEffect(GameObject target, Effect effect, EffectStartContext context)
protected override void StartEffect(Effect effect, ref EffectChangeResult statusChange)
{
var upgrade = Upgrade.FromEffects(createUpgradeEffect(effect));
if (!target.CanApplyUpgrade(upgrade)) return;
receipt = target.ApplyUpgrade(upgrade);
if (!Target.CanApplyUpgrade(upgrade)) return;
receipt = Target.ApplyUpgrade(upgrade);
}

protected override void ApplyEffectTick(GameObject target, Effect effect) { }
protected override void ApplyEffectTick(Effect effect) { }

protected override void EndEffect(GameObject target, Effect effect)
protected override void EndEffect()
{
receipt?.Rollback();
receipt = null;
}

protected override void EndScope(GameObject target) { }
protected override void EndScope()
{
}

private static IUpgradeEffect createUpgradeEffect(Effect effect)
private static ModifyParameter createUpgradeEffect(Effect effect)
{
return new ModifyParameter(
AttributeType.Range,
Expand Down
13 changes: 0 additions & 13 deletions src/Bearded.TD/Game/Simulation/Elements/Phenomena/OnFire.Effect.cs

This file was deleted.

This file was deleted.

Loading