cirnogodot/Scripts/Actors/3D/VendingMachine3D.cs

222 lines
No EOL
5.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Cirno.Scripts.Interactables;
using Cirno.Scripts.Resources;
using Cirno.Scripts.UI;
using Cirno.Scripts.Utils;
using Godot;
namespace Cirno.Scripts.Actors._3D;
[Tool]
public partial class VendingMachine3D : Interactable3D
{
public sealed class RuntimeShopEntry
{
public RuntimeShopEntry(VendingShopEntry entry)
{
Entry = entry;
RemainingQuantity = Math.Max(0, entry.StartingQuantity);
}
public VendingShopEntry Entry { get; }
public LootItem Item => Entry.Item;
public bool Unlimited => Entry.Unlimited;
public int RemainingQuantity { get; private set; }
public bool CanPurchase(int purchaseCount = 1)
{
return purchaseCount <= 0 || Unlimited || RemainingQuantity >= purchaseCount;
}
public bool TryConsume(int purchaseCount = 1)
{
if (purchaseCount <= 0 || Unlimited)
{
return true;
}
if (RemainingQuantity < purchaseCount)
{
return false;
}
RemainingQuantity -= purchaseCount;
return true;
}
}
[Signal]
public delegate void ShopOpenedEventHandler();
[Signal]
public delegate void ShopClosedEventHandler();
[Export]
public PackedScene ShopUiScene { get; private set; } =
ResourceLoader.Load<PackedScene>("res://Scenes/HUD/VendingMachineShopUi.tscn");
[Export]
public VendingShopDefinition ShopDefinition { get; private set; } =
ResourceLoader.Load<VendingShopDefinition>("res://Resources/Shops/VendingMachine_Default.tres");
private readonly List<RuntimeShopEntry> _runtimeEntries = [];
private VendingMachineShopUi _activeShopUi;
private bool _runtimeInitialized;
public override void _Ready()
{
AddToGroup("Interactable");
if (Engine.IsEditorHint())
{
return;
}
EnsureRuntimeEntries();
}
public override bool CanActivate()
{
if (Engine.IsEditorHint())
{
return false;
}
EnsureRuntimeEntries();
return !IsShopOpen()
&& ShopUiScene != null
&& _runtimeEntries.Count > 0
&& GameStateManager.Instance?.GameState is not GameState.Shop;
}
public override bool Activate(ActivationType activationType = ActivationType.Toggle)
{
if (Engine.IsEditorHint())
{
return false;
}
var typeToUse = activationType is ActivationType.Toggle ? ActivationType.Use : activationType;
if (typeToUse is not ActivationType.Use)
{
return false;
}
if (!MeetsRequirements() || !CanActivate())
{
return false;
}
EnsureRuntimeEntries();
if (GameStateManager.Instance == null)
{
return false;
}
GameStateManager.Instance.ChangeState(GameState.Shop);
_activeShopUi = ShopUiScene.Instantiate<VendingMachineShopUi>();
_activeShopUi.Init(this);
_activeShopUi.Closed += OnShopUiClosed;
var parent = GetTree().CurrentScene ?? GetTree().Root;
parent.AddChild(_activeShopUi);
EmitSignal(SignalName.ShopOpened);
return true;
}
public override void _ExitTree()
{
if (IsInstanceValid(_activeShopUi))
{
_activeShopUi.Closed -= OnShopUiClosed;
_activeShopUi.QueueFree();
}
_activeShopUi = null;
}
public IReadOnlyList<RuntimeShopEntry> GetEntries()
{
EnsureRuntimeEntries();
return _runtimeEntries;
}
public RuntimeShopEntry FindEntry(StringName itemKey)
{
EnsureRuntimeEntries();
return _runtimeEntries.FirstOrDefault(entry => entry.Item?.ItemKey == itemKey);
}
public bool TryConsumeStock(StringName itemKey, int purchaseCount = 1)
{
var entry = FindEntry(itemKey);
return entry?.TryConsume(purchaseCount) ?? false;
}
// public void _func_godot_apply_properties(Godot.Collections.Dictionary props)
// {
// if (props.TryGetValue("shop_definition_path", out var shopDefinitionPath))
// {
// var path = shopDefinitionPath.AsString();
// if (!string.IsNullOrWhiteSpace(path))
// {
// var loadedDefinition = ResourceLoader.Load<VendingShopDefinition>(path);
// if (loadedDefinition != null)
// {
// ShopDefinition = loadedDefinition;
// _runtimeInitialized = false;
// }
// else
// {
// GD.PrintErr($"Failed to load vending shop definition from '{path}'");
// }
// }
// }
// }
private void EnsureRuntimeEntries()
{
if (_runtimeInitialized)
{
return;
}
_runtimeInitialized = true;
_runtimeEntries.Clear();
if (ShopDefinition == null)
{
return;
}
foreach (var entry in ShopDefinition.Entries.Where(entry => entry?.Item != null))
{
_runtimeEntries.Add(new RuntimeShopEntry(entry));
}
}
private bool IsShopOpen()
{
return IsInstanceValid(_activeShopUi);
}
private void OnShopUiClosed()
{
if (IsInstanceValid(_activeShopUi))
{
_activeShopUi.Closed -= OnShopUiClosed;
}
_activeShopUi = null;
EmitSignal(SignalName.ShopClosed);
}
}