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