diff --git a/zzio/InventoryCard.cs b/zzio/InventoryCard.cs index 3a937ea3..0589a0cc 100644 --- a/zzio/InventoryCard.cs +++ b/zzio/InventoryCard.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; namespace zzio; @@ -155,6 +156,26 @@ public IEnumerator GetEnumerator() if (class2 != ZZClass.None) yield return class2; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public bool IsCompatible(zzio.db.SpellRow spell) + { + var zzclasses = Enum.GetValues().Where(s => s != ZZClass.None); + List skills = [class0, class1, class2]; + List reqs = [spell.PriceA, spell.PriceB, spell.PriceC]; + + var neutralsNeeded = 0; + foreach (var zzclass in zzclasses) + { + var skillHad = skills.Where(s => s == zzclass).Count(); + var skillNeeded = reqs.Where(s => s == zzclass).Count(); + var skillDeficit = skillNeeded - skillHad; + if (skillDeficit > 0) + neutralsNeeded += skillDeficit; + } + if (skills.Where(s => s == ZZClass.Neutral).Count() - neutralsNeeded < 0) + return false; + return true; + } } public enum ZZPermSpellStatus diff --git a/zzre.core/math/WorldCollider.cs b/zzre.core/math/WorldCollider.cs index 54036f32..a794aaa3 100644 --- a/zzre.core/math/WorldCollider.cs +++ b/zzre.core/math/WorldCollider.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using System.Collections.Generic; using System.Linq; using zzio.rwbs; @@ -94,7 +94,7 @@ public static WorldCollider Create(RWWorld world) sectorType = RWPlaneSectionType.XPlane, leftValue = float.PositiveInfinity, rightValue = float.PositiveInfinity, - children = [rootAtomic, new RWString()] + children = [rootAtomic!, new RWString()] }; var splits = new CollisionSplit[splitCount]; diff --git a/zzre/game/Game.cs b/zzre/game/Game.cs index e938cea1..8348c4aa 100644 --- a/zzre/game/Game.cs +++ b/zzre/game/Game.cs @@ -128,6 +128,7 @@ protected void LoadScene(string sceneName) clearColor = (scene.misc.clearColor.ToFColor() with { a = 1f }).ToVeldrid(); ecsWorld.Publish(new messages.SceneLoaded(scene, GetTag())); + ui.World.Publish(new messages.SceneLoaded(scene, GetTag())); } private void DisposeUnusedAssets(in messages.SceneLoaded _) diff --git a/zzre/game/UI.cs b/zzre/game/UI.cs index 533cefa1..47b5abe8 100644 --- a/zzre/game/UI.cs +++ b/zzre/game/UI.cs @@ -81,6 +81,7 @@ public UI(ITagContainer diContainer) new systems.ui.Tooltip(this), new systems.ui.CorrectRenderOrder(this), new systems.ui.Fade(this), + new systems.ui.UIScript(this), new systems.Reaper(this), new systems.ParentReaper(this)); diff --git a/zzre/game/Zanzarah.cs b/zzre/game/Zanzarah.cs index 986ce00a..0091d673 100644 --- a/zzre/game/Zanzarah.cs +++ b/zzre/game/Zanzarah.cs @@ -24,6 +24,7 @@ public interface IZanzarahContainer event Action OnMouseDown; event Action OnMouseUp; event Action OnMouseMove; + event Action OnScroll; } public class Zanzarah : ITagContainer diff --git a/zzre/game/components/ui/DraggedCard.cs b/zzre/game/components/ui/DraggedCard.cs new file mode 100644 index 00000000..9166a59f --- /dev/null +++ b/zzre/game/components/ui/DraggedCard.cs @@ -0,0 +1,5 @@ +using zzio; + +namespace zzre.game.components.ui; + +public record struct DraggedCard(InventoryCard card); diff --git a/zzre/game/components/ui/ScrBookMenu.cs b/zzre/game/components/ui/ScrBookMenu.cs index e2db6d45..a7315456 100644 --- a/zzre/game/components/ui/ScrBookMenu.cs +++ b/zzre/game/components/ui/ScrBookMenu.cs @@ -1,13 +1,10 @@ -using System.Collections.Generic; using zzio.db; namespace zzre.game.components.ui; public struct ScrBookMenu { - public Inventory Inventory; public FairyRow[] Fairies; - public Dictionary FairyButtons; public DefaultEcs.Entity Sidebar; public DefaultEcs.Entity Crosshair; } diff --git a/zzre/game/components/ui/ScrDeck.cs b/zzre/game/components/ui/ScrDeck.cs index 9e9e20ea..6ea5117c 100644 --- a/zzre/game/components/ui/ScrDeck.cs +++ b/zzre/game/components/ui/ScrDeck.cs @@ -1,4 +1,6 @@ -namespace zzre.game.components.ui; +using zzio; + +namespace zzre.game.components.ui; public struct ScrDeck { @@ -14,14 +16,23 @@ public enum Tab public Tab ActiveTab; public bool IsGridMode; public int Scroll; - public Inventory Inventory; public DefaultEcs.Entity SummaryBackground; public DefaultEcs.Entity SpellBackground; + + public DefaultEcs.Entity[] TabButtons; public DefaultEcs.Entity ListSlider; - public DefaultEcs.Entity FairyHoverSummary; - public DefaultEcs.Entity[] ListTabs; - public DefaultEcs.Entity[] ListButtons; - public DefaultEcs.Entity[] ListUsedMarkers; - public DefaultEcs.Entity[] ListSummaries; - public DefaultEcs.Entity[] DeckSlotParents; + + public DefaultEcs.Entity[] DeckSlots; + public DefaultEcs.Entity[] ListSlots; + + public DefaultEcs.Entity LastHovered; + public DefaultEcs.Entity StatsTitle; + public DefaultEcs.Entity StatsDescriptions; + public DefaultEcs.Entity StatsLights; + public DefaultEcs.Entity StatsLevel; + + public int VacatedDeckSlot; + public InventoryCard? DraggedCard; + public DefaultEcs.Entity DraggedCardImage; + public DefaultEcs.Entity DraggedOverlay; } diff --git a/zzre/game/components/ui/Slot.cs b/zzre/game/components/ui/Slot.cs new file mode 100644 index 00000000..5a641f61 --- /dev/null +++ b/zzre/game/components/ui/Slot.cs @@ -0,0 +1,18 @@ +using zzio; + +namespace zzre.game.components.ui; + +public record struct Slot +{ + public enum Type { None, DeckSlot, ListSlot, SpellSlot } + public Type type; + public int index; + public InventoryCard? card; + public DefaultEcs.Entity button; + public DefaultEcs.Entity usedMarker; // ListSlot only + public DefaultEcs.Entity[] spellImages; // ListSlot only + public bool showSpells; // ListSlot only + public DefaultEcs.Entity summary; + public DefaultEcs.Entity req; // SpellSlot only + public DefaultEcs.Entity[] spellSlots; // DeckSlot only +}; diff --git a/zzre/game/components/ui/SlotButton.cs b/zzre/game/components/ui/SlotButton.cs new file mode 100644 index 00000000..f50f9a7f --- /dev/null +++ b/zzre/game/components/ui/SlotButton.cs @@ -0,0 +1,3 @@ +namespace zzre.game.components.ui; + +public record struct SlotButton; diff --git a/zzre/game/components/ui/UIScript.cs b/zzre/game/components/ui/UIScript.cs new file mode 100644 index 00000000..0172e6b1 --- /dev/null +++ b/zzre/game/components/ui/UIScript.cs @@ -0,0 +1,6 @@ +namespace zzre.game.components.ui; + +public record struct UIScript( + DefaultEcs.Entity DeckSlotEntity, + bool ItemConsumed +); diff --git a/zzre/game/messages/ui/ExecuteUIScript.cs b/zzre/game/messages/ui/ExecuteUIScript.cs new file mode 100644 index 00000000..4a065437 --- /dev/null +++ b/zzre/game/messages/ui/ExecuteUIScript.cs @@ -0,0 +1,6 @@ +namespace zzre.game.messages.ui; + +public readonly record struct ExecuteUIScript( + zzio.InventoryItem Item, + DefaultEcs.Entity DeckSlotEntity +); diff --git a/zzre/game/messages/ui/UIScriptFinished.cs b/zzre/game/messages/ui/UIScriptFinished.cs new file mode 100644 index 00000000..d3417040 --- /dev/null +++ b/zzre/game/messages/ui/UIScriptFinished.cs @@ -0,0 +1,6 @@ +namespace zzre.game.messages.ui; + +public readonly record struct UIScriptFinished( + DefaultEcs.Entity DeckSlotEntity, + bool ItemConsumed +); diff --git a/zzre/game/systems/player/OpenMenuKeys.cs b/zzre/game/systems/player/OpenMenuKeys.cs index 9dac5711..4918cdd9 100644 --- a/zzre/game/systems/player/OpenMenuKeys.cs +++ b/zzre/game/systems/player/OpenMenuKeys.cs @@ -18,10 +18,12 @@ public class OpenMenuKeys : ISystem private readonly IZanzarahContainer zzContainer; private readonly PlayerControls playerControls; private readonly UI ui; + private readonly Zanzarah zanzarah; public OpenMenuKeys(ITagContainer diContainer) { ui = diContainer.GetTag(); + zanzarah = diContainer.GetTag(); playerControls = diContainer.GetTag(); zzContainer = diContainer.GetTag(); zzContainer.OnKeyDown += HandleKeyDown; @@ -34,15 +36,28 @@ public void Dispose() private void HandleKeyDown(KeyCode code) { + var inventory = zanzarah.CurrentGame!.PlayerEntity.Get(); if (!IsEnabled || playerControls.IsLocked) return; switch (code) { - case MenuKey: ui.Publish(); break; - case RuneMenuKey: ui.Publish(); break; - case BookMenuKey: ui.Publish(); break; - case MapMenuKey: ui.Publish(); break; - case DeckMenuKey: ui.Publish(); break; + case RuneMenuKey: + if (inventory.Contains(StdItemId.RuneFairyGarden)) + ui.Publish(); + break; + case BookMenuKey: + if (inventory.Contains(StdItemId.FairyBook)) + ui.Publish(); + break; + case MapMenuKey: + if (inventory.Contains(StdItemId.MapFairyGarden)) + ui.Publish(); + break; + case MenuKey: + case DeckMenuKey: + if (inventory.Contains(StdItemId.FairyBag)) + ui.Publish(); + break; } } diff --git a/zzre/game/systems/ui/BaseScreen.cs b/zzre/game/systems/ui/BaseScreen.cs index d8e41e91..a84ea460 100644 --- a/zzre/game/systems/ui/BaseScreen.cs +++ b/zzre/game/systems/ui/BaseScreen.cs @@ -24,6 +24,7 @@ protected enum BlockFlags private readonly IDisposable removedSubscription; protected readonly IZanzarahContainer zzContainer; protected readonly Zanzarah zanzarah; + protected Inventory inventory => zanzarah.CurrentGame!.PlayerEntity.Get(); protected readonly UI ui; protected readonly DefaultEcs.World uiWorld;// necessary as Dialog UI is owned by the game and not by the UI. // Probably worth some cleanup but this would mean merging @@ -31,6 +32,7 @@ protected enum BlockFlags protected readonly UIBuilder preload; protected event Action? OnElementDown; protected event Action? OnElementUp; + protected event Action? OnRightClick; protected Vector2 ScreenCenter => ui.LogicalScreen.Center; @@ -62,6 +64,7 @@ protected virtual void HandleAdded(in DefaultEcs.Entity entity, in TComponent _) zzContainer.OnMouseDown += HandleMouseDown; zzContainer.OnMouseUp += HandleMouseUp; zzContainer.OnKeyDown += HandleKeyDown; + zzContainer.OnScroll += HandleScroll; if (zanzarah.CurrentGame != null && blockFlags.HasFlag(BlockFlags.DisableGameUpdate)) zanzarah.CurrentGame.IsUpdateEnabled = false; @@ -76,6 +79,7 @@ protected virtual void HandleRemoved(in DefaultEcs.Entity entity, in TComponent zzContainer.OnMouseDown -= HandleMouseDown; zzContainer.OnMouseUp -= HandleMouseUp; zzContainer.OnKeyDown -= HandleKeyDown; + zzContainer.OnScroll -= HandleScroll; if (zanzarah.CurrentGame != null && blockFlags.HasFlag(BlockFlags.DisableGameUpdate)) zanzarah.CurrentGame.IsUpdateEnabled = true; @@ -89,6 +93,12 @@ protected virtual void HandleRemoved(in DefaultEcs.Entity entity, in TComponent private void HandleMouseUp(MouseButton button, Vector2 pos) => HandleMouse(button, pos, isDown: false); private void HandleMouse(MouseButton button, Vector2 pos, bool isDown) { + if (button == MouseButton.Right) + { + if (isDown) + OnRightClick?.Invoke(); + return; + } if (button != MouseButton.Left) return; @@ -105,10 +115,12 @@ private void HandleMouse(MouseButton button, Vector2 pos, bool isDown) protected virtual void HandleKeyDown(KeyCode key) { } - + protected virtual void HandleScroll(float scrollAmount) + { + } protected abstract void HandleOpen(in TMessage message); [Update] protected virtual void Update(float timeElapsed, in DefaultEcs.Entity entity, ref TComponent component) { } -} \ No newline at end of file +} diff --git a/zzre/game/systems/ui/InGameScreen.cs b/zzre/game/systems/ui/InGameScreen.cs index 44703d5c..7eb5e5db 100644 --- a/zzre/game/systems/ui/InGameScreen.cs +++ b/zzre/game/systems/ui/InGameScreen.cs @@ -1,5 +1,8 @@ -using System.Numerics; +using System.Collections.Generic; +using System.Numerics; +using System.Linq; using zzio; +using KeyCode = Silk.NET.SDL.KeyCode; namespace zzre.game.systems.ui; @@ -14,13 +17,13 @@ public static class InGameScreen public static readonly components.ui.ElementId IDOpenMap = new(1004); public static readonly components.ui.ElementId IDSaveGame = new(1005); - private record struct TabInfo(components.ui.ElementId Id, int PosX, int TileI, UID TooltipUID, StdItemId Item); - private static readonly TabInfo[] Tabs = + private record struct TabInfo(components.ui.ElementId Id, int PosX, int TileI, UID TooltipUID, KeyCode Key, StdItemId Item); + private static readonly List Tabs = [ - new(IDOpenDeck, PosX: 553, TileI: 21, TooltipUID: new(0x6659B4A1), StdItemId.FairyBag), - new(IDOpenRunes, PosX: 427, TileI: 12, TooltipUID: new(0x6636B4A1), StdItemId.RuneFairyGarden), - new(IDOpenFairybook, PosX: 469, TileI: 15, TooltipUID: new(0x8D1BBCA1), StdItemId.FairyBook), - new(IDOpenMap, PosX: 511, TileI: 18, TooltipUID: new(0xC51E6991), StdItemId.MapFairyGarden) + new(IDOpenDeck, PosX: 553, TileI: 21, TooltipUID: new(0x6659B4A1), KeyCode.KF5, StdItemId.FairyBag), + new(IDOpenRunes, PosX: 427, TileI: 12, TooltipUID: new(0x6636B4A1), KeyCode.KF2, StdItemId.RuneFairyGarden), + new(IDOpenFairybook, PosX: 469, TileI: 15, TooltipUID: new(0x8D1BBCA1), KeyCode.KF3, StdItemId.FairyBook), + new(IDOpenMap, PosX: 511, TileI: 18, TooltipUID: new(0xC51E6991), KeyCode.KF4, StdItemId.MapFairyGarden) ]; public static void CreateTopButtons(UIBuilder preload, in DefaultEcs.Entity parent, Inventory inventory, components.ui.ElementId curTab) @@ -35,7 +38,7 @@ public static void CreateTopButtons(UIBuilder preload, in DefaultEcs.Entity pare preload.CreateButton(parent) .With(IDSaveGame) - .With(Mid + new Vector2(384, 3)) + .With(Mid + new Vector2(385, 3)) .With(new components.ui.ButtonTiles(26, 27)) .With(UIPreloadAsset.Btn002) .WithTooltip(0x7113B8A1) @@ -60,4 +63,60 @@ public static void CreateTopButtons(UIBuilder preload, in DefaultEcs.Entity pare } } } + + public static void HandleNavClick(components.ui.ElementId id, Zanzarah zanzarah, DefaultEcs.Entity uiEntity, components.ui.ElementId curTab) + { + var tab = Tabs.FirstOrDefault(tab => tab.Id == id); + if (tab.Id == curTab) + { + // Ignore current tab + return; + } + if (tab != default) + TryOpenTab(tab, zanzarah, uiEntity, curTab); + if (id == IDClose) + uiEntity.Dispose(); + } + + public static void HandleNavKeyDown(KeyCode key, Zanzarah zanzarah, DefaultEcs.Entity uiEntity, components.ui.ElementId curTab) + { + var tab = Tabs.FirstOrDefault(tab => tab.Key == key); + if (tab.Id == curTab) + { + // Exit current tab + uiEntity.Dispose(); + return; + } + if (tab != default) + TryOpenTab(tab, zanzarah, uiEntity, curTab); + if (key == KeyCode.KReturn || key == KeyCode.KEscape) + uiEntity.Dispose(); + } + + private static void TryOpenTab(TabInfo tab, Zanzarah zanzarah, DefaultEcs.Entity uiEntity, components.ui.ElementId curTab) + { + var inventory = zanzarah.CurrentGame!.PlayerEntity.Get(); + if (!inventory.Contains(tab.Item)) return; + switch (tab.Key) + { + case KeyCode.KF2: + uiEntity.Dispose(); + zanzarah.UI.Publish(); + break; + case KeyCode.KF3: + uiEntity.Dispose(); + zanzarah.UI.Publish(); + break; + case KeyCode.KF4: + uiEntity.Dispose(); + zanzarah.UI.Publish(); + break; + case KeyCode.KF5: + uiEntity.Dispose(); + zanzarah.UI.Publish(); + break; + default: + break; + } + } } diff --git a/zzre/game/systems/ui/ScrBookMenu.cs b/zzre/game/systems/ui/ScrBookMenu.cs index d074d595..de7fe4e8 100644 --- a/zzre/game/systems/ui/ScrBookMenu.cs +++ b/zzre/game/systems/ui/ScrBookMenu.cs @@ -19,7 +19,10 @@ public partial class ScrBookMenu : BaseScreen(); - if (!inventory.Contains(StdItemId.FairyBook)) - return; - - var entity = World.CreateEntity(); - entity.Set(); - ref var book = ref entity.Get(); - book.Inventory = inventory; - - preload.CreateFullBackOverlay(entity); - + World.Publish(new messages.SpawnSample($"resources/audio/sfx/gui/_g006.wav")); + var uiEntity = World.CreateEntity(); + uiEntity.Set(); + ref var book = ref uiEntity.Get(); book.Fairies = [.. db.Fairies.OrderBy(fairyRow => fairyRow.CardId.EntityId)]; - book.FairyButtons = []; book.Sidebar = default; book.Crosshair = default; - preload.CreateImage(entity) - .With(-new Vector2(320, 240)) + preload.CreateFullBackOverlay(uiEntity); + + // Draw Fairy Book background + preload.CreateImage(uiEntity) + .With(components.ui.FullAlignment.Center) .WithBitmap("col000") .WithRenderOrder(1) .Build(); - preload.CreateTooltipTarget(entity) - .With(new Vector2(-320 + 11, -240 + 11)) + preload.CreateTooltipTarget(uiEntity) + .With(Mid + new Vector2(11, 11)) .WithText("{205} - ") .Build(); - CreateTopButtons(preload, entity, inventory, IDOpenFairybook); - CreateFairyButtons(preload, entity, inventory, ref book); + CreateTopButtons(preload, uiEntity, inventory, IDOpenFairybook); + CreateFairyButtons(uiEntity, ref book); } - private static void CreateFairyButtons(UIBuilder preload, in DefaultEcs.Entity entity, Inventory inventory, ref components.ui.ScrBookMenu book) + private void CreateFairyButtons(in DefaultEcs.Entity entity, ref components.ui.ScrBookMenu book) { var fairies = book.Fairies; for (int i = 0; i < fairies.Length; i++) { if (inventory.Contains(fairies[i].CardId)) { - var element = new components.ui.ElementId(1 + i); + // Fairy icon var button = preload.CreateButton(entity) - .With(element) + .With(new components.ui.ElementId(i)) .With(Mid + FairyButtonPos(i)) .With(new components.ui.ButtonTiles(fairies[i].CardId.EntityId)) .With(UIPreloadAsset.Wiz000) .Build(); button.Set(button.Get().GrownBy(new Vector2(5, 5))); // No gaps button.Set(new components.ui.Silent()); - book.FairyButtons.Add(element, fairies[i]); // In the original engine, only the first fairy is checked for isInUse // This is an intentional bug fix - if (inventory.Fairies.Any(c => fairies[i].CardId == c.cardId && c.isInUse)) { + if (inventory.Fairies.Any(c => fairies[i].CardId == c.cardId && c.isInUse)) + { + // "Fairy is equipped" indicator preload.CreateImage(entity) .With(Mid + FairyButtonPos(i)) .With(UIPreloadAsset.Inf000, 16) @@ -91,24 +90,16 @@ private static void CreateFairyButtons(UIBuilder preload, in DefaultEcs.Entity e } } - private DefaultEcs.Entity CreateSidebar(UIBuilder preload, in DefaultEcs.Entity parent, FairyRow fairyRow, ref components.ui.ScrBookMenu book) + private DefaultEcs.Entity CreateSidebar(in DefaultEcs.Entity parent, ref components.ui.ScrBookMenu book, int fairyI) { var entity = World.CreateEntity(); entity.Set(new components.Parent(parent)); - var fairyI = Array.IndexOf(book.Fairies, fairyRow) + 1; - - var element = new components.ui.ElementId(0); - preload.CreateButton(entity) - .With(element) - .With(Mid + new Vector2(160, 218)) - .With(new components.ui.ButtonTiles(fairyRow.CardId.EntityId)) - .With(UIPreloadAsset.Wiz000) - .Build(); + var fairyRow = book.Fairies[fairyI]; preload.CreateLabel(entity) .With(Mid + new Vector2(21, 57)) - .WithText($"#{fairyI} {fairyRow.Name}") + .WithText($"#{fairyI + 1} {fairyRow.Name}") .With(UIPreloadAsset.Fnt000) .Build(); @@ -130,99 +121,70 @@ private DefaultEcs.Entity CreateSidebar(UIBuilder preload, in DefaultEcs.Entity .With(UIPreloadAsset.Fnt002) .Build(); - CreateStat(preload, entity, 0, Math.Min(500, fairyRow.MHP) / 100); - CreateStat(preload, entity, 1, fairyRow.MovSpeed + 1); - CreateStat(preload, entity, 2, fairyRow.JumpPower + 1); - CreateStat(preload, entity, 3, fairyRow.CriticalHit + 1); + preload.CreateImage(entity) + .With(Mid + new Vector2(160, 218)) + .With(UIPreloadAsset.Wiz000, fairyRow.CardId.EntityId) + .Build(); - const float MaxTextWidth = 190f; preload.CreateLabel(entity) - .With(Mid + new Vector2(21, 346)) - .WithText(fairyRow.Info) + .With(Mid + new Vector2(21, 271)) + .WithText(String.Join("\n", UIDStatNames.Select(uid => db.GetText(uid).Text))) + .WithLineHeight(17) .With(UIPreloadAsset.Fnt002) - .WithLineWrap(MaxTextWidth) .Build(); - return entity; - } - - private void CreateStat(UIBuilder preload, in DefaultEcs.Entity entity, int index, int value) - { preload.CreateLabel(entity) - .With(Mid + new Vector2(21, 271 + index*17)) - .WithText(db.GetText(UIDStatNames[index]).Text) - .With(UIPreloadAsset.Fnt002) + .With(Mid + new Vector2(111, 266)) + .WithText(StatsLights([ + Math.Min(500, fairyRow.MHP) / 100, + fairyRow.MovSpeed + 1, + fairyRow.JumpPower + 1, + fairyRow.CriticalHit + 1 + ])) + .WithLineHeight(17) + .With(UIPreloadAsset.Fnt001) .Build(); preload.CreateLabel(entity) - .With(Mid + new Vector2(111, 266 + index*17)) - .WithText(UIBuilder.GetLightsIndicator(value)) - .With(UIPreloadAsset.Fnt001) + .With(Mid + new Vector2(21, 346)) + .WithText(fairyRow.Info) + .With(UIPreloadAsset.Fnt002) + .WithLineWrap(190f) .Build(); - } - private static Vector2 FairyButtonPos(int fairyI) { - return new Vector2(226 + 45 * (fairyI % 9), 66 + 45 * (fairyI / 9)); + return entity; } - protected override void Update(float timeElapsed, in DefaultEcs.Entity entity, ref components.ui.ScrBookMenu bookMenu) - { - base.Update(timeElapsed, entity, ref bookMenu); - } + private static string StatsLights(int[] values) => + String.Join("\n", values.Select(value => UIBuilder.GetLightsIndicator(value))); - private void HandleElementDown(DefaultEcs.Entity entity, components.ui.ElementId id) - { - var bookMenuEntity = Set.GetEntities()[0]; - ref var book = ref bookMenuEntity.Get(); + private static Vector2 FairyButtonPos(int fairyI) => + new Vector2(226 + 45 * (fairyI % 9), 66 + 45 * (fairyI / 9)); - if (book.FairyButtons.TryGetValue(id, out var fairyRow)) + private void HandleElementDown(DefaultEcs.Entity clickedEntity, components.ui.ElementId id) + { + var uiEntity = Set.GetEntities()[0]; + ref var book = ref uiEntity.Get(); + var fairyI = id.Value; + var fairyRow = book.Fairies.ElementAtOrDefault(fairyI); + if (fairyRow != default) { book.Sidebar.Dispose(); - book.Sidebar = CreateSidebar(preload, entity, fairyRow, ref book); + book.Sidebar = CreateSidebar(uiEntity, ref book, fairyI); book.Crosshair.Dispose(); - book.Crosshair = preload.CreateImage(entity) - .With(Mid + new Vector2(-2, -2) + FairyButtonPos(book.Fairies.IndexOf(fairyRow))) + book.Crosshair = preload.CreateImage(uiEntity) + .With(Mid + new Vector2(-2, -2) + FairyButtonPos(fairyI)) .With(UIPreloadAsset.Dnd000, 0) .WithRenderOrder(-2) .Build(); } - - if (id == IDOpenDeck) - { - bookMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - else if (id == IDOpenRunes) - { - bookMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - else if (id == IDOpenMap) - { - bookMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - else if (id == IDClose) - bookMenuEntity.Dispose(); + HandleNavClick(id, zanzarah, uiEntity, IDOpenFairybook); } protected override void HandleKeyDown(KeyCode key) { - var bookMenuEntity = Set.GetEntities()[0]; + var uiEntity = Set.GetEntities()[0]; base.HandleKeyDown(key); - if (key == KeyCode.KF2) { - bookMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - if (key == KeyCode.KF4) { - bookMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - if (key == KeyCode.KF5) { - bookMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - if (key == KeyCode.KReturn|| key == KeyCode.KEscape || key == KeyCode.KF3) - Set.DisposeAll(); + HandleNavKeyDown(key, zanzarah, uiEntity, IDOpenFairybook); } } diff --git a/zzre/game/systems/ui/ScrDeck.BaseSlot.cs b/zzre/game/systems/ui/ScrDeck.BaseSlot.cs new file mode 100644 index 00000000..ecbc491f --- /dev/null +++ b/zzre/game/systems/ui/ScrDeck.BaseSlot.cs @@ -0,0 +1,175 @@ +using System; +using System.Numerics; +using zzio; + +namespace zzre.game.systems.ui; + +public partial class ScrDeck +{ + // Provide a size for buttons, including unset ones. + public Vector2 SlotButtonSize = Vector2.One * 38; + + private void CreateSlotSummary(DefaultEcs.Entity entity, Vector2 offset) + { + ref var slot = ref entity.Get(); + slot.summary = preload.CreateLabel(entity) + .With(slot.button.Get().Min + offset) + .With(UIPreloadAsset.Fnt002); + } + + private static UITileSheetAsset.Info TileSheet(CardId cardId) => cardId.Type switch + { + CardType.Fairy => UIPreloadAsset.Wiz000, + CardType.Item => UIPreloadAsset.Itm000, + CardType.Spell => UIPreloadAsset.Spl000, + _ => throw new NotSupportedException("Unknown inventory card type") + }; + + private void ChangeTileSheet(DefaultEcs.Entity entity, UITileSheetAsset.Info tileSheetInfo) + { + // This is probably highly inefficient? + preload.UI.GetTag().LoadUITileSheet(entity, tileSheetInfo); + } + + private static void SetSummaryOffset(ref components.ui.Slot slot) + { + var min = slot.button.Get().Min + SummaryOffset(ref slot); + slot.summary.Set(Rect.FromMinMax(min, min)); + } + + private static Vector2 SummaryOffset(ref components.ui.Slot slot) + { + if (slot.type == components.ui.Slot.Type.DeckSlot) + return new(48, 4); + if (slot.card!.cardId.Type == CardType.Item) + return new(42, 18); + return new(42, 9); + } + + private void SetSlot(ref components.ui.Slot slot, InventoryCard card) + { + slot.card = card; + slot.button.Set(components.Visibility.Visible); + ChangeTileSheet(slot.button, TileSheet(card.cardId)); + slot.button.Set(new components.ui.ButtonTiles(card.cardId.EntityId)); + slot.button.Set(CardTooltip(card)); + + SetSlotSummary(ref slot); + } + + private void UnsetSlot(ref components.ui.Slot slot) + { + slot.card = default; + slot.button.Set(components.Visibility.Invisible); + if (slot.button.Has()) + slot.button.Remove(); + if (slot.usedMarker != default) + slot.usedMarker.Set(components.Visibility.Invisible); + UnsetSlotSummary(ref slot); + if (slot.spellSlots != default) + foreach (var spellSlot in slot.spellSlots) + UnsetSpellSlot(spellSlot); + if (slot.spellImages != default) + UnsetSpellImages(ref slot); + } + + private void SetSlotSummary(ref components.ui.Slot slot) + { + if (slot.summary == default) return; + if (slot.card == default) + { + UnsetSlotSummary(ref slot); + return; + } + slot.summary.Set(new components.ui.Label(slot.card switch + { + InventoryItem item => FormatSlotSummary(item), + InventorySpell spell => FormatSlotSummary(spell), + InventoryFairy fairy => FormatSlotSummary(fairy), + _ => throw new NotSupportedException("Unknown inventory card type") + })); + SetSummaryOffset(ref slot); + } + + private static void UnsetSlotSummary(ref components.ui.Slot slot) + { + if (slot.summary == default) return; + slot.summary.Set(new components.ui.Label("")); + } + + private void HandleSlotClick(DefaultEcs.Entity deckEntity, ref components.ui.ScrDeck deck, DefaultEcs.Entity slotEntity) + { + ref var slot = ref slotEntity.Get(); + switch (slot.type) + { + case components.ui.Slot.Type.DeckSlot: + HandleDeckSlotClick(deckEntity, ref deck, slotEntity, ref slot); + break; + case components.ui.Slot.Type.ListSlot: + HandleListSlotClick(deckEntity, ref deck, slotEntity, ref slot); + break; + case components.ui.Slot.Type.SpellSlot: + HandleSpellSlotClick(deckEntity, ref deck, slotEntity, ref slot); + break; + default: + throw new ArgumentException($"Invalid slot type: {slot.type}"); + } + } + + private string FormatSlotSummary(InventoryFairy fairy) + { + var builder = new System.Text.StringBuilder(); + builder.Append(fairy.name); + builder.Append(' '); + + builder.Append(fairy.status switch + { + ZZPermSpellStatus.Poisoned => "{110}", + ZZPermSpellStatus.Cursed => "{111}", + ZZPermSpellStatus.Burned => "{115}", + ZZPermSpellStatus.Frozen => "{114}", + ZZPermSpellStatus.Silenced => "{112}", + _ => "" + }); + builder.Append('\n'); + + builder.Append("{100}"); + builder.Append(fairy.currentMHP); + builder.Append('/'); + builder.Append(fairy.maxMHP); + if (fairy.currentMHP < 100) + builder.Append(' '); + if (fairy.currentMHP < 10) + builder.Append(' '); + if (fairy.maxMHP < 100) + builder.Append(' '); + // no second space for maxMHP + + builder.Append(" L-"); + builder.Append(fairy.level); + if (fairy.level < 10) + builder.Append(' '); + + builder.Append(" {101}"); + builder.Append(fairy.xp); + var levelupXP = inventory.GetLevelupXP(fairy); + if (levelupXP.HasValue) + { + builder.Append("{105}"); + builder.Append(levelupXP.Value + 1); + } + + return builder.ToString(); + } + + private string FormatSlotSummary(InventoryItem item) => item.amount > 1 + ? $"{item.amount} x {mappedDB.GetItem(item.dbUID).Name}" + : mappedDB.GetItem(item.dbUID).Name; + + private string FormatSlotSummary(InventorySpell spell) + { + var dbSpell = mappedDB.GetSpell(spell.dbUID); + var mana = dbSpell.Mana == 5 ? "-/-" : $"{spell.mana}/{dbSpell.MaxMana}"; + return $"{dbSpell.Name}\n{{104}}{mana} {UIBuilder.GetSpellPrices(dbSpell)}"; + } +} diff --git a/zzre/game/systems/ui/ScrDeck.DeckSlot.cs b/zzre/game/systems/ui/ScrDeck.DeckSlot.cs new file mode 100644 index 00000000..39544201 --- /dev/null +++ b/zzre/game/systems/ui/ScrDeck.DeckSlot.cs @@ -0,0 +1,189 @@ +using System.Numerics; +using zzio; +using static zzre.game.systems.ui.InGameScreen; + +namespace zzre.game.systems.ui; + +public partial class ScrDeck +{ + private DefaultEcs.Entity CreateDeckSlot( + DefaultEcs.Entity parent, + components.ui.ElementId id, + int index + ) + { + var entity = World.CreateEntity(); + entity.Set(new components.Parent(parent)); + entity.Set(new components.ui.Slot()); + ref var slot = ref entity.Get(); + slot.index = index; + slot.button = preload.CreateButton(entity) + .With(id) + .With(Rect.FromTopLeftSize(Mid + new Vector2(31, 60 + 79 * slot.index), new Vector2(40, 40))) + .With(new components.ui.ButtonTiles(-1)) + .With(UIPreloadAsset.Wiz000) + .Build(); + slot.button.Set(new components.ui.SlotButton()); + slot.type = components.ui.Slot.Type.DeckSlot; + + CreateSlotSummary(entity, new(48, 4)); + + slot.spellSlots = new DefaultEcs.Entity[4]; + for (int i = 0; i < InventoryFairy.SpellSlotCount; i++) + slot.spellSlots[i] = CreateSpellSlot(entity, ref slot, i); + + return entity; + } + + private void SetDeckSlot(DefaultEcs.Entity slotEntity, ref components.ui.ScrDeck deck) + { + ref var slot = ref slotEntity.Get(); + var card = inventory.GetFairyAtSlot(slot.index); + if (card == default) + { + UnsetDeckSlot(ref deck, ref slot); + return; + } + + slot.card = card; + slot.button.Set(components.Visibility.Visible); + slot.button.Set(new components.ui.ButtonTiles(card.cardId.EntityId)); + SetDeckSlotTooltip(ref deck, ref slot); + slot.summary.Set(new components.ui.Label(FormatSlotSummary(card))); + SetSummaryOffset(ref slot); + + for (int i = 0; i < InventoryFairy.SpellSlotCount; i++) + { + var spell = slot.card == null + ? null + : inventory.GetSpellAtSlot((InventoryFairy)slot.card, i); + if (spell != default) + SetSpellSlot(slot.spellSlots[i], spell); + else + UnsetSpellSlot(slot.spellSlots[i]); + } + if (IsInfoTab(deck.ActiveTab)) InfoMode(ref deck, ref slot); + else SpellMode(ref deck, ref slot); + } + + private void UnsetDeckSlot(ref components.ui.ScrDeck deck, ref components.ui.Slot slot) + { + slot.card = default; + slot.button.Set(components.Visibility.Invisible); + SetDeckSlotTooltip(ref deck, ref slot); + UnsetSlotSummary(ref slot); + foreach (var spellSlot in slot.spellSlots) + UnsetSpellSlot(spellSlot); + } + + private static void SetDeckSlotTooltip(ref components.ui.ScrDeck deck, ref components.ui.Slot slot) + { + var tooltip = DeckCardTooltip(ref deck, ref slot); + if (tooltip != null) + slot.button.Set(new components.ui.TooltipUID(tooltip.Value)); + else if (slot.button.Has()) + slot.button.Remove(); + } + + private static UID? DeckCardTooltip(ref components.ui.ScrDeck deck, ref components.ui.Slot slot) + { + if (deck.DraggedCard == default) + return slot.card != default + ? new UID(0x41912581) // select fairy + : null; + + if (deck.DraggedCard.cardId.Type == CardType.Fairy) + return slot.card != default + ? new UID(0x5B971D81) // replace fairy + : new UID(0xD66A2581); // place fairy + + if (deck.DraggedCard.cardId.Type == CardType.Item) + return slot.card != default + ? new UID(0x89A21981) // press key 1 + : new UID(0xD66A2581); // place fairy (?!) + + return null; // no tooltip for dragged spells + } + + private void InfoMode(ref components.ui.ScrDeck deck, ref components.ui.Slot slot) + { + if (slot.card != default) + slot.summary.Set(new components.ui.Label(FormatSlotSummary((InventoryFairy)(slot.card)))); + foreach (var spellSlot in slot.spellSlots) + InfoModeSpell(ref deck, ref slot, ref spellSlot.Get()); + } + + private static void SpellMode(ref components.ui.ScrDeck deck, ref components.ui.Slot slot) + { + slot.summary.Set(new components.ui.Label("")); + foreach (var spellSlot in slot.spellSlots) + SpellModeSpell(ref deck, ref slot, ref spellSlot.Get()); + } + + private void HandleDeckSlotClick(DefaultEcs.Entity deckEntity, ref components.ui.ScrDeck deck, DefaultEcs.Entity slotEntity, ref components.ui.Slot slot) + { + if (deck.DraggedCard == default) + { + // Clicking on an empty slot with an empty hand + if (slot.card == default) + return; + // Picking up a card + inventory.SetSlot((InventoryFairy)slot.card, -1); + deck.VacatedDeckSlot = slot.index; + DragCard(deckEntity, ref deck, slot.card); + UnsetDeckSlot(ref deck, ref slot); + SetListSlots(ref deck); + return; + } + + // Applying a card + if (deck.DraggedCard.cardId.Type == CardType.Spell) + return; + if (deck.DraggedCard.cardId.Type == CardType.Fairy) + { + var oldDrag = deck.DraggedCard; + var newDrag = slot.card; + + World.Publish(new messages.SpawnSample("resources/audio/sfx/gui/_g012.wav")); + inventory.SetSlot((InventoryFairy)oldDrag, slot.index); + + // Swap fairies + if (newDrag != default) + { + DragCard(deckEntity, ref deck, newDrag); + SetListSlots(ref deck); + if (newDrag.isInUse) + { + inventory.SetSlot((InventoryFairy)newDrag, -1); + deck.VacatedDeckSlot = slot.index; + } + } + else + { + // Drop off fairy + deck.VacatedDeckSlot = -1; + DropCard(ref deck); + } + SetDeckSlot(slotEntity, ref deck); + return; + } + else if (deck.DraggedCard.cardId.Type == CardType.Item) + { + if (slot.card == default) + return; + World.Publish(new messages.ui.ExecuteUIScript((InventoryItem)deck.DraggedCard, slotEntity)); + } + } + + protected void HandleUIScriptFinished(in messages.ui.UIScriptFinished message) + { + ref var deck = ref message.DeckSlotEntity.Get().Entity.Get(); + + SetDeckSlot(message.DeckSlotEntity, ref deck); + if (message.ItemConsumed) + { + inventory.RemoveCards(deck.DraggedCard!.cardId, 1); + DropCard(ref deck); + } + } +} diff --git a/zzre/game/systems/ui/ScrDeck.Drag.cs b/zzre/game/systems/ui/ScrDeck.Drag.cs new file mode 100644 index 00000000..3edf7afb --- /dev/null +++ b/zzre/game/systems/ui/ScrDeck.Drag.cs @@ -0,0 +1,98 @@ +using zzio; + +using static zzre.game.systems.ui.InGameScreen; +namespace zzre.game.systems.ui; + +public partial class ScrDeck : BaseScreen +{ + private void DragCard(DefaultEcs.Entity deckEntity, ref components.ui.ScrDeck deck, InventoryCard card) + { + deck.DraggedCard = card; + + if (deck.DraggedCardImage != default) deck.DraggedCardImage.Dispose(); + deck.DraggedCardImage = preload.CreateImage(deckEntity) + .With(Mid) + .With(card.cardId) + .WithRenderOrder(-2) + .Build(); + deck.DraggedCardImage.Set(new components.ui.DraggedCard(card)); + deck.DraggedCardImage.Set(components.ui.UIOffset.GameUpperLeft); + + if (deck.DraggedOverlay != default) deck.DraggedOverlay.Dispose(); + deck.DraggedOverlay = preload.CreateImage(deckEntity) + .With(Mid) + .With(UIPreloadAsset.Dnd000) + .WithRenderOrder(-3) + .Build(); + deck.DraggedOverlay.Set(components.ui.UIOffset.GameUpperLeft); + SetDragOverlayTile(ref deck); + RefreshTooltips(ref deck); + } + + private void DropCard(ref components.ui.ScrDeck deck) + { + if (deck.VacatedDeckSlot != -1) + { + inventory.SetSlot((InventoryFairy)deck.DraggedCard!, deck.VacatedDeckSlot); + SetDeckSlot(deck.DeckSlots[deck.VacatedDeckSlot], ref deck); + } + foreach (var slotEntity in deck.ListSlots) + UnsetHoverMode(slotEntity); + deck.VacatedDeckSlot = -1; + deck.DraggedCard = default; + deck.DraggedCardImage.Dispose(); + deck.DraggedCardImage = default; + deck.DraggedOverlay.Dispose(); + deck.DraggedOverlay = default; + SetListSlots(ref deck); + RefreshTooltips(ref deck); + } + + private static void RefreshTooltips(ref components.ui.ScrDeck deck) + { + foreach (var slotEntity in deck.DeckSlots) + { + ref var slot = ref slotEntity.Get(); + SetDeckSlotTooltip(ref deck, ref slot); + foreach (var spellSlotEntity in slot.spellSlots) + { + ref var spellSlot = ref spellSlotEntity.Get(); + SetSpellSlotTooltip(ref deck, ref slot, ref spellSlot); + } + } + } + + private void SetDragOverlayTile(ref components.ui.ScrDeck deck) + { + if (deck.DraggedCard == default) return; + deck.DraggedOverlay.Set(new components.ui.ButtonTiles(DragOverlayTile(ref deck))); + } + + private int DragOverlayTile(ref components.ui.ScrDeck deck) + { + if (deck.LastHovered == default) return 1; + if (!deck.LastHovered.Has()) return 1; + var slotEntity = deck.LastHovered.Get().Entity; + ref var slot = ref slotEntity.Get(); + if (slot.type == components.ui.Slot.Type.DeckSlot && deck.DraggedCard!.cardId.Type != CardType.Spell) + return 0; + if (slot.type == components.ui.Slot.Type.SpellSlot && deck.DraggedCard!.cardId.Type == CardType.Spell && IsOfMatchingSpellType(ref slot, deck.DraggedCard!)) + return 0; + return 1; + } + + private void Drag(ref components.ui.ScrDeck deck) + { + if (deck.DraggedCardImage != default) + { + DragImage(deck.DraggedCardImage); + DragImage(deck.DraggedOverlay); + } + } + + private void DragImage(DefaultEcs.Entity entity) + { + var tiles = entity.Get(); + tiles[0].Rect = tiles[0].Rect with { Center = ui.CursorEntity.Get().Center }; + } +} diff --git a/zzre/game/systems/ui/ScrDeck.ListSlot.cs b/zzre/game/systems/ui/ScrDeck.ListSlot.cs new file mode 100644 index 00000000..0f996dfe --- /dev/null +++ b/zzre/game/systems/ui/ScrDeck.ListSlot.cs @@ -0,0 +1,168 @@ +using System; +using System.Numerics; +using zzio; + +namespace zzre.game.systems.ui; + +public partial class ScrDeck +{ + + private DefaultEcs.Entity CreateListSlot( + DefaultEcs.Entity parent, + Vector2 pos, + components.ui.ElementId id, + int index + ) + { + var entity = World.CreateEntity(); + entity.Set(new components.Parent(parent)); + entity.Set(new components.ui.Slot()); + ref var slot = ref entity.Get(); + slot.type = components.ui.Slot.Type.ListSlot; + slot.index = index; + slot.button = preload.CreateButton(entity) + .With(id) + .With(pos) + .With(new components.ui.ButtonTiles(-1)) + .With(UIPreloadAsset.Wiz000) + .Build(); + slot.button.Set(new components.ui.SlotButton()); + + slot.usedMarker = preload.CreateImage(entity) + .With(pos) + .With(UIPreloadAsset.Inf000, 16) + .WithRenderOrder(-1) + .Invisible() + .Build(); + + UnsetSlot(ref slot); + return entity; + } + + private void SetListSlot(DefaultEcs.Entity entity, InventoryCard card) + { + ref var slot = ref entity.Get(); + SetSlot(ref slot, card); + slot.usedMarker.Set(!IsDraggable(card) + ? components.Visibility.Visible + : components.Visibility.Invisible); + slot.button.Set(CardTooltip(card)); + + if (slot.spellImages != default) + if (slot.showSpells) + SetSpellImages(entity); + else + UnsetSpellImages(ref slot); + } + + private bool IsDraggable(InventoryCard card) => card.cardId.Type switch + { + CardType.Item => mappedDB.GetItem(card.dbUID).Script.Length != 0, + CardType.Spell => !card.isInUse, + CardType.Fairy => !card.isInUse, + _ => throw new ArgumentException($"Invalid inventory card type: {card.cardId.Type}") + }; + + private void HandleListSlotClick(DefaultEcs.Entity deckEntity, ref components.ui.ScrDeck deck, DefaultEcs.Entity slotEntity, ref components.ui.Slot slot) + { + if (deck.DraggedCard != default) return; + if (slot.card == default) return; + if (!IsDraggable(slot.card)) return; + if (!scene.dataset.canChangeDeck && slot.card.cardId.Type == CardType.Fairy) + { + ui.Publish(new messages.ui.Notification(mappedDB.GetText(new UID(0xC21C5531)).Text)); + return; + } + DragCard(deckEntity, ref deck, slot.card); + } + + private void CreateSpellImages(DefaultEcs.Entity entity) + { + ref var slot = ref entity.Get(); + slot.spellImages = new DefaultEcs.Entity[4]; + for (var i = 0; i < slot.spellImages.Length; i++) + { + slot.spellImages[i] = preload.CreateImage(entity) + .With(slot.button.Get().Min + new Vector2(42 + 40 * i, 0)) + .With(UIPreloadAsset.Spl000) + .Build(); + } + UnsetSpellImages(ref slot); + } + + private void SetHoverMode(DefaultEcs.Entity entity) + { + ref var slot = ref entity.Get(); + if (slot.summary == default) return; + slot.showSpells = true; + SetSpellImages(entity); + } + private void UnsetHoverMode(DefaultEcs.Entity entity) + { + ref var slot = ref entity.Get(); + if (slot.summary == default) return; + slot.showSpells = false; + UnsetSpellImages(ref slot); + } + + private void SetSpellImages(DefaultEcs.Entity entity) + { + ref var slot = ref entity.Get(); + if (slot.card == default || slot.card.cardId.Type != CardType.Fairy) + { + UnsetSpellImages(ref slot); + return; + } + for (var i = 0; i < slot.spellImages.Length; i++) + { + var spell = inventory.GetSpellAtSlot((InventoryFairy)slot.card, i); + if (spell != default) + { + slot.spellImages[i].Set(components.Visibility.Visible); + slot.spellImages[i].Set(new components.ui.ButtonTiles(spell.cardId.EntityId)); + } + else UnsetSpellImage(slot.spellImages[i]); + } + UnsetSlotSummary(ref slot); + } + + private void UnsetSpellImages(ref components.ui.Slot slot) + { + foreach (var spellImage in slot.spellImages) + UnsetSpellImage(spellImage); + SetSlotSummary(ref slot); + } + + private static void UnsetSpellImage(DefaultEcs.Entity spellImage) + { + spellImage.Set(components.Visibility.Invisible); + spellImage.Set(new components.ui.ButtonTiles(-1)); + } + + private components.ui.TooltipUID CardTooltip(InventoryItem item) + => !IsDraggable(item) + ? new UID(0x8F4510A1) // item cannot be used + : new UID(0x75F10CA1); // select item + + private components.ui.TooltipUID CardTooltip(InventoryFairy fairy) + => !IsDraggable(fairy) + ? new UID(0x9054EAB1) // fairy is in use + : scene.dataset.canChangeDeck + ? new UID(0x00B500A1) // select fairy + : new UID(0x4D1B04A1); // can only be changed in London + + private components.ui.TooltipUID CardTooltip(InventorySpell spell) + => !IsDraggable(spell) + ? new UID(0x6B46EEB1) // spell is in use + : mappedDB.GetSpell(spell.dbUID).Type == 0 + ? new UID(0xDA2B08A1) // select offensive spell + : new UID(0x93840CA1); // select passive spell + + private components.ui.TooltipUID CardTooltip(InventoryCard card) => card.cardId.Type switch + { + CardType.Item => CardTooltip((InventoryItem)card), + CardType.Spell => CardTooltip((InventorySpell)card), + CardType.Fairy => CardTooltip((InventoryFairy)card), + _ => throw new ArgumentException($"Invalid inventory card type: {card.cardId.Type}") + }; +} diff --git a/zzre/game/systems/ui/ScrDeck.SpellSlot.cs b/zzre/game/systems/ui/ScrDeck.SpellSlot.cs new file mode 100644 index 00000000..4c083876 --- /dev/null +++ b/zzre/game/systems/ui/ScrDeck.SpellSlot.cs @@ -0,0 +1,214 @@ +using System.Numerics; +using System.Linq; +using zzio; + +namespace zzre.game.systems.ui; + +public partial class ScrDeck +{ + public Vector2 SpellSlotSize = new(38, 38); + private static readonly UID[] UIDSpellSlotNames = + [ + new(0x37697321), // First offensive slot + new(0x8A717321), // First defensive slot + new(0x0F207721), // Second offensive slot + new(0x5C577721) // Second defensive slot + ]; + + private static readonly UID[] UIDFairyInfoDescriptions = + [ + new(0x45B032A1), // Current and max HP + new(0xE58236A1), // Level of your fairy + new(0xB26B36A1), // XP, current and necessary for next level + new(0xB26B36A1) + ]; + + private DefaultEcs.Entity CreateSpellSlot( + DefaultEcs.Entity parent, + ref components.ui.Slot parentSlot, + int index + ) + { + var entity = World.CreateEntity(); + entity.Set(new components.Parent(parent)); + entity.Set(new components.ui.Slot()); + ref var slot = ref entity.Get(); + slot.type = components.ui.Slot.Type.SpellSlot; + slot.index = index; + slot.button = preload.CreateButton(entity) + .With(parentSlot.button.Get() + index + 1) + .With(Rect.FromTopLeftSize(parentSlot.button.Get().Min + new Vector2(50 + 46 * slot.index, 0), SpellSlotSize)) + .With(new components.ui.ButtonTiles(-1)) + .With(UIPreloadAsset.Spl000) + .Build(); + slot.button.Set(new components.ui.SlotButton()); + + InfoModeSpell(ref parent.Get().Entity.Get(), ref parentSlot, ref slot); + + return entity; + } + + public void SetSpellSlot(DefaultEcs.Entity entity, InventorySpell invSpell) + { + ref var slot = ref entity.Get(); + slot.card = invSpell; + slot.button.Set(components.Visibility.Visible); + slot.button.Set(new components.ui.ButtonTiles(invSpell.cardId.EntityId)); + CreateSpellSummary(entity); + CreateSpellReq(entity); + } + + public void UnsetSpellSlot(DefaultEcs.Entity entity) + { + ref var slot = ref entity.Get(); + UnsetSlot(ref slot); + slot.button.Set(new components.ui.ButtonTiles(-1)); + CreateSpellSummary(entity); + CreateSpellReq(entity); + } + + private static void SetSpellSlotTooltip(ref components.ui.ScrDeck deck, ref components.ui.Slot fairySlot, ref components.ui.Slot slot) + { + var tooltip = SpellSlotTooltip(ref deck, ref fairySlot, ref slot); + if (tooltip != null) + slot.button.Set(new components.ui.TooltipUID(tooltip.Value)); + else if (slot.button.Has()) + slot.button.Remove(); + } + + private static UID? SpellSlotTooltip(ref components.ui.ScrDeck deck, ref components.ui.Slot fairySlot, ref components.ui.Slot slot) + { + if (fairySlot.card == default) return null; + if (deck.DraggedCard == default) + return IsInfoTab(deck.ActiveTab) + ? UIDFairyInfoDescriptions[slot.index] + : UIDSpellSlotNames[slot.index]; + if (deck.DraggedCard.cardId.Type == CardType.Spell) + return slot.card == default + ? new UID(0x3B46CC81) // deploy spell + : new UID(0x0A05D081); // overwrite spell + return null; + } + + public void InfoModeSpell(ref components.ui.ScrDeck deck, ref components.ui.Slot fairySlot, ref components.ui.Slot slot) + { + slot.button.Set(components.Visibility.Invisible); + SetSpellSlotTooltip(ref deck, ref fairySlot, ref slot); + if (slot.summary != default) + slot.summary.Set(slot.card != default + ? new components.ui.Label(FormatManaAmount((InventorySpell)slot.card)) + : new components.ui.Label("")); + if (slot.req != default) + slot.req.Set(components.Visibility.Invisible); + } + + public static void SpellModeSpell(ref components.ui.ScrDeck deck, ref components.ui.Slot fairySlot, ref components.ui.Slot slot) + { + if (slot.button.Get().Normal != -1) + slot.button.Set(components.Visibility.Visible); + SetSpellSlotTooltip(ref deck, ref fairySlot, ref slot); + if (slot.summary != default) + slot.summary.Set(new components.ui.Label("")); + if (slot.req != default) + slot.req.Set(components.Visibility.Visible); + } + + private void HandleSpellSlotClick(DefaultEcs.Entity deckEntity, ref components.ui.ScrDeck deck, DefaultEcs.Entity slotEntity, ref components.ui.Slot slot) + { + if (deck.DraggedCard == default) return; + if (deck.DraggedCard.cardId.Type != CardType.Spell) return; + + var fairyEntity = slotEntity.Get().Entity; + ref var fairySlot = ref fairyEntity.Get(); + + if (fairySlot.card == default) return; + + if (!IsOfMatchingSpellType(ref slot, deck.DraggedCard)) + { + var note = slot.index % 2 == 0 ? new UID(0xC18D4C31) : new UID(0x9CD74C31); + ui.Publish(new messages.ui.Notification(mappedDB.GetText(note).Text)); + return; + } + if (!((InventoryFairy)fairySlot.card).spellReqs[slot.index].IsCompatible(mappedDB.GetSpell(((InventorySpell)deck.DraggedCard).dbUID))) + { + // Not enough skills to use spell + ui.Publish(new messages.ui.Notification(mappedDB.GetText(new UID(0x79C75031)).Text)); + return; + } + + var oldDrag = deck.DraggedCard; + var newDrag = slot.card; + + World.Publish(new messages.SpawnSample("resources/audio/sfx/gui/_g008.wav")); + inventory.SetSpellSlot((InventoryFairy)fairySlot.card, (InventorySpell)oldDrag, slot.index); + + // Swap spells + if (newDrag != default) + { + newDrag.isInUse = false; // Is this never handled in Inventory.GameLogic? + DragCard(deckEntity, ref deck, newDrag); + SetListSlots(ref deck); + } + else + { + // Drop off spell + DropCard(ref deck); + } + // Update the slot visual + SetDeckSlot(fairyEntity, ref deck); + return; + } + + private bool IsOfMatchingSpellType(ref components.ui.Slot slot, InventoryCard card) => + mappedDB.GetSpell(card.dbUID).Type == slot.index % 2; + + private void CreateSpellSummary(DefaultEcs.Entity entity) + { + ref var spellSlot = ref entity.Get(); + if (spellSlot.summary != default) spellSlot.summary.Dispose(); + spellSlot.summary = preload.CreateLabel(entity) + .With(spellSlot.button.Get().Min + new Vector2(0, 48)) + .With(UIPreloadAsset.Fnt002) + .Build(); + } + + private void CreateSpellReq(DefaultEcs.Entity entity) + { + ref var slot = ref entity.Get(); + if (slot.req != default) + { + slot.req.Dispose(); + slot.req = default; + }; + + ref var deckSlot = ref entity.Get().Entity.Get(); + if (deckSlot.card == default) return; + + var spellReq = ((InventoryFairy)deckSlot.card).spellReqs[slot.index]; + var isAttack = slot.index % 2 == 0; + var pos = slot.button.Get().Min + new Vector2(2, 45); + + slot.req = World.CreateEntity(); + slot.req.Set(new components.Parent(entity)); + slot.req.Set(components.Visibility.Visible); + slot.req.Set(components.ui.UIOffset.Center); + slot.req.Set(new components.ui.RenderOrder(0)); + slot.req.Set(IColor.White); + assetRegistry.LoadUITileSheet(slot.req, isAttack ? UIPreloadAsset.Cls001 : UIPreloadAsset.Cls000); + + var tileSize = slot.req.Get().GetPixelSize(0); + slot.req.Set(Rect.FromTopLeftSize(pos, tileSize * 3)); + slot.req.Set(spellReq.Select((req, i) => new components.ui.Tile( + TileId: (int)req, + Rect: Rect.FromTopLeftSize(pos + i * new Vector2(8, 5), tileSize))) + .ToArray()); + } + + private string FormatManaAmount(InventorySpell spell) + { + var dbSpell = mappedDB.GetSpell(spell.dbUID); + return dbSpell.Mana == 5 + ? "{104}-" + : $"{{104}}{spell.mana}/{dbSpell.MaxMana}"; + } +} diff --git a/zzre/game/systems/ui/ScrDeck.Summary.cs b/zzre/game/systems/ui/ScrDeck.Summary.cs new file mode 100644 index 00000000..35e355d1 --- /dev/null +++ b/zzre/game/systems/ui/ScrDeck.Summary.cs @@ -0,0 +1,139 @@ +using System; +using System.Numerics; +using System.Linq; +using zzio; +using static zzre.game.systems.ui.InGameScreen; + +namespace zzre.game.systems.ui; + +public partial class ScrDeck +{ + private static readonly UID[] UIDStatNames = + [ + new(0xE946ECA1), // Damage + new(0x238A3981), // Mana + new(0x04211121), // Fire Rate + ]; + private static readonly UID numCollectedFairies = new(0xA3EC01B1); + + private void CreateStats(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) + { + var card = GetHoveredCard(entity, ref deck); + var summary = card == default ? GetDefaultSummary(ref deck) : FormatStats(card); + + if (deck.StatsTitle != default) deck.StatsTitle.Dispose(); + deck.StatsTitle = preload.CreateLabel(entity) + .With(Mid + new Vector2(320, 350)) + .With(UIPreloadAsset.Fnt000) + .WithText(summary[0]) + .Build(); + if (deck.StatsDescriptions != default) deck.StatsDescriptions.Dispose(); + deck.StatsDescriptions = preload.CreateLabel(entity) + .With(Mid + new Vector2(330, 379)) + .With(UIPreloadAsset.Fnt002) + .WithText(summary[1]) + .WithLineWrap(260f) + .Build(); + if (deck.StatsLights != default) deck.StatsLights.Dispose(); + deck.StatsLights = preload.CreateLabel(entity) + .With(Mid + new Vector2(380, 379)) + .With(UIPreloadAsset.Fnt002) + .WithText(summary[2]) + .Build(); + if (deck.StatsLevel != default) deck.StatsLevel.Dispose(); + deck.StatsLevel = preload.CreateLabel(entity) + .With(Mid + new Vector2(473, 379)) + .With(UIPreloadAsset.Fnt002) + .WithText(summary[3]) + .Build(); + } + + private static InventoryCard? GetHoveredCard(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) + { + // Hovering over a non-slot entity + if (!deck.LastHovered.Has()) return default; + + ref var slot = ref deck.LastHovered.Get().Entity.Get(); + + // Hovering over an empty slot entity + if (slot.card == default) + return default; + + // Hovering over deck slot while in fairy tab (get "faery count" instead) + if (deck.ActiveTab == components.ui.ScrDeck.Tab.Fairies && slot.type == components.ui.Slot.Type.DeckSlot) + return default; + + // Dragging fairy over non-deck slot + if (deck.DraggedCard?.cardId.Type == CardType.Fairy && slot.type != components.ui.Slot.Type.DeckSlot) + return default; + + // Dragging spell over non-spell slot + if (deck.DraggedCard?.cardId.Type == CardType.Spell && slot.type != components.ui.Slot.Type.SpellSlot) + return default; + + return slot.card; + } + + private string[] GetDefaultSummary(ref components.ui.ScrDeck deck) + { + if (deck.ActiveTab == components.ui.ScrDeck.Tab.Fairies) + return ["", $"\n\n{mappedDB.GetText(numCollectedFairies).Text} {inventory.Fairies.Count()}", "", ""]; + return ["", "", "", ""]; + } + + private string[] FormatStats(InventoryFairy fairy) + { + var fairyRow = mappedDB.GetFairy(fairy.dbUID); + return [fairy.name, fairyRow.Info, "", ""]; + } + + private string[] FormatStats(InventoryItem item) + { + var itemRow = mappedDB.GetItem(item.dbUID); + var count = item.amount != 1 ? $"{{5*x{item.amount}}}" : ""; + return [$"{itemRow.Name} {count}", itemRow.Info, "", ""]; + } + + private string[] FormatStats(InventorySpell spell) + { + var spellRow = mappedDB.GetSpell(spell.dbUID); + if (spellRow.Type == 0) + { + var stats = new[] { + spellRow.Damage + 1, + spellRow.Mana + 1, + spellRow.Loadup + 1, + }; + var descs = new[] { + mappedDB.GetText(UIDStatNames[0]).Text, + mappedDB.GetText(UIDStatNames[1]).Text, + mappedDB.GetText(UIDStatNames[2]).Text, + spellRow.Info, + }; + return [ + spellRow.Name, + String.Join(":\n", descs), + String.Join("\n", stats.Select(stat => UIBuilder.GetLightsIndicator(stat))), + $"Level: {UIBuilder.GetSpellPrices(spellRow)}", + ]; + } + else + { + return [ + spellRow.Name, + $"{mappedDB.GetText(UIDStatNames[1]).Text}:\n{spellRow.Info}", + spellRow.Mana != 5 ? UIBuilder.GetLightsIndicator(spellRow.Mana + 1) : "-", + $"Level: {UIBuilder.GetSpellPrices(spellRow)}", + ]; + } + } + + private string[] FormatStats(InventoryCard card) + => card.cardId.Type switch + { + CardType.Item => FormatStats((InventoryItem)card), + CardType.Spell => FormatStats((InventorySpell)card), + CardType.Fairy => FormatStats((InventoryFairy)card), + _ => throw new ArgumentException($"Invalid inventory card type: {card.cardId.Type}") + }; +} diff --git a/zzre/game/systems/ui/ScrDeck.cs b/zzre/game/systems/ui/ScrDeck.cs index 72a37937..134f53a7 100644 --- a/zzre/game/systems/ui/ScrDeck.cs +++ b/zzre/game/systems/ui/ScrDeck.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Numerics; using System.Collections.Generic; using System.Linq; @@ -14,22 +14,6 @@ public partial class ScrDeck : BaseScreen Tabs = + [ + new(Tab.Fairies, IDTabFairies, PosY: 79, TileNormal: 0, TileHovered: 1, TileActive: 2, TooltipUID: new(0x7DB4EEB1)), + new(Tab.Items, IDTabItems, PosY: 123, TileNormal: 3, TileHovered: 4, TileActive: 5, TooltipUID: new(0x93530331)), + new(Tab.AttackSpells, IDTabAttackSpells, PosY: 167, TileNormal: 6, TileHovered: 7, TileActive: 8, TooltipUID: new(0xB5E80331)), + new(Tab.SupportSpells, IDTabSupportSpells, PosY: 211, TileNormal: 9, TileHovered: 10, TileActive: 11, TooltipUID: new(0x9D0DAD11)), + ]; + private readonly IAssetRegistry assetRegistry; private readonly zzio.db.MappedDB mappedDB; + private zzio.scn.Scene scene = null!; + private readonly IDisposable SceneLoadedDisposable; + private readonly IDisposable OnUIScriptFinishedDisposable; public ScrDeck(ITagContainer diContainer) : base(diContainer, BlockFlags.All) { assetRegistry = diContainer.GetTag(); mappedDB = diContainer.GetTag(); OnElementDown += HandleElementDown; + OnRightClick += HandleRightClick; + SceneLoadedDisposable = World.Subscribe(HandleSceneLoaded); + OnUIScriptFinishedDisposable = World.Subscribe(HandleUIScriptFinished); } - protected override void HandleOpen(in messages.ui.OpenDeck message) + public override void Dispose() { - var inventory = zanzarah.CurrentGame!.PlayerEntity.Get(); - if (!inventory.Contains(StdItemId.FairyBag)) - return; + base.Dispose(); + OnElementDown -= HandleElementDown; + OnRightClick -= HandleRightClick; + SceneLoadedDisposable.Dispose(); + OnUIScriptFinishedDisposable.Dispose(); + } + + protected void HandleSceneLoaded(in messages.SceneLoaded message) => scene = message.Scene; + protected override void HandleOpen(in messages.ui.OpenDeck message) + { World.Publish(new messages.SpawnSample($"resources/audio/sfx/gui/_g006.wav")); var entity = World.CreateEntity(); entity.Set(); ref var deck = ref entity.Get(); - deck.Inventory = inventory; - deck.DeckSlotParents = []; CreateBackgrounds(entity, ref deck); CreateListControls(entity, ref deck); CreateTopButtons(preload, entity, inventory, IDOpenDeck); - CreateFairySlots(entity, ref deck); + CreateDeckSlots(entity, ref deck); if (deck.ActiveTab == Tab.None) OpenTab(entity, ref deck, Tab.Fairies); + + deck.VacatedDeckSlot = -1; } private void CreateBackgrounds(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) @@ -80,25 +92,31 @@ private void CreateBackgrounds(DefaultEcs.Entity entity, ref components.ui.ScrDe preload.CreateFullBackOverlay(entity); preload.CreateImage(entity) - .With(-new Vector2(52, 240)) + .With(Mid + new Vector2(268, 0)) .WithBitmap("dec000") .WithRenderOrder(1) .Build(); deck.SpellBackground = preload.CreateImage(entity) - .With(-new Vector2(320, 240)) + .With(Mid) .WithBitmap("dec001") .WithRenderOrder(1); deck.SummaryBackground = preload.CreateImage(entity) - .With(-new Vector2(320, 240)) + .With(Mid) .WithBitmap("dec002") .WithRenderOrder(1); preload.CreateTooltipTarget(entity) - .With(new Vector2(-320 + 11, -240 + 11)) + .With(Mid + new Vector2(11, 11)) .WithText("{205} - ") .Build(); + + preload.CreateLabel(entity) + .With(Mid + new Vector2(337, 44)) + .With(UIPreloadAsset.Fnt002) + .WithText($"{zanzarah.OverworldGame!.GetTag().pixiesCatched}/30") + .Build(); } private void CreateListControls(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) @@ -124,384 +142,111 @@ private void CreateListControls(DefaultEcs.Entity entity, ref components.ui.ScrD .With(UIPreloadAsset.Btn001); deck.ListSlider.Set(components.ui.Slider.Vertical); - deck.ListTabs = new DefaultEcs.Entity[4]; - var tabButtonRect = Rect.FromTopLeftSize(Mid + new Vector2(281, 0f), new Vector2(35, 35)); - - deck.ListTabs[(int)Tab.Fairies - 1] = preload.CreateButton(entity) - .With(IDTabFairies) - .With(tabButtonRect.OffsettedBy(0, 79)) - .With(new components.ui.ButtonTiles(0, 1, 2)) - .With(UIPreloadAsset.Btn002) - .WithTooltip(0x7DB4EEB1); - - deck.ListTabs[(int)Tab.Items - 1] = preload.CreateButton(entity) - .With(IDTabItems) - .With(tabButtonRect.OffsettedBy(0, 123)) - .With(new components.ui.ButtonTiles(3, 4, 5)) - .With(UIPreloadAsset.Btn002) - .WithTooltip(0x93530331); - - deck.ListTabs[(int)Tab.AttackSpells - 1] = preload.CreateButton(entity) - .With(IDTabAttackSpells) - .With(tabButtonRect.OffsettedBy(0, 167)) - .With(new components.ui.ButtonTiles(6, 7, 8)) - .With(UIPreloadAsset.Btn002) - .WithTooltip(0xB5E80331); - - deck.ListTabs[(int)Tab.SupportSpells - 1] = preload.CreateButton(entity) - .With(IDTabSupportSpells) - .With(tabButtonRect.OffsettedBy(0, 211)) - .With(new components.ui.ButtonTiles(9, 10, 11)) - .With(UIPreloadAsset.Btn002) - .WithTooltip(0x9D0DAD11); + deck.TabButtons = new DefaultEcs.Entity[4]; + foreach (var tab in Tabs) + deck.TabButtons[(int)tab.Type - 1] = preload.CreateButton(entity) + .With(tab.Id) + .With(Mid + new Vector2(282, tab.PosY)) + .With(new components.ui.ButtonTiles(tab.TileNormal, tab.TileHovered, tab.TileActive)) + .With(UIPreloadAsset.Btn002) + .WithTooltip(tab.TooltipUID); preload.CreateButton(entity) .With(IDSwitchListMode) - .With(tabButtonRect.Min + new Vector2(15, 261)) + .With(Mid + new Vector2(297, 261)) .With(new components.ui.ButtonTiles(28, 29)) .With(UIPreloadAsset.Btn002) .WithTooltip(0xA086B911) .Build(); - - // TODO: Add pixie count label } - private void CreateFairySlots(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) + private void CreateDeckSlots(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) { + deck.DeckSlots = new DefaultEcs.Entity[Inventory.FairySlotCount]; for (int i = 0; i < Inventory.FairySlotCount; i++) - { - var fairy = deck.Inventory.GetFairyAtSlot(i); - var fairyI = fairy?.cardId.EntityId ?? -1; - preload.CreateButton(entity) - .With(FirstFairySlot + i) - .With(Mid + new Vector2(31, 60 + 79 * i)) - .With(new components.ui.ButtonTiles(fairyI)) - .With(UIPreloadAsset.Wiz000) - .WithTooltip(UIDChooseFairyToSwap) - .Build(); - } + deck.DeckSlots[i] = CreateDeckSlot(entity, FirstFairySlot + i, i); + SetDeckSlots(ref deck); } - private void ResetDeckSlotParents(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck, bool createNewEntities) + private void SetDeckSlots(ref components.ui.ScrDeck deck) { - foreach (var oldParent in deck.DeckSlotParents) - oldParent.Dispose(); - - if (createNewEntities) - { - deck.DeckSlotParents = Enumerable - .Repeat(0, Inventory.FairySlotCount) - .Select(_ => - { - var slotParent = World.CreateEntity(); - slotParent.Set(new components.Parent(entity)); - return slotParent; - }) - .ToArray(); - } - else - deck.DeckSlotParents = new DefaultEcs.Entity[Inventory.FairySlotCount]; + foreach (var deckSlot in deck.DeckSlots) + SetDeckSlot(deckSlot, ref deck); } - private static Vector2 DeckSlotPos(int fairyI, int slotI) => - Mid + new Vector2(81 + 46 * slotI, 60 + 79 * fairyI); - - private void CreateSpellSlots(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) + private static void SpellMode(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) { deck.SpellBackground.Set(components.Visibility.Visible); deck.SummaryBackground.Set(components.Visibility.Invisible); - - ResetDeckSlotParents(entity, ref deck, createNewEntities: true); - var nextElementId = FirstSpellSlot; - for (int fairyI = 0; fairyI < Inventory.FairySlotCount; fairyI++) - { - var fairy = deck.Inventory.GetFairyAtSlot(fairyI); - for (int spellI = 0; spellI < InventoryFairy.SpellSlotCount; spellI++) - { - var spell = fairy == null ? null : deck.Inventory.GetSpellAtSlot(fairy, spellI); - preload.CreateButton(deck.DeckSlotParents[fairyI]) - .With(nextElementId) - .With(DeckSlotPos(fairyI, spellI)) - .With(new components.ui.ButtonTiles(spell?.cardId.EntityId ?? -1)) - .With(UIPreloadAsset.Spl000) - .WithTooltip(UIDSpellSlotNames[spellI]) - .Build(); - nextElementId += 1; - - var spellReq = fairy == null ? default : fairy.spellReqs[spellI]; - if (spellReq != default) - CreateSpellReq( - deck.DeckSlotParents[fairyI], - spellReq, - isAttack: (spellI % 2) == 0, - DeckSlotPos(fairyI, spellI) + new Vector2(2, 45)); - } - } - } - - private DefaultEcs.Entity CreateSpellReq(DefaultEcs.Entity parent, SpellReq spellReq, bool isAttack, Vector2 pos, int renderOrder = 0) - { - var entity = World.CreateEntity(); - entity.Set(new components.Parent(parent)); - entity.Set(components.Visibility.Visible); - entity.Set(components.ui.UIOffset.Center); - entity.Set(new components.ui.RenderOrder(renderOrder)); - entity.Set(IColor.White); - assetRegistry.LoadUITileSheet(entity, isAttack ? UIPreloadAsset.Cls001 : UIPreloadAsset.Cls000); - - var tileSize = entity.Get().GetPixelSize(0); - entity.Set(Rect.FromTopLeftSize(pos, tileSize * 3)); - entity.Set(spellReq.Select((req, i) => new components.ui.Tile( - TileId: (int)req, - Rect: Rect.FromTopLeftSize(pos + i * new Vector2(8, 5), tileSize))) - .ToArray()); - - return entity; - } - - private void CreateFairyInfo(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck, int fairyI) - { - if (deck.DeckSlotParents[fairyI] != default) - deck.DeckSlotParents[fairyI].Dispose(); - var slotParent = deck.DeckSlotParents[fairyI] = World.CreateEntity(); - slotParent.Set(new components.Parent(entity)); - var fairy = deck.Inventory.GetFairyAtSlot(fairyI); - if (fairy == null) - return; - - for (int slotI = 0; slotI < InventoryFairy.SpellSlotCount; slotI++) - { - preload.CreateTooltipArea(slotParent) - .With(FirstSpellSlot + fairyI * InventoryFairy.SpellSlotCount + slotI) - .With(Rect.FromTopLeftSize(DeckSlotPos(fairyI, slotI), Vector2.One * 40)) - .WithTooltip(UIDFairyInfoDescriptions[slotI]) - .Build(); - - var spell = deck.Inventory.GetSpellAtSlot(fairy, slotI); - if (spell == null) - continue; - preload.CreateLabel(slotParent) - .With(DeckSlotPos(fairyI, slotI) + new Vector2(0, 44)) - .With(UIPreloadAsset.Fnt002) - .WithText(FormatManaAmount(spell)) - .Build(); - } - - preload.CreateLabel(slotParent) - .With(DeckSlotPos(fairyI, 0)) - .With(UIPreloadAsset.Fnt002) - .WithText(FormatSummary(deck.Inventory, fairy)) - .Build(); - + foreach (var deckCard in deck.DeckSlots) + SpellMode(ref deck, ref deckCard.Get()); } - private string FormatManaAmount(InventorySpell spell) - { - var dbSpell = mappedDB.GetSpell(spell.dbUID); - return dbSpell.MaxMana == 5 - ? "{104}-" - : $"{{104}}{spell.mana}/{dbSpell.MaxMana}"; - } - - private static string FormatSummary(Inventory inv, InventoryFairy fairy) - { - var builder = new System.Text.StringBuilder(); - builder.Append(fairy.name); - builder.Append(' '); - - builder.Append(fairy.status switch - { - ZZPermSpellStatus.Poisoned => "{110}", - ZZPermSpellStatus.Cursed => "{111}", - ZZPermSpellStatus.Burned => "{115}", - ZZPermSpellStatus.Frozen => "{114}", - ZZPermSpellStatus.Silenced => "{112}", - _ => "" - }); - builder.Append('\n'); - - builder.Append("{100}"); - builder.Append(fairy.currentMHP); - builder.Append('/'); - builder.Append(fairy.maxMHP); - if (fairy.currentMHP < 100) - builder.Append(' '); - if (fairy.currentMHP < 10) - builder.Append(' '); - if (fairy.maxMHP < 100) - builder.Append(' '); - // no second space for maxMHP - - builder.Append(" L-"); - builder.Append(fairy.level); - if (fairy.level < 10) - builder.Append(' '); - - builder.Append(" {101}"); - builder.Append(fairy.xp); - var levelupXP = inv.GetLevelupXP(fairy); - if (levelupXP.HasValue) - { - builder.Append("{105}"); - builder.Append(levelupXP.Value + 1); - } - - return builder.ToString(); - } - - private string FormatSummary(InventoryItem item) => item.amount > 1 - ? $"{item.amount} x {mappedDB.GetItem(item.dbUID).Name}" - : mappedDB.GetItem(item.dbUID).Name; - - private string FormatSummary(InventorySpell spell) - { - var dbSpell = mappedDB.GetSpell(spell.dbUID); - var mana = dbSpell.Mana == 5 ? "-/-" : $"{spell.mana}/{dbSpell.MaxMana}"; - return $"{dbSpell.Name}\n{{104}}{mana} {UIBuilder.GetSpellPrices(dbSpell)}"; - } - - private void CreateFairyInfos(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) + private void InfoMode(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) { deck.SpellBackground.Set(components.Visibility.Invisible); deck.SummaryBackground.Set(components.Visibility.Visible); - - ResetDeckSlotParents(entity, ref deck, createNewEntities: false); - for (int i = 0; i < Inventory.FairySlotCount; i++) - CreateFairyInfo(entity, ref deck, i); - } - - private static void ResetList(ref components.ui.ScrDeck deck) - { - var allEntities = new[] - { - deck.ListButtons, - deck.ListSummaries, - deck.ListUsedMarkers - }.NotNull().SelectMany(); - foreach (var entity in allEntities) - entity.Dispose(); - deck.ListButtons = deck.ListSummaries = deck.ListUsedMarkers = - []; + foreach (var deckCard in deck.DeckSlots) + InfoMode(ref deck, ref deckCard.Get()); } - private static Vector2 ListCellPos(int column, int row) => + private static Vector2 ListSlotPos(int column, int row) => Mid + new Vector2(322 + column * 42, 70 + row * 43); - private static UITileSheetAsset.Info ListTileSheet(in components.ui.ScrDeck deck) => deck.ActiveTab switch + private void CreateListSlots(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck, int columns, int rows = ListRows) { - Tab.Fairies => UIPreloadAsset.Wiz000, - Tab.Items => UIPreloadAsset.Itm000, - Tab.SupportSpells => UIPreloadAsset.Spl000, - Tab.AttackSpells => UIPreloadAsset.Spl000, - _ => UIPreloadAsset.Wiz000 - }; - - private void CreateListCells(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck, int columns, int rows = ListRows) - { - var buttonTileSheet = ListTileSheet(deck); - deck.ListButtons = new DefaultEcs.Entity[rows * columns]; - deck.ListUsedMarkers = new DefaultEcs.Entity[rows * columns]; + deck.ListSlots = new DefaultEcs.Entity[rows * columns]; for (int y = 0; y < rows; y++) { for (int x = 0; x < columns; x++) { var i = y * columns + x; - deck.ListButtons[i] = preload.CreateButton(entity) - .With(FirstListCell + i) - .With(ListCellPos(x, y)) - .With(new components.ui.ButtonTiles(-1)) - .With(buttonTileSheet); - - deck.ListUsedMarkers[i] = preload.CreateImage(entity) - .With(ListCellPos(x, y)) - .With(UIPreloadAsset.Inf000, 16) - .WithRenderOrder(-1) - .Invisible(); + deck.ListSlots[i] = CreateListSlot(entity, ListSlotPos(x, y), FirstListCell + i, i); + if (columns == 1) + { + CreateSlotSummary(deck.ListSlots[i], new(42, 9)); + CreateSpellImages(deck.ListSlots[i]); + } } } } - private void CreateRowList(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) - { - ResetList(ref deck); - CreateListCells(entity, ref deck, columns: 1); - var summaryOffset = new Vector2(42, deck.ActiveTab == Tab.Items ? 14 : 5); - deck.ListSummaries = new DefaultEcs.Entity[ListRows]; - for (int i = 0; i < ListRows; i++) - { - deck.ListSummaries[i % ListRows] = preload.CreateLabel(entity) - .With(ListCellPos(column: 0, row: i) + summaryOffset) - .With(UIPreloadAsset.Fnt002); - } - } - - private void CreateGridList(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) - { - ResetList(ref deck); - CreateListCells(entity, ref deck, columns: 6); - } - private IEnumerable AllCardsOfType(in components.ui.ScrDeck deck) => deck.ActiveTab switch { - Tab.Items => deck.Inventory.Items + Tab.Items => inventory.Items .OrderBy(c => mappedDB.GetItem(c.dbUID).Unknown switch { 1 => 0, 0 => 1, _ => 2 }) .ThenBy(c => c.cardId.EntityId), - Tab.Fairies => deck.Inventory.Fairies.OrderByDescending(c => c.level), - Tab.AttackSpells => deck.Inventory.AttackSpells.OrderBy(c => c.cardId.EntityId), - Tab.SupportSpells => deck.Inventory.SupportSpells.OrderBy(c => c.cardId.EntityId), + Tab.Fairies => inventory.Fairies.OrderByDescending(c => c.level), + Tab.AttackSpells => inventory.AttackSpells.OrderBy(c => c.cardId.EntityId), + Tab.SupportSpells => inventory.SupportSpells.OrderBy(c => c.cardId.EntityId), _ => [] }; - private void FillList(ref components.ui.ScrDeck deck) + private void SetListSlots(ref components.ui.ScrDeck deck) { var allCardsOfType = AllCardsOfType(deck); var count = allCardsOfType.Count(); deck.Scroll = Math.Clamp(deck.Scroll, 0, Math.Max(0, count - 1)); var shownCards = allCardsOfType .Skip(deck.Scroll) - .Take(deck.ListButtons.Length) - .ToArray(); - - int i; - for (i = 0; i < shownCards.Length; i++) - { - deck.ListButtons[i].Set(components.Visibility.Visible); - deck.ListButtons[i].Set(ListTileSheet(deck)); - deck.ListButtons[i].Set(new components.ui.ButtonTiles(shownCards[i].cardId.EntityId)); - deck.ListUsedMarkers[i].Set(shownCards[i].isInUse - ? components.Visibility.Visible - : components.Visibility.Invisible); - } - for (; i < deck.ListButtons.Length; i++) - { - deck.ListButtons[i].Set(components.Visibility.Invisible); - deck.ListUsedMarkers[i].Set(components.Visibility.Invisible); - } + .Take(deck.ListSlots.Length); - if (deck.IsGridMode) - return; - for (i = 0; i < shownCards.Length; i++) + for (var i = 0; i < deck.ListSlots.Length; i++) { - var summary = shownCards[i] switch - { - InventoryItem item => FormatSummary(item), - InventorySpell spell => FormatSummary(spell), - InventoryFairy fairy => FormatSummary(deck.Inventory, fairy), - _ => throw new NotSupportedException("Unknown inventory card type") - }; - deck.ListSummaries[i].Set(new components.ui.Label(summary)); + var shownCard = shownCards.ElementAtOrDefault(i); + if (shownCard != default) + SetListSlot(deck.ListSlots[i], shownCard); + else UnsetSlot(ref deck.ListSlots[i].Get()); } - for (; i < deck.ListButtons.Length; i++) - deck.ListSummaries[i].Set(new components.ui.Label("")); } private void RecreateList(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) { - if (deck.IsGridMode) - CreateGridList(entity, ref deck); - else - CreateRowList(entity, ref deck); - FillList(ref deck); + if (deck.ListSlots != default) + foreach (var listSlot in deck.ListSlots) + listSlot.Dispose(); + CreateListSlots(entity, ref deck, columns: deck.IsGridMode ? 6 : 1); + SetListSlots(ref deck); } private static bool IsInfoTab(Tab tab) => tab == Tab.Fairies || tab == Tab.Items; @@ -513,13 +258,13 @@ private void OpenTab(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck, T deck.ActiveTab = newTab; if (oldTab != Tab.None) - deck.ListTabs[(int)oldTab - 1].Remove(); - deck.ListTabs[(int)newTab - 1].Set(); + deck.TabButtons[(int)oldTab - 1].Remove(); + deck.TabButtons[(int)newTab - 1].Set(); if (IsInfoTab(newTab) && !IsInfoTab(oldTab)) - CreateFairyInfos(entity, ref deck); + InfoMode(entity, ref deck); if (IsSpellTab(newTab) && !IsSpellTab(oldTab)) - CreateSpellSlots(entity, ref deck); + SpellMode(entity, ref deck); RecreateList(entity, ref deck); } @@ -529,6 +274,13 @@ private void HandleElementDown(DefaultEcs.Entity clickedEntity, components.ui.El var deckEntity = Set.GetEntities()[0]; ref var deck = ref deckEntity.Get(); + if (clickedEntity.Has()) + { + HandleSlotClick(deckEntity, ref deck, clickedEntity.Get().Entity); + } + if (deck.VacatedDeckSlot != -1) + return; + if (id == IDSwitchListMode) { deck.IsGridMode = !deck.IsGridMode; @@ -542,31 +294,19 @@ private void HandleElementDown(DefaultEcs.Entity clickedEntity, components.ui.El { deck.Scroll += deck.IsGridMode ? ListRows : 1; UpdateSliderPosition(deck); - FillList(ref deck); + SetListSlots(ref deck); } else if (id == IDSliderUp) { deck.Scroll -= deck.IsGridMode ? ListRows : 1; UpdateSliderPosition(deck); - FillList(ref deck); + SetListSlots(ref deck); } - else if (id == IDOpenRunes) - { - deckEntity.Dispose(); - zanzarah.UI.Publish(); - } - else if (id == IDOpenFairybook) - { - deckEntity.Dispose(); - zanzarah.UI.Publish(); - } - else if (id == IDOpenMap) - { - deckEntity.Dispose(); - zanzarah.UI.Publish(); - } - else if (id == IDClose) - deckEntity.Dispose(); + + if (deck.DraggedCardImage != default) + return; + + HandleNavClick(id, zanzarah, deckEntity, IDOpenDeck); } private void UpdateSliderPosition(in components.ui.ScrDeck deck) @@ -576,38 +316,96 @@ private void UpdateSliderPosition(in components.ui.ScrDeck deck) slider = slider with { Current = Vector2.UnitY * Math.Clamp(deck.Scroll / (allCardsCount - 1f), 0, 1f) }; } - protected override void Update( - float elapsedTime, - in DefaultEcs.Entity entity, - ref components.ui.ScrDeck deck) + private void HandleHover(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) + { + var curHovered = World.Has() + ? World.Get() + : default; + + // Still hovering over the same entity + if (curHovered.Entity == deck.LastHovered) return; + + // Unhovered an entity + if (deck.LastHovered != default) + { + if (deck.DraggedCard == default) + if (deck.LastHovered.IsAlive && deck.LastHovered.Has()) + { + var slotEntity = deck.LastHovered.Get().Entity; + ref var slot = ref slotEntity.Get(); + if (slot.type == components.ui.Slot.Type.ListSlot) + UnsetHoverMode(slotEntity); + } + + deck.LastHovered = default; + CreateStats(entity, ref deck); + SetDragOverlayTile(ref deck); + return; + } + + // Hovered an entity + deck.LastHovered = curHovered.Entity; + + if (deck.DraggedCard == default) + if (deck.LastHovered.IsAlive && deck.LastHovered.Has()) + { + var slotEntity = deck.LastHovered.Get().Entity; + ref var slot = ref slotEntity.Get(); + if (slot.type == components.ui.Slot.Type.ListSlot) + SetHoverMode(slotEntity); + } + + CreateStats(entity, ref deck); + SetDragOverlayTile(ref deck); + } + + private void UpdateScroll(DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) { var slider = deck.ListSlider.Get(); var allCardsCount = AllCardsOfType(deck).Count(); - var newScrollI = (int)MathF.Round(slider.Current.Y * (allCardsCount - 1) * (deck.IsGridMode ? ListRows : 1)); + var newScrollI = (int)MathF.Round(slider.Current.Y * (allCardsCount - 1)); if (newScrollI != deck.Scroll) { deck.Scroll = newScrollI; - FillList(ref deck); + SetListSlots(ref deck); } } + protected override void Update(float elapsedTime, in DefaultEcs.Entity entity, ref components.ui.ScrDeck deck) + { + HandleHover(entity, ref deck); + UpdateScroll(entity, ref deck); + Drag(ref deck); + } + protected override void HandleKeyDown(KeyCode key) { var deckEntity = Set.GetEntities()[0]; + ref var deck = ref deckEntity.Get(); base.HandleKeyDown(key); - if (key == KeyCode.KF2) { - deckEntity.Dispose(); - zanzarah.UI.Publish(); - } - if (key == KeyCode.KF3) { - deckEntity.Dispose(); - zanzarah.UI.Publish(); - } - if (key == KeyCode.KF4) { - deckEntity.Dispose(); - zanzarah.UI.Publish(); - } - if (key == KeyCode.KReturn || key == KeyCode.KEscape || key == KeyCode.KF3) - Set.DisposeAll(); + if (deck.DraggedCardImage == default) + HandleNavKeyDown(key, zanzarah, deckEntity, IDOpenDeck); + } + + protected void HandleRightClick() + { + var deckEntity = Set.GetEntities()[0]; + ref var deck = ref deckEntity.Get(); + if (deck.DraggedCard == default) + return; + DropCard(ref deck); + } + + protected override void HandleScroll(float scrollAmount) + { + var deckEntity = Set.GetEntities()[0]; + ref var deck = ref deckEntity.Get(); + + if (scrollAmount < 0) + deck.Scroll += deck.IsGridMode ? ListRows : 1; + else + deck.Scroll -= deck.IsGridMode ? ListRows : 1; + UpdateSliderPosition(deck); + SetListSlots(ref deck); } } diff --git a/zzre/game/systems/ui/ScrMapMenu.cs b/zzre/game/systems/ui/ScrMapMenu.cs index c8996fa4..dcba80aa 100644 --- a/zzre/game/systems/ui/ScrMapMenu.cs +++ b/zzre/game/systems/ui/ScrMapMenu.cs @@ -18,10 +18,6 @@ public ScrMapMenu(ITagContainer diContainer) : base(diContainer, BlockFlags.All) protected override void HandleOpen(in messages.ui.OpenMapMenu message) { - var inventory = zanzarah.CurrentGame!.PlayerEntity.Get(); - if (!inventory.Contains(StdItemId.MapFairyGarden)) - return; - var entity = World.CreateEntity(); entity.Set(); ref var mapMenu = ref entity.Get(); @@ -67,42 +63,13 @@ protected override void Update(float timeElapsed, in DefaultEcs.Entity entity, r private void HandleElementDown(DefaultEcs.Entity entity, components.ui.ElementId id) { var mapMenuEntity = Set.GetEntities()[0]; - if (id == IDOpenDeck) - { - mapMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - else if (id == IDOpenRunes) - { - mapMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - else if (id == IDOpenFairybook) - { - mapMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - else if (id == IDClose) - mapMenuEntity.Dispose(); + HandleNavClick(id, zanzarah, mapMenuEntity, IDOpenMap); } protected override void HandleKeyDown(KeyCode key) { var mapMenuEntity = Set.GetEntities()[0]; base.HandleKeyDown(key); - if (key == KeyCode.KF2) { - mapMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - if (key == KeyCode.KF3) { - mapMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - if (key == KeyCode.KF5) { - mapMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - if (key == KeyCode.KReturn || key == KeyCode.KEscape || key == KeyCode.KF3) - Set.DisposeAll(); + HandleNavKeyDown(key, zanzarah, mapMenuEntity, IDOpenMap); } } diff --git a/zzre/game/systems/ui/ScrRuneMenu.cs b/zzre/game/systems/ui/ScrRuneMenu.cs index b7449621..d83e75ad 100644 --- a/zzre/game/systems/ui/ScrRuneMenu.cs +++ b/zzre/game/systems/ui/ScrRuneMenu.cs @@ -54,10 +54,6 @@ public ScrRuneMenu(ITagContainer diContainer) : base(diContainer, BlockFlags.All protected override void HandleOpen(in messages.ui.OpenRuneMenu message) { - var inventory = zanzarah.CurrentGame!.PlayerEntity.Get(); - if (!inventory.Contains(StdItemId.RuneFairyGarden)) - return; - var entity = World.CreateEntity(); entity.Set(); ref var runeMenu = ref entity.Get(); @@ -145,42 +141,13 @@ private void HandleElementDown(DefaultEcs.Entity entity, components.ui.ElementId runeMenuEntity.Dispose(); zanzarah.CurrentGame!.Publish(new messages.Teleport(runeInfo.scene, targetEntry: -1)); } - else if (id == IDOpenDeck) - { - runeMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - else if (id == IDOpenFairybook) - { - runeMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - else if (id == IDOpenMap) - { - runeMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - else if (id == IDClose) - runeMenuEntity.Dispose(); + else HandleNavClick(id, zanzarah, runeMenuEntity, IDOpenRunes); } protected override void HandleKeyDown(KeyCode key) { var runeMenuEntity = Set.GetEntities()[0]; base.HandleKeyDown(key); - if (key == KeyCode.KF3) { - runeMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - if (key == KeyCode.KF4) { - runeMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - if (key == KeyCode.KF5) { - runeMenuEntity.Dispose(); - zanzarah.UI.Publish(); - } - if (key == KeyCode.KReturn || key == KeyCode.KEscape || key == KeyCode.KF3) - Set.DisposeAll(); + HandleNavKeyDown(key, zanzarah, runeMenuEntity, IDOpenRunes); } } diff --git a/zzre/game/systems/ui/UIScript.Execute.cs b/zzre/game/systems/ui/UIScript.Execute.cs new file mode 100644 index 00000000..c92b1353 --- /dev/null +++ b/zzre/game/systems/ui/UIScript.Execute.cs @@ -0,0 +1,26 @@ +using System.Linq; +using zzio.script; + +namespace zzre.game.systems.ui; + +partial class UIScript +{ + private const string CmdModifyWizform = ";"; + private const string CmdIfIsWizform = "D"; + + protected override OpReturn Execute(in DefaultEcs.Entity entity, ref components.ScriptExecution script, RawInstruction instruction) + { + var args = instruction.Arguments; + ref var uiScript = ref entity.Get(); + switch (instruction.Command) + { + case CmdModifyWizform: + uiScript.ItemConsumed = ModifyWizform(entity, (ModifyWizformType)int.Parse(args[0]), int.Parse(args[1])); + return OpReturn.Continue; + case CmdIfIsWizform: + return IfIsWizform(entity, int.Parse(args.Single())) ? OpReturn.Continue : OpReturn.ConditionalSkip; + default: + return OpReturn.UnknownInstruction; + } + } +} diff --git a/zzre/game/systems/ui/UIScript.cs b/zzre/game/systems/ui/UIScript.cs new file mode 100644 index 00000000..12de4da1 --- /dev/null +++ b/zzre/game/systems/ui/UIScript.cs @@ -0,0 +1,143 @@ +using System; +using System.Linq; +using DefaultEcs.System; +using DefaultEcs.Command; +using zzio.db; +using zzio; + +namespace zzre.game.systems.ui; + +public partial class UIScript : BaseScript +{ + private readonly MappedDB db; + private readonly EntityCommandRecorder recorder; + private readonly IDisposable executeUIScriptDisposable; + protected readonly Zanzarah zanzarah; + protected readonly UI ui; + protected Inventory inventory => zanzarah.CurrentGame!.PlayerEntity.Get(); + + public enum ModifyWizformType + { + Heal = 0, + AddXP = 1, + ClearStatusEffects = 2, + Transform = 7, + AddNearLevelXP = 8, + // 9-13: Set Unknown + // 14: Add Unknown + Revive = 16, + FillMana = 17, + Rename = 18, + } + + private void PlayChime() + { + World.Publish(new messages.SpawnSample("resources/audio/sfx/specials/_s021.wav")); + } + + public UIScript(ITagContainer diContainer) : base(diContainer, CreateEntityContainer) + { + db = diContainer.GetTag(); + zanzarah = diContainer.GetTag(); + ui = diContainer.GetTag(); + recorder = diContainer.GetTag(); + executeUIScriptDisposable = World.Subscribe(HandleExecuteUIScript); + } + + public override void Dispose() + { + base.Dispose(); + executeUIScriptDisposable.Dispose(); + } + + private bool ModifyWizform(DefaultEcs.Entity scriptEntity, ModifyWizformType type, int value) + { + ref var script = ref scriptEntity.Get(); + ref var slot = ref script.DeckSlotEntity.Get(); + var fairy = (InventoryFairy)slot.card!; + + switch (type) + { + case ModifyWizformType.Heal: + if (fairy.currentMHP == fairy.maxMHP) return false; + PlayChime(); + fairy.currentMHP += (uint)value; + if (fairy.currentMHP > fairy.maxMHP) + fairy.currentMHP = fairy.maxMHP; + return true; + case ModifyWizformType.AddXP: + // TODO: is there a chime? + inventory.AddXP(fairy, (uint)value); + return true; + case ModifyWizformType.ClearStatusEffects: + PlayChime(); + fairy.status = ZZPermSpellStatus.None; + return true; + case ModifyWizformType.Transform: + fairy.cardId = new CardId(CardType.Fairy, value); + fairy.dbUID = db.GetFairy(value).Uid; + // TODO: correctly handle evolution, including name, fairy stats + return true; + case ModifyWizformType.AddNearLevelXP: + PlayChime(); + var nearLevel = inventory.GetLevelupXP(fairy) - fairy.xp; + if (nearLevel != null) + inventory.AddXP(fairy, (uint)nearLevel); + // TODO: investigate golden carrot behaviour on level 59 & 60 + return true; + case ModifyWizformType.Revive: + if (fairy.currentMHP != 0) + { + ui.Publish(new messages.ui.Notification(db.GetText(new UID(0x3D422781)).Text)); + return false; + } + // TODO: is there a chime? + // TODO: Determine the correct revive hp factor + fairy.currentMHP = (uint)(fairy.maxMHP * 0.5); + return true; + case ModifyWizformType.FillMana: + PlayChime(); + inventory.FillMana(fairy); + return true; + case ModifyWizformType.Rename: + Console.WriteLine($"Not implemented: ModifyWizform: Rename"); + return false; + default: + throw new NotImplementedException($"Unimplemented ModifyWizformType: {type}"); + } + } + + private static bool IfIsWizform(DefaultEcs.Entity scriptEntity, int fairyI) + { + ref var script = ref scriptEntity.Get(); + ref var slot = ref script.DeckSlotEntity.Get(); + if (slot.card!.cardId.EntityId == fairyI) + return true; + return false; + } + + private void HandleExecuteUIScript(in messages.ui.ExecuteUIScript message) + { + var scriptEntity = World.CreateEntity(); + scriptEntity.Set(new components.ui.UIScript(message.DeckSlotEntity, false)); + var scriptEntityRecord = recorder.Record(scriptEntity); + scriptEntityRecord.Set(new components.ScriptExecution(db.GetItem(message.Item.dbUID).Script)); + } + + [Update] + private void Update(in DefaultEcs.Entity scriptEntity, ref components.ScriptExecution execution) + { + Continue(scriptEntity, ref execution); + ref var script = ref scriptEntity.Get(); + + if (!script.ItemConsumed) + { + World.Publish(new messages.SpawnSample("resources/audio/sfx/gui/_g013.wav")); + if (World.GetEntities().AsEnumerable().All(entity => !entity.Has())) + // Default failure notification if no notification was thrown during script execution + ui.Publish(new messages.ui.Notification(db.GetText(new UID(0xB5E90B81)).Text)); + } + World.Publish(new messages.ui.UIScriptFinished(script.DeckSlotEntity, script.ItemConsumed)); + scriptEntity.Dispose(); + } +} diff --git a/zzre/game/uibuilder/UIBuilder.cs b/zzre/game/uibuilder/UIBuilder.cs index e8ee2035..71bad71f 100644 --- a/zzre/game/uibuilder/UIBuilder.cs +++ b/zzre/game/uibuilder/UIBuilder.cs @@ -166,6 +166,7 @@ public static string GetLightsIndicator(int value) ZZClass.Dark => GetDBText(new UID(0x8313DCA1)), // Dark ZZClass.Chaos => GetDBText(new UID(0xC659DCA1)), // Chaos ZZClass.Metal => GetDBText(new UID(0x3CE1DCA1)), // Metal + ZZClass.Neutral => GetDBText(new UID(0x8B6BDCA1)), // Neutral _ => throw new ArgumentException($"Unknown spell class: {zzClass}") }; } diff --git a/zzre/tools/ECSExplorer/ECSExplorer.Standard.cs b/zzre/tools/ECSExplorer/ECSExplorer.Standard.cs index fc1d35bf..676bda32 100644 --- a/zzre/tools/ECSExplorer/ECSExplorer.Standard.cs +++ b/zzre/tools/ECSExplorer/ECSExplorer.Standard.cs @@ -1,4 +1,4 @@ -using System; +using System; using zzre.game.components; using zzre.game.components.ui; using zzre.materials; @@ -69,6 +69,7 @@ private static void AddStandardEntityNaming() AddEntityNamerByComponent(High, e => $"Tooltip Target {e}"); AddEntityNamerByComponent(High, e => $"Fade {e}"); AddEntityNamerByComponent(High, e => $"Slider #{e.TryGet().GetValueOrDefault(default).Value} {e}"); + AddEntityNamerByComponent(High, e => $"{e.TryGet().GetValueOrDefault(default).type} #{e.TryGet().GetValueOrDefault(default).index} {e}"); AddEntityNamerByComponent(High, (in AnimatedLabel label) => $"Anim. Label \"{Sanitize(label.FullText)}\""); AddEntityNamerByComponent(Def, (in Label label) => $"Label \"{Sanitize(label.Text)}\""); AddEntityNamerByComponent(Low, e => $"Visuals {e}"); diff --git a/zzre/tools/ZanzarahWindow.cs b/zzre/tools/ZanzarahWindow.cs index a3db16a7..7ff19c0b 100644 --- a/zzre/tools/ZanzarahWindow.cs +++ b/zzre/tools/ZanzarahWindow.cs @@ -73,6 +73,12 @@ public event Action OnMouseUp remove => mouseArea.OnButtonUp -= value; } + public event Action OnScroll + { + add => mouseArea.OnScroll += value; + remove => mouseArea.OnScroll -= value; + } + public event Action OnMouseMove { add => onMouseMove += value;