You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Babushka/scripts/CSharp/Common/Minigame/MinigameController.cs

212 lines
5.3 KiB

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 enum RegionTheme
{
Disabled,
VeryBad,
Bad,
Normal,
NormalAlt1,
NormalAlt2,
God,
VeryGood
}
public class Builder<T>
{
internal class Region
{
public required T value;
public float proportion = 1f;
public string text = "";
public RegionTheme theme = RegionTheme.Normal;
}
public enum DoubleHitHandling
{
Allow,
Disallow,
Remove,
}
internal List<Region> regions = new();
internal DoubleHitHandling doubleHitHandling = DoubleHitHandling.Allow;
internal int maxHitCount = 3;
// add region
public Builder<T> AddRegion(T value)
{
regions.Add(new Region { value = value });
return this;
}
// region settings
public Builder<T> RegionWithProportion(float proportion)
{
if (regions.Count == 0)
throw new InvalidOperationException("No region to set proportion for");
regions.Last().proportion = proportion;
return this;
}
public Builder<T> RegionWithText(string text)
{
if (regions.Count == 0)
throw new InvalidOperationException("No region to set text for");
regions.Last().text = text;
return this;
}
public Builder<T> RegionWithTheme(RegionTheme theme)
{
if (regions.Count == 0)
throw new InvalidOperationException("No region to set theme for");
regions.Last().theme = theme;
return this;
}
// general settings
public Builder<T> WithDoubleHitHandling(DoubleHitHandling handling)
{
if (handling != DoubleHitHandling.Allow)
throw new NotImplementedException();
doubleHitHandling = handling;
return this;
}
public Builder<T> WithHitCount(int hitCount)
{
this.maxHitCount = hitCount;
return this;
}
}
private TaskCompletionSource? _minigameComplete;
private List<int>? _hits;
private float _armPosition = 0f;
private float _armSpeed = 1f;
private List<float>? _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<List<T>> Run<T>(Builder<T> 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<T>(Builder<T> builder)
{
_minigameComplete = new();
_hits = [];
_armPosition = 0f;
_armSpeed = 1f;
_regions = [];
SetupRegions(builder);
_maxHitCount = builder.maxHitCount;
}
private void SetupRegions<T>(Builder<T> 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<RegionVisual>();
_regionsParent.AddChild(regionVisual);
var normalisedAngleStart = regionSum / totalRegionProportion;
var normalisedAngleEnd = (regionSum + region.proportion) / totalRegionProportion;
var normalAngles = new Vector2(normalisedAngleStart, normalisedAngleEnd);
regionVisual.Setup(normalAngles, _baseRegionColor.RandomHue(), region.text, region.theme);
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());
}
}