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