From 135230f13abfbc0b48345ebd7dcd28a412948447 Mon Sep 17 00:00:00 2001 From: Michal Vrba Date: Tue, 22 Sep 2026 15:03:10 +0200 Subject: [PATCH] changes --- .../Components/BattleArena.razor | 74 +++- .../Components/CharacterSelect.razor | 1 + .../Components/ChoiceSelector.razor | 3 + .../Components/GlobalAudioControl.razor | 106 +++++ src/CiselnaVyprava/Components/MainMenu.razor | 10 +- .../Components/MathKeypad.razor | 4 + src/CiselnaVyprava/Components/WorldMap.razor | 19 +- src/CiselnaVyprava/Layout/MainLayout.razor | 5 +- src/CiselnaVyprava/Models/GameModels.cs | 3 +- src/CiselnaVyprava/Pages/Home.razor | 68 ++-- src/CiselnaVyprava/Program.cs | 1 + src/CiselnaVyprava/Services/AudioService.cs | 148 +++++++ .../Services/LocalStorageService.cs | 24 +- .../wwwroot/assets/audio/background.mp3 | Bin 0 -> 1463276 bytes .../wwwroot/assets/audio/sfx/arrow_shoot.wav | Bin 0 -> 67076 bytes .../wwwroot/assets/audio/sfx/button_click.wav | Bin 0 -> 21212 bytes .../wwwroot/assets/audio/sfx/coin_collect.wav | Bin 0 -> 79424 bytes .../wwwroot/assets/audio/sfx/combo_streak.wav | Bin 0 -> 114704 bytes .../wwwroot/assets/audio/sfx/crit_hit.wav | Bin 0 -> 149984 bytes .../wwwroot/assets/audio/sfx/keypad_0.wav | Bin 0 -> 35324 bytes .../wwwroot/assets/audio/sfx/keypad_1.wav | Bin 0 -> 35324 bytes .../wwwroot/assets/audio/sfx/keypad_2.wav | Bin 0 -> 35324 bytes .../wwwroot/assets/audio/sfx/keypad_3.wav | Bin 0 -> 35324 bytes .../wwwroot/assets/audio/sfx/keypad_4.wav | Bin 0 -> 35324 bytes .../wwwroot/assets/audio/sfx/keypad_5.wav | Bin 0 -> 35324 bytes .../wwwroot/assets/audio/sfx/keypad_6.wav | Bin 0 -> 35324 bytes .../wwwroot/assets/audio/sfx/keypad_7.wav | Bin 0 -> 35324 bytes .../wwwroot/assets/audio/sfx/keypad_8.wav | Bin 0 -> 35324 bytes .../wwwroot/assets/audio/sfx/keypad_9.wav | Bin 0 -> 35324 bytes .../wwwroot/assets/audio/sfx/magic_cast.wav | Bin 0 -> 132344 bytes .../wwwroot/assets/audio/sfx/owl_idea.wav | Bin 0 -> 114704 bytes .../wwwroot/assets/audio/sfx/shield_block.wav | Bin 0 -> 114704 bytes .../wwwroot/assets/audio/sfx/star_earned.wav | Bin 0 -> 149984 bytes .../audio/sfx/star_earned_v1_sparkle.wav | Bin 0 -> 167624 bytes .../assets/audio/sfx/star_earned_v2_harp.wav | Bin 0 -> 202900 bytes .../audio/sfx/star_earned_v3_crystal.wav | Bin 0 -> 149984 bytes .../audio/sfx/star_earned_v4_cascade.wav | Bin 0 -> 176444 bytes .../wwwroot/assets/audio/sfx/sword_slash.wav | Bin 0 -> 56492 bytes .../audio/sfx/sword_slash_v1_silver.wav | Bin 0 -> 79424 bytes .../audio/sfx/sword_slash_v2_cartoon.wav | Bin 0 -> 70604 bytes .../audio/sfx/sword_slash_v3_heroic.wav | Bin 0 -> 91772 bytes .../assets/audio/sfx/sword_slash_v4_crisp.wav | Bin 0 -> 56492 bytes .../assets/audio/sfx/victory_fanfare.wav | Bin 0 -> 247000 bytes .../audio/sfx/victory_fanfare_v1_royal.wav | Bin 0 -> 352844 bytes .../audio/sfx/victory_fanfare_v2_magical.wav | Bin 0 -> 388124 bytes .../audio/sfx/victory_fanfare_v3_playful.wav | Bin 0 -> 247000 bytes .../audio/sfx/victory_fanfare_v4_golden.wav | Bin 0 -> 423404 bytes .../wwwroot/assets/audio/sfx/wrong_answer.wav | Bin 0 -> 67076 bytes .../wwwroot/assets/audio/theme.mp3 | Bin 0 -> 1304204 bytes src/CiselnaVyprava/wwwroot/css/app.css | 166 ++++++++ src/CiselnaVyprava/wwwroot/index.html | 1 + src/CiselnaVyprava/wwwroot/js/audioManager.js | 284 +++++++++++++ src/CiselnaVyprava/wwwroot/sfx-lab.html | 378 ++++++++++++++++++ src/CiselnaVyprava/wwwroot/web.config | 17 +- wwwroot/assets/audio/background.mp3 | Bin 0 -> 1463276 bytes wwwroot/assets/audio/theme.mp3 | Bin 0 -> 1304204 bytes 56 files changed, 1275 insertions(+), 37 deletions(-) create mode 100644 src/CiselnaVyprava/Components/GlobalAudioControl.razor create mode 100644 src/CiselnaVyprava/Services/AudioService.cs create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/background.mp3 create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/arrow_shoot.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/button_click.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/coin_collect.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/combo_streak.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/crit_hit.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/keypad_0.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/keypad_1.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/keypad_2.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/keypad_3.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/keypad_4.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/keypad_5.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/keypad_6.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/keypad_7.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/keypad_8.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/keypad_9.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/magic_cast.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/owl_idea.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/shield_block.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/star_earned.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/star_earned_v1_sparkle.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/star_earned_v2_harp.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/star_earned_v3_crystal.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/star_earned_v4_cascade.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/sword_slash.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/sword_slash_v1_silver.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/sword_slash_v2_cartoon.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/sword_slash_v3_heroic.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/sword_slash_v4_crisp.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/victory_fanfare.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/victory_fanfare_v1_royal.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/victory_fanfare_v2_magical.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/victory_fanfare_v3_playful.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/victory_fanfare_v4_golden.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/sfx/wrong_answer.wav create mode 100644 src/CiselnaVyprava/wwwroot/assets/audio/theme.mp3 create mode 100644 src/CiselnaVyprava/wwwroot/js/audioManager.js create mode 100644 src/CiselnaVyprava/wwwroot/sfx-lab.html create mode 100644 wwwroot/assets/audio/background.mp3 create mode 100644 wwwroot/assets/audio/theme.mp3 diff --git a/src/CiselnaVyprava/Components/BattleArena.razor b/src/CiselnaVyprava/Components/BattleArena.razor index daf281d..9cc4bb8 100644 --- a/src/CiselnaVyprava/Components/BattleArena.razor +++ b/src/CiselnaVyprava/Components/BattleArena.razor @@ -1,11 +1,15 @@ @inject IGameDataLoader DataLoader @inject IBattleEngine BattleEngine +@inject IAudioService AudioService @implements IDisposable
- +
+ + +
@CurrentLevel.Name @if (CurrentLevel.IsBoss) @@ -257,6 +261,7 @@ [Parameter] public string BackgroundImage { get; set; } = "assets/images/backgrounds/world1_meadow.jpg"; [Parameter] public GameConfig Config { get; set; } = new(); [Parameter] public EventCallback OnReturnToMap { get; set; } + [Parameter] public EventCallback OnReturnToMainMenu { get; set; } [Parameter] public EventCallback OnLevelCompleted { get; set; } [Parameter] public EventCallback OnNextLevelRequested { get; set; } @@ -331,6 +336,8 @@ ExplanationUserAnswer = "Čas vypršal ⏱️"; IsExplanationFromDefense = true; ShowExplanationModal = true; + await PlayCombatSfx(result); + _ = AudioService.PlaySfxAsync("owl_idea"); StateHasChanged(); } else @@ -341,6 +348,8 @@ ExplanationUserAnswer = "Čas vypršal ⏱️"; IsExplanationFromDefense = false; ShowExplanationModal = true; + await PlayCombatSfx(result); + _ = AudioService.PlaySfxAsync("owl_idea"); StateHasChanged(); } }); @@ -351,6 +360,49 @@ }, null, 100, 100); } + private async Task PlayCombatSfx(CombatResultType result) + { + switch (result) + { + case CombatResultType.CriticalHit: + _ = AudioService.PlaySfxAsync("crit_hit"); + if (State.ComboStreak > 1) + { + _ = Task.Delay(250).ContinueWith(_ => AudioService.PlaySfxAsync("combo_streak")); + } + break; + case CombatResultType.StandardHit: + case CombatResultType.WeakHit: + string attackSfx = "sword_slash"; + if (Hero.Id.Contains("mage", StringComparison.OrdinalIgnoreCase) || Hero.AssetFolder.Contains("mage", StringComparison.OrdinalIgnoreCase)) + { + attackSfx = "magic_cast"; + } + else if (Hero.Id.Contains("archer", StringComparison.OrdinalIgnoreCase) || Hero.AssetFolder.Contains("archer", StringComparison.OrdinalIgnoreCase)) + { + attackSfx = "arrow_shoot"; + } + _ = AudioService.PlaySfxAsync(attackSfx); + if (State.ComboStreak > 1) + { + _ = Task.Delay(250).ContinueWith(_ => AudioService.PlaySfxAsync("combo_streak")); + } + break; + case CombatResultType.DefenseSuccess: + _ = AudioService.PlaySfxAsync("shield_block"); + break; + case CombatResultType.PlayerMissed: + case CombatResultType.DefenseFailed: + _ = AudioService.PlaySfxAsync("wrong_answer"); + break; + case CombatResultType.Victory: + _ = AudioService.PlaySfxAsync("victory_fanfare"); + _ = Task.Delay(1300).ContinueWith(_ => AudioService.PlaySfxAsync("star_earned")); + _ = Task.Delay(2100).ContinueWith(_ => AudioService.PlaySfxAsync("coin_collect")); + break; + } + } + private async Task HandleInputSubmit(string input) { if (State.IsBattleOver || ShowExplanationModal) return; @@ -360,6 +412,7 @@ var problemBeforeSubmit = State.CurrentProblem; var result = await BattleEngine.SubmitDefenseAnswerAsync(input); CurrentInput = string.Empty; + await PlayCombatSfx(result); if (result is CombatResultType.DefenseFailed or CombatResultType.Defeat) { @@ -368,6 +421,7 @@ ExplanationUserAnswer = input; IsExplanationFromDefense = true; ShowExplanationModal = true; + _ = AudioService.PlaySfxAsync("owl_idea"); StateHasChanged(); return; } @@ -377,6 +431,7 @@ var problemBeforeSubmit = State.CurrentProblem; var result = await BattleEngine.SubmitAnswerAsync(input); CurrentInput = string.Empty; + await PlayCombatSfx(result); if (result == CombatResultType.PlayerMissed) { @@ -385,6 +440,7 @@ ExplanationUserAnswer = input; IsExplanationFromDefense = false; ShowExplanationModal = true; + _ = AudioService.PlaySfxAsync("owl_idea"); StateHasChanged(); return; } @@ -401,6 +457,7 @@ { var problemBeforeSubmit = State.CurrentProblem; var result = await BattleEngine.SubmitDefenseAnswerAsync(choice); + await PlayCombatSfx(result); if (result is CombatResultType.DefenseFailed or CombatResultType.Defeat) { @@ -409,6 +466,7 @@ ExplanationUserAnswer = choice; IsExplanationFromDefense = true; ShowExplanationModal = true; + _ = AudioService.PlaySfxAsync("owl_idea"); StateHasChanged(); return; } @@ -417,6 +475,7 @@ { var problemBeforeSubmit = State.CurrentProblem; var result = await BattleEngine.SubmitAnswerAsync(choice); + await PlayCombatSfx(result); if (result == CombatResultType.PlayerMissed) { @@ -425,6 +484,7 @@ ExplanationUserAnswer = choice; IsExplanationFromDefense = false; ShowExplanationModal = true; + _ = AudioService.PlaySfxAsync("owl_idea"); StateHasChanged(); return; } @@ -435,6 +495,7 @@ private async Task DismissExplanation() { + _ = AudioService.PlaySfxAsync("button_click"); ShowExplanationModal = false; if (IsExplanationFromDefense) @@ -479,17 +540,20 @@ private async Task RetryBattle() { + _ = AudioService.PlaySfxAsync("button_click"); StartBattle(); StateHasChanged(); } private async Task OnVictoryContinue() { + _ = AudioService.PlaySfxAsync("button_click"); await OnLevelCompleted.InvokeAsync(CurrentLevel); } private async Task HandleNextLevelClick() { + _ = AudioService.PlaySfxAsync("button_click"); if (NextLevel != null) { await OnNextLevelRequested.InvokeAsync(NextLevel); @@ -502,10 +566,18 @@ private async Task GoBackToMap() { + _ = AudioService.PlaySfxAsync("button_click"); _timer?.Dispose(); await OnReturnToMap.InvokeAsync(); } + private async Task GoToMainMenu() + { + _ = AudioService.PlaySfxAsync("button_click"); + _timer?.Dispose(); + await OnReturnToMainMenu.InvokeAsync(); + } + private int EnemyHpPercent => State.EnemyMaxHp > 0 ? (int)((double)State.EnemyCurrentHp / State.EnemyMaxHp * 100) : 0; diff --git a/src/CiselnaVyprava/Components/CharacterSelect.razor b/src/CiselnaVyprava/Components/CharacterSelect.razor index 5c2013c..4998ab0 100644 --- a/src/CiselnaVyprava/Components/CharacterSelect.razor +++ b/src/CiselnaVyprava/Components/CharacterSelect.razor @@ -368,6 +368,7 @@ var profile = new PlayerProfile { + HasCreatedHero = true, Name = finalName, SelectedGrade = SelectedGrade, SelectedHeroId = SelectedHeroId, diff --git a/src/CiselnaVyprava/Components/ChoiceSelector.razor b/src/CiselnaVyprava/Components/ChoiceSelector.razor index b6ae7ef..60b0a40 100644 --- a/src/CiselnaVyprava/Components/ChoiceSelector.razor +++ b/src/CiselnaVyprava/Components/ChoiceSelector.razor @@ -1,3 +1,5 @@ +@inject IAudioService AudioService +
@foreach (var choice in Choices) @@ -15,6 +17,7 @@ private async Task SelectChoice(string choice) { + _ = AudioService.PlaySfxAsync("button_click"); await OnChoiceSelected.InvokeAsync(choice); } } diff --git a/src/CiselnaVyprava/Components/GlobalAudioControl.razor b/src/CiselnaVyprava/Components/GlobalAudioControl.razor new file mode 100644 index 0000000..80ac419 --- /dev/null +++ b/src/CiselnaVyprava/Components/GlobalAudioControl.razor @@ -0,0 +1,106 @@ +@using CiselnaVyprava.Services +@inject IAudioService AudioService + +
+ +
+ + +
+ + @((int)(State.MusicVolume * 100))% +
+
+ + +
+ + +
+ + @((int)(State.SfxVolume * 100))% +
+
+
+ +@code { + private AudioState State { get; set; } = new AudioState { MusicVolume = 0.45, MusicMuted = false, SfxVolume = 0.85, SfxMuted = false }; + + protected override async Task OnInitializedAsync() + { + try + { + State = await AudioService.GetStateAsync(); + } + catch { } + } + + private async Task HandleToggleMusicMute() + { + State = await AudioService.ToggleMusicMuteAsync(); + StateHasChanged(); + } + + private async Task HandleMusicVolumeInput(ChangeEventArgs e) + { + if (int.TryParse(e.Value?.ToString(), out int val)) + { + double newVol = Math.Clamp(val / 100.0, 0.0, 1.0); + State.MusicVolume = newVol; + if (State.MusicMuted && newVol > 0) + { + State.MusicMuted = false; + } + State = await AudioService.SetMusicVolumeAsync(newVol); + StateHasChanged(); + } + } + + private async Task HandleToggleSfxMute() + { + State = await AudioService.ToggleSfxMuteAsync(); + if (!State.SfxMuted) + { + _ = AudioService.PlaySfxAsync("button_click"); + } + StateHasChanged(); + } + + private async Task HandleSfxVolumeInput(ChangeEventArgs e) + { + if (int.TryParse(e.Value?.ToString(), out int val)) + { + double newVol = Math.Clamp(val / 100.0, 0.0, 1.0); + State.SfxVolume = newVol; + if (State.SfxMuted && newVol > 0) + { + State.SfxMuted = false; + } + State = await AudioService.SetSfxVolumeAsync(newVol); + _ = AudioService.PlaySfxAsync("button_click"); + StateHasChanged(); + } + } +} diff --git a/src/CiselnaVyprava/Components/MainMenu.razor b/src/CiselnaVyprava/Components/MainMenu.razor index 91b6511..2f496bf 100644 --- a/src/CiselnaVyprava/Components/MainMenu.razor +++ b/src/CiselnaVyprava/Components/MainMenu.razor @@ -1,4 +1,5 @@ @inject ILocalStorageService Storage +@inject IAudioService AudioService