168 lines
13 KiB
Plaintext
168 lines
13 KiB
Plaintext
@page "/"
|
||
@inject GameService Game
|
||
|
||
<PageTitle>NULL DEPTH</PageTitle>
|
||
|
||
<main class="game-shell" tabindex="0" @ref="_shell" @onkeydown="HandleKey" @onkeydown:preventDefault>
|
||
@if (!_ready)
|
||
{
|
||
<section class="loading-screen"><span>NULL DEPTH // BOOTING</span></section>
|
||
}
|
||
else if (_screen == Screen.Title)
|
||
{
|
||
<section class="title-screen screen-image" style="background-image:url('assets/images/title/title_null_depth.webp')">
|
||
<div class="vignette"></div>
|
||
<div class="title-card"><p class="eyebrow">FACILITY 03</p><h1>NULL DEPTH</h1><p class="subtitle">SUBLEVEL // 03</p></div>
|
||
<nav class="title-menu" aria-label="Main menu">
|
||
<button class="@MenuClass(0)" @onclick="StartNewGame">@Game.T("menu.newGame")</button>
|
||
<button class="@MenuClass(1)" disabled="@(!Game.HasSave)" @onclick="ContinueGame">@Game.T("menu.continue")</button>
|
||
<button class="@MenuClass(2)" @onclick="() => _showSettings = true">@Game.T("menu.settings")</button>
|
||
<small>@Game.T("menu.controlsHint")</small>
|
||
</nav>
|
||
</section>
|
||
}
|
||
else if (_screen == Screen.Briefing)
|
||
{
|
||
<section class="briefing-screen screen-image" style="background-image:url('assets/images/title/title_null_depth.webp')">
|
||
<div class="vignette"></div>
|
||
<article class="terminal-card briefing-card">
|
||
<p class="eyebrow">@Game.T("briefing.header")</p>
|
||
<dl>
|
||
<dt>@Game.T("briefing.clientLabel")</dt><dd>VANTAGE SYSTEMS</dd>
|
||
<dt>@Game.T("briefing.siteLabel")</dt><dd>FACILITY 03</dd>
|
||
<dt>@Game.T("briefing.assignmentLabel")</dt><dd>@Game.T("briefing.assignment")</dd>
|
||
<dt>@Game.T("briefing.compensationLabel")</dt><dd>18,000</dd>
|
||
<dt>@Game.T("briefing.infoLabel")</dt><dd>@Game.T("briefing.none")</dd>
|
||
</dl>
|
||
<p>@Game.T("briefing.story")</p>
|
||
<button class="primary-action" @onclick="BeginPlay">@Game.T("common.continue")</button>
|
||
</article>
|
||
</section>
|
||
}
|
||
else if (_screen == Screen.Game)
|
||
{
|
||
var room = Game.CurrentRoom;
|
||
<section class="play-screen screen-image" style="background-image:url('@Game.RoomImage(room)')">
|
||
<div class="vignette"></div>
|
||
<header class="top-hud"><span>NULL DEPTH</span><span>@Game.T("game.autosave")</span></header>
|
||
<article class="room-panel">
|
||
<p class="eyebrow">@room.Id.Replace("_", " // ").ToUpperInvariant()</p><h2>@Game.T(room.NameKey)</h2><p>@Game.T(room.DescriptionKey)</p>
|
||
@if (!string.IsNullOrWhiteSpace(_notice)) { <p class="notice">@_notice</p> }
|
||
<div class="interaction-list">@foreach (var action in Game.AvailableInteractions()) { <button @onclick="() => OpenInteraction(action)"><kbd>E</kbd> @Game.T(action.LabelKey)</button> }</div>
|
||
</article>
|
||
<nav class="movement-pad" aria-label="Movement">
|
||
<button class="north" disabled="@(!HasNorth)" @onclick="MoveNorth">↑</button>
|
||
<button class="west" disabled="@(!HasWest)" @onclick="MoveWest">←</button><span class="position">@room.X:@room.Y</span>
|
||
<button class="east" disabled="@(!HasEast)" @onclick="MoveEast">→</button>
|
||
<button class="south" disabled="@(!HasSouth)" @onclick="MoveSouth">↓</button>
|
||
</nav>
|
||
<footer class="command-bar">
|
||
<button @onclick="OpenFirstInteraction"><kbd>E</kbd> @Game.T("game.interact")</button><button @onclick="() => _showMap = true"><kbd>M</kbd> @Game.T("game.map")</button>
|
||
<button @onclick="() => _showInventory = true"><kbd>I</kbd> @Game.T("game.inventory")</button><button @onclick="() => _showPause = true"><kbd>ESC</kbd> @Game.T("game.pause")</button>
|
||
</footer>
|
||
</section>
|
||
}
|
||
else if (_screen == Screen.Death)
|
||
{
|
||
<section class="outcome-screen death-screen"><p class="eyebrow">FACILITY EVENT // FATAL</p><h1>@Game.T("death.title")</h1><p>@Game.T("death.body")</p><button class="primary-action" @onclick="RestoreCheckpoint">@Game.T("death.restore")</button><button @onclick="ReturnToTitle">@Game.T("common.mainMenu")</button></section>
|
||
}
|
||
else if (_screen == Screen.Ending)
|
||
{
|
||
<section class="outcome-screen ending-screen"><p class="eyebrow">ENDING // @_ending?.ToUpperInvariant()</p><h1>@Game.T($"ending.{_ending}.title")</h1><p>@Game.T($"ending.{_ending}.body")</p><button class="primary-action" @onclick="ReturnToTitle">@Game.T("ending.return")</button></section>
|
||
}
|
||
|
||
@if (_interaction is not null)
|
||
{
|
||
<div class="modal-layer" role="dialog" aria-modal="true"><article class="terminal-card interaction-modal">
|
||
<button class="close-button" aria-label="Close" @onclick="CloseOverlays">×</button><p class="eyebrow">@_interaction.Type.ToUpperInvariant() // @_interaction.Id.ToUpperInvariant()</p><h2>@Game.T(_interaction.LabelKey)</h2><p class="terminal-copy">@Game.T(_interaction.ContentKey)</p>
|
||
@if (_interaction.Choices.Count > 0) { <div class="choice-list">@foreach (var choice in _interaction.Choices.Where(c => Game.HasAll(c.RequiresFlags))) { <button @onclick="() => ResolveChoice(choice)">@Game.T(choice.LabelKey)</button> }</div> }
|
||
else { <button class="primary-action" @onclick="ResolveInteraction">@Game.T("common.confirm")</button> }
|
||
</article></div>
|
||
}
|
||
@if (_showMap)
|
||
{
|
||
<div class="modal-layer" role="dialog" aria-modal="true"><article class="overlay-card map-card"><button class="close-button" aria-label="Close" @onclick="CloseOverlays">×</button><p class="eyebrow">FACILITY 03 // @Game.T("game.map")</p><h2>@Game.T("map.title")</h2>
|
||
<div class="map-grid">@foreach (var mapRoom in Game.Data.Rooms) { var visited = Game.State.VisitedRooms.Contains(mapRoom.Id); <div class="map-cell @(visited ? "visited" : "unknown") @(mapRoom.Id == Game.State.CurrentRoomId ? "current" : "")" style="grid-column:@(mapRoom.X + 1);grid-row:@(mapRoom.Y + 1)" title="@(visited ? Game.T(mapRoom.NameKey) : Game.T("map.unknown"))">@(visited ? (mapRoom.Id == Game.State.CurrentRoomId ? "●" : "□") : "?")</div> }</div><p>@Game.T("map.visited"): @Game.State.VisitedRooms.Count / @Game.Data.Rooms.Count</p>
|
||
</article></div>
|
||
}
|
||
@if (_showInventory)
|
||
{
|
||
<div class="modal-layer" role="dialog" aria-modal="true"><article class="overlay-card inventory-card"><button class="close-button" aria-label="Close" @onclick="CloseOverlays">×</button><p class="eyebrow">ELIAS VENN // LOADOUT</p><h2>@Game.T("inventory.title")</h2>@if (Game.State.Inventory.Count == 0) { <p>@Game.T("inventory.empty")</p> } else { <ul>@foreach (var item in Game.State.Inventory) { <li>@Game.T($"item.{item}")</li> }</ul> }</article></div>
|
||
}
|
||
@if (_showPause)
|
||
{
|
||
<div class="modal-layer" role="dialog" aria-modal="true"><article class="overlay-card pause-card"><p class="eyebrow">SYSTEM // PAUSED</p><h2>@Game.T("pause.title")</h2><button class="primary-action" @onclick="CloseOverlays">@Game.T("pause.resume")</button><button @onclick="() => _showSettings = true">@Game.T("menu.settings")</button><button @onclick="ReturnToTitle">@Game.T("common.mainMenu")</button></article></div>
|
||
}
|
||
@if (_showSettings)
|
||
{
|
||
<div class="modal-layer settings-layer" role="dialog" aria-modal="true"><article class="overlay-card settings-card"><button class="close-button" aria-label="Close" @onclick="CloseSettings">×</button><p class="eyebrow">SYSTEM // CONFIGURATION</p><h2>@Game.T("settings.title")</h2>
|
||
<label>@Game.T("settings.language")</label><div class="segmented-control"><button class="@(Game.Settings.Language == "sk" ? "active" : "")" @onclick="ChangeToSlovak">Slovenčina</button><button class="@(Game.Settings.Language == "en" ? "active" : "")" @onclick="ChangeToEnglish">English</button></div>
|
||
<label for="volume">@Game.T("settings.volume") — @Game.Settings.MasterVolume%</label><input id="volume" type="range" min="0" max="100" step="5" @bind="Game.Settings.MasterVolume" @bind:event="oninput" @onchange="SaveSettings" /><button class="primary-action" @onclick="CloseSettings">@Game.T("common.back")</button>
|
||
</article></div>
|
||
}
|
||
</main>
|
||
|
||
@code {
|
||
private ElementReference _shell;
|
||
private bool _ready;
|
||
private Screen _screen = Screen.Title;
|
||
private int _menuIndex;
|
||
private string? _notice;
|
||
private string? _ending;
|
||
private InteractionDefinition? _interaction;
|
||
private bool _showMap;
|
||
private bool _showInventory;
|
||
private bool _showPause;
|
||
private bool _showSettings;
|
||
|
||
protected override async Task OnInitializedAsync() { await Game.InitializeAsync(); _ready = true; }
|
||
protected override async Task OnAfterRenderAsync(bool firstRender) { if (_ready) await _shell.FocusAsync(); }
|
||
private async Task StartNewGame() { await Game.NewGameAsync(); _screen = Screen.Briefing; _notice = null; }
|
||
private async Task ContinueGame() { if (await Game.ContinueAsync()) _screen = Screen.Game; }
|
||
private void BeginPlay() => _screen = Screen.Game;
|
||
private async Task Move(string direction) { var result = await Game.MoveAsync(direction); if (result is not null) _notice = result; if (result == "") _notice = null; }
|
||
private Task MoveNorth() => Move("north");
|
||
private Task MoveSouth() => Move("south");
|
||
private Task MoveWest() => Move("west");
|
||
private Task MoveEast() => Move("east");
|
||
private bool HasNorth => Game.CurrentRoom.Exits.ContainsKey("north");
|
||
private bool HasSouth => Game.CurrentRoom.Exits.ContainsKey("south");
|
||
private bool HasWest => Game.CurrentRoom.Exits.ContainsKey("west");
|
||
private bool HasEast => Game.CurrentRoom.Exits.ContainsKey("east");
|
||
private void OpenFirstInteraction() { var first = Game.AvailableInteractions().FirstOrDefault(); if (first is not null) OpenInteraction(first); }
|
||
private void OpenInteraction(InteractionDefinition interaction) { _interaction = interaction; _notice = null; }
|
||
private async Task ResolveInteraction() { if (_interaction is null) return; await Game.ApplyInteractionAsync(_interaction); _notice = Game.T(_interaction.ContentKey); _interaction = null; }
|
||
private async Task ResolveChoice(ChoiceDefinition choice) { await Game.ApplyChoiceAsync(choice); _interaction = null; if (choice.Death) { _screen = Screen.Death; return; } if (!string.IsNullOrWhiteSpace(choice.Ending)) { _ending = choice.Ending; _screen = Screen.Ending; return; } _notice = choice.ResultKey is null ? null : Game.T(choice.ResultKey); }
|
||
private async Task RestoreCheckpoint() { await Game.RestoreCheckpointAsync(); _screen = Screen.Game; _notice = Game.T("death.restored"); }
|
||
private void ReturnToTitle() { CloseOverlays(); _showSettings = false; _screen = Screen.Title; _menuIndex = 0; }
|
||
private async Task ChangeLanguage(string language) { await Game.SetLanguageAsync(language); StateHasChanged(); }
|
||
private Task ChangeToSlovak() => ChangeLanguage("sk");
|
||
private Task ChangeToEnglish() => ChangeLanguage("en");
|
||
private async Task SaveSettings(ChangeEventArgs _) => await Game.SaveSettingsAsync();
|
||
private async Task CloseSettings() { await Game.SaveSettingsAsync(); _showSettings = false; }
|
||
private void CloseOverlays() { _interaction = null; _showMap = false; _showInventory = false; _showPause = false; }
|
||
private string MenuClass(int index) => _menuIndex == index ? "selected" : "";
|
||
|
||
private async Task HandleKey(KeyboardEventArgs args)
|
||
{
|
||
if (!_ready) return;
|
||
if (_showSettings) { if (args.Key == "Escape") await CloseSettings(); return; }
|
||
if (_interaction is not null || _showMap || _showInventory) { if (args.Key == "Escape") CloseOverlays(); return; }
|
||
if (_showPause) { if (args.Key == "Escape") CloseOverlays(); return; }
|
||
if (_screen == Screen.Title)
|
||
{
|
||
if (args.Key is "ArrowDown" or "ArrowUp") { var delta = args.Key == "ArrowDown" ? 1 : -1; do { _menuIndex = (_menuIndex + delta + 3) % 3; } while (_menuIndex == 1 && !Game.HasSave); }
|
||
else if (args.Key == "Enter") { if (_menuIndex == 0) await StartNewGame(); else if (_menuIndex == 1) await ContinueGame(); else _showSettings = true; }
|
||
return;
|
||
}
|
||
if (_screen == Screen.Briefing && args.Key == "Enter") { BeginPlay(); return; }
|
||
if (_screen != Screen.Game) return;
|
||
switch (args.Key.ToLowerInvariant())
|
||
{
|
||
case "arrowup": await Move("north"); break; case "arrowdown": await Move("south"); break; case "arrowleft": await Move("west"); break; case "arrowright": await Move("east"); break;
|
||
case "e": OpenFirstInteraction(); break; case "m": _showMap = true; break; case "i": _showInventory = true; break; case "escape": _showPause = true; break;
|
||
}
|
||
}
|
||
private enum Screen { Title, Briefing, Game, Death, Ending }
|
||
}
|