From 7291e4c2f3ee68457ca3c5c924d17664577bde85 Mon Sep 17 00:00:00 2001 From: Yentis Date: Wed, 11 Mar 2026 16:51:36 +0100 Subject: [PATCH 01/11] Tooltip improvements --- src/resources/ui_resources.rs | 1 + src/ui/tooltips.rs | 544 +++++++++++++++++++++------------- 2 files changed, 344 insertions(+), 201 deletions(-) diff --git a/src/resources/ui_resources.rs b/src/resources/ui_resources.rs index d31b3965..aeb02863 100644 --- a/src/resources/ui_resources.rs +++ b/src/resources/ui_resources.rs @@ -473,6 +473,7 @@ pub fn load_ui_resources( style.visuals.popup_shadow = egui::epaint::Shadow::NONE; style.visuals.window_shadow = egui::epaint::Shadow::NONE; style.visuals.widgets.noninteractive.fg_stroke.color = egui::Color32::WHITE; + style.interaction.show_tooltips_only_when_still = false; egui_context.ctx_mut().set_style(style); commands.init_resource::(); diff --git a/src/ui/tooltips.rs b/src/ui/tooltips.rs index 3c14c867..e0497324 100644 --- a/src/ui/tooltips.rs +++ b/src/ui/tooltips.rs @@ -16,6 +16,7 @@ use rose_game_common::components::{ use crate::{bundles::ability_values_get_value, resources::GameData}; const TOOLTIP_MAX_WIDTH: f32 = 300.0; +const POSITIVE_EFFECT_COLOR: egui::Color32 = egui::Color32::from_rgb(100, 200, 255); #[derive(WorldQuery)] pub struct PlayerTooltipQuery<'w> { @@ -99,13 +100,17 @@ fn add_equipment_item_life_durability( game_data: &GameData, equipment_item: &EquipmentItem, ) { - ui.label(format!( - "{}:{: >3}% {}:{: >3}", - game_data.client_strings.item_life, - (equipment_item.life + 9) / 10, - game_data.client_strings.item_durability, - equipment_item.durability - )); + ui.horizontal(|ui| { + let item_life = format!("{: >3}%", (equipment_item.life + 9) / 10); + add_label_key_value(ui, game_data.client_strings.item_life, &item_life); + + let item_durability = format!("{: >3}", equipment_item.durability); + add_label_key_value( + ui, + game_data.client_strings.item_durability, + &item_durability, + ); + }); } fn add_item_defence( @@ -114,29 +119,45 @@ fn add_item_defence( item_data: &BaseItemData, grade_data: Option<&ItemGradeData>, ) { - ui.label(format!( - "{}:{} {}:{}", - game_data - .string_database - .get_ability_type(AbilityType::Defence), - item_data.defence + ui.horizontal(|ui| { + let defence = item_data.defence + grade_data .map(|grade_data| grade_data.defence as u32) - .unwrap_or(0), - game_data - .string_database - .get_ability_type(AbilityType::Resistance), - item_data.resistance + .unwrap_or(0); + + add_label_key_value( + ui, + game_data + .string_database + .get_ability_type(AbilityType::Defence), + &defence.to_string(), + ); + + let resistance = item_data.resistance + grade_data .map(|grade_data| grade_data.resistance as u32) - .unwrap_or(0) - )); + .unwrap_or(0); + + add_label_key_value( + ui, + game_data + .string_database + .get_ability_type(AbilityType::Resistance), + &resistance.to_string(), + ); + }); } fn add_item_add_ability(ui: &mut egui::Ui, game_data: &GameData, item_data: &BaseItemData) { for &(ability_type, value) in item_data.add_ability.iter() { + let color = if value < 0 { + egui::Color32::RED + } else { + POSITIVE_EFFECT_COLOR + }; + ui.colored_label( - egui::Color32::from_rgb(100, 200, 255), + color, format!( "[{} {}{}]", game_data.string_database.get_ability_type(ability_type), @@ -176,7 +197,7 @@ fn add_equipment_item_add_appraisal( if is_gem { egui::Color32::YELLOW } else { - egui::Color32::from_rgb(100, 200, 255) + POSITIVE_EFFECT_COLOR }, format!( "[{} {}{}]", @@ -282,13 +303,22 @@ fn add_item_equip_requirement( } fn add_item_description(ui: &mut egui::Ui, game_data: &GameData, item_data: &BaseItemData) { - ui.label(format!( - "{}:{}", - game_data.client_strings.item_weight, item_data.weight - )); + add_label_key_value( + ui, + game_data.client_strings.item_weight, + &item_data.weight.to_string(), + ); ui.label(item_data.description); } +fn add_label_key_value(ui: &mut egui::Ui, key: &str, value: &str) { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.colored_label(egui::Color32::from_rgb(130, 145, 195), format!("{}: ", key)); + ui.label(value); + }); +} + pub fn ui_add_item_tooltip( ui: &mut egui::Ui, game_data: &GameData, @@ -326,13 +356,19 @@ pub fn ui_add_item_tooltip( + equipment_item.durability as f32 * 0.8 + grade_data.map(|grade| grade.hit).unwrap_or(0) as f32; - ui.label(format!( - "{}:{} {}:{}", - game_data.client_strings.item_class, - game_data.string_database.get_item_class(item_data.class), - game_data.string_database.get_ability_type(AbilityType::Hit), - hit_rate as i32 - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data.client_strings.item_class, + game_data.string_database.get_item_class(item_data.class), + ); + + add_label_key_value( + ui, + game_data.string_database.get_ability_type(AbilityType::Hit), + &(hit_rate as i32).to_string(), + ); + }); add_equipment_item_life_durability(ui, game_data, equipment_item); @@ -340,53 +376,82 @@ pub fn ui_add_item_tooltip( + grade_data.map(|grade| grade.attack).unwrap_or(0); match weapon_item_data.attack_speed.cmp(&12) { Ordering::Less => { - ui.label(format!( - "{}:{} {}:{} +{}", - game_data - .string_database - .get_ability_type(AbilityType::Attack), - attack_power, - game_data - .string_database - .get_ability_type(AbilityType::AttackSpeed), - game_data.client_strings.item_attack_speed_fast, - 12 - weapon_item_data.attack_speed - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data + .string_database + .get_ability_type(AbilityType::Attack), + &attack_power.to_string(), + ); + + let attack_speed = format!( + "{} +{}", + game_data.client_strings.item_attack_speed_fast, + 12 - weapon_item_data.attack_speed, + ); + + add_label_key_value( + ui, + game_data + .string_database + .get_ability_type(AbilityType::AttackSpeed), + &attack_speed, + ); + }); } Ordering::Equal => { - ui.label(format!( - "{}:{} {}:{}", - game_data - .string_database - .get_ability_type(AbilityType::Attack), - attack_power, - game_data - .string_database - .get_ability_type(AbilityType::AttackSpeed), - game_data.client_strings.item_attack_speed_normal, - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data + .string_database + .get_ability_type(AbilityType::Attack), + &attack_power.to_string(), + ); + + add_label_key_value( + ui, + game_data + .string_database + .get_ability_type(AbilityType::AttackSpeed), + game_data.client_strings.item_attack_speed_normal, + ); + }); } Ordering::Greater => { - ui.label(format!( - "{}:{} {}:{} -{}", - game_data - .string_database - .get_ability_type(AbilityType::Attack), - attack_power, - game_data - .string_database - .get_ability_type(AbilityType::AttackSpeed), - game_data.client_strings.item_attack_speed_slow, - weapon_item_data.attack_speed - 12 - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data + .string_database + .get_ability_type(AbilityType::Attack), + &attack_power.to_string(), + ); + + let attack_speed = format!( + "{} -{}", + game_data.client_strings.item_attack_speed_slow, + weapon_item_data.attack_speed - 12, + ); + + add_label_key_value( + ui, + game_data + .string_database + .get_ability_type(AbilityType::AttackSpeed), + &attack_speed, + ); + }); } } - ui.label(format!( - "{}:{}M", + let attack_range = format!("{}m", weapon_item_data.attack_range / 100); + add_label_key_value( + ui, game_data.client_strings.item_attack_range, - weapon_item_data.attack_range / 100 - )); + &attack_range, + ); add_item_add_ability(ui, game_data, item_data); add_equipment_item_add_appraisal(ui, game_data, equipment_item); @@ -400,23 +465,35 @@ pub fn ui_add_item_tooltip( let avoid_rate = equipment_item.durability as f32 * 0.3 + grade_data.map(|grade| grade.avoid).unwrap_or(0) as f32; - ui.label(format!( - "{}:{} {}:{}", - game_data.client_strings.item_class, - game_data.string_database.get_item_class(item_data.class), - game_data - .string_database - .get_ability_type(AbilityType::Avoid), - avoid_rate as i32 - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data.client_strings.item_class, + game_data.string_database.get_item_class(item_data.class), + ); + + add_label_key_value( + ui, + game_data + .string_database + .get_ability_type(AbilityType::Avoid), + &(avoid_rate as i32).to_string(), + ); + }); } else { - ui.label(format!( - "{}:{} {}:{}", - game_data.client_strings.item_class, - game_data.string_database.get_item_class(item_data.class), - game_data.client_strings.item_quality, - item_data.quality - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data.client_strings.item_class, + game_data.string_database.get_item_class(item_data.class), + ); + + add_label_key_value( + ui, + game_data.client_strings.item_quality, + &item_data.quality.to_string(), + ); + }); } add_equipment_item_life_durability(ui, game_data, equipment_item); @@ -439,26 +516,38 @@ pub fn ui_add_item_tooltip( let grade_data = game_data.items.get_item_grade(equipment_item.grade); if matches!(equipment_item.item.item_type, ItemType::Face) { - ui.label(format!( - "{}:{} {}:{}", - game_data.client_strings.item_class, - game_data.string_database.get_item_class(item_data.class), - game_data.client_strings.item_quality, - item_data.quality - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data.client_strings.item_class, + game_data.string_database.get_item_class(item_data.class), + ); + + add_label_key_value( + ui, + game_data.client_strings.item_quality, + &item_data.quality.to_string(), + ); + }); } else { let avoid_rate = equipment_item.durability as f32 * 0.3 + grade_data.map(|grade| grade.avoid).unwrap_or(0) as f32; - ui.label(format!( - "{}:{} {}:{}", - game_data.client_strings.item_class, - game_data.string_database.get_item_class(item_data.class), - game_data - .string_database - .get_ability_type(AbilityType::Avoid), - avoid_rate as i32 - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data.client_strings.item_class, + game_data.string_database.get_item_class(item_data.class), + ); + + add_label_key_value( + ui, + game_data + .string_database + .get_ability_type(AbilityType::Avoid), + &(avoid_rate as i32).to_string(), + ); + }); } add_equipment_item_life_durability(ui, game_data, equipment_item); @@ -470,10 +559,13 @@ pub fn ui_add_item_tooltip( .get_feet_item(equipment_item.item.item_number) .map(|feet_item_data| feet_item_data.move_speed) { - ui.label(format!( - "[{} {}]", - game_data.client_strings.item_move_speed, move_speed - )); + ui.colored_label( + POSITIVE_EFFECT_COLOR, + format!( + "[{} {}]", + game_data.client_strings.item_move_speed, move_speed + ), + ); } } else if matches!(equipment_item.item.item_type, ItemType::Back) { if let Some(move_speed) = game_data @@ -481,10 +573,13 @@ pub fn ui_add_item_tooltip( .get_back_item(equipment_item.item.item_number) .map(|back_item_data| back_item_data.move_speed) { - ui.label(format!( - "[{} {}]", - game_data.client_strings.item_move_speed, move_speed - )); + ui.colored_label( + POSITIVE_EFFECT_COLOR, + format!( + "[{} {}]", + game_data.client_strings.item_move_speed, move_speed + ), + ); } } @@ -494,13 +589,19 @@ pub fn ui_add_item_tooltip( add_item_description(ui, game_data, item_data); } ItemType::Jewellery => { - ui.label(format!( - "{}:{} {}:{}", - game_data.client_strings.item_class, - game_data.string_database.get_item_class(item_data.class), - game_data.client_strings.item_quality, - item_data.quality - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data.client_strings.item_class, + game_data.string_database.get_item_class(item_data.class), + ); + + add_label_key_value( + ui, + game_data.client_strings.item_quality, + &item_data.quality.to_string(), + ); + }); add_item_add_ability(ui, game_data, item_data); add_equipment_item_add_appraisal(ui, game_data, equipment_item); @@ -508,13 +609,19 @@ pub fn ui_add_item_tooltip( add_item_description(ui, game_data, item_data); } ItemType::Vehicle => { - ui.label(format!( - "{}:{} {}:{}", - game_data.client_strings.item_class, - game_data.string_database.get_item_class(item_data.class), - game_data.client_strings.item_quality, - item_data.quality - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data.client_strings.item_class, + game_data.string_database.get_item_class(item_data.class), + ); + + add_label_key_value( + ui, + game_data.client_strings.item_quality, + &item_data.quality.to_string(), + ); + }); // TODO: Vehicle tooltip add_item_description(ui, game_data, item_data); @@ -531,13 +638,19 @@ pub fn ui_add_item_tooltip( .items .get_consumable_item(stackable_item.item.item_number); - ui.label(format!( - "{}:{} {}:{}", - game_data.client_strings.item_class, - game_data.string_database.get_item_class(item_data.class), - game_data.client_strings.item_quality, - item_data.quality - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data.client_strings.item_class, + game_data.string_database.get_item_class(item_data.class), + ); + + add_label_key_value( + ui, + game_data.client_strings.item_quality, + &item_data.quality.to_string(), + ); + }); match item_data.class { ItemClass::EngineFuel => { @@ -557,7 +670,10 @@ pub fn ui_add_item_tooltip( if let Some((ability_type, value)) = use_item_data.add_ability.as_ref() { - ui.label(format!("[{:?} {}]", ability_type, value)); + ui.colored_label( + POSITIVE_EFFECT_COLOR, + format!("[{:?} {}]", ability_type, value), + ); } } } @@ -570,18 +686,24 @@ pub fn ui_add_item_tooltip( .items .get_gem_item(stackable_item.item.item_number); - ui.label(format!( - "{}:{} {}:{}", - game_data.client_strings.item_class, - game_data.string_database.get_item_class(item_data.class), - game_data.client_strings.item_quality, - item_data.quality - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data.client_strings.item_class, + game_data.string_database.get_item_class(item_data.class), + ); + + add_label_key_value( + ui, + game_data.client_strings.item_quality, + &item_data.quality.to_string(), + ); + }); if let Some(gem_item_data) = gem_item_data { for &(ability_type, value) in gem_item_data.gem_add_ability.iter() { ui.colored_label( - egui::Color32::from_rgb(100, 200, 255), + POSITIVE_EFFECT_COLOR, format!( "[{} {}{}]", game_data.string_database.get_ability_type(ability_type), @@ -599,13 +721,19 @@ pub fn ui_add_item_tooltip( add_item_description(ui, game_data, item_data); } ItemType::Material => { - ui.label(format!( - "{}:{} {}:{}", - game_data.client_strings.item_class, - game_data.string_database.get_item_class(item_data.class), - game_data.client_strings.item_quality, - item_data.quality - )); + ui.horizontal(|ui| { + add_label_key_value( + ui, + game_data.client_strings.item_class, + game_data.string_database.get_item_class(item_data.class), + ); + + add_label_key_value( + ui, + game_data.client_strings.item_quality, + &item_data.quality.to_string(), + ); + }); add_item_description(ui, game_data, item_data); } @@ -683,21 +811,15 @@ fn add_skill_next_level<'a>( fn add_skill_aoe_range(ui: &mut egui::Ui, game_data: &GameData, skill_data: &SkillData) { if skill_data.scope > 0 { - ui.label(format!( - "{}: {}m", - game_data.client_strings.skill_aoe_range, - skill_data.scope / 100 - )); + let scope = format!("{}m", skill_data.scope / 100); + add_label_key_value(ui, game_data.client_strings.skill_aoe_range, &scope); } } fn add_skill_cast_range(ui: &mut egui::Ui, game_data: &GameData, skill_data: &SkillData) { if skill_data.cast_range > 0 { - ui.label(format!( - "{}: {}m", - game_data.client_strings.skill_cast_range, - skill_data.cast_range / 100 - )); + let range = format!("{}m", skill_data.cast_range / 100); + add_label_key_value(ui, game_data.client_strings.skill_cast_range, &range); } } @@ -718,17 +840,18 @@ fn add_skill_power(ui: &mut egui::Ui, game_data: &GameData, skill_data: &SkillDa _ => "", }; - ui.label(format!( - "{}: {} ({})", - game_data.client_strings.skill_power, damage_type, skill_data.power - )); + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + add_label_key_value(ui, game_data.client_strings.skill_power, damage_type); + + let power = format!(" ({})", skill_data.power); + ui.colored_label(egui::Color32::GREEN, power); + }); } fn add_skill_recover_xp(ui: &mut egui::Ui, game_data: &GameData, skill_data: &SkillData) { - ui.label(format!( - "{}: {}%", - game_data.client_strings.skill_recover_xp, skill_data.power - )); + let power = format!("{}%", skill_data.power); + add_label_key_value(ui, game_data.client_strings.skill_recover_xp, &power); } fn add_skill_require_ability( @@ -999,7 +1122,7 @@ fn add_skill_status_effects( text.push(']'); } - ui.colored_label(egui::Color32::from_rgb(100, 200, 255), text); + ui.colored_label(POSITIVE_EFFECT_COLOR, text); } } else if let Some(skill_add_ability) = skill_data.add_ability[index].as_ref() { let mut text = format!( @@ -1013,43 +1136,62 @@ fn add_skill_status_effects( add_skill_add_ability(&mut text, skill_add_ability); text.push(']'); - ui.colored_label(egui::Color32::from_rgb(100, 200, 255), text); + ui.colored_label(POSITIVE_EFFECT_COLOR, text); } } if skill_data.status_effects.iter().any(|x| x.is_some()) { if skill_data.success_ratio > 0 { - ui.label(format!( - "{}: {}-{}% {}: {}{}", - game_data.client_strings.skill_success_rate, - (skill_data.success_ratio as f32 * 0.8) as i32, - skill_data.success_ratio, - game_data.client_strings.skill_duration, - skill_data.status_effect_duration.as_secs(), - game_data.client_strings.duration_seconds, - )); + ui.horizontal(|ui| { + let success_rate = format!( + "{}-{}%", + (skill_data.success_ratio as f32 * 0.8) as i32, + skill_data.success_ratio, + ); + + add_label_key_value( + ui, + game_data.client_strings.skill_success_rate, + &success_rate, + ); + + let duration = format!( + "{} {}", + skill_data.status_effect_duration.as_secs(), + game_data.client_strings.duration_seconds, + ); + + add_label_key_value(ui, game_data.client_strings.skill_duration, &duration); + }); } else { - ui.label(format!( - "{}: 100% {}: {} {}", - game_data.client_strings.skill_success_rate, - game_data.client_strings.skill_duration, - skill_data.status_effect_duration.as_secs(), - game_data.client_strings.duration_seconds, - )); + ui.horizontal(|ui| { + add_label_key_value(ui, game_data.client_strings.skill_success_rate, "100%"); + + let duration = format!( + "{} {}", + skill_data.status_effect_duration.as_secs(), + game_data.client_strings.duration_seconds, + ); + + add_label_key_value(ui, game_data.client_strings.skill_duration, &duration); + }); } } } fn add_skill_steal_ability_value(ui: &mut egui::Ui, game_data: &GameData, skill_data: &SkillData) { for skill_add_ability in skill_data.add_ability.iter().filter_map(|x| x.as_ref()) { - ui.label(format!( - "{}: {} {}", - game_data.client_strings.skill_steal_ability, - game_data - .string_database - .get_ability_type(skill_add_ability.ability_type), - skill_add_ability.value, - )); + ui.horizontal(|ui| { + let ability = format!( + "{} {}", + game_data + .string_database + .get_ability_type(skill_add_ability.ability_type), + skill_add_ability.value, + ); + + add_label_key_value(ui, game_data.client_strings.skill_steal_ability, &ability); + }); } } @@ -1068,23 +1210,23 @@ fn add_skill_summon_points(ui: &mut egui::Ui, game_data: &GameData, skill_data: } fn add_skill_type(ui: &mut egui::Ui, game_data: &GameData, skill_data: &SkillData) { - ui.label(format!( - "{}: {}", + add_label_key_value( + ui, game_data.client_strings.skill_type, game_data .string_database - .get_skill_type(skill_data.skill_type) - )); + .get_skill_type(skill_data.skill_type), + ); } fn add_skill_target(ui: &mut egui::Ui, game_data: &GameData, skill_data: &SkillData) { - ui.label(format!( - "{}: {}", + add_label_key_value( + ui, game_data.client_strings.skill_target, game_data .string_database - .get_skill_target_filter(skill_data.target_filter) - )); + .get_skill_target_filter(skill_data.target_filter), + ); } fn add_skill_type_and_target(ui: &mut egui::Ui, game_data: &GameData, skill_data: &SkillData) { From 4047569fd3dd0710b731874cd9c565765b689609 Mon Sep 17 00:00:00 2001 From: Yentis Date: Fri, 13 Mar 2026 15:12:34 +0100 Subject: [PATCH 02/11] Add sound trigger system which triggers sound effects based on other events + missing pickup and equip sounds --- src/audio/global_sound.rs | 4 +- src/audio/spatial_sound.rs | 8 +- src/events/player_command_event.rs | 6 +- src/lib.rs | 14 +-- src/render/damage_digit_pipeline.rs | 2 +- src/render/particle_pipeline.rs | 2 +- src/systems/client_entity_event_system.rs | 47 +------ src/systems/game_connection_system.rs | 35 +----- src/systems/mod.rs | 4 +- src/systems/player_command_system.rs | 34 ++++- src/systems/sound_trigger_system.rs | 147 ++++++++++++++++++++++ src/systems/use_item_event_system.rs | 33 +---- src/ui/ui_quest_list_system.rs | 16 +-- 13 files changed, 222 insertions(+), 130 deletions(-) create mode 100644 src/systems/sound_trigger_system.rs diff --git a/src/audio/global_sound.rs b/src/audio/global_sound.rs index b1e88b5e..dd2c0d3f 100644 --- a/src/audio/global_sound.rs +++ b/src/audio/global_sound.rs @@ -14,14 +14,14 @@ enum ControlHandle { #[allow(dead_code)] impl ControlHandle { - pub fn gain_control(&mut self) -> oddio::GainControl { + pub fn gain_control(&mut self) -> oddio::GainControl<'_> { match self { ControlHandle::Stereo(handle) => handle.control::, _>(), ControlHandle::Mono(handle) => handle.control::, _>(), } } - pub fn stop_control(&mut self) -> oddio::StopControl { + pub fn stop_control(&mut self) -> oddio::StopControl<'_> { match self { ControlHandle::Stereo(handle) => handle.control::, _>(), ControlHandle::Mono(handle) => handle.control::, _>(), diff --git a/src/audio/spatial_sound.rs b/src/audio/spatial_sound.rs index 1c15f2d1..4e0c17e6 100644 --- a/src/audio/spatial_sound.rs +++ b/src/audio/spatial_sound.rs @@ -19,19 +19,19 @@ struct SpatialControlHandle( #[allow(dead_code)] impl SpatialControlHandle { - pub fn gain_control(&mut self) -> oddio::GainControl { + pub fn gain_control(&mut self) -> oddio::GainControl<'_> { self.0.control::, _>() } - pub fn stop_control(&mut self) -> oddio::StopControl { + pub fn stop_control(&mut self) -> oddio::StopControl<'_> { self.0.control::, _>() } - pub fn stream_control(&mut self) -> oddio::StreamControl { + pub fn stream_control(&mut self) -> oddio::StreamControl<'_, f32> { self.0.control::, _>() } - pub fn spatial_control(&mut self) -> oddio::SpatialControl { + pub fn spatial_control(&mut self) -> oddio::SpatialControl<'_> { self.0.control::, _>() } } diff --git a/src/events/player_command_event.rs b/src/events/player_command_event.rs index ad7d50d2..9c1eb06f 100644 --- a/src/events/player_command_event.rs +++ b/src/events/player_command_event.rs @@ -1,7 +1,7 @@ use bevy::prelude::{Entity, Event}; -use rose_data::{AmmoIndex, EquipmentIndex, VehiclePartIndex}; -use rose_game_common::components::{HotbarSlot, ItemSlot, SkillSlot}; +use rose_data::{AmmoIndex, EquipmentIndex, Item, VehiclePartIndex}; +use rose_game_common::components::{HotbarSlot, ItemSlot, Money, SkillSlot}; use crate::components::Position; @@ -23,4 +23,6 @@ pub enum PlayerCommandEvent { DropMoney(usize), BankDepositItem(ItemSlot), BankWithdrawItem(usize), + PickupDropItem(Item, Entity, ItemSlot), + PickupDropMoney(Money, Entity), } diff --git a/src/lib.rs b/src/lib.rs index eac5fb98..f7050950 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,7 @@ pub mod zms_asset_loader; pub mod zone_loader; use audio::OddioPlugin; +use components::SoundCategory; use events::{ BankEvent, CharacterSelectEvent, ChatboxEvent, ClanDialogEvent, ClientEntityEvent, ConversationDialogEvent, GameConnectionEvent, HitEvent, LoadZoneEvent, LoginEvent, @@ -88,11 +89,11 @@ use systems::{ npc_model_update_system, orbit_camera_system, particle_sequence_system, passive_recovery_system, pending_damage_system, pending_skill_effect_system, personal_store_model_add_collider_system, personal_store_model_system, player_command_system, - projectile_system, quest_trigger_system, spawn_effect_system, spawn_projectile_system, - status_effect_system, system_func_event_system, update_position_system, use_item_event_system, - vehicle_model_system, vehicle_sound_system, visible_status_effects_system, - world_connection_system, world_time_system, zone_time_system, zone_viewer_enter_system, - DebugInspectorPlugin, + projectile_system, quest_trigger_system, sound_trigger_system, spawn_effect_system, + spawn_projectile_system, status_effect_system, system_func_event_system, + update_position_system, use_item_event_system, vehicle_model_system, vehicle_sound_system, + visible_status_effects_system, world_connection_system, world_time_system, zone_time_system, + zone_viewer_enter_system, DebugInspectorPlugin, }; use ui::{ load_dialog_sprites_system, ui_bank_system, ui_character_create_system, @@ -116,8 +117,6 @@ use vfs_asset_io::VfsAssetIo; use zms_asset_loader::{ZmsAssetLoader, ZmsMaterialNumFaces, ZmsNoSkinAssetLoader}; use zone_loader::{zone_loader_system, ZoneLoader, ZoneLoaderAsset}; -use crate::components::SoundCategory; - #[derive(Default, Deserialize)] #[serde(default)] pub struct AccountConfig { @@ -874,6 +873,7 @@ fn run_client(config: &Config, app_state: AppState, mut systems_config: SystemsC passive_recovery_system, quest_trigger_system, game_mouse_input_system.after(GameSystemSets::Ui), + sound_trigger_system, ) .run_if(in_state(AppState::Game)), ); diff --git a/src/render/damage_digit_pipeline.rs b/src/render/damage_digit_pipeline.rs index 60ed1019..2f6e52d5 100644 --- a/src/render/damage_digit_pipeline.rs +++ b/src/render/damage_digit_pipeline.rs @@ -418,7 +418,7 @@ fn batch_copy(src: &[T], dst: &mut BufferVec) { } } -fn bind_buffer(buffer: &BufferVec, count: u64) -> BindingResource { +fn bind_buffer(buffer: &BufferVec, count: u64) -> BindingResource<'_> { BindingResource::Buffer(BufferBinding { buffer: buffer.buffer().expect("missing buffer"), offset: 0, diff --git a/src/render/particle_pipeline.rs b/src/render/particle_pipeline.rs index 1a09f2a3..dffb803c 100644 --- a/src/render/particle_pipeline.rs +++ b/src/render/particle_pipeline.rs @@ -581,7 +581,7 @@ fn batch_copy(src: &[T], dst: &mut BufferVec) { } } -fn bind_buffer(buffer: &BufferVec, count: u64) -> BindingResource { +fn bind_buffer(buffer: &BufferVec, count: u64) -> BindingResource<'_> { BindingResource::Buffer(BufferBinding { buffer: buffer.buffer().expect("missing buffer"), offset: 0, diff --git a/src/systems/client_entity_event_system.rs b/src/systems/client_entity_event_system.rs index ba415efe..e6d6c6c3 100644 --- a/src/systems/client_entity_event_system.rs +++ b/src/systems/client_entity_event_system.rs @@ -1,51 +1,31 @@ use bevy::prelude::{ - AssetServer, Commands, EventReader, EventWriter, GlobalTransform, Query, Res, Transform, + EventReader, EventWriter, GlobalTransform, Query, Res, }; -use rose_data::SoundId; use rose_file_readers::VfsPathBuf; use rose_game_common::components::Npc; use crate::{ - audio::SpatialSound, - components::{PlayerCharacter, SoundCategory}, + components::PlayerCharacter, events::{ChatboxEvent, ClientEntityEvent, SpawnEffectData, SpawnEffectEvent}, - resources::{GameData, SoundCache, SoundSettings}, + resources::GameData, }; pub fn client_entity_event_system( - mut commands: Commands, mut client_entity_events: EventReader, mut chatbox_events: EventWriter, mut spawn_effect_events: EventWriter, query_player: Query<&PlayerCharacter>, - query_global_transform: Query<&GlobalTransform>, query_npc: Query<(&Npc, &GlobalTransform)>, - asset_server: Res, game_data: Res, - sound_settings: Res, - sound_cache: Res, ) { let is_player = |entity| query_player.contains(entity); for event in client_entity_events.iter() { match *event { ClientEntityEvent::Die(entity) => { - if let Ok((npc, global_transform)) = query_npc.get(entity) { + if let Ok((npc, _)) = query_npc.get(entity) { if let Some(npc_data) = game_data.npcs.get_npc(npc.id) { - if let Some(sound_data) = npc_data - .die_sound_id - .and_then(|id| game_data.sounds.get_sound(id)) - { - commands.spawn(( - SoundCategory::NpcSounds, - sound_settings.gain(SoundCategory::NpcSounds), - SpatialSound::new(sound_cache.load(sound_data, &asset_server)), - Transform::from_translation(global_transform.translation()), - GlobalTransform::from_translation(global_transform.translation()), - )); - } - if let Some(die_effect_file_id) = npc_data.die_effect_file_id { spawn_effect_events.send(SpawnEffectEvent::OnEntity( entity, @@ -57,32 +37,15 @@ pub fn client_entity_event_system( } } ClientEntityEvent::LevelUp(entity, level) => { - let sound_category = if is_player(entity) { + if is_player(entity) { if let Some(level) = level { chatbox_events.send(ChatboxEvent::System(format!( "Congratulations! You are now level {}!", level ))); } - - SoundCategory::PlayerCombat - } else { - SoundCategory::OtherCombat }; - if let Ok(global_transform) = query_global_transform.get(entity) { - if let Some(sound_data) = game_data.sounds.get_sound(SoundId::new(16).unwrap()) - { - commands.spawn(( - sound_category, - sound_settings.gain(sound_category), - SpatialSound::new(sound_cache.load(sound_data, &asset_server)), - Transform::from_translation(global_transform.translation()), - GlobalTransform::from_translation(global_transform.translation()), - )); - } - } - spawn_effect_events.send(SpawnEffectEvent::OnEntity( entity, None, diff --git a/src/systems/game_connection_system.rs b/src/systems/game_connection_system.rs index 7528fae4..67510acf 100644 --- a/src/systems/game_connection_system.rs +++ b/src/systems/game_connection_system.rs @@ -41,7 +41,8 @@ use crate::{ }, events::{ BankEvent, ChatboxEvent, ClientEntityEvent, GameConnectionEvent, LoadZoneEvent, - MessageBoxEvent, PartyEvent, PersonalStoreEvent, QuestTriggerEvent, UseItemEvent, + MessageBoxEvent, PartyEvent, PersonalStoreEvent, PlayerCommandEvent, QuestTriggerEvent, + UseItemEvent, }, resources::{AppState, ClientEntityList, GameConnection, GameData, WorldRates, WorldTime}, }; @@ -137,6 +138,7 @@ pub fn game_connection_system( mut personal_store_events: EventWriter, mut quest_trigger_events: EventWriter, mut message_box_events: EventWriter, + mut player_command_events: EventWriter, ) { let Some(game_connection) = game_connection else { return; @@ -1089,39 +1091,12 @@ pub fn game_connection_system( } Ok(ServerMessage::PickupDropItem { drop_entity_id: _, item_slot, item }) => { if let Some(player_entity) = client_entity_list.player_entity { - if let Some(item_data) = - game_data.items.get_base_item(item.get_item_reference()) - { - chatbox_events.send(ChatboxEvent::System(format!( - "You have earned {}.", - item_data.name - ))); - } - - commands.add(move |world: &mut World| { - let mut player = world.entity_mut(player_entity); - if let Some(mut inventory) = player.get_mut::() { - if let Some(inventory_slot) = inventory.get_item_slot_mut(item_slot) - { - *inventory_slot = Some(item); - } - } - }); + player_command_events.send(PlayerCommandEvent::PickupDropItem(item, player_entity, item_slot)); } } Ok(ServerMessage::PickupDropMoney { drop_entity_id: _, money }) => { if let Some(player_entity) = client_entity_list.player_entity { - chatbox_events.send(ChatboxEvent::System(format!( - "You have earned {} Zuly.", - money.0 - ))); - - commands.add(move |world: &mut World| { - let mut player = world.entity_mut(player_entity); - if let Some(mut inventory) = player.get_mut::() { - inventory.try_add_money(money).ok(); - } - }); + player_command_events.send(PlayerCommandEvent::PickupDropMoney(money, player_entity)); } } Ok(ServerMessage::PickupDropError { drop_entity_id: _, error }) => match error{ diff --git a/src/systems/mod.rs b/src/systems/mod.rs index e02d75aa..7bb29151 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -49,6 +49,7 @@ mod personal_store_model_system; mod player_command_system; mod projectile_system; mod quest_trigger_system; +mod sound_trigger_system; mod spawn_effect_system; mod spawn_projectile_system; mod status_effect_system; @@ -123,6 +124,7 @@ pub use personal_store_model_system::personal_store_model_system; pub use player_command_system::player_command_system; pub use projectile_system::projectile_system; pub use quest_trigger_system::quest_trigger_system; +pub use sound_trigger_system::sound_trigger_system; pub use spawn_effect_system::spawn_effect_system; pub use spawn_projectile_system::spawn_projectile_system; pub use status_effect_system::status_effect_system; @@ -135,4 +137,4 @@ pub use visible_status_effects_system::visible_status_effects_system; pub use world_connection_system::world_connection_system; pub use world_time_system::world_time_system; pub use zone_time_system::zone_time_system; -pub use zone_viewer_system::zone_viewer_enter_system; +pub use zone_viewer_system::zone_viewer_enter_system; \ No newline at end of file diff --git a/src/systems/player_command_system.rs b/src/systems/player_command_system.rs index a9b6c8dc..dd8922e5 100644 --- a/src/systems/player_command_system.rs +++ b/src/systems/player_command_system.rs @@ -3,9 +3,8 @@ use std::time::Duration; use bevy::{ ecs::query::WorldQuery, math::Vec3Swizzles, - prelude::{Entity, EventReader, EventWriter, Query, Res, With}, + prelude::{Commands, Entity, EventReader, EventWriter, Query, Res, With, World}, }; - use rose_data::{ AmmoIndex, EquipmentIndex, ItemClass, ItemType, SkillBasicCommand, SkillCooldown, SkillTargetFilter, SkillType, VehiclePartIndex, @@ -64,6 +63,7 @@ pub fn player_command_system( game_connection: Option>, game_data: Res, selected_target: Res, + mut commands: Commands, ) { let query_player_result = query_player.get_single_mut(); if query_player_result.is_err() { @@ -770,6 +770,36 @@ pub fn player_command_system( } } PlayerCommandEvent::UseHotbar(_, _) => {} // Handled above + PlayerCommandEvent::PickupDropItem(item, entity, item_slot) => { + if let Some(item_data) = game_data.items.get_base_item(item.get_item_reference()) { + chatbox_events.send(ChatboxEvent::System(format!( + "You have earned {}.", + item_data.name + ))); + } + + commands.add(move |world: &mut World| { + let mut player = world.entity_mut(entity); + if let Some(mut inventory) = player.get_mut::() { + if let Some(inventory_slot) = inventory.get_item_slot_mut(item_slot) { + *inventory_slot = Some(item); + } + } + }); + } + PlayerCommandEvent::PickupDropMoney(money, entity) => { + chatbox_events.send(ChatboxEvent::System(format!( + "You have earned {} Zuly.", + money.0 + ))); + + commands.add(move |world: &mut World| { + let mut player = world.entity_mut(entity); + if let Some(mut inventory) = player.get_mut::() { + inventory.try_add_money(money).ok(); + } + }); + } } } } diff --git a/src/systems/sound_trigger_system.rs b/src/systems/sound_trigger_system.rs new file mode 100644 index 00000000..4ed40205 --- /dev/null +++ b/src/systems/sound_trigger_system.rs @@ -0,0 +1,147 @@ +use crate::{ + audio::SpatialSound, + components::SoundCategory, + events::{ClientEntityEvent, PlayerCommandEvent, UseItemEvent}, + resources::{GameData, SoundCache, SoundSettings}, + ui::UiSoundEvent, +}; +use bevy::{ + asset::AssetServer, + ecs::query::WorldQuery, + prelude::{Commands, Entity, EventReader, EventWriter, GlobalTransform, Query, Res, Transform}, +}; +use rose_data::SoundId; +use rose_game_common::components::{Inventory, ItemSlot, Npc}; + +const LEVEL_UP: u16 = 16; +const GET_ITEM: u16 = 531; + +#[derive(WorldQuery)] +#[world_query(mutable)] +pub struct PlayerQuery<'w> { + entity: Entity, + inventory: &'w Inventory, +} + +pub fn sound_trigger_system( + mut commands: Commands, + mut ui_sound_events: EventWriter, + mut player_command_events: EventReader, + mut client_entity_events: EventReader, + mut use_item_events: EventReader, + mut query_player: Query, + query_global_transform: Query<&GlobalTransform>, + query_npc: Query<(&Npc, &GlobalTransform)>, + game_data: Res, + sound_cache: Res, + asset_server: Res, + sound_settings: Res, +) { + let player = match query_player.get_single_mut() { + Ok(player) => player, + Err(_) => return, + }; + + let mut play_sound = + |sound_id: SoundId, sound_category: SoundCategory, global_transform: &GlobalTransform| { + let sound_data = match game_data.sounds.get_sound(sound_id) { + Some(sound_data) => sound_data, + None => return, + }; + + commands.spawn(( + sound_category, + sound_settings.gain(sound_category), + SpatialSound::new(sound_cache.load(sound_data, &asset_server)), + Transform::from_translation(global_transform.translation()), + GlobalTransform::from_translation(global_transform.translation()), + )); + }; + + let get_entity_sound = |entity: Entity| -> Option<(SoundCategory, &GlobalTransform)> { + let sound_category = if player.entity == entity { + SoundCategory::PlayerCombat + } else { + SoundCategory::OtherCombat + }; + + let global_transform = query_global_transform.get(entity).ok()?; + Some((sound_category, global_transform)) + }; + + let get_equip_sound = |item_slot: ItemSlot| -> Option { + let item = player.inventory.get_item(item_slot)?; + let item_data = game_data.items.get_base_item(item.get_item_reference())?; + + item_data.equip_sound_id + }; + + for event in player_command_events.iter() { + let event = event.clone(); + + match event { + PlayerCommandEvent::EquipAmmo(item_slot) + | PlayerCommandEvent::EquipEquipment(item_slot) + | PlayerCommandEvent::EquipVehicle(item_slot) => { + if let Some(sound_id) = get_equip_sound(item_slot) { + ui_sound_events.send(UiSoundEvent::new(sound_id)); + } + } + PlayerCommandEvent::PickupDropItem(_, _, _) + | PlayerCommandEvent::PickupDropMoney(_, _) => { + ui_sound_events.send(UiSoundEvent::new(SoundId::new(GET_ITEM).unwrap())); + } + _ => {} + }; + } + + for event in client_entity_events.iter() { + match *event { + ClientEntityEvent::Die(entity) => { + let (npc, global_transform) = match query_npc.get(entity) { + Ok((npc, global_transform)) => (npc, global_transform), + Err(_) => continue, + }; + + let npc_data = match game_data.npcs.get_npc(npc.id) { + Some(npc_data) => npc_data, + None => continue, + }; + + let sound_id = match npc_data.die_sound_id { + Some(sound_id) => sound_id, + None => continue, + }; + + play_sound(sound_id, SoundCategory::NpcSounds, global_transform); + } + ClientEntityEvent::LevelUp(entity, _) => { + if let Some((sound_category, global_transform)) = get_entity_sound(entity) { + play_sound( + SoundId::new(LEVEL_UP).unwrap(), + sound_category, + global_transform, + ); + } + } + } + } + + for event in use_item_events.iter() { + let UseItemEvent { entity, item } = *event; + + let item_data = match game_data.items.get_consumable_item(item.item_number) { + Some(item_data) => item_data, + None => continue, + }; + + let sound_id = match item_data.effect_sound_id { + Some(sound_id) => sound_id, + None => continue, + }; + + if let Some((sound_category, global_transform)) = get_entity_sound(entity) { + play_sound(sound_id, sound_category, global_transform); + } + } +} diff --git a/src/systems/use_item_event_system.rs b/src/systems/use_item_event_system.rs index 262e43c2..83155df0 100644 --- a/src/systems/use_item_event_system.rs +++ b/src/systems/use_item_event_system.rs @@ -2,10 +2,7 @@ use std::time::Duration; use bevy::{ ecs::query::WorldQuery, - prelude::{ - AssetServer, Commands, Entity, EventReader, EventWriter, GlobalTransform, Query, Res, - Transform, - }, + prelude::{Entity, EventReader, EventWriter, GlobalTransform, Query, Res}, time::Time, }; @@ -13,10 +10,9 @@ use rose_data::ItemType; use rose_game_common::components::{StatusEffects, StatusEffectsRegen}; use crate::{ - audio::SpatialSound, - components::{PlayerCharacter, SoundCategory}, + components::PlayerCharacter, events::{SpawnEffectData, SpawnEffectEvent, UseItemEvent}, - resources::{GameData, SoundCache, SoundSettings}, + resources::GameData, }; #[derive(WorldQuery)] @@ -30,14 +26,10 @@ pub struct EntityQuery<'w> { } pub fn use_item_event_system( - mut commands: Commands, mut events: EventReader, mut spawn_effect_events: EventWriter, mut query: Query, - asset_server: Res, game_data: Res, - sound_settings: Res, - sound_cache: Res, time: Res