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