Skip to content

Commit

Permalink
zzre: add DialogChestPuzzle (#331)
Browse files Browse the repository at this point in the history
* ChestPuzzle: dialog template

* Fix cancel button

* Temporary succeed button

* Complete layout

* Correctly handle size variable

* Ugly opacity api

* Add board state functionality

* style: IDE0300

* Add win condition

* Minor fixes

* Tile buttons seamlessly

* Make buttons silent

* Load and save min tries

* Update Label instead of recreating entity

* Use static

* Use ButtonTiles Active component
  • Loading branch information
AcipenserSturio authored Mar 4, 2024
1 parent 5d803e5 commit 1c861e2
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 14 deletions.
1 change: 1 addition & 0 deletions zzre/game/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ public Game(ITagContainer diContainer, Savegame savegame)
new systems.DialogChoice(this),
new systems.DialogTrading(this),
new systems.DialogGambling(this),
new systems.DialogChestPuzzle(this),

new systems.NonFairyAnimation(this),
new systems.AmbientSounds(this),
Expand Down
15 changes: 15 additions & 0 deletions zzre/game/components/dialog/DialogChestPuzzle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

namespace zzre.game.components;

public struct DialogChestPuzzle
{
public DefaultEcs.Entity DialogEntity;
public int Size;
public int LabelExit;
public uint NumAttempts;
public bool LockBoard;
public Rect BgRect;
public DefaultEcs.Entity[] Cells;
public DefaultEcs.Entity Attempts;
public DefaultEcs.Entity Action;
}
1 change: 1 addition & 0 deletions zzre/game/components/dialog/DialogState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public enum DialogState
Choice,
Trading,
Gambling,
ChestPuzzle,
PreFightWild,
PreFightNpc,
NpcWalking,
Expand Down
4 changes: 2 additions & 2 deletions zzre/game/components/ui/Fade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ public record struct Fade(
float OutDuration,
float CurrentTime = 0f)
{
public static Fade SingleIn(float duration) => new(
public static Fade SingleIn(float duration, float to = 0.8f) => new(
From: 0f,
To: 0.8f,
To: to,
StartDelay: 0f,
InDuration: duration,
SustainDelay: float.PositiveInfinity,
Expand Down
3 changes: 3 additions & 0 deletions zzre/game/components/ui/Silent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace zzre.game.components.ui;

public readonly record struct Silent();
8 changes: 8 additions & 0 deletions zzre/game/messages/dialog/DialogChestPuzzle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

namespace zzre.game.messages;

public record struct DialogChestPuzzle(
DefaultEcs.Entity DialogEntity,
int Size,
int LabelExit
);
198 changes: 198 additions & 0 deletions zzre/game/systems/dialog/DialogChestPuzzle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
using System;
using System.Linq;
using System.Numerics;
using zzio;
using zzio.db;

namespace zzre.game.systems;

public partial class DialogChestPuzzle : ui.BaseScreen<components.DialogChestPuzzle, messages.DialogChestPuzzle>
{
private static readonly components.ui.ElementId IDCancel = new(1000);
private static readonly components.ui.ElementId IDNext = new(1001);

private static readonly UID UIDCancel = new(0xD45B15B1);
private static readonly UID UIDNext = new(0xCABAD411);

private static readonly UID UIDAttempts = new(0x7B48CC11);
private static readonly UID UIDMinTries = new(0xEE63D011);
private static readonly UID UIDBoxOfTricks = new(0x6588C491);
private static readonly UID UIDChestOpened = new(0xF798C91);

private readonly MappedDB db;
private readonly zzio.Savegame savegame;
private readonly IDisposable resetUISubscription;

public DialogChestPuzzle(ITagContainer diContainer) : base(diContainer, BlockFlags.None)
{
db = diContainer.GetTag<MappedDB>();
savegame = diContainer.GetTag<zzio.Savegame>();

resetUISubscription = World.Subscribe<messages.DialogResetUI>(HandleResetUI);
OnElementDown += HandleElementDown;
}

public override void Dispose()
{
base.Dispose();
resetUISubscription.Dispose();
}

private void HandleResetUI(in messages.DialogResetUI message)
{
foreach (var entity in Set.GetEntities())
entity.Dispose();
}

protected override void HandleOpen(in messages.DialogChestPuzzle message)
{
message.DialogEntity.Set(components.DialogState.ChestPuzzle);

World.Publish(new messages.DialogResetUI(message.DialogEntity));
var uiEntity = World.CreateEntity();
uiEntity.Set(new components.Parent(message.DialogEntity));

preload.CreateDialogBackground(uiEntity, animateOverlay: true, out var bgRect, opacity: 1f);

uiEntity.Set(new components.DialogChestPuzzle{
DialogEntity = message.DialogEntity,
Size = message.Size,
LabelExit = message.LabelExit,
NumAttempts = 0,
Cells = new DefaultEcs.Entity[message.Size*message.Size],
BgRect = bgRect
});
ref var puzzle = ref uiEntity.Get<components.DialogChestPuzzle>();

preload.CreateLabel(uiEntity)
.With(puzzle.BgRect.Min + new Vector2(20, 20))
.With(preload.Fnt001)
.WithText(db.GetText(UIDBoxOfTricks).Text)
.Build();

CreateBoard(uiEntity, ref puzzle);
puzzle.Attempts = preload.CreateLabel(uiEntity)
.With(puzzle.BgRect.Min + new Vector2(25, 120))
.With(preload.Fnt000)
.WithText(FormatAttempts(ref puzzle))
.WithLineHeight(15)
.Build();
puzzle.Action = preload.CreateSingleDialogButton(uiEntity, UIDCancel, IDCancel, puzzle.BgRect, buttonOffsetY: -45f);
}

private string FormatAttempts(ref components.DialogChestPuzzle puzzle) =>
$"{db.GetText(UIDAttempts).Text}: {puzzle.NumAttempts}\n{db.GetText(UIDMinTries).Text}: {savegame.switchGameMinMoves}";

private DefaultEcs.Entity CreateBoard(DefaultEcs.Entity parent, ref components.DialogChestPuzzle puzzle)
{
var entity = World.CreateEntity();
entity.Set(new components.Parent(parent));

var offset = new Vector2(-1, -1) + new Vector2(-23, -23) * puzzle.Size;

for (int row = 0; row < puzzle.Size; row++)
{
for (int col = 0; col < puzzle.Size; col++)
{
var cell = row * puzzle.Size + col;
var IDCell = new components.ui.ElementId(cell);
var button = preload.CreateButton(entity)
.With(IDCell)
.With(offset + new Vector2(46 * col, 46 * row))
.With(new components.ui.ButtonTiles(1, Active: 2))
.With(preload.Swt000)
.Build();
button.Set(button.Get<Rect>().GrownBy(new Vector2(1, 1))); // No gaps
button.Set(new components.ui.Silent());
if (cell % 2 == 0) button.Set(new components.ui.Active());
puzzle.Cells[cell] = button;
}
}

return entity;
}

private static readonly (int row, int col)[] flipped = [
(0, 0),
(-1, 0),
(0, -1),
(1, 0),
(0, 1)
];

private void UpdateBoard(DefaultEcs.Entity parent, ref components.DialogChestPuzzle puzzle, int cellId)
{
puzzle.NumAttempts += 1;
World.Publish(new messages.SpawnSample("resources/audio/sfx/gui/_g000.wav"));

int row = cellId / puzzle.Size;
int col = cellId % puzzle.Size;

foreach (var coord in flipped)
{
if (coord.row + row < puzzle.Size && coord.row + row >= 0 &&
coord.col + col < puzzle.Size && coord.col + col >= 0)
{
var cell = (coord.row + row) * puzzle.Size + (coord.col + col);
if (puzzle.Cells[cell].TryGet<components.ui.Active>(out var _))
puzzle.Cells[cell].Remove<components.ui.Active>();
else
puzzle.Cells[cell].Set(new components.ui.Active());
}
}

if (puzzle.Cells.All(x => x.Has<components.ui.Active>()) || puzzle.Cells.All(x => !x.Has<components.ui.Active>()))
Succeed(parent, ref puzzle);

puzzle.Attempts.Set(new components.ui.Label(FormatAttempts(ref puzzle)));
}

private void Succeed(DefaultEcs.Entity parent, ref components.DialogChestPuzzle puzzle)
{
World.Publish(new messages.SpawnSample($"resources/audio/sfx/specials/_s022.wav"));

preload.CreateLabel(parent)
.With(new Vector2(0, -126))
.With(components.ui.FullAlignment.Center)
.With(preload.Fnt001)
.WithText(db.GetText(UIDChestOpened).Text)
.Build();

if (savegame.switchGameMinMoves > puzzle.NumAttempts)
savegame.switchGameMinMoves = puzzle.NumAttempts;

puzzle.LockBoard = true;

puzzle.Action.Dispose();
puzzle.Action = preload.CreateSingleDialogButton(parent, UIDNext, IDNext, puzzle.BgRect, buttonOffsetY: -45f);
}

private static readonly components.ui.ElementId FirstCellId = new(0);

private void HandleElementDown(DefaultEcs.Entity entity, components.ui.ElementId clickedId)
{
var uiEntity = Set.GetEntities()[0];
ref var puzzle = ref uiEntity.Get<components.DialogChestPuzzle>();
ref var script = ref puzzle.DialogEntity.Get<components.ScriptExecution>();

if (!puzzle.LockBoard && clickedId.InRange(FirstCellId, FirstCellId + puzzle.Size*puzzle.Size, out var cellId)) {
UpdateBoard(uiEntity, ref puzzle, cellId);
}
else if (clickedId == IDCancel)
{
World.Publish(new messages.SpawnSample($"resources/audio/sfx/gui/_g003.wav"));
puzzle.DialogEntity.Set(components.DialogState.NextScriptOp);
uiEntity.Dispose();
}
else if (clickedId == IDNext)
{
script.CurrentI = script.LabelTargets[puzzle.LabelExit];
puzzle.DialogEntity.Set(components.DialogState.NextScriptOp);
uiEntity.Dispose();
}
}

protected override void Update(float timeElapsed, in DefaultEcs.Entity entity, ref components.DialogChestPuzzle component)
{
}
}
2 changes: 1 addition & 1 deletion zzre/game/systems/dialog/DialogGambling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ private void RebuildPrimary(DefaultEcs.Entity parent, ref components.DialogGambl
private void AddBottomButtons(DefaultEcs.Entity parent, ref components.DialogGambling gambling)
{
if (CanAfford(ref gambling))
preload.CreateSingleDialogButton(parent, UIDRepeat, IDRepeat, gambling.BgRect, offset: 1);
preload.CreateSingleDialogButton(parent, UIDRepeat, IDRepeat, gambling.BgRect, buttonOffsetY: -90f);
preload.CreateSingleDialogButton(parent, UIDExit, IDExit, gambling.BgRect);
}

Expand Down
11 changes: 10 additions & 1 deletion zzre/game/systems/dialog/DialogScript.cs
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,16 @@ private void EndGame(DefaultEcs.Entity entity)

private void SubGame(DefaultEcs.Entity entity, SubGameType subGameType, int size, int labelExit)
{
LogUnimplementedInstructionWarning();
switch (subGameType) {
case SubGameType.ChestPuzzle:
World.Publish(new messages.DialogChestPuzzle(entity, size + 2, labelExit));
break;
case SubGameType.ElfGame:
LogUnimplementedInstructionWarning();
break;
default:
throw new NotSupportedException($"Unsupported SubGameType: {subGameType}");
};
}

private void PlayAnimation(DefaultEcs.Entity entity, AnimationType animation)
Expand Down
3 changes: 2 additions & 1 deletion zzre/game/systems/ui/Cursor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ private void Update(
if (!entity.Has<components.ui.Hovered>())
{
entity.Set<components.ui.Hovered>();
World.Publish(new messages.SpawnSample("resources/audio/sfx/gui/_g000.wav"));
if (!entity.Has<components.ui.Silent>())
World.Publish(new messages.SpawnSample("resources/audio/sfx/gui/_g000.wav"));
}
hoveredElement = new(entity, elementId);
}
Expand Down
20 changes: 11 additions & 9 deletions zzre/game/systems/ui/UIPreloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public class UIPreloader
Log000,
Cls000,
Cls001,
Map000;
Map000,
Swt000;

private static readonly UID UIDYouHave = new(0x070EE421);

Expand Down Expand Up @@ -70,6 +71,7 @@ public UIPreloader(ITagContainer diContainer)
Cls000 = Preload(out var tsCls000, "cls000", isFont: false);
Cls001 = Preload(out var tsCls001, "cls001", isFont: false);
Map000 = Preload(out var tsMap000, "map000", isFont: false);
Swt000 = Preload(out var tsSwt000, "swt000", isFont: false);

tsFnt000.Alternatives.Add(tsFnt001);
tsFnt000.Alternatives.Add(tsFnt002);
Expand Down Expand Up @@ -153,7 +155,8 @@ public UIPreloader(ITagContainer diContainer)
public void CreateDialogBackground(
DefaultEcs.Entity parent,
bool animateOverlay,
out Rect backgroundRect)
out Rect backgroundRect,
float opacity = 0.8f)
{
var image = CreateImage(parent)
.WithBitmap("std000")
Expand All @@ -162,20 +165,21 @@ public void CreateDialogBackground(
.Build();
backgroundRect = image.Get<Rect>();

CreateBackOverlay(parent, animateOverlay, backgroundRect);
CreateBackOverlay(parent, animateOverlay, opacity, backgroundRect);
}

public void CreateBackOverlay(
DefaultEcs.Entity parent,
bool animateOverlay,
float opacity,
Rect backgroundRect)
{
var overlay = CreateImage(parent)
.With(DefaultOverlayColor with { a = animateOverlay ? 0f : 0.8f })
.With(DefaultOverlayColor with { a = animateOverlay ? 0f : opacity })
.With(backgroundRect)
.WithRenderOrder(2);
if (animateOverlay)
overlay.With(components.ui.Fade.SingleIn(0.8f));
overlay.With(components.ui.Fade.SingleIn(0.8f, opacity));
overlay.Build();
}

Expand Down Expand Up @@ -214,13 +218,11 @@ public DefaultEcs.Entity CreateCurrencyLabel(DefaultEcs.Entity parent, ItemRow c
.WithText($"{GetDBText(UIDYouHave)} {{{3000 + currency.CardId.EntityId}}}x{inventory.CountCards(currency.CardId)}")
.Build();

private const float ButtonOffsetY = -50f;
private const float RepeatButtonOffsetY = -40f;
public DefaultEcs.Entity CreateSingleDialogButton(DefaultEcs.Entity entity, UID textUID, components.ui.ElementId elementId, Rect bgRect, int offset = 0)
public DefaultEcs.Entity CreateSingleDialogButton(DefaultEcs.Entity entity, UID textUID, components.ui.ElementId elementId, Rect bgRect, float buttonOffsetY = -50f)
{
var button = CreateButton(entity)
.With(elementId)
.With(new Vector2(bgRect.Center.X, bgRect.Max.Y + ButtonOffsetY + RepeatButtonOffsetY * offset))
.With(new Vector2(bgRect.Center.X, bgRect.Max.Y + buttonOffsetY))
.With(new components.ui.ButtonTiles(0, 1))
.With(components.ui.FullAlignment.TopCenter)
.With(Btn000)
Expand Down

0 comments on commit 1c861e2

Please sign in to comment.