veloren_voxygen/hud/
map.rs

1use super::{
2    MapMarkers, QUALITY_COMMON, QUALITY_EPIC, QUALITY_HIGH, QUALITY_LOW, QUALITY_MODERATE, TEXT_BG,
3    TEXT_BLUE_COLOR, TEXT_COLOR, TEXT_GRAY_COLOR, TEXT_VELORITE, UI_HIGHLIGHT_0, UI_MAIN,
4    img_ids::{Imgs, ImgsRot},
5};
6use crate::{
7    GlobalState,
8    game_input::GameInput,
9    session::settings_change::{Interface as InterfaceChange, Interface::*},
10    ui::{ImageFrame, Tooltip, TooltipManager, Tooltipable, fonts::Fonts, img_ids},
11    window::KeyMouse,
12};
13use client::{self, Client, SiteMarker};
14use common::{
15    comp,
16    comp::group::Role,
17    map::{Marker, MarkerFlags, MarkerKind},
18    terrain::{CoordinateConversions, TerrainChunkSize},
19    trade::Good,
20    vol::RectVolSize,
21};
22use common_net::msg::world_msg::{PoiKind, SiteId};
23use conrod_core::{
24    Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget, WidgetCommon, color,
25    input::MouseButton as ConrodMouseButton,
26    position,
27    widget::{self, Button, Image, Rectangle, Text},
28    widget_ids,
29};
30use i18n::Localization;
31use specs::WorldExt;
32use std::borrow::Cow;
33use vek::*;
34use winit::event::MouseButton;
35
36widget_ids! {
37    struct Ids {
38        frame,
39        bg,
40        icon,
41        close,
42        title,
43        map_align,
44        qlog_align,
45        location_name,
46        indicator,
47        indicator_overlay,
48        map_layers[],
49        map_title,
50        qlog_title,
51        zoom_slider,
52        mmap_site_icons[],
53        mmap_poi_icons[],
54        mmap_poi_title_bgs[],
55        mmap_poi_titles[],
56        peaks_txt,
57        peaks_txt_bg,
58        site_difs[],
59        member_indicators[],
60        member_height_indicators[],
61        location_marker,
62        location_marker_group[],
63        map_settings_align,
64        show_towns_img,
65        show_towns_box,
66        show_towns_text,
67        show_sea_chapels_img,
68        show_sea_chapels_box,
69        show_sea_chapels_text,
70        show_castles_img,
71        show_castles_box,
72        show_castles_text,
73        show_bridges_img,
74        show_bridges_box,
75        show_bridges_text,
76        show_dungeons_img,
77        show_dungeons_box,
78        show_dungeons_text,
79        show_caves_img,
80        show_caves_box,
81        show_caves_text,
82        show_trees_img,
83        show_trees_box,
84        show_trees_text,
85        show_peaks_img,
86        show_peaks_box,
87        show_peaks_text,
88        show_biomes_img,
89        show_biomes_box,
90        show_biomes_text,
91        show_glider_courses_img,
92        show_glider_courses_box,
93        show_glider_courses_text,
94        show_quests_img,
95        show_quests_box,
96        show_quests_text,
97        show_voxel_map_img,
98        show_voxel_map_box,
99        show_voxel_map_text,
100        show_difficulty_img,
101        show_difficulty_box,
102        show_difficulty_text,
103        recenter_button,
104        drag_txt,
105        drag_ico,
106        zoom_txt,
107        zoom_ico,
108        waypoint_binding_txt,
109        waypoint_txt,
110        map_mode_btn,
111        map_mode_overlay,
112        minimap_mode_btn,
113        minimap_mode_overlay,
114
115    }
116}
117
118const SHOW_ECONOMY: bool = false; // turn this display off (for 0.9) until we have an improved look
119
120pub struct ExtraMarker {
121    pub recv_pos: Vec2<f32>,
122    pub marker: Marker,
123}
124
125#[derive(WidgetCommon)]
126pub struct Map<'a> {
127    client: &'a Client,
128    world_map: &'a (Vec<img_ids::Rotations>, Vec2<u32>),
129    imgs: &'a Imgs,
130    fonts: &'a Fonts,
131    #[conrod(common_builder)]
132    common: widget::CommonBuilder,
133    pulse: f32,
134    localized_strings: &'a Localization,
135    global_state: &'a GlobalState,
136    rot_imgs: &'a ImgsRot,
137    tooltip_manager: &'a mut TooltipManager,
138    location_markers: &'a MapMarkers,
139    map_drag: Vec2<f64>,
140    extra_markers: &'a [ExtraMarker],
141}
142impl<'a> Map<'a> {
143    pub fn new(
144        client: &'a Client,
145        imgs: &'a Imgs,
146        rot_imgs: &'a ImgsRot,
147        world_map: &'a (Vec<img_ids::Rotations>, Vec2<u32>),
148        fonts: &'a Fonts,
149        pulse: f32,
150        localized_strings: &'a Localization,
151        global_state: &'a GlobalState,
152        tooltip_manager: &'a mut TooltipManager,
153        location_markers: &'a MapMarkers,
154        map_drag: Vec2<f64>,
155        extra_markers: &'a [ExtraMarker],
156    ) -> Self {
157        Self {
158            imgs,
159            rot_imgs,
160            world_map,
161            client,
162            fonts,
163            common: widget::CommonBuilder::default(),
164            pulse,
165            localized_strings,
166            global_state,
167            tooltip_manager,
168            location_markers,
169            map_drag,
170            extra_markers,
171        }
172    }
173}
174
175pub struct State {
176    ids: Ids,
177}
178
179pub enum Event {
180    SettingsChange(InterfaceChange),
181    Close,
182    RequestSiteInfo(SiteId),
183    SetLocationMarker(Vec2<i32>),
184    MapDrag(Vec2<f64>),
185    RemoveMarker,
186}
187
188fn get_site_economy(site: &SiteMarker) -> String {
189    if SHOW_ECONOMY {
190        if let Some(economy) = &site.economy {
191            use common::trade::Good::{Armor, Coin, Food, Ingredients, Potions, Tools};
192            let mut result = format!("\n\nPopulation {:?}", economy.population);
193            result += "\nStock";
194            for i in [Food, Potions, Ingredients, Coin, Tools, Armor].iter() {
195                result += &format!("\n  {:?}={:.3}", *i, *economy.stock.get(i).unwrap_or(&0.0));
196            }
197            result += "\nPrice";
198            for i in [Food, Potions, Ingredients, Coin, Tools, Armor].iter() {
199                result += &format!("\n  {:?}={:.3}", *i, *economy.values.get(i).unwrap_or(&0.0));
200            }
201
202            let mut trade_sorted: Vec<(&Good, &f32)> = economy.last_exports.iter().collect();
203            trade_sorted.sort_unstable_by(|a, b| a.1.partial_cmp(b.1).unwrap());
204            if !trade_sorted.is_empty() {
205                result += &format!("\nTrade {:.1} ", *(trade_sorted.first().unwrap().1));
206                for i in trade_sorted.iter().filter(|x| *x.1 != 0.0) {
207                    result += &format!("{:?} ", i.0);
208                }
209                result += &format!("{:.3}", *(trade_sorted.last().unwrap().1));
210            }
211            result
212        } else {
213            format!("\nloading economy for\n{:?}", site.marker.site)
214        }
215    } else {
216        "".into()
217    }
218}
219
220impl From<&KeyMouse> for ConrodMouseButton {
221    fn from(key: &KeyMouse) -> Self {
222        match key {
223            KeyMouse::Mouse(MouseButton::Left) => ConrodMouseButton::Left,
224            KeyMouse::Mouse(MouseButton::Right) => ConrodMouseButton::Right,
225            KeyMouse::Mouse(MouseButton::Middle) => ConrodMouseButton::Middle,
226            KeyMouse::Mouse(MouseButton::Other(0)) => ConrodMouseButton::X1,
227            KeyMouse::Mouse(MouseButton::Other(1)) => ConrodMouseButton::X2,
228            KeyMouse::Mouse(MouseButton::Other(2)) => ConrodMouseButton::Button6,
229            KeyMouse::Mouse(MouseButton::Other(3)) => ConrodMouseButton::Button7,
230            KeyMouse::Mouse(MouseButton::Other(4)) => ConrodMouseButton::Button8,
231            _ => conrod_core::input::MouseButton::Unknown,
232        }
233    }
234}
235
236impl Widget for Map<'_> {
237    type Event = Vec<Event>;
238    type State = State;
239    type Style = ();
240
241    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
242        State {
243            ids: Ids::new(id_gen),
244        }
245    }
246
247    fn style(&self) -> Self::Style {}
248
249    #[expect(clippy::neg_multiply)]
250    fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
251        common_base::prof_span!("Map::update");
252        let widget::UpdateArgs { state, ui, .. } = args;
253        let colored_player_marker = self
254            .global_state
255            .settings
256            .interface
257            .minimap_colored_player_marker;
258        let zoom = self.global_state.settings.interface.map_zoom;
259        let show_difficulty = self.global_state.settings.interface.map_show_difficulty;
260        let show_towns = self.global_state.settings.interface.map_show_towns;
261        let show_dungeons = self.global_state.settings.interface.map_show_dungeons;
262        let show_castles = self.global_state.settings.interface.map_show_castles;
263        let show_bridges = self.global_state.settings.interface.map_show_bridges;
264        let show_caves = self.global_state.settings.interface.map_show_caves;
265        let show_trees = self.global_state.settings.interface.map_show_trees;
266        let show_peaks = self.global_state.settings.interface.map_show_peaks;
267        let show_biomes = self.global_state.settings.interface.map_show_biomes;
268        let show_glider_courses = self.global_state.settings.interface.map_show_glider_courses;
269        let show_quests = self.global_state.settings.interface.map_show_quests;
270        let show_voxel_map = self.global_state.settings.interface.map_show_voxel_map;
271        let show_topo_map = self.global_state.settings.interface.map_show_topo_map;
272        let location_marker_binding = self
273            .global_state
274            .settings
275            .controls
276            .keybindings
277            .get(&GameInput::MapSetMarker)
278            .cloned()
279            .flatten()
280            .unwrap_or(KeyMouse::Mouse(MouseButton::Middle));
281        let mut events = Vec::new();
282        let i18n = &self.localized_strings;
283        // Tooltips
284        let site_tooltip = Tooltip::new({
285            // Edge images [t, b, r, l]
286            // Corner images [tr, tl, br, bl]
287            let edge = &self.rot_imgs.tt_side;
288            let corner = &self.rot_imgs.tt_corner;
289            ImageFrame::new(
290                [edge.cw180, edge.none, edge.cw270, edge.cw90],
291                [corner.none, corner.cw270, corner.cw90, corner.cw180],
292                Color::Rgba(0.08, 0.07, 0.04, 1.0),
293                5.0,
294            )
295        })
296        .title_font_size(self.fonts.cyri.scale(15))
297        .parent(ui.window)
298        .desc_font_size(self.fonts.cyri.scale(12))
299        .font_id(self.fonts.cyri.conrod_id)
300        .desc_text_color(TEXT_COLOR);
301        // Frame
302        Image::new(self.imgs.map_bg)
303            .w_h(1202.0, 886.0)
304            .mid_top_with_margin_on(ui.window, 5.0)
305            .color(Some(UI_MAIN))
306            .set(state.ids.bg, ui);
307
308        Image::new(self.imgs.map_frame)
309            .w_h(1202.0, 886.0)
310            .middle_of(state.ids.bg)
311            .color(Some(UI_HIGHLIGHT_0))
312            .set(state.ids.frame, ui);
313
314        // Map Content Alignment
315        Rectangle::fill_with([814.0, 834.0], color::TRANSPARENT)
316            .top_left_with_margins_on(state.ids.frame, 46.0, 240.0)
317            .set(state.ids.map_align, ui);
318
319        // Questlog Content Alignment
320        Rectangle::fill_with([232.0, 814.0], color::TRANSPARENT)
321            .top_left_with_margins_on(state.ids.frame, 44.0, 2.0)
322            .set(state.ids.qlog_align, ui);
323
324        // Icon
325        Image::new(self.imgs.map_icon)
326            .w_h(30.0, 30.0)
327            .top_left_with_margins_on(state.ids.frame, 6.0, 8.0)
328            .set(state.ids.icon, ui);
329
330        // Map Title
331        Text::new(&i18n.get_msg("hud-map-map_title"))
332            .mid_top_with_margin_on(state.ids.frame, 3.0)
333            .font_id(self.fonts.cyri.conrod_id)
334            .font_size(self.fonts.cyri.scale(29))
335            .color(TEXT_COLOR)
336            .set(state.ids.map_title, ui);
337
338        // Questlog Title
339        Text::new(&i18n.get_msg("hud-map-qlog_title"))
340            .mid_top_with_margin_on(state.ids.qlog_align, 6.0)
341            .font_id(self.fonts.cyri.conrod_id)
342            .font_size(self.fonts.cyri.scale(21))
343            .color(TEXT_COLOR)
344            .set(state.ids.qlog_title, ui);
345
346        // Location Name
347        /*match self.client.current_chunk() {
348            Some(chunk) => Text::new(chunk.meta().name())
349                .mid_top_with_margin_on(state.ids.bg, 55.0)
350                .font_size(self.fonts.alkhemi.scale(60))
351                .color(TEXT_COLOR)
352                .font_id(self.fonts.alkhemi.conrod_id)
353                .parent(state.ids.frame)
354                .set(state.ids.location_name, ui),
355            None => Text::new(" ")
356                .mid_top_with_margin_on(state.ids.bg, 3.0)
357                .font_size(self.fonts.alkhemi.scale(40))
358                .font_id(self.fonts.alkhemi.conrod_id)
359                .color(TEXT_COLOR)
360                .set(state.ids.location_name, ui),
361        }*/
362        // Map Layers
363        // It is assumed that there is at least one layer
364        if state.ids.map_layers.len() < self.world_map.0.len() {
365            state.update(|state| {
366                state
367                    .ids
368                    .map_layers
369                    .resize(self.world_map.0.len(), &mut ui.widget_id_generator())
370            });
371        }
372
373        // Map Size
374        let worldsize = self.world_map.1;
375
376        // Coordinates
377        let player_pos = self
378            .client
379            .state()
380            .ecs()
381            .read_storage::<comp::Pos>()
382            .get(self.client.entity())
383            .map_or(Vec3::zero(), |pos| pos.0);
384
385        let map_size = Vec2::new(760.0, 760.0);
386
387        let player_pos_chunks =
388            player_pos.xy().map(|x| x as f64) / TerrainChunkSize::RECT_SIZE.map(|x| x as f64);
389        let min_drag = player_pos_chunks - worldsize.map(|x| x as f64);
390        let max_drag = player_pos_chunks;
391        let drag = self.map_drag.clamped(min_drag, max_drag);
392
393        enum MarkerChange {
394            Pos(Vec2<f32>),
395            ClickPos,
396            Remove,
397        }
398
399        let handle_widget_mouse_events =
400            |widget, marker: MarkerChange, ui: &mut UiCell, events: &mut Vec<Event>, map_widget| {
401                // Handle Location Marking
402                if let Some(click) = ui
403                    .widget_input(widget)
404                    .clicks()
405                    .button(ConrodMouseButton::from(&location_marker_binding))
406                    .next()
407                {
408                    match marker {
409                        MarkerChange::Pos(ref wpos) => {
410                            events.push(Event::SetLocationMarker(wpos.as_()))
411                        },
412                        MarkerChange::ClickPos => {
413                            let tmp: Vec2<f64> = Vec2::<f64>::from(click.xy) / zoom - drag;
414                            let wpos = tmp.as_::<f32>().cpos_to_wpos() + player_pos;
415                            events.push(Event::SetLocationMarker(wpos.as_()));
416                        },
417                        MarkerChange::Remove => events.push(Event::RemoveMarker),
418                    }
419                }
420
421                // Handle zooming with the mouse wheel
422                let scrolled: f64 = ui
423                    .widget_input(widget)
424                    .scrolls()
425                    .map(|scroll| scroll.y)
426                    .sum();
427                if scrolled != 0.0 {
428                    let min_zoom = map_size.x / worldsize.reduce_partial_max() as f64 / 2.0;
429                    let new_zoom_lvl: f64 = (f64::log2(zoom) - scrolled * 0.03)
430                        .exp2()
431                        .clamp(min_zoom, 16.0);
432                    events.push(Event::SettingsChange(MapZoom(new_zoom_lvl)));
433                    let cursor_mouse_pos = ui
434                        .widget_input(map_widget)
435                        .mouse()
436                        .map(|mouse| mouse.rel_xy());
437                    if let Some(cursor_pos) = cursor_mouse_pos {
438                        let mouse_pos = Vec2::from_slice(&cursor_pos);
439                        let drag_new = drag + mouse_pos * (1.0 / new_zoom_lvl - 1.0 / zoom);
440                        if drag_new != drag {
441                            events.push(Event::MapDrag(drag_new));
442                        }
443                    }
444                }
445
446                // Handle dragging
447                let dragged: Vec2<f64> = ui
448                    .widget_input(widget)
449                    .drags()
450                    .left()
451                    .map(|drag| Vec2::<f64>::from(drag.delta_xy))
452                    .sum();
453                // Drag represents offset of view from the player_pos in chunk coords
454                let drag_new = drag + dragged / zoom;
455                if drag_new != drag {
456                    events.push(Event::MapDrag(drag_new));
457                }
458            };
459
460        handle_widget_mouse_events(
461            state.ids.map_layers[0],
462            MarkerChange::ClickPos,
463            ui,
464            &mut events,
465            state.ids.map_layers[0],
466        );
467
468        let rect_src = position::Rect::from_xy_dim(
469            [
470                (player_pos.x as f64 / TerrainChunkSize::RECT_SIZE.x as f64) - drag.x,
471                (worldsize.y as f64 - (player_pos.y as f64 / TerrainChunkSize::RECT_SIZE.y as f64))
472                    + drag.y,
473            ],
474            [map_size.x / zoom, map_size.y / zoom],
475        );
476
477        // X-Button
478        if Button::image(self.imgs.close_button)
479            .w_h(24.0, 25.0)
480            .hover_image(self.imgs.close_btn_hover)
481            .press_image(self.imgs.close_btn_press)
482            .top_right_with_margins_on(state.ids.frame, 0.0, 0.0)
483            .set(state.ids.close, ui)
484            .was_clicked()
485        {
486            events.push(Event::Close);
487        }
488
489        // Map Layer Images
490        for (index, layer) in self.world_map.0.iter().enumerate() {
491            if index == 0 {
492                Button::image(layer.none)
493                    .mid_top_with_margin_on(state.ids.map_align, 10.0)
494                    .w_h(map_size.x, map_size.y)
495                    .parent(state.ids.bg)
496                    .source_rectangle(rect_src)
497                    .set(state.ids.map_layers[index], ui);
498            } else if show_topo_map {
499                Button::image(layer.none)
500                    .mid_top_with_margin_on(state.ids.map_align, 10.0)
501                    .w_h(map_size.x, map_size.y)
502                    .parent(state.ids.bg)
503                    .source_rectangle(rect_src)
504                    .graphics_for(state.ids.map_layers[0])
505                    .set(state.ids.map_layers[index], ui);
506            }
507        }
508
509        // Icon settings
510        // Alignment
511        Rectangle::fill_with([150.0, 200.0], color::TRANSPARENT)
512            .top_right_with_margins_on(state.ids.frame, 55.0, 10.0)
513            .set(state.ids.map_settings_align, ui);
514        // Checkboxes
515        // Show difficulties
516        Image::new(self.imgs.map_dif_icon)
517            .top_left_with_margins_on(state.ids.map_settings_align, 5.0, 5.0)
518            .w_h(20.0, 20.0)
519            .set(state.ids.show_difficulty_img, ui);
520        if Button::image(if show_difficulty {
521            self.imgs.checkbox_checked
522        } else {
523            self.imgs.checkbox
524        })
525        .w_h(18.0, 18.0)
526        .hover_image(if show_difficulty {
527            self.imgs.checkbox_checked_mo
528        } else {
529            self.imgs.checkbox_mo
530        })
531        .press_image(if show_difficulty {
532            self.imgs.checkbox_checked
533        } else {
534            self.imgs.checkbox_press
535        })
536        .right_from(state.ids.show_difficulty_img, 10.0)
537        .set(state.ids.show_difficulty_box, ui)
538        .was_clicked()
539        {
540            events.push(Event::SettingsChange(MapShowDifficulty(!show_difficulty)));
541        }
542        Text::new(&i18n.get_msg("hud-map-difficulty"))
543            .right_from(state.ids.show_difficulty_box, 10.0)
544            .font_size(self.fonts.cyri.scale(14))
545            .font_id(self.fonts.cyri.conrod_id)
546            .graphics_for(state.ids.show_difficulty_box)
547            .color(TEXT_COLOR)
548            .set(state.ids.show_difficulty_text, ui);
549        // Towns
550        Image::new(self.imgs.mmap_site_town)
551            .down_from(state.ids.show_difficulty_img, 10.0)
552            .w_h(20.0, 20.0)
553            .set(state.ids.show_towns_img, ui);
554        if Button::image(if show_towns {
555            self.imgs.checkbox_checked
556        } else {
557            self.imgs.checkbox
558        })
559        .w_h(18.0, 18.0)
560        .hover_image(if show_towns {
561            self.imgs.checkbox_checked_mo
562        } else {
563            self.imgs.checkbox_mo
564        })
565        .press_image(if show_towns {
566            self.imgs.checkbox_checked
567        } else {
568            self.imgs.checkbox_press
569        })
570        .right_from(state.ids.show_towns_img, 10.0)
571        .set(state.ids.show_towns_box, ui)
572        .was_clicked()
573        {
574            events.push(Event::SettingsChange(MapShowTowns(!show_towns)));
575        }
576        Text::new(&i18n.get_msg("hud-map-towns"))
577            .right_from(state.ids.show_towns_box, 10.0)
578            .font_size(self.fonts.cyri.scale(14))
579            .font_id(self.fonts.cyri.conrod_id)
580            .graphics_for(state.ids.show_towns_box)
581            .color(TEXT_COLOR)
582            .set(state.ids.show_towns_text, ui);
583        // Castles
584        Image::new(self.imgs.mmap_site_castle)
585            .down_from(state.ids.show_towns_img, 10.0)
586            .w_h(20.0, 20.0)
587            .set(state.ids.show_castles_img, ui);
588        if Button::image(if show_castles {
589            self.imgs.checkbox_checked
590        } else {
591            self.imgs.checkbox
592        })
593        .w_h(18.0, 18.0)
594        .hover_image(if show_castles {
595            self.imgs.checkbox_checked_mo
596        } else {
597            self.imgs.checkbox_mo
598        })
599        .press_image(if show_castles {
600            self.imgs.checkbox_checked
601        } else {
602            self.imgs.checkbox_press
603        })
604        .right_from(state.ids.show_castles_img, 10.0)
605        .set(state.ids.show_castles_box, ui)
606        .was_clicked()
607        {
608            events.push(Event::SettingsChange(MapShowCastles(!show_castles)));
609        }
610        Text::new(&i18n.get_msg("hud-map-castles"))
611            .right_from(state.ids.show_castles_box, 10.0)
612            .font_size(self.fonts.cyri.scale(14))
613            .font_id(self.fonts.cyri.conrod_id)
614            .graphics_for(state.ids.show_castles_box)
615            .color(TEXT_COLOR)
616            .set(state.ids.show_castles_text, ui);
617        // Bridges
618        Image::new(self.imgs.mmap_site_bridge)
619            .down_from(state.ids.show_castles_img, 10.0)
620            .w_h(20.0, 20.0)
621            .set(state.ids.show_bridges_img, ui);
622        if Button::image(if show_bridges {
623            self.imgs.checkbox_checked
624        } else {
625            self.imgs.checkbox
626        })
627        .w_h(18.0, 18.0)
628        .hover_image(if show_bridges {
629            self.imgs.checkbox_checked_mo
630        } else {
631            self.imgs.checkbox_mo
632        })
633        .press_image(if show_bridges {
634            self.imgs.checkbox_checked
635        } else {
636            self.imgs.checkbox_press
637        })
638        .right_from(state.ids.show_bridges_img, 10.0)
639        .set(state.ids.show_bridges_box, ui)
640        .was_clicked()
641        {
642            events.push(Event::SettingsChange(MapShowBridges(!show_bridges)));
643        }
644        Text::new(&i18n.get_msg("hud-map-bridges"))
645            .right_from(state.ids.show_bridges_box, 10.0)
646            .font_size(self.fonts.cyri.scale(14))
647            .font_id(self.fonts.cyri.conrod_id)
648            .graphics_for(state.ids.show_bridges_box)
649            .color(TEXT_COLOR)
650            .set(state.ids.show_bridges_text, ui);
651        // Dungeons
652        Image::new(self.imgs.mmap_site_dungeon)
653            .down_from(state.ids.show_bridges_img, 10.0)
654            .w_h(20.0, 20.0)
655            .set(state.ids.show_dungeons_img, ui);
656        if Button::image(if show_dungeons {
657            self.imgs.checkbox_checked
658        } else {
659            self.imgs.checkbox
660        })
661        .w_h(18.0, 18.0)
662        .hover_image(if show_dungeons {
663            self.imgs.checkbox_checked_mo
664        } else {
665            self.imgs.checkbox_mo
666        })
667        .press_image(if show_dungeons {
668            self.imgs.checkbox_checked
669        } else {
670            self.imgs.checkbox_press
671        })
672        .right_from(state.ids.show_dungeons_img, 10.0)
673        .set(state.ids.show_dungeons_box, ui)
674        .was_clicked()
675        {
676            events.push(Event::SettingsChange(MapShowDungeons(!show_dungeons)));
677        }
678        Text::new(&i18n.get_msg("hud-map-dungeons"))
679            .right_from(state.ids.show_dungeons_box, 10.0)
680            .font_size(self.fonts.cyri.scale(14))
681            .font_id(self.fonts.cyri.conrod_id)
682            .graphics_for(state.ids.show_dungeons_box)
683            .color(TEXT_COLOR)
684            .set(state.ids.show_dungeons_text, ui);
685        // Caves
686        Image::new(self.imgs.mmap_site_cave)
687            .down_from(state.ids.show_dungeons_img, 10.0)
688            .w_h(20.0, 20.0)
689            .set(state.ids.show_caves_img, ui);
690        if Button::image(if show_caves {
691            self.imgs.checkbox_checked
692        } else {
693            self.imgs.checkbox
694        })
695        .w_h(18.0, 18.0)
696        .hover_image(if show_caves {
697            self.imgs.checkbox_checked_mo
698        } else {
699            self.imgs.checkbox_mo
700        })
701        .press_image(if show_caves {
702            self.imgs.checkbox_checked
703        } else {
704            self.imgs.checkbox_press
705        })
706        .right_from(state.ids.show_caves_img, 10.0)
707        .set(state.ids.show_caves_box, ui)
708        .was_clicked()
709        {
710            events.push(Event::SettingsChange(MapShowCaves(!show_caves)));
711        }
712        Text::new(&i18n.get_msg("hud-map-caves"))
713            .right_from(state.ids.show_caves_box, 10.0)
714            .font_size(self.fonts.cyri.scale(14))
715            .font_id(self.fonts.cyri.conrod_id)
716            .graphics_for(state.ids.show_caves_box)
717            .color(TEXT_COLOR)
718            .set(state.ids.show_caves_text, ui);
719        // Trees
720        Image::new(self.imgs.mmap_site_tree)
721            .down_from(state.ids.show_caves_img, 10.0)
722            .w_h(20.0, 20.0)
723            .set(state.ids.show_trees_img, ui);
724        if Button::image(if show_trees {
725            self.imgs.checkbox_checked
726        } else {
727            self.imgs.checkbox
728        })
729        .w_h(18.0, 18.0)
730        .hover_image(if show_trees {
731            self.imgs.checkbox_checked_mo
732        } else {
733            self.imgs.checkbox_mo
734        })
735        .press_image(if show_trees {
736            self.imgs.checkbox_checked
737        } else {
738            self.imgs.checkbox_press
739        })
740        .right_from(state.ids.show_trees_img, 10.0)
741        .set(state.ids.show_trees_box, ui)
742        .was_clicked()
743        {
744            events.push(Event::SettingsChange(MapShowTrees(!show_trees)));
745        }
746        Text::new(&i18n.get_msg("hud-map-trees"))
747            .right_from(state.ids.show_trees_box, 10.0)
748            .font_size(self.fonts.cyri.scale(14))
749            .font_id(self.fonts.cyri.conrod_id)
750            .graphics_for(state.ids.show_trees_box)
751            .color(TEXT_COLOR)
752            .set(state.ids.show_trees_text, ui);
753        // Biomes
754        Image::new(self.imgs.mmap_poi_biome)
755            .down_from(state.ids.show_trees_img, 10.0)
756            .w_h(20.0, 20.0)
757            .set(state.ids.show_biomes_img, ui);
758        if Button::image(if show_biomes {
759            self.imgs.checkbox_checked
760        } else {
761            self.imgs.checkbox
762        })
763        .w_h(18.0, 18.0)
764        .hover_image(if show_biomes {
765            self.imgs.checkbox_checked_mo
766        } else {
767            self.imgs.checkbox_mo
768        })
769        .press_image(if show_biomes {
770            self.imgs.checkbox_checked
771        } else {
772            self.imgs.checkbox_press
773        })
774        .right_from(state.ids.show_biomes_img, 10.0)
775        .set(state.ids.show_biomes_box, ui)
776        .was_clicked()
777        {
778            events.push(Event::SettingsChange(MapShowBiomes(!show_biomes)));
779        }
780        Text::new(&i18n.get_msg("hud-map-biomes"))
781            .right_from(state.ids.show_biomes_box, 10.0)
782            .font_size(self.fonts.cyri.scale(14))
783            .font_id(self.fonts.cyri.conrod_id)
784            .graphics_for(state.ids.show_biomes_box)
785            .color(TEXT_COLOR)
786            .set(state.ids.show_biomes_text, ui);
787        // Peaks
788        Image::new(self.imgs.mmap_poi_peak)
789            .down_from(state.ids.show_biomes_img, 10.0)
790            .w_h(20.0, 20.0)
791            .set(state.ids.show_peaks_img, ui);
792        if Button::image(if show_peaks {
793            self.imgs.checkbox_checked
794        } else {
795            self.imgs.checkbox
796        })
797        .w_h(18.0, 18.0)
798        .hover_image(if show_peaks {
799            self.imgs.checkbox_checked_mo
800        } else {
801            self.imgs.checkbox_mo
802        })
803        .press_image(if show_peaks {
804            self.imgs.checkbox_checked
805        } else {
806            self.imgs.checkbox_press
807        })
808        .right_from(state.ids.show_peaks_img, 10.0)
809        .set(state.ids.show_peaks_box, ui)
810        .was_clicked()
811        {
812            events.push(Event::SettingsChange(MapShowPeaks(!show_peaks)));
813        }
814        Text::new(&i18n.get_msg("hud-map-peaks"))
815            .right_from(state.ids.show_peaks_box, 10.0)
816            .font_size(self.fonts.cyri.scale(14))
817            .font_id(self.fonts.cyri.conrod_id)
818            .graphics_for(state.ids.show_peaks_box)
819            .color(TEXT_COLOR)
820            .set(state.ids.show_peaks_text, ui);
821        Text::new(&i18n.get_msg("hud-map-peaks"))
822            .right_from(state.ids.show_peaks_box, 10.0)
823            .font_size(self.fonts.cyri.scale(14))
824            .font_id(self.fonts.cyri.conrod_id)
825            .graphics_for(state.ids.show_peaks_box)
826            .color(TEXT_COLOR)
827            .set(state.ids.show_peaks_text, ui);
828        // Glider Courses
829        Image::new(self.imgs.mmap_site_glider_course)
830            .down_from(state.ids.show_peaks_img, 10.0)
831            .w_h(20.0, 20.0)
832            .set(state.ids.show_glider_courses_img, ui);
833        if Button::image(if show_glider_courses {
834            self.imgs.checkbox_checked
835        } else {
836            self.imgs.checkbox
837        })
838        .w_h(18.0, 18.0)
839        .hover_image(if show_glider_courses {
840            self.imgs.checkbox_checked_mo
841        } else {
842            self.imgs.checkbox_mo
843        })
844        .press_image(if show_glider_courses {
845            self.imgs.checkbox_checked
846        } else {
847            self.imgs.checkbox_press
848        })
849        .right_from(state.ids.show_glider_courses_img, 10.0)
850        .set(state.ids.show_glider_courses_box, ui)
851        .was_clicked()
852        {
853            events.push(Event::SettingsChange(MapShowGliderCourses(
854                !show_glider_courses,
855            )));
856        }
857        Text::new(&i18n.get_msg("hud-map-glider_courses"))
858            .right_from(state.ids.show_glider_courses_box, 10.0)
859            .font_size(self.fonts.cyri.scale(14))
860            .font_id(self.fonts.cyri.conrod_id)
861            .graphics_for(state.ids.show_glider_courses_box)
862            .color(TEXT_COLOR)
863            .set(state.ids.show_glider_courses_text, ui);
864        // Quests
865        Image::new(self.imgs.mmap_unknown)
866            .down_from(state.ids.show_glider_courses_img, 10.0)
867            .w_h(20.0, 20.0)
868            .set(state.ids.show_quests_img, ui);
869        if Button::image(if show_quests {
870            self.imgs.checkbox_checked
871        } else {
872            self.imgs.checkbox
873        })
874        .w_h(18.0, 18.0)
875        .hover_image(if show_quests {
876            self.imgs.checkbox_checked_mo
877        } else {
878            self.imgs.checkbox_mo
879        })
880        .press_image(if show_quests {
881            self.imgs.checkbox_checked
882        } else {
883            self.imgs.checkbox_press
884        })
885        .right_from(state.ids.show_quests_img, 10.0)
886        .set(state.ids.show_quests_box, ui)
887        .was_clicked()
888        {
889            events.push(Event::SettingsChange(MapShowQuests(!show_quests)));
890        }
891        Text::new(&i18n.get_msg("hud-map-quests"))
892            .right_from(state.ids.show_quests_box, 10.0)
893            .font_size(self.fonts.cyri.scale(14))
894            .font_id(self.fonts.cyri.conrod_id)
895            .graphics_for(state.ids.show_quests_box)
896            .color(TEXT_COLOR)
897            .set(state.ids.show_quests_text, ui);
898
899        const EXPOSE_VOXEL_MAP_TOGGLE_IN_UI: bool = false;
900        if EXPOSE_VOXEL_MAP_TOGGLE_IN_UI {
901            Image::new(self.imgs.mmap_poi_peak)
902                .down_from(state.ids.show_peaks_img, 10.0)
903                .w_h(20.0, 20.0)
904                .set(state.ids.show_voxel_map_img, ui);
905            if Button::image(if show_voxel_map {
906                self.imgs.checkbox_checked
907            } else {
908                self.imgs.checkbox
909            })
910            .w_h(18.0, 18.0)
911            .hover_image(if show_voxel_map {
912                self.imgs.checkbox_checked_mo
913            } else {
914                self.imgs.checkbox_mo
915            })
916            .press_image(if show_voxel_map {
917                self.imgs.checkbox_checked
918            } else {
919                self.imgs.checkbox_press
920            })
921            .right_from(state.ids.show_voxel_map_img, 10.0)
922            .set(state.ids.show_voxel_map_box, ui)
923            .was_clicked()
924            {
925                events.push(Event::SettingsChange(MapShowVoxelMap(!show_voxel_map)));
926            }
927            Text::new(&i18n.get_msg("hud-map-voxel_map"))
928                .right_from(state.ids.show_voxel_map_box, 10.0)
929                .font_size(self.fonts.cyri.scale(14))
930                .font_id(self.fonts.cyri.conrod_id)
931                .graphics_for(state.ids.show_voxel_map_box)
932                .color(TEXT_COLOR)
933                .set(state.ids.show_voxel_map_text, ui);
934        }
935        // Map icons
936        if state.ids.mmap_poi_icons.len() < self.client.pois().len() {
937            state.update(|state| {
938                state
939                    .ids
940                    .mmap_poi_icons
941                    .resize(self.client.pois().len(), &mut ui.widget_id_generator())
942            });
943            state.update(|state| {
944                state
945                    .ids
946                    .mmap_poi_titles
947                    .resize(self.client.pois().len(), &mut ui.widget_id_generator())
948            });
949            state.update(|state| {
950                state
951                    .ids
952                    .mmap_poi_title_bgs
953                    .resize(self.client.pois().len(), &mut ui.widget_id_generator())
954            });
955        }
956
957        let markers = self
958            .client
959            .markers()
960            .chain(self.extra_markers.iter().map(|em| &em.marker))
961            .collect::<Vec<_>>();
962
963        if state.ids.mmap_site_icons.len() < markers.len() {
964            state.update(|state| {
965                state
966                    .ids
967                    .mmap_site_icons
968                    .resize(markers.len(), &mut ui.widget_id_generator())
969            });
970        }
971        if state.ids.site_difs.len() < markers.len() {
972            state.update(|state| {
973                state
974                    .ids
975                    .site_difs
976                    .resize(markers.len(), &mut ui.widget_id_generator())
977            });
978        }
979
980        let wpos_to_rpos_fade =
981            |wpos: Vec2<f32>, bounding_rect_size: Vec2<f32>, fade_start: f32| {
982                // Site pos in world coordinates relative to the player
983                let rwpos = wpos - player_pos;
984                // Convert to chunk coordinates
985                let rcpos = rwpos.wpos_to_cpos()
986                // Add map dragging
987                + drag.map(|e| e as f32);
988                // Convert to relative pixel coordinates from the center of the map
989                // Accounting for zooming
990                let rpos = rcpos.map(|e| e * zoom as f32);
991
992                let dist_to_closest_map_edge =
993                    (rpos.map2(map_size, |e, sz| sz as f32 / 2.0 - e.abs()) - bounding_rect_size)
994                        .reduce_partial_min();
995                match dist_to_closest_map_edge {
996                    x if x <= 0.0 => None,
997                    x if x < fade_start => Some((
998                        rpos,
999                        // Easing function
1000                        1.0 - 2.0_f32.powf(-10.0 * x / fade_start),
1001                    )),
1002                    _ => Some((rpos, 1.0)),
1003                }
1004            };
1005
1006        for (i, marker) in markers.iter().enumerate() {
1007            let rside = zoom as f32 * 8.0 * 1.2;
1008
1009            let (rpos, fade) =
1010                match wpos_to_rpos_fade(marker.wpos, Vec2::from(rside / 2.0), rside / 2.0) {
1011                    Some(rpos) => rpos,
1012                    None => continue,
1013                };
1014
1015            let title = marker
1016                .label
1017                .as_ref()
1018                .map(|name| i18n.get_content(name))
1019                .map(Cow::Owned)
1020                .unwrap_or_else(|| match &marker.kind {
1021                    MarkerKind::Unknown => i18n.get_msg("hud-map-unknown"),
1022                    MarkerKind::Town => i18n.get_msg("hud-map-town"),
1023                    MarkerKind::Castle => i18n.get_msg("hud-map-castle"),
1024                    MarkerKind::Cave => i18n.get_msg("hud-map-cave"),
1025                    MarkerKind::Tree => i18n.get_msg("hud-map-tree"),
1026                    MarkerKind::Gnarling => i18n.get_msg("hud-map-gnarling"),
1027                    MarkerKind::ChapelSite => i18n.get_msg("hud-map-chapel_site"),
1028                    MarkerKind::Terracotta => i18n.get_msg("hud-map-terracotta"),
1029                    MarkerKind::Bridge => i18n.get_msg("hud-map-bridge"),
1030                    MarkerKind::GliderCourse => i18n.get_msg("hud-map-glider_course"),
1031                    MarkerKind::Adlet => i18n.get_msg("hud-map-adlet"),
1032                    MarkerKind::Haniwa => i18n.get_msg("hud-map-haniwa"),
1033                    MarkerKind::Cultist => i18n.get_msg("hud-map-cultist"),
1034                    MarkerKind::Sahagin => i18n.get_msg("hud-map-sahagin"),
1035                    MarkerKind::Myrmidon => i18n.get_msg("hud-map-myrmidon"),
1036                    MarkerKind::DwarvenMine => i18n.get_msg("hud-map-df_mine"),
1037                    MarkerKind::VampireCastle => i18n.get_msg("hud-map-vampire_castle"),
1038                    MarkerKind::Character => i18n.get_msg("hud-map-character"),
1039                });
1040            let (difficulty, desc) = match &marker.kind {
1041                MarkerKind::Unknown => (None, i18n.get_msg("hud-map-unknown")),
1042                MarkerKind::Town => (None, i18n.get_msg("hud-map-town")),
1043                MarkerKind::Castle => (None, i18n.get_msg("hud-map-castle")),
1044                MarkerKind::Cave => (None, i18n.get_msg("hud-map-cave")),
1045                MarkerKind::Tree => (None, i18n.get_msg("hud-map-tree")),
1046                MarkerKind::Gnarling => (Some(0), i18n.get_msg("hud-map-gnarling")),
1047                MarkerKind::Terracotta => (Some(5), i18n.get_msg("hud-map-terracotta")),
1048                MarkerKind::ChapelSite => (Some(4), i18n.get_msg("hud-map-chapel_site")),
1049                MarkerKind::Bridge => (None, i18n.get_msg("hud-map-bridge")),
1050                MarkerKind::GliderCourse => (None, i18n.get_msg("hud-map-glider_course")),
1051                MarkerKind::Adlet => (Some(1), i18n.get_msg("hud-map-adlet")),
1052                MarkerKind::Haniwa => (Some(3), i18n.get_msg("hud-map-haniwa")),
1053                MarkerKind::Cultist => (Some(5), i18n.get_msg("hud-map-cultist")),
1054                MarkerKind::Sahagin => (Some(2), i18n.get_msg("hud-map-sahagin")),
1055                MarkerKind::Myrmidon => (Some(4), i18n.get_msg("hud-map-myrmidon")),
1056                MarkerKind::DwarvenMine => (Some(5), i18n.get_msg("hud-map-df_mine")),
1057                MarkerKind::VampireCastle => (Some(3), i18n.get_msg("hud-map-vampire_castle")),
1058                MarkerKind::Character => (None, i18n.get_msg("hud-map-character")),
1059            };
1060            let desc = if let Some(site_id) = marker.site
1061                && let Some(site) = self.client.sites().get(&site_id)
1062            {
1063                desc.into_owned() + &get_site_economy(site)
1064            } else {
1065                desc.into_owned()
1066            };
1067            let site_btn = Button::image(match &marker.kind {
1068                MarkerKind::Unknown => self.imgs.mmap_unknown,
1069                MarkerKind::Town => self.imgs.mmap_site_town,
1070                MarkerKind::ChapelSite => self.imgs.mmap_site_sea_chapel,
1071                MarkerKind::Terracotta => self.imgs.mmap_site_terracotta,
1072                MarkerKind::Castle => self.imgs.mmap_site_castle,
1073                MarkerKind::Cave => self.imgs.mmap_site_cave,
1074                MarkerKind::Tree => self.imgs.mmap_site_tree,
1075                MarkerKind::Gnarling => self.imgs.mmap_site_gnarling,
1076                MarkerKind::Adlet => self.imgs.mmap_site_adlet,
1077                MarkerKind::Haniwa => self.imgs.mmap_site_haniwa,
1078                MarkerKind::Cultist => self.imgs.mmap_site_cultist,
1079                MarkerKind::Sahagin => self.imgs.mmap_site_sahagin,
1080                MarkerKind::Myrmidon => self.imgs.mmap_site_myrmidon,
1081                MarkerKind::DwarvenMine => self.imgs.mmap_site_mine,
1082                MarkerKind::VampireCastle => self.imgs.mmap_site_vampire_castle,
1083
1084                MarkerKind::Bridge => self.imgs.mmap_site_bridge,
1085                MarkerKind::GliderCourse => self.imgs.mmap_site_glider_course,
1086                MarkerKind::Character => self.imgs.mmap_character,
1087            })
1088            .x_y_position_relative_to(
1089                state.ids.map_layers[0],
1090                position::Relative::Scalar(rpos.x as f64),
1091                position::Relative::Scalar(rpos.y as f64),
1092            )
1093            .w_h(rside as f64, rside as f64)
1094            .hover_image(match &marker.kind {
1095                MarkerKind::Unknown => self.imgs.mmap_unknown_hover,
1096                MarkerKind::Town => self.imgs.mmap_site_town_hover,
1097                MarkerKind::ChapelSite => self.imgs.mmap_site_sea_chapel_hover,
1098                MarkerKind::Terracotta => self.imgs.mmap_site_terracotta_hover,
1099                MarkerKind::Castle => self.imgs.mmap_site_castle_hover,
1100                MarkerKind::Cave => self.imgs.mmap_site_cave_hover,
1101                MarkerKind::Tree => self.imgs.mmap_site_tree_hover,
1102                MarkerKind::Gnarling => self.imgs.mmap_site_gnarling_hover,
1103                MarkerKind::Adlet => self.imgs.mmap_site_adlet_hover,
1104                MarkerKind::Haniwa => self.imgs.mmap_site_haniwa_hover,
1105                MarkerKind::Cultist => self.imgs.mmap_site_cultist_hover,
1106                MarkerKind::Sahagin => self.imgs.mmap_site_sahagin_hover,
1107                MarkerKind::Myrmidon => self.imgs.mmap_site_myrmidon_hover,
1108                MarkerKind::DwarvenMine => self.imgs.mmap_site_mine_hover,
1109                MarkerKind::VampireCastle => self.imgs.mmap_site_vampire_castle_hover,
1110                MarkerKind::Bridge => self.imgs.mmap_site_bridge_hover,
1111                MarkerKind::GliderCourse => self.imgs.mmap_site_glider_course_hover,
1112                MarkerKind::Character => self.imgs.mmap_character_hover,
1113            });
1114            let site_btn = if let Some(color) = marker_color(marker, self.pulse) {
1115                site_btn.image_color(color.alpha(fade))
1116            } else {
1117                site_btn.image_color(UI_HIGHLIGHT_0.alpha(fade))
1118            };
1119            let site_btn = site_btn.with_tooltip(
1120                self.tooltip_manager,
1121                &title,
1122                &desc,
1123                &site_tooltip,
1124                match &marker.kind {
1125                    MarkerKind::Gnarling
1126                    | MarkerKind::ChapelSite
1127                    | MarkerKind::Terracotta
1128                    | MarkerKind::Adlet
1129                    | MarkerKind::VampireCastle
1130                    | MarkerKind::Haniwa
1131                    | MarkerKind::Cultist
1132                    | MarkerKind::Sahagin
1133                    | MarkerKind::Myrmidon
1134                    | MarkerKind::DwarvenMine => match difficulty {
1135                        Some(0) => QUALITY_LOW,
1136                        Some(1) => QUALITY_COMMON,
1137                        Some(2) => QUALITY_MODERATE,
1138                        Some(3) => QUALITY_HIGH,
1139                        Some(4 | 5) => QUALITY_EPIC,
1140                        _ => TEXT_COLOR,
1141                    },
1142                    _ => TEXT_COLOR,
1143                },
1144            );
1145
1146            handle_widget_mouse_events(
1147                state.ids.mmap_site_icons[i],
1148                MarkerChange::Pos(marker.wpos),
1149                ui,
1150                &mut events,
1151                state.ids.map_layers[0],
1152            );
1153
1154            // Only display sites that are toggled on
1155            let show_site = match &marker.kind {
1156                _ if marker.flags.contains(MarkerFlags::IS_QUEST) => show_quests,
1157                MarkerKind::Unknown => true,
1158                MarkerKind::Town => show_towns,
1159                MarkerKind::Gnarling
1160                | MarkerKind::ChapelSite
1161                | MarkerKind::DwarvenMine
1162                | MarkerKind::Haniwa
1163                | MarkerKind::Cultist
1164                | MarkerKind::Sahagin
1165                | MarkerKind::Myrmidon
1166                | MarkerKind::Terracotta
1167                | MarkerKind::Adlet
1168                | MarkerKind::VampireCastle => show_dungeons,
1169                MarkerKind::Castle => show_castles,
1170                MarkerKind::Cave => show_caves,
1171                MarkerKind::Tree => show_trees,
1172                MarkerKind::Bridge => show_bridges,
1173                MarkerKind::GliderCourse => show_glider_courses,
1174                MarkerKind::Character => true, // TODO: Toggle for quest markers?
1175            };
1176            if show_site {
1177                let tooltip_visible = site_btn.set_ext(state.ids.mmap_site_icons[i], ui).1;
1178
1179                if SHOW_ECONOMY
1180                    && tooltip_visible
1181                    && let Some(site_id) = marker.site
1182                    && let Some(site) = self.client.sites().get(&site_id)
1183                    && site.economy.is_none()
1184                {
1185                    events.push(Event::RequestSiteInfo(site_id));
1186                }
1187            }
1188
1189            // Difficulty from 0-6
1190            // 0 = towns and places without a difficulty level
1191            if show_difficulty {
1192                let rsize = zoom * 2.4; // Size factor for difficulty indicators
1193                let dif_img = Image::new(match difficulty {
1194                    Some(0) => self.imgs.map_dif_1,
1195                    Some(1) => self.imgs.map_dif_2,
1196                    Some(2) => self.imgs.map_dif_3,
1197                    Some(3) => self.imgs.map_dif_4,
1198                    Some(4 | 5) => self.imgs.map_dif_5,
1199                    Some(_) => self.imgs.map_dif_unknown,
1200                    None => self.imgs.nothing,
1201                })
1202                .mid_top_with_margin_on(state.ids.mmap_site_icons[i], match difficulty {
1203                    Some(0 | 1) => -1.0 * rsize,
1204                    Some(_) => -2.0 * rsize,
1205                    _ => -1.0 * rsize,
1206                })
1207                .w(match difficulty {
1208                    Some(0) => 1.0 * rsize,
1209                    Some(1 | 2) => 2.0 * rsize,
1210                    Some(_) => 3.0 * rsize,
1211                    _ => 1.0 * rsize,
1212                })
1213                .h(match difficulty {
1214                    Some(0 | 1) => 1.0 * rsize,
1215                    Some(_) => 2.0 * rsize,
1216                    _ => 1.0 * rsize,
1217                })
1218                .color(Some(match difficulty {
1219                    Some(0) => QUALITY_LOW,
1220                    Some(1) => QUALITY_COMMON,
1221                    Some(2) => QUALITY_MODERATE,
1222                    Some(3) => QUALITY_HIGH,
1223                    Some(4 | 5) => QUALITY_EPIC, // Change this whenever difficulty is fixed
1224                    _ => TEXT_COLOR,
1225                }));
1226                match &marker.kind {
1227                    MarkerKind::Unknown | MarkerKind::Character => {
1228                        dif_img.set(state.ids.site_difs[i], ui)
1229                    },
1230                    MarkerKind::Town => {
1231                        if show_towns {
1232                            dif_img.set(state.ids.site_difs[i], ui)
1233                        }
1234                    },
1235                    MarkerKind::Gnarling
1236                    | MarkerKind::ChapelSite
1237                    | MarkerKind::Haniwa
1238                    | MarkerKind::Cultist
1239                    | MarkerKind::Sahagin
1240                    | MarkerKind::Myrmidon
1241                    | MarkerKind::Terracotta
1242                    | MarkerKind::Adlet
1243                    | MarkerKind::VampireCastle => {
1244                        if show_dungeons {
1245                            dif_img.set(state.ids.site_difs[i], ui)
1246                        }
1247                    },
1248                    MarkerKind::DwarvenMine => {
1249                        if show_dungeons {
1250                            dif_img.set(state.ids.site_difs[i], ui)
1251                        }
1252                    },
1253                    MarkerKind::Castle => {
1254                        if show_castles {
1255                            dif_img.set(state.ids.site_difs[i], ui)
1256                        }
1257                    },
1258                    MarkerKind::Cave => {
1259                        if show_caves {
1260                            dif_img.set(state.ids.site_difs[i], ui)
1261                        }
1262                    },
1263                    MarkerKind::Tree => {
1264                        if show_trees {
1265                            dif_img.set(state.ids.site_difs[i], ui)
1266                        }
1267                    },
1268                    MarkerKind::Bridge => {
1269                        if show_bridges {
1270                            dif_img.set(state.ids.site_difs[i], ui)
1271                        }
1272                    },
1273                    MarkerKind::GliderCourse => {
1274                        if show_glider_courses {
1275                            dif_img.set(state.ids.site_difs[i], ui)
1276                        }
1277                    },
1278                }
1279
1280                handle_widget_mouse_events(
1281                    state.ids.site_difs[i],
1282                    MarkerChange::Pos(marker.wpos),
1283                    ui,
1284                    &mut events,
1285                    state.ids.map_layers[0],
1286                );
1287            }
1288        }
1289        for (i, poi) in self.client.pois().iter().enumerate() {
1290            // TODO: computation of text size to pass to wpos_to_rpos_fade, so it can
1291            // determine when it's going past the edge of the map screen
1292            let (rpos, fade) = match wpos_to_rpos_fade(
1293                poi.wpos.map(|e| e as f32),
1294                Vec2::from(zoom as f32 * 3.0),
1295                zoom as f32 * 5.0,
1296            ) {
1297                Some(rpos) => rpos,
1298                None => continue,
1299            };
1300            let title = &poi.name;
1301            match poi.kind {
1302                PoiKind::Peak(alt) => {
1303                    let height = format!("{} m", alt);
1304                    if show_peaks && zoom > 2.0 {
1305                        Text::new(title)
1306                            .x_y_position_relative_to(
1307                                state.ids.map_layers[0],
1308                                position::Relative::Scalar(rpos.x as f64),
1309                                position::Relative::Scalar(rpos.y as f64 + zoom * 4.0),
1310                            )
1311                            .font_size(self.fonts.cyri.scale((zoom * 3.0) as u32))
1312                            .font_id(self.fonts.cyri.conrod_id)
1313                            .graphics_for(state.ids.map_layers[0])
1314                            .color(TEXT_BG.alpha(fade))
1315                            .set(state.ids.mmap_poi_title_bgs[i], ui);
1316                        Text::new(title)
1317                                .bottom_left_with_margins_on(state.ids.mmap_poi_title_bgs[i], 1.0, 1.0)
1318                                .font_size(self.fonts.cyri.scale((zoom * 3.0) as u32))
1319                                .font_id(self.fonts.cyri.conrod_id)
1320                                //.graphics_for(state.ids.map_layers[0])
1321                                .color(TEXT_COLOR.alpha(fade))
1322                                .set(state.ids.mmap_poi_titles[i], ui);
1323
1324                        handle_widget_mouse_events(
1325                            state.ids.mmap_poi_titles[i],
1326                            MarkerChange::Pos(poi.wpos.map(|e| e as f32)),
1327                            ui,
1328                            &mut events,
1329                            state.ids.map_layers[0],
1330                        );
1331
1332                        // Show peak altitude
1333                        if ui
1334                            .widget_input(state.ids.mmap_poi_titles[i])
1335                            .mouse()
1336                            .is_some_and(|m| m.is_over())
1337                        {
1338                            Text::new(&height)
1339                                .mid_bottom_with_margin_on(
1340                                    state.ids.mmap_poi_title_bgs[i],
1341                                    zoom * 3.5,
1342                                )
1343                                .font_size(self.fonts.cyri.scale((zoom * 3.0) as u32))
1344                                .font_id(self.fonts.cyri.conrod_id)
1345                                .graphics_for(state.ids.map_layers[0])
1346                                .color(TEXT_BG.alpha(fade))
1347                                .set(state.ids.peaks_txt_bg, ui);
1348                            Text::new(&height)
1349                                .bottom_left_with_margins_on(state.ids.peaks_txt_bg, 1.0, 1.0)
1350                                .font_size(self.fonts.cyri.scale((zoom * 3.0) as u32))
1351                                .font_id(self.fonts.cyri.conrod_id)
1352                                .graphics_for(state.ids.map_layers[0])
1353                                .color(TEXT_COLOR.alpha(fade))
1354                                .set(state.ids.peaks_txt, ui);
1355                        }
1356                    }
1357                },
1358                PoiKind::Lake(size) => {
1359                    if show_biomes && zoom > 2.0 && zoom.powi(2) * size as f64 > 30.0 {
1360                        let font_scale_factor = if size > 20 {
1361                            size as f64 / 25.0
1362                        } else if size > 10 {
1363                            size as f64 / 10.0
1364                        } else if size > 5 {
1365                            size as f64 / 6.0
1366                        } else {
1367                            size as f64 / 2.5
1368                        };
1369                        Text::new(title)
1370                            .x_y_position_relative_to(
1371                                state.ids.map_layers[0],
1372                                position::Relative::Scalar(rpos.x as f64),
1373                                position::Relative::Scalar(rpos.y as f64),
1374                            )
1375                            .font_size(
1376                                self.fonts.cyri.scale(
1377                                    (2.0 + font_scale_factor * zoom).clamp(10.0, 18.0) as u32,
1378                                ),
1379                            )
1380                            .font_id(self.fonts.cyri.conrod_id)
1381                            .graphics_for(state.ids.map_layers[0])
1382                            .color(TEXT_BLUE_COLOR.alpha(fade))
1383                            .set(state.ids.mmap_poi_icons[i], ui);
1384                    }
1385                },
1386            }
1387        }
1388        // Group member indicators
1389        let client_state = self.client.state();
1390        let stats = client_state.ecs().read_storage::<comp::Stats>();
1391        let member_pos = client_state.ecs().read_storage::<comp::Pos>();
1392        let group_members = self
1393            .client
1394            .group_members()
1395            .iter()
1396            .filter_map(|(u, r)| match r {
1397                Role::Member => Some(u),
1398                Role::Pet => None,
1399            })
1400            .collect::<Vec<_>>();
1401        let group_size = group_members.len();
1402        //let in_group = !group_members.is_empty();
1403        let id_maps = client_state
1404            .ecs()
1405            .read_resource::<common_net::sync::IdMaps>();
1406        if state.ids.member_indicators.len() < group_size {
1407            state.update(|s| {
1408                s.ids
1409                    .member_indicators
1410                    .resize(group_size, &mut ui.widget_id_generator())
1411            })
1412        };
1413        for (i, &uid) in group_members.iter().copied().enumerate() {
1414            let entity = id_maps.uid_entity(uid);
1415            let member_pos = entity.and_then(|entity| member_pos.get(entity));
1416            let stats = entity.and_then(|entity| stats.get(entity));
1417            let name = if let Some(stats) = stats {
1418                i18n.get_content(&stats.name)
1419            } else {
1420                "".to_string()
1421            };
1422
1423            if let Some(member_pos) = member_pos {
1424                let factor = 1.2;
1425                let side_length = 20.0 * factor;
1426
1427                let (rpos, fade) = match wpos_to_rpos_fade(
1428                    member_pos.0.xy(),
1429                    Vec2::from(side_length / 2.0),
1430                    side_length / 2.0,
1431                ) {
1432                    Some(x) => x,
1433                    None => continue,
1434                };
1435
1436                let z_comparison = (member_pos.0.z - player_pos.z) as i32;
1437
1438                Button::image(match z_comparison {
1439                    10..=i32::MAX => self.imgs.indicator_group_up,
1440                    i32::MIN..=-10 => self.imgs.indicator_group_down,
1441                    _ => self.imgs.indicator_group,
1442                })
1443                .x_y_position_relative_to(
1444                    state.ids.map_layers[0],
1445                    position::Relative::Scalar(rpos.x as f64),
1446                    position::Relative::Scalar(rpos.y as f64),
1447                )
1448                .w_h(side_length as f64, side_length as f64)
1449                .image_color(Color::Rgba(1.0, 1.0, 1.0, fade))
1450                .floating(true)
1451                .with_tooltip(self.tooltip_manager, &name, "", &site_tooltip, TEXT_COLOR)
1452                .set(state.ids.member_indicators[i], ui);
1453
1454                handle_widget_mouse_events(
1455                    state.ids.member_indicators[i],
1456                    MarkerChange::Pos(member_pos.0.xy()),
1457                    ui,
1458                    &mut events,
1459                    state.ids.map_layers[0],
1460                );
1461            }
1462        }
1463
1464        let factor = 1.4;
1465        let side_length = 20.0 * factor;
1466        // Groups location markers
1467        if state.ids.location_marker_group.len() < self.location_markers.group.len() {
1468            state.update(|s| {
1469                s.ids.location_marker_group.resize(
1470                    self.location_markers.group.len(),
1471                    &mut ui.widget_id_generator(),
1472                )
1473            })
1474        };
1475        for (i, (&uid, &rpos)) in self.location_markers.group.iter().enumerate() {
1476            let lm = rpos.as_();
1477            if let Some((rpos, fade)) =
1478                wpos_to_rpos_fade(lm, Vec2::from(side_length / 2.0), side_length / 2.0)
1479            {
1480                let name = self
1481                    .client
1482                    .player_list()
1483                    .get(&uid)
1484                    .map(|info| info.player_alias.clone())
1485                    .or_else(|| {
1486                        id_maps
1487                            .uid_entity(uid)
1488                            .and_then(|entity| stats.get(entity))
1489                            .map(|stats| i18n.get_content(&stats.name))
1490                    })
1491                    .unwrap_or(String::new());
1492
1493                let image_id = match self.client.group_info().map(|info| info.1) {
1494                    Some(leader) if leader == uid => self.imgs.location_marker_group_leader,
1495                    _ => self.imgs.location_marker_group,
1496                };
1497
1498                Button::image(image_id)
1499                    .x_y_position_relative_to(
1500                        state.ids.map_layers[0],
1501                        position::Relative::Scalar(rpos.x as f64),
1502                        position::Relative::Scalar(rpos.y as f64 + 10.0 * factor as f64),
1503                    )
1504                    .w_h(side_length as f64, side_length as f64)
1505                    .image_color(Color::Rgba(1.0, 1.0, 1.0, fade))
1506                    .floating(true)
1507                    .with_tooltip(
1508                        self.tooltip_manager,
1509                        &i18n.get_msg("hud-map-marked_location"),
1510                        &format!(
1511                            "X: {}, Y: {}\n\n{}",
1512                            lm.x as i32,
1513                            lm.y as i32,
1514                            i18n.get_msg_ctx("hud-map-placed_by", &i18n::fluent_args! {
1515                                "name" => name
1516                            }),
1517                        ),
1518                        &site_tooltip,
1519                        TEXT_VELORITE,
1520                    )
1521                    .set(state.ids.location_marker_group[i], ui);
1522                handle_widget_mouse_events(
1523                    state.ids.location_marker_group[i],
1524                    MarkerChange::Pos(lm),
1525                    ui,
1526                    &mut events,
1527                    state.ids.map_layers[0],
1528                );
1529            }
1530        }
1531        // Location marker
1532        if let Some((lm, (rpos, fade))) = self.location_markers.owned.and_then(|lm| {
1533            let lm = lm.as_();
1534            Some(lm).zip(wpos_to_rpos_fade(
1535                lm,
1536                Vec2::from(side_length / 2.0),
1537                side_length / 2.0,
1538            ))
1539        }) {
1540            if Button::image(self.imgs.location_marker)
1541                .x_y_position_relative_to(
1542                    state.ids.map_layers[0],
1543                    position::Relative::Scalar(rpos.x as f64),
1544                    position::Relative::Scalar(rpos.y as f64 + 10.0 * factor as f64),
1545                )
1546                .w_h(side_length as f64, side_length as f64)
1547                .image_color(Color::Rgba(1.0, 1.0, 1.0, fade))
1548                .floating(true)
1549                .with_tooltip(
1550                    self.tooltip_manager,
1551                    &i18n.get_msg("hud-map-marked_location"),
1552                    &format!(
1553                        "X: {}, Y: {}\n\n{}",
1554                        lm.x as i32,
1555                        lm.y as i32,
1556                        i18n.get_msg("hud-map-marked_location_remove")
1557                    ),
1558                    &site_tooltip,
1559                    TEXT_VELORITE,
1560                )
1561                .set(state.ids.location_marker, ui)
1562                .was_clicked()
1563            {
1564                events.push(Event::RemoveMarker);
1565            }
1566
1567            handle_widget_mouse_events(
1568                state.ids.location_marker,
1569                MarkerChange::Remove,
1570                ui,
1571                &mut events,
1572                state.ids.map_layers[0],
1573            );
1574        }
1575
1576        // Cursor pos relative to playerpos and widget size
1577        // Cursor stops moving on an axis as soon as it's position exceeds the maximum
1578        // // size of the widget
1579
1580        // Don't show if outside or near the edge of the map
1581        let arrow_sz = {
1582            let scale = 0.5;
1583            Vec2::new(36.0, 37.0) * scale
1584        };
1585        // Hide if icon could go off of the edge of the map
1586        if let Some((rpos, fade)) =
1587            wpos_to_rpos_fade(player_pos.xy(), arrow_sz, arrow_sz.reduce_partial_min())
1588        {
1589            let ind = {
1590                if colored_player_marker {
1591                    self.rot_imgs.indicator_mmap_colored.target_north
1592                } else {
1593                    self.rot_imgs.indicator_mmap.target_north
1594                }
1595            };
1596            Image::new(ind)
1597                .x_y_position_relative_to(
1598                    state.ids.map_layers[0],
1599                    position::Relative::Scalar(rpos.x as f64),
1600                    position::Relative::Scalar(rpos.y as f64),
1601                )
1602                .w_h(arrow_sz.x as f64, arrow_sz.y as f64)
1603                .color(Some(UI_HIGHLIGHT_0.alpha(fade)))
1604                .set(state.ids.indicator, ui);
1605
1606            handle_widget_mouse_events(
1607                state.ids.indicator,
1608                MarkerChange::Pos(player_pos.xy()),
1609                ui,
1610                &mut events,
1611                state.ids.map_layers[0],
1612            );
1613        }
1614
1615        // Info about controls
1616        let icon_size = Vec2::new(25.6, 28.8);
1617        let recenter: bool = drag.x != 0.0 || drag.y != 0.0;
1618        if Button::image(self.imgs.button)
1619            .w_h(92.0, icon_size.y)
1620            .mid_bottom_with_margin_on(state.ids.map_layers[0], -36.0)
1621            .hover_image(if recenter {
1622                self.imgs.button_hover
1623            } else {
1624                self.imgs.button
1625            })
1626            .press_image(if recenter {
1627                self.imgs.button_press
1628            } else {
1629                self.imgs.button
1630            })
1631            .label(&i18n.get_msg("hud-map-recenter"))
1632            .label_y(position::Relative::Scalar(1.0))
1633            .label_color(if recenter {
1634                TEXT_COLOR
1635            } else {
1636                TEXT_GRAY_COLOR
1637            })
1638            .image_color(if recenter {
1639                TEXT_COLOR
1640            } else {
1641                TEXT_GRAY_COLOR
1642            })
1643            .label_font_size(self.fonts.cyri.scale(12))
1644            .label_font_id(self.fonts.cyri.conrod_id)
1645            .set(state.ids.recenter_button, ui)
1646            .was_clicked()
1647        {
1648            events.push(Event::MapDrag(Vec2::zero()));
1649        };
1650
1651        Image::new(self.imgs.m_move_ico)
1652            .bottom_left_with_margins_on(state.ids.map_layers[0], -36.0, 0.0)
1653            .w_h(icon_size.x, icon_size.y)
1654            .color(Some(UI_HIGHLIGHT_0))
1655            .set(state.ids.drag_ico, ui);
1656        Text::new(&i18n.get_msg("hud-map-drag"))
1657            .right_from(state.ids.drag_ico, 5.0)
1658            .font_size(self.fonts.cyri.scale(14))
1659            .font_id(self.fonts.cyri.conrod_id)
1660            .graphics_for(state.ids.map_layers[0])
1661            .color(TEXT_COLOR)
1662            .set(state.ids.drag_txt, ui);
1663        Image::new(self.imgs.m_scroll_ico)
1664            .right_from(state.ids.drag_txt, 5.0)
1665            .w_h(icon_size.x, icon_size.y)
1666            .color(Some(UI_HIGHLIGHT_0))
1667            .set(state.ids.zoom_ico, ui);
1668        Text::new(&i18n.get_msg("hud-map-zoom"))
1669            .right_from(state.ids.zoom_ico, 5.0)
1670            .font_size(self.fonts.cyri.scale(14))
1671            .font_id(self.fonts.cyri.conrod_id)
1672            .graphics_for(state.ids.map_layers[0])
1673            .color(TEXT_COLOR)
1674            .set(state.ids.zoom_txt, ui);
1675
1676        Text::new(&location_marker_binding.display_shortest())
1677            .right_from(state.ids.zoom_txt, 15.0)
1678            .font_size(self.fonts.cyri.scale(14))
1679            .font_id(self.fonts.cyri.conrod_id)
1680            .graphics_for(state.ids.map_layers[0])
1681            .color(TEXT_COLOR)
1682            .set(state.ids.waypoint_binding_txt, ui);
1683
1684        Text::new(&i18n.get_msg("hud-map-mid_click"))
1685            .right_from(state.ids.waypoint_binding_txt, 5.0)
1686            .font_size(self.fonts.cyri.scale(14))
1687            .font_id(self.fonts.cyri.conrod_id)
1688            .graphics_for(state.ids.map_layers[0])
1689            .color(TEXT_COLOR)
1690            .set(state.ids.waypoint_txt, ui);
1691
1692        // Show topographic map
1693        if Button::image(self.imgs.button)
1694            .w_h(92.0, icon_size.y)
1695            .hover_image(self.imgs.button_hover)
1696            .press_image(self.imgs.button_press)
1697            .bottom_right_with_margins_on(state.ids.map_layers[0], -36.0, 0.0)
1698            .with_tooltip(
1699                self.tooltip_manager,
1700                &i18n.get_msg("hud-map-change_map_mode"),
1701                "",
1702                &site_tooltip,
1703                TEXT_COLOR,
1704            )
1705            .set(state.ids.map_mode_btn, ui)
1706            .was_clicked()
1707        {
1708            events.push(Event::SettingsChange(MapShowTopoMap(!show_topo_map)));
1709        };
1710        Button::image(self.imgs.map_mode_overlay)
1711            .w_h(92.0, icon_size.y)
1712            .graphics_for(state.ids.map_mode_btn)
1713            .middle_of(state.ids.map_mode_btn)
1714            .set(state.ids.map_mode_overlay, ui);
1715
1716        // Render voxel view on minimap
1717        if Button::image(self.imgs.button)
1718            .w_h(92.0, icon_size.y)
1719            .hover_image(self.imgs.button_hover)
1720            .press_image(self.imgs.button_press)
1721            .left_from(state.ids.map_mode_btn, 5.0)
1722            .with_tooltip(
1723                self.tooltip_manager,
1724                &i18n.get_msg("hud-map-toggle_minimap_voxel"),
1725                &i18n.get_msg("hud-map-zoom_minimap_explanation"),
1726                &site_tooltip,
1727                TEXT_COLOR,
1728            )
1729            .set(state.ids.minimap_mode_btn, ui)
1730            .was_clicked()
1731        {
1732            events.push(Event::SettingsChange(MapShowVoxelMap(!show_voxel_map)));
1733        };
1734        Button::image(self.imgs.minimap_mode_overlay)
1735            .w_h(92.0, icon_size.y)
1736            .graphics_for(state.ids.minimap_mode_btn)
1737            .middle_of(state.ids.minimap_mode_btn)
1738            .set(state.ids.minimap_mode_overlay, ui);
1739
1740        events
1741    }
1742}
1743
1744pub fn marker_color(marker: &Marker, pulse: f32) -> Option<Color> {
1745    if marker.flags.contains(MarkerFlags::IS_QUEST) {
1746        Some(Color::Rgba(
1747            0.3,
1748            1.0,
1749            0.85,
1750            0.75 + (pulse * 4.0).sin() * 0.25,
1751        ))
1752    } else {
1753        None
1754    }
1755}