628 lines
22 KiB
C#
628 lines
22 KiB
C#
using System.Collections.Generic;
|
|
using BumpCombat.Audio;
|
|
using BumpCombat.Combat;
|
|
using BumpCombat.Core;
|
|
using BumpCombat.Player;
|
|
using BumpCombat.Spawning;
|
|
using BumpCombat.UI;
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using UnityEngine.UI;
|
|
|
|
namespace BumpCombat.Progression
|
|
{
|
|
public sealed class ArtifactRewardController : MonoBehaviour
|
|
{
|
|
private enum RewardSource
|
|
{
|
|
Elite,
|
|
EliteRed,
|
|
EliteBlue,
|
|
MidBoss,
|
|
}
|
|
|
|
private const int RewardOptionCount = 2;
|
|
private const int DebugOptionCount = 6;
|
|
private const int StartupColorCount = 3;
|
|
private const float OptionCardWidth = 380f;
|
|
private const float OptionCardHeight = 128f;
|
|
private const float OptionLabelHeight = 64f;
|
|
|
|
private readonly Queue<RewardSource> pendingRewards = new();
|
|
private readonly HashSet<RewardSource> queuedSources = new();
|
|
private readonly ActiveArtifactDefinition[] shownArtifacts =
|
|
new ActiveArtifactDefinition[DebugOptionCount];
|
|
private ActiveArtifactController artifacts;
|
|
private SpawnDirector spawnDirector;
|
|
private GameObject panel;
|
|
private Text titleText;
|
|
private Text[] optionTexts;
|
|
private Image[] optionCardImages = System.Array.Empty<Image>();
|
|
private Image[] optionIconImages = System.Array.Empty<Image>();
|
|
private int shownCount;
|
|
private int selectedIndex;
|
|
private bool debugStartupSelection;
|
|
private int startupSelectionCount;
|
|
private bool debugStartupSelectionPending;
|
|
private int debugStartupSelectionEarliestFrame;
|
|
private int debugSelectionsRequired;
|
|
private int debugSelectionsRemaining;
|
|
private int ignoreInputThroughFrame;
|
|
|
|
#if UNITY_EDITOR
|
|
public static bool GrantCatalogForTests { get; set; }
|
|
#endif
|
|
|
|
private void Start()
|
|
{
|
|
artifacts = FindAnyObjectByType<ActiveArtifactController>();
|
|
spawnDirector = FindAnyObjectByType<SpawnDirector>();
|
|
if (artifacts == null || spawnDirector == null)
|
|
{
|
|
enabled = false;
|
|
return;
|
|
}
|
|
|
|
CreatePanel();
|
|
spawnDirector.OnEventEnemyDefeated += HandleEventEnemyDefeated;
|
|
if (RunManager.Instance != null)
|
|
{
|
|
RunManager.Instance.OnSelectionChanged += HandleSelectionChanged;
|
|
RunManager.Instance.OnRunStarted += HandleRunStarted;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
if (GrantCatalogForTests)
|
|
{
|
|
artifacts.DebugGrantCatalogArtifacts();
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
QueueStartupSelection(
|
|
RunManager.Instance?.StartupArtifactSelectionCount ?? 3);
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (spawnDirector != null)
|
|
{
|
|
spawnDirector.OnEventEnemyDefeated -= HandleEventEnemyDefeated;
|
|
}
|
|
if (RunManager.Instance != null)
|
|
{
|
|
RunManager.Instance.OnSelectionChanged -= HandleSelectionChanged;
|
|
RunManager.Instance.OnRunStarted -= HandleRunStarted;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (debugStartupSelectionPending
|
|
&& Time.frameCount >= debugStartupSelectionEarliestFrame
|
|
&& RunManager.Instance != null
|
|
&& !RunManager.Instance.IsTitleScreen
|
|
&& !RunManager.Instance.IsSelectionOpen
|
|
&& !RunManager.Instance.IsPaused
|
|
&& !RunManager.Instance.IsGameOver)
|
|
{
|
|
if (OpenStartupSelection())
|
|
{
|
|
debugStartupSelectionPending = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (panel == null
|
|
|| !panel.activeSelf
|
|
|| shownCount == 0
|
|
|| Time.frameCount <= ignoreInputThroughFrame
|
|
|| Keyboard.current == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Keyboard.current.leftArrowKey.wasPressedThisFrame
|
|
|| Keyboard.current.upArrowKey.wasPressedThisFrame)
|
|
{
|
|
selectedIndex = (selectedIndex + shownCount - 1) % shownCount;
|
|
BumpCombatAudioService.Instance?.PlayUi("ui_move");
|
|
UpdateSelectionVisual();
|
|
}
|
|
else if (Keyboard.current.rightArrowKey.wasPressedThisFrame
|
|
|| Keyboard.current.downArrowKey.wasPressedThisFrame)
|
|
{
|
|
selectedIndex = (selectedIndex + 1) % shownCount;
|
|
BumpCombatAudioService.Instance?.PlayUi("ui_move");
|
|
UpdateSelectionVisual();
|
|
}
|
|
else if (Keyboard.current.spaceKey.wasPressedThisFrame)
|
|
{
|
|
ChooseOption(selectedIndex);
|
|
}
|
|
}
|
|
|
|
private void HandleRunStarted()
|
|
{
|
|
QueueStartupSelection(
|
|
RunManager.Instance?.StartupArtifactSelectionCount ?? 3);
|
|
}
|
|
|
|
private void QueueStartupSelection(int selectionCount)
|
|
{
|
|
startupSelectionCount = Mathf.Max(0, selectionCount);
|
|
debugStartupSelectionPending = true;
|
|
debugStartupSelectionEarliestFrame = Time.frameCount + 1;
|
|
}
|
|
|
|
private bool ChooseOption(int optionIndex)
|
|
{
|
|
if (panel == null
|
|
|| !panel.activeSelf
|
|
|| optionIndex < 0
|
|
|| optionIndex >= shownCount
|
|
|| !artifacts.TryAddArtifact(shownArtifacts[optionIndex]))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
BumpCombatAudioService.Instance?.PlayUi("ui_confirm");
|
|
if (artifacts.OwnedArtifactCount == 1)
|
|
{
|
|
GetComponent<RunTutorialController>()?.NotifyArtifactAcquired();
|
|
}
|
|
|
|
if (debugStartupSelection)
|
|
{
|
|
debugSelectionsRemaining--;
|
|
if (debugSelectionsRemaining > 0)
|
|
{
|
|
// Startup selection is deliberately color ordered. Each
|
|
// step exposes only the two definitions in the next color
|
|
// group, so a failed duplicate-color click cannot consume
|
|
// a slot or leave an invalid loadout.
|
|
if (!PopulateStartupOptions())
|
|
{
|
|
debugStartupSelection = false;
|
|
CloseSelection();
|
|
return true;
|
|
}
|
|
|
|
selectedIndex = 0;
|
|
UpdateDebugTitle();
|
|
ShowOptions();
|
|
return true;
|
|
}
|
|
|
|
debugStartupSelection = false;
|
|
}
|
|
|
|
CloseSelection();
|
|
return true;
|
|
}
|
|
|
|
private bool OpenStartupSelection()
|
|
{
|
|
if (RunManager.Instance == null
|
|
|| !RunManager.Instance.TryOpenSelection(this))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
debugSelectionsRequired = Mathf.Min(
|
|
startupSelectionCount,
|
|
artifacts.MaxOwnedArtifacts - artifacts.OwnedArtifactCount);
|
|
if (debugSelectionsRequired <= 0)
|
|
{
|
|
RunManager.Instance.CloseSelection(this);
|
|
return true;
|
|
}
|
|
|
|
debugStartupSelection = true;
|
|
debugSelectionsRemaining = debugSelectionsRequired;
|
|
if (!PopulateStartupOptions())
|
|
{
|
|
debugStartupSelection = false;
|
|
RunManager.Instance.CloseSelection(this);
|
|
return true;
|
|
}
|
|
|
|
selectedIndex = 0;
|
|
ShowOptions();
|
|
UpdateDebugTitle();
|
|
return true;
|
|
}
|
|
|
|
private bool PopulateStartupOptions()
|
|
{
|
|
shownCount = 0;
|
|
int selectedCount = debugSelectionsRequired - debugSelectionsRemaining;
|
|
if (selectedCount < 0 || selectedCount >= StartupColorCount)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ArtifactColor requiredColor = GetStartupColor(selectedCount);
|
|
for (int i = 0;
|
|
i < artifacts.CatalogCount && shownCount < DebugOptionCount;
|
|
i++)
|
|
{
|
|
ActiveArtifactDefinition definition =
|
|
artifacts.GetCatalogArtifactAt(i);
|
|
if (definition == null
|
|
|| definition.ArtifactColor != requiredColor
|
|
|| artifacts.OwnsArtifact(definition))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
shownArtifacts[shownCount++] = definition;
|
|
}
|
|
|
|
return shownCount > 0;
|
|
}
|
|
|
|
private static ArtifactColor GetStartupColor(int selectionIndex)
|
|
{
|
|
return selectionIndex switch
|
|
{
|
|
0 => ArtifactColor.Green,
|
|
1 => ArtifactColor.Red,
|
|
_ => ArtifactColor.Blue,
|
|
};
|
|
}
|
|
|
|
private static string GetArtifactColorName(ArtifactColor color)
|
|
{
|
|
return color switch
|
|
{
|
|
ArtifactColor.Green => "GREEN",
|
|
ArtifactColor.Red => "RED",
|
|
_ => "BLUE",
|
|
};
|
|
}
|
|
|
|
private void HandleEventEnemyDefeated(RunTimedEvent timedEvent)
|
|
{
|
|
if (timedEvent == RunTimedEvent.Elite)
|
|
{
|
|
if (RunManager.Instance?.IsProductionRun == true)
|
|
{
|
|
int spawnedEliteCount =
|
|
spawnDirector?.TimedEliteSpawnCount ?? 0;
|
|
if (spawnedEliteCount == 2)
|
|
{
|
|
QueueReward(RewardSource.EliteRed);
|
|
}
|
|
else if (spawnedEliteCount == 3)
|
|
{
|
|
QueueReward(RewardSource.EliteBlue);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QueueReward(RewardSource.Elite);
|
|
}
|
|
}
|
|
else if (timedEvent == RunTimedEvent.MidBoss
|
|
&& RunManager.Instance?.IsProductionRun != true)
|
|
{
|
|
QueueReward(RewardSource.MidBoss);
|
|
}
|
|
}
|
|
|
|
private void QueueReward(RewardSource source)
|
|
{
|
|
if (artifacts == null
|
|
|| artifacts.OwnedArtifactCount >= artifacts.MaxOwnedArtifacts)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!queuedSources.Add(source))
|
|
{
|
|
return;
|
|
}
|
|
|
|
pendingRewards.Enqueue(source);
|
|
TryOpenPendingReward();
|
|
}
|
|
|
|
private void TryOpenPendingReward()
|
|
{
|
|
if (pendingRewards.Count == 0 || artifacts == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (artifacts.OwnedArtifactCount >= artifacts.MaxOwnedArtifacts)
|
|
{
|
|
pendingRewards.Clear();
|
|
return;
|
|
}
|
|
|
|
if (RunManager.Instance == null
|
|
|| !RunManager.Instance.TryOpenSelection(this))
|
|
{
|
|
return;
|
|
}
|
|
|
|
RewardSource source = pendingRewards.Peek();
|
|
bool hasRequiredColor = TryGetRewardColor(
|
|
source,
|
|
out ArtifactColor requiredColor);
|
|
List<ActiveArtifactDefinition> candidates = new();
|
|
for (int i = 0; i < artifacts.CatalogCount; i++)
|
|
{
|
|
ActiveArtifactDefinition definition =
|
|
artifacts.GetCatalogArtifactAt(i);
|
|
if (definition != null
|
|
&& !artifacts.OwnsArtifact(definition)
|
|
&& (!hasRequiredColor
|
|
|| definition.ArtifactColor == requiredColor))
|
|
{
|
|
candidates.Add(definition);
|
|
}
|
|
}
|
|
|
|
shownCount = Mathf.Min(RewardOptionCount, candidates.Count);
|
|
if (shownCount == 0)
|
|
{
|
|
pendingRewards.Clear();
|
|
RunManager.Instance.CloseSelection(this);
|
|
return;
|
|
}
|
|
|
|
pendingRewards.Dequeue();
|
|
debugStartupSelection = false;
|
|
for (int i = 0; i < shownCount; i++)
|
|
{
|
|
int choiceIndex = Random.Range(0, candidates.Count);
|
|
shownArtifacts[i] = candidates[choiceIndex];
|
|
candidates.RemoveAt(choiceIndex);
|
|
}
|
|
|
|
titleText.text = source switch
|
|
{
|
|
RewardSource.Elite => "ELITE REWARD - CHOOSE ARTIFACT",
|
|
RewardSource.EliteRed => "ELITE REWARD - CHOOSE RED ARTIFACT",
|
|
RewardSource.EliteBlue => "ELITE REWARD - CHOOSE BLUE ARTIFACT",
|
|
RewardSource.MidBoss => "MID BOSS REWARD - CHOOSE ARTIFACT",
|
|
_ => "CHOOSE ARTIFACT",
|
|
};
|
|
selectedIndex = 0;
|
|
ShowOptions();
|
|
}
|
|
|
|
private void HandleSelectionChanged(bool isOpen)
|
|
{
|
|
if (!isOpen && (panel == null || !panel.activeSelf))
|
|
{
|
|
TryOpenPendingReward();
|
|
}
|
|
}
|
|
|
|
private void UpdateSelectionVisual()
|
|
{
|
|
for (int i = 0; i < optionTexts.Length; i++)
|
|
{
|
|
bool ownedDebugOption = debugStartupSelection
|
|
&& i < shownCount
|
|
&& artifacts.OwnsArtifact(shownArtifacts[i]);
|
|
bool selected = i == selectedIndex && i < shownCount;
|
|
optionTexts[i].color = ownedDebugOption
|
|
? new Color(0.35f, 1f, 0.55f)
|
|
: selected
|
|
? new Color(1f, 0.85f, 0.2f)
|
|
: Color.white;
|
|
if (i < optionCardImages.Length)
|
|
{
|
|
PresentationUiStyle.ApplyCard(
|
|
optionCardImages[i],
|
|
selected || ownedDebugOption);
|
|
optionCardImages[i].gameObject.SetActive(
|
|
optionTexts[i].gameObject.activeSelf);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ShowOptions()
|
|
{
|
|
RefreshOptionTexts();
|
|
LayoutOptions();
|
|
panel.SetActive(true);
|
|
ignoreInputThroughFrame = Time.frameCount;
|
|
UpdateSelectionVisual();
|
|
}
|
|
|
|
private void RefreshOptionTexts()
|
|
{
|
|
for (int i = 0; i < optionTexts.Length; i++)
|
|
{
|
|
bool visible = i < shownCount;
|
|
optionTexts[i].gameObject.SetActive(visible);
|
|
if (!visible)
|
|
{
|
|
if (i < optionIconImages.Length)
|
|
{
|
|
optionIconImages[i].gameObject.SetActive(false);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
ActiveArtifactDefinition definition = shownArtifacts[i];
|
|
Sprite icon = PresentationUiStyle.GetArtifactIcon(definition);
|
|
optionIconImages[i].sprite = icon;
|
|
optionIconImages[i].enabled = icon != null;
|
|
optionIconImages[i].gameObject.SetActive(icon != null);
|
|
optionTexts[i].text = icon != null
|
|
? definition.DisplayName
|
|
: $"{definition.PlaceholderSymbol}\n{definition.DisplayName}";
|
|
if (debugStartupSelection && artifacts.OwnsArtifact(definition))
|
|
{
|
|
optionTexts[i].text += "\nSELECTED";
|
|
}
|
|
}
|
|
}
|
|
|
|
private void LayoutOptions()
|
|
{
|
|
bool twoRows = shownCount > RewardOptionCount;
|
|
for (int i = 0; i < shownCount; i++)
|
|
{
|
|
int row = twoRows ? i / RewardOptionCount : 0;
|
|
int column = twoRows ? i % RewardOptionCount : i;
|
|
int rowStart = row * RewardOptionCount;
|
|
int rowCount = Mathf.Min(
|
|
RewardOptionCount,
|
|
shownCount - rowStart);
|
|
Vector2 cardPosition = new Vector2(
|
|
(column - (rowCount - 1) * 0.5f) * 430f,
|
|
twoRows ? 90f - row * 190f : 0f);
|
|
if (i < optionCardImages.Length)
|
|
{
|
|
optionCardImages[i].rectTransform.anchoredPosition =
|
|
cardPosition;
|
|
}
|
|
optionTexts[i].rectTransform.anchoredPosition =
|
|
cardPosition + Vector2.down * 25f;
|
|
if (i < optionIconImages.Length)
|
|
{
|
|
optionIconImages[i].rectTransform.anchoredPosition =
|
|
cardPosition + Vector2.up * 20f;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateDebugTitle()
|
|
{
|
|
int selectedCount =
|
|
debugSelectionsRequired - debugSelectionsRemaining;
|
|
if (startupSelectionCount <= 1)
|
|
{
|
|
titleText.text = "CHOOSE GREEN ARTIFACT "
|
|
+ $"({selectedCount}/{debugSelectionsRequired})";
|
|
return;
|
|
}
|
|
|
|
ArtifactColor requiredColor = GetStartupColor(
|
|
Mathf.Clamp(selectedCount, 0, StartupColorCount - 1));
|
|
titleText.text =
|
|
$"CHOOSE {GetArtifactColorName(requiredColor)} ARTIFACT "
|
|
+ $"({selectedCount}/{debugSelectionsRequired})";
|
|
}
|
|
|
|
private static bool TryGetRewardColor(
|
|
RewardSource source,
|
|
out ArtifactColor color)
|
|
{
|
|
switch (source)
|
|
{
|
|
case RewardSource.EliteRed:
|
|
color = ArtifactColor.Red;
|
|
return true;
|
|
case RewardSource.EliteBlue:
|
|
color = ArtifactColor.Blue;
|
|
return true;
|
|
default:
|
|
color = ArtifactColor.Green;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void CloseSelection()
|
|
{
|
|
panel.SetActive(false);
|
|
RunManager.Instance?.CloseSelection(this);
|
|
}
|
|
|
|
private void CreatePanel()
|
|
{
|
|
Font font = PresentationUiStyle.GetGalmuriFont();
|
|
panel = PresentationUiStyle.CreatePanel(
|
|
transform,
|
|
"Artifact Reward Selection",
|
|
new Vector2(0.5f, 0.5f),
|
|
Vector2.zero,
|
|
new Vector2(1500f, 650f),
|
|
new Color(0.03f, 0.04f, 0.08f, 0.96f));
|
|
PresentationUiStyle.ApplyPanel(panel.GetComponent<Image>());
|
|
|
|
titleText = PresentationUiStyle.CreateText(
|
|
panel.transform,
|
|
"Title",
|
|
font,
|
|
38,
|
|
TextAnchor.MiddleCenter,
|
|
new Vector2(0f, 255f),
|
|
new Vector2(1200f, 60f));
|
|
optionTexts = new Text[DebugOptionCount];
|
|
optionCardImages = new Image[DebugOptionCount];
|
|
optionIconImages = new Image[DebugOptionCount];
|
|
for (int i = 0; i < optionTexts.Length; i++)
|
|
{
|
|
optionTexts[i] = PresentationUiStyle.CreateText(
|
|
panel.transform,
|
|
$"Option {i + 1}",
|
|
font,
|
|
26,
|
|
TextAnchor.MiddleCenter,
|
|
Vector2.zero,
|
|
new Vector2(OptionCardWidth, OptionLabelHeight));
|
|
optionCardImages[i] = PresentationUiStyle.CreateCardBackdrop(
|
|
optionTexts[i],
|
|
$"{optionTexts[i].name} Card",
|
|
new Vector2(OptionCardWidth, OptionCardHeight));
|
|
RectTransform optionRect = optionTexts[i].rectTransform;
|
|
optionIconImages[i] = PresentationUiStyle.CreateImage(
|
|
optionTexts[i].transform.parent,
|
|
$"{optionTexts[i].name} Icon",
|
|
Vector2.zero,
|
|
new Vector2(48f, 48f));
|
|
optionIconImages[i].rectTransform.anchorMin = optionRect.anchorMin;
|
|
optionIconImages[i].rectTransform.anchorMax = optionRect.anchorMax;
|
|
optionIconImages[i].rectTransform.pivot = optionRect.pivot;
|
|
optionIconImages[i].rectTransform.anchoredPosition =
|
|
optionRect.anchoredPosition + Vector2.up * 20f;
|
|
optionIconImages[i].transform.SetSiblingIndex(
|
|
optionTexts[i].transform.GetSiblingIndex());
|
|
optionIconImages[i].enabled = false;
|
|
}
|
|
|
|
Text help = PresentationUiStyle.CreateText(
|
|
panel.transform,
|
|
"Help",
|
|
font,
|
|
22,
|
|
TextAnchor.MiddleCenter,
|
|
new Vector2(0f, -275f),
|
|
new Vector2(900f, 40f));
|
|
help.text = "Arrow keys: select Space: confirm";
|
|
PresentationUiStyle.ApplyText(help, 22);
|
|
panel.SetActive(false);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
public int DebugShownCount => shownCount;
|
|
public bool DebugIsSelectionVisible => panel != null && panel.activeSelf;
|
|
|
|
public ActiveArtifactDefinition DebugGetShownArtifact(int index)
|
|
{
|
|
return index >= 0 && index < shownCount
|
|
? shownArtifacts[index]
|
|
: null;
|
|
}
|
|
|
|
public bool DebugChooseOption(int optionIndex)
|
|
{
|
|
return ChooseOption(optionIndex);
|
|
}
|
|
|
|
public void DebugQueueRedArtifactReward()
|
|
{
|
|
QueueReward(RewardSource.EliteRed);
|
|
}
|
|
#endif
|
|
}
|
|
}
|