From 73a29b339c40c4f6c43ecf7c40e140490acefcf7 Mon Sep 17 00:00:00 2001 From: CapsizeGlimmer <> Date: Sun, 24 May 2020 02:37:10 -0400 Subject: [PATCH 1/7] Added chat bubbles. Refactored bubble+name+hp+energy into new overhead widget --- .../voxygen/element/frames/bubble/bottom.png | 3 + .../element/frames/bubble/bottom_left.png | 3 + .../element/frames/bubble/bottom_right.png | 3 + assets/voxygen/element/frames/bubble/left.png | 3 + assets/voxygen/element/frames/bubble/mid.png | 3 + .../voxygen/element/frames/bubble/right.png | 3 + assets/voxygen/element/frames/bubble/tail.png | 3 + assets/voxygen/element/frames/bubble/top.png | 3 + .../element/frames/bubble/top_left.png | 3 + .../element/frames/bubble/top_right.png | 3 + voxygen/src/hud/img_ids.rs | 12 + voxygen/src/hud/mod.rs | 518 ++++++------------ voxygen/src/hud/overhead.rs | 300 ++++++++++ 13 files changed, 509 insertions(+), 351 deletions(-) create mode 100644 assets/voxygen/element/frames/bubble/bottom.png create mode 100644 assets/voxygen/element/frames/bubble/bottom_left.png create mode 100644 assets/voxygen/element/frames/bubble/bottom_right.png create mode 100644 assets/voxygen/element/frames/bubble/left.png create mode 100644 assets/voxygen/element/frames/bubble/mid.png create mode 100644 assets/voxygen/element/frames/bubble/right.png create mode 100644 assets/voxygen/element/frames/bubble/tail.png create mode 100644 assets/voxygen/element/frames/bubble/top.png create mode 100644 assets/voxygen/element/frames/bubble/top_left.png create mode 100644 assets/voxygen/element/frames/bubble/top_right.png create mode 100644 voxygen/src/hud/overhead.rs diff --git a/assets/voxygen/element/frames/bubble/bottom.png b/assets/voxygen/element/frames/bubble/bottom.png new file mode 100644 index 0000000000..a133651d2a --- /dev/null +++ b/assets/voxygen/element/frames/bubble/bottom.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b2389dc62c0765c9ed56e53afab639c7fcb90656d83a051e8cf513cda926804 +size 136 diff --git a/assets/voxygen/element/frames/bubble/bottom_left.png b/assets/voxygen/element/frames/bubble/bottom_left.png new file mode 100644 index 0000000000..514e7f025d --- /dev/null +++ b/assets/voxygen/element/frames/bubble/bottom_left.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2983324f979fe0ee212e7b4527d4d2ae042ec7da418cee01cdd0637d9410b042 +size 3485 diff --git a/assets/voxygen/element/frames/bubble/bottom_right.png b/assets/voxygen/element/frames/bubble/bottom_right.png new file mode 100644 index 0000000000..418b586b89 --- /dev/null +++ b/assets/voxygen/element/frames/bubble/bottom_right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7681e4b43db2d68f8576b26e91c8395f292b9aab76b0fac27ef2f5239e438766 +size 3639 diff --git a/assets/voxygen/element/frames/bubble/left.png b/assets/voxygen/element/frames/bubble/left.png new file mode 100644 index 0000000000..533725330d --- /dev/null +++ b/assets/voxygen/element/frames/bubble/left.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6da4e97492532b7c8b83119bd419c5689c00149e217c26dcff6e9628e0eef6de +size 124 diff --git a/assets/voxygen/element/frames/bubble/mid.png b/assets/voxygen/element/frames/bubble/mid.png new file mode 100644 index 0000000000..f00c3ea319 --- /dev/null +++ b/assets/voxygen/element/frames/bubble/mid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4adcd985ae2c5fb0f3f9c32995c1da886f91503ca238052e4eec87fd51831efe +size 109 diff --git a/assets/voxygen/element/frames/bubble/right.png b/assets/voxygen/element/frames/bubble/right.png new file mode 100644 index 0000000000..210af16f17 --- /dev/null +++ b/assets/voxygen/element/frames/bubble/right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b17fa4cacc5617de542e8ea054113dba1e8aa19272567f4a8cf026d1e849d0d0 +size 125 diff --git a/assets/voxygen/element/frames/bubble/tail.png b/assets/voxygen/element/frames/bubble/tail.png new file mode 100644 index 0000000000..19889b332f --- /dev/null +++ b/assets/voxygen/element/frames/bubble/tail.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16f8d6165a5071be33830a33215bbcd5fc4a7783a932bff396df98167e5241bb +size 227 diff --git a/assets/voxygen/element/frames/bubble/top.png b/assets/voxygen/element/frames/bubble/top.png new file mode 100644 index 0000000000..0c88a60f54 --- /dev/null +++ b/assets/voxygen/element/frames/bubble/top.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14518edba8bc71829422acd9c3e0c76dc9c78678ed1acc87e81257667eb46a79 +size 136 diff --git a/assets/voxygen/element/frames/bubble/top_left.png b/assets/voxygen/element/frames/bubble/top_left.png new file mode 100644 index 0000000000..e1b770cec8 --- /dev/null +++ b/assets/voxygen/element/frames/bubble/top_left.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4fd9c69f48c863371cef8ed8ea332710b975583b6ba7f0fa91359042a471d32 +size 184 diff --git a/assets/voxygen/element/frames/bubble/top_right.png b/assets/voxygen/element/frames/bubble/top_right.png new file mode 100644 index 0000000000..d994a4198c --- /dev/null +++ b/assets/voxygen/element/frames/bubble/top_right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8127cb08af479b2e08f3a4decfd5640c97095d7333bf7cd40a4a3e61c8cf09ae +size 219 diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index 11f8fcc76b..bec188275e 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -272,6 +272,18 @@ image_ids! { progress_frame: "voxygen.element.frames.progress_bar", progress: "voxygen.element.misc_bg.progress", + // Chat bubbles + chat_bubble_top_left: "voxygen.element.frames.bubble.top_left", + chat_bubble_top: "voxygen.element.frames.bubble.top", + chat_bubble_top_right: "voxygen.element.frames.bubble.top_right", + chat_bubble_left: "voxygen.element.frames.bubble.left", + chat_bubble_mid: "voxygen.element.frames.bubble.mid", + chat_bubble_right: "voxygen.element.frames.bubble.right", + chat_bubble_bottom_left: "voxygen.element.frames.bubble.bottom_left", + chat_bubble_bottom: "voxygen.element.frames.bubble.bottom", + chat_bubble_bottom_right: "voxygen.element.frames.bubble.bottom_right", + chat_bubble_tail: "voxygen.element.frames.bubble.tail", + <BlankGraphic> nothing: (), } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index c5a1e2878b..f4743ec34f 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -7,6 +7,7 @@ mod img_ids; mod item_imgs; mod map; mod minimap; +mod overhead; mod popup; mod settings_window; mod skillbar; @@ -102,17 +103,6 @@ widget_ids! { crosshair_inner, crosshair_outer, - // Character Names - name_tags[], - name_tags_bgs[], - levels[], - levels_skull[], - // Health Bars - health_bars[], - mana_bars[], - health_bar_fronts[], - health_bar_backs[], - // SCT player_scts[], player_sct_bgs[], @@ -125,6 +115,8 @@ widget_ids! { sct_bgs[], scts[], + overheads[], + // Intro Text intro_bg, intro_text, @@ -643,13 +635,9 @@ impl Hud { } } - // Nametags and healthbars - // Max amount the sct font size increases when "flashing" const FLASH_MAX: f32 = 25.0; - const BARSIZE: f64 = 2.0; - const MANA_BAR_HEIGHT: f64 = BARSIZE * 1.5; - const MANA_BAR_Y: f64 = MANA_BAR_HEIGHT / 2.0; + // Get player position. let player_pos = client .state() @@ -657,265 +645,6 @@ impl Hud { .read_storage::<comp::Pos>() .get(client.entity()) .map_or(Vec3::zero(), |pos| pos.0); - let mut name_id_walker = self.ids.name_tags.walk(); - let mut name_id_bg_walker = self.ids.name_tags_bgs.walk(); - let mut level_id_walker = self.ids.levels.walk(); - let mut level_skull_id_walker = self.ids.levels_skull.walk(); - let mut health_id_walker = self.ids.health_bars.walk(); - let mut mana_id_walker = self.ids.mana_bars.walk(); - let mut health_back_id_walker = self.ids.health_bar_backs.walk(); - let mut health_front_id_walker = self.ids.health_bar_fronts.walk(); - let mut sct_bg_id_walker = self.ids.sct_bgs.walk(); - let mut sct_id_walker = self.ids.scts.walk(); - - // Render Health Bars - for (pos, stats, energy, height_offset, hp_floater_list) in ( - &entities, - &pos, - interpolated.maybe(), - &stats, - &energy, - scales.maybe(), - &bodies, - &hp_floater_lists, - ) - .join() - .filter(|(entity, _, _, stats, _, _, _, _)| { - *entity != me && !stats.is_dead - //&& stats.health.current() != stats.health.maximum() - }) - // Don't show outside a certain range - .filter(|(_, pos, _, _, _, _, _, hpfl)| { - pos.0.distance_squared(player_pos) - < (if hpfl - .time_since_last_dmg_by_me - .map_or(false, |t| t < NAMETAG_DMG_TIME) - { - NAMETAG_DMG_RANGE - } else { - NAMETAG_RANGE - }) - .powi(2) - }) - .map(|(_, pos, interpolated, stats, energy, scale, body, f)| { - ( - interpolated.map_or(pos.0, |i| i.pos), - stats, - energy, - // TODO: when body.height() is more accurate remove the 2.0 - body.height() * 2.0 * scale.map_or(1.0, |s| s.0), - f, - ) - }) - { - let back_id = health_back_id_walker.next( - &mut self.ids.health_bar_backs, - &mut ui_widgets.widget_id_generator(), - ); - let health_bar_id = health_id_walker.next( - &mut self.ids.health_bars, - &mut ui_widgets.widget_id_generator(), - ); - let mana_bar_id = mana_id_walker.next( - &mut self.ids.mana_bars, - &mut ui_widgets.widget_id_generator(), - ); - let front_id = health_front_id_walker.next( - &mut self.ids.health_bar_fronts, - &mut ui_widgets.widget_id_generator(), - ); - let hp_percentage = - stats.health.current() as f64 / stats.health.maximum() as f64 * 100.0; - let energy_percentage = energy.current() as f64 / energy.maximum() as f64 * 100.0; - let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 1.0; //Animation timer - let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); - - let ingame_pos = pos + Vec3::unit_z() * height_offset; - - // Background - Image::new(self.imgs.enemy_health_bg) - .w_h(84.0 * BARSIZE, 10.0 * BARSIZE) - .x_y(0.0, MANA_BAR_Y + 6.5) //-25.5) - .color(Some(Color::Rgba(0.1, 0.1, 0.1, 0.8))) - .position_ingame(ingame_pos) - .set(back_id, ui_widgets); - - // % HP Filling - Image::new(self.imgs.enemy_bar) - .w_h(73.0 * (hp_percentage / 100.0) * BARSIZE, 6.0 * BARSIZE) - .x_y( - (4.5 + (hp_percentage / 100.0 * 36.45 - 36.45)) * BARSIZE, - MANA_BAR_Y + 7.5, - ) - .color(Some(if hp_percentage <= 25.0 { - crit_hp_color - } else if hp_percentage <= 50.0 { - LOW_HP_COLOR - } else { - HP_COLOR - })) - .position_ingame(ingame_pos) - .set(health_bar_id, ui_widgets); - // % Mana Filling - Rectangle::fill_with( - [ - 72.0 * (energy.current() as f64 / energy.maximum() as f64) * BARSIZE, - MANA_BAR_HEIGHT, - ], - MANA_COLOR, - ) - .x_y( - ((3.5 + (energy_percentage / 100.0 * 36.5)) - 36.45) * BARSIZE, - MANA_BAR_Y, //-32.0, - ) - .position_ingame(ingame_pos) - .set(mana_bar_id, ui_widgets); - - // Foreground - Image::new(self.imgs.enemy_health) - .w_h(84.0 * BARSIZE, 10.0 * BARSIZE) - .x_y(0.0, MANA_BAR_Y + 6.5) //-25.5) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.99))) - .position_ingame(ingame_pos) - .set(front_id, ui_widgets); - - // Enemy SCT - if let Some(floaters) = Some(hp_floater_list) - .filter(|fl| !fl.floaters.is_empty() && global_state.settings.gameplay.sct) - .map(|l| &l.floaters) - { - // Colors - const WHITE: Rgb<f32> = Rgb::new(1.0, 0.9, 0.8); - const LIGHT_OR: Rgb<f32> = Rgb::new(1.0, 0.925, 0.749); - const LIGHT_MED_OR: Rgb<f32> = Rgb::new(1.0, 0.85, 0.498); - const MED_OR: Rgb<f32> = Rgb::new(1.0, 0.776, 0.247); - const DARK_ORANGE: Rgb<f32> = Rgb::new(1.0, 0.7, 0.0); - const RED_ORANGE: Rgb<f32> = Rgb::new(1.0, 0.349, 0.0); - const DAMAGE_COLORS: [Rgb<f32>; 6] = [ - WHITE, - LIGHT_OR, - LIGHT_MED_OR, - MED_OR, - DARK_ORANGE, - RED_ORANGE, - ]; - // Largest value that select the first color is 40, then it shifts colors - // every 5 - let font_col = |font_size: u32| { - DAMAGE_COLORS[(font_size.saturating_sub(36) / 5).min(5) as usize] - }; - - if global_state.settings.gameplay.sct_damage_batch { - let number_speed = 50.0; // Damage number speed - let sct_bg_id = sct_bg_id_walker - .next(&mut self.ids.sct_bgs, &mut ui_widgets.widget_id_generator()); - let sct_id = sct_id_walker - .next(&mut self.ids.scts, &mut ui_widgets.widget_id_generator()); - // Calculate total change - // Ignores healing - let hp_damage = floaters.iter().fold(0, |acc, f| { - if f.hp_change < 0 { - acc + f.hp_change - } else { - acc - } - }); - let max_hp_frac = hp_damage.abs() as f32 / stats.health.maximum() as f32; - let timer = floaters - .last() - .expect("There must be at least one floater") - .timer; - // Increase font size based on fraction of maximum health - // "flashes" by having a larger size in the first 100ms - let font_size = 30 - + (max_hp_frac * 30.0) as u32 - + if timer < 0.1 { - (FLASH_MAX * (1.0 - timer / 0.1)) as u32 - } else { - 0 - }; - let font_col = font_col(font_size); - // Timer sets the widget offset - let y = (timer as f64 / crate::ecs::sys::floater::HP_SHOWTIME as f64 - * number_speed) - + 100.0; - // Timer sets text transparency - let fade = ((crate::ecs::sys::floater::HP_SHOWTIME - timer) * 0.25) + 0.2; - - Text::new(&format!("{}", (hp_damage).abs())) - .font_size(font_size) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(0.0, 0.0, 0.0, fade)) - .x_y(0.0, y - 3.0) - .position_ingame(ingame_pos) - .set(sct_bg_id, ui_widgets); - Text::new(&format!("{}", hp_damage.abs())) - .font_size(font_size) - .font_id(self.fonts.cyri.conrod_id) - .x_y(0.0, y) - .color(if hp_damage < 0 { - Color::Rgba(font_col.r, font_col.g, font_col.b, fade) - } else { - Color::Rgba(0.1, 1.0, 0.1, fade) - }) - .position_ingame(ingame_pos) - .set(sct_id, ui_widgets); - } else { - for floater in floaters { - let number_speed = 250.0; // Single Numbers Speed - let sct_bg_id = sct_bg_id_walker - .next(&mut self.ids.sct_bgs, &mut ui_widgets.widget_id_generator()); - let sct_id = sct_id_walker - .next(&mut self.ids.scts, &mut ui_widgets.widget_id_generator()); - // Calculate total change - let max_hp_frac = - floater.hp_change.abs() as f32 / stats.health.maximum() as f32; - // Increase font size based on fraction of maximum health - // "flashes" by having a larger size in the first 100ms - let font_size = 30 - + (max_hp_frac * 30.0) as u32 - + if floater.timer < 0.1 { - (FLASH_MAX * (1.0 - floater.timer / 0.1)) as u32 - } else { - 0 - }; - let font_col = font_col(font_size); - // Timer sets the widget offset - let y = (floater.timer as f64 - / crate::ecs::sys::floater::HP_SHOWTIME as f64 - * number_speed) - + 100.0; - // Timer sets text transparency - let fade = ((crate::ecs::sys::floater::HP_SHOWTIME - floater.timer) - * 0.25) - + 0.2; - - Text::new(&format!("{}", (floater.hp_change).abs())) - .font_size(font_size) - .font_id(self.fonts.cyri.conrod_id) - .color(if floater.hp_change < 0 { - Color::Rgba(0.0, 0.0, 0.0, fade) - } else { - Color::Rgba(0.0, 0.0, 0.0, 1.0) - }) - .x_y(0.0, y - 3.0) - .position_ingame(ingame_pos) - .set(sct_bg_id, ui_widgets); - Text::new(&format!("{}", (floater.hp_change).abs())) - .font_size(font_size) - .font_id(self.fonts.cyri.conrod_id) - .x_y(0.0, y) - .color(if floater.hp_change < 0 { - Color::Rgba(font_col.r, font_col.g, font_col.b, fade) - } else { - Color::Rgba(0.1, 1.0, 0.1, 1.0) - }) - .position_ingame(ingame_pos) - .set(sct_id, ui_widgets); - } - } - } - } if global_state.settings.gameplay.sct { // Render Player SCT numbers @@ -1156,21 +885,26 @@ impl Hud { } } - // Render Name Tags - for (pos, name, level, height_offset) in ( + let mut overhead_walker = self.ids.overheads.walk(); + let mut sct_walker = self.ids.scts.walk(); + let mut sct_bg_walker = self.ids.sct_bgs.walk(); + + // Render overhead name tags and health bars + for (pos, name, stats, energy, height_offset, hpfl) in ( &entities, &pos, interpolated.maybe(), &stats, + &energy, players.maybe(), scales.maybe(), &bodies, &hp_floater_lists, ) .join() - .filter(|(entity, _, _, stats, _, _, _, _)| *entity != me && !stats.is_dead) + .filter(|(entity, _, _, stats, _, _, _, _, _)| *entity != me && !stats.is_dead) // Don't show outside a certain range - .filter(|(_, pos, _, _, _, _, _, hpfl)| { + .filter(|(_, pos, _, _, _, _, _, _, hpfl)| { pos.0.distance_squared(player_pos) < (if hpfl .time_since_last_dmg_by_me @@ -1182,7 +916,7 @@ impl Hud { }) .powi(2) }) - .map(|(_, pos, interpolated, stats, player, scale, body, _)| { + .map(|(_, pos, interpolated, stats, energy, player, scale, body, hpfl)| { // TODO: This is temporary // If the player used the default character name display their name instead let name = if stats.name == "Character Name" { @@ -1192,87 +926,169 @@ impl Hud { }; ( interpolated.map_or(pos.0, |i| i.pos), - format!("{}", name), - stats.level, + name, + stats, + energy, + // TODO: when body.height() is more accurate remove the 2.0 body.height() * 2.0 * scale.map_or(1.0, |s| s.0), + hpfl, ) }) { - let name_id = name_id_walker.next( - &mut self.ids.name_tags, + let overhead_id = overhead_walker.next( + &mut self.ids.overheads, &mut ui_widgets.widget_id_generator(), ); - let name_bg_id = name_id_bg_walker.next( - &mut self.ids.name_tags_bgs, - &mut ui_widgets.widget_id_generator(), - ); - let level_id = level_id_walker - .next(&mut self.ids.levels, &mut ui_widgets.widget_id_generator()); - let level_skull_id = level_skull_id_walker.next( - &mut self.ids.levels_skull, - &mut ui_widgets.widget_id_generator(), - ); - let ingame_pos = pos + Vec3::unit_z() * height_offset; - // Name - Text::new(&name) - .font_id(self.fonts.cyri.conrod_id) - .font_size(30) - .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) - .x_y(-1.0, MANA_BAR_Y + 48.0) - .position_ingame(ingame_pos) - .set(name_bg_id, ui_widgets); - Text::new(&name) - .font_id(self.fonts.cyri.conrod_id) - .font_size(30) - .color(Color::Rgba(0.61, 0.61, 0.89, 1.0)) - .x_y(0.0, MANA_BAR_Y + 50.0) - .position_ingame(ingame_pos) - .set(name_id, ui_widgets); + // Chat bubble, name, level, and hp bars + overhead::Overhead::new( + &name, + stats, + energy, + own_level, + self.pulse, + &self.imgs, + &self.fonts, + ) + .x_y(0.0, 100.0) + .position_ingame(ingame_pos) + .set(overhead_id, ui_widgets); - // Level - const LOW: Color = Color::Rgba(0.54, 0.81, 0.94, 0.4); - const HIGH: Color = Color::Rgba(1.0, 0.0, 0.0, 1.0); - const EQUAL: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); - let op_level = level.level(); - let level_str = format!("{}", op_level); - // Change visuals of the level display depending on the player level/opponent - // level - let level_comp = op_level as i64 - own_level as i64; - // + 10 level above player -> skull - // + 5-10 levels above player -> high - // -5 - +5 levels around player level -> equal - // - 5 levels below player -> low - Text::new(if level_comp < 10 { &level_str } else { "?" }) - .font_id(self.fonts.cyri.conrod_id) - .font_size(if op_level > 9 && level_comp < 10 { - 14 + // Enemy SCT + if global_state.settings.gameplay.sct && !hpfl.floaters.is_empty() { + let floaters = &hpfl.floaters; + + // Colors + const WHITE: Rgb<f32> = Rgb::new(1.0, 0.9, 0.8); + const LIGHT_OR: Rgb<f32> = Rgb::new(1.0, 0.925, 0.749); + const LIGHT_MED_OR: Rgb<f32> = Rgb::new(1.0, 0.85, 0.498); + const MED_OR: Rgb<f32> = Rgb::new(1.0, 0.776, 0.247); + const DARK_ORANGE: Rgb<f32> = Rgb::new(1.0, 0.7, 0.0); + const RED_ORANGE: Rgb<f32> = Rgb::new(1.0, 0.349, 0.0); + const DAMAGE_COLORS: [Rgb<f32>; 6] = [ + WHITE, + LIGHT_OR, + LIGHT_MED_OR, + MED_OR, + DARK_ORANGE, + RED_ORANGE, + ]; + // Largest value that select the first color is 40, then it shifts colors + // every 5 + let font_col = |font_size: u32| { + DAMAGE_COLORS[(font_size.saturating_sub(36) / 5).min(5) as usize] + }; + + if global_state.settings.gameplay.sct_damage_batch { + let number_speed = 50.0; // Damage number speed + let sct_id = sct_walker + .next(&mut self.ids.scts, &mut ui_widgets.widget_id_generator()); + let sct_bg_id = sct_bg_walker + .next(&mut self.ids.sct_bgs, &mut ui_widgets.widget_id_generator()); + // Calculate total change + // Ignores healing + let hp_damage = floaters.iter().fold(0, |acc, f| { + if f.hp_change < 0 { + acc + f.hp_change + } else { + acc + } + }); + let max_hp_frac = hp_damage.abs() as f32 / stats.health.maximum() as f32; + let timer = floaters + .last() + .expect("There must be at least one floater") + .timer; + // Increase font size based on fraction of maximum health + // "flashes" by having a larger size in the first 100ms + let font_size = 30 + + (max_hp_frac * 30.0) as u32 + + if timer < 0.1 { + (FLASH_MAX * (1.0 - timer / 0.1)) as u32 + } else { + 0 + }; + let font_col = font_col(font_size); + // Timer sets the widget offset + let y = (timer as f64 / crate::ecs::sys::floater::HP_SHOWTIME as f64 + * number_speed) + + 100.0; + // Timer sets text transparency + let fade = ((crate::ecs::sys::floater::HP_SHOWTIME - timer) * 0.25) + 0.2; + + Text::new(&format!("{}", (hp_damage).abs())) + .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) + .color(Color::Rgba(0.0, 0.0, 0.0, fade)) + .x_y(0.0, y - 3.0) + .position_ingame(ingame_pos) + .set(sct_bg_id, ui_widgets); + Text::new(&format!("{}", hp_damage.abs())) + .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) + .x_y(0.0, y) + .color(if hp_damage < 0 { + Color::Rgba(font_col.r, font_col.g, font_col.b, fade) + } else { + Color::Rgba(0.1, 1.0, 0.1, fade) + }) + .position_ingame(ingame_pos) + .set(sct_id, ui_widgets); } else { - 15 - }) - .color(if level_comp > 4 { - HIGH - } else if level_comp < -5 { - LOW - } else { - EQUAL - }) - .x_y(-37.0 * BARSIZE, MANA_BAR_Y + 9.0) - .position_ingame(ingame_pos) - .set(level_id, ui_widgets); - if level_comp > 9 { - let skull_ani = ((self.pulse * 0.7/* speed factor */).cos() * 0.5 + 0.5) * 10.0; //Animation timer - Image::new(if skull_ani as i32 == 1 && rand::random::<f32>() < 0.9 { - self.imgs.skull_2 - } else { - self.imgs.skull - }) - .w_h(18.0 * BARSIZE, 18.0 * BARSIZE) - .x_y(-39.0 * BARSIZE, MANA_BAR_Y + 7.0) - .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) - .position_ingame(ingame_pos) - .set(level_skull_id, ui_widgets); + for floater in floaters { + let number_speed = 250.0; // Single Numbers Speed + let sct_id = sct_walker + .next(&mut self.ids.scts, &mut ui_widgets.widget_id_generator()); + let sct_bg_id = sct_bg_walker + .next(&mut self.ids.sct_bgs, &mut ui_widgets.widget_id_generator()); + // Calculate total change + let max_hp_frac = + floater.hp_change.abs() as f32 / stats.health.maximum() as f32; + // Increase font size based on fraction of maximum health + // "flashes" by having a larger size in the first 100ms + let font_size = 30 + + (max_hp_frac * 30.0) as u32 + + if floater.timer < 0.1 { + (FLASH_MAX * (1.0 - floater.timer / 0.1)) as u32 + } else { + 0 + }; + let font_col = font_col(font_size); + // Timer sets the widget offset + let y = (floater.timer as f64 + / crate::ecs::sys::floater::HP_SHOWTIME as f64 + * number_speed) + + 100.0; + // Timer sets text transparency + let fade = ((crate::ecs::sys::floater::HP_SHOWTIME - floater.timer) + * 0.25) + + 0.2; + + Text::new(&format!("{}", (floater.hp_change).abs())) + .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) + .color(if floater.hp_change < 0 { + Color::Rgba(0.0, 0.0, 0.0, fade) + } else { + Color::Rgba(0.0, 0.0, 0.0, 1.0) + }) + .x_y(0.0, y - 3.0) + .position_ingame(ingame_pos) + .set(sct_bg_id, ui_widgets); + Text::new(&format!("{}", (floater.hp_change).abs())) + .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) + .x_y(0.0, y) + .color(if floater.hp_change < 0 { + Color::Rgba(font_col.r, font_col.g, font_col.b, fade) + } else { + Color::Rgba(0.1, 1.0, 0.1, 1.0) + }) + .position_ingame(ingame_pos) + .set(sct_id, ui_widgets); + } + } } } } diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs new file mode 100644 index 0000000000..2f66aa5493 --- /dev/null +++ b/voxygen/src/hud/overhead.rs @@ -0,0 +1,300 @@ +use super::{img_ids::Imgs, HP_COLOR, LOW_HP_COLOR, MANA_COLOR}; +use crate::ui::{fonts::ConrodVoxygenFonts, Ingameable}; +use common::comp::{Energy, Stats}; +use conrod_core::{ + position::Align, + widget::{self, Image, Rectangle, Text}, + widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, +}; + +widget_ids! { + struct Ids { + // Chat bubble + chat_bubble_text, + chat_bubble_text2, + chat_bubble_top_left, + chat_bubble_top, + chat_bubble_top_right, + chat_bubble_left, + chat_bubble_mid, + chat_bubble_right, + chat_bubble_bottom_left, + chat_bubble_bottom, + chat_bubble_bottom_right, + chat_bubble_tail, + + // Name + name_bg, + name, + + // HP + level, + level_skull, + health_bar, + health_bar_bg, + mana_bar, + health_bar_fg, + } +} + +/// ui widget containing everything that goes over a character's head +/// (Speech bubble, Name, Level, HP/energy bars, etc.) +#[derive(WidgetCommon)] +pub struct Overhead<'a> { + name: &'a str, + stats: &'a Stats, + energy: &'a Energy, + own_level: u32, + pulse: f32, + imgs: &'a Imgs, + fonts: &'a ConrodVoxygenFonts, + #[conrod(common_builder)] + common: widget::CommonBuilder, +} + +impl<'a> Overhead<'a> { + pub fn new( + name: &'a str, + stats: &'a Stats, + energy: &'a Energy, + own_level: u32, + pulse: f32, + imgs: &'a Imgs, + fonts: &'a ConrodVoxygenFonts, + ) -> Self { + Self { + name, + stats, + energy, + own_level, + pulse, + imgs, + fonts, + common: widget::CommonBuilder::default(), + } + } +} + +pub struct State { + ids: Ids, +} + +impl<'a> Ingameable for Overhead<'a> { + fn prim_count(&self) -> usize { + // Number of conrod primitives contained in the overhead display. TODO maybe + // this could be done automatically? + // - 2 Text::new for name + // - 2 Text::new for speech bubble + // - 10 Image::new for speech bubble (9-slice + tail) + // - 1 for level: either Text or Image + // - 4 for HP + mana + fg + bg + 19 + } +} + +impl<'a> Widget for Overhead<'a> { + type Event = (); + type State = State; + type Style = (); + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + } + } + + fn style(&self) -> Self::Style { () } + + fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event { + let widget::UpdateArgs { id, state, ui, .. } = args; + + const BARSIZE: f64 = 2.0; + const MANA_BAR_HEIGHT: f64 = BARSIZE * 1.5; + const MANA_BAR_Y: f64 = MANA_BAR_HEIGHT / 2.0; + + // Name + Text::new(&self.name) + .font_id(self.fonts.cyri.conrod_id) + .font_size(30) + .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) + .x_y(-1.0, MANA_BAR_Y + 48.0) + .set(state.ids.name_bg, ui); + Text::new(&self.name) + .font_id(self.fonts.cyri.conrod_id) + .font_size(30) + .color(Color::Rgba(0.61, 0.61, 0.89, 1.0)) + .x_y(0.0, MANA_BAR_Y + 50.0) + .set(state.ids.name, ui); + + // Speech bubble + Text::new("Hello") + .font_id(self.fonts.cyri.conrod_id) + .font_size(15) + .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) + .up_from(state.ids.name, 10.0) + .x_align_to(state.ids.name, Align::Middle) + .parent(id) + .set(state.ids.chat_bubble_text, ui); + Image::new(self.imgs.chat_bubble_top_left) + .w_h(10.0, 10.0) + .top_left_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_top_left, ui); + Image::new(self.imgs.chat_bubble_top) + .h(10.0) + .w_of(state.ids.chat_bubble_text) + .mid_top_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_top, ui); + Image::new(self.imgs.chat_bubble_top_right) + .w_h(10.0, 10.0) + .top_right_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_top_right, ui); + Image::new(self.imgs.chat_bubble_left) + .w(10.0) + .h_of(state.ids.chat_bubble_text) + .mid_left_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_left, ui); + Image::new(self.imgs.chat_bubble_mid) + .wh_of(state.ids.chat_bubble_text) + .top_left_of(state.ids.chat_bubble_text) + .parent(id) + .set(state.ids.chat_bubble_mid, ui); + Image::new(self.imgs.chat_bubble_right) + .w(10.0) + .h_of(state.ids.chat_bubble_text) + .mid_right_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_right, ui); + Image::new(self.imgs.chat_bubble_bottom_left) + .w_h(10.0, 10.0) + .bottom_left_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_bottom_left, ui); + Image::new(self.imgs.chat_bubble_bottom) + .h(10.0) + .w_of(state.ids.chat_bubble_text) + .mid_bottom_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_bottom, ui); + Image::new(self.imgs.chat_bubble_bottom_right) + .w_h(10.0, 10.0) + .bottom_right_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_bottom_right, ui); + Image::new(self.imgs.chat_bubble_tail) + .w_h(11.0, 16.0) + .mid_bottom_with_margin_on(state.ids.chat_bubble_text, -16.0) + .parent(id) + .set(state.ids.chat_bubble_tail, ui); + // Why is there a second text widget?: The first is to position the 9-slice + // around and the second is to display text. Changing .depth manually + // causes strange problems in unrelated parts of the ui (the debug + // overlay is offset by a npc's screen position) TODO + Text::new("Hello") + .font_id(self.fonts.cyri.conrod_id) + .font_size(15) + .top_left_of(state.ids.chat_bubble_text) + .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) + .parent(id) + .set(state.ids.chat_bubble_text2, ui); + + let hp_percentage = + self.stats.health.current() as f64 / self.stats.health.maximum() as f64 * 100.0; + let energy_percentage = self.energy.current() as f64 / self.energy.maximum() as f64 * 100.0; + let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 1.0; //Animation timer + let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); + + // Background + Image::new(self.imgs.enemy_health_bg) + .w_h(84.0 * BARSIZE, 10.0 * BARSIZE) + .x_y(0.0, MANA_BAR_Y + 6.5) //-25.5) + .color(Some(Color::Rgba(0.1, 0.1, 0.1, 0.8))) + .parent(id) + .set(state.ids.health_bar_bg, ui); + + // % HP Filling + Image::new(self.imgs.enemy_bar) + .w_h(73.0 * (hp_percentage / 100.0) * BARSIZE, 6.0 * BARSIZE) + .x_y( + (4.5 + (hp_percentage / 100.0 * 36.45 - 36.45)) * BARSIZE, + MANA_BAR_Y + 7.5, + ) + .color(Some(if hp_percentage <= 25.0 { + crit_hp_color + } else if hp_percentage <= 50.0 { + LOW_HP_COLOR + } else { + HP_COLOR + })) + .parent(id) + .set(state.ids.health_bar, ui); + // % Mana Filling + Rectangle::fill_with( + [ + 72.0 * (self.energy.current() as f64 / self.energy.maximum() as f64) * BARSIZE, + MANA_BAR_HEIGHT, + ], + MANA_COLOR, + ) + .x_y( + ((3.5 + (energy_percentage / 100.0 * 36.5)) - 36.45) * BARSIZE, + MANA_BAR_Y, //-32.0, + ) + .parent(id) + .set(state.ids.mana_bar, ui); + + // Foreground + Image::new(self.imgs.enemy_health) + .w_h(84.0 * BARSIZE, 10.0 * BARSIZE) + .x_y(0.0, MANA_BAR_Y + 6.5) //-25.5) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.99))) + .parent(id) + .set(state.ids.health_bar_fg, ui); + + // Level + const LOW: Color = Color::Rgba(0.54, 0.81, 0.94, 0.4); + const HIGH: Color = Color::Rgba(1.0, 0.0, 0.0, 1.0); + const EQUAL: Color = Color::Rgba(1.0, 1.0, 1.0, 1.0); + // Change visuals of the level display depending on the player level/opponent + // level + let level_comp = self.stats.level.level() as i64 - self.own_level as i64; + // + 10 level above player -> skull + // + 5-10 levels above player -> high + // -5 - +5 levels around player level -> equal + // - 5 levels below player -> low + if level_comp > 9 { + let skull_ani = ((self.pulse * 0.7/* speed factor */).cos() * 0.5 + 0.5) * 10.0; //Animation timer + Image::new(if skull_ani as i32 == 1 && rand::random::<f32>() < 0.9 { + self.imgs.skull_2 + } else { + self.imgs.skull + }) + .w_h(18.0 * BARSIZE, 18.0 * BARSIZE) + .x_y(-39.0 * BARSIZE, MANA_BAR_Y + 7.0) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) + .parent(id) + .set(state.ids.level_skull, ui); + } else { + Text::new(&format!("{}", self.stats.level.level())) + .font_id(self.fonts.cyri.conrod_id) + .font_size(if self.stats.level.level() > 9 && level_comp < 10 { + 14 + } else { + 15 + }) + .color(if level_comp > 4 { + HIGH + } else if level_comp < -5 { + LOW + } else { + EQUAL + }) + .x_y(-37.0 * BARSIZE, MANA_BAR_Y + 9.0) + .parent(id) + .set(state.ids.level, ui); + } + } +} From c65967ccdbf991cc15410047f5c788ce59bd03f1 Mon Sep 17 00:00:00 2001 From: CapsizeGlimmer <> Date: Sun, 24 May 2020 18:18:41 -0400 Subject: [PATCH 2/7] Chatting now creates speech bubbles --- common/src/comp/agent.rs | 18 +++- common/src/comp/mod.rs | 2 +- common/src/msg/ecs_packet.rs | 7 ++ common/src/state.rs | 1 + server/src/lib.rs | 1 + server/src/sys/message.rs | 31 ++++--- server/src/sys/mod.rs | 4 + server/src/sys/sentinel.rs | 17 +++- server/src/sys/speech_bubble.rs | 30 ++++++ voxygen/src/hud/mod.rs | 12 ++- voxygen/src/hud/overhead.rs | 158 ++++++++++++++++---------------- 11 files changed, 184 insertions(+), 97 deletions(-) create mode 100644 server/src/sys/speech_bubble.rs diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index be33b4bb49..a98c38e2f8 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -1,5 +1,5 @@ -use crate::path::Chaser; -use specs::{Component, Entity as EcsEntity}; +use crate::{path::Chaser, state::Time}; +use specs::{Component, Entity as EcsEntity, FlaggedStorage, HashMapStorage}; use specs_idvs::IDVStorage; use vek::*; @@ -85,3 +85,17 @@ impl Activity { impl Default for Activity { fn default() -> Self { Activity::Idle(Vec2::zero()) } } + +/// Default duration in seconds of chat bubbles +pub const SPEECH_BUBBLE_DURATION: f64 = 5.0; + +/// Adds a speech bubble to the entity +#[derive(Clone, Default, Debug, Serialize, Deserialize)] +pub struct SpeechBubble { + pub message: String, + pub timeout: Option<Time>, + // TODO add icon enum for player chat type / npc quest+trade +} +impl Component for SpeechBubble { + type Storage = FlaggedStorage<Self, HashMapStorage<Self>>; +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 5bf573e9f0..dfea20e7ca 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -18,7 +18,7 @@ mod visual; // Reexports pub use ability::{CharacterAbility, ItemConfig, Loadout}; pub use admin::Admin; -pub use agent::{Agent, Alignment}; +pub use agent::{Agent, Alignment, SpeechBubble, SPEECH_BUBBLE_DURATION}; pub use body::{ biped_large, bird_medium, bird_small, critter, dragon, fish_medium, fish_small, golem, humanoid, object, quadruped_medium, quadruped_small, AllBodies, Body, BodyData, diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index 2e7ad564c5..83ad59777f 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -24,6 +24,7 @@ sum_type! { Sticky(comp::Sticky), Loadout(comp::Loadout), CharacterState(comp::CharacterState), + SpeechBubble(comp::SpeechBubble), Pos(comp::Pos), Vel(comp::Vel), Ori(comp::Ori), @@ -50,6 +51,7 @@ sum_type! { Sticky(PhantomData<comp::Sticky>), Loadout(PhantomData<comp::Loadout>), CharacterState(PhantomData<comp::CharacterState>), + SpeechBubble(PhantomData<comp::SpeechBubble>), Pos(PhantomData<comp::Pos>), Vel(PhantomData<comp::Vel>), Ori(PhantomData<comp::Ori>), @@ -76,6 +78,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Loadout(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::CharacterState(comp) => sync::handle_insert(comp, entity, world), + EcsCompPacket::SpeechBubble(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Pos(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Vel(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Ori(comp) => sync::handle_insert(comp, entity, world), @@ -100,6 +103,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Loadout(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::CharacterState(comp) => sync::handle_modify(comp, entity, world), + EcsCompPacket::SpeechBubble(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Pos(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Vel(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Ori(comp) => sync::handle_modify(comp, entity, world), @@ -128,6 +132,9 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPhantom::CharacterState(_) => { sync::handle_remove::<comp::CharacterState>(entity, world) }, + EcsCompPhantom::SpeechBubble(_) => { + sync::handle_remove::<comp::SpeechBubble>(entity, world) + }, EcsCompPhantom::Pos(_) => sync::handle_remove::<comp::Pos>(entity, world), EcsCompPhantom::Vel(_) => sync::handle_remove::<comp::Vel>(entity, world), EcsCompPhantom::Ori(_) => sync::handle_remove::<comp::Ori>(entity, world), diff --git a/common/src/state.rs b/common/src/state.rs index 9d47b9bbb6..b48d4ad35a 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -122,6 +122,7 @@ impl State { ecs.register::<comp::Sticky>(); ecs.register::<comp::Gravity>(); ecs.register::<comp::CharacterState>(); + ecs.register::<comp::SpeechBubble>(); // Register components send from clients -> server ecs.register::<comp::Controller>(); diff --git a/server/src/lib.rs b/server/src/lib.rs index 10fdb50740..2cec36ad00 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -109,6 +109,7 @@ impl Server { state.ecs_mut().insert(sys::TerrainSyncTimer::default()); state.ecs_mut().insert(sys::TerrainTimer::default()); state.ecs_mut().insert(sys::WaypointTimer::default()); + state.ecs_mut().insert(sys::SpeechBubbleTimer::default()); state .ecs_mut() .insert(sys::StatsPersistenceTimer::default()); diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 8307c97d41..1579ccb477 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -4,7 +4,10 @@ use crate::{ CLIENT_TIMEOUT, }; use common::{ - comp::{Admin, CanBuild, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, Stats, Vel}, + comp::{ + Admin, CanBuild, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, SpeechBubble, + Stats, Vel, SPEECH_BUBBLE_DURATION, + }, event::{EventBus, ServerEvent}, msg::{ validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate, @@ -43,6 +46,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Player>, WriteStorage<'a, Client>, WriteStorage<'a, Controller>, + WriteStorage<'a, SpeechBubble>, ); fn run( @@ -67,6 +71,7 @@ impl<'a> System<'a> for Sys { mut players, mut clients, mut controllers, + mut speech_bubbles, ): Self::SystemData, ) { timer.start(); @@ -394,13 +399,20 @@ impl<'a> System<'a> for Sys { for (entity, msg) in new_chat_msgs { match msg { ServerMsg::ChatMsg { chat_type, message } => { - if let Some(entity) = entity { + let message = if let Some(entity) = entity { // Handle chat commands. if message.starts_with("/") && message.len() > 1 { let argv = String::from(&message[1..]); server_emitter.emit(ServerEvent::ChatCmd(entity, argv)); + continue; } else { - let message = match players.get(entity) { + let timeout = Some(Time(time + SPEECH_BUBBLE_DURATION)); + let bubble = SpeechBubble { + message: message.clone(), + timeout, + }; + let _ = speech_bubbles.insert(entity, bubble); + match players.get(entity) { Some(player) => { if admins.get(entity).is_some() { format!("[ADMIN][{}] {}", &player.alias, message) @@ -409,17 +421,14 @@ impl<'a> System<'a> for Sys { } }, None => format!("[<Unknown>] {}", message), - }; - let msg = ServerMsg::ChatMsg { chat_type, message }; - for client in (&mut clients).join().filter(|c| c.is_registered()) { - client.notify(msg.clone()); } } } else { - let msg = ServerMsg::ChatMsg { chat_type, message }; - for client in (&mut clients).join().filter(|c| c.is_registered()) { - client.notify(msg.clone()); - } + message + }; + let msg = ServerMsg::ChatMsg { chat_type, message }; + for client in (&mut clients).join().filter(|c| c.is_registered()) { + client.notify(msg.clone()); } }, _ => { diff --git a/server/src/sys/mod.rs b/server/src/sys/mod.rs index 6035fea593..e18e853ad8 100644 --- a/server/src/sys/mod.rs +++ b/server/src/sys/mod.rs @@ -2,6 +2,7 @@ pub mod entity_sync; pub mod message; pub mod persistence; pub mod sentinel; +pub mod speech_bubble; pub mod subscription; pub mod terrain; pub mod terrain_sync; @@ -20,6 +21,7 @@ pub type SubscriptionTimer = SysTimer<subscription::Sys>; pub type TerrainTimer = SysTimer<terrain::Sys>; pub type TerrainSyncTimer = SysTimer<terrain_sync::Sys>; pub type WaypointTimer = SysTimer<waypoint::Sys>; +pub type SpeechBubbleTimer = SysTimer<speech_bubble::Sys>; pub type StatsPersistenceTimer = SysTimer<persistence::stats::Sys>; pub type StatsPersistenceScheduler = SysScheduler<persistence::stats::Sys>; @@ -31,11 +33,13 @@ pub type StatsPersistenceScheduler = SysScheduler<persistence::stats::Sys>; //const TERRAIN_SYNC_SYS: &str = "server_terrain_sync_sys"; const TERRAIN_SYS: &str = "server_terrain_sys"; const WAYPOINT_SYS: &str = "waypoint_sys"; +const SPEECH_BUBBLE_SYS: &str = "speech_bubble_sys"; const STATS_PERSISTENCE_SYS: &str = "stats_persistence_sys"; pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) { dispatch_builder.add(terrain::Sys, TERRAIN_SYS, &[]); dispatch_builder.add(waypoint::Sys, WAYPOINT_SYS, &[]); + dispatch_builder.add(speech_bubble::Sys, SPEECH_BUBBLE_SYS, &[]); dispatch_builder.add(persistence::stats::Sys, STATS_PERSISTENCE_SYS, &[]); } diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index 3c90aec221..27acbd7a0f 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -2,7 +2,7 @@ use super::SysTimer; use common::{ comp::{ Body, CanBuild, CharacterState, Collider, Energy, Gravity, Item, LightEmitter, Loadout, - Mass, MountState, Mounting, Ori, Player, Pos, Scale, Stats, Sticky, Vel, + Mass, MountState, Mounting, Ori, Player, Pos, Scale, SpeechBubble, Stats, Sticky, Vel, }, msg::EcsCompPacket, sync::{CompSyncPackage, EntityPackage, EntitySyncPackage, Uid, UpdateTracker, WorldSyncExt}, @@ -54,6 +54,7 @@ pub struct TrackedComps<'a> { pub gravity: ReadStorage<'a, Gravity>, pub loadout: ReadStorage<'a, Loadout>, pub character_state: ReadStorage<'a, CharacterState>, + pub speech_bubble: ReadStorage<'a, SpeechBubble>, } impl<'a> TrackedComps<'a> { pub fn create_entity_package( @@ -125,6 +126,10 @@ impl<'a> TrackedComps<'a> { .get(entity) .cloned() .map(|c| comps.push(c.into())); + self.speech_bubble + .get(entity) + .cloned() + .map(|c| comps.push(c.into())); // Add untracked comps pos.map(|c| comps.push(c.into())); vel.map(|c| comps.push(c.into())); @@ -152,6 +157,7 @@ pub struct ReadTrackers<'a> { pub gravity: ReadExpect<'a, UpdateTracker<Gravity>>, pub loadout: ReadExpect<'a, UpdateTracker<Loadout>>, pub character_state: ReadExpect<'a, UpdateTracker<CharacterState>>, + pub speech_bubble: ReadExpect<'a, UpdateTracker<SpeechBubble>>, } impl<'a> ReadTrackers<'a> { pub fn create_sync_packages( @@ -188,6 +194,12 @@ impl<'a> ReadTrackers<'a> { &*self.character_state, &comps.character_state, filter, + ) + .with_component( + &comps.uid, + &*self.speech_bubble, + &comps.speech_bubble, + filter, ); (entity_sync_package, comp_sync_package) @@ -213,6 +225,7 @@ pub struct WriteTrackers<'a> { gravity: WriteExpect<'a, UpdateTracker<Gravity>>, loadout: WriteExpect<'a, UpdateTracker<Loadout>>, character_state: WriteExpect<'a, UpdateTracker<CharacterState>>, + speech_bubble: WriteExpect<'a, UpdateTracker<SpeechBubble>>, } fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { @@ -236,6 +249,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { trackers .character_state .record_changes(&comps.character_state); + trackers.speech_bubble.record_changes(&comps.speech_bubble); } pub fn register_trackers(world: &mut World) { @@ -256,6 +270,7 @@ pub fn register_trackers(world: &mut World) { world.register_tracker::<Gravity>(); world.register_tracker::<Loadout>(); world.register_tracker::<CharacterState>(); + world.register_tracker::<SpeechBubble>(); } /// Deleted entities grouped by region diff --git a/server/src/sys/speech_bubble.rs b/server/src/sys/speech_bubble.rs new file mode 100644 index 0000000000..0af465498f --- /dev/null +++ b/server/src/sys/speech_bubble.rs @@ -0,0 +1,30 @@ +use super::SysTimer; +use common::{comp::SpeechBubble, state::Time}; +use specs::{Entities, Join, Read, System, Write, WriteStorage}; + +/// This system removes timed-out speech bubbles +pub struct Sys; +impl<'a> System<'a> for Sys { + type SystemData = ( + Entities<'a>, + Read<'a, Time>, + WriteStorage<'a, SpeechBubble>, + Write<'a, SysTimer<Self>>, + ); + + fn run(&mut self, (entities, time, mut speech_bubbles, mut timer): Self::SystemData) { + timer.start(); + + let expired_ents: Vec<_> = (&entities, &mut speech_bubbles) + .join() + .filter(|(_, speech_bubble)| speech_bubble.timeout.map_or(true, |t| t.0 < time.0)) + .map(|(ent, _)| ent) + .collect(); + for ent in expired_ents { + println!("Remoaving bobble"); + speech_bubbles.remove(ent); + } + + timer.end(); + } +} diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index f4743ec34f..f6bd1afdf3 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -566,6 +566,7 @@ impl Hud { let stats = ecs.read_storage::<comp::Stats>(); let energy = ecs.read_storage::<comp::Energy>(); let hp_floater_lists = ecs.read_storage::<vcomp::HpFloaterList>(); + let speech_bubbles = ecs.read_storage::<comp::SpeechBubble>(); let interpolated = ecs.read_storage::<vcomp::Interpolated>(); let players = ecs.read_storage::<comp::Player>(); let scales = ecs.read_storage::<comp::Scale>(); @@ -890,7 +891,7 @@ impl Hud { let mut sct_bg_walker = self.ids.sct_bgs.walk(); // Render overhead name tags and health bars - for (pos, name, stats, energy, height_offset, hpfl) in ( + for (pos, name, stats, energy, height_offset, hpfl, bubble) in ( &entities, &pos, interpolated.maybe(), @@ -900,11 +901,12 @@ impl Hud { scales.maybe(), &bodies, &hp_floater_lists, + speech_bubbles.maybe(), ) .join() - .filter(|(entity, _, _, stats, _, _, _, _, _)| *entity != me && !stats.is_dead) + .filter(|(entity, _, _, stats, _, _, _, _, _, _)| *entity != me && !stats.is_dead) // Don't show outside a certain range - .filter(|(_, pos, _, _, _, _, _, _, hpfl)| { + .filter(|(_, pos, _, _, _, _, _, _, hpfl, _)| { pos.0.distance_squared(player_pos) < (if hpfl .time_since_last_dmg_by_me @@ -916,7 +918,7 @@ impl Hud { }) .powi(2) }) - .map(|(_, pos, interpolated, stats, energy, player, scale, body, hpfl)| { + .map(|(_, pos, interpolated, stats, energy, player, scale, body, hpfl, bubble)| { // TODO: This is temporary // If the player used the default character name display their name instead let name = if stats.name == "Character Name" { @@ -932,6 +934,7 @@ impl Hud { // TODO: when body.height() is more accurate remove the 2.0 body.height() * 2.0 * scale.map_or(1.0, |s| s.0), hpfl, + bubble, ) }) { @@ -944,6 +947,7 @@ impl Hud { // Chat bubble, name, level, and hp bars overhead::Overhead::new( &name, + bubble, stats, energy, own_level, diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index 2f66aa5493..e46c07423e 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -1,6 +1,6 @@ use super::{img_ids::Imgs, HP_COLOR, LOW_HP_COLOR, MANA_COLOR}; use crate::ui::{fonts::ConrodVoxygenFonts, Ingameable}; -use common::comp::{Energy, Stats}; +use common::comp::{Energy, SpeechBubble, Stats}; use conrod_core::{ position::Align, widget::{self, Image, Rectangle, Text}, @@ -42,6 +42,7 @@ widget_ids! { #[derive(WidgetCommon)] pub struct Overhead<'a> { name: &'a str, + bubble: Option<&'a SpeechBubble>, stats: &'a Stats, energy: &'a Energy, own_level: u32, @@ -55,6 +56,7 @@ pub struct Overhead<'a> { impl<'a> Overhead<'a> { pub fn new( name: &'a str, + bubble: Option<&'a SpeechBubble>, stats: &'a Stats, energy: &'a Energy, own_level: u32, @@ -64,6 +66,7 @@ impl<'a> Overhead<'a> { ) -> Self { Self { name, + bubble, stats, energy, own_level, @@ -84,11 +87,12 @@ impl<'a> Ingameable for Overhead<'a> { // Number of conrod primitives contained in the overhead display. TODO maybe // this could be done automatically? // - 2 Text::new for name - // - 2 Text::new for speech bubble - // - 10 Image::new for speech bubble (9-slice + tail) // - 1 for level: either Text or Image // - 4 for HP + mana + fg + bg - 19 + // If there's a speech bubble + // - 1 Text::new for speech bubble + // - 10 Image::new for speech bubble (9-slice + tail) + 7 + if self.bubble.is_some() { 11 } else { 0 } } } @@ -126,80 +130,78 @@ impl<'a> Widget for Overhead<'a> { .x_y(0.0, MANA_BAR_Y + 50.0) .set(state.ids.name, ui); - // Speech bubble - Text::new("Hello") - .font_id(self.fonts.cyri.conrod_id) - .font_size(15) - .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) - .up_from(state.ids.name, 10.0) - .x_align_to(state.ids.name, Align::Middle) - .parent(id) - .set(state.ids.chat_bubble_text, ui); - Image::new(self.imgs.chat_bubble_top_left) - .w_h(10.0, 10.0) - .top_left_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_top_left, ui); - Image::new(self.imgs.chat_bubble_top) - .h(10.0) - .w_of(state.ids.chat_bubble_text) - .mid_top_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_top, ui); - Image::new(self.imgs.chat_bubble_top_right) - .w_h(10.0, 10.0) - .top_right_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_top_right, ui); - Image::new(self.imgs.chat_bubble_left) - .w(10.0) - .h_of(state.ids.chat_bubble_text) - .mid_left_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_left, ui); - Image::new(self.imgs.chat_bubble_mid) - .wh_of(state.ids.chat_bubble_text) - .top_left_of(state.ids.chat_bubble_text) - .parent(id) - .set(state.ids.chat_bubble_mid, ui); - Image::new(self.imgs.chat_bubble_right) - .w(10.0) - .h_of(state.ids.chat_bubble_text) - .mid_right_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_right, ui); - Image::new(self.imgs.chat_bubble_bottom_left) - .w_h(10.0, 10.0) - .bottom_left_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_bottom_left, ui); - Image::new(self.imgs.chat_bubble_bottom) - .h(10.0) - .w_of(state.ids.chat_bubble_text) - .mid_bottom_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_bottom, ui); - Image::new(self.imgs.chat_bubble_bottom_right) - .w_h(10.0, 10.0) - .bottom_right_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_bottom_right, ui); - Image::new(self.imgs.chat_bubble_tail) - .w_h(11.0, 16.0) - .mid_bottom_with_margin_on(state.ids.chat_bubble_text, -16.0) - .parent(id) - .set(state.ids.chat_bubble_tail, ui); - // Why is there a second text widget?: The first is to position the 9-slice - // around and the second is to display text. Changing .depth manually - // causes strange problems in unrelated parts of the ui (the debug - // overlay is offset by a npc's screen position) TODO - Text::new("Hello") - .font_id(self.fonts.cyri.conrod_id) - .font_size(15) - .top_left_of(state.ids.chat_bubble_text) - .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) - .parent(id) - .set(state.ids.chat_bubble_text2, ui); + if let Some(bubble) = self.bubble { + // Speech bubble + let mut text = Text::new(&bubble.message) + .font_id(self.fonts.cyri.conrod_id) + .font_size(18) + .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) + .up_from(state.ids.name, 10.0) + .x_align_to(state.ids.name, Align::Middle) + .parent(id); + if let Some(w) = text.get_w(ui) { + if w > 250.0 { + text = text.w(250.0); + } + } + Image::new(self.imgs.chat_bubble_top_left) + .w_h(10.0, 10.0) + .top_left_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_top_left, ui); + Image::new(self.imgs.chat_bubble_top) + .h(10.0) + .w_of(state.ids.chat_bubble_text) + .mid_top_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_top, ui); + Image::new(self.imgs.chat_bubble_top_right) + .w_h(10.0, 10.0) + .top_right_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_top_right, ui); + Image::new(self.imgs.chat_bubble_left) + .w(10.0) + .h_of(state.ids.chat_bubble_text) + .mid_left_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_left, ui); + Image::new(self.imgs.chat_bubble_mid) + .wh_of(state.ids.chat_bubble_text) + .top_left_of(state.ids.chat_bubble_text) + .parent(id) + .set(state.ids.chat_bubble_mid, ui); + Image::new(self.imgs.chat_bubble_right) + .w(10.0) + .h_of(state.ids.chat_bubble_text) + .mid_right_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_right, ui); + Image::new(self.imgs.chat_bubble_bottom_left) + .w_h(10.0, 10.0) + .bottom_left_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_bottom_left, ui); + Image::new(self.imgs.chat_bubble_bottom) + .h(10.0) + .w_of(state.ids.chat_bubble_text) + .mid_bottom_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_bottom, ui); + Image::new(self.imgs.chat_bubble_bottom_right) + .w_h(10.0, 10.0) + .bottom_right_with_margin_on(state.ids.chat_bubble_text, -10.0) + .parent(id) + .set(state.ids.chat_bubble_bottom_right, ui); + let tail = Image::new(self.imgs.chat_bubble_tail) + .w_h(11.0, 16.0) + .mid_bottom_with_margin_on(state.ids.chat_bubble_text, -16.0) + .parent(id); + // Move text to front (conrod depth is lowest first; not a z-index) + tail.set(state.ids.chat_bubble_tail, ui); + text.depth(tail.get_depth() - 1.0) + .set(state.ids.chat_bubble_text, ui); + } let hp_percentage = self.stats.health.current() as f64 / self.stats.health.maximum() as f64 * 100.0; From 3c07d02218a5055416f776ffcd90eb87b0ab4ae1 Mon Sep 17 00:00:00 2001 From: CapsizeGlimmer <> Date: Sun, 24 May 2020 21:29:47 -0400 Subject: [PATCH 3/7] Add a dark mode to speech bubbles; consistantly use 'speech bubble' instead of 'chat bubble' --- .../element/frames/bubble_dark/bottom.png | 3 + .../frames/bubble_dark/bottom_left.png | 3 + .../frames/bubble_dark/bottom_right.png | 3 + .../element/frames/bubble_dark/left.png | 3 + .../element/frames/bubble_dark/mid.png | 3 + .../element/frames/bubble_dark/right.png | 3 + .../element/frames/bubble_dark/tail.png | 3 + .../element/frames/bubble_dark/top.png | 3 + .../element/frames/bubble_dark/top_left.png | 3 + .../element/frames/bubble_dark/top_right.png | 3 + assets/voxygen/i18n/en.ron | 1 + common/src/comp/agent.rs | 2 +- voxygen/src/hud/img_ids.rs | 33 ++- voxygen/src/hud/mod.rs | 7 +- voxygen/src/hud/overhead.rs | 193 +++++++++++------- voxygen/src/hud/settings_window.rs | 47 ++++- voxygen/src/session.rs | 4 + voxygen/src/settings.rs | 2 + 18 files changed, 227 insertions(+), 92 deletions(-) create mode 100644 assets/voxygen/element/frames/bubble_dark/bottom.png create mode 100644 assets/voxygen/element/frames/bubble_dark/bottom_left.png create mode 100644 assets/voxygen/element/frames/bubble_dark/bottom_right.png create mode 100644 assets/voxygen/element/frames/bubble_dark/left.png create mode 100644 assets/voxygen/element/frames/bubble_dark/mid.png create mode 100644 assets/voxygen/element/frames/bubble_dark/right.png create mode 100644 assets/voxygen/element/frames/bubble_dark/tail.png create mode 100644 assets/voxygen/element/frames/bubble_dark/top.png create mode 100644 assets/voxygen/element/frames/bubble_dark/top_left.png create mode 100644 assets/voxygen/element/frames/bubble_dark/top_right.png diff --git a/assets/voxygen/element/frames/bubble_dark/bottom.png b/assets/voxygen/element/frames/bubble_dark/bottom.png new file mode 100644 index 0000000000..5f2253a85f --- /dev/null +++ b/assets/voxygen/element/frames/bubble_dark/bottom.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98a71bfeb1258b3cfba3528ce236fbd1f6cb6a41327ba176b43b02476606c7fc +size 140 diff --git a/assets/voxygen/element/frames/bubble_dark/bottom_left.png b/assets/voxygen/element/frames/bubble_dark/bottom_left.png new file mode 100644 index 0000000000..e95dc19f4e --- /dev/null +++ b/assets/voxygen/element/frames/bubble_dark/bottom_left.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:887ef372eddeff35cbd6e2d77e97b9220a51b9dd5814f1b9f780c4e64e5ea3b8 +size 193 diff --git a/assets/voxygen/element/frames/bubble_dark/bottom_right.png b/assets/voxygen/element/frames/bubble_dark/bottom_right.png new file mode 100644 index 0000000000..033fc658e3 --- /dev/null +++ b/assets/voxygen/element/frames/bubble_dark/bottom_right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81b50d4071f07b24d0b82c37d1bb53e1fbf839feda632bc3419edfcbdf2337d9 +size 208 diff --git a/assets/voxygen/element/frames/bubble_dark/left.png b/assets/voxygen/element/frames/bubble_dark/left.png new file mode 100644 index 0000000000..62033229c0 --- /dev/null +++ b/assets/voxygen/element/frames/bubble_dark/left.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2632608c1365465c4e6dcb19963acdeec6890658b45aae5031d085b067b8627f +size 132 diff --git a/assets/voxygen/element/frames/bubble_dark/mid.png b/assets/voxygen/element/frames/bubble_dark/mid.png new file mode 100644 index 0000000000..6ca7fbf351 --- /dev/null +++ b/assets/voxygen/element/frames/bubble_dark/mid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c21f661d44fcdff48c6579e69ae978654ae964d476ab24253c5dc9ab6f7cfed +size 109 diff --git a/assets/voxygen/element/frames/bubble_dark/right.png b/assets/voxygen/element/frames/bubble_dark/right.png new file mode 100644 index 0000000000..d92b45226d --- /dev/null +++ b/assets/voxygen/element/frames/bubble_dark/right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39b8d3074ef4f73027492dc8a8dd04722f0e5bd9543e470c440695153b85d8f3 +size 132 diff --git a/assets/voxygen/element/frames/bubble_dark/tail.png b/assets/voxygen/element/frames/bubble_dark/tail.png new file mode 100644 index 0000000000..4fa55e9887 --- /dev/null +++ b/assets/voxygen/element/frames/bubble_dark/tail.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96a54addc80798ca47b2b652e60928bfcb0c039486b3b9adf29c4538f8888172 +size 256 diff --git a/assets/voxygen/element/frames/bubble_dark/top.png b/assets/voxygen/element/frames/bubble_dark/top.png new file mode 100644 index 0000000000..f6a2dda333 --- /dev/null +++ b/assets/voxygen/element/frames/bubble_dark/top.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5da78e4c5e5b8556bb738d4a80debf64755a8e4f7c3d4790007b32e84bb285a +size 141 diff --git a/assets/voxygen/element/frames/bubble_dark/top_left.png b/assets/voxygen/element/frames/bubble_dark/top_left.png new file mode 100644 index 0000000000..0f139f145d --- /dev/null +++ b/assets/voxygen/element/frames/bubble_dark/top_left.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:061afcd80c0d69bacdce77c322d92b575d281b951ef7051f24a150b9c049d0cf +size 171 diff --git a/assets/voxygen/element/frames/bubble_dark/top_right.png b/assets/voxygen/element/frames/bubble_dark/top_right.png new file mode 100644 index 0000000000..4fde80e6b4 --- /dev/null +++ b/assets/voxygen/element/frames/bubble_dark/top_right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1a57638ec421508453151c1549a7c7cf197f9d7db503ec6c2f2a60c4b2275f3 +size 210 diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index c2ce3c3618..7dcd1b08b1 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -238,6 +238,7 @@ Enjoy your stay in the World of Veloren."#, "hud.settings.cumulated_damage": "Cumulated Damage", "hud.settings.incoming_damage": "Incoming Damage", "hud.settings.cumulated_incoming_damage": "Cumulated Incoming Damage", + "hud.settings.speech_bubble_dark_mode": "Speech Bubble Dark Mode", "hud.settings.energybar_numbers": "Energybar Numbers", "hud.settings.values": "Values", "hud.settings.percentages": "Percentages", diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index a98c38e2f8..90be10bb0b 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -86,7 +86,7 @@ impl Default for Activity { fn default() -> Self { Activity::Idle(Vec2::zero()) } } -/// Default duration in seconds of chat bubbles +/// Default duration in seconds of speech bubbles pub const SPEECH_BUBBLE_DURATION: f64 = 5.0; /// Adds a speech bubble to the entity diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index bec188275e..9417722fe4 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -272,17 +272,28 @@ image_ids! { progress_frame: "voxygen.element.frames.progress_bar", progress: "voxygen.element.misc_bg.progress", - // Chat bubbles - chat_bubble_top_left: "voxygen.element.frames.bubble.top_left", - chat_bubble_top: "voxygen.element.frames.bubble.top", - chat_bubble_top_right: "voxygen.element.frames.bubble.top_right", - chat_bubble_left: "voxygen.element.frames.bubble.left", - chat_bubble_mid: "voxygen.element.frames.bubble.mid", - chat_bubble_right: "voxygen.element.frames.bubble.right", - chat_bubble_bottom_left: "voxygen.element.frames.bubble.bottom_left", - chat_bubble_bottom: "voxygen.element.frames.bubble.bottom", - chat_bubble_bottom_right: "voxygen.element.frames.bubble.bottom_right", - chat_bubble_tail: "voxygen.element.frames.bubble.tail", + // Speech bubbles + speech_bubble_top_left: "voxygen.element.frames.bubble.top_left", + speech_bubble_top: "voxygen.element.frames.bubble.top", + speech_bubble_top_right: "voxygen.element.frames.bubble.top_right", + speech_bubble_left: "voxygen.element.frames.bubble.left", + speech_bubble_mid: "voxygen.element.frames.bubble.mid", + speech_bubble_right: "voxygen.element.frames.bubble.right", + speech_bubble_bottom_left: "voxygen.element.frames.bubble.bottom_left", + speech_bubble_bottom: "voxygen.element.frames.bubble.bottom", + speech_bubble_bottom_right: "voxygen.element.frames.bubble.bottom_right", + speech_bubble_tail: "voxygen.element.frames.bubble.tail", + + dark_bubble_top_left: "voxygen.element.frames.bubble_dark.top_left", + dark_bubble_top: "voxygen.element.frames.bubble_dark.top", + dark_bubble_top_right: "voxygen.element.frames.bubble_dark.top_right", + dark_bubble_left: "voxygen.element.frames.bubble_dark.left", + dark_bubble_mid: "voxygen.element.frames.bubble_dark.mid", + dark_bubble_right: "voxygen.element.frames.bubble_dark.right", + dark_bubble_bottom_left: "voxygen.element.frames.bubble_dark.bottom_left", + dark_bubble_bottom: "voxygen.element.frames.bubble_dark.bottom", + dark_bubble_bottom_right: "voxygen.element.frames.bubble_dark.bottom_right", + dark_bubble_tail: "voxygen.element.frames.bubble_dark.tail", <BlankGraphic> nothing: (), diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index f6bd1afdf3..eb2690aad6 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -235,6 +235,7 @@ pub enum Event { Sct(bool), SctPlayerBatch(bool), SctDamageBatch(bool), + SpeechBubbleDarkMode(bool), ToggleDebug(bool), UiScale(ScaleChange), CharacterSelection, @@ -944,13 +945,14 @@ impl Hud { ); let ingame_pos = pos + Vec3::unit_z() * height_offset; - // Chat bubble, name, level, and hp bars + // Speech bubble, name, level, and hp bars overhead::Overhead::new( &name, bubble, stats, energy, own_level, + &global_state.settings.gameplay, self.pulse, &self.imgs, &self.fonts, @@ -1626,6 +1628,9 @@ impl Hud { .set(self.ids.settings_window, ui_widgets) { match event { + settings_window::Event::SpeechBubbleDarkMode(sbdm) => { + events.push(Event::SpeechBubbleDarkMode(sbdm)); + }, settings_window::Event::Sct(sct) => { events.push(Event::Sct(sct)); }, diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index e46c07423e..9466fcde50 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -1,5 +1,8 @@ use super::{img_ids::Imgs, HP_COLOR, LOW_HP_COLOR, MANA_COLOR}; -use crate::ui::{fonts::ConrodVoxygenFonts, Ingameable}; +use crate::{ + settings::GameplaySettings, + ui::{fonts::ConrodVoxygenFonts, Ingameable}, +}; use common::comp::{Energy, SpeechBubble, Stats}; use conrod_core::{ position::Align, @@ -9,19 +12,19 @@ use conrod_core::{ widget_ids! { struct Ids { - // Chat bubble - chat_bubble_text, - chat_bubble_text2, - chat_bubble_top_left, - chat_bubble_top, - chat_bubble_top_right, - chat_bubble_left, - chat_bubble_mid, - chat_bubble_right, - chat_bubble_bottom_left, - chat_bubble_bottom, - chat_bubble_bottom_right, - chat_bubble_tail, + // Speech bubble + speech_bubble_text, + speech_bubble_text2, + speech_bubble_top_left, + speech_bubble_top, + speech_bubble_top_right, + speech_bubble_left, + speech_bubble_mid, + speech_bubble_right, + speech_bubble_bottom_left, + speech_bubble_bottom, + speech_bubble_bottom_right, + speech_bubble_tail, // Name name_bg, @@ -46,6 +49,7 @@ pub struct Overhead<'a> { stats: &'a Stats, energy: &'a Energy, own_level: u32, + settings: &'a GameplaySettings, pulse: f32, imgs: &'a Imgs, fonts: &'a ConrodVoxygenFonts, @@ -60,6 +64,7 @@ impl<'a> Overhead<'a> { stats: &'a Stats, energy: &'a Energy, own_level: u32, + settings: &'a GameplaySettings, pulse: f32, imgs: &'a Imgs, fonts: &'a ConrodVoxygenFonts, @@ -70,6 +75,7 @@ impl<'a> Overhead<'a> { stats, energy, own_level, + settings, pulse, imgs, fonts, @@ -130,77 +136,122 @@ impl<'a> Widget for Overhead<'a> { .x_y(0.0, MANA_BAR_Y + 50.0) .set(state.ids.name, ui); + // Speech bubble if let Some(bubble) = self.bubble { - // Speech bubble + let dark_mode = self.settings.speech_bubble_dark_mode; let mut text = Text::new(&bubble.message) .font_id(self.fonts.cyri.conrod_id) .font_size(18) - .color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .up_from(state.ids.name, 10.0) .x_align_to(state.ids.name, Align::Middle) .parent(id); + text = if dark_mode { + text.color(Color::Rgba(1.0, 1.0, 1.0, 1.0)) + } else { + text.color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) + }; if let Some(w) = text.get_w(ui) { if w > 250.0 { text = text.w(250.0); } } - Image::new(self.imgs.chat_bubble_top_left) - .w_h(10.0, 10.0) - .top_left_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_top_left, ui); - Image::new(self.imgs.chat_bubble_top) - .h(10.0) - .w_of(state.ids.chat_bubble_text) - .mid_top_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_top, ui); - Image::new(self.imgs.chat_bubble_top_right) - .w_h(10.0, 10.0) - .top_right_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_top_right, ui); - Image::new(self.imgs.chat_bubble_left) - .w(10.0) - .h_of(state.ids.chat_bubble_text) - .mid_left_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_left, ui); - Image::new(self.imgs.chat_bubble_mid) - .wh_of(state.ids.chat_bubble_text) - .top_left_of(state.ids.chat_bubble_text) - .parent(id) - .set(state.ids.chat_bubble_mid, ui); - Image::new(self.imgs.chat_bubble_right) - .w(10.0) - .h_of(state.ids.chat_bubble_text) - .mid_right_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_right, ui); - Image::new(self.imgs.chat_bubble_bottom_left) - .w_h(10.0, 10.0) - .bottom_left_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_bottom_left, ui); - Image::new(self.imgs.chat_bubble_bottom) - .h(10.0) - .w_of(state.ids.chat_bubble_text) - .mid_bottom_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_bottom, ui); - Image::new(self.imgs.chat_bubble_bottom_right) - .w_h(10.0, 10.0) - .bottom_right_with_margin_on(state.ids.chat_bubble_text, -10.0) - .parent(id) - .set(state.ids.chat_bubble_bottom_right, ui); - let tail = Image::new(self.imgs.chat_bubble_tail) - .w_h(11.0, 16.0) - .mid_bottom_with_margin_on(state.ids.chat_bubble_text, -16.0) - .parent(id); + Image::new(if dark_mode { + self.imgs.dark_bubble_top_left + } else { + self.imgs.speech_bubble_top_left + }) + .w_h(10.0, 10.0) + .top_left_with_margin_on(state.ids.speech_bubble_text, -10.0) + .parent(id) + .set(state.ids.speech_bubble_top_left, ui); + Image::new(if dark_mode { + self.imgs.dark_bubble_top + } else { + self.imgs.speech_bubble_top + }) + .h(10.0) + .w_of(state.ids.speech_bubble_text) + .mid_top_with_margin_on(state.ids.speech_bubble_text, -10.0) + .parent(id) + .set(state.ids.speech_bubble_top, ui); + Image::new(if dark_mode { + self.imgs.dark_bubble_top_right + } else { + self.imgs.speech_bubble_top_right + }) + .w_h(10.0, 10.0) + .top_right_with_margin_on(state.ids.speech_bubble_text, -10.0) + .parent(id) + .set(state.ids.speech_bubble_top_right, ui); + Image::new(if dark_mode { + self.imgs.dark_bubble_left + } else { + self.imgs.speech_bubble_left + }) + .w(10.0) + .h_of(state.ids.speech_bubble_text) + .mid_left_with_margin_on(state.ids.speech_bubble_text, -10.0) + .parent(id) + .set(state.ids.speech_bubble_left, ui); + Image::new(if dark_mode { + self.imgs.dark_bubble_mid + } else { + self.imgs.speech_bubble_mid + }) + .wh_of(state.ids.speech_bubble_text) + .top_left_of(state.ids.speech_bubble_text) + .parent(id) + .set(state.ids.speech_bubble_mid, ui); + Image::new(if dark_mode { + self.imgs.dark_bubble_right + } else { + self.imgs.speech_bubble_right + }) + .w(10.0) + .h_of(state.ids.speech_bubble_text) + .mid_right_with_margin_on(state.ids.speech_bubble_text, -10.0) + .parent(id) + .set(state.ids.speech_bubble_right, ui); + Image::new(if dark_mode { + self.imgs.dark_bubble_bottom_left + } else { + self.imgs.speech_bubble_bottom_left + }) + .w_h(10.0, 10.0) + .bottom_left_with_margin_on(state.ids.speech_bubble_text, -10.0) + .parent(id) + .set(state.ids.speech_bubble_bottom_left, ui); + Image::new(if dark_mode { + self.imgs.dark_bubble_bottom + } else { + self.imgs.speech_bubble_bottom + }) + .h(10.0) + .w_of(state.ids.speech_bubble_text) + .mid_bottom_with_margin_on(state.ids.speech_bubble_text, -10.0) + .parent(id) + .set(state.ids.speech_bubble_bottom, ui); + Image::new(if dark_mode { + self.imgs.dark_bubble_bottom_right + } else { + self.imgs.speech_bubble_bottom_right + }) + .w_h(10.0, 10.0) + .bottom_right_with_margin_on(state.ids.speech_bubble_text, -10.0) + .parent(id) + .set(state.ids.speech_bubble_bottom_right, ui); + let tail = Image::new(if dark_mode { + self.imgs.dark_bubble_tail + } else { + self.imgs.speech_bubble_tail + }) + .w_h(11.0, 16.0) + .mid_bottom_with_margin_on(state.ids.speech_bubble_text, -16.0) + .parent(id); // Move text to front (conrod depth is lowest first; not a z-index) - tail.set(state.ids.chat_bubble_tail, ui); + tail.set(state.ids.speech_bubble_tail, ui); text.depth(tail.get_depth() - 1.0) - .set(state.ids.chat_bubble_text, ui); + .set(state.ids.speech_bubble_text, ui); } let hp_percentage = diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 4279d7b964..c4f5643878 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -153,6 +153,8 @@ widget_ids! { sct_num_dur_text, sct_num_dur_slider, sct_num_dur_value, + speech_bubble_dark_mode_text, + speech_bubble_dark_mode_button, free_look_behavior_text, free_look_behavior_list } @@ -235,6 +237,7 @@ pub enum Event { Sct(bool), SctPlayerBatch(bool), SctDamageBatch(bool), + SpeechBubbleDarkMode(bool), ChangeLanguage(LanguageMetadata), ChangeBinding(GameInput), ChangeFreeLookBehavior(PressBehavior), @@ -943,17 +946,45 @@ impl<'a> Widget for SettingsWindow<'a> { .set(state.ids.sct_batch_inc_text, ui); } + // Speech bubble dark mode + let speech_bubble_dark_mode = ToggleButton::new( + self.global_state.settings.gameplay.speech_bubble_dark_mode, + self.imgs.checkbox, + self.imgs.checkbox_checked, + ) + .down_from( + if self.global_state.settings.gameplay.sct { + state.ids.sct_batch_inc_radio + } else { + state.ids.sct_show_radio + }, + 20.0, + ) + .x(0.0) + .w_h(18.0, 18.0) + .hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo) + .press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked) + .set(state.ids.speech_bubble_dark_mode_button, ui); + if self.global_state.settings.gameplay.speech_bubble_dark_mode + != speech_bubble_dark_mode + { + events.push(Event::SpeechBubbleDarkMode(speech_bubble_dark_mode)); + } + Text::new( + &self + .localized_strings + .get("hud.settings.speech_bubble_dark_mode"), + ) + .right_from(state.ids.speech_bubble_dark_mode_button, 10.0) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.speech_bubble_dark_mode_text, ui); + // Energybars Numbers // Hotbar text Text::new(&self.localized_strings.get("hud.settings.energybar_numbers")) - .down_from( - if self.global_state.settings.gameplay.sct { - state.ids.sct_batch_inc_radio - } else { - state.ids.sct_show_radio - }, - 20.0, - ) + .down_from(state.ids.speech_bubble_dark_mode_button, 20.0) .font_size(self.fonts.cyri.scale(18)) .font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR) diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index f8ac11ef23..e785de0d9c 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -594,6 +594,10 @@ impl PlayState for SessionState { global_state.settings.gameplay.sct_damage_batch = sct_damage_batch; global_state.settings.save_to_file_warn(); }, + HudEvent::SpeechBubbleDarkMode(sbdm) => { + global_state.settings.gameplay.speech_bubble_dark_mode = sbdm; + global_state.settings.save_to_file_warn(); + }, HudEvent::ToggleDebug(toggle_debug) => { global_state.settings.gameplay.toggle_debug = toggle_debug; global_state.settings.save_to_file_warn(); diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 8b76af4a4a..5ae6d87294 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -455,6 +455,7 @@ pub struct GameplaySettings { pub sct: bool, pub sct_player_batch: bool, pub sct_damage_batch: bool, + pub speech_bubble_dark_mode: bool, pub mouse_y_inversion: bool, pub smooth_pan_enable: bool, pub crosshair_transp: f32, @@ -480,6 +481,7 @@ impl Default for GameplaySettings { sct: true, sct_player_batch: true, sct_damage_batch: false, + speech_bubble_dark_mode: false, crosshair_transp: 0.6, chat_transp: 0.4, crosshair_type: CrosshairType::Round, From 3cea76b82f331e3ed5b28951756ffaa1a38a2705 Mon Sep 17 00:00:00 2001 From: CapsizeGlimmer <> Date: Mon, 25 May 2020 20:11:22 -0400 Subject: [PATCH 4/7] NPCs now call for help when you hit them. Redraw speech bubble dark mode. --- .../element/frames/bubble_dark/bottom.png | 4 +- .../frames/bubble_dark/bottom_left.png | 4 +- .../frames/bubble_dark/bottom_right.png | 4 +- .../element/frames/bubble_dark/left.png | 4 +- .../element/frames/bubble_dark/mid.png | 2 +- .../element/frames/bubble_dark/right.png | 4 +- .../element/frames/bubble_dark/tail.png | 4 +- .../element/frames/bubble_dark/top.png | 4 +- .../element/frames/bubble_dark/top_left.png | 4 +- .../element/frames/bubble_dark/top_right.png | 4 +- assets/voxygen/i18n/de_DE.ron | 5 +- assets/voxygen/i18n/en.ron | 44 ++++++++++++- assets/voxygen/i18n/fr_FR.ron | 5 +- assets/voxygen/i18n/it_IT.ron | 5 +- assets/voxygen/i18n/pt_PT.ron | 3 + assets/voxygen/i18n/ru_RU.ron | 3 + assets/voxygen/i18n/tr_TR.ron | 3 + common/src/comp/agent.rs | 37 ++++++++++- common/src/sys/agent.rs | 8 ++- server/src/sys/message.rs | 16 ++--- server/src/sys/speech_bubble.rs | 1 - voxygen/src/hud/mod.rs | 1 + voxygen/src/hud/overhead.rs | 10 ++- voxygen/src/i18n.rs | 64 +++++++++++++++---- 24 files changed, 191 insertions(+), 52 deletions(-) diff --git a/assets/voxygen/element/frames/bubble_dark/bottom.png b/assets/voxygen/element/frames/bubble_dark/bottom.png index 5f2253a85f..47fc0ae80b 100644 --- a/assets/voxygen/element/frames/bubble_dark/bottom.png +++ b/assets/voxygen/element/frames/bubble_dark/bottom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98a71bfeb1258b3cfba3528ce236fbd1f6cb6a41327ba176b43b02476606c7fc -size 140 +oid sha256:0649ffdee5c242f0e268de834dab7bacfcd6758cbb40fa30af6ef088b7bb1b88 +size 129 diff --git a/assets/voxygen/element/frames/bubble_dark/bottom_left.png b/assets/voxygen/element/frames/bubble_dark/bottom_left.png index e95dc19f4e..80e6afd9c3 100644 --- a/assets/voxygen/element/frames/bubble_dark/bottom_left.png +++ b/assets/voxygen/element/frames/bubble_dark/bottom_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:887ef372eddeff35cbd6e2d77e97b9220a51b9dd5814f1b9f780c4e64e5ea3b8 -size 193 +oid sha256:cbde3c25035fbb219ba954c748fb5ca3f5d57f9d0632ebb4a288238146e95a62 +size 180 diff --git a/assets/voxygen/element/frames/bubble_dark/bottom_right.png b/assets/voxygen/element/frames/bubble_dark/bottom_right.png index 033fc658e3..888a0514ef 100644 --- a/assets/voxygen/element/frames/bubble_dark/bottom_right.png +++ b/assets/voxygen/element/frames/bubble_dark/bottom_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81b50d4071f07b24d0b82c37d1bb53e1fbf839feda632bc3419edfcbdf2337d9 -size 208 +oid sha256:aad2eb4c05eb6215c5641478c1ad8f7f5819e524c7fa5f610b1bc2568ebc978a +size 185 diff --git a/assets/voxygen/element/frames/bubble_dark/left.png b/assets/voxygen/element/frames/bubble_dark/left.png index 62033229c0..2a726a2cf3 100644 --- a/assets/voxygen/element/frames/bubble_dark/left.png +++ b/assets/voxygen/element/frames/bubble_dark/left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2632608c1365465c4e6dcb19963acdeec6890658b45aae5031d085b067b8627f -size 132 +oid sha256:048b2251d7f2970b8085c8792239f71888aa5344231620af1871ae3cad6fbb1e +size 126 diff --git a/assets/voxygen/element/frames/bubble_dark/mid.png b/assets/voxygen/element/frames/bubble_dark/mid.png index 6ca7fbf351..e5cf820008 100644 --- a/assets/voxygen/element/frames/bubble_dark/mid.png +++ b/assets/voxygen/element/frames/bubble_dark/mid.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c21f661d44fcdff48c6579e69ae978654ae964d476ab24253c5dc9ab6f7cfed +oid sha256:4a7de055972cc48b200a54c8fc86c1e9470c74b20ef64fd0b4a5bf658601a823 size 109 diff --git a/assets/voxygen/element/frames/bubble_dark/right.png b/assets/voxygen/element/frames/bubble_dark/right.png index d92b45226d..f5682b6f24 100644 --- a/assets/voxygen/element/frames/bubble_dark/right.png +++ b/assets/voxygen/element/frames/bubble_dark/right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39b8d3074ef4f73027492dc8a8dd04722f0e5bd9543e470c440695153b85d8f3 -size 132 +oid sha256:35af73b41aa32b94276f143e3156bdca0151590162a610f7700f898011bdddfa +size 131 diff --git a/assets/voxygen/element/frames/bubble_dark/tail.png b/assets/voxygen/element/frames/bubble_dark/tail.png index 4fa55e9887..3cc41634fa 100644 --- a/assets/voxygen/element/frames/bubble_dark/tail.png +++ b/assets/voxygen/element/frames/bubble_dark/tail.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96a54addc80798ca47b2b652e60928bfcb0c039486b3b9adf29c4538f8888172 -size 256 +oid sha256:0b21fefeab508b57af56eaa72153cd584fa8f6db41f639778368b46d2a0f2f38 +size 221 diff --git a/assets/voxygen/element/frames/bubble_dark/top.png b/assets/voxygen/element/frames/bubble_dark/top.png index f6a2dda333..01d893ab00 100644 --- a/assets/voxygen/element/frames/bubble_dark/top.png +++ b/assets/voxygen/element/frames/bubble_dark/top.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a5da78e4c5e5b8556bb738d4a80debf64755a8e4f7c3d4790007b32e84bb285a -size 141 +oid sha256:59fba25aad27f065ee5e2446400a1d9ce1a98b59b100ab9328880d1c25f9031a +size 129 diff --git a/assets/voxygen/element/frames/bubble_dark/top_left.png b/assets/voxygen/element/frames/bubble_dark/top_left.png index 0f139f145d..c89832ec6f 100644 --- a/assets/voxygen/element/frames/bubble_dark/top_left.png +++ b/assets/voxygen/element/frames/bubble_dark/top_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:061afcd80c0d69bacdce77c322d92b575d281b951ef7051f24a150b9c049d0cf -size 171 +oid sha256:33c47f93a1641466781dea3fb5115ccb689ed80a210875032d7cddce481fce47 +size 169 diff --git a/assets/voxygen/element/frames/bubble_dark/top_right.png b/assets/voxygen/element/frames/bubble_dark/top_right.png index 4fde80e6b4..14aeb65943 100644 --- a/assets/voxygen/element/frames/bubble_dark/top_right.png +++ b/assets/voxygen/element/frames/bubble_dark/top_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e1a57638ec421508453151c1549a7c7cf197f9d7db503ec6c2f2a60c4b2275f3 -size 210 +oid sha256:60c4e98e21e2e738552833a080e80ab02bc61a5bbec99101d7c9b75c39caeb7c +size 186 diff --git a/assets/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron index 7825275b69..630a118ef3 100644 --- a/assets/voxygen/i18n/de_DE.ron +++ b/assets/voxygen/i18n/de_DE.ron @@ -383,5 +383,8 @@ Willenskraft "esc_menu.logout": "Ausloggen", "esc_menu.quit_game": "Desktop", /// End Escape Menu Section + }, + + vector_map: { } -) \ No newline at end of file +) diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index 7dcd1b08b1..fef4e49ccb 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -376,14 +376,52 @@ Fitness Willpower "#, - - - /// Start character window section + /// End character window section /// Start Escape Menu Section "esc_menu.logout": "Logout", "esc_menu.quit_game": "Quit Game", /// End Escape Menu Section + }, + + vector_map: { + "npc.speech.villager_under_attack": [ + "Help, I'm under attack!", + "Help! I'm under attack!", + "Ouch! I'm under attack!", + "Ouch! I'm under attack! Help!", + "Help me! I'm under attack!", + "I'm under attack! Help!", + "I'm under attack! Help me!", + "Help!", + "Help! Help!", + "Help! Help! Help!", + "I'm under attack!", + "AAAHHH! I'm under attack!", + "AAAHHH! I'm under attack! Help!", + "Help! We're under attack!", + "Help! Murderer!", + "Help! There's a murder on the loose!", + "Help! They're trying to kill me!", + "Guards, I'm under attack!", + "Guards! I'm under attack!", + "I'm under attack! Guards!", + "Help! Guards! I'm under attack!", + "Guards! Come quick!", + "Guards! Guards!", + "Guards! There's a villain attacking me!", + "Guards, slay this foul villain!", + "Guards! There's a murderer!", + "Guards! Help me!", + "You won't get away with this! Guards!", + "You fiend!", + "Help me!", + "Help! Please!", + "Ouch! Guards! Help!", + "They're coming for me!", + "Help! Help! I'm being repressed", + "Ah, now we see the violence inherent in the system.", + ], } ) diff --git a/assets/voxygen/i18n/fr_FR.ron b/assets/voxygen/i18n/fr_FR.ron index 6d5c4d581c..89a2685dd4 100644 --- a/assets/voxygen/i18n/fr_FR.ron +++ b/assets/voxygen/i18n/fr_FR.ron @@ -323,5 +323,8 @@ Force Dexterité Intelligence"#, + }, + + vector_map: { } -) \ No newline at end of file +) diff --git a/assets/voxygen/i18n/it_IT.ron b/assets/voxygen/i18n/it_IT.ron index 5af28e1e58..5a14c15a9b 100644 --- a/assets/voxygen/i18n/it_IT.ron +++ b/assets/voxygen/i18n/it_IT.ron @@ -524,5 +524,8 @@ Volontà "esc_menu.logout": "Disconnettiti", "esc_menu.quit_game": "Esci dal Gioco", /// End Escape Menu Section - } + }, + + vector_map: { + } ) diff --git a/assets/voxygen/i18n/pt_PT.ron b/assets/voxygen/i18n/pt_PT.ron index a29d45f147..c4832499a9 100644 --- a/assets/voxygen/i18n/pt_PT.ron +++ b/assets/voxygen/i18n/pt_PT.ron @@ -368,5 +368,8 @@ Força de vontade "esc_menu.logout": "Desconectar", "esc_menu.quit_game": "Sair do jogo", /// End Escape Menu Section + }, + + vector_map: { } ) diff --git a/assets/voxygen/i18n/ru_RU.ron b/assets/voxygen/i18n/ru_RU.ron index 51ae278708..da55da407b 100644 --- a/assets/voxygen/i18n/ru_RU.ron +++ b/assets/voxygen/i18n/ru_RU.ron @@ -365,5 +365,8 @@ https://account.veloren.net."#, "esc_menu.logout": "Выйти в меню", "esc_menu.quit_game": "Выйти из игры", /// End Escape Menu Section + }, + + vector_map: { } ) diff --git a/assets/voxygen/i18n/tr_TR.ron b/assets/voxygen/i18n/tr_TR.ron index 17d62c5ce8..2f88227ad9 100644 --- a/assets/voxygen/i18n/tr_TR.ron +++ b/assets/voxygen/i18n/tr_TR.ron @@ -397,5 +397,8 @@ Hareket gücü "esc_menu.logout": "Çıkış yap", "esc_menu.quit_game": "Oyundan çık", /// End Escape Menu Section + }, + + vector_map: { } ) diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 90be10bb0b..b0e3ecac60 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -89,13 +89,46 @@ impl Default for Activity { /// Default duration in seconds of speech bubbles pub const SPEECH_BUBBLE_DURATION: f64 = 5.0; +/// The contents of a speech bubble +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum SpeechBubbleMessage { + /// This message was said by a player and needs no translation + Plain(String), + /// This message was said by an NPC. The fields are a i18n key and a random + /// u16 index + Localized(String, u16), +} + /// Adds a speech bubble to the entity -#[derive(Clone, Default, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct SpeechBubble { - pub message: String, + pub message: SpeechBubbleMessage, pub timeout: Option<Time>, // TODO add icon enum for player chat type / npc quest+trade } impl Component for SpeechBubble { type Storage = FlaggedStorage<Self, HashMapStorage<Self>>; } +impl SpeechBubble { + pub fn npc_new(i18n_key: String, now: Time) -> Self { + let message = SpeechBubbleMessage::Localized(i18n_key, rand::random()); + let timeout = Some(Time(now.0 + SPEECH_BUBBLE_DURATION)); + Self { message, timeout } + } + + pub fn player_new(message: String, now: Time) -> Self { + let message = SpeechBubbleMessage::Plain(message); + let timeout = Some(Time(now.0 + SPEECH_BUBBLE_DURATION)); + Self { message, timeout } + } + + pub fn message<F>(&self, i18n_variation: F) -> String + where + F: Fn(String, u16) -> String, + { + match &self.message { + SpeechBubbleMessage::Plain(m) => m.to_string(), + SpeechBubbleMessage::Localized(k, i) => i18n_variation(k.to_string(), *i).to_string(), + } + } +} diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 9f349ada3c..70015af7c8 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -4,7 +4,7 @@ use crate::{ agent::Activity, item::{tool::ToolKind, ItemKind}, Agent, Alignment, CharacterState, ControlAction, Controller, Loadout, MountState, Ori, Pos, - Scale, Stats, + Scale, SpeechBubble, Stats, }, path::Chaser, state::{DeltaTime, Time}, @@ -38,6 +38,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Alignment>, WriteStorage<'a, Agent>, WriteStorage<'a, Controller>, + WriteStorage<'a, SpeechBubble>, ReadStorage<'a, MountState>, ); @@ -58,6 +59,7 @@ impl<'a> System<'a> for Sys { alignments, mut agents, mut controllers, + mut speech_bubbles, mount_states, ): Self::SystemData, ) { @@ -386,6 +388,10 @@ impl<'a> System<'a> for Sys { if !agent.activity.is_attack() { if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id()) { + let message = "npc.speech.villager_under_attack".to_string(); + let bubble = SpeechBubble::npc_new(message, *time); + let _ = speech_bubbles.insert(entity, bubble); + agent.activity = Activity::Attack { target: attacker, chaser: Chaser::default(), diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 1579ccb477..3e18372abd 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -6,7 +6,7 @@ use crate::{ use common::{ comp::{ Admin, CanBuild, ControlEvent, Controller, ForceUpdate, Ori, Player, Pos, SpeechBubble, - Stats, Vel, SPEECH_BUBBLE_DURATION, + Stats, Vel, }, event::{EventBus, ServerEvent}, msg::{ @@ -76,8 +76,6 @@ impl<'a> System<'a> for Sys { ) { timer.start(); - let time = time.0; - let persistence_db_dir = &persistence_db_dir.0; let mut server_emitter = server_event_bus.emitter(); @@ -97,13 +95,13 @@ impl<'a> System<'a> for Sys { // Update client ping. if new_msgs.len() > 0 { - client.last_ping = time - } else if time - client.last_ping > CLIENT_TIMEOUT // Timeout + client.last_ping = time.0 + } else if time.0 - client.last_ping > CLIENT_TIMEOUT // Timeout || client.postbox.error().is_some() // Postbox error { server_emitter.emit(ServerEvent::ClientDisconnect(entity)); - } else if time - client.last_ping > CLIENT_TIMEOUT * 0.5 { + } else if time.0 - client.last_ping > CLIENT_TIMEOUT * 0.5 { // Try pinging the client if the timeout is nearing. client.postbox.send_message(ServerMsg::Ping); } @@ -406,11 +404,7 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::ChatCmd(entity, argv)); continue; } else { - let timeout = Some(Time(time + SPEECH_BUBBLE_DURATION)); - let bubble = SpeechBubble { - message: message.clone(), - timeout, - }; + let bubble = SpeechBubble::player_new(message.clone(), *time); let _ = speech_bubbles.insert(entity, bubble); match players.get(entity) { Some(player) => { diff --git a/server/src/sys/speech_bubble.rs b/server/src/sys/speech_bubble.rs index 0af465498f..2a3183fbaf 100644 --- a/server/src/sys/speech_bubble.rs +++ b/server/src/sys/speech_bubble.rs @@ -21,7 +21,6 @@ impl<'a> System<'a> for Sys { .map(|(ent, _)| ent) .collect(); for ent in expired_ents { - println!("Remoaving bobble"); speech_bubbles.remove(ent); } diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index eb2690aad6..8372f0a171 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -954,6 +954,7 @@ impl Hud { own_level, &global_state.settings.gameplay, self.pulse, + &self.voxygen_i18n, &self.imgs, &self.fonts, ) diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index 9466fcde50..bfe14afd76 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -1,5 +1,6 @@ use super::{img_ids::Imgs, HP_COLOR, LOW_HP_COLOR, MANA_COLOR}; use crate::{ + i18n::VoxygenLocalization, settings::GameplaySettings, ui::{fonts::ConrodVoxygenFonts, Ingameable}, }; @@ -51,6 +52,7 @@ pub struct Overhead<'a> { own_level: u32, settings: &'a GameplaySettings, pulse: f32, + voxygen_i18n: &'a std::sync::Arc<VoxygenLocalization>, imgs: &'a Imgs, fonts: &'a ConrodVoxygenFonts, #[conrod(common_builder)] @@ -66,6 +68,7 @@ impl<'a> Overhead<'a> { own_level: u32, settings: &'a GameplaySettings, pulse: f32, + voxygen_i18n: &'a std::sync::Arc<VoxygenLocalization>, imgs: &'a Imgs, fonts: &'a ConrodVoxygenFonts, ) -> Self { @@ -77,6 +80,7 @@ impl<'a> Overhead<'a> { own_level, settings, pulse, + voxygen_i18n, imgs, fonts, common: widget::CommonBuilder::default(), @@ -139,7 +143,11 @@ impl<'a> Widget for Overhead<'a> { // Speech bubble if let Some(bubble) = self.bubble { let dark_mode = self.settings.speech_bubble_dark_mode; - let mut text = Text::new(&bubble.message) + let localizer = + |s: String, i| -> String { self.voxygen_i18n.get_variation(&s, i).to_string() }; + let bubble_contents: String = bubble.message(localizer); + + let mut text = Text::new(&bubble_contents) .font_id(self.fonts.cyri.conrod_id) .font_size(18) .up_from(state.ids.name, 10.0) diff --git a/voxygen/src/i18n.rs b/voxygen/src/i18n.rs index 81b758a128..e38108bb3e 100644 --- a/voxygen/src/i18n.rs +++ b/voxygen/src/i18n.rs @@ -53,10 +53,15 @@ pub type VoxygenFonts = HashMap<String, Font>; pub struct VoxygenLocalization { /// A map storing the localized texts /// - /// Localized content can be access using a String key + /// Localized content can be accessed using a String key. pub string_map: HashMap<String, String>, - /// Either to convert the input text encoded in UTF-8 + /// A map for storing variations of localized texts, for example multiple + /// ways of saying "Help, I'm under attack". Used primarily for npc + /// dialogue. + pub vector_map: HashMap<String, Vec<String>>, + + /// Whether to convert the input text encoded in UTF-8 /// into a ASCII version by using the `deunicode` crate. pub convert_utf8_to_ascii: bool, @@ -78,23 +83,56 @@ impl VoxygenLocalization { } } - /// Return the missing keys compared to the reference language and return - /// them - pub fn list_missing_entries(&self) -> HashSet<String> { + /// Get a variation of localized text from the given key + /// + /// `index` should be a random number from `0` to `u16::max()` + /// + /// If the key is not present in the localization object + /// then the key is returned. + pub fn get_variation<'a>(&'a self, key: &'a str, index: u16) -> &str { + match self.vector_map.get(key) { + Some(v) if !v.is_empty() => &v[index as usize % v.len()], + _ => key, + } + } + + /// Return the missing keys compared to the reference language + pub fn list_missing_entries(&self) -> (HashSet<String>, HashSet<String>) { let reference_localization = load_expect::<VoxygenLocalization>(i18n_asset_key(REFERENCE_LANG).as_ref()); - let reference_keys: HashSet<_> = - reference_localization.string_map.keys().cloned().collect(); - let current_keys: HashSet<_> = self.string_map.keys().cloned().collect(); - reference_keys.difference(¤t_keys).cloned().collect() + let reference_string_keys: HashSet<_> = + reference_localization.string_map.keys().cloned().collect(); + let string_keys: HashSet<_> = self.string_map.keys().cloned().collect(); + let strings = reference_string_keys + .difference(&string_keys) + .cloned() + .collect(); + + let reference_vector_keys: HashSet<_> = + reference_localization.vector_map.keys().cloned().collect(); + let vector_keys: HashSet<_> = self.vector_map.keys().cloned().collect(); + let vectors = reference_vector_keys + .difference(&vector_keys) + .cloned() + .collect(); + + (strings, vectors) } /// Log missing entries (compared to the reference language) as warnings pub fn log_missing_entries(&self) { - for missing_key in self.list_missing_entries() { + let (missing_strings, missing_vectors) = self.list_missing_entries(); + for missing_key in missing_strings { log::warn!( - "[{:?}] Missing key {:?}", + "[{:?}] Missing string key {:?}", + self.metadata.language_identifier, + missing_key + ); + } + for missing_key in missing_vectors { + log::warn!( + "[{:?}] Missing vector key {:?}", self.metadata.language_identifier, missing_key ); @@ -116,6 +154,10 @@ impl Asset for VoxygenLocalization { for value in asked_localization.string_map.values_mut() { *value = deunicode(value); } + + for value in asked_localization.vector_map.values_mut() { + *value = value.into_iter().map(|s| deunicode(s)).collect(); + } } asked_localization.metadata.language_name = deunicode(&asked_localization.metadata.language_name); From 78a06550d0bf6ae591ba7096903acd243e9c3f22 Mon Sep 17 00:00:00 2001 From: CapsizeGlimmer <> Date: Mon, 25 May 2020 22:45:13 -0400 Subject: [PATCH 5/7] Only NPCs speak when hit. Farm animal alignment changed from NPC to Tame --- assets/voxygen/i18n/en.ron | 2 +- common/src/comp/agent.rs | 22 ++++++++++++++++++++++ common/src/sys/agent.rs | 31 ++++++++++++++++++------------- server/src/sys/terrain.rs | 4 +++- world/src/site/settlement/mod.rs | 18 +++++++++++++----- 5 files changed, 57 insertions(+), 20 deletions(-) diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index fef4e49ccb..0cea650ed6 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -402,7 +402,7 @@ Willpower "AAAHHH! I'm under attack! Help!", "Help! We're under attack!", "Help! Murderer!", - "Help! There's a murder on the loose!", + "Help! There's a murderer on the loose!", "Help! They're trying to kill me!", "Guards, I'm under attack!", "Guards! I'm under attack!", diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index b0e3ecac60..dffde7484e 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -5,9 +5,15 @@ use vek::*; #[derive(Copy, Clone, Debug, PartialEq)] pub enum Alignment { + /// Wild animals and gentle giants Wild, + /// Dungeon cultists and bandits Enemy, + /// Friendly folk in villages Npc, + /// Farm animals and pets of villagers + Tame, + /// Pets you've tamed with a collar Owned(EcsEntity), } @@ -27,6 +33,10 @@ impl Alignment { match (self, other) { (Alignment::Enemy, Alignment::Enemy) => true, (Alignment::Owned(a), Alignment::Owned(b)) if a == b => true, + (Alignment::Npc, Alignment::Npc) => true, + (Alignment::Npc, Alignment::Tame) => true, + (Alignment::Tame, Alignment::Npc) => true, + (Alignment::Tame, Alignment::Tame) => true, _ => false, } } @@ -40,6 +50,9 @@ impl Component for Alignment { pub struct Agent { pub patrol_origin: Option<Vec3<f32>>, pub activity: Activity, + /// Does the agent talk when e.g. hit by the player + // TODO move speech patterns into a Behavior component + pub can_speak: bool, } impl Agent { @@ -47,6 +60,15 @@ impl Agent { self.patrol_origin = Some(origin); self } + + pub fn new(origin: Vec3<f32>, can_speak: bool) -> Self { + let patrol_origin = Some(origin); + Agent { + patrol_origin, + can_speak, + ..Default::default() + } + } } impl Component for Agent { diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 70015af7c8..7dbff74b1c 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -378,27 +378,32 @@ impl<'a> System<'a> for Sys { // last!) --- // Attack a target that's attacking us - if let Some(stats) = stats.get(entity) { + if let Some(my_stats) = stats.get(entity) { // Only if the attack was recent - if stats.health.last_change.0 < 5.0 { + if my_stats.health.last_change.0 < 5.0 { if let comp::HealthSource::Attack { by } | comp::HealthSource::Projectile { owner: Some(by) } = - stats.health.last_change.1.cause + my_stats.health.last_change.1.cause { if !agent.activity.is_attack() { if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id()) { - let message = "npc.speech.villager_under_attack".to_string(); - let bubble = SpeechBubble::npc_new(message, *time); - let _ = speech_bubbles.insert(entity, bubble); + if stats.get(attacker).map_or(false, |a| !a.is_dead) { + if agent.can_speak { + let message = + "npc.speech.villager_under_attack".to_string(); + let bubble = SpeechBubble::npc_new(message, *time); + let _ = speech_bubbles.insert(entity, bubble); + } - agent.activity = Activity::Attack { - target: attacker, - chaser: Chaser::default(), - time: time.0, - been_close: false, - powerup: 0.0, - }; + agent.activity = Activity::Attack { + target: attacker, + chaser: Chaser::default(), + time: time.0, + been_close: false, + powerup: 0.0, + }; + } } } } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 25b838e61a..70268cf7dc 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -329,6 +329,8 @@ impl<'a> System<'a> for Sys { .health .set_to(stats.health.maximum(), comp::HealthSource::Revive); + let can_speak = alignment == comp::Alignment::Npc; + // TODO: This code sets an appropriate base_damage for the enemy. This doesn't // work because the damage is now saved in an ability /* @@ -344,7 +346,7 @@ impl<'a> System<'a> for Sys { loadout, body, alignment, - agent: comp::Agent::default().with_patrol_origin(entity.pos), + agent: comp::Agent::new(entity.pos, can_speak), scale: comp::Scale(scale), drop_item: entity.loot_drop, }) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index c274cff062..23c2793e67 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -784,8 +784,8 @@ impl Settlement { if matches!(sample.plot, Some(Plot::Town)) && RandomField::new(self.seed).chance(Vec3::from(wpos2d), 1.0 / (50.0 * 50.0)) { + let is_human: bool; let entity = EntityInfo::at(entity_wpos) - .with_alignment(comp::Alignment::Npc) .with_body(match rng.gen_range(0, 4) { 0 => { let species = match rng.gen_range(0, 3) { @@ -793,7 +793,7 @@ impl Settlement { 1 => quadruped_small::Species::Sheep, _ => quadruped_small::Species::Cat, }; - + is_human = false; comp::Body::QuadrupedSmall(quadruped_small::Body::random_with( rng, &species, )) @@ -805,14 +805,22 @@ impl Settlement { 2 => bird_medium::Species::Goose, _ => bird_medium::Species::Peacock, }; - + is_human = false; comp::Body::BirdMedium(bird_medium::Body::random_with( rng, &species, )) }, - _ => comp::Body::Humanoid(humanoid::Body::random()), + _ => { + is_human = true; + comp::Body::Humanoid(humanoid::Body::random()) + }, }) - .do_if(rng.gen(), |entity| { + .with_alignment(if is_human { + comp::Alignment::Npc + } else { + comp::Alignment::Tame + }) + .do_if(is_human && rng.gen(), |entity| { entity.with_main_tool(assets::load_expect_cloned( match rng.gen_range(0, 7) { 0 => "common.items.weapons.tool.broom", From d5216cc8f34a4428830afcd0b29ee30959eed73d Mon Sep 17 00:00:00 2001 From: CapsizeGlimmer <> Date: Tue, 26 May 2020 12:31:51 -0400 Subject: [PATCH 6/7] Redraw dark mode bubbles. Add padding to light mode bubbles. --- .../voxygen/element/frames/bubble/bottom.png | 2 +- .../element/frames/bubble/bottom_left.png | 4 +- .../element/frames/bubble/bottom_right.png | 4 +- assets/voxygen/element/frames/bubble/left.png | 2 +- .../voxygen/element/frames/bubble/right.png | 4 +- assets/voxygen/element/frames/bubble/tail.png | 2 +- assets/voxygen/element/frames/bubble/top.png | 2 +- .../element/frames/bubble/top_left.png | 4 +- .../element/frames/bubble/top_right.png | 4 +- .../element/frames/bubble_dark/bottom.png | 4 +- .../frames/bubble_dark/bottom_left.png | 4 +- .../frames/bubble_dark/bottom_right.png | 4 +- .../element/frames/bubble_dark/left.png | 4 +- .../element/frames/bubble_dark/right.png | 4 +- .../element/frames/bubble_dark/tail.png | 4 +- .../element/frames/bubble_dark/top.png | 4 +- .../element/frames/bubble_dark/top_left.png | 4 +- .../element/frames/bubble_dark/top_right.png | 4 +- voxygen/src/hud/overhead.rs | 50 +++++++++---------- 19 files changed, 57 insertions(+), 57 deletions(-) diff --git a/assets/voxygen/element/frames/bubble/bottom.png b/assets/voxygen/element/frames/bubble/bottom.png index a133651d2a..248c1269ad 100644 --- a/assets/voxygen/element/frames/bubble/bottom.png +++ b/assets/voxygen/element/frames/bubble/bottom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b2389dc62c0765c9ed56e53afab639c7fcb90656d83a051e8cf513cda926804 +oid sha256:251f361255513af3996b30c447f884801eeb6ede3539724b72865ba362570616 size 136 diff --git a/assets/voxygen/element/frames/bubble/bottom_left.png b/assets/voxygen/element/frames/bubble/bottom_left.png index 514e7f025d..ff26abaf6a 100644 --- a/assets/voxygen/element/frames/bubble/bottom_left.png +++ b/assets/voxygen/element/frames/bubble/bottom_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2983324f979fe0ee212e7b4527d4d2ae042ec7da418cee01cdd0637d9410b042 -size 3485 +oid sha256:8957c16fc6d1f1d2ea23a814d1ed4c066b90e8f7bc1c7a9ecd5464c56136d853 +size 199 diff --git a/assets/voxygen/element/frames/bubble/bottom_right.png b/assets/voxygen/element/frames/bubble/bottom_right.png index 418b586b89..7391d3b157 100644 --- a/assets/voxygen/element/frames/bubble/bottom_right.png +++ b/assets/voxygen/element/frames/bubble/bottom_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7681e4b43db2d68f8576b26e91c8395f292b9aab76b0fac27ef2f5239e438766 -size 3639 +oid sha256:35a2bef9dc6766ee2614bd7c2434192617b10a321b90a38c40d1dda97d25bbae +size 214 diff --git a/assets/voxygen/element/frames/bubble/left.png b/assets/voxygen/element/frames/bubble/left.png index 533725330d..b9f2a14d43 100644 --- a/assets/voxygen/element/frames/bubble/left.png +++ b/assets/voxygen/element/frames/bubble/left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6da4e97492532b7c8b83119bd419c5689c00149e217c26dcff6e9628e0eef6de +oid sha256:15b2b4d70d2d3a2eafb92c4b6aaa30bc2930e609ee2db3c035a02f4c3eaed096 size 124 diff --git a/assets/voxygen/element/frames/bubble/right.png b/assets/voxygen/element/frames/bubble/right.png index 210af16f17..c3115ed544 100644 --- a/assets/voxygen/element/frames/bubble/right.png +++ b/assets/voxygen/element/frames/bubble/right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b17fa4cacc5617de542e8ea054113dba1e8aa19272567f4a8cf026d1e849d0d0 -size 125 +oid sha256:3e137addc48edb5a02697fc71c395e9a9be87af8d88665730a61df172cbe8fdb +size 124 diff --git a/assets/voxygen/element/frames/bubble/tail.png b/assets/voxygen/element/frames/bubble/tail.png index 19889b332f..e07bce62fa 100644 --- a/assets/voxygen/element/frames/bubble/tail.png +++ b/assets/voxygen/element/frames/bubble/tail.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16f8d6165a5071be33830a33215bbcd5fc4a7783a932bff396df98167e5241bb +oid sha256:d5113b9561fa79dddbf9b0d98f692d8c7d6e980f3249df03ef9c0052891da11b size 227 diff --git a/assets/voxygen/element/frames/bubble/top.png b/assets/voxygen/element/frames/bubble/top.png index 0c88a60f54..5c94a8ec10 100644 --- a/assets/voxygen/element/frames/bubble/top.png +++ b/assets/voxygen/element/frames/bubble/top.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14518edba8bc71829422acd9c3e0c76dc9c78678ed1acc87e81257667eb46a79 +oid sha256:14041a782ce25fe04e2c419f52cbd0e07f0004897ae0f9e85cfd603379e24360 size 136 diff --git a/assets/voxygen/element/frames/bubble/top_left.png b/assets/voxygen/element/frames/bubble/top_left.png index e1b770cec8..676927924f 100644 --- a/assets/voxygen/element/frames/bubble/top_left.png +++ b/assets/voxygen/element/frames/bubble/top_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4fd9c69f48c863371cef8ed8ea332710b975583b6ba7f0fa91359042a471d32 -size 184 +oid sha256:59a63d20416f94eeb7cf6f3bfe6b9022710a0e2cfe1c669621fd6e375dd13aa5 +size 180 diff --git a/assets/voxygen/element/frames/bubble/top_right.png b/assets/voxygen/element/frames/bubble/top_right.png index d994a4198c..90d3538cd2 100644 --- a/assets/voxygen/element/frames/bubble/top_right.png +++ b/assets/voxygen/element/frames/bubble/top_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8127cb08af479b2e08f3a4decfd5640c97095d7333bf7cd40a4a3e61c8cf09ae -size 219 +oid sha256:a97673498e8f45266d0e4009ea6237a33471f421b211c8e50c0de69c360abc46 +size 217 diff --git a/assets/voxygen/element/frames/bubble_dark/bottom.png b/assets/voxygen/element/frames/bubble_dark/bottom.png index 47fc0ae80b..6c29aea33b 100644 --- a/assets/voxygen/element/frames/bubble_dark/bottom.png +++ b/assets/voxygen/element/frames/bubble_dark/bottom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0649ffdee5c242f0e268de834dab7bacfcd6758cbb40fa30af6ef088b7bb1b88 -size 129 +oid sha256:46628924ae6326359ed3fa0ad2f834cef06a2e387c0dee5a58b167435eab3a37 +size 131 diff --git a/assets/voxygen/element/frames/bubble_dark/bottom_left.png b/assets/voxygen/element/frames/bubble_dark/bottom_left.png index 80e6afd9c3..fcfb10d211 100644 --- a/assets/voxygen/element/frames/bubble_dark/bottom_left.png +++ b/assets/voxygen/element/frames/bubble_dark/bottom_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cbde3c25035fbb219ba954c748fb5ca3f5d57f9d0632ebb4a288238146e95a62 -size 180 +oid sha256:6ca13242a394ccf91cf751d114bb3304aa5ef6ffa28fece1c978769a1de1f2d4 +size 199 diff --git a/assets/voxygen/element/frames/bubble_dark/bottom_right.png b/assets/voxygen/element/frames/bubble_dark/bottom_right.png index 888a0514ef..6fc710cdd5 100644 --- a/assets/voxygen/element/frames/bubble_dark/bottom_right.png +++ b/assets/voxygen/element/frames/bubble_dark/bottom_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aad2eb4c05eb6215c5641478c1ad8f7f5819e524c7fa5f610b1bc2568ebc978a -size 185 +oid sha256:9fdf825f1f07a7485ffa7492081d5811196367b7a438d757c2401c736447d9d7 +size 220 diff --git a/assets/voxygen/element/frames/bubble_dark/left.png b/assets/voxygen/element/frames/bubble_dark/left.png index 2a726a2cf3..30c954abbc 100644 --- a/assets/voxygen/element/frames/bubble_dark/left.png +++ b/assets/voxygen/element/frames/bubble_dark/left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:048b2251d7f2970b8085c8792239f71888aa5344231620af1871ae3cad6fbb1e -size 126 +oid sha256:524afbf0961dcef3e0798d7e973ed51bb30816c37b1f3353163aff38bd473d23 +size 120 diff --git a/assets/voxygen/element/frames/bubble_dark/right.png b/assets/voxygen/element/frames/bubble_dark/right.png index f5682b6f24..de076ca355 100644 --- a/assets/voxygen/element/frames/bubble_dark/right.png +++ b/assets/voxygen/element/frames/bubble_dark/right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35af73b41aa32b94276f143e3156bdca0151590162a610f7700f898011bdddfa -size 131 +oid sha256:d1caf5ee920d29eb2f690bdcb0fad4cc2f029bbfce464af0fccf01540607d33c +size 120 diff --git a/assets/voxygen/element/frames/bubble_dark/tail.png b/assets/voxygen/element/frames/bubble_dark/tail.png index 3cc41634fa..32cb0a0222 100644 --- a/assets/voxygen/element/frames/bubble_dark/tail.png +++ b/assets/voxygen/element/frames/bubble_dark/tail.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b21fefeab508b57af56eaa72153cd584fa8f6db41f639778368b46d2a0f2f38 -size 221 +oid sha256:e1d6707a666716bf2a935705cb015b86e1ee91e0cd0cbc607be99caa598f2cdc +size 247 diff --git a/assets/voxygen/element/frames/bubble_dark/top.png b/assets/voxygen/element/frames/bubble_dark/top.png index 01d893ab00..77651db5fb 100644 --- a/assets/voxygen/element/frames/bubble_dark/top.png +++ b/assets/voxygen/element/frames/bubble_dark/top.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59fba25aad27f065ee5e2446400a1d9ce1a98b59b100ab9328880d1c25f9031a -size 129 +oid sha256:6f84fb7cd1ff7df43d944d52598d320abd7e7d24182e9c969293b62818755c36 +size 132 diff --git a/assets/voxygen/element/frames/bubble_dark/top_left.png b/assets/voxygen/element/frames/bubble_dark/top_left.png index c89832ec6f..b6d6f6c0b3 100644 --- a/assets/voxygen/element/frames/bubble_dark/top_left.png +++ b/assets/voxygen/element/frames/bubble_dark/top_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33c47f93a1641466781dea3fb5115ccb689ed80a210875032d7cddce481fce47 -size 169 +oid sha256:d8470ac055477bc559fc8199556d1d880586c5f865a2127d619fefff35f3f937 +size 177 diff --git a/assets/voxygen/element/frames/bubble_dark/top_right.png b/assets/voxygen/element/frames/bubble_dark/top_right.png index 14aeb65943..38bc5f9192 100644 --- a/assets/voxygen/element/frames/bubble_dark/top_right.png +++ b/assets/voxygen/element/frames/bubble_dark/top_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60c4e98e21e2e738552833a080e80ab02bc61a5bbec99101d7c9b75c39caeb7c -size 186 +oid sha256:fea204668ba0bd8ec95023ba642347ea9832a2b172b469854b8582359783586a +size 207 diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index bfe14afd76..238f48a3d2 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -150,7 +150,7 @@ impl<'a> Widget for Overhead<'a> { let mut text = Text::new(&bubble_contents) .font_id(self.fonts.cyri.conrod_id) .font_size(18) - .up_from(state.ids.name, 10.0) + .up_from(state.ids.name, 20.0) .x_align_to(state.ids.name, Align::Middle) .parent(id); text = if dark_mode { @@ -168,8 +168,8 @@ impl<'a> Widget for Overhead<'a> { } else { self.imgs.speech_bubble_top_left }) - .w_h(10.0, 10.0) - .top_left_with_margin_on(state.ids.speech_bubble_text, -10.0) + .w_h(16.0, 16.0) + .top_left_with_margin_on(state.ids.speech_bubble_text, -20.0) .parent(id) .set(state.ids.speech_bubble_top_left, ui); Image::new(if dark_mode { @@ -177,9 +177,9 @@ impl<'a> Widget for Overhead<'a> { } else { self.imgs.speech_bubble_top }) - .h(10.0) - .w_of(state.ids.speech_bubble_text) - .mid_top_with_margin_on(state.ids.speech_bubble_text, -10.0) + .h(16.0) + .padded_w_of(state.ids.speech_bubble_text, -4.0) + .mid_top_with_margin_on(state.ids.speech_bubble_text, -20.0) .parent(id) .set(state.ids.speech_bubble_top, ui); Image::new(if dark_mode { @@ -187,8 +187,8 @@ impl<'a> Widget for Overhead<'a> { } else { self.imgs.speech_bubble_top_right }) - .w_h(10.0, 10.0) - .top_right_with_margin_on(state.ids.speech_bubble_text, -10.0) + .w_h(16.0, 16.0) + .top_right_with_margin_on(state.ids.speech_bubble_text, -20.0) .parent(id) .set(state.ids.speech_bubble_top_right, ui); Image::new(if dark_mode { @@ -196,9 +196,9 @@ impl<'a> Widget for Overhead<'a> { } else { self.imgs.speech_bubble_left }) - .w(10.0) - .h_of(state.ids.speech_bubble_text) - .mid_left_with_margin_on(state.ids.speech_bubble_text, -10.0) + .w(16.0) + .padded_h_of(state.ids.speech_bubble_text, -4.0) + .mid_left_with_margin_on(state.ids.speech_bubble_text, -20.0) .parent(id) .set(state.ids.speech_bubble_left, ui); Image::new(if dark_mode { @@ -206,8 +206,8 @@ impl<'a> Widget for Overhead<'a> { } else { self.imgs.speech_bubble_mid }) - .wh_of(state.ids.speech_bubble_text) - .top_left_of(state.ids.speech_bubble_text) + .padded_wh_of(state.ids.speech_bubble_text, -4.0) + .top_left_with_margin_on(state.ids.speech_bubble_text, -4.0) .parent(id) .set(state.ids.speech_bubble_mid, ui); Image::new(if dark_mode { @@ -215,9 +215,9 @@ impl<'a> Widget for Overhead<'a> { } else { self.imgs.speech_bubble_right }) - .w(10.0) - .h_of(state.ids.speech_bubble_text) - .mid_right_with_margin_on(state.ids.speech_bubble_text, -10.0) + .w(16.0) + .padded_h_of(state.ids.speech_bubble_text, -4.0) + .mid_right_with_margin_on(state.ids.speech_bubble_text, -20.0) .parent(id) .set(state.ids.speech_bubble_right, ui); Image::new(if dark_mode { @@ -225,8 +225,8 @@ impl<'a> Widget for Overhead<'a> { } else { self.imgs.speech_bubble_bottom_left }) - .w_h(10.0, 10.0) - .bottom_left_with_margin_on(state.ids.speech_bubble_text, -10.0) + .w_h(16.0, 16.0) + .bottom_left_with_margin_on(state.ids.speech_bubble_text, -20.0) .parent(id) .set(state.ids.speech_bubble_bottom_left, ui); Image::new(if dark_mode { @@ -234,9 +234,9 @@ impl<'a> Widget for Overhead<'a> { } else { self.imgs.speech_bubble_bottom }) - .h(10.0) - .w_of(state.ids.speech_bubble_text) - .mid_bottom_with_margin_on(state.ids.speech_bubble_text, -10.0) + .h(16.0) + .padded_w_of(state.ids.speech_bubble_text, -4.0) + .mid_bottom_with_margin_on(state.ids.speech_bubble_text, -20.0) .parent(id) .set(state.ids.speech_bubble_bottom, ui); Image::new(if dark_mode { @@ -244,8 +244,8 @@ impl<'a> Widget for Overhead<'a> { } else { self.imgs.speech_bubble_bottom_right }) - .w_h(10.0, 10.0) - .bottom_right_with_margin_on(state.ids.speech_bubble_text, -10.0) + .w_h(16.0, 16.0) + .bottom_right_with_margin_on(state.ids.speech_bubble_text, -20.0) .parent(id) .set(state.ids.speech_bubble_bottom_right, ui); let tail = Image::new(if dark_mode { @@ -253,8 +253,8 @@ impl<'a> Widget for Overhead<'a> { } else { self.imgs.speech_bubble_tail }) - .w_h(11.0, 16.0) - .mid_bottom_with_margin_on(state.ids.speech_bubble_text, -16.0) + .w_h(22.0, 28.0) + .mid_bottom_with_margin_on(state.ids.speech_bubble_text, -32.0) .parent(id); // Move text to front (conrod depth is lowest first; not a z-index) tail.set(state.ids.speech_bubble_tail, ui); From 72e90d53767d21ff2284c488fc49426d24fb78aa Mon Sep 17 00:00:00 2001 From: CapsizeGlimmer <> Date: Tue, 26 May 2020 12:55:13 -0400 Subject: [PATCH 7/7] speech bubble changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de9b21f47f..881cbdb84f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added context-sensitive crosshair - Announce alias changes to all clients. - Dance animation +- Speech bubbles appear when nearby players talk +- NPCs call for help when attacked ### Changed