1use super::{
2 DEFAULT_NPC, ENEMY_HP_COLOR, FACTION_COLOR, GROUP_COLOR, GROUP_MEMBER, HP_COLOR, LOW_HP_COLOR,
3 QUALITY_EPIC, REGION_COLOR, SAY_COLOR, STAMINA_COLOR, TELL_COLOR, TEXT_BG, TEXT_COLOR,
4 cr_color, img_ids::Imgs,
5};
6use crate::{
7 game_input::GameInput,
8 hud::BuffIcon,
9 settings::{ControlSettings, InterfaceSettings},
10 ui::{Ingameable, fonts::Fonts},
11};
12use common::{
13 comp::{Buffs, Energy, Health, SpeechBubble, SpeechBubbleType, Stance},
14 resources::Time,
15};
16use conrod_core::{
17 Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, color,
18 position::Align,
19 widget::{self, Image, Rectangle, RoundedRectangle, Text},
20 widget_ids,
21};
22use i18n::Localization;
23use keyboard_keynames::key_layout::KeyLayout;
24
25const MAX_BUBBLE_WIDTH: f64 = 250.0;
26widget_ids! {
27 struct Ids {
28 speech_bubble_text,
30 speech_bubble_shadow,
31 speech_bubble_top_left,
32 speech_bubble_top,
33 speech_bubble_top_right,
34 speech_bubble_left,
35 speech_bubble_mid,
36 speech_bubble_right,
37 speech_bubble_bottom_left,
38 speech_bubble_bottom,
39 speech_bubble_bottom_right,
40 speech_bubble_tail,
41 speech_bubble_icon,
42
43 name_bg,
45 name,
46
47 level,
49 level_skull,
50 hardcore,
51 health_bar,
52 decay_bar,
53 health_bar_bg,
54 health_txt,
55 mana_bar,
56 health_bar_fg,
57
58 buffs_align,
60 buffs[],
61 buff_timers[],
62
63 interaction_hints,
65 interaction_hints_bg,
66 }
67}
68
69pub struct Info<'a> {
70 pub name: Option<String>,
71 pub health: Option<&'a Health>,
72 pub buffs: Option<&'a Buffs>,
73 pub energy: Option<&'a Energy>,
74 pub combat_rating: Option<f32>,
75 pub hardcore: bool,
76 pub stance: Option<&'a Stance>,
77}
78
79pub fn should_show_healthbar(health: &Health) -> bool {
81 (health.current() - health.maximum()).abs() > Health::HEALTH_EPSILON
82 || health.current() < health.base_max()
83}
84pub fn decayed_health_displayed(health: &Health) -> bool {
86 (1.0 - health.maximum() / health.base_max()) > 0.0
87}
88#[derive(WidgetCommon)]
91pub struct Overhead<'a> {
92 info: Option<Info<'a>>,
93 bubble: Option<&'a SpeechBubble>,
94 in_group: bool,
95 settings: &'a InterfaceSettings,
96 pulse: f32,
97 i18n: &'a Localization,
98 controls: &'a ControlSettings,
99 imgs: &'a Imgs,
100 fonts: &'a Fonts,
101 key_layout: &'a Option<KeyLayout>,
102 interaction_options: Vec<(GameInput, String)>,
103 time: &'a Time,
104
105 #[conrod(common_builder)]
106 common: widget::CommonBuilder,
107}
108
109impl<'a> Overhead<'a> {
110 pub fn new(
111 info: Option<Info<'a>>,
112 bubble: Option<&'a SpeechBubble>,
113 in_group: bool,
114 settings: &'a InterfaceSettings,
115 pulse: f32,
116 i18n: &'a Localization,
117 controls: &'a ControlSettings,
118 imgs: &'a Imgs,
119 fonts: &'a Fonts,
120 key_layout: &'a Option<KeyLayout>,
121 interaction_options: Vec<(GameInput, String)>,
122 time: &'a Time,
123 ) -> Self {
124 Self {
125 info,
126 bubble,
127 in_group,
128 settings,
129 pulse,
130 i18n,
131 controls,
132 imgs,
133 fonts,
134 key_layout,
135 interaction_options,
136 time,
137 common: widget::CommonBuilder::default(),
138 }
139 }
140}
141
142pub struct State {
143 ids: Ids,
144}
145
146impl Ingameable for Overhead<'_> {
147 fn prim_count(&self) -> usize {
148 self.info.as_ref().map_or(0, |info| {
170 2 + 1
171 + if self.bubble.is_none() {
172 2 * info
173 .buffs
174 .as_ref()
175 .map_or(0, |buffs| BuffIcon::icons_vec(buffs, info.stance).len())
176 .min(11)
177 } else {
178 0
179 }
180 + if info.health.is_some_and(should_show_healthbar) {
181 5 + usize::from(info.energy.is_some()) + usize::from(info.hardcore)
182 } else {
183 0
184 }
185 + usize::from(info.health.is_some_and(decayed_health_displayed))
186 + (!self.interaction_options.is_empty()) as usize * 2
187 }) + if self.bubble.is_some() { 13 } else { 0 }
188 }
189}
190
191impl Widget for Overhead<'_> {
192 type Event = ();
193 type State = State;
194 type Style = ();
195
196 fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
197 State {
198 ids: Ids::new(id_gen),
199 }
200 }
201
202 fn style(&self) -> Self::Style {}
203
204 fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
205 let widget::UpdateArgs { id, state, ui, .. } = args;
206 const BARSIZE: f64 = 2.0; const MANA_BAR_HEIGHT: f64 = BARSIZE * 1.5;
208 const MANA_BAR_Y: f64 = MANA_BAR_HEIGHT / 2.0;
209 if let Some(Info {
210 ref name,
211 health,
212 buffs,
213 energy,
214 combat_rating,
215 hardcore,
216 stance,
217 }) = self.info
218 {
219 let hp_percentage = health.map_or(100.0, |h| {
221 f64::from(h.current() / h.base_max().max(h.maximum()) * 100.0)
222 });
223 let health_current = health.map_or(1.0, |h| f64::from(h.current()));
225 let health_max = health.map_or(1.0, |h| f64::from(h.maximum()));
226 let name_y = if (health_current - health_max).abs() < 1e-6 {
227 MANA_BAR_Y + 20.0
228 } else {
229 MANA_BAR_Y + 32.0
230 };
231 let font_size = if hp_percentage.abs() > 99.9 { 24 } else { 20 };
232 let health_cur_txt = if self.settings.use_health_prefixes {
235 match health_current as u32 {
236 0..=999 => format!("{:.0}", health_current.max(1.0)),
237 1000..=999999 => format!("{:.0}K", (health_current / 1000.0).max(1.0)),
238 _ => format!("{:.0}M", (health_current / 1.0e6).max(1.0)),
239 }
240 } else {
241 format!("{:.0}", health_current.max(1.0))
242 };
243 let health_max_txt = if self.settings.use_health_prefixes {
244 match health_max as u32 {
245 0..=999 => format!("{:.0}", health_max.max(1.0)),
246 1000..=999999 => format!("{:.0}K", (health_max / 1000.0).max(1.0)),
247 _ => format!("{:.0}M", (health_max / 1.0e6).max(1.0)),
248 }
249 } else {
250 format!("{:.0}", health_max.max(1.0))
251 };
252 let buff_icons = buffs
255 .as_ref()
256 .map(|buffs| BuffIcon::icons_vec(buffs, stance))
257 .unwrap_or_default();
258 let buff_count = buff_icons.len().min(11);
259 Rectangle::fill_with([168.0, 100.0], color::TRANSPARENT)
260 .x_y(-1.0, name_y + 60.0)
261 .parent(id)
262 .set(state.ids.buffs_align, ui);
263
264 let gen = &mut ui.widget_id_generator();
265 if state.ids.buffs.len() < buff_count {
266 state.update(|state| state.ids.buffs.resize(buff_count, gen));
267 };
268 if state.ids.buff_timers.len() < buff_count {
269 state.update(|state| state.ids.buff_timers.resize(buff_count, gen));
270 };
271
272 let buff_ani = ((self.pulse * 4.0).cos() * 0.5 + 0.8) + 0.5; let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani);
274 let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0);
275 if self.bubble.is_none() {
277 state
278 .ids
279 .buffs
280 .iter()
281 .copied()
282 .zip(state.ids.buff_timers.iter().copied())
283 .zip(buff_icons.iter())
284 .enumerate()
285 .for_each(|(i, ((id, timer_id), buff))| {
286 let max_duration = buff.kind.max_duration();
288 let current_duration = buff.end_time.map(|end| end - self.time.0);
289 let duration_percentage = current_duration.map_or(1000.0, |cur| {
290 max_duration.map_or(1000.0, |max| cur / max.0 * 1000.0)
291 }) as u32; let buff_img = buff.kind.image(self.imgs);
293 let buff_widget = Image::new(buff_img).w_h(20.0, 20.0);
294 let x = i % 5;
296 let y = i / 5;
297 let buff_widget = buff_widget.bottom_left_with_margins_on(
298 state.ids.buffs_align,
299 0.0 + y as f64 * (21.0),
300 0.0 + x as f64 * (21.0),
301 );
302 buff_widget
303 .color(if current_duration.is_some_and(|cur| cur < 10.0) {
304 Some(pulsating_col)
305 } else {
306 Some(norm_col)
307 })
308 .set(id, ui);
309
310 Image::new(match duration_percentage as u64 {
311 875..=1000 => self.imgs.nothing, 750..=874 => self.imgs.buff_0, 625..=749 => self.imgs.buff_1, 500..=624 => self.imgs.buff_2, 375..=499 => self.imgs.buff_3, 250..=374 => self.imgs.buff_4, 125..=249 => self.imgs.buff_5, 0..=124 => self.imgs.buff_6, _ => self.imgs.nothing,
320 })
321 .w_h(20.0, 20.0)
322 .middle_of(id)
323 .set(timer_id, ui);
324 });
325 }
326 Text::new(name.as_deref().unwrap_or(""))
328 .font_id(self.fonts.cyri.conrod_id)
330 .font_size(font_size)
331 .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
332 .x_y(-1.0, name_y)
333 .parent(id)
334 .set(state.ids.name_bg, ui);
335 Text::new(name.as_deref().unwrap_or(""))
336 .font_id(self.fonts.cyri.conrod_id)
338 .font_size(font_size)
339 .color(if self.in_group {
340 GROUP_MEMBER
341 } else {
344 DEFAULT_NPC
345 })
346 .x_y(0.0, name_y + 1.0)
347 .parent(id)
348 .set(state.ids.name, ui);
349
350 match health {
351 Some(health)
352 if should_show_healthbar(health) || decayed_health_displayed(health) =>
353 {
354 let hp_ani = (self.pulse * 4.0).cos() * 0.5 + 1.0; let crit_hp_color: Color = Color::Rgba(0.93, 0.59, 0.03, hp_ani);
357 let decayed_health = f64::from(1.0 - health.maximum() / health.base_max());
358 Image::new(if self.in_group {self.imgs.health_bar_group_bg} else {self.imgs.enemy_health_bg})
360 .w_h(84.0 * BARSIZE, 10.0 * BARSIZE)
361 .x_y(0.0, MANA_BAR_Y + 6.5) .color(Some(Color::Rgba(0.1, 0.1, 0.1, 0.8)))
363 .parent(id)
364 .set(state.ids.health_bar_bg, ui);
365
366 let size_factor = (hp_percentage / 100.0) * BARSIZE;
368 let w = if self.in_group {
369 82.0 * size_factor
370 } else {
371 73.0 * size_factor
372 };
373 let h = 6.0 * BARSIZE;
374 let x = if self.in_group {
375 (0.0 + (hp_percentage / 100.0 * 41.0 - 41.0)) * BARSIZE
376 } else {
377 (4.5 + (hp_percentage / 100.0 * 36.45 - 36.45)) * BARSIZE
378 };
379 Image::new(self.imgs.enemy_bar)
380 .w_h(w, h)
381 .x_y(x, MANA_BAR_Y + 8.0)
382 .color(if self.in_group {
383 Some(match hp_percentage {
385 x if (0.0..25.0).contains(&x) => crit_hp_color,
386 x if (25.0..50.0).contains(&x) => LOW_HP_COLOR,
387 _ => HP_COLOR,
388 })
389 } else {
390 Some(ENEMY_HP_COLOR)
391 })
392 .parent(id)
393 .set(state.ids.health_bar, ui);
394
395 if decayed_health > 0.0 {
396 let x_decayed = if self.in_group {
397 (0.0 - (decayed_health * 41.0 - 41.0)) * BARSIZE
398 } else {
399 (4.5 - (decayed_health * 36.45 - 36.45)) * BARSIZE
400 };
401
402 let decay_bar_len = decayed_health
403 * if self.in_group {
404 82.0 * BARSIZE
405 } else {
406 73.0 * BARSIZE
407 };
408 Image::new(self.imgs.enemy_bar)
409 .w_h(decay_bar_len, h)
410 .x_y(x_decayed, MANA_BAR_Y + 8.0)
411 .color(Some(QUALITY_EPIC))
412 .parent(id)
413 .set(state.ids.decay_bar, ui);
414 }
415 let mut txt = format!("{}/{}", health_cur_txt, health_max_txt);
416 if health.is_dead {
417 txt = self.i18n.get_msg("hud-group-dead").to_string()
418 };
419 Text::new(&txt)
420 .mid_top_with_margin_on(state.ids.health_bar_bg, 2.0)
421 .font_size(10)
422 .font_id(self.fonts.cyri.conrod_id)
423 .color(TEXT_COLOR)
424 .parent(id)
425 .set(state.ids.health_txt, ui);
426
427 if let Some(energy) = energy {
429 let energy_factor = f64::from(energy.current() / energy.maximum());
430 let size_factor = energy_factor * BARSIZE;
431 let w = if self.in_group {
432 80.0 * size_factor
433 } else {
434 72.0 * size_factor
435 };
436 let x = if self.in_group {
437 ((0.0 + (energy_factor * 40.0)) - 40.0) * BARSIZE
438 } else {
439 ((3.5 + (energy_factor * 36.5)) - 36.45) * BARSIZE
440 };
441 Rectangle::fill_with([w, MANA_BAR_HEIGHT], STAMINA_COLOR)
442 .x_y(
443 x, MANA_BAR_Y, )
445 .parent(id)
446 .set(state.ids.mana_bar, ui);
447 }
448
449 Image::new(if self.in_group {self.imgs.health_bar_group} else {self.imgs.enemy_health})
451 .w_h(84.0 * BARSIZE, 10.0 * BARSIZE)
452 .x_y(0.0, MANA_BAR_Y + 6.5) .color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.99)))
454 .parent(id)
455 .set(state.ids.health_bar_fg, ui);
456
457 if let Some(combat_rating) = combat_rating {
458 let indicator_col = cr_color(combat_rating);
459 let artifact_diffculty = 122.0;
460
461 if combat_rating > artifact_diffculty && !self.in_group {
462 let skull_ani =
463 ((self.pulse * 0.7).cos() * 0.5 + 0.5) * 10.0; Image::new(if skull_ani as i32 == 1 && rand::random::<f32>() < 0.9 {
465 self.imgs.skull_2
466 } else {
467 self.imgs.skull
468 })
469 .w_h(18.0 * BARSIZE, 18.0 * BARSIZE)
470 .x_y(-39.0 * BARSIZE, MANA_BAR_Y + 7.0)
471 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
472 .parent(id)
473 .set(state.ids.level_skull, ui);
474 } else {
475 Image::new(if self.in_group {
476 self.imgs.nothing
477 } else {
478 self.imgs.combat_rating_ico
479 })
480 .w_h(7.0 * BARSIZE, 7.0 * BARSIZE)
481 .x_y(-37.0 * BARSIZE, MANA_BAR_Y + 6.0)
482 .color(Some(indicator_col))
483 .parent(id)
484 .set(state.ids.level, ui);
485 }
486 }
487
488 if hardcore {
489 Image::new(self.imgs.hardcore)
490 .w_h(18.0 * BARSIZE, 18.0 * BARSIZE)
491 .x_y(39.0 * BARSIZE, MANA_BAR_Y + 13.0)
492 .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
493 .parent(id)
494 .set(state.ids.hardcore, ui);
495 }
496 },
497 _ => {},
498 }
499
500 if !self.interaction_options.is_empty() {
502 let text = self
503 .interaction_options
504 .iter()
505 .filter_map(|(input, action)| {
506 Some((self.controls.get_binding(*input)?, action))
507 })
508 .map(|(input, action)| {
509 format!(
510 "{} {}",
511 input.display_string(self.key_layout).as_str(),
512 action
513 )
514 })
515 .collect::<Vec<_>>()
516 .join("\n");
517
518 let scale = 30.0;
519 let btn_rect_size = scale * 0.8;
520 let btn_font_size = scale * 0.6;
521 let btn_radius = btn_rect_size / 5.0;
522 let btn_color = Color::Rgba(0.0, 0.0, 0.0, 0.8);
523
524 let hints_text = Text::new(&text)
525 .font_id(self.fonts.cyri.conrod_id)
526 .font_size(btn_font_size as u32)
527 .color(TEXT_COLOR)
528 .parent(id)
529 .down_from(
530 self.info.map_or(state.ids.name, |info| {
531 if info.health.is_some_and(should_show_healthbar) {
532 if info.energy.is_some() {
533 state.ids.mana_bar
534 } else {
535 state.ids.health_bar
536 }
537 } else {
538 state.ids.name
539 }
540 }),
541 12.0,
542 )
543 .align_middle_x_of(state.ids.name)
544 .depth(1.0);
545
546 let [w, h] = hints_text.get_wh(ui).unwrap_or([btn_rect_size; 2]);
547
548 hints_text.set(state.ids.interaction_hints, ui);
549
550 RoundedRectangle::fill_with(
551 [w + btn_radius * 2.0, h + btn_radius * 2.0],
552 btn_radius,
553 btn_color,
554 )
555 .depth(2.0)
556 .middle_of(state.ids.interaction_hints)
557 .align_middle_y_of(state.ids.interaction_hints)
558 .parent(id)
559 .set(state.ids.interaction_hints_bg, ui);
560 }
561 }
562 if let Some(bubble) = self.bubble {
564 let dark_mode = self.settings.speech_bubble_dark_mode;
565 let bubble_contents: String = self.i18n.get_content(bubble.content());
566 let (text_color, shadow_color) = bubble_color(bubble, dark_mode);
567 let mut text = Text::new(&bubble_contents)
568 .color(text_color)
569 .font_id(self.fonts.cyri.conrod_id)
570 .font_size(18)
571 .up_from(state.ids.name, 26.0)
572 .x_align_to(state.ids.name, Align::Middle)
573 .parent(id);
574
575 if let Some(w) = text.get_w(ui) {
576 if w > MAX_BUBBLE_WIDTH {
577 text = text.w(MAX_BUBBLE_WIDTH);
578 }
579 }
580 Image::new(if dark_mode {
581 self.imgs.dark_bubble_top_left
582 } else {
583 self.imgs.speech_bubble_top_left
584 })
585 .w_h(16.0, 16.0)
586 .top_left_with_margin_on(state.ids.speech_bubble_text, -20.0)
587 .parent(id)
588 .set(state.ids.speech_bubble_top_left, ui);
589 Image::new(if dark_mode {
590 self.imgs.dark_bubble_top
591 } else {
592 self.imgs.speech_bubble_top
593 })
594 .h(16.0)
595 .padded_w_of(state.ids.speech_bubble_text, -4.0)
596 .mid_top_with_margin_on(state.ids.speech_bubble_text, -20.0)
597 .parent(id)
598 .set(state.ids.speech_bubble_top, ui);
599 Image::new(if dark_mode {
600 self.imgs.dark_bubble_top_right
601 } else {
602 self.imgs.speech_bubble_top_right
603 })
604 .w_h(16.0, 16.0)
605 .top_right_with_margin_on(state.ids.speech_bubble_text, -20.0)
606 .parent(id)
607 .set(state.ids.speech_bubble_top_right, ui);
608 Image::new(if dark_mode {
609 self.imgs.dark_bubble_left
610 } else {
611 self.imgs.speech_bubble_left
612 })
613 .w(16.0)
614 .padded_h_of(state.ids.speech_bubble_text, -4.0)
615 .mid_left_with_margin_on(state.ids.speech_bubble_text, -20.0)
616 .parent(id)
617 .set(state.ids.speech_bubble_left, ui);
618 Image::new(if dark_mode {
619 self.imgs.dark_bubble_mid
620 } else {
621 self.imgs.speech_bubble_mid
622 })
623 .padded_wh_of(state.ids.speech_bubble_text, -4.0)
624 .top_left_with_margin_on(state.ids.speech_bubble_text, -4.0)
625 .parent(id)
626 .set(state.ids.speech_bubble_mid, ui);
627 Image::new(if dark_mode {
628 self.imgs.dark_bubble_right
629 } else {
630 self.imgs.speech_bubble_right
631 })
632 .w(16.0)
633 .padded_h_of(state.ids.speech_bubble_text, -4.0)
634 .mid_right_with_margin_on(state.ids.speech_bubble_text, -20.0)
635 .parent(id)
636 .set(state.ids.speech_bubble_right, ui);
637 Image::new(if dark_mode {
638 self.imgs.dark_bubble_bottom_left
639 } else {
640 self.imgs.speech_bubble_bottom_left
641 })
642 .w_h(16.0, 16.0)
643 .bottom_left_with_margin_on(state.ids.speech_bubble_text, -20.0)
644 .parent(id)
645 .set(state.ids.speech_bubble_bottom_left, ui);
646 Image::new(if dark_mode {
647 self.imgs.dark_bubble_bottom
648 } else {
649 self.imgs.speech_bubble_bottom
650 })
651 .h(16.0)
652 .padded_w_of(state.ids.speech_bubble_text, -4.0)
653 .mid_bottom_with_margin_on(state.ids.speech_bubble_text, -20.0)
654 .parent(id)
655 .set(state.ids.speech_bubble_bottom, ui);
656 Image::new(if dark_mode {
657 self.imgs.dark_bubble_bottom_right
658 } else {
659 self.imgs.speech_bubble_bottom_right
660 })
661 .w_h(16.0, 16.0)
662 .bottom_right_with_margin_on(state.ids.speech_bubble_text, -20.0)
663 .parent(id)
664 .set(state.ids.speech_bubble_bottom_right, ui);
665 let tail = Image::new(if dark_mode {
666 self.imgs.dark_bubble_tail
667 } else {
668 self.imgs.speech_bubble_tail
669 })
670 .parent(id)
671 .mid_bottom_with_margin_on(state.ids.speech_bubble_text, -32.0);
672
673 if dark_mode {
674 tail.w_h(22.0, 13.0)
675 } else {
676 tail.w_h(22.0, 28.0)
677 }
678 .set(state.ids.speech_bubble_tail, ui);
679
680 let mut text_shadow = Text::new(&bubble_contents)
681 .color(shadow_color)
682 .font_id(self.fonts.cyri.conrod_id)
683 .font_size(18)
684 .x_relative_to(state.ids.speech_bubble_text, 1.0)
685 .y_relative_to(state.ids.speech_bubble_text, -1.0)
686 .parent(id);
687 text.depth(text_shadow.get_depth() - 1.0)
689 .set(state.ids.speech_bubble_text, ui);
690 if let Some(w) = text_shadow.get_w(ui) {
691 if w > MAX_BUBBLE_WIDTH {
692 text_shadow = text_shadow.w(MAX_BUBBLE_WIDTH);
693 }
694 }
695 text_shadow.set(state.ids.speech_bubble_shadow, ui);
696 let icon = if self.settings.speech_bubble_icon {
697 bubble_icon(bubble, self.imgs)
698 } else {
699 self.imgs.nothing
700 };
701 Image::new(icon)
702 .w_h(16.0, 16.0)
703 .top_left_with_margin_on(state.ids.speech_bubble_text, -16.0)
704 .set(state.ids.speech_bubble_icon, ui);
707 }
708 }
709}
710
711fn bubble_color(bubble: &SpeechBubble, dark_mode: bool) -> (Color, Color) {
712 let light_color = match bubble.icon {
713 SpeechBubbleType::Tell => TELL_COLOR,
714 SpeechBubbleType::Say => SAY_COLOR,
715 SpeechBubbleType::Region => REGION_COLOR,
716 SpeechBubbleType::Group => GROUP_COLOR,
717 SpeechBubbleType::Faction => FACTION_COLOR,
718 SpeechBubbleType::World
719 | SpeechBubbleType::Quest
720 | SpeechBubbleType::Trade
721 | SpeechBubbleType::None => TEXT_COLOR,
722 };
723 if dark_mode {
724 (light_color, TEXT_BG)
725 } else {
726 (TEXT_BG, light_color)
727 }
728}
729
730fn bubble_icon(sb: &SpeechBubble, imgs: &Imgs) -> conrod_core::image::Id {
731 match sb.icon {
732 SpeechBubbleType::Tell => imgs.chat_tell_small,
734 SpeechBubbleType::Say => imgs.chat_say_small,
735 SpeechBubbleType::Region => imgs.chat_region_small,
736 SpeechBubbleType::Group => imgs.chat_group_small,
737 SpeechBubbleType::Faction => imgs.chat_faction_small,
738 SpeechBubbleType::World => imgs.chat_world_small,
739 SpeechBubbleType::Quest => imgs.nothing, SpeechBubbleType::Trade => imgs.nothing, SpeechBubbleType::None => imgs.nothing, }
743}