Skip to main content

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