Rank badges and level equipment; no edge scrolling before the mouse has moved
- Military rank badges instead of level numbers: Lv 2-4 bronze chevrons, Lv 5-7 silver bars, Lv 8-9 gold stars, Lv 10 gold star in a wreath; rank names in the unit info (EN/DE) - Equipment grows with the level: shoulder pieces (bronze/silver/gold), helmets with horns (creatures) or plumes (heroes), capes from Lv 7, glowing ring from Lv 9, aura at Lv 10 - Fix: before the first mouse move the pointer counts as (0, 0) and the edge scrolling moved the camera at the start and after loading a savegame Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
52d49b0243
commit
729dd4fe4c
6 files changed
+170
-5
No files matched your search
+57
@@ -1183,6 +1183,63 @@ impl Gfx {
|
||||
s.chars().count() as f32 * GW * scale
|
||||
}
|
||||
|
||||
// Military rank badge for a level (k = size of one badge pixel in device pixels):
|
||||
// Lv 2-4: 1-3 bronze chevrons, Lv 5-7: 1-3 silver bars, Lv 8-9: 1-2 gold stars,
|
||||
// Lv 10: a gold star in a green wreath. Lv 1 has none. Returns the width drawn.
|
||||
pub fn badge(&self, x: f32, y: f32, level: u32, k: f32) -> f32 {
|
||||
let px = |gx: i32, gy: i32, c: Color| draw_rectangle(x + gx as f32 * k, y + gy as f32 * k, k, k, c);
|
||||
let plate = |w: i32, h: i32| draw_rectangle(x - k, y - k, (w + 2) as f32 * k, (h + 2) as f32 * k, Color::new(0.05, 0.03, 0.04, 0.85));
|
||||
let bronze = Color::from_rgba(214, 140, 70, 255);
|
||||
let silver = Color::from_rgba(215, 222, 235, 255);
|
||||
let gold = Color::from_rgba(255, 210, 70, 255);
|
||||
let star = |ox: i32, oy: i32, c: Color| {
|
||||
for (gx, gy) in [(2, 0), (1, 1), (2, 1), (3, 1), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2), (1, 3), (2, 3), (3, 3), (0, 4), (1, 4), (3, 4), (4, 4)] {
|
||||
px(ox + gx, oy + gy, c);
|
||||
}
|
||||
};
|
||||
match level {
|
||||
0 | 1 => 0.0,
|
||||
2..=4 => {
|
||||
let n = (level - 1) as i32;
|
||||
plate(5, n * 2 + 1);
|
||||
for c in 0..n {
|
||||
// a chevron pointing up
|
||||
for (gx, gy) in [(2, 0), (1, 1), (3, 1), (0, 2), (4, 2)] {
|
||||
px(gx, c * 2 + gy - if gy == 2 { 1 } else { 0 }, bronze);
|
||||
}
|
||||
}
|
||||
7.0 * k
|
||||
}
|
||||
5..=7 => {
|
||||
let n = (level - 4) as i32;
|
||||
plate(5, n * 2 - 1);
|
||||
for b in 0..n {
|
||||
for gx in 0..5 {
|
||||
px(gx, b * 2, silver);
|
||||
}
|
||||
}
|
||||
7.0 * k
|
||||
}
|
||||
8 | 9 => {
|
||||
let n = (level - 7) as i32;
|
||||
plate(n * 6 - 1, 5);
|
||||
for s in 0..n {
|
||||
star(s * 6, 0, gold);
|
||||
}
|
||||
(n * 6 + 1) as f32 * k
|
||||
}
|
||||
_ => {
|
||||
plate(9, 7);
|
||||
let green = Color::from_rgba(90, 170, 80, 255);
|
||||
for (gx, gy) in [(0, 2), (0, 3), (0, 4), (1, 5), (2, 6), (8, 2), (8, 3), (8, 4), (7, 5), (6, 6), (3, 6), (5, 6), (1, 1), (7, 1)] {
|
||||
px(gx, gy, green);
|
||||
}
|
||||
star(2, 1, gold);
|
||||
11.0 * k
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// text with a dark shadow
|
||||
pub fn text_sh(&self, s: &str, x: f32, y: f32, scale: f32, color: Color) {
|
||||
self.text(s, x + scale, y + scale, scale, Color::new(0.0, 0.0, 0.0, color.a * 0.85));
|
||||
|
||||
+7
-2
@@ -322,7 +322,9 @@ async fn main() {
|
||||
app.game.hints = app.settings.hints;
|
||||
let mut inp = Input { ldown: None, rdown: None, rlast: (0.0, 0.0), rdrag: false, mlast: None, minimap_drag: false, tlast: None, touch_world: false, inspect: None };
|
||||
let mut touch = TouchInput::new();
|
||||
let mut auto_touch = platform::coarse_pointer(); // automatic mode: touch interface after a touch, desktop after a mouse click
|
||||
let mut auto_touch = platform::coarse_pointer();
|
||||
// until the mouse has really moved its position is (0, 0): no edge scrolling before that
|
||||
let mut mouse_moved = false; // automatic mode: touch interface after a touch, desktop after a mouse click
|
||||
let mut autosave_t = 0.0f32;
|
||||
let mut cache_t = 0.0f32;
|
||||
|
||||
@@ -504,7 +506,10 @@ async fn main() {
|
||||
move_cam(pan, 0.0, cam);
|
||||
}
|
||||
let edge = 6.0;
|
||||
if keys.edge_scroll && !touch_mode && mx >= 0.0 && my >= 0.0 && mx < screen_width() && my < screen_height() {
|
||||
if !mouse_moved && mouse_delta_position() != Vec2::ZERO {
|
||||
mouse_moved = true;
|
||||
}
|
||||
if keys.edge_scroll && mouse_moved && !touch_mode && mx >= 0.0 && my >= 0.0 && mx < screen_width() && my < screen_height() {
|
||||
if mx < edge {
|
||||
move_cam(-pan, 0.0, cam);
|
||||
} else if mx > screen_width() - edge {
|
||||
|
||||
+79
-3
@@ -1020,6 +1020,7 @@ impl R3d {
|
||||
}
|
||||
draw_landmarks_solid(g, t);
|
||||
draw_towers_solid(g);
|
||||
draw_gear(g, cam);
|
||||
crate::props::draw_props_solid(g, cam, t);
|
||||
|
||||
// ---- unlit glow / overlays
|
||||
@@ -1052,6 +1053,7 @@ impl R3d {
|
||||
// translucent glows and effects last (they write depth, see glow_material)
|
||||
draw_landmarks_glow(g, t);
|
||||
draw_towers_glow(g, t);
|
||||
draw_gear_glow(g, cam, t);
|
||||
crate::props::draw_props_glow(g, cam, t);
|
||||
for f in &g.fx {
|
||||
draw_fx(f, t);
|
||||
@@ -1176,9 +1178,11 @@ impl R3d {
|
||||
if e.guard {
|
||||
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 * F() as f32 {
|
||||
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));
|
||||
// rank badge left of the health bar
|
||||
if e.level > 1 && px_per_tile >= 20.0 * F() as f32 {
|
||||
let k = 2.0 * F() as f32;
|
||||
let x = head.x - (cols as f32 * cw * 0.5).floor() - 12.0 * k;
|
||||
gfx.badge(x, head.y - GH * small - 4.0 * k, e.level, k);
|
||||
}
|
||||
if matches!(e.job, Job::Sleep(_)) && !e.moving {
|
||||
let k = (t * 1.5 + e.anim) % 1.0;
|
||||
@@ -1216,6 +1220,78 @@ fn shade_c(r: u8, g: u8, b: u8, f: f32) -> Color {
|
||||
Color::new(r as f32 / 255.0 * f, g as f32 / 255.0 * f, b as f32 / 255.0 * f, 1.0)
|
||||
}
|
||||
|
||||
// size of a unit's sprite (same as in the billboards)
|
||||
fn sprite_size(k: EK) -> f32 {
|
||||
if k == EK::Chainbreaker {
|
||||
1.0
|
||||
} else if matches!(k, EK::Ogre | EK::Paladin | EK::Jailer) {
|
||||
0.8
|
||||
} else {
|
||||
0.62
|
||||
}
|
||||
}
|
||||
|
||||
fn gear_shown(e: &Ent) -> bool {
|
||||
e.alive && !e.held && e.captive == 0 && e.level >= 3 && !(matches!(e.job, Job::Sleep(_)) && !e.moving)
|
||||
}
|
||||
|
||||
// Equipment that grows with the level: shoulder pieces (Lv 3+: bronze, 5+: silver, 8+: gold),
|
||||
// a helmet (Lv 5+: horns for creatures, a plume for heroes) and a cape (Lv 7+).
|
||||
fn draw_gear(g: &Game, cam: &Cam) {
|
||||
let (_, r, _) = cam.basis();
|
||||
let toward = cam.eye_dir();
|
||||
for e in g.ents.iter().filter(|e| gear_shown(e)) {
|
||||
let w = sprite_size(e.kind);
|
||||
let bob = if e.moving { (e.anim * 12.0).sin().abs() * 0.06 } else { 0.0 };
|
||||
let base = vec3(e.x, bob, e.y) + toward * 0.5;
|
||||
let at = |dx: f32, dy: f32| base + r * (dx * w) + Vec3::Y * (dy * w);
|
||||
let metal = if e.level >= 8 {
|
||||
Color::from_rgba(240, 200, 70, 255)
|
||||
} else if e.level >= 5 {
|
||||
Color::from_rgba(200, 206, 220, 255)
|
||||
} else {
|
||||
Color::from_rgba(190, 120, 60, 255)
|
||||
};
|
||||
if e.level >= 7 {
|
||||
// cape behind the figure
|
||||
let back = base - toward * 0.1;
|
||||
draw_cube(back + Vec3::Y * (0.5 * w), vec3(0.42 * w, 0.55 * w, 0.42 * w), None, if e.hero { Color::from_rgba(40, 70, 150, 255) } else { Color::from_rgba(120, 20, 30, 255) });
|
||||
}
|
||||
for side in [-1.0f32, 1.0] {
|
||||
draw_cube(at(side * 0.26, 0.6), Vec3::splat(0.09 * w + 0.02), None, metal);
|
||||
}
|
||||
if e.level >= 5 {
|
||||
draw_cube(at(0.0, 0.93), vec3(0.2 * w, 0.06 * w, 0.2 * w), None, metal);
|
||||
if e.hero {
|
||||
draw_cube(at(0.0, 1.02), vec3(0.04, 0.1 * w, 0.04), None, Color::from_rgba(200, 40, 40, 255));
|
||||
} else {
|
||||
for side in [-1.0f32, 1.0] {
|
||||
draw_cube(at(side * 0.16, 1.0), vec3(0.035, 0.09 * w, 0.035), None, Color::from_rgba(230, 220, 200, 255));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lv 9+: a glowing ring above the head, Lv 10 also a faint aura at the feet
|
||||
fn draw_gear_glow(g: &Game, cam: &Cam, t: f32) {
|
||||
let toward = cam.eye_dir();
|
||||
for e in g.ents.iter().filter(|e| gear_shown(e) && e.level >= 9) {
|
||||
let w = sprite_size(e.kind);
|
||||
let base = vec3(e.x, 0.0, e.y) + toward * 0.5;
|
||||
let c = if e.hero { Color::new(0.8, 0.9, 1.0, 0.8) } else { Color::new(1.0, 0.8, 0.3, 0.8) };
|
||||
let y = w * 1.12 + (t * 2.0 + e.anim).sin() * 0.02;
|
||||
for k in 0..8 {
|
||||
let a0 = k as f32 * 0.785 + t;
|
||||
let a1 = a0 + 0.785;
|
||||
line3(base + vec3(a0.cos() * 0.12, y, a0.sin() * 0.12), base + vec3(a1.cos() * 0.12, y, a1.sin() * 0.12), 0.025, c);
|
||||
}
|
||||
if e.level >= 10 {
|
||||
disc(vec3(e.x, 0.015, e.y), 0.42 + 0.03 * (t * 3.0).sin(), Color::new(c.r, c.g, c.b, 0.18), 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_towers_solid(g: &Game) {
|
||||
for tw in &g.towers {
|
||||
let (x, z) = tw.pos();
|
||||
|
||||
@@ -1023,6 +1023,7 @@ pub fn hover_info(g: &Game, ent: Option<usize>, tile: Option<(i32, i32)>) -> Vec
|
||||
let e = &g.ents[j];
|
||||
let s = st(e.kind);
|
||||
out.push((trf("info.level", &[tr(s.name), e.level.to_string()]), if e.hero { BLUE } else { FRAME_HI }));
|
||||
out.push((trf("info.rank", &[tr(&format!("rank.{}", e.level.clamp(1, 10)))]), GOLD));
|
||||
out.push((trf("info.hp", &[(e.hp.max(0.0) as i32).to_string(), (e.max_hp as i32).to_string()]), GREEN));
|
||||
if e.hero && e.captive != 0 {
|
||||
out.push((tr(["", "info.unconscious", "info.carried", "info.tortured"][e.captive.min(3) as usize]), RED));
|
||||
|
||||
Reference in new issue
Block a user