From b0bae18160c3ae6eaa586dfee153f9237af529b5 Mon Sep 17 00:00:00 2001 From: CapsizeGlimmer <> Date: Sun, 24 May 2020 02:37:10 -0400 Subject: [PATCH] Added chat bubbles. Refactored bubble+name+hp+energy into new overhead widget --- .../voxygen/element/frames/bubble/bottom.png | Bin 0 -> 136 bytes .../element/frames/bubble/bottom_left.png | Bin 0 -> 3485 bytes .../element/frames/bubble/bottom_right.png | Bin 0 -> 3639 bytes assets/voxygen/element/frames/bubble/left.png | Bin 0 -> 124 bytes assets/voxygen/element/frames/bubble/mid.png | Bin 0 -> 109 bytes .../voxygen/element/frames/bubble/right.png | Bin 0 -> 125 bytes assets/voxygen/element/frames/bubble/tail.png | Bin 0 -> 227 bytes assets/voxygen/element/frames/bubble/top.png | Bin 0 -> 136 bytes .../element/frames/bubble/top_left.png | Bin 0 -> 184 bytes .../element/frames/bubble/top_right.png | Bin 0 -> 219 bytes voxygen/src/hud/img_ids.rs | 12 + voxygen/src/hud/mod.rs | 518 ++++++------------ voxygen/src/hud/overhead.rs | 300 ++++++++++ 13 files changed, 479 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 0000000000000000000000000000000000000000..176620d939d741789a0b6bdc8f034c2cc9d0d921 GIT binary patch literal 136 zcmeAS@N?(olHy`uVBq!ia0vp^j6lrA!3HEtFPV4&DYhhUcNd2LAh=-f^2tCE&H|6f zVg?3oVGw3ym^DWND5&A-;uyklJ-MQ+?0CHz8xX8Hayopyq{NLMt?!Ht3=Hn1KMKEA a$H4IXC}*Vnj4Sd$Jq(_%elF{r5}E*T{3Z+l literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a29e659ba187a2eda9c9535f362bb6e84da58b28 GIT binary patch literal 3485 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 LNkvXXu0mjf%0I1T literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a8d2ad180eef00ca6ab4f9c43ad711d6cb2b00bd GIT binary patch literal 3639 zcmV-74#@F|P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+P#@;a_c${g#U9Dvjp)0K9<90)$CxFKOZRBaU3T( zK6h&3II<~;0vkZ1!I=4iE93HU!|fTieI7UYI^*&+(e26CgkGQQ_%I%gWZa$OHe3^xd47l` zr$2m)~Rqe0yE)KOW+TP{o6? zVa_bD9_^f>iT#Kzzy0jFPB@-fVNDv>1VF^I1!Jto14p!8Y_vymE?EnX83$D?cRsj= z2ds!280mud(V673(`R!}vin*mE;=2Cn{b5q=o7vvzT&J9g!oAz6GK(J1QCgnBpWGG zqmLm?s?1fnzB1BW z^DQ)Kv89$hu9?z<1q{gK~{_Jhy-9)Eo_PXoSNKYdE8V_QsXDT!kp$6Rnc2*^4^ zTKJ*510&R~dPE5$aFH_Vns(oMNL;hZTJBhdr>z-XqfAZTZ0TmDb5*4rX~Yu5qE9H_ z6^hr-T8IDSXnwyiWp_ey0dgEbqKMq_SR70qAmDtJjn&B1g|)O~5e`j83#=?8D`Dl4 z&GE|xUyke0RgN7FCn6gUqXTzHW+DHZnwvrctA+whYm!{9 zbpofwvZ%V#)xvehBP`|j7sW4Ds>)15N~*SPFG#S(v`5@<=PvuuS~ZYA8k~sr{VfFY&E~(Fu)FhVbkW z9mzjd(fnV*0FSk8;UBDtQ?$%4&jgN$_Tkl7uFa;ZruZ?MQe~22ASFDU9r09L)(6E& zDYThtB$T`D7P*J`w_bTa`|ro*=eBBEwaO4cm26#CjI3kFi1v~BECeW%T;N)FmY-IK zrZQFQgEtbqVY2!`i6c%Br=~(T>vGp52jrFmX9`%(CM^?CK_;VG)^H6S>DP*eG>J=GaI!J z%$^{l;B3(DQT`2V9r4Gh6ituZq~Zi_Z;KUGG%b_+w00rTZ7=$mPji9H<7`7Wq|9v| z-9b8M(%r9zxn9lm)hgX&`M+93@^5$IEReNSLGE5#wIP~z_D>hLsG{3Sv^Q!|JL97T z(e{z{hdL4>Q&0DOfS4;n!t=uri|505QiFzQmb|JRFGtb!&4`Mak;ZEg3^tj)gQiQ?vyF}5#cCR#*-6^H4BywOeW=nSqs6Ji+Xd4TX%L57F=x78 zQ5Hws0<2SYk;RROurM91#)lN>eG#hynb5QLIv->x_*^oyypW;i(flqqm%9>XVB~ym zc}NhT&(t;Tx3%d>q$15}Es-40m_5t5ijmT-=;q8dc+u=R9LQ>xM|b2;r-(28nSv(V zH*K=C=zM^M!fvw~{V<_w(+-;HmPJIMe9A1~nr00eHE=b!R_rT1HI<@+IAjhbRv?^i zx20v#fWYiX-|ZCBW$Y8Uxb=Y33EQf9ML)o|+#uG0eds)^4d2JCnhn>y%xWH{z2>~l zNQ{1(lN-OE6*cm$vG3VxzBl$gTg~^zzGtiX8(?4AYW@b;SGJnJ0rr~p4BO@or+icd za)Fa13_-NhnATXl?IEeE+T83%gX@CQ6U85bnk_ZDJj!@$B|_^xbI-g7S?>|bpi(plOb$i$c6wY8y{O+O6{aKXzOh4O3lS%Lha7cCWWR&Y1bEw za{g*pavz3%3xT6I4khQBNw$HpcL3I-%Zl-D$$Z=LoZ!JuRZ)n+N^zbIdOfdVG$E_JL71Zf;Ap5dqn$ z9byjZK!n+RAJmvmGaXgpxVkBoxS+gGQ0yAfc8xL=X`)mE+6DqEuTpF@OskSI5sK-m z`z^^KxM>_AXiagU#e@JwcS*BEp~PJbY#-kBBioX#GY5o9m;|b}(9seU_??Vc<$#rG zid%@p%2ZXnJLKd^GuQ0;94cnVN*fS-BPR9#VRWA^*%4AB+%$|@^JrMz!nKzKj9>TT z%sV@K4@XK?l*^)Bc8#hBY#Q;D-{F+s1gB5rLTNUhNj{Dq?$xT+SZ~`K?UY`0*&eT5 zXKv%F)I9Q{f#~SHpOw!cSe`2%Wwxsd*%s}hr~if8`a!n_RX?B}oc15|CB<45dIaF( z1zTXK0<^*+Oos?qjczd_xRRr7`aDd@ZHR6ye0j4vSXu7W0 z=e5C833(d)Xs>MD-gEYv|L%6lC5X)PicW*~npo12zO3kRJrDMpA6E3Zo(F3m*7L9& z^RVJ4W4~X`D+ig+9(ui+Yq=q6sC#%nB%7lxETBgM2ky*fAOcH^_Z`}d=tL~KIO-hP z=dN_oSBpdcX>)E+bvQwH&d+Wa^6hj-Oo(VhK++FLuwIf!q>dK%^7f`Q8cAd>WHaRf ziJZsIKUfX}_1`0&(qbTq+m7*zWQ>;#ZQ9%Y(;fCNlgBfisQ9Oh^r|k+nDy4hKa6XQ zn+j^Y{v%++w>$atAccVZFG^XL0YViPq5uE^gK0xUP)S2WAaHVTW@&6?004NLeUUv# z!$2IxUsH=B6$gumIAo|!7Q`Q;E2k$*zi>uLvQGeoO%} zvy3@OO2T)1-6O!)yBN>%KlkV8QF9gp0wVD&GfbO!gLrz=HaPDSM_5r-iO-2gO}ZfQ zBi9v=-#8as7IZLMN=c5B#x?@PWeK{W0mt3XRTai&3p0}hI9JL zGS_JiA%R6KL4pVcRg_SMjTo&uDHc++ANTPOx_*gV3b{&P#t9I72Cnp$zfuQgK1r{&wCEAgyA51iw=`uBxZD8- zo($QPT`5RY$mfCgGy0}1(02=TuX(*S_i_3Fq^Yaq4RCM>j1(w)-RIq1?Y;ebrrF;Q zP(5;)v96OO00006VoOIv0RI600RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000Mc zNliru4wfg0&*9_(n`45oASYTEl1DK*;AKx+XD{DVsU|?Wi zWaAgQkIhtMMIY~8b!22@olV literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/frames/bubble/left.png b/assets/voxygen/element/frames/bubble/left.png new file mode 100644 index 0000000000000000000000000000000000000000..8a4234273fcf9873137d5c73313e003e13514229 GIT binary patch literal 124 zcmeAS@N?(olHy`uVBq!ia0vp^AT}ch8<2ed)xHi$u_bxCyDx`7I;J! zGca%qgD@k*tT_@uK^adM#}JO|$xp(s-OFFKZbiY17lv)@zo%&$JYZlrF2k1da_w7Q PpgIOmS3j3^P6cs&D0F>`iGT`Vt9g2B_(&t;ucLK6Ux`7I;J! zGca%qgD@k*tT_@uL0L~1#}JO|$u(tV$LrZ-91d!}YMi@O^zh5s-{lz?7A)YDtGoVk QBTylOr>mdKI;Vst06&`}YybcN literal 0 HcmV?d00001 diff --git a/assets/voxygen/element/frames/bubble/tail.png b/assets/voxygen/element/frames/bubble/tail.png new file mode 100644 index 0000000000000000000000000000000000000000..ecbd89244eb029cef43c4daffeff5662d183f664 GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^+(0bA!3HD`>}{$~~)_x-Tv+5D0FZhAt(4*sd`3?;l4qDhwf4u_bxCyDx`7I;J! zGca%qgD@k*tT_@u!3a+m#}J9j$tey@e@@-{Em^?eR^s~3v60I{R7v}mLAF$x@J7w5 z;wYJYwbJ?x^E!l&n2&}x5gUz!5T3<)6Ex3}Wd0U&5NZNjsfbCw4W1B0Kn W3fH^%tbIT`7(8A5T-G@yGywqC%s93H literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7930e8372365e6fee74b286fc742d289e08d7b9f GIT binary patch literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4u_bxCyDx`7I;J! zGca%qgD@k*tT_@u!D>$z#}J9j$xp(s-8C|PV-GJ;O7cS0D(hk zx|@h{$3#y^~{A1^5Z1pD?@c+Kf_iRNWyc&%x7IKVyZ4$v74p00i_>zopr E0QE6Sp#T5? literal 0 HcmV?d00001 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", + 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::() .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 = Rgb::new(1.0, 0.9, 0.8); - const LIGHT_OR: Rgb = Rgb::new(1.0, 0.925, 0.749); - const LIGHT_MED_OR: Rgb = Rgb::new(1.0, 0.85, 0.498); - const MED_OR: Rgb = Rgb::new(1.0, 0.776, 0.247); - const DARK_ORANGE: Rgb = Rgb::new(1.0, 0.7, 0.0); - const RED_ORANGE: Rgb = Rgb::new(1.0, 0.349, 0.0); - const DAMAGE_COLORS: [Rgb; 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 = Rgb::new(1.0, 0.9, 0.8); + const LIGHT_OR: Rgb = Rgb::new(1.0, 0.925, 0.749); + const LIGHT_MED_OR: Rgb = Rgb::new(1.0, 0.85, 0.498); + const MED_OR: Rgb = Rgb::new(1.0, 0.776, 0.247); + const DARK_ORANGE: Rgb = Rgb::new(1.0, 0.7, 0.0); + const RED_ORANGE: Rgb = Rgb::new(1.0, 0.349, 0.0); + const DAMAGE_COLORS: [Rgb; 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::() < 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::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::() < 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); + } + } +}