using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; using Babushka.scripts.CSharp.Common.Util; using Godot; namespace Babushka.scripts.CSharp.Common.Minigame; public partial class MinigameController : Node2D { public class Builder { internal class Region { public required T value; public float proportion = 1f; } public enum DoubleHitHandling { Allow, Disallow, Remove, } internal List regions = new(); internal DoubleHitHandling doubleHitHandling = DoubleHitHandling.Allow; internal int maxHitCount = 3; // add region public Builder AddRegion(T value) { regions.Add(new Region { value = value }); return this; } // region settings public Builder RegionWithProportion(float proportion) { if (regions.Count == 0) throw new InvalidOperationException("No region to set proportion for"); regions.Last().proportion = proportion; return this; } // general settings public Builder WithDoubleHitHandling(DoubleHitHandling handling) { if (handling != DoubleHitHandling.Allow) throw new NotImplementedException(); doubleHitHandling = handling; return this; } public Builder WithHitCount(int hitCount) { this.maxHitCount = hitCount; return this; } } private TaskCompletionSource? _minigameComplete; private List? _hits; private float _armPosition = 0f; private float _armSpeed = 1f; private List? _regions; private int _maxHitCount; [Export] private PackedScene _regionVisualPrefab = null!; [Export] private Node2D _regionsParent = null!; [Export] private Color _baseRegionColor = Colors.Red; [Signal] public delegate void ArmMovedEventHandler(float newPos); public override void _EnterTree() { HideMinigame(); } public override void _Process(double delta) { _armPosition += _armSpeed * (float)delta; _armPosition = Mathf.PosMod(_armPosition, 1); EmitSignalArmMoved(_armPosition); } public async Task> Run(Builder builder) { ShowMinigame(); Setup(builder); await _minigameComplete!.Task; var returnValue = _hits!.Select(h => builder.regions[h].value).ToList(); Reset(); HideMinigame(); return returnValue; } public void Hit() { if (_hits == null) return; int i; for (i = 0; i < _regions!.Count - 1; i++) { if (_armPosition < _regions[i]) { break; } } _hits.Add(i); _armSpeed = -_armSpeed; if (_hits.Count >= _maxHitCount) { _minigameComplete!.SetResult(); } } private void Setup(Builder builder) { _minigameComplete = new(); _hits = []; _armPosition = 0f; _armSpeed = 1f; _regions = []; SetupRegions(builder); _maxHitCount = builder.maxHitCount; } private void SetupRegions(Builder builder) { // common calculations var totalRegionProportion = builder.regions.Sum(r => r.proportion); // spawn regions var regionSum = 0f; foreach (var region in builder.regions) { var regionVisual = _regionVisualPrefab.Instantiate(); _regionsParent.AddChild(regionVisual); var normalisedAngleStart = regionSum / totalRegionProportion; var normalisedAngleEnd = (regionSum + region.proportion) / totalRegionProportion; var normalAngles = new Vector2(normalisedAngleStart, normalisedAngleEnd); regionVisual.Setup(normalAngles, _baseRegionColor.RandomHue()); regionSum += region.proportion; _regions!.Add(normalisedAngleEnd); } } private void ShowMinigame() { Show(); ProcessMode = ProcessModeEnum.Inherit; } private void HideMinigame() { Hide(); ProcessMode = ProcessModeEnum.Disabled; } private void Reset() { _minigameComplete = null; _hits = null; _regionsParent.GetChildren().ForEach(c => c.QueueFree()); } }