Weapons/PeachLauncher/PeachAmmoSource.cs
/// <summary>
/// Peach Tree (or any ammo source) — players entering the trigger zone
/// have their PeachLauncherWeapon reserve topped up via AddReserveAmmo().
///
/// Two modes:
/// Passive — tops up every TickInterval seconds while inside
/// OneShot — gives ammo once on entry, recharges after TickInterval
///
/// Wire up:
/// Add a SphereCollider or BoxCollider (IsTrigger = true) on this GO or a child.
/// Place PeachAmmoSource on the same GO as the collider's parent.
/// </summary>
[Title( "Peach Ammo Source" )]
[Category( "Game / Peach" )]
public sealed class PeachAmmoSource : Component, Component.ITriggerListener
{
public enum FillMode { Passive, OneShot }
// ── Settings ───────────────────────────────────────────────────────────────
[Property, Range( 1, 20 )] public int MaxAmmo { get; set; } = 5;
[Property, Range( 1, 5 )] public int AmmoPerTick { get; set; } = 1;
[Property, Range( 0.1f, 10f )] public float TickInterval { get; set; } = 1.0f;
[Property] public FillMode Mode { get; set; } = FillMode.Passive;
[Property] public GameObject PickupEffect { get; set; }
// ── Runtime ────────────────────────────────────────────────────────────────
private readonly HashSet<GameObject> _playersInside = new();
private TimeSince _timeSinceTick;
private bool _oneShotReady = true;
// ── ITriggerListener ───────────────────────────────────────────────────────
void Component.ITriggerListener.OnTriggerEnter( Collider self, GameObject other )
{
if ( !IsLocalPlayer( other ) ) return;
_playersInside.Add( other );
if ( Mode == FillMode.OneShot && _oneShotReady )
{
GiveAmmo( other );
_oneShotReady = false;
}
}
void Component.ITriggerListener.OnTriggerExit( Collider self, GameObject other )
{
_playersInside.Remove( other );
}
// ── Tick ──────────────────────────────────────────────────────────────────
protected override void OnUpdate()
{
if ( _timeSinceTick < TickInterval ) return;
_timeSinceTick = 0;
if ( Mode == FillMode.OneShot )
{
if ( !_oneShotReady ) _oneShotReady = true;
return;
}
foreach ( var player in _playersInside )
{
if ( player.IsValid() )
GiveAmmo( player );
}
}
// ── Ammo logic ─────────────────────────────────────────────────────────────
private void GiveAmmo( GameObject player )
{
// Weapon lives as a child of the player GO inside PlayerInventory —
// EverythingInSelfAndDescendants is required to find it.
var launcher = player.Components.Get<PeachLauncherWeapon>( FindMode.EverythingInSelfAndDescendants );
if ( !launcher.IsValid() ) return;
var added = launcher.AddReserveAmmo( AmmoPerTick );
if ( added > 0 && PickupEffect.IsValid() )
PickupEffect.Clone( WorldPosition );
}
// ── Helper ─────────────────────────────────────────────────────────────────
private static bool IsLocalPlayer( GameObject go )
{
if ( !go.Tags.Has( "player" ) ) return false;
return go.Components
.GetAll<Component>( FindMode.EverythingInSelfAndDescendants )
.Any( c => !c.IsProxy );
}
}