mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
692 lines
27 KiB
Rust
692 lines
27 KiB
Rust
use super::{
|
|
img_ids::{Imgs, ImgsRot},
|
|
Show, QUALITY_COMMON, QUALITY_DEBUG, QUALITY_EPIC, QUALITY_HIGH, QUALITY_LOW, QUALITY_MODERATE,
|
|
TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN,
|
|
};
|
|
use crate::{
|
|
hud::{Graphic, Ui},
|
|
session::settings_change::{Interface as InterfaceChange, Interface::*},
|
|
ui::{fonts::Fonts, img_ids},
|
|
GlobalState,
|
|
};
|
|
use client::{self, Client};
|
|
use common::{
|
|
comp,
|
|
comp::group::Role,
|
|
grid::Grid,
|
|
terrain::TerrainChunkSize,
|
|
vol::{ReadVol, RectVolSize},
|
|
};
|
|
use common_net::msg::world_msg::SiteKind;
|
|
use conrod_core::{
|
|
color, position,
|
|
widget::{self, Button, Image, Rectangle, Text},
|
|
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
|
};
|
|
use hashbrown::HashMap;
|
|
use image::{DynamicImage, RgbaImage};
|
|
use specs::{saveload::MarkerAllocator, WorldExt};
|
|
use std::sync::Arc;
|
|
use vek::*;
|
|
|
|
pub struct VoxelMinimap {
|
|
chunk_minimaps: HashMap<Vec2<i32>, (i32, Vec<Grid<[u8; 4]>>)>,
|
|
composited: RgbaImage,
|
|
image_id: img_ids::Rotations,
|
|
last_pos: Vec3<i32>,
|
|
}
|
|
|
|
const VOXEL_MINIMAP_SIDELENGTH: u32 = 512;
|
|
impl VoxelMinimap {
|
|
pub fn new(ui: &mut Ui) -> Self {
|
|
let mut composited = RgbaImage::new(VOXEL_MINIMAP_SIDELENGTH, VOXEL_MINIMAP_SIDELENGTH);
|
|
for x in 0..VOXEL_MINIMAP_SIDELENGTH {
|
|
for y in 0..VOXEL_MINIMAP_SIDELENGTH {
|
|
composited.put_pixel(x, y, image::Rgba([0, 0, 0, 64]));
|
|
}
|
|
}
|
|
Self {
|
|
chunk_minimaps: HashMap::new(),
|
|
image_id: ui.add_graphic_with_rotations(Graphic::Image(
|
|
Arc::new(DynamicImage::ImageRgba8(composited.clone())),
|
|
Some(Rgba::from([0.0, 0.0, 0.0, 0.0])),
|
|
)),
|
|
composited,
|
|
last_pos: Vec3::zero(),
|
|
}
|
|
}
|
|
|
|
pub fn maintain(&mut self, client: &Client, ui: &mut Ui) {
|
|
let terrain = client.state().terrain();
|
|
for (key, chunk) in terrain.iter() {
|
|
if !self.chunk_minimaps.contains_key(&key) {
|
|
let mut layers = Vec::new();
|
|
for z in chunk.get_min_z()..chunk.get_max_z() {
|
|
let grid = Grid::populate_from(Vec2::new(32, 32), |v| {
|
|
chunk
|
|
.get(Vec3::new(v.x, v.y, z))
|
|
.ok()
|
|
.and_then(|block| block.get_color())
|
|
.map(|rgb| [rgb.r, rgb.g, rgb.b, 192])
|
|
.unwrap_or([0, 0, 0, 0])
|
|
});
|
|
layers.push(grid);
|
|
}
|
|
self.chunk_minimaps.insert(key, (chunk.get_min_z(), layers));
|
|
}
|
|
}
|
|
let player = client.entity();
|
|
if let Some(pos) = client.state().ecs().read_storage::<comp::Pos>().get(player) {
|
|
let pos = pos.0;
|
|
let vpos = pos.xy() - VOXEL_MINIMAP_SIDELENGTH as f32 / 2.0;
|
|
let cpos: Vec2<i32> = vpos.map(|i| (i as i32).div_euclid(32));
|
|
if cpos.distance_squared(self.last_pos.xy()) >= 1 || self.last_pos.z != pos.z as i32 {
|
|
self.last_pos = cpos.with_z(pos.z as i32);
|
|
for y in 0..VOXEL_MINIMAP_SIDELENGTH {
|
|
for x in 0..VOXEL_MINIMAP_SIDELENGTH {
|
|
let voff = Vec2::new(x as f32, y as f32);
|
|
let coff: Vec2<i32> = voff.map(|i| (i as i32).div_euclid(32));
|
|
let cmod: Vec2<i32> = voff.map(|i| (i as i32).rem_euclid(32));
|
|
let mut rgba = Vec4::<u16>::zero();
|
|
let (weights, zoff) =
|
|
if (x as i32 - VOXEL_MINIMAP_SIDELENGTH as i32 / 2).abs() < 96
|
|
&& (y as i32 - VOXEL_MINIMAP_SIDELENGTH as i32 / 2).abs() < 96
|
|
{
|
|
(&[2, 4, 1, 1, 1][..], -1)
|
|
} else {
|
|
(&[1][..], 0)
|
|
};
|
|
for z in 0..weights.len() {
|
|
if let Some(grid) =
|
|
self.chunk_minimaps
|
|
.get(&(cpos + coff))
|
|
.and_then(|(zlo, g)| {
|
|
g.get((pos.z as i32 + z as i32 - zlo + zoff) as usize)
|
|
})
|
|
{
|
|
let tmp: Vec4<u16> = grid
|
|
.get(cmod)
|
|
.map(|c| Vec4::<u8>::from(*c).as_())
|
|
.unwrap_or(Vec4::one());
|
|
rgba += tmp.as_() * weights[z];
|
|
}
|
|
}
|
|
let color = {
|
|
let rgba: Vec4<u8> = (rgba / weights.iter().sum::<u16>()).as_();
|
|
image::Rgba([rgba.x, rgba.y, rgba.z, rgba.w])
|
|
};
|
|
self.composited
|
|
.put_pixel(x, VOXEL_MINIMAP_SIDELENGTH - y - 1, color);
|
|
}
|
|
}
|
|
|
|
ui.replace_graphic(
|
|
self.image_id.none,
|
|
Graphic::Image(
|
|
Arc::new(DynamicImage::ImageRgba8(self.composited.clone())),
|
|
Some(Rgba::from([0.0, 0.0, 0.0, 0.0])),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
widget_ids! {
|
|
struct Ids {
|
|
mmap_frame,
|
|
mmap_frame_2,
|
|
mmap_frame_bg,
|
|
mmap_location,
|
|
mmap_button,
|
|
mmap_plus,
|
|
mmap_minus,
|
|
mmap_north_button,
|
|
map_layers[],
|
|
indicator,
|
|
mmap_north,
|
|
mmap_east,
|
|
mmap_south,
|
|
mmap_west,
|
|
mmap_site_icons_bgs[],
|
|
mmap_site_icons[],
|
|
member_indicators[],
|
|
location_marker,
|
|
voxel_minimap,
|
|
}
|
|
}
|
|
|
|
#[derive(WidgetCommon)]
|
|
pub struct MiniMap<'a> {
|
|
show: &'a Show,
|
|
client: &'a Client,
|
|
imgs: &'a Imgs,
|
|
rot_imgs: &'a ImgsRot,
|
|
world_map: &'a (Vec<img_ids::Rotations>, Vec2<u32>),
|
|
fonts: &'a Fonts,
|
|
#[conrod(common_builder)]
|
|
common: widget::CommonBuilder,
|
|
ori: Vec3<f32>,
|
|
global_state: &'a GlobalState,
|
|
location_marker: Option<Vec2<f32>>,
|
|
voxel_minimap: &'a VoxelMinimap,
|
|
}
|
|
|
|
impl<'a> MiniMap<'a> {
|
|
pub fn new(
|
|
show: &'a Show,
|
|
client: &'a Client,
|
|
imgs: &'a Imgs,
|
|
rot_imgs: &'a ImgsRot,
|
|
world_map: &'a (Vec<img_ids::Rotations>, Vec2<u32>),
|
|
fonts: &'a Fonts,
|
|
ori: Vec3<f32>,
|
|
global_state: &'a GlobalState,
|
|
location_marker: Option<Vec2<f32>>,
|
|
voxel_minimap: &'a VoxelMinimap,
|
|
) -> Self {
|
|
Self {
|
|
show,
|
|
client,
|
|
imgs,
|
|
rot_imgs,
|
|
world_map,
|
|
fonts,
|
|
common: widget::CommonBuilder::default(),
|
|
ori,
|
|
global_state,
|
|
location_marker,
|
|
voxel_minimap,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct State {
|
|
ids: Ids,
|
|
}
|
|
|
|
pub enum Event {
|
|
SettingsChange(InterfaceChange),
|
|
}
|
|
|
|
impl<'a> Widget for MiniMap<'a> {
|
|
type Event = Vec<Event>;
|
|
type State = State;
|
|
type Style = ();
|
|
|
|
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
|
|
State {
|
|
ids: Ids::new(id_gen),
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::unused_unit)] // TODO: Pending review in #587
|
|
fn style(&self) -> Self::Style { () }
|
|
|
|
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
|
let mut events = Vec::new();
|
|
|
|
let widget::UpdateArgs { state, ui, .. } = args;
|
|
let mut zoom = self.global_state.settings.interface.minimap_zoom;
|
|
const SCALE: f64 = 1.5; // TODO Make this a setting
|
|
let show_minimap = self.global_state.settings.interface.minimap_show;
|
|
let is_facing_north = self.global_state.settings.interface.minimap_face_north;
|
|
let show_topo_map = self.global_state.settings.interface.map_show_topo_map;
|
|
//let show_voxel_map = self.global_state.settings.interface.map_show_voxel_map;
|
|
let show_voxel_map = true;
|
|
let orientation = if is_facing_north {
|
|
Vec3::new(0.0, 1.0, 0.0)
|
|
} else {
|
|
self.ori
|
|
};
|
|
|
|
if show_minimap {
|
|
Image::new(self.imgs.mmap_frame)
|
|
.w_h(174.0 * SCALE, 190.0 * SCALE)
|
|
.top_right_with_margins_on(ui.window, 5.0, 5.0)
|
|
.color(Some(UI_MAIN))
|
|
.set(state.ids.mmap_frame, ui);
|
|
Image::new(self.imgs.mmap_frame_2)
|
|
.w_h(174.0 * SCALE, 190.0 * SCALE)
|
|
.middle_of(state.ids.mmap_frame)
|
|
.color(Some(UI_HIGHLIGHT_0))
|
|
.set(state.ids.mmap_frame_2, ui);
|
|
Rectangle::fill_with([170.0 * SCALE, 170.0 * SCALE], color::TRANSPARENT)
|
|
.mid_top_with_margin_on(state.ids.mmap_frame_2, 18.0 * SCALE)
|
|
.set(state.ids.mmap_frame_bg, ui);
|
|
|
|
// Map size in chunk coords
|
|
let worldsize = self.world_map.1;
|
|
// Map Layers
|
|
// It is assumed that there is at least one layer
|
|
if state.ids.map_layers.len() < self.world_map.0.len() {
|
|
state.update(|state| {
|
|
state
|
|
.ids
|
|
.map_layers
|
|
.resize(self.world_map.0.len(), &mut ui.widget_id_generator())
|
|
});
|
|
}
|
|
|
|
// Zoom Buttons
|
|
|
|
// Pressing + multiplies, and - divides, zoom by ZOOM_FACTOR.
|
|
const ZOOM_FACTOR: f64 = 2.0;
|
|
|
|
// TODO: Either prevent zooming all the way in, *or* see if we can interpolate
|
|
// somehow if you zoom in too far. Or both.
|
|
let min_zoom = 1.0;
|
|
let max_zoom = worldsize
|
|
.reduce_partial_max() as f64/*.min(f64::MAX)*/;
|
|
|
|
// NOTE: Not sure if a button can be clicked while disabled, but we still double
|
|
// check for both kinds of zoom to make sure that not only was the
|
|
// button clicked, it is also okay to perform the zoom action.
|
|
// Note that since `Button::image` has side effects, we must perform
|
|
// the `can_zoom_in` and `can_zoom_out` checks after the `&&` to avoid
|
|
// undesired early termination.
|
|
let can_zoom_in = zoom < max_zoom;
|
|
let can_zoom_out = zoom > min_zoom;
|
|
|
|
if Button::image(self.imgs.mmap_minus)
|
|
.w_h(16.0 * SCALE, 18.0 * SCALE)
|
|
.hover_image(self.imgs.mmap_minus_hover)
|
|
.press_image(self.imgs.mmap_minus_press)
|
|
.top_left_with_margins_on(state.ids.mmap_frame, 0.0, 0.0)
|
|
.image_color(UI_HIGHLIGHT_0)
|
|
.enabled(can_zoom_out)
|
|
.set(state.ids.mmap_minus, ui)
|
|
.was_clicked()
|
|
&& can_zoom_out
|
|
{
|
|
// Set the image dimensions here, rather than recomputing each time.
|
|
zoom = min_zoom.max(zoom / ZOOM_FACTOR);
|
|
// set_image_dims(zoom);
|
|
}
|
|
if Button::image(self.imgs.mmap_plus)
|
|
.w_h(18.0 * SCALE, 18.0 * SCALE)
|
|
.hover_image(self.imgs.mmap_plus_hover)
|
|
.press_image(self.imgs.mmap_plus_press)
|
|
.right_from(state.ids.mmap_minus, 0.0)
|
|
.image_color(UI_HIGHLIGHT_0)
|
|
.enabled(can_zoom_in)
|
|
.set(state.ids.mmap_plus, ui)
|
|
.was_clicked()
|
|
&& can_zoom_in
|
|
{
|
|
zoom = min_zoom.max(zoom * ZOOM_FACTOR);
|
|
// set_image_dims(zoom);
|
|
}
|
|
|
|
// Always northfacing button
|
|
if Button::image(if is_facing_north {
|
|
self.imgs.mmap_north_press
|
|
} else {
|
|
self.imgs.mmap_north
|
|
})
|
|
.w_h(18.0 * SCALE, 18.0 * SCALE)
|
|
.hover_image(if is_facing_north {
|
|
self.imgs.mmap_north_press_hover
|
|
} else {
|
|
self.imgs.mmap_north_hover
|
|
})
|
|
.press_image(if is_facing_north {
|
|
self.imgs.mmap_north_press_hover
|
|
} else {
|
|
self.imgs.mmap_north_press
|
|
})
|
|
.left_from(state.ids.mmap_button, 0.0)
|
|
.image_color(UI_HIGHLIGHT_0)
|
|
.set(state.ids.mmap_north_button, ui)
|
|
.was_clicked()
|
|
{
|
|
events.push(Event::SettingsChange(MinimapFaceNorth(!is_facing_north)));
|
|
}
|
|
|
|
events.push(Event::SettingsChange(MinimapZoom(zoom)));
|
|
|
|
// Coordinates
|
|
let player_pos = self
|
|
.client
|
|
.state()
|
|
.ecs()
|
|
.read_storage::<comp::Pos>()
|
|
.get(self.client.entity())
|
|
.map_or(Vec3::zero(), |pos| pos.0);
|
|
|
|
// Get map image source rectangle dimensions.
|
|
let w_src = max_zoom / zoom;
|
|
let h_src = max_zoom / zoom;
|
|
|
|
// Set map image to be centered around player coordinates.
|
|
let rect_src = position::Rect::from_xy_dim(
|
|
[
|
|
player_pos.x as f64 / TerrainChunkSize::RECT_SIZE.x as f64,
|
|
worldsize.y as f64
|
|
- (player_pos.y as f64 / TerrainChunkSize::RECT_SIZE.y as f64),
|
|
],
|
|
[w_src, h_src],
|
|
);
|
|
|
|
let map_size = Vec2::new(170.0 * SCALE, 170.0 * SCALE);
|
|
|
|
// Map Image
|
|
// Map Layer Images
|
|
for (index, layer) in self.world_map.0.iter().enumerate() {
|
|
let world_map_rotation = if is_facing_north {
|
|
layer.none
|
|
} else {
|
|
layer.source_north
|
|
};
|
|
if index == 0 {
|
|
Image::new(world_map_rotation)
|
|
.middle_of(state.ids.mmap_frame_bg)
|
|
.w_h(map_size.x, map_size.y)
|
|
.parent(state.ids.mmap_frame_bg)
|
|
.source_rectangle(rect_src)
|
|
.set(state.ids.map_layers[index], ui);
|
|
} else if show_topo_map {
|
|
Image::new(world_map_rotation)
|
|
.middle_of(state.ids.mmap_frame_bg)
|
|
.w_h(map_size.x, map_size.y)
|
|
.parent(state.ids.mmap_frame_bg)
|
|
.source_rectangle(rect_src)
|
|
.graphics_for(state.ids.map_layers[0])
|
|
.set(state.ids.map_layers[index], ui);
|
|
}
|
|
}
|
|
if show_voxel_map {
|
|
let voxelmap_rotation = if is_facing_north {
|
|
self.voxel_minimap.image_id.none
|
|
} else {
|
|
self.voxel_minimap.image_id.source_north
|
|
};
|
|
let scaling = (VOXEL_MINIMAP_SIDELENGTH as f64 / 32.0) * max_zoom / zoom;
|
|
let cmod: Vec2<f64> = (player_pos.xy() % 32.0).as_();
|
|
let rect_src = position::Rect::from_xy_dim(
|
|
[
|
|
cmod.x + VOXEL_MINIMAP_SIDELENGTH as f64 / 2.0,
|
|
-cmod.y + VOXEL_MINIMAP_SIDELENGTH as f64 / 2.0,
|
|
],
|
|
[scaling, scaling],
|
|
);
|
|
Image::new(voxelmap_rotation)
|
|
.middle_of(state.ids.mmap_frame_bg)
|
|
.w_h(map_size.x, map_size.y)
|
|
.parent(state.ids.mmap_frame_bg)
|
|
.source_rectangle(rect_src)
|
|
.graphics_for(state.ids.map_layers[0])
|
|
.set(state.ids.voxel_minimap, ui);
|
|
}
|
|
|
|
// Map icons
|
|
if state.ids.mmap_site_icons.len() < self.client.sites().len() {
|
|
state.update(|state| {
|
|
state
|
|
.ids
|
|
.mmap_site_icons
|
|
.resize(self.client.sites().len(), &mut ui.widget_id_generator())
|
|
});
|
|
}
|
|
if state.ids.mmap_site_icons_bgs.len() < self.client.sites().len() {
|
|
state.update(|state| {
|
|
state
|
|
.ids
|
|
.mmap_site_icons_bgs
|
|
.resize(self.client.sites().len(), &mut ui.widget_id_generator())
|
|
});
|
|
}
|
|
|
|
let wpos_to_rpos = |wpos: Vec2<f32>, limit: bool| {
|
|
// Site pos in world coordinates relative to the player
|
|
let rwpos = wpos - player_pos;
|
|
// Convert to chunk coordinates
|
|
let rcpos = rwpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e / sz as f32);
|
|
// Convert to fractional coordinates relative to the worldsize
|
|
let rfpos = rcpos / max_zoom as f32;
|
|
// Convert to unrotated pixel coordinates from the player location on the map
|
|
// (the center)
|
|
// Accounting for zooming
|
|
let rpixpos = rfpos.map2(map_size, |e, sz| e * sz as f32 * zoom as f32);
|
|
let rpos = Vec2::unit_x().rotated_z(orientation.x) * rpixpos.x
|
|
+ Vec2::unit_y().rotated_z(orientation.x) * rpixpos.y;
|
|
|
|
if rpos
|
|
.map2(map_size, |e, sz| e.abs() > sz as f32 / 2.0)
|
|
.reduce_or()
|
|
{
|
|
limit.then(|| {
|
|
let clamped = rpos / rpos.map(|e| e.abs()).reduce_partial_max();
|
|
clamped * map_size.map(|e| e as f32) / 2.0
|
|
})
|
|
} else {
|
|
Some(rpos)
|
|
}
|
|
};
|
|
|
|
for (i, site_rich) in self.client.sites().values().enumerate() {
|
|
let site = &site_rich.site;
|
|
|
|
let rpos = match wpos_to_rpos(site.wpos.map(|e| e as f32), false) {
|
|
Some(rpos) => rpos,
|
|
None => continue,
|
|
};
|
|
|
|
Image::new(match &site.kind {
|
|
SiteKind::Town => self.imgs.mmap_site_town_bg,
|
|
SiteKind::Dungeon { .. } => self.imgs.mmap_site_dungeon_bg,
|
|
SiteKind::Castle => self.imgs.mmap_site_castle_bg,
|
|
SiteKind::Cave => self.imgs.mmap_site_cave_bg,
|
|
SiteKind::Tree => self.imgs.mmap_site_tree,
|
|
})
|
|
.x_y_position_relative_to(
|
|
state.ids.map_layers[0],
|
|
position::Relative::Scalar(rpos.x as f64),
|
|
position::Relative::Scalar(rpos.y as f64),
|
|
)
|
|
.w_h(20.0, 20.0)
|
|
.color(Some(match &site.kind {
|
|
SiteKind::Town => Color::Rgba(1.0, 1.0, 1.0, 0.0),
|
|
SiteKind::Castle => Color::Rgba(1.0, 1.0, 1.0, 0.0),
|
|
SiteKind::Dungeon { difficulty } => match difficulty {
|
|
0 => QUALITY_LOW,
|
|
1 => QUALITY_COMMON,
|
|
2 => QUALITY_MODERATE,
|
|
3 => QUALITY_HIGH,
|
|
4 => QUALITY_EPIC,
|
|
5 => QUALITY_DEBUG,
|
|
_ => Color::Rgba(1.0, 1.0, 1.0, 0.0),
|
|
},
|
|
SiteKind::Cave => Color::Rgba(1.0, 1.0, 1.0, 0.0),
|
|
SiteKind::Tree => Color::Rgba(1.0, 1.0, 1.0, 0.0),
|
|
}))
|
|
.set(state.ids.mmap_site_icons_bgs[i], ui);
|
|
Image::new(match &site.kind {
|
|
SiteKind::Town => self.imgs.mmap_site_town,
|
|
SiteKind::Dungeon { .. } => self.imgs.mmap_site_dungeon,
|
|
SiteKind::Castle => self.imgs.mmap_site_castle,
|
|
SiteKind::Cave => self.imgs.mmap_site_cave,
|
|
SiteKind::Tree => self.imgs.mmap_site_tree,
|
|
})
|
|
.middle_of(state.ids.mmap_site_icons_bgs[i])
|
|
.w_h(20.0, 20.0)
|
|
.color(Some(UI_HIGHLIGHT_0))
|
|
.set(state.ids.mmap_site_icons[i], ui);
|
|
}
|
|
|
|
// Group member indicators
|
|
let client_state = self.client.state();
|
|
let member_pos = client_state.ecs().read_storage::<common::comp::Pos>();
|
|
let group_members = self
|
|
.client
|
|
.group_members()
|
|
.iter()
|
|
.filter_map(|(u, r)| match r {
|
|
Role::Member => Some(u),
|
|
Role::Pet => None,
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let group_size = group_members.len();
|
|
//let in_group = !group_members.is_empty();
|
|
let uid_allocator = client_state
|
|
.ecs()
|
|
.read_resource::<common_net::sync::UidAllocator>();
|
|
if state.ids.member_indicators.len() < group_size {
|
|
state.update(|s| {
|
|
s.ids
|
|
.member_indicators
|
|
.resize(group_size, &mut ui.widget_id_generator())
|
|
})
|
|
};
|
|
for (i, &uid) in group_members.iter().copied().enumerate() {
|
|
let entity = uid_allocator.retrieve_entity_internal(uid.into());
|
|
let member_pos = entity.and_then(|entity| member_pos.get(entity));
|
|
|
|
if let Some(member_pos) = member_pos {
|
|
let rpos = match wpos_to_rpos(member_pos.0.xy().map(|e| e as f32), false) {
|
|
Some(rpos) => rpos,
|
|
None => continue,
|
|
};
|
|
|
|
let factor = 1.2;
|
|
let z_comparison = (member_pos.0.z - player_pos.z) as i32;
|
|
Button::image(match z_comparison {
|
|
10..=i32::MAX => self.imgs.indicator_group_up,
|
|
i32::MIN..=-10 => self.imgs.indicator_group_down,
|
|
_ => self.imgs.indicator_group,
|
|
})
|
|
.x_y_position_relative_to(
|
|
state.ids.map_layers[0],
|
|
position::Relative::Scalar(rpos.x as f64),
|
|
position::Relative::Scalar(rpos.y as f64),
|
|
)
|
|
.w_h(16.0 * factor, 16.0 * factor)
|
|
.image_color(Color::Rgba(1.0, 1.0, 1.0, 1.0))
|
|
.set(state.ids.member_indicators[i], ui);
|
|
}
|
|
}
|
|
|
|
// Location marker
|
|
if self.show.map_marker {
|
|
if let Some(rpos) = self.location_marker.and_then(|lm| wpos_to_rpos(lm, true)) {
|
|
let factor = 1.2;
|
|
|
|
Button::image(self.imgs.location_marker)
|
|
.x_y_position_relative_to(
|
|
state.ids.map_layers[0],
|
|
position::Relative::Scalar(rpos.x as f64),
|
|
position::Relative::Scalar(rpos.y as f64 + 8.0 * factor),
|
|
)
|
|
.w_h(16.0 * factor, 16.0 * factor)
|
|
//.image_color(Color::Rgba(1.0, 1.0, 1.0, 1.0))
|
|
.floating(true)
|
|
.set(state.ids.location_marker, ui);
|
|
}
|
|
}
|
|
// Indicator
|
|
let ind_scale = 0.4;
|
|
let ind_rotation = if is_facing_north {
|
|
self.rot_imgs.indicator_mmap_small.target_north
|
|
} else {
|
|
self.rot_imgs.indicator_mmap_small.none
|
|
};
|
|
Image::new(ind_rotation)
|
|
.middle_of(state.ids.map_layers[0])
|
|
.w_h(32.0 * ind_scale, 37.0 * ind_scale)
|
|
.color(Some(UI_HIGHLIGHT_0))
|
|
.floating(true)
|
|
.set(state.ids.indicator, ui);
|
|
|
|
// Compass directions
|
|
let dirs = [
|
|
(Vec2::new(0.0, 1.0), state.ids.mmap_north, "N", true),
|
|
(Vec2::new(1.0, 0.0), state.ids.mmap_east, "E", false),
|
|
(Vec2::new(0.0, -1.0), state.ids.mmap_south, "S", false),
|
|
(Vec2::new(-1.0, 0.0), state.ids.mmap_west, "W", false),
|
|
];
|
|
for (dir, id, name, bold) in dirs.iter() {
|
|
let cardinal_dir = Vec2::unit_x().rotated_z(orientation.x as f64) * dir.x
|
|
+ Vec2::unit_y().rotated_z(orientation.x as f64) * dir.y;
|
|
let clamped = cardinal_dir / cardinal_dir.map(|e| e.abs()).reduce_partial_max();
|
|
let pos = clamped * (map_size / 2.0 - 10.0);
|
|
Text::new(name)
|
|
.x_y_position_relative_to(
|
|
state.ids.map_layers[0],
|
|
position::Relative::Scalar(pos.x),
|
|
position::Relative::Scalar(pos.y),
|
|
)
|
|
.font_size(self.fonts.cyri.scale(18))
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.color(if *bold {
|
|
Color::Rgba(0.75, 0.0, 0.0, 1.0)
|
|
} else {
|
|
TEXT_COLOR
|
|
})
|
|
.parent(ui.window)
|
|
.set(*id, ui);
|
|
}
|
|
} else {
|
|
Image::new(self.imgs.mmap_frame_closed)
|
|
.w_h(174.0 * SCALE, 18.0 * SCALE)
|
|
.color(Some(UI_MAIN))
|
|
.top_right_with_margins_on(ui.window, 0.0, 5.0)
|
|
.set(state.ids.mmap_frame, ui);
|
|
}
|
|
|
|
if Button::image(if show_minimap {
|
|
self.imgs.mmap_open
|
|
} else {
|
|
self.imgs.mmap_closed
|
|
})
|
|
.w_h(18.0 * SCALE, 18.0 * SCALE)
|
|
.hover_image(if show_minimap {
|
|
self.imgs.mmap_open_hover
|
|
} else {
|
|
self.imgs.mmap_closed_hover
|
|
})
|
|
.press_image(if show_minimap {
|
|
self.imgs.mmap_open_press
|
|
} else {
|
|
self.imgs.mmap_closed_press
|
|
})
|
|
.top_right_with_margins_on(state.ids.mmap_frame, 0.0, 0.0)
|
|
.image_color(UI_HIGHLIGHT_0)
|
|
.set(state.ids.mmap_button, ui)
|
|
.was_clicked()
|
|
{
|
|
events.push(Event::SettingsChange(MinimapShow(!show_minimap)));
|
|
}
|
|
|
|
// TODO: Subregion name display
|
|
|
|
// Title
|
|
|
|
match self.client.current_chunk() {
|
|
Some(chunk) => {
|
|
// Count characters in the name to avoid clipping with the name display
|
|
let name_len = chunk.meta().name().chars().count();
|
|
Text::new(chunk.meta().name())
|
|
.mid_top_with_margin_on(state.ids.mmap_frame, match name_len {
|
|
15..=30 => 4.0,
|
|
_ => 2.0,
|
|
})
|
|
.font_size(self.fonts.cyri.scale(match name_len {
|
|
0..=15 => 18,
|
|
16..=30 => 14,
|
|
_ => 14,
|
|
}))
|
|
.font_id(self.fonts.cyri.conrod_id)
|
|
.color(TEXT_COLOR)
|
|
.set(state.ids.mmap_location, ui)
|
|
},
|
|
None => Text::new(" ")
|
|
.mid_top_with_margin_on(state.ids.mmap_frame, 0.0)
|
|
.font_size(self.fonts.cyri.scale(18))
|
|
.color(TEXT_COLOR)
|
|
.set(state.ids.mmap_location, ui),
|
|
}
|
|
|
|
events
|
|
}
|
|
}
|