UI in device pixels on the character grid with fixed integer font sizes, ASCII bars; 3D render scale for a pixel look
- text only at 1x/2x/4x times an integer interface factor (auto or 1-4 in the settings) - high DPI rendering, own 2D camera in device pixels, mouse converted - toolbar buttons 6 columns, integer icon scale, minimap integer pixels per tile - all bars (top bar, unit/tower/gate HP) as CP437 block characters - 3D world optionally rendered at 1/2, 1/3, 1/4 and upscaled with nearest filtering Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
889ca66dbf
commit
50d013bb28
8 files changed
+344
-213
No files matched your search
@@ -6,7 +6,10 @@ An original dungeon management game (genre: dungeon building & defence) that run
|
|||||||
- Rust + **macroquad** (WebGL), target `wasm32-unknown-unknown`
|
- Rust + **macroquad** (WebGL), target `wasm32-unknown-unknown`
|
||||||
- User interface drawn entirely **in the canvas**
|
- User interface drawn entirely **in the canvas**
|
||||||
- Font: **DINOBYTE** bitmap font (CP437, 6×8 px, CC BY 4.0, see CREDITS.md) for all texts, integer scaled
|
- Font: **DINOBYTE** bitmap font (CP437, 6×8 px, CC BY 4.0, see CREDITS.md) for all texts, integer scaled
|
||||||
- All UI windows use **CP437 frames** (single lines for panels, double lines for dialogs); bars (HP/mana/gate) are pixel bars
|
- All UI windows use **CP437 frames** (single lines for panels, double lines for dialogs); **all bars are CP437 block characters** (█ ▌ ░)
|
||||||
|
- Text only in **fixed integer sizes**: 1×, 2× and 4× the 6×8 glyph size, times an integer **interface factor** (automatic from the device resolution, or 1×–4× in the settings)
|
||||||
|
- The interface is drawn in **device pixels** (high DPI) and laid out on the **character grid**, so every font pixel has exactly the same size
|
||||||
|
- **3D resolution** setting: the world can be rendered at 1/1, 1/2, 1/3 or 1/4 resolution and scaled up by exactly that factor (pixel look); the interface stays sharp
|
||||||
- Sound: event system + macroquad audio prepared (volume, mute key M); sounds will be generated later
|
- Sound: event system + macroquad audio prepared (volume, mute key M); sounds will be generated later
|
||||||
- Languages: all in-game texts live in **language files** (`lang/*.lang`, key = text); English is the reference/fallback, German included, more can be added. The language is chosen on first start and stored in the browser.
|
- Languages: all in-game texts live in **language files** (`lang/*.lang`, key = text); English is the reference/fallback, German included, more can be added. The language is chosen on first start and stored in the browser.
|
||||||
- Repository language: **English** (code, comments, docs, commit messages)
|
- Repository language: **English** (code, comments, docs, commit messages)
|
||||||
|
|||||||
@@ -333,3 +333,6 @@ dir.s = Süden
|
|||||||
dir.sw = Südwesten
|
dir.sw = Südwesten
|
||||||
dir.w = Westen
|
dir.w = Westen
|
||||||
dir.nw = Nordwesten
|
dir.nw = Nordwesten
|
||||||
|
|
||||||
|
val.auto = Auto ({0}x)
|
||||||
|
set.render_scale = 3D-Auflösung
|
||||||
@@ -334,3 +334,6 @@ dir.s = south
|
|||||||
dir.sw = south-west
|
dir.sw = south-west
|
||||||
dir.w = west
|
dir.w = west
|
||||||
dir.nw = north-west
|
dir.nw = north-west
|
||||||
|
|
||||||
|
val.auto = Auto ({0}x)
|
||||||
|
set.render_scale = 3D resolution
|
||||||
+13
-10
@@ -36,7 +36,7 @@ fn conf() -> macroquad::conf::Conf {
|
|||||||
window_title: "DungeonCraft".into(),
|
window_title: "DungeonCraft".into(),
|
||||||
window_width: 1280,
|
window_width: 1280,
|
||||||
window_height: 800,
|
window_height: 800,
|
||||||
high_dpi: false,
|
high_dpi: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
draw_call_vertex_capacity: 40000,
|
draw_call_vertex_capacity: 40000,
|
||||||
@@ -67,10 +67,11 @@ fn next_seed(seed: u32) -> u32 {
|
|||||||
|
|
||||||
fn apply_settings(s: &Settings, r3d: &mut R3d) {
|
fn apply_settings(s: &Settings, r3d: &mut R3d) {
|
||||||
i18n::set_language(&s.language);
|
i18n::set_language(&s.language);
|
||||||
ui::set_ui_scale(s.ui_scale as f32);
|
ui::set_ui_factor(s.ui_factor);
|
||||||
r3d.outlines = s.outlines;
|
r3d.outlines = s.outlines;
|
||||||
r3d.set_wall_detail(s.wall_subdivisions());
|
r3d.set_wall_detail(s.wall_subdivisions());
|
||||||
r3d.max_lights = s.max_lights as usize;
|
r3d.max_lights = s.max_lights as usize;
|
||||||
|
r3d.render_div = s.render_scale.clamp(1, 4) as u32;
|
||||||
audio::set_volumes(s.music_volume, s.volume, s.muted);
|
audio::set_volumes(s.music_volume, s.volume, s.muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +239,8 @@ async fn main() {
|
|||||||
loop {
|
loop {
|
||||||
let dt = get_frame_time().min(0.1);
|
let dt = get_frame_time().min(0.1);
|
||||||
let now = get_time();
|
let now = get_time();
|
||||||
let (mx, my) = mouse_position();
|
let (mx, my) = mouse_position(); // CSS pixels (3D picking)
|
||||||
|
let (ux, uy) = ui::mouse(); // device pixels (interface)
|
||||||
let mut settings_changed = false;
|
let mut settings_changed = false;
|
||||||
|
|
||||||
// ---------------- savegame files: file picker result or dropped onto the window
|
// ---------------- savegame files: file picker result or dropped onto the window
|
||||||
@@ -272,7 +274,7 @@ async fn main() {
|
|||||||
let dialog = ui.show_langsel || ui.show_help;
|
let dialog = ui.show_langsel || ui.show_help;
|
||||||
let in_menu = menu.open();
|
let in_menu = menu.open();
|
||||||
let playing = !in_menu && !dialog;
|
let playing = !in_menu && !dialog;
|
||||||
let over_ui = ui.over_ui(mx, my);
|
let over_ui = ui.over_ui(ux, uy);
|
||||||
let picked = app.cam.pick_tile(&app.game.map, mx, my);
|
let picked = app.cam.pick_tile(&app.game.map, mx, my);
|
||||||
let hover_tile = picked.map(|(x, z, _)| (x, z));
|
let hover_tile = picked.map(|(x, z, _)| (x, z));
|
||||||
let ground = app.cam.ground_point(mx, my);
|
let ground = app.cam.ground_point(mx, my);
|
||||||
@@ -436,7 +438,7 @@ async fn main() {
|
|||||||
// ---------------- left button
|
// ---------------- left button
|
||||||
if is_mouse_button_pressed(MouseButton::Left) {
|
if is_mouse_button_pressed(MouseButton::Left) {
|
||||||
if dialog {
|
if dialog {
|
||||||
match ui.click(mx, my, &app.game) {
|
match ui.click(ux, uy, &app.game) {
|
||||||
UiAction::SelectLanguage(code) => {
|
UiAction::SelectLanguage(code) => {
|
||||||
app.settings.language = code;
|
app.settings.language = code;
|
||||||
settings_changed = true;
|
settings_changed = true;
|
||||||
@@ -446,7 +448,7 @@ async fn main() {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if in_menu {
|
} else if in_menu {
|
||||||
match menu.click(mx, my, &mut app.settings) {
|
match menu.click(ux, uy, &mut app.settings) {
|
||||||
MenuAction::SettingsChanged => settings_changed = true,
|
MenuAction::SettingsChanged => settings_changed = true,
|
||||||
MenuAction::Resume => {}
|
MenuAction::Resume => {}
|
||||||
MenuAction::NewGame => {
|
MenuAction::NewGame => {
|
||||||
@@ -483,7 +485,7 @@ async fn main() {
|
|||||||
MenuAction::None | MenuAction::Consumed => {}
|
MenuAction::None | MenuAction::Consumed => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match ui.click(mx, my, &app.game) {
|
match ui.click(ux, uy, &app.game) {
|
||||||
UiAction::SelectTool(t) => {
|
UiAction::SelectTool(t) => {
|
||||||
ui.tool = t;
|
ui.tool = t;
|
||||||
app.game.sfx_ui(game::Sfx::Click);
|
app.game.sfx_ui(game::Sfx::Click);
|
||||||
@@ -520,8 +522,8 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if inp.minimap_drag {
|
if inp.minimap_drag {
|
||||||
if is_mouse_button_down(MouseButton::Left) && ui.minimap_contains(mx, my) {
|
if is_mouse_button_down(MouseButton::Left) && ui.minimap_contains(ux, uy) {
|
||||||
let (x, y) = ui.minimap_world(mx, my);
|
let (x, y) = ui.minimap_world(ux, uy);
|
||||||
app.cam.tx = x;
|
app.cam.tx = x;
|
||||||
app.cam.tz = y;
|
app.cam.tz = y;
|
||||||
} else if !is_mouse_button_down(MouseButton::Left) {
|
} else if !is_mouse_button_down(MouseButton::Left) {
|
||||||
@@ -591,8 +593,9 @@ async fn main() {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn draw_frame(app: &mut App, gfx: &Gfx, r3d: &mut R3d, ui: &mut Ui, menu: &mut Menu, inp: &Input, now: f64) {
|
fn draw_frame(app: &mut App, gfx: &Gfx, r3d: &mut R3d, ui: &mut Ui, menu: &mut Menu, inp: &Input, now: f64) {
|
||||||
let (mx, my) = mouse_position();
|
let (mx, my) = mouse_position();
|
||||||
|
let (ux, uy) = ui::mouse();
|
||||||
let blocked = menu.open() || ui.show_help || ui.show_langsel;
|
let blocked = menu.open() || ui.show_help || ui.show_langsel;
|
||||||
let over_ui = blocked || ui.over_ui(mx, my);
|
let over_ui = blocked || ui.over_ui(ux, uy);
|
||||||
let hover_tile = app.cam.pick_tile(&app.game.map, mx, my).map(|(x, z, _)| (x, z));
|
let hover_tile = app.cam.pick_tile(&app.game.map, mx, my).map(|(x, z, _)| (x, z));
|
||||||
let ground = app.cam.ground_point(mx, my);
|
let ground = app.cam.ground_point(mx, my);
|
||||||
clear_background(Color::from_rgba(4, 3, 5, 255));
|
clear_background(Color::from_rgba(4, 3, 5, 255));
|
||||||
|
|||||||
+58
-48
@@ -5,7 +5,7 @@ use crate::gfx::*;
|
|||||||
use crate::i18n::{languages, tr, trf};
|
use crate::i18n::{languages, tr, trf};
|
||||||
use crate::map::{MAX_SIZE, MIN_SIZE};
|
use crate::map::{MAX_SIZE, MIN_SIZE};
|
||||||
use crate::settings::{all_actions, key_name, Action, Settings};
|
use crate::settings::{all_actions, key_name, Action, Settings};
|
||||||
use crate::ui::{tool_info, CH, CW, DIM, FRAME, FRAME_HI, GOLD, S, TOOLS, TXT};
|
use crate::ui::{grid_center, grid_center_y, mouse, sh, sw, tool_info, CH, CW, DIM, FRAME, FRAME_HI, GOLD, S, SMALL, TITLE, TOOLS, TXT};
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
@@ -34,6 +34,7 @@ pub enum Item {
|
|||||||
WallDetail,
|
WallDetail,
|
||||||
Lights,
|
Lights,
|
||||||
UiScale,
|
UiScale,
|
||||||
|
RenderScale,
|
||||||
GameSpeed,
|
GameSpeed,
|
||||||
Hints,
|
Hints,
|
||||||
}
|
}
|
||||||
@@ -43,7 +44,7 @@ fn tab_items(tab: usize) -> &'static [Item] {
|
|||||||
match tab {
|
match tab {
|
||||||
0 => &[Language, Volume, MusicVolume, Mute],
|
0 => &[Language, Volume, MusicVolume, Mute],
|
||||||
1 => &[RotateSpeed, ZoomSpeed, EdgeScroll, InvertMouse],
|
1 => &[RotateSpeed, ZoomSpeed, EdgeScroll, InvertMouse],
|
||||||
2 => &[Outlines, WallDetail, Lights, UiScale],
|
2 => &[Outlines, WallDetail, Lights, UiScale, RenderScale],
|
||||||
3 => &[GameSpeed, Hints],
|
3 => &[GameSpeed, Hints],
|
||||||
_ => &[],
|
_ => &[],
|
||||||
}
|
}
|
||||||
@@ -63,6 +64,7 @@ fn item_label(it: Item) -> String {
|
|||||||
Item::WallDetail => "set.wall_detail",
|
Item::WallDetail => "set.wall_detail",
|
||||||
Item::Lights => "set.lights",
|
Item::Lights => "set.lights",
|
||||||
Item::UiScale => "set.ui_scale",
|
Item::UiScale => "set.ui_scale",
|
||||||
|
Item::RenderScale => "set.render_scale",
|
||||||
Item::GameSpeed => "set.game_speed",
|
Item::GameSpeed => "set.game_speed",
|
||||||
Item::Hints => "set.hints",
|
Item::Hints => "set.hints",
|
||||||
})
|
})
|
||||||
@@ -93,7 +95,14 @@ fn item_value(it: Item, s: &Settings) -> String {
|
|||||||
_ => "val.medium",
|
_ => "val.medium",
|
||||||
}),
|
}),
|
||||||
Item::Lights => s.max_lights.to_string(),
|
Item::Lights => s.max_lights.to_string(),
|
||||||
Item::UiScale => tr(if s.ui_scale >= 3 { "val.large" } else { "val.normal" }),
|
Item::UiScale => {
|
||||||
|
if s.ui_factor == 0 {
|
||||||
|
trf("val.auto", &[crate::ui::F().to_string()])
|
||||||
|
} else {
|
||||||
|
format!("{}x", s.ui_factor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item::RenderScale => format!("1/{}", s.render_scale.max(1)),
|
||||||
Item::GameSpeed => format!("{}x", s.game_speed as i32),
|
Item::GameSpeed => format!("{}x", s.game_speed as i32),
|
||||||
Item::Hints => onoff(s.hints),
|
Item::Hints => onoff(s.hints),
|
||||||
}
|
}
|
||||||
@@ -126,7 +135,8 @@ fn item_change(it: Item, s: &mut Settings, d: i32) {
|
|||||||
Item::Outlines => s.outlines = !s.outlines,
|
Item::Outlines => s.outlines = !s.outlines,
|
||||||
Item::WallDetail => s.wall_detail = cycle(&[1u8, 2, 3], s.wall_detail, d),
|
Item::WallDetail => s.wall_detail = cycle(&[1u8, 2, 3], s.wall_detail, d),
|
||||||
Item::Lights => s.max_lights = cycle(&[8u8, 16, 24], s.max_lights, d),
|
Item::Lights => s.max_lights = cycle(&[8u8, 16, 24], s.max_lights, d),
|
||||||
Item::UiScale => s.ui_scale = cycle(&[2u8, 3], s.ui_scale, d),
|
Item::UiScale => s.ui_factor = cycle(&[0u8, 1, 2, 3, 4], s.ui_factor, d),
|
||||||
|
Item::RenderScale => s.render_scale = cycle(&[1u8, 2, 3, 4], s.render_scale.max(1), d),
|
||||||
Item::GameSpeed => s.game_speed = cycle(&[1.0f32, 2.0], s.game_speed, d),
|
Item::GameSpeed => s.game_speed = cycle(&[1.0f32, 2.0], s.game_speed, d),
|
||||||
Item::Hints => s.hints = !s.hints,
|
Item::Hints => s.hints = !s.hints,
|
||||||
}
|
}
|
||||||
@@ -285,8 +295,8 @@ impl Menu {
|
|||||||
pub fn draw(&mut self, gfx: &Gfx, settings: &Settings, status: Option<&str>) {
|
pub fn draw(&mut self, gfx: &Gfx, settings: &Settings, status: Option<&str>) {
|
||||||
self.buttons.clear();
|
self.buttons.clear();
|
||||||
let Some(page) = self.page else { return };
|
let Some(page) = self.page else { return };
|
||||||
let sw = screen_width();
|
let sw = sw();
|
||||||
let sh = screen_height();
|
let sh = sh();
|
||||||
draw_rectangle(0.0, 0.0, sw, sh, Color::new(0.0, 0.0, 0.0, if page == Page::Title { 0.55 } else { 0.45 }));
|
draw_rectangle(0.0, 0.0, sw, sh, Color::new(0.0, 0.0, 0.0, if page == Page::Title { 0.55 } else { 0.45 }));
|
||||||
match page {
|
match page {
|
||||||
Page::Title => self.draw_title(gfx),
|
Page::Title => self.draw_title(gfx),
|
||||||
@@ -299,8 +309,8 @@ impl Menu {
|
|||||||
let a = all_actions()[action];
|
let a = all_actions()[action];
|
||||||
let text = trf("set.press_key", &[action_label(a)]);
|
let text = trf("set.press_key", &[action_label(a)]);
|
||||||
let cols = (text.chars().count() as i32 + 8).max(36);
|
let cols = (text.chars().count() as i32 + 8).max(36);
|
||||||
let x = (sw - cols as f32 * CW()) * 0.5;
|
let x = grid_center(sw, cols as f32 * CW());
|
||||||
let y = sh * 0.4;
|
let y = (sh * 0.4 / CH()).floor() * CH();
|
||||||
gfx.window(x, y, cols, 5, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.98));
|
gfx.window(x, y, cols, 5, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.98));
|
||||||
gfx.text(&text, x + CW() * 4.0, y + CH() * 1.5, S(), TXT);
|
gfx.text(&text, x + CW() * 4.0, y + CH() * 1.5, S(), TXT);
|
||||||
let esc = tr("set.esc_cancel");
|
let esc = tr("set.esc_cancel");
|
||||||
@@ -309,7 +319,7 @@ impl Menu {
|
|||||||
}
|
}
|
||||||
if let Some(st) = status {
|
if let Some(st) = status {
|
||||||
let w = gfx.measure(st, S()) + CW() * 4.0;
|
let w = gfx.measure(st, S()) + CW() * 4.0;
|
||||||
let x = (sw - w) * 0.5;
|
let x = grid_center(sw, w);
|
||||||
let y = sh - CH() * 9.0;
|
let y = sh - CH() * 9.0;
|
||||||
draw_rectangle(x, y, w, CH() * 2.0, Color::new(0.05, 0.03, 0.04, 0.95));
|
draw_rectangle(x, y, w, CH() * 2.0, Color::new(0.05, 0.03, 0.04, 0.95));
|
||||||
draw_rectangle_lines(x, y, w, CH() * 2.0, 2.0, GOLD);
|
draw_rectangle_lines(x, y, w, CH() * 2.0, 2.0, GOLD);
|
||||||
@@ -318,24 +328,24 @@ impl Menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn column(&mut self, gfx: &Gfx, x: f32, y: f32, w: f32, items: &[(Btn, String)]) {
|
fn column(&mut self, gfx: &Gfx, x: f32, y: f32, w: f32, items: &[(Btn, String)]) {
|
||||||
let (mx, my) = mouse_position();
|
let (mx, my) = mouse();
|
||||||
for (k, (b, label)) in items.iter().enumerate() {
|
for (k, (b, label)) in items.iter().enumerate() {
|
||||||
let r = Rect::new(x, y + k as f32 * CH() * 3.0, w, CH() * 2.2);
|
let r = Rect::new(x, y + k as f32 * CH() * 3.0, w, CH() * 2.0);
|
||||||
button(gfx, r, label, r.contains(vec2(mx, my)), FRAME);
|
button(gfx, r, label, r.contains(vec2(mx, my)), FRAME);
|
||||||
self.buttons.push((r, *b));
|
self.buttons.push((r, *b));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_title(&mut self, gfx: &Gfx) {
|
fn draw_title(&mut self, gfx: &Gfx) {
|
||||||
let sw = screen_width();
|
let sw = sw();
|
||||||
let sh = screen_height();
|
let sh = sh();
|
||||||
let big = (S() * 4.0).min((sw / (GW * 14.0)).floor()).max(S());
|
let big = TITLE();
|
||||||
let title = "DungeonCraft";
|
let title = "DungeonCraft";
|
||||||
let ty = sh * 0.12;
|
let ty = (sh * 0.12 / CH()).floor() * CH();
|
||||||
gfx.text(title, (sw - gfx.measure(title, big)) * 0.5 + big, ty + big, big, Color::new(0.0, 0.0, 0.0, 0.8));
|
gfx.text(title, grid_center(sw, gfx.measure(title, big)) + big, ty + big, big, Color::new(0.0, 0.0, 0.0, 0.8));
|
||||||
gfx.text(title, (sw - gfx.measure(title, big)) * 0.5, ty, big, FRAME_HI);
|
gfx.text(title, grid_center(sw, gfx.measure(title, big)), ty, big, FRAME_HI);
|
||||||
let sub = tr("menu.subtitle");
|
let sub = tr("menu.subtitle");
|
||||||
gfx.text(&sub, (sw - gfx.measure(&sub, S())) * 0.5, ty + GH * big + CH(), S(), TXT);
|
gfx.text(&sub, grid_center(sw, gfx.measure(&sub, S())), ty + GH * big + CH(), S(), TXT);
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
if self.has_autosave {
|
if self.has_autosave {
|
||||||
items.push((Btn::Continue, tr("menu.continue")));
|
items.push((Btn::Continue, tr("menu.continue")));
|
||||||
@@ -346,35 +356,35 @@ impl Menu {
|
|||||||
items.push((Btn::Help, tr("menu.help")));
|
items.push((Btn::Help, tr("menu.help")));
|
||||||
let w = 26.0 * CW();
|
let w = 26.0 * CW();
|
||||||
let y = ty + GH * big + CH() * 4.0;
|
let y = ty + GH * big + CH() * 4.0;
|
||||||
self.column(gfx, (sw - w) * 0.5, y, w, &items);
|
self.column(gfx, grid_center(sw, w), y, w, &items);
|
||||||
let hint = tr("menu.drop_hint");
|
let hint = tr("menu.drop_hint");
|
||||||
gfx.text(&hint, (sw - gfx.measure(&hint, (S() / 2.0).max(1.0))) * 0.5, sh - CH() * 7.5, (S() / 2.0).max(1.0), DIM);
|
gfx.text(&hint, grid_center(sw, gfx.measure(&hint, SMALL())), sh - CH() * 8.0, SMALL(), DIM);
|
||||||
let credit = tr("help.font");
|
let credit = tr("help.font");
|
||||||
gfx.text(&credit, (sw - gfx.measure(&credit, (S() / 2.0).max(1.0))) * 0.5, sh - CH() * 6.5, (S() / 2.0).max(1.0), DIM);
|
gfx.text(&credit, grid_center(sw, gfx.measure(&credit, SMALL())), sh - CH() * 7.0, SMALL(), DIM);
|
||||||
}
|
}
|
||||||
|
|
||||||
// New game: choose the map size (width and height separately)
|
// New game: choose the map size (width and height separately)
|
||||||
fn draw_new_game(&mut self, gfx: &Gfx, s: &Settings) {
|
fn draw_new_game(&mut self, gfx: &Gfx, s: &Settings) {
|
||||||
let sw = screen_width();
|
let sw = sw();
|
||||||
let sh = screen_height();
|
let sh = sh();
|
||||||
let (mx, my) = mouse_position();
|
let (mx, my) = mouse();
|
||||||
let cols = 44;
|
let cols = 44;
|
||||||
let rows = 17;
|
let rows = 17;
|
||||||
let x = (sw - cols as f32 * CW()) * 0.5;
|
let x = grid_center(sw, cols as f32 * CW());
|
||||||
let y = ((sh - rows as f32 * CH()) * 0.5).max(0.0);
|
let y = (grid_center_y(sh, rows as f32 * CH())).max(0.0);
|
||||||
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.new"), FRAME_HI);
|
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.new"), FRAME_HI);
|
||||||
let lines = [("newgame.width", s.map_w, 0), ("newgame.height", s.map_h, 1)];
|
let lines = [("newgame.width", s.map_w, 0), ("newgame.height", s.map_h, 1)];
|
||||||
for (k, &(key, val, which)) in lines.iter().enumerate() {
|
for (k, &(key, val, which)) in lines.iter().enumerate() {
|
||||||
let by = y + CH() * (2.0 + k as f32 * 2.5);
|
let by = y + CH() * (2.0 + k as f32 * 2.5);
|
||||||
gfx.text(&tr(key), x + CW() * 3.0, by + CH() * 0.4, S(), TXT);
|
gfx.text(&tr(key), x + CW() * 3.0, by + CH() * 0.5, S(), TXT);
|
||||||
let vw = 14.0 * CW();
|
let vw = 14.0 * CW();
|
||||||
let vx = x + cols as f32 * CW() - vw - CW() * 3.0;
|
let vx = x + cols as f32 * CW() - vw - CW() * 3.0;
|
||||||
let left = Rect::new(vx, by, CW() * 2.5, CH() * 1.8);
|
let left = Rect::new(vx, by, CW() * 2.5, CH() * 2.0);
|
||||||
let right = Rect::new(vx + vw - CW() * 2.5, by, CW() * 2.5, CH() * 1.8);
|
let right = Rect::new(vx + vw - CW() * 2.5, by, CW() * 2.5, CH() * 2.0);
|
||||||
button(gfx, left, "<", left.contains(vec2(mx, my)), FRAME);
|
button(gfx, left, "<", left.contains(vec2(mx, my)), FRAME);
|
||||||
button(gfx, right, ">", right.contains(vec2(mx, my)), FRAME);
|
button(gfx, right, ">", right.contains(vec2(mx, my)), FRAME);
|
||||||
let v = val.to_string();
|
let v = val.to_string();
|
||||||
gfx.text(&v, vx + (vw - gfx.measure(&v, S())) * 0.5, by + CH() * 0.4, S(), GOLD);
|
gfx.text(&v, vx + (vw - gfx.measure(&v, S())) * 0.5, by + CH() * 0.5, S(), GOLD);
|
||||||
let (l, r) = if which == 0 { (Btn::MapW(-1), Btn::MapW(1)) } else { (Btn::MapH(-1), Btn::MapH(1)) };
|
let (l, r) = if which == 0 { (Btn::MapW(-1), Btn::MapW(1)) } else { (Btn::MapH(-1), Btn::MapH(1)) };
|
||||||
self.buttons.push((left, l));
|
self.buttons.push((left, l));
|
||||||
self.buttons.push((right, r));
|
self.buttons.push((right, r));
|
||||||
@@ -389,8 +399,8 @@ impl Menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_pause(&mut self, gfx: &Gfx) {
|
fn draw_pause(&mut self, gfx: &Gfx) {
|
||||||
let sw = screen_width();
|
let sw = sw();
|
||||||
let sh = screen_height();
|
let sh = sh();
|
||||||
let items = vec![
|
let items = vec![
|
||||||
(Btn::Resume, tr("menu.resume")),
|
(Btn::Resume, tr("menu.resume")),
|
||||||
(Btn::Save, tr("menu.save")),
|
(Btn::Save, tr("menu.save")),
|
||||||
@@ -401,21 +411,21 @@ impl Menu {
|
|||||||
];
|
];
|
||||||
let cols = 32;
|
let cols = 32;
|
||||||
let rows = items.len() as i32 * 3 + 4;
|
let rows = items.len() as i32 * 3 + 4;
|
||||||
let x = (sw - cols as f32 * CW()) * 0.5;
|
let x = grid_center(sw, cols as f32 * CW());
|
||||||
let y = ((sh - rows as f32 * CH()) * 0.5).max(0.0);
|
let y = (grid_center_y(sh, rows as f32 * CH())).max(0.0);
|
||||||
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.paused"), FRAME_HI);
|
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.paused"), FRAME_HI);
|
||||||
let w = (cols - 6) as f32 * CW();
|
let w = (cols - 6) as f32 * CW();
|
||||||
self.column(gfx, x + CW() * 3.0, y + CH() * 2.0, w, &items);
|
self.column(gfx, x + CW() * 3.0, y + CH() * 2.0, w, &items);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_settings(&mut self, gfx: &Gfx, s: &Settings, tab: usize) {
|
fn draw_settings(&mut self, gfx: &Gfx, s: &Settings, tab: usize) {
|
||||||
let sw = screen_width();
|
let sw = sw();
|
||||||
let sh = screen_height();
|
let sh = sh();
|
||||||
let (mx, my) = mouse_position();
|
let (mx, my) = mouse();
|
||||||
let cols = ((sw / CW()) as i32 - 2).min(66);
|
let cols = ((sw / CW()) as i32 - 2).min(66);
|
||||||
let rows = ((sh / CH()) as i32 - 2).min(34);
|
let rows = ((sh / CH()) as i32 - 2).min(34);
|
||||||
let x = ((sw - cols as f32 * CW()) * 0.5).max(0.0);
|
let x = (grid_center(sw, cols as f32 * CW())).max(0.0);
|
||||||
let y = ((sh - rows as f32 * CH()) * 0.5).max(0.0);
|
let y = (grid_center_y(sh, rows as f32 * CH())).max(0.0);
|
||||||
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.settings"), FRAME_HI);
|
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.settings"), FRAME_HI);
|
||||||
// tabs
|
// tabs
|
||||||
let mut tx = x + CW() * 2.0;
|
let mut tx = x + CW() * 2.0;
|
||||||
@@ -437,7 +447,7 @@ impl Menu {
|
|||||||
let acts = all_actions();
|
let acts = all_actions();
|
||||||
let half = (acts.len() + 1) / 2;
|
let half = (acts.len() + 1) / 2;
|
||||||
let colw = (cols as f32 - 6.0) * CW() * 0.5;
|
let colw = (cols as f32 - 6.0) * CW() * 0.5;
|
||||||
let rowh = CH() * 1.35;
|
let rowh = CH();
|
||||||
for (k, a) in acts.iter().enumerate() {
|
for (k, a) in acts.iter().enumerate() {
|
||||||
let c = k / half;
|
let c = k / half;
|
||||||
let r = k % half;
|
let r = k % half;
|
||||||
@@ -456,32 +466,32 @@ impl Menu {
|
|||||||
self.buttons.push((rect, Btn::Key(k)));
|
self.buttons.push((rect, Btn::Key(k)));
|
||||||
}
|
}
|
||||||
let hint = tr("set.keys_hint");
|
let hint = tr("set.keys_hint");
|
||||||
gfx.text(&hint, x + CW() * 2.0, top + half as f32 * rowh + CH() * 0.5, (S() / 2.0).max(1.0), DIM);
|
gfx.text(&hint, x + CW() * 2.0, top + half as f32 * rowh + CH() * 0.5, SMALL(), DIM);
|
||||||
let rw = gfx.measure(&tr("set.reset_keys"), S()) + CW() * 4.0;
|
let rw = gfx.measure(&tr("set.reset_keys"), S()) + CW() * 4.0;
|
||||||
let rr = Rect::new(x + cols as f32 * CW() - rw - CW() * 2.0, y + (rows - 4) as f32 * CH(), rw, CH() * 2.2);
|
let rr = Rect::new(x + cols as f32 * CW() - rw - CW() * 2.0, y + (rows - 4) as f32 * CH(), rw, CH() * 2.0);
|
||||||
button(gfx, rr, &tr("set.reset_keys"), rr.contains(vec2(mx, my)), FRAME);
|
button(gfx, rr, &tr("set.reset_keys"), rr.contains(vec2(mx, my)), FRAME);
|
||||||
self.buttons.push((rr, Btn::ResetKeys));
|
self.buttons.push((rr, Btn::ResetKeys));
|
||||||
} else {
|
} else {
|
||||||
for (i, &it) in tab_items(tab).iter().enumerate() {
|
for (i, &it) in tab_items(tab).iter().enumerate() {
|
||||||
let by = top + i as f32 * CH() * 2.5;
|
let by = top + i as f32 * CH() * 2.5;
|
||||||
gfx.text(&item_label(it), x + CW() * 3.0, by + CH() * 0.4, S(), TXT);
|
gfx.text(&item_label(it), x + CW() * 3.0, by + CH() * 0.5, S(), TXT);
|
||||||
// value with < > buttons
|
// value with < > buttons
|
||||||
let vw = 18.0 * CW();
|
let vw = 18.0 * CW();
|
||||||
let vx = x + cols as f32 * CW() - vw - CW() * 3.0;
|
let vx = x + cols as f32 * CW() - vw - CW() * 3.0;
|
||||||
let left = Rect::new(vx, by, CW() * 2.5, CH() * 1.8);
|
let left = Rect::new(vx, by, CW() * 2.5, CH() * 2.0);
|
||||||
let right = Rect::new(vx + vw - CW() * 2.5, by, CW() * 2.5, CH() * 1.8);
|
let right = Rect::new(vx + vw - CW() * 2.5, by, CW() * 2.5, CH() * 2.0);
|
||||||
button(gfx, left, "<", left.contains(vec2(mx, my)), FRAME);
|
button(gfx, left, "<", left.contains(vec2(mx, my)), FRAME);
|
||||||
button(gfx, right, ">", right.contains(vec2(mx, my)), FRAME);
|
button(gfx, right, ">", right.contains(vec2(mx, my)), FRAME);
|
||||||
let val = item_value(it, s);
|
let val = item_value(it, s);
|
||||||
gfx.text(&val, vx + (vw - gfx.measure(&val, S())) * 0.5, by + CH() * 0.4, S(), GOLD);
|
gfx.text(&val, vx + (vw - gfx.measure(&val, S())) * 0.5, by + CH() * 0.5, S(), GOLD);
|
||||||
let mid = Rect::new(vx + CW() * 2.5, by, vw - CW() * 5.0, CH() * 1.8);
|
let mid = Rect::new(vx + CW() * 2.5, by, vw - CW() * 5.0, CH() * 2.0);
|
||||||
self.buttons.push((left, Btn::Change(i, -1)));
|
self.buttons.push((left, Btn::Change(i, -1)));
|
||||||
self.buttons.push((right, Btn::Change(i, 1)));
|
self.buttons.push((right, Btn::Change(i, 1)));
|
||||||
self.buttons.push((mid, Btn::Change(i, 1)));
|
self.buttons.push((mid, Btn::Change(i, 1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let bw = gfx.measure(&tr("menu.back"), S()) + CW() * 6.0;
|
let bw = gfx.measure(&tr("menu.back"), S()) + CW() * 6.0;
|
||||||
let br = Rect::new(x + CW() * 2.0, y + (rows - 4) as f32 * CH(), bw, CH() * 2.2);
|
let br = Rect::new(x + CW() * 2.0, y + (rows - 4) as f32 * CH(), bw, CH() * 2.0);
|
||||||
button(gfx, br, &tr("menu.back"), br.contains(vec2(mx, my)), FRAME);
|
button(gfx, br, &tr("menu.back"), br.contains(vec2(mx, my)), FRAME);
|
||||||
self.buttons.push((br, Btn::Back));
|
self.buttons.push((br, Btn::Back));
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-25
@@ -681,6 +681,8 @@ pub struct R3d {
|
|||||||
pub outlines: bool, // tile outlines on/off
|
pub outlines: bool, // tile outlines on/off
|
||||||
wall_sub: usize, // wall face subdivisions (detail)
|
wall_sub: usize, // wall face subdivisions (detail)
|
||||||
pub max_lights: usize,
|
pub max_lights: usize,
|
||||||
|
pub render_div: u32, // 3D resolution divisor (pixel look)
|
||||||
|
target: Option<(RenderTarget, u32, u32)>, // low resolution render target
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rgba(u: u32) -> Color {
|
fn rgba(u: u32) -> Color {
|
||||||
@@ -710,7 +712,7 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32, fill_bucket: u8) -> u64 {
|
|||||||
|
|
||||||
impl R3d {
|
impl R3d {
|
||||||
pub fn new() -> R3d {
|
pub fn new() -> R3d {
|
||||||
R3d { lit: lit_material(), glow: glow_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS }
|
R3d { lit: lit_material(), glow: glow_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, render_div: 1, target: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changing the wall detail rebuilds all chunks
|
// Changing the wall detail rebuilds all chunks
|
||||||
@@ -854,7 +856,28 @@ impl R3d {
|
|||||||
let m = cam.matrix();
|
let m = cam.matrix();
|
||||||
self.update_chunks(g, gfx, &m);
|
self.update_chunks(g, gfx, &m);
|
||||||
EYE.with(|e| e.set(cam.eye_dir()));
|
EYE.with(|e| e.set(cam.eye_dir()));
|
||||||
set_camera(&cam.camera());
|
// pixel look: render the world at 1/div resolution and scale it up by exactly div
|
||||||
|
let div = self.render_div.max(1);
|
||||||
|
let d = crate::ui::dpr();
|
||||||
|
let (pw, ph) = ((screen_width() * d).round(), (screen_height() * d).round());
|
||||||
|
let mut cam3 = cam.camera();
|
||||||
|
if div > 1 {
|
||||||
|
let (tw, th) = ((pw / div as f32).ceil() as u32, (ph / div as f32).ceil() as u32);
|
||||||
|
if self.target.as_ref().map_or(true, |(_, w, h)| *w != tw || *h != th) {
|
||||||
|
let rt = render_target_ex(tw, th, RenderTargetParams { sample_count: 1, depth: true });
|
||||||
|
rt.texture.set_filter(FilterMode::Nearest);
|
||||||
|
self.target = Some((rt, tw, th));
|
||||||
|
}
|
||||||
|
let (rt, tw, th) = self.target.as_ref().unwrap();
|
||||||
|
cam3.render_target = Some(rt.clone());
|
||||||
|
cam3.aspect = Some(*tw as f32 / *th as f32);
|
||||||
|
} else {
|
||||||
|
self.target = None;
|
||||||
|
}
|
||||||
|
set_camera(&cam3);
|
||||||
|
if div > 1 {
|
||||||
|
clear_background(Color::from_rgba(4, 3, 5, 255));
|
||||||
|
}
|
||||||
|
|
||||||
// ---- lit geometry
|
// ---- lit geometry
|
||||||
let (lp, lc) = self.lights(g, cam, t);
|
let (lp, lc) = self.lights(g, cam, t);
|
||||||
@@ -919,9 +942,13 @@ impl R3d {
|
|||||||
draw_fx(f, t);
|
draw_fx(f, t);
|
||||||
}
|
}
|
||||||
gl_use_default_material();
|
gl_use_default_material();
|
||||||
set_default_camera();
|
crate::ui::begin_ui();
|
||||||
|
if let (true, Some((rt, tw, th))) = (div > 1, self.target.as_ref()) {
|
||||||
|
let size = vec2((*tw * div) as f32, (*th * div) as f32);
|
||||||
|
draw_texture_ex(&rt.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(size), flip_y: true, ..Default::default() });
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 2D overlay: health bars, sleep marks, levels, gate HP
|
// ---- 2D overlay in device pixels: health bars, sleep marks, levels, gate HP
|
||||||
self.draw_2d(g, gfx, cam, &m, t);
|
self.draw_2d(g, gfx, cam, &m, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -999,15 +1026,25 @@ impl R3d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_2d(&self, g: &Game, gfx: &Gfx, cam: &Cam, m: &Mat4, t: f32) {
|
fn draw_2d(&self, g: &Game, gfx: &Gfx, cam: &Cam, m: &Mat4, t: f32) {
|
||||||
let px_per_tile = screen_height() / cam.zoom;
|
use crate::ui::{ascii_bar, dpr, F, S, SMALL};
|
||||||
|
let d = dpr();
|
||||||
|
// projected positions are CSS pixels; the overlay is drawn in whole device pixels
|
||||||
|
let proj = |p: Vec3| {
|
||||||
|
let v = Cam::project(m, p) * d;
|
||||||
|
vec2(v.x.floor(), v.y.floor())
|
||||||
|
};
|
||||||
|
let small = SMALL();
|
||||||
|
let cw = GW * small; // width of one small block character
|
||||||
|
let px_per_tile = screen_height() / cam.zoom * d;
|
||||||
for e in &g.ents {
|
for e in &g.ents {
|
||||||
if !e.alive || e.held {
|
if !e.alive || e.held {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let big = matches!(e.kind, EK::Ogre | EK::Paladin | EK::Jailer);
|
let big = matches!(e.kind, EK::Ogre | EK::Paladin | EK::Jailer);
|
||||||
let head = Cam::project(m, vec3(e.x, if big { 0.9 } else { 0.72 }, e.y));
|
let head = proj(vec3(e.x, if big { 0.9 } else { 0.72 }, e.y));
|
||||||
let w = (px_per_tile * 0.6).clamp(12.0, 40.0);
|
// bar width in block characters, depending on the zoom
|
||||||
if e.hp < e.max_hp || e.hero {
|
let cols = ((px_per_tile * 0.6 / cw) as usize).clamp(3, 8);
|
||||||
|
if (e.hp < e.max_hp || e.hero) && e.captive == 0 {
|
||||||
let f = (e.hp / e.max_hp).clamp(0.0, 1.0);
|
let f = (e.hp / e.max_hp).clamp(0.0, 1.0);
|
||||||
let c = if e.hero {
|
let c = if e.hero {
|
||||||
Color::from_rgba(120, 180, 255, 255)
|
Color::from_rgba(120, 180, 255, 255)
|
||||||
@@ -1018,40 +1055,38 @@ impl R3d {
|
|||||||
} else {
|
} else {
|
||||||
Color::from_rgba(230, 60, 50, 255)
|
Color::from_rgba(230, 60, 50, 255)
|
||||||
};
|
};
|
||||||
draw_rectangle(head.x - w * 0.5 - 1.0, head.y - 1.0, w + 2.0, 4.0, Color::new(0.0, 0.0, 0.0, 0.8));
|
let x = head.x - (cols as f32 * cw * 0.5).floor();
|
||||||
draw_rectangle(head.x - w * 0.5, head.y, w * f, 2.0, c);
|
ascii_bar(gfx, x, head.y - GH * small, cols, f, c, small);
|
||||||
}
|
}
|
||||||
if e.guard {
|
if e.guard {
|
||||||
gfx.glyph(0x0F, head.x - 3.0, head.y - 11.0, 1.0, Color::from_rgba(255, 220, 90, 255));
|
gfx.glyph(0x0F, head.x - (GW * small * 0.5).floor(), head.y - 2.0 * GH * small, small, Color::from_rgba(255, 220, 90, 255));
|
||||||
}
|
}
|
||||||
if e.level > 1 && px_per_tile >= 24.0 {
|
if e.level > 1 && px_per_tile >= 24.0 * F() as f32 {
|
||||||
gfx.text_sh(&e.level.to_string(), head.x - w * 0.5 - 8.0, head.y - 3.0, 1.0, Color::from_rgba(255, 220, 90, 255));
|
let x = head.x - (cols as f32 * cw * 0.5).floor() - 2.0 * cw;
|
||||||
|
gfx.text_sh(&e.level.to_string(), x, head.y - GH * small, small, Color::from_rgba(255, 220, 90, 255));
|
||||||
}
|
}
|
||||||
if matches!(e.job, Job::Sleep(_)) && !e.moving {
|
if matches!(e.job, Job::Sleep(_)) && !e.moving {
|
||||||
let k = (t * 1.5 + e.anim) % 1.0;
|
let k = (t * 1.5 + e.anim) % 1.0;
|
||||||
gfx.glyph(b'z', head.x + w * 0.3, head.y - 6.0 - k * 14.0, 2.0, Color::new(0.8, 0.85, 1.0, 1.0 - k));
|
let rise = (k * 14.0 * F() as f32).floor();
|
||||||
|
gfx.glyph(b'z', head.x + cw * 2.0, head.y - GH * S() - rise, S(), Color::new(0.8, 0.85, 1.0, 1.0 - k));
|
||||||
}
|
}
|
||||||
if e.carry > 0 {
|
if e.carry > 0 {
|
||||||
let p = Cam::project(m, vec3(e.x, 0.35, e.y));
|
let p = proj(vec3(e.x, 0.35, e.y));
|
||||||
draw_circle(p.x + w * 0.35, p.y, 3.0, Color::from_rgba(250, 210, 60, 255));
|
gfx.glyph(0x0F, p.x + cw * 2.0, p.y - GH * small, small, Color::from_rgba(250, 210, 60, 255));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for tw in &g.towers {
|
for tw in &g.towers {
|
||||||
if tw.hp < tw.max_hp() {
|
if tw.hp < tw.max_hp() {
|
||||||
let (x, z) = tw.pos();
|
let (x, z) = tw.pos();
|
||||||
let p = Cam::project(m, vec3(x, tower_height(tw) + 0.9, z));
|
let p = proj(vec3(x, tower_height(tw) + 0.9, z));
|
||||||
let w = 40.0;
|
ascii_bar(gfx, p.x - (3.0 * cw).floor(), p.y, 6, tw.hp / tw.max_hp(), Color::from_rgba(255, 160, 60, 255), small);
|
||||||
let f = (tw.hp / tw.max_hp()).clamp(0.0, 1.0);
|
|
||||||
draw_rectangle(p.x - w * 0.5 - 1.0, p.y - 1.0, w + 2.0, 5.0, BLACK);
|
|
||||||
draw_rectangle(p.x - w * 0.5, p.y, w * f, 3.0, Color::from_rgba(255, 160, 60, 255));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if g.gate_hp > 0.0 && g.gate_hp < GATE_HP {
|
if g.gate_hp > 0.0 && g.gate_hp < GATE_HP {
|
||||||
let (gx, gz) = g.gate_center();
|
let (gx, gz) = g.gate_center();
|
||||||
let p = Cam::project(m, vec3(gx, 2.4, gz));
|
let p = proj(vec3(gx, 2.4, gz));
|
||||||
let w = 100.0;
|
let cols = 12;
|
||||||
draw_rectangle(p.x - w * 0.5 - 1.0, p.y - 1.0, w + 2.0, 8.0, BLACK);
|
ascii_bar(gfx, p.x - (cols as f32 * GW * S() * 0.5).floor(), p.y, cols, g.gate_hp / GATE_HP, Color::from_rgba(120, 180, 255, 255), S());
|
||||||
draw_rectangle(p.x - w * 0.5, p.y, w * g.gate_hp / GATE_HP, 6.0, Color::from_rgba(120, 180, 255, 255));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -141,7 +141,8 @@ pub struct Settings {
|
|||||||
pub outlines: bool,
|
pub outlines: bool,
|
||||||
pub wall_detail: u8, // 1 low, 2 medium, 3 high
|
pub wall_detail: u8, // 1 low, 2 medium, 3 high
|
||||||
pub max_lights: u8, // 8, 16 or 24
|
pub max_lights: u8, // 8, 16 or 24
|
||||||
pub ui_scale: u8, // font scale 2 (normal) or 3 (large)
|
pub ui_factor: u8, // interface size: 0 = automatic, 1..4 = fixed integer factor
|
||||||
|
pub render_scale: u8, // 3D world resolution divisor: 1 = full, 2..4 = pixel look
|
||||||
// game
|
// game
|
||||||
pub game_speed: f32, // 1 or 2
|
pub game_speed: f32, // 1 or 2
|
||||||
pub hints: bool,
|
pub hints: bool,
|
||||||
@@ -165,7 +166,8 @@ impl Default for Settings {
|
|||||||
outlines: true,
|
outlines: true,
|
||||||
wall_detail: 2,
|
wall_detail: 2,
|
||||||
max_lights: 24,
|
max_lights: 24,
|
||||||
ui_scale: 2,
|
ui_factor: 0,
|
||||||
|
render_scale: 1,
|
||||||
game_speed: 1.0,
|
game_speed: 1.0,
|
||||||
hints: true,
|
hints: true,
|
||||||
map_w: 64,
|
map_w: 64,
|
||||||
|
|||||||
@@ -9,18 +9,58 @@ use crate::render3d::{minimap_color, Cam};
|
|||||||
use crate::settings::{key_name, Action, Settings};
|
use crate::settings::{key_name, Action, Settings};
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
|
|
||||||
// UI font scale (setting: 2 = normal, 3 = large); character cell size follows it
|
// ---- interface scaling
|
||||||
|
// The interface is drawn in device pixels (see begin_ui). Every size is an integer multiple
|
||||||
|
// of the font's 6x8 pixel glyphs: small text = 1*F, normal text = 2*F, titles = 4*F, where F
|
||||||
|
// is the interface factor (automatic from the resolution or fixed in the settings).
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static UI_SCALE: std::cell::Cell<f32> = std::cell::Cell::new(2.0);
|
static UI_FACTOR_SETTING: std::cell::Cell<u8> = std::cell::Cell::new(0);
|
||||||
}
|
}
|
||||||
pub fn set_ui_scale(s: f32) {
|
|
||||||
UI_SCALE.with(|c| c.set(s));
|
pub fn set_ui_factor(f: u8) {
|
||||||
|
UI_FACTOR_SETTING.with(|c| c.set(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// device pixels per CSS pixel
|
||||||
|
pub fn dpr() -> f32 {
|
||||||
|
screen_dpi_scale()
|
||||||
|
}
|
||||||
|
// screen size and mouse in device pixels
|
||||||
|
pub fn sw() -> f32 {
|
||||||
|
(screen_width() * dpr()).round()
|
||||||
|
}
|
||||||
|
pub fn sh() -> f32 {
|
||||||
|
(screen_height() * dpr()).round()
|
||||||
|
}
|
||||||
|
pub fn mouse() -> (f32, f32) {
|
||||||
|
let (x, y) = mouse_position();
|
||||||
|
(x * dpr(), y * dpr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// interface factor (integer)
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn F() -> u32 {
|
||||||
|
let set = UI_FACTOR_SETTING.with(|c| c.get());
|
||||||
|
if set > 0 {
|
||||||
|
return set as u32;
|
||||||
|
}
|
||||||
|
((sw() / 1280.0).min(sh() / 760.0).floor() as u32).clamp(1, 4)
|
||||||
|
}
|
||||||
|
// text scales: 1x, 2x and 4x the glyph size, times F
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn SMALL() -> f32 {
|
||||||
|
F() as f32
|
||||||
}
|
}
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn S() -> f32 {
|
pub fn S() -> f32 {
|
||||||
UI_SCALE.with(|c| c.get())
|
2.0 * F() as f32
|
||||||
}
|
}
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
pub fn TITLE() -> f32 {
|
||||||
|
4.0 * F() as f32
|
||||||
|
}
|
||||||
|
// character cell of normal text = the layout grid
|
||||||
|
#[allow(non_snake_case)]
|
||||||
pub fn CW() -> f32 {
|
pub fn CW() -> f32 {
|
||||||
GW * S()
|
GW * S()
|
||||||
}
|
}
|
||||||
@@ -28,10 +68,44 @@ pub fn CW() -> f32 {
|
|||||||
pub fn CH() -> f32 {
|
pub fn CH() -> f32 {
|
||||||
GH * S()
|
GH * S()
|
||||||
}
|
}
|
||||||
// factor relative to the normal scale, for pixel sizes such as buttons and icons
|
// pixel factor for icons and other pixel art
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn K() -> f32 {
|
pub fn K() -> f32 {
|
||||||
S() / 2.0
|
F() as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
// x position that centres a width on the character grid
|
||||||
|
pub fn grid_center(total: f32, w: f32) -> f32 {
|
||||||
|
(((total - w) * 0.5) / CW()).floor() * CW()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grid_center_y(total: f32, h: f32) -> f32 {
|
||||||
|
(((total - h) * 0.5) / CH()).floor() * CH()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2D camera mapping one unit to one device pixel (top-left origin)
|
||||||
|
pub fn begin_ui() {
|
||||||
|
let (w, h) = (sw(), sh());
|
||||||
|
set_camera(&Camera2D { target: vec2(w * 0.5, h * 0.5), zoom: vec2(2.0 / w, 2.0 / h), ..Default::default() });
|
||||||
|
}
|
||||||
|
|
||||||
|
// bar made of CP437 block characters: full blocks, a half block, shaded rest
|
||||||
|
pub fn ascii_bar(gfx: &Gfx, x: f32, y: f32, cols: usize, frac: f32, col: Color, scale: f32) {
|
||||||
|
let f = frac.clamp(0.0, 1.0) * cols as f32;
|
||||||
|
let full = f.floor() as usize;
|
||||||
|
let half = f - full as f32 >= 0.5;
|
||||||
|
let dim = Color::new(col.r * 0.35, col.g * 0.35, col.b * 0.35, col.a);
|
||||||
|
for k in 0..cols {
|
||||||
|
let px = x + k as f32 * GW * scale;
|
||||||
|
if k < full {
|
||||||
|
gfx.glyph(0xDB, px, y, scale, col);
|
||||||
|
} else if k == full && half {
|
||||||
|
gfx.glyph(0xB0, px, y, scale, dim);
|
||||||
|
gfx.glyph(0xDD, px, y, scale, col);
|
||||||
|
} else {
|
||||||
|
gfx.glyph(0xB0, px, y, scale, dim);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const FRAME: Color = Color::new(0.62, 0.52, 0.36, 1.0);
|
pub const FRAME: Color = Color::new(0.62, 0.52, 0.36, 1.0);
|
||||||
@@ -176,12 +250,6 @@ fn wrap(s: &str, cols: usize) -> Vec<String> {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bar(x: f32, y: f32, w: f32, h: f32, frac: f32, col: Color) {
|
|
||||||
draw_rectangle(x - 1.0, y - 1.0, w + 2.0, h + 2.0, Color::new(0.0, 0.0, 0.0, 1.0));
|
|
||||||
draw_rectangle(x, y, w, h, Color::new(col.r * 0.25, col.g * 0.25, col.b * 0.25, 1.0));
|
|
||||||
draw_rectangle(x, y, w * frac.clamp(0.0, 1.0), h, col);
|
|
||||||
draw_rectangle(x, y, w * frac.clamp(0.0, 1.0), (h * 0.3).max(1.0), Color::new(1.0, 1.0, 1.0, 0.25));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mmss(t: f32) -> String {
|
fn mmss(t: f32) -> String {
|
||||||
let t = t.max(0.0) as i32;
|
let t = t.max(0.0) as i32;
|
||||||
@@ -259,64 +327,86 @@ impl Ui {
|
|||||||
pub fn draw(&mut self, g: &Game, gfx: &Gfx, cam: &Cam, settings: &Settings, info: &[(String, Color)], now: f64) {
|
pub fn draw(&mut self, g: &Game, gfx: &Gfx, cam: &Cam, settings: &Settings, info: &[(String, Color)], now: f64) {
|
||||||
self.rects.clear();
|
self.rects.clear();
|
||||||
let keys: Vec<String> = (0..TOOLS.len()).map(|i| key_name(settings.key(Action::Tool(i)))).collect();
|
let keys: Vec<String> = (0..TOOLS.len()).map(|i| key_name(settings.key(Action::Tool(i)))).collect();
|
||||||
let sw = screen_width();
|
let sw = sw();
|
||||||
let sh = screen_height();
|
let sh = sh();
|
||||||
let (mx, my) = mouse_position();
|
let (mx, my) = mouse();
|
||||||
|
|
||||||
// ---------------- top bar
|
// ---------------- top bar (everything on the character grid)
|
||||||
let cols = (sw / CW()).ceil() as i32;
|
let cols = (sw / CW()).floor() as i32;
|
||||||
gfx.window(0.0, 0.0, cols, 3, S(), false, FRAME, BG);
|
gfx.window(0.0, 0.0, cols, 3, S(), false, FRAME, BG);
|
||||||
self.rects.push(Rect::new(0.0, 0.0, sw, 3.0 * CH()));
|
self.rects.push(Rect::new(0.0, 0.0, sw, 3.0 * CH()));
|
||||||
let y = CH();
|
let y = CH();
|
||||||
let mut x = CW() * 2.0;
|
let mut c = 2usize; // current column
|
||||||
let seg = |label: &str, lc: Color, value: &str, vc: Color, x: &mut f32| {
|
let col_x = |c: usize| c as f32 * CW();
|
||||||
gfx.text(label, *x, y, S(), lc);
|
let put = |s: &str, color: Color, c: &mut usize| {
|
||||||
*x += gfx.measure(label, S()) + CW();
|
gfx.text(s, col_x(*c), y, S(), color);
|
||||||
gfx.text(value, *x, y, S(), vc);
|
*c += s.chars().count();
|
||||||
*x += gfx.measure(value, S()) + CW() * 2.0;
|
|
||||||
};
|
};
|
||||||
seg(&tr("hud.gold"), GOLD, &format!("{}/{}", g.gold as i32, g.gold_cap() as i32), TXT, &mut x);
|
let bar = |frac: f32, color: Color, c: &mut usize| {
|
||||||
let mana = tr("hud.mana");
|
ascii_bar(gfx, col_x(*c), y, 6, frac, color, S());
|
||||||
gfx.text(&mana, x, y, S(), MANA);
|
*c += 6;
|
||||||
x += gfx.measure(&mana, S()) + CW() * 0.5;
|
};
|
||||||
bar(x, y + 3.0, 70.0, 10.0, g.mana / MANA_MAX, MANA);
|
put(&tr("hud.gold"), GOLD, &mut c);
|
||||||
x += 70.0 + CW() * 0.5;
|
c += 1;
|
||||||
gfx.text(&format!("{}", g.mana as i32), x, y, S(), TXT);
|
put(&format!("{}/{}", g.gold as i32, g.gold_cap() as i32), TXT, &mut c);
|
||||||
x += CW() * 5.0;
|
c += 2;
|
||||||
let core = tr("hud.core");
|
put(&tr("hud.mana"), MANA, &mut c);
|
||||||
gfx.text(&core, x, y, S(), RED);
|
c += 1;
|
||||||
x += gfx.measure(&core, S()) + CW() * 0.5;
|
bar(g.mana / MANA_MAX, MANA, &mut c);
|
||||||
bar(x, y + 3.0, 70.0, 10.0, g.core_hp / CORE_HP, RED);
|
c += 1;
|
||||||
x += 70.0 + CW() * 1.5;
|
put(&format!("{:<3}", g.mana as i32), TXT, &mut c);
|
||||||
let gate = tr("hud.gate");
|
c += 2;
|
||||||
gfx.text(&gate, x, y, S(), BLUE);
|
put(&tr("hud.core"), RED, &mut c);
|
||||||
x += gfx.measure(&gate, S()) + CW() * 0.5;
|
c += 1;
|
||||||
bar(x, y + 3.0, 70.0, 10.0, g.gate_hp / GATE_HP, BLUE);
|
bar(g.core_hp / CORE_HP, RED, &mut c);
|
||||||
x += 70.0 + CW() * 1.5;
|
c += 2;
|
||||||
|
put(&tr("hud.gate"), BLUE, &mut c);
|
||||||
|
c += 1;
|
||||||
|
bar(g.gate_hp / GATE_HP, BLUE, &mut c);
|
||||||
|
c += 2;
|
||||||
let cap = g.counts[1].min(30);
|
let cap = g.counts[1].min(30);
|
||||||
seg(&tr("hud.creatures"), TXT, &format!("{}/{}", g.creature_count(), cap), TXT, &mut x);
|
let mut segs: Vec<(String, Color, String)> = vec![
|
||||||
seg(&tr("hud.workers"), TXT, &format!("{}", g.worker_count()), TXT, &mut x);
|
(tr("hud.creatures"), TXT, format!("{}/{}", g.creature_count(), cap)),
|
||||||
|
(tr("hud.workers"), TXT, g.worker_count().to_string()),
|
||||||
|
];
|
||||||
let wave_col = if g.wave_timer < 30.0 { RED } else { TXT };
|
let wave_col = if g.wave_timer < 30.0 { RED } else { TXT };
|
||||||
seg(&trf("hud.wave", &[(g.wave + 1).to_string()]), wave_col, &mmss(g.wave_timer), wave_col, &mut x);
|
segs.push((trf("hud.wave", &[(g.wave + 1).to_string()]), wave_col, mmss(g.wave_timer)));
|
||||||
seg(&tr("hud.army"), TXT, &format!("{}", g.army_value() as i32), TXT, &mut x);
|
segs.push((tr("hud.army"), TXT, (g.army_value() as i32).to_string()));
|
||||||
|
if !g.hand.is_empty() {
|
||||||
|
segs.push((tr("hud.hand"), FRAME_HI, format!("{}/8", g.hand.len())));
|
||||||
|
}
|
||||||
let vol = if settings.muted { tr("hud.off") } else { format!("{}%", (settings.volume * 100.0).round() as i32) };
|
let vol = if settings.muted { tr("hud.off") } else { format!("{}%", (settings.volume * 100.0).round() as i32) };
|
||||||
// volume only if it still fits into the bar
|
segs.push(("\u{266a}".to_string(), DIM, vol));
|
||||||
if x + gfx.measure(&format!("♪ {}", vol), S()) < sw - CW() * 2.0 {
|
for (label, lc, value) in segs {
|
||||||
seg("♪", DIM, &vol, DIM, &mut x);
|
// only what still fits into the bar
|
||||||
|
if c + label.chars().count() + value.chars().count() + 2 > cols as usize - 1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
put(&label, lc, &mut c);
|
||||||
|
c += 1;
|
||||||
|
put(&value, if lc == DIM { DIM } else { TXT }, &mut c);
|
||||||
|
c += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- toolbar
|
// ---------------- toolbar: buttons of 6 x 3 characters, icons and labels on the grid
|
||||||
let tb_rows = 5;
|
let tb_rows = 5;
|
||||||
let tb_y = sh - tb_rows as f32 * CH();
|
let tb_y = sh - tb_rows as f32 * CH();
|
||||||
|
let tb_y = (tb_y / CH()).floor() * CH();
|
||||||
|
let tb_h = sh - tb_y;
|
||||||
|
let tb_rows = (tb_h / CH()).floor() as i32;
|
||||||
gfx.window(0.0, tb_y, cols, tb_rows, S(), false, FRAME, BG);
|
gfx.window(0.0, tb_y, cols, tb_rows, S(), false, FRAME, BG);
|
||||||
self.rects.push(Rect::new(0.0, tb_y, sw, tb_rows as f32 * CH()));
|
self.rects.push(Rect::new(0.0, tb_y, sw, tb_h));
|
||||||
self.tool_rects.clear();
|
self.tool_rects.clear();
|
||||||
let bw = (80.0 * K()).min(((sw - CW() * 2.0 - 150.0) / TOOLS.len() as f32 - 4.0 * K()).max(48.0));
|
|
||||||
let mut hover_tool = None;
|
let mut hover_tool = None;
|
||||||
|
let bw = 6.0 * CW();
|
||||||
|
let px = K(); // one pixel of the font, times the factor
|
||||||
for (i, &t) in TOOLS.iter().enumerate() {
|
for (i, &t) in TOOLS.iter().enumerate() {
|
||||||
let bx = CW() + i as f32 * (bw + 4.0 * K());
|
let bx = CW() + i as f32 * bw;
|
||||||
let by = tb_y + CH() * 0.75;
|
let by = tb_y + CH();
|
||||||
let r = Rect::new(bx, by, bw, CH() * 3.5);
|
if bx + bw > sw - CW() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let r = Rect::new(bx + px, by, bw - 2.0 * px, 3.0 * CH());
|
||||||
self.tool_rects.push(r);
|
self.tool_rects.push(r);
|
||||||
let hovered = r.contains(vec2(mx, my));
|
let hovered = r.contains(vec2(mx, my));
|
||||||
if hovered {
|
if hovered {
|
||||||
@@ -324,43 +414,27 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
let sel = self.tool == t;
|
let sel = self.tool == t;
|
||||||
draw_rectangle(r.x, r.y, r.w, r.h, if sel { Color::new(0.35, 0.22, 0.1, 1.0) } else if hovered { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
|
draw_rectangle(r.x, r.y, r.w, r.h, if sel { Color::new(0.35, 0.22, 0.1, 1.0) } else if hovered { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
|
||||||
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0, if sel { FRAME_HI } else { Color::new(0.35, 0.28, 0.2, 1.0) });
|
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0 * px, if sel { FRAME_HI } else { Color::new(0.35, 0.28, 0.2, 1.0) });
|
||||||
let ti = tool_info(t);
|
let ti = tool_info(t);
|
||||||
let icon = 32.0 * K();
|
// icon: the 16 px atlas cell at 2*F, centred on the grid
|
||||||
let small = (S() / 2.0).floor().max(1.0);
|
let icon = 32.0 * px;
|
||||||
gfx.draw_cell(ti.icon, bx + (bw - icon) * 0.5, by + 4.0 * K(), icon, icon, WHITE);
|
gfx.draw_cell(ti.icon, bx + (bw - icon) * 0.5, by + 4.0 * px, icon, icon, WHITE);
|
||||||
gfx.text(&keys[i], bx + 3.0, by + 3.0, small, if sel { FRAME_HI } else { DIM });
|
gfx.text(&keys[i], bx + 3.0 * px, by + 3.0 * px, SMALL(), if sel { FRAME_HI } else { DIM });
|
||||||
let lw = gfx.measure(&ti.label, small);
|
// label in small text, centred on whole small-character cells
|
||||||
gfx.text(&ti.label, bx + (bw - lw) * 0.5, by + 42.0 * K(), small, if sel { FRAME_HI } else { TXT });
|
let label: String = ti.label.chars().take(11).collect();
|
||||||
}
|
let lw = gfx.measure(&label, SMALL());
|
||||||
// hand contents
|
let lx = bx + ((bw - lw) / (GW * SMALL()) / 2.0).floor() * GW * SMALL();
|
||||||
let hx = CW() + TOOLS.len() as f32 * (bw + 4.0 * K()) + CW();
|
gfx.text(&label, lx, by + 2.0 * CH() + 4.0 * px, SMALL(), if sel { FRAME_HI } else { TXT });
|
||||||
if hx + 120.0 < sw - 200.0 {
|
|
||||||
gfx.text(&tr("hud.hand"), hx, tb_y + CH(), S(), TXT);
|
|
||||||
for (k, id) in g.hand.iter().enumerate() {
|
|
||||||
if let Some(e) = g.ents.iter().find(|e| e.id == *id) {
|
|
||||||
let sx = hx + (k % 4) as f32 * 26.0;
|
|
||||||
let sy = tb_y + CH() * 2.2 + (k / 4) as f32 * 24.0;
|
|
||||||
draw_texture_ex(
|
|
||||||
&gfx.sprites,
|
|
||||||
sx,
|
|
||||||
sy,
|
|
||||||
WHITE,
|
|
||||||
DrawTextureParams { dest_size: Some(vec2(20.0, 20.0)), source: Some(Gfx::sprite_rect(e.kind, false)), ..Default::default() },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if g.hand.is_empty() {
|
|
||||||
gfx.text(&tr("hud.empty"), hx, tb_y + CH() * 2.5, 1.0, DIM);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- minimap
|
// ---------------- minimap: whole device pixels per tile
|
||||||
let mm_cols = 16;
|
let ms = ((14.0 * CW()) / mw().max(mh()) as f32).floor().max(1.0);
|
||||||
let mm_rows = 12;
|
let (map_w, map_h) = (mw() as f32 * ms, mh() as f32 * ms);
|
||||||
|
let mm_cols = ((map_w / CW()).ceil() as i32 + 2).max(10);
|
||||||
|
let mm_rows = (map_h / CH()).ceil() as i32 + 2;
|
||||||
let mm_w = mm_cols as f32 * CW();
|
let mm_w = mm_cols as f32 * CW();
|
||||||
let mm_h = mm_rows as f32 * CH();
|
let mm_h = mm_rows as f32 * CH();
|
||||||
let mm_x = sw - mm_w;
|
let mm_x = ((sw - mm_w) / CW()).floor() * CW();
|
||||||
let mm_y = tb_y - mm_h;
|
let mm_y = tb_y - mm_h;
|
||||||
gfx.window_titled(mm_x, mm_y, mm_cols, mm_rows, S(), false, FRAME, BG, &tr("ui.map"), FRAME_HI);
|
gfx.window_titled(mm_x, mm_y, mm_cols, mm_rows, S(), false, FRAME, BG, &tr("ui.map"), FRAME_HI);
|
||||||
self.rects.push(Rect::new(mm_x, mm_y, mm_w, mm_h));
|
self.rects.push(Rect::new(mm_x, mm_y, mm_w, mm_h));
|
||||||
@@ -383,39 +457,37 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
self.minimap_tex.update(&self.minimap_img);
|
self.minimap_tex.update(&self.minimap_img);
|
||||||
}
|
}
|
||||||
// keep the map's aspect ratio inside the window
|
let mrx = (mm_x + (mm_w - map_w) * 0.5).floor();
|
||||||
let avail = (mm_w - CW() * 2.0).min(mm_h - CH() * 2.0);
|
let mry = (mm_y + (mm_h - map_h) * 0.5).floor();
|
||||||
let ms = avail / mw().max(mh()) as f32;
|
|
||||||
let (map_w, map_h) = (mw() as f32 * ms, mh() as f32 * ms);
|
|
||||||
let mrx = mm_x + (mm_w - map_w) * 0.5;
|
|
||||||
let mry = mm_y + CH() + (mm_h - CH() * 2.0 - map_h) * 0.5;
|
|
||||||
self.minimap_rect = Rect::new(mrx, mry, map_w, map_h);
|
self.minimap_rect = Rect::new(mrx, mry, map_w, map_h);
|
||||||
draw_texture_ex(&self.minimap_tex, mrx, mry, WHITE, DrawTextureParams { dest_size: Some(vec2(map_w, map_h)), ..Default::default() });
|
draw_texture_ex(&self.minimap_tex, mrx, mry, WHITE, DrawTextureParams { dest_size: Some(vec2(map_w, map_h)), ..Default::default() });
|
||||||
|
let dot = (2.0 * px).max(ms);
|
||||||
for e in &g.ents {
|
for e in &g.ents {
|
||||||
if e.alive && !e.held {
|
if e.alive && !e.held {
|
||||||
let c = if e.hero { Color::new(0.6, 0.85, 1.0, 1.0) } else if e.kind == EK::Worker { Color::new(1.0, 0.6, 0.3, 1.0) } else { Color::new(1.0, 0.25, 0.25, 1.0) };
|
let c = if e.hero { Color::new(0.6, 0.85, 1.0, 1.0) } else if e.kind == EK::Worker { Color::new(1.0, 0.6, 0.3, 1.0) } else { Color::new(1.0, 0.25, 0.25, 1.0) };
|
||||||
draw_rectangle(mrx + e.x * ms - 1.0, mry + e.y * ms - 1.0, 3.0, 3.0, c);
|
draw_rectangle((mrx + e.x.floor() * ms).floor(), (mry + e.y.floor() * ms).floor(), dot, dot, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(f) = g.flag {
|
if let Some(f) = g.flag {
|
||||||
let (fx, fy) = xy(f);
|
let (fx, fy) = xy(f);
|
||||||
draw_rectangle(mrx + fx as f32 * ms - 2.0, mry + fy as f32 * ms - 2.0, 5.0, 5.0, RED);
|
draw_rectangle(mrx + fx as f32 * ms - px, mry + fy as f32 * ms - px, dot + 2.0 * px, dot + 2.0 * px, RED);
|
||||||
}
|
}
|
||||||
// expected entry points of the hero wave (blinking)
|
// expected entry points of the hero wave (blinking)
|
||||||
if (now * 3.0) as i64 % 2 == 0 {
|
if (now * 3.0) as i64 % 2 == 0 {
|
||||||
for &(ex, ey) in &g.planned_entries {
|
for &(ex, ey) in &g.planned_entries {
|
||||||
let (px, py) = (mrx + ex as f32 * ms, mry + ey as f32 * ms);
|
let (ppx, ppy) = (mrx + ex as f32 * ms, mry + ey as f32 * ms);
|
||||||
draw_rectangle_lines(px - 4.0, py - 4.0, 9.0, 9.0, 2.0, Color::new(0.6, 0.85, 1.0, 1.0));
|
draw_rectangle_lines(ppx - 4.0 * px, ppy - 4.0 * px, 9.0 * px, 9.0 * px, 2.0 * px, Color::new(0.6, 0.85, 1.0, 1.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// camera footprint on the ground
|
// camera footprint on the ground (the 3D camera works in CSS pixels)
|
||||||
let corners = [cam.ground_point(0.0, 0.0), cam.ground_point(sw, 0.0), cam.ground_point(sw, sh), cam.ground_point(0.0, sh)];
|
let (lw, lh) = (screen_width(), screen_height());
|
||||||
|
let corners = [cam.ground_point(0.0, 0.0), cam.ground_point(lw, 0.0), cam.ground_point(lw, lh), cam.ground_point(0.0, lh)];
|
||||||
for k in 0..4 {
|
for k in 0..4 {
|
||||||
let a = corners[k];
|
let a = corners[k];
|
||||||
let b = corners[(k + 1) % 4];
|
let b = corners[(k + 1) % 4];
|
||||||
let clampp = |p: Vec2| vec2(mrx + p.x.clamp(0.0, mw() as f32) * ms, mry + p.y.clamp(0.0, mh() as f32) * ms);
|
let clampp = |p: Vec2| vec2(mrx + p.x.clamp(0.0, mw() as f32) * ms, mry + p.y.clamp(0.0, mh() as f32) * ms);
|
||||||
let (pa, pb) = (clampp(a), clampp(b));
|
let (pa, pb) = (clampp(a), clampp(b));
|
||||||
draw_line(pa.x, pa.y, pb.x, pb.y, 1.5, WHITE);
|
draw_line(pa.x, pa.y, pb.x, pb.y, px, WHITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- info panel
|
// ---------------- info panel
|
||||||
@@ -471,8 +543,8 @@ impl Ui {
|
|||||||
}
|
}
|
||||||
lines.push((trf("ui.key", &[keys[i].clone()]), DIM));
|
lines.push((trf("ui.key", &[keys[i].clone()]), DIM));
|
||||||
let rows = lines.len() as i32 + 2;
|
let rows = lines.len() as i32 + 2;
|
||||||
let tx = (mx - tcols as f32 * CW() * 0.5).clamp(0.0, sw - tcols as f32 * CW());
|
let tx = (((mx - tcols as f32 * CW() * 0.5) / CW()).floor() * CW()).clamp(0.0, sw - tcols as f32 * CW());
|
||||||
let ty = tb_y - rows as f32 * CH() - 4.0;
|
let ty = tb_y - rows as f32 * CH();
|
||||||
gfx.window_titled(tx, ty, tcols as i32, rows, S(), false, FRAME_HI, BG, &ti.name, FRAME_HI);
|
gfx.window_titled(tx, ty, tcols as i32, rows, S(), false, FRAME_HI, BG, &ti.name, FRAME_HI);
|
||||||
for (k, (l, c)) in lines.iter().enumerate() {
|
for (k, (l, c)) in lines.iter().enumerate() {
|
||||||
gfx.text(l, tx + CW() * 2.0, ty + CH() * (k as f32 + 1.0), S(), *c);
|
gfx.text(l, tx + CW() * 2.0, ty + CH() * (k as f32 + 1.0), S(), *c);
|
||||||
@@ -483,8 +555,8 @@ impl Ui {
|
|||||||
|
|
||||||
// Dialogs on top of everything (help, language selection, end screen, pause)
|
// Dialogs on top of everything (help, language selection, end screen, pause)
|
||||||
pub fn draw_dialogs(&mut self, g: &Game, gfx: &Gfx) {
|
pub fn draw_dialogs(&mut self, g: &Game, gfx: &Gfx) {
|
||||||
let sw = screen_width();
|
let sw = sw();
|
||||||
let sh = screen_height();
|
let sh = sh();
|
||||||
if self.show_langsel {
|
if self.show_langsel {
|
||||||
self.draw_langsel(gfx);
|
self.draw_langsel(gfx);
|
||||||
} else if g.state != 0 {
|
} else if g.state != 0 {
|
||||||
@@ -493,13 +565,13 @@ impl Ui {
|
|||||||
self.draw_help(gfx);
|
self.draw_help(gfx);
|
||||||
} else if g.paused {
|
} else if g.paused {
|
||||||
let w = 24;
|
let w = 24;
|
||||||
let x = (sw - w as f32 * CW()) * 0.5;
|
let x = grid_center(sw, w as f32 * CW());
|
||||||
let y = sh * 0.35;
|
let y = sh * 0.35;
|
||||||
gfx.window(x, y, w, 5, S(), true, FRAME_HI, BG);
|
gfx.window(x, y, w, 5, S(), true, FRAME_HI, BG);
|
||||||
let t = tr("ui.pause");
|
let t = tr("ui.pause");
|
||||||
gfx.text(&t, (sw - gfx.measure(&t, 3.0)) * 0.5, y + CH(), 3.0, FRAME_HI);
|
gfx.text(&t, grid_center(sw, gfx.measure(&t, TITLE())), y + CH(), TITLE(), FRAME_HI);
|
||||||
let t2 = tr("ui.pause_hint");
|
let t2 = tr("ui.pause_hint");
|
||||||
gfx.text(&t2, (sw - gfx.measure(&t2, S())) * 0.5, y + CH() * 3.0, S(), DIM);
|
gfx.text(&t2, grid_center(sw, gfx.measure(&t2, S())), y + CH() * 3.0, S(), DIM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,10 +601,10 @@ impl Ui {
|
|||||||
let lines: Vec<(String, Color)> = keys.iter().map(|(k, c)| (if k.is_empty() { String::new() } else { tr(k) }, *c)).collect();
|
let lines: Vec<(String, Color)> = keys.iter().map(|(k, c)| (if k.is_empty() { String::new() } else { tr(k) }, *c)).collect();
|
||||||
let cols = 74;
|
let cols = 74;
|
||||||
let rows = lines.len() as i32 + 4;
|
let rows = lines.len() as i32 + 4;
|
||||||
let sw = screen_width();
|
let sw = sw();
|
||||||
let sh = screen_height();
|
let sh = sh();
|
||||||
let x = ((sw - cols as f32 * CW()) * 0.5).max(0.0);
|
let x = (grid_center(sw, cols as f32 * CW())).max(0.0);
|
||||||
let y = ((sh - rows as f32 * CH()) * 0.5).max(0.0);
|
let y = (grid_center_y(sh, rows as f32 * CH())).max(0.0);
|
||||||
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), "DungeonCraft", FRAME_HI);
|
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), "DungeonCraft", FRAME_HI);
|
||||||
for (k, (l, c)) in lines.iter().enumerate() {
|
for (k, (l, c)) in lines.iter().enumerate() {
|
||||||
gfx.text(l, x + CW() * 3.0, y + CH() * (k as f32 + 2.0), S(), *c);
|
gfx.text(l, x + CW() * 3.0, y + CH() * (k as f32 + 2.0), S(), *c);
|
||||||
@@ -542,22 +614,22 @@ impl Ui {
|
|||||||
|
|
||||||
// First start: choose the language (names shown in their own language)
|
// First start: choose the language (names shown in their own language)
|
||||||
fn draw_langsel(&mut self, gfx: &Gfx) {
|
fn draw_langsel(&mut self, gfx: &Gfx) {
|
||||||
let sw = screen_width();
|
let sw = sw();
|
||||||
let sh = screen_height();
|
let sh = sh();
|
||||||
draw_rectangle(0.0, 0.0, sw, sh, Color::new(0.0, 0.0, 0.0, 0.6));
|
draw_rectangle(0.0, 0.0, sw, sh, Color::new(0.0, 0.0, 0.0, 0.6));
|
||||||
let langs = languages();
|
let langs = languages();
|
||||||
let cols = 50;
|
let cols = 50;
|
||||||
let rows = 6 + langs.len() as i32 * 3;
|
let rows = 6 + langs.len() as i32 * 3;
|
||||||
let x = ((sw - cols as f32 * CW()) * 0.5).max(0.0);
|
let x = (grid_center(sw, cols as f32 * CW())).max(0.0);
|
||||||
let y = ((sh - rows as f32 * CH()) * 0.5).max(0.0);
|
let y = (grid_center_y(sh, rows as f32 * CH())).max(0.0);
|
||||||
// the title shows all language names, since no language is chosen yet
|
// the title shows all language names, since no language is chosen yet
|
||||||
let title = langs.iter().map(|(c, _)| crate::i18n::tr_in(c, "langsel.title")).collect::<Vec<_>>().join(" / ");
|
let title = langs.iter().map(|(c, _)| crate::i18n::tr_in(c, "langsel.title")).collect::<Vec<_>>().join(" / ");
|
||||||
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &title, FRAME_HI);
|
gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &title, FRAME_HI);
|
||||||
self.lang_rects.clear();
|
self.lang_rects.clear();
|
||||||
let (mx, my) = mouse_position();
|
let (mx, my) = mouse();
|
||||||
for (k, (code, name)) in langs.iter().enumerate() {
|
for (k, (code, name)) in langs.iter().enumerate() {
|
||||||
let bw = 24.0 * CW();
|
let bw = 24.0 * CW();
|
||||||
let bx = (sw - bw) * 0.5;
|
let bx = grid_center(sw, bw);
|
||||||
let by = y + CH() * (2.0 + k as f32 * 3.0);
|
let by = y + CH() * (2.0 + k as f32 * 3.0);
|
||||||
let r = Rect::new(bx, by, bw, CH() * 2.0);
|
let r = Rect::new(bx, by, bw, CH() * 2.0);
|
||||||
let hov = r.contains(vec2(mx, my));
|
let hov = r.contains(vec2(mx, my));
|
||||||
@@ -569,35 +641,35 @@ impl Ui {
|
|||||||
let hint_y = y + CH() * (2.0 + langs.len() as f32 * 3.0 + 0.5);
|
let hint_y = y + CH() * (2.0 + langs.len() as f32 * 3.0 + 0.5);
|
||||||
for (k, (c, _)) in langs.iter().enumerate() {
|
for (k, (c, _)) in langs.iter().enumerate() {
|
||||||
let h = crate::i18n::tr_in(c, "langsel.hint");
|
let h = crate::i18n::tr_in(c, "langsel.hint");
|
||||||
gfx.text(&h, x + CW() * 2.0, hint_y + CH() * k as f32, 1.0, DIM);
|
gfx.text(&h, x + CW() * 2.0, hint_y + CH() * k as f32, SMALL(), DIM);
|
||||||
}
|
}
|
||||||
self.rects.push(Rect::new(0.0, 0.0, sw, sh));
|
self.rects.push(Rect::new(0.0, 0.0, sw, sh));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_end(&mut self, g: &Game, gfx: &Gfx) {
|
fn draw_end(&mut self, g: &Game, gfx: &Gfx) {
|
||||||
let sw = screen_width();
|
let sw = sw();
|
||||||
let sh = screen_height();
|
let sh = sh();
|
||||||
draw_rectangle(0.0, 0.0, sw, sh, Color::new(0.0, 0.0, 0.0, 0.5));
|
draw_rectangle(0.0, 0.0, sw, sh, Color::new(0.0, 0.0, 0.0, 0.5));
|
||||||
let win = g.state == 1;
|
let win = g.state == 1;
|
||||||
let cols = 44;
|
let cols = 44;
|
||||||
let rows = 11;
|
let rows = 11;
|
||||||
let x = (sw - cols as f32 * CW()) * 0.5;
|
let x = grid_center(sw, cols as f32 * CW());
|
||||||
let y = (sh - rows as f32 * CH()) * 0.5;
|
let y = grid_center_y(sh, rows as f32 * CH());
|
||||||
let col = if win { GOLD } else { RED };
|
let col = if win { GOLD } else { RED };
|
||||||
gfx.window(x, y, cols, rows, S(), true, col, Color::new(0.05, 0.03, 0.04, 0.97));
|
gfx.window(x, y, cols, rows, S(), true, col, Color::new(0.05, 0.03, 0.04, 0.97));
|
||||||
let title = tr(if win { "end.victory" } else { "end.defeat" });
|
let title = tr(if win { "end.victory" } else { "end.defeat" });
|
||||||
gfx.text(&title, (sw - gfx.measure(&title, 4.0)) * 0.5, y + CH() * 1.5, 4.0, col);
|
gfx.text(&title, grid_center(sw, gfx.measure(&title, TITLE())), y + CH(), TITLE(), col);
|
||||||
let sub = tr(if win { "end.victory_sub" } else { "end.defeat_sub" });
|
let sub = tr(if win { "end.victory_sub" } else { "end.defeat_sub" });
|
||||||
gfx.text(&sub, (sw - gfx.measure(&sub, S())) * 0.5, y + CH() * 4.5, S(), TXT);
|
gfx.text(&sub, grid_center(sw, gfx.measure(&sub, S())), y + CH() * 4.5, S(), TXT);
|
||||||
let st = trf("end.stats", &[mmss(g.time), g.wave.to_string(), (g.army_value() as i32).to_string()]);
|
let st = trf("end.stats", &[mmss(g.time), g.wave.to_string(), (g.army_value() as i32).to_string()]);
|
||||||
gfx.text(&st, (sw - gfx.measure(&st, S())) * 0.5, y + CH() * 6.0, S(), DIM);
|
gfx.text(&st, grid_center(sw, gfx.measure(&st, S())), y + CH() * 6.0, S(), DIM);
|
||||||
let b = format!(" {} ", tr("end.new"));
|
let b = format!(" {} ", tr("end.new"));
|
||||||
let b = b.as_str();
|
let b = b.as_str();
|
||||||
let bw = gfx.measure(b, S()) + CW() * 2.0;
|
let bw = gfx.measure(b, S()) + CW() * 2.0;
|
||||||
let bx = (sw - bw) * 0.5;
|
let bx = grid_center(sw, bw);
|
||||||
let by = y + CH() * 7.75;
|
let by = y + CH() * 7.75;
|
||||||
self.end_button = Rect::new(bx, by, bw, CH() * 2.0);
|
self.end_button = Rect::new(bx, by, bw, CH() * 2.0);
|
||||||
let (mx, my) = mouse_position();
|
let (mx, my) = mouse();
|
||||||
let hov = self.end_button.contains(vec2(mx, my));
|
let hov = self.end_button.contains(vec2(mx, my));
|
||||||
draw_rectangle(bx, by, bw, CH() * 2.0, if hov { Color::new(0.35, 0.22, 0.1, 1.0) } else { Color::new(0.15, 0.1, 0.08, 1.0) });
|
draw_rectangle(bx, by, bw, CH() * 2.0, if hov { Color::new(0.35, 0.22, 0.1, 1.0) } else { Color::new(0.15, 0.1, 0.08, 1.0) });
|
||||||
draw_rectangle_lines(bx, by, bw, CH() * 2.0, 2.0, col);
|
draw_rectangle_lines(bx, by, bw, CH() * 2.0, 2.0, col);
|
||||||
|
|||||||
Reference in new issue
Block a user