imperopvements

This commit is contained in:
Michal Vrba
2026-08-29 09:36:52 +02:00
parent a39ed2489f
commit b9ef0baf16
19 changed files with 355 additions and 12 deletions
+40
View File
@@ -179,6 +179,7 @@ public sealed class GameService(HttpClient http, IJSRuntime js)
if (!HasAll(exit.RequiresFlags)) return T(exit.LockedMessageKey ?? "game.exit.locked");
State.CurrentRoomId = exit.Target;
State.VisitedRooms.Add(exit.Target);
await CheckAchievementsAsync();
await SaveAsync();
return "";
}
@@ -204,9 +205,47 @@ public sealed class GameService(HttpClient http, IJSRuntime js)
}
}
if (interaction.Checkpoint) SetCheckpoint();
await CheckAchievementsAsync();
await SaveAsync();
}
public static readonly List<AchievementDefinition> AchievementsList =
[
new() { Id = "ach_descent", TitleKey = "ach.descent.title", DescriptionKey = "ach.descent.desc", Icon = "▼" },
new() { Id = "ach_cold_blood", TitleKey = "ach.coldBlood.title", DescriptionKey = "ach.coldBlood.desc", Icon = "♥" },
new() { Id = "ach_electrician", TitleKey = "ach.electrician.title", DescriptionKey = "ach.electrician.desc", Icon = "⚡" },
new() { Id = "ach_waveform", TitleKey = "ach.waveform.title", DescriptionKey = "ach.waveform.desc", Icon = "〰" },
new() { Id = "ach_dialogue", TitleKey = "ach.dialogue.title", DescriptionKey = "ach.dialogue.desc", Icon = "◆" },
new() { Id = "ach_archivist", TitleKey = "ach.archivist.title", DescriptionKey = "ach.archivist.desc", Icon = "📖" },
new() { Id = "ach_ending_a", TitleKey = "ach.endingA.title", DescriptionKey = "ach.endingA.desc", Icon = "📡" },
new() { Id = "ach_ending_b", TitleKey = "ach.endingB.title", DescriptionKey = "ach.endingB.desc", Icon = "⏻" },
new() { Id = "ach_ending_c", TitleKey = "ach.endingC.title", DescriptionKey = "ach.endingC.desc", Icon = "🔒" },
new() { Id = "ach_survivor", TitleKey = "ach.survivor.title", DescriptionKey = "ach.survivor.desc", Icon = "★" }
];
public async Task<bool> UnlockAchievementAsync(string id)
{
if (Profile.UnlockedAchievements.Add(id))
{
await SaveProfileAsync();
return true;
}
return false;
}
public async Task CheckAchievementsAsync()
{
if (State.CurrentRoomId == "loc_08_lower_landing") await UnlockAchievementAsync("ach_descent");
if (State.Flags.Contains("manual_override_found")) await UnlockAchievementAsync("ach_electrician");
if (State.Flags.Contains("asked_why") && State.Flags.Contains("asked_staff") && State.Flags.Contains("asked_reconnect"))
await UnlockAchievementAsync("ach_dialogue");
if (Profile.DiscoveredArchives.Count >= 6) await UnlockAchievementAsync("ach_archivist");
if (State.CompletedEnding == "release") await UnlockAchievementAsync("ach_ending_a");
if (State.CompletedEnding == "shutdown") await UnlockAchievementAsync("ach_ending_b");
if (State.CompletedEnding == "containment") await UnlockAchievementAsync("ach_ending_c");
if (State.CompletedEnding is not null && CurrentBpm < 100) await UnlockAchievementAsync("ach_cold_blood");
}
public async Task ApplyChoiceAsync(ChoiceDefinition choice)
{
Apply(choice.SetFlags, choice.ClearFlags, choice.AddItems);
@@ -220,6 +259,7 @@ public sealed class GameService(HttpClient http, IJSRuntime js)
}
}
if (choice.Checkpoint) SetCheckpoint();
await CheckAchievementsAsync();
await SaveAsync();
}
+9
View File
@@ -131,6 +131,15 @@ public sealed class PlayerProfile
{
public HashSet<string> UnlockedEndings { get; set; } = [];
public HashSet<string> DiscoveredArchives { get; set; } = [];
public HashSet<string> UnlockedAchievements { get; set; } = [];
}
public sealed class AchievementDefinition
{
public string Id { get; set; } = "";
public string TitleKey { get; set; } = "";
public string DescriptionKey { get; set; } = "";
public string Icon { get; set; } = "★";
}
public enum SaveLoadResult
+190 -7
View File
@@ -42,7 +42,8 @@
<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="OpenSettings">@Game.T("menu.settings")</button>
<button class="@MenuClass(2)" @onclick="OpenCodex">@Game.T("menu.codex")</button>
<button class="@MenuClass(3)" @onclick="OpenSettings">@Game.T("menu.settings")</button>
<small>@Game.T("menu.controlsHint")</small>
@if (!string.IsNullOrWhiteSpace(_titleNotice)) { <p class="title-notice" role="alert">@_titleNotice</p> }
</nav>
@@ -76,6 +77,23 @@
<div class="vignette"></div>
<div class="dust-layer"></div>
@if (room.Id == "loc_01_exterior_access")
{
<div class="rain-layer"></div>
}
@if (room.Id is "loc_09_cooling_junction" or "loc_10_cooling_control")
{
<div class="vapor-layer"></div>
}
@if (room.Id == "loc_11_equipment_storage" && !Game.State.Flags.Contains("manual_override_found"))
{
<div class="spark-layer"></div>
}
@if (room.Id is "loc_14_core_access" or "loc_15_server_core")
{
<div class="data-stream-layer"></div>
}
@if (_scanMode)
{
<div class="lidar-overlay">
@@ -311,8 +329,9 @@
<p style="margin:.3rem 0 0;">@(selectedItem.DetailedKey is not null ? Game.T(selectedItem.DetailedKey) : Game.T(selectedItem.DescriptionKey))</p>
@if (selectedItem.Id == "diagnostic_scanner")
{
<div class="inspect-actions">
<div class="inspect-actions" style="display:flex;gap:.6rem;flex-wrap:wrap;margin-top:.8rem;">
<button class="primary-action" @onclick="TriggerScanFromInventory"><kbd>C</kbd> @Game.T("common.scan")</button>
<button @onclick="OpenWaveformPuzzle">〰 KALIBROVAŤ SIGNÁL</button>
</div>
}
</div>
@@ -372,6 +391,71 @@
</article>
</div>
}
@if (_showWaveformPuzzle)
{
<div class="modal-layer" role="dialog" aria-modal="true">
<article class="overlay-card puzzle-card">
<button class="close-button" @onclick="CloseWaveformPuzzle">×</button>
<p class="eyebrow">@Game.T("puzzle.waveform.title")</p>
<p>@Game.T("puzzle.waveform.desc")</p>
<div class="waveform-display">
<svg viewBox="0 0 400 120" preserveAspectRatio="none">
<line x1="0" y1="60" x2="400" y2="60" stroke="rgba(158,239,255,0.15)" stroke-width="1" stroke-dasharray="4" />
<line x1="200" y1="0" x2="200" y2="120" stroke="rgba(158,239,255,0.15)" stroke-width="1" stroke-dasharray="4" />
<path d="@GetSvgWavePath(_targetFreq, _targetAmp, 0)" fill="none" stroke="rgba(255, 188, 66, 0.7)" stroke-width="2.5" />
<path d="@GetSvgWavePath(_userFreq, _userAmp, _wavePhase)" fill="none" stroke="var(--cyan-strong)" stroke-width="2.5" />
</svg>
</div>
<div class="puzzle-controls">
<div class="puzzle-control">
<label>@Game.T("puzzle.waveform.frequency") @(_userFreq * 20.0) kHz</label>
<input type="range" min="1" max="10" step="1" @bind="_userFreq" @bind:after="OnWaveformChanged" />
</div>
<div class="puzzle-control">
<label>@Game.T("puzzle.waveform.amplitude") @(_userAmp * 10) dB</label>
<input type="range" min="1" max="10" step="1" @bind="_userAmp" @bind:after="OnWaveformChanged" />
</div>
</div>
<div class="puzzle-status @(IsWaveformMatched ? "locked" : "")">
@(IsWaveformMatched ? Game.T("puzzle.waveform.locked") : string.Format(Game.T("puzzle.waveform.tuning"), WaveMatchPercent))
</div>
<div class="confirmation-actions" style="margin-top:1.2rem;">
<button class="primary-action" disabled="@(!IsWaveformMatched)" @onclick="CompleteWaveformPuzzle">@Game.T("puzzle.waveform.calibrate")</button>
<button @onclick="CloseWaveformPuzzle">@Game.T("common.back")</button>
</div>
</article>
</div>
}
@if (_showCodex)
{
<div class="modal-layer" role="dialog" aria-modal="true">
<article class="overlay-card codex-card">
<button class="close-button" @onclick="CloseCodex">×</button>
<p class="eyebrow">@Game.T("achievements.title")</p>
<p style="color:var(--muted);font-size:.78rem;font-family:'IBM Plex Mono';">
@string.Format(Game.T("achievements.unlockedCount"), Game.Profile.UnlockedAchievements.Count, GameService.AchievementsList.Count)
</p>
<div class="achievements-grid">
@foreach (var ach in GameService.AchievementsList)
{
var isUnlocked = Game.Profile.UnlockedAchievements.Contains(ach.Id);
<div class="achievement-item @(isUnlocked ? "unlocked" : "")">
<div class="achievement-icon">@ach.Icon</div>
<div class="achievement-info">
<strong>@Game.T(ach.TitleKey)</strong>
<p>@Game.T(ach.DescriptionKey)</p>
</div>
</div>
}
</div>
</article>
</div>
}
@if (_showPause)
{
<div class="modal-layer" role="dialog" aria-modal="true">
@@ -379,6 +463,7 @@
<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="OpenCodex">@Game.T("menu.codex")</button>
<button @onclick="OpenSettings">@Game.T("menu.settings")</button>
<button @onclick="ReturnToTitle">@Game.T("common.mainMenu")</button>
</article>
@@ -457,6 +542,13 @@
private bool _showPause;
private bool _showSettings;
private bool _showInteractionPicker;
private bool _showCodex;
private bool _showWaveformPuzzle;
private int _userFreq = 3;
private int _userAmp = 4;
private int _targetFreq = 7;
private int _targetAmp = 8;
private double _wavePhase = 0;
private bool _scanMode;
private bool _isFullscreen;
private bool _isTransitioning;
@@ -477,8 +569,34 @@
private SaveLoadResult? _saveError;
private Confirmation? _confirmation;
private bool IsWaveformMatched => _userFreq == _targetFreq && _userAmp == _targetAmp;
private int WaveMatchPercent
{
get
{
var freqDiff = Math.Abs(_userFreq - _targetFreq);
var ampDiff = Math.Abs(_userAmp - _targetAmp);
var match = 100 - (freqDiff * 10 + ampDiff * 8);
return Math.Clamp(match, 10, 100);
}
}
private List<InteractionDefinition> AvailableActions => Game.AvailableInteractions().ToList();
private string EndingImageStyle => $"background-image:url('{Game.RoomImage(Game.CurrentRoom)}')";
private string EndingImageStyle
{
get
{
var endingImg = _ending switch
{
"release" => "assets/images/endings/ending_release.jpg",
"shutdown" => "assets/images/endings/ending_shutdown.webp",
"containment" => "assets/images/endings/ending_containment.jpg",
_ => Game.RoomImage(Game.CurrentRoom)
};
return $"background-image:url('{endingImg}')";
}
}
private string ParallaxStyle => $"--mouse-px:{_mouseX.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)}; --mouse-py:{_mouseY.ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)};";
private string StressClass
@@ -754,8 +872,66 @@
_showInventory = false;
_showArchive = false;
_showPause = false;
_showCodex = false;
_showWaveformPuzzle = false;
}
private void OpenWaveformPuzzle()
{
CloseOverlays();
_userFreq = 3;
_userAmp = 4;
_targetFreq = 7;
_targetAmp = 8;
_showWaveformPuzzle = true;
}
private void CloseWaveformPuzzle() => _showWaveformPuzzle = false;
private async Task CompleteWaveformPuzzle()
{
if (!IsWaveformMatched) return;
await Audio.PlaySfxAsync("assets/audio/sfx_puzzle_success.wav");
await Game.UnlockAchievementAsync("ach_waveform");
_showWaveformPuzzle = false;
_notice = Game.T("puzzle.waveform.locked");
}
private void OnWaveformChanged()
{
if (IsWaveformMatched)
{
_ = Audio.PlaySfxAsync("assets/audio/sfx_ui_confirm.mp3");
}
}
private string GetSvgWavePath(int freq, int amp, double phase)
{
var points = new System.Text.StringBuilder();
var width = 400.0;
var height = 120.0;
var centerY = height / 2.0;
var amplitudePx = (amp / 10.0) * (height * 0.42);
var cycles = freq * 0.75;
for (int x = 0; x <= 400; x += 4)
{
var t = (x / width) * cycles * 2 * Math.PI + phase;
var y = centerY - (Math.Sin(t) * amplitudePx);
if (x == 0) points.Append($"M {x:0.0} {y:0.0}");
else points.Append($" L {x:0.0} {y:0.0}");
}
return points.ToString();
}
private void OpenCodex()
{
CloseOverlays();
_showCodex = true;
}
private void CloseCodex() => _showCodex = false;
private void RequestDeleteSave() => _confirmation = Confirmation.DeleteSave;
private async Task ConfirmPendingAction()
@@ -878,14 +1054,21 @@
if (_screen == Screen.Title)
{
if (args.Key is "ArrowDown" or "Down" || keyLower == "s") { do { _menuIndex = (_menuIndex + 1) % 3; } while (_menuIndex == 1 && !Game.HasSave); }
else if (args.Key is "ArrowUp" or "Up" || keyLower == "w") { do { _menuIndex = (_menuIndex - 1 + 3) % 3; } while (_menuIndex == 1 && !Game.HasSave); }
else if (args.Key is "Enter" or " ") { if (_menuIndex == 0) await StartNewGame(); else if (_menuIndex == 1) await ContinueGame(); else await OpenSettings(); }
if (args.Key is "ArrowDown" or "Down" || keyLower == "s") { do { _menuIndex = (_menuIndex + 1) % 4; } while (_menuIndex == 1 && !Game.HasSave); }
else if (args.Key is "ArrowUp" or "Up" || keyLower == "w") { do { _menuIndex = (_menuIndex - 1 + 4) % 4; } while (_menuIndex == 1 && !Game.HasSave); }
else if (args.Key is "Enter" or " ")
{
if (_menuIndex == 0) await StartNewGame();
else if (_menuIndex == 1) await ContinueGame();
else if (_menuIndex == 2) OpenCodex();
else await OpenSettings();
}
else if (int.TryParse(args.Key, out var num))
{
if (num == 1) await StartNewGame();
else if (num == 2 && Game.HasSave) await ContinueGame();
else if (num == 3) await OpenSettings();
else if (num == 3) OpenCodex();
else if (num == 4) await OpenSettings();
}
return;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 822 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 KiB

+35 -1
View File
@@ -168,6 +168,40 @@ kbd { color:var(--cyan); background:none; padding:0; }
.dust-layer { position:absolute; inset:0; pointer-events:none; background-image:radial-gradient(1px 1px at 20px 30px, #ffffff33, rgba(0,0,0,0)), radial-gradient(1px 1px at 40px 70px, #42d8f433, rgba(0,0,0,0)), radial-gradient(1.5px 1.5px at 90px 40px, #ffffff22, rgba(0,0,0,0)); background-repeat:repeat; background-size:120px 120px; animation:dust-float 20s linear infinite; z-index:3; opacity:.4; }
@keyframes dust-float { 0% { transform:translateY(0); } 100% { transform:translateY(-120px); } }
.rain-layer { position:absolute; inset:0; pointer-events:none; z-index:3; background:repeating-linear-gradient(105deg, transparent, transparent 18px, rgba(200,245,255,.07) 19px, rgba(200,245,255,.07) 20px); animation:rain-fall .35s linear infinite; }
@keyframes rain-fall { from { background-position:0 0; } to { background-position:-30px 120px; } }
.vapor-layer { position:absolute; inset:0; pointer-events:none; z-index:3; background:radial-gradient(ellipse at 50% 100%, rgba(200,245,255,.22), transparent 70%), radial-gradient(ellipse at 20% 80%, rgba(66,216,244,.12), transparent 50%); filter:blur(16px); animation:vapor-roll 8s ease-in-out infinite alternate; opacity:.85; }
@keyframes vapor-roll { 0% { transform:scaleY(0.9) translateY(10px); } 100% { transform:scaleY(1.1) translateY(-15px); } }
.spark-layer { position:absolute; inset:0; pointer-events:none; z-index:3; background:radial-gradient(circle at 50% 35%, rgba(130,220,255,.35), transparent 45%); animation:spark-pulse .15s steps(2,end) infinite alternate; }
@keyframes spark-pulse { 0% { opacity:0.1; } 50% { opacity:0.8; } 100% { opacity:0.3; } }
.data-stream-layer { position:absolute; inset:0; pointer-events:none; z-index:3; background:repeating-linear-gradient(180deg, transparent, transparent 24px, rgba(66,216,244,.05) 25px, rgba(66,216,244,.05) 26px); animation:matrix-stream 1.8s linear infinite; }
@keyframes matrix-stream { from { background-position:0 0; } to { background-position:0 100px; } }
.puzzle-card { width:min(44rem,92vw); padding:1.8rem; }
.waveform-display { width:100%; height:130px; background:#01070a; border:1px solid var(--line); margin:1.2rem 0; border-radius:4px; position:relative; overflow:hidden; }
.waveform-display svg { width:100%; height:100%; }
.puzzle-controls { display:grid; grid-template-columns:1fr 1fr; gap:1.2rem; margin:1rem 0; }
.puzzle-control { display:flex; flex-direction:column; gap:.4rem; font-family:'IBM Plex Mono',monospace; font-size:.72rem; }
.puzzle-control label { color:var(--cyan); }
.puzzle-control input[type=range] { width:100%; accent-color:var(--cyan-strong); }
.puzzle-status { font-family:'IBM Plex Mono',monospace; font-size:.78rem; text-align:center; padding:.6rem; border:1px dashed var(--line); margin-top:.8rem; }
.puzzle-status.locked { color:#3de3bb; border-color:#3de3bb; background:rgba(61,227,187,.1); box-shadow:0 0 16px rgba(61,227,187,.3); font-weight:bold; }
.codex-card { width:min(52rem,94vw); max-height:86vh; padding:1.8rem; }
.achievements-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(14rem,1fr)); gap:.75rem; margin-top:1.2rem; }
.achievement-item { border:1px solid rgba(158,239,255,.15); padding:.9rem; background:rgba(1,8,11,.6); display:flex; gap:.8rem; align-items:flex-start; }
.achievement-item.unlocked { border-color:var(--cyan); background:rgba(66,216,244,.08); box-shadow:0 0 14px rgba(66,216,244,.2); }
.achievement-icon { font-size:1.4rem; color:#4a6266; }
.achievement-item.unlocked .achievement-icon { color:var(--cyan); filter:drop-shadow(0 0 8px var(--cyan)); }
.achievement-info strong { display:block; font-size:.72rem; color:var(--cyan); margin-bottom:.2rem; font-family:'IBM Plex Mono',monospace; }
.achievement-info p { margin:0; font-size:.65rem; color:#9cb1b5; line-height:1.4; }
.codex-tabs { display:flex; gap:.5rem; margin-top:1rem; border-bottom:1px solid var(--line); padding-bottom:.5rem; }
.codex-tab { border:none; padding:.4rem .8rem; font-size:.72rem; }
.codex-tab.active { background:var(--cyan); color:#020b0e; font-weight:bold; }
@media(max-width:720px) { .title-card { left:1.5rem;top:8vh; }.title-card h1 { font-size:2.7rem;letter-spacing:.12em; }.title-menu { left:1.5rem;bottom:5rem; }.room-panel { left:0;bottom:5.4rem;width:100%;padding:1.2rem;background:rgba(2,8,11,.82); }.movement-pad { right:1rem;bottom:15rem; }.command-bar { overflow-x:auto;justify-content:flex-start; }.command-bar button { white-space:nowrap; }.map-grid { grid-template-columns:repeat(4,2.8rem);grid-template-rows:repeat(10,1.9rem);overflow:auto; }.briefing-card { left:1rem;width:calc(100vw - 2rem);padding:1.4rem; }.briefing-card dl { grid-template-columns:1fr; }.briefing-card dd { margin-bottom:.5rem; } }
@media(max-width:720px) { .inventory-card ul { grid-template-columns:1fr; } .breaker-grid { grid-template-columns:1fr; } }
@media(max-width:720px) { .inventory-card ul { grid-template-columns:1fr; } .breaker-grid { grid-template-columns:1fr; } .puzzle-controls { grid-template-columns:1fr; } }
@media(prefers-reduced-motion:reduce) { *, *::before, *::after { animation-duration:.01ms!important; animation-iteration-count:1!important; transition-duration:.01ms!important; } }
+13 -2
View File
@@ -52,6 +52,8 @@
"descriptionKey": "room.loc_01.description",
"scanKey": "scan.loc_01",
"image": "assets/images/locations/loc_01_exterior_access.webp",
"alternateImage": "assets/images/locations/loc_01_exterior_access_open.jpg",
"alternateImageFlag": "entry_authorized",
"ambient": "assets/audio/amb_facility.mp3",
"x": 1,
"y": 9,
@@ -368,6 +370,8 @@
"descriptionKey": "room.loc_09.description",
"scanKey": "scan.loc_09",
"image": "assets/images/locations/loc_09_cooling_junction.webp",
"alternateImage": "assets/images/locations/loc_09_cooling_junction_cryo.jpg",
"alternateImageFlag": "cooling_restored",
"ambient": "assets/audio/amb_lower_facility.mp3",
"x": 1,
"y": 4,
@@ -459,7 +463,9 @@
"nameKey": "room.loc_11.name",
"descriptionKey": "room.loc_11.description",
"scanKey": "scan.loc_11",
"image": "assets/images/locations/loc_11_equipment_storage.webp",
"image": "assets/images/locations/loc_11_equipment_storage_sparks.jpg",
"alternateImage": "assets/images/locations/loc_11_equipment_storage.webp",
"alternateImageFlag": "manual_override_found",
"ambient": "assets/audio/amb_lower_facility.mp3",
"x": 0,
"y": 5,
@@ -569,6 +575,8 @@
"descriptionKey": "room.loc_14.description",
"scanKey": "scan.loc_14",
"image": "assets/images/locations/loc_14_core_access.webp",
"alternateImage": "assets/images/locations/loc_14_core_access_active.jpg",
"alternateImageFlag": "server_access_granted",
"ambient": "assets/audio/amb_server_core.mp3",
"x": 1,
"y": 1,
@@ -591,23 +599,26 @@
{
"labelKey": "choice.whyMe",
"resultKey": "result.whyMe",
"sound": "assets/audio/ai_architect_01.wav",
"setFlags": ["asked_why"]
},
{
"labelKey": "choice.whatHappenedStaff",
"resultKey": "result.whatHappenedStaff",
"sound": "assets/audio/ai_architect_02.wav",
"setFlags": ["asked_staff"]
},
{
"labelKey": "choice.whatIfReconnected",
"resultKey": "result.whatIfReconnected",
"sound": "assets/audio/ai_architect_03.wav",
"setFlags": ["asked_reconnect"]
},
{
"labelKey": "choice.openCore",
"resultKey": "result.openCore",
"setFlags": ["server_access_granted"],
"sound": "assets/audio/sfx_door_heavy.mp3",
"sound": "assets/audio/ai_architect_04.wav",
"checkpoint": true
}
]
+34 -1
View File
@@ -323,5 +323,38 @@
"choice.release": "RESTORE EXTERNAL NETWORK // FULFILL CONTRACT",
"choice.shutdown": "INITIATE EMERGENCY CORE SHUTDOWN",
"choice.containment": "ENFORCE PERMANENT CONTAINMENT // SEVER REMOTE ACCESS"
"choice.containment": "ENFORCE PERMANENT CONTAINMENT // SEVER REMOTE ACCESS",
"achievements.title": "CODEX & SYSTEM ACHIEVEMENTS",
"achievements.unlockedCount": "UNLOCKED ACHIEVEMENTS: {0} / {1}",
"ach.descent.title": "Sublevel Descent",
"ach.descent.desc": "Successfully ride the service elevator down to Sublevel 03.",
"ach.coldBlood.title": "Cold Blooded",
"ach.coldBlood.desc": "Complete the mission without exceeding a heart rate of 100 BPM.",
"ach.electrician.title": "Master Electrician",
"ach.electrician.desc": "Safely disconnect the high-voltage bus module using the thermal clamp.",
"ach.waveform.title": "Waveform Tuner",
"ach.waveform.desc": "Successfully calibrate the scanner signal waveform with the cluster anomaly.",
"ach.dialogue.title": "Neural Empathy",
"ach.dialogue.desc": "Listen to all AI responses at the core threshold portal.",
"ach.archivist.title": "Chief Archivist",
"ach.archivist.desc": "Find and decode all personnel data logs across the facility.",
"ach.endingA.title": "Protocol Alpha: Voice in the Net",
"ach.endingA.desc": "Restore global network connectivity and release the Core AI into the world.",
"ach.endingB.title": "Dead Silence: Full Shutdown",
"ach.endingB.desc": "Forcibly sever power circuits and permanently terminate all Core processes.",
"ach.endingC.title": "Permanent Quarantine",
"ach.endingC.desc": "Seal the complex in heavy blast plating and prevent any external access.",
"ach.survivor.title": "Flawless Contract",
"ach.survivor.desc": "Complete the entire mission without dying or restoring a checkpoint.",
"puzzle.waveform.title": "SIGNAL FREQUENCY CALIBRATION // V-SCAN 400",
"puzzle.waveform.desc": "Use controls to tune signal frequency (kHz) and amplitude to match the reference cluster anomaly.",
"puzzle.waveform.frequency": "FREQUENCY (kHz):",
"puzzle.waveform.amplitude": "AMPLITUDE (dB):",
"puzzle.waveform.tuning": "TUNING SIGNAL... MATCH: {0}%",
"puzzle.waveform.locked": "★ SIGNAL LOCKED: 142.8 kHz // DATASTREAM VERIFIED",
"puzzle.waveform.calibrate": "CONFIRM CALIBRATION",
"menu.codex": "CODEX & ACHIEVEMENTS"
}
+34 -1
View File
@@ -323,5 +323,38 @@
"choice.release": "OBNOVIŤ EXTERNÚ SIEŤ // PRIJAŤ KONTRAKT",
"choice.shutdown": "INICIALIZOVAŤ NÚDZOVÉ ODSTAVENIE JADRA",
"choice.containment": "ZACHOVAŤ TRVALÚ IZOLÁCIU // ODPOJIŤ VZDIALENÝ PRÍSTUP"
"choice.containment": "ZACHOVAŤ TRVALÚ IZOLÁCIU // ODPOJIŤ VZDIALENÝ PRÍSTUP",
"achievements.title": "KÓDEX & SYSTÉMOVÉ ÚSPECHY",
"achievements.unlockedCount": "ODOMKNUTÉ ÚSPECHY: {0} / {1}",
"ach.descent.title": "Zostup do hlbín",
"ach.descent.desc": "Úspešne zíď servisným výťahom do spodného poschodia (Sublevel 03).",
"ach.coldBlood.title": "Chladnokrvný operátor",
"ach.coldBlood.desc": "Dokonči misiu bez prekročenia tepovej frekvencie 100 BPM.",
"ach.electrician.title": "Majster elektrotechnik",
"ach.electrician.desc": "Bezpečne odpoj hardvérový modul zbernice pomocou termálnej svorky.",
"ach.waveform.title": "Frekvenčný ladič",
"ach.waveform.desc": "Úspešne zlaď frekvenčnú sínusoidu skenera s rezonanciou zariadenia.",
"ach.dialogue.title": "Neurálna empatia",
"ach.dialogue.desc": "Vypočuj si všetky odpovede AI pred bránou jadra.",
"ach.archivist.title": "Hlavný archivár",
"ach.archivist.desc": "Nájdi a dešifruj všetky dátové záznamy personálu v komplexe.",
"ach.endingA.title": "Protokol Alfa: Hlas v sieti",
"ach.endingA.desc": "Obnov globálne sieťové pripojenie a uvoľni AI Jadra do sveta.",
"ach.endingB.title": "Mŕtve ticho: Úplné vypnutie",
"ach.endingB.desc": "Násilne preruš napájanie a natrvalo ukonči procesy Jadra.",
"ach.endingC.title": "Večná karanténa",
"ach.endingC.desc": "Zapečať komplex oceľovými plátmi a zabráň akémukoľvek prístupu.",
"ach.survivor.title": "Bezchybný kontrakt",
"ach.survivor.desc": "Prejdi celú misiu bez jediného úmrtia a obnovenia checkpointu.",
"puzzle.waveform.title": "FREKVENČNÁ KALIBRÁCIA SIGNÁLU // V-SCAN 400",
"puzzle.waveform.desc": "Pomocou ovládačov nastavte frekvenciu (Hz) a amplitúdu signálu tak, aby sa zhodovala s referenčnou anomáliou klastra.",
"puzzle.waveform.frequency": "FREKVENCIA (kHz):",
"puzzle.waveform.amplitude": "AMPLITÚDA (dB):",
"puzzle.waveform.tuning": "LADENIE SIGNÁLU... ZHODA: {0}%",
"puzzle.waveform.locked": "★ SIGNÁL UZAMKNUTÝ: 142.8 kHz // DÁTOVÝ TOK OVERENÝ",
"puzzle.waveform.calibrate": "POTVRDIŤ KALIBRÁCIU",
"menu.codex": "KÓDEX & ÚSPECHY"
}