Guard and attack markers (shields and swords, up to eight each, right click removes); the world and the interface are copied opaque so glows keep their brightness

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-06 07:47:21 +01:00
1 parent a1a53273cd
commit c9dafb1606
24 files changed
+470 -46

No files matched your search

+6
View File
@@ -92,6 +92,10 @@ pub enum Act {
y: i32,
},
CancelOrders,
Unmark {
x: f32,
y: f32,
}, // right click with an order tool: removes the marker there (or cancels the guard orders)
// gold for an allied master of a match (`crate::netplay::GOLD_ALL`: everything)
SendGold {
to: u8,
@@ -172,6 +176,7 @@ impl Game {
},
Act::Guard { x, y } => (point(x as f32, y as f32), MARK_GUARD, 10.0),
Act::CancelOrders => (point(self.map.core.0 as f32, self.map.core.1 as f32), MARK_CANCEL, 4.0),
Act::Unmark { x, y } => (point(x, y), MARK_CANCEL, 3.0),
};
let fixed = matches!(act, Act::Guard { .. } | Act::Attack { .. } | Act::Flag { .. });
let dur = match act {
@@ -220,6 +225,7 @@ impl Game {
}
Act::Guard { x, y } => self.order_guard(x, y),
Act::CancelOrders => self.cancel_orders(),
Act::Unmark { x, y } => self.unmark(x, y),
Act::SendGold { to, amount } => self.send_gold(to, amount),
}
}
+34 -2
View File
@@ -221,6 +221,7 @@ pub enum Job {
Spar(usize), // sparring in the arena
MendBridge(usize), // imp mends a worn bridge tile
RaiseWall(usize), // imp raises a planned wall on own floor
March(usize), // creature follows an attack marker (sword) at this tile
}
#[derive(Clone, Serialize, Deserialize)]
@@ -499,6 +500,8 @@ pub struct MasterState {
pub room_level: Vec<u8>,
pub gear_stock: Vec<u16>,
pub gear_work: f32,
pub guard_marks: Vec<usize>,
pub attack_marks: Vec<usize>,
pub springs: usize,
pub known: Vec<bool>,
pub researchable: Vec<bool>,
@@ -624,6 +627,12 @@ pub struct Game {
#[serde(default)]
pub happenings: Option<crate::events::Events>, // random events (free games and versus), see events.rs
#[serde(default)]
pub guard_marks: Vec<usize>, // guard markers (shields) of the master being processed (orders.rs)
#[serde(default)]
pub attack_marks: Vec<usize>, // attack markers (swords)
#[serde(default)]
pub marks_t: f32,
#[serde(default)]
pub stats: crate::stats::GameStats, // statistics of the master at this computer (see stats.rs)
#[serde(default)]
pub wall_plans: Vec<(usize, u8)>, // walls to raise: tile, master side (see walls.rs)
@@ -967,6 +976,9 @@ impl Game {
bridgework: Default::default(),
trading: Default::default(),
happenings: None,
guard_marks: Vec::new(),
attack_marks: Vec::new(),
marks_t: 0.0,
stats: Default::default(),
wall_plans: Vec::new(),
wood: crate::wood::WOOD_START,
@@ -1476,6 +1488,8 @@ impl Game {
room_level: self.room_level.clone(),
gear_stock: self.gear_stock.clone(),
gear_work: self.gear_work,
guard_marks: std::mem::take(&mut self.guard_marks),
attack_marks: std::mem::take(&mut self.attack_marks),
springs: self.springs,
known: std::mem::take(&mut self.known),
researchable: std::mem::take(&mut self.researchable),
@@ -1509,6 +1523,8 @@ impl Game {
self.room_level = m.room_level;
self.gear_stock = m.gear_stock;
self.gear_work = m.gear_work;
self.guard_marks = m.guard_marks;
self.attack_marks = m.attack_marks;
self.springs = m.springs;
self.known = m.known;
self.researchable = m.researchable;
@@ -1715,6 +1731,7 @@ impl Game {
}
self.update_forge(dt);
self.update_research();
self.update_marks(dt);
}
fn after_economy(&mut self, dt: f32) {
@@ -3776,7 +3793,7 @@ impl Game {
// a new creature builds its own bed in the lair soon after its arrival
let bed_kind = if flies(e.kind) { Kind::Aerie } else { Kind::Lair };
let beds = if flies(e.kind) { self.counts[7] } else { self.counts[1] };
if e.bed.is_none() && beds > 0 && !matches!(e.job, Job::Attack(_) | Job::Leave | Job::Rally) && self.rng.f() < 0.15 {
if e.bed.is_none() && beds > 0 && !matches!(e.job, Job::Attack(_) | Job::Leave | Job::Rally | Job::March(_)) && self.rng.f() < 0.15 {
let cur = e.tile();
let free = |i: usize| self.map.t[i].kind == bed_kind && self.map.t[i].bed == 0;
let found = if flies(e.kind) { self.map.search(cur, self.map.fly_cost(), free, true) } else { self.map.search(cur, self.map.walk_cost(), free, true) };
@@ -3797,7 +3814,7 @@ impl Game {
}
}
if !matches!(e.job, Job::Attack(_) | Job::Leave | Job::Rest(_) | Job::Brawl(_)) {
let radius = if e.job == Job::Rally { 8.5 } else if matches!(e.job, Job::Guard(_)) { 10.0 } else if flies(e.kind) { 9.0 } else { 7.0 };
let radius = if matches!(e.job, Job::Rally | Job::March(_)) { 8.5 } else if matches!(e.job, Job::Guard(_)) { 10.0 } else if flies(e.kind) { 9.0 } else { 7.0 };
if let Some(id) = self.nearest_enemy(e, radius) {
self.unreserve(me);
e.job = Job::Attack(id);
@@ -3893,6 +3910,15 @@ impl Game {
self.rally(e, dt);
return;
}
if let Job::March(f) = e.job {
if self.attack_marks.contains(&f) {
self.rally_to(e, f, dt);
} else {
e.job = Job::None;
e.path.clear();
}
return;
}
if self.step_path(e, dt) {
return;
}
@@ -4082,6 +4108,11 @@ impl Game {
e.path.clear();
return;
};
self.rally_to(e, f, dt);
}
// march to tile f and fight there (the attack flag, or an attack marker)
pub(crate) fn rally_to(&mut self, e: &mut Ent, f: usize, dt: f32) {
if e.hp < e.max_hp * 0.3 || e.energy < 0.1 || e.hunger > 2.2 {
// exhausted or badly hurt: retreat
e.job = Job::None;
@@ -5781,6 +5812,7 @@ pub fn job_name(e: &Ent) -> &'static str {
Job::Repair(_) => "job.repair",
Job::MendBridge(_) => "job.mend_bridge",
Job::RaiseWall(_) => "job.raise_wall",
Job::March(_) => "job.march",
Job::Carry(_) => "job.carry",
Job::Pray(_) => "job.pray",
Job::HealCore => "job.heal_core",
+1 -1
View File
@@ -1373,7 +1373,7 @@ async fn main() {
if inp.ldown.is_some() {
inp.ldown = None;
} else if app.game.hand.is_empty() && matches!(ui.tool, Tool::Order(_)) {
app.game.queue(Act::CancelOrders);
app.game.queue(Act::Unmark { x: wx, y: wy });
} else {
app.game.queue(Act::Drop { x: wx, y: wy });
}
+212 -6
View File
@@ -1,11 +1,14 @@
// Orders from the hand: "attack" sends the fighting creatures nearby at an enemy unit or tower,
// "guard" posts a few creatures in a room. Right click with an order tool cancels the orders.
// Orders: "attack" on an enemy unit or tower sends the fighting creatures nearby at it; on open
// ground it sets an attack marker (a sword) - the ready creatures share out over all swords and
// fight where they stand. "Guard" sets a guard marker (a shield) that three creatures hold. Up to
// eight of each; a right click on a marker removes it (elsewhere: the guards stand down).
use crate::game::*;
use crate::map::*;
const ORDER_RADIUS: f32 = 18.0;
const GUARDS: usize = 3;
pub const MAX_MARKS: usize = 8;
fn ready(e: &Ent) -> bool {
e.alive && !e.held && e.creature() && !matches!(e.job, Job::Leave | Job::Register | Job::MakeBed(_)) && e.hp > e.max_hp * 0.25
@@ -26,8 +29,8 @@ impl Game {
(Some(j), _) => (self.ents[j].x, self.ents[j].y),
(None, Some((_, p))) => p,
_ => {
self.hint(31, "hint.order_attack");
self.sfx_ui(Sfx::Error);
// open ground: an attack marker
self.place_mark(wx.floor() as i32, wy.floor() as i32, true);
return;
}
};
@@ -59,8 +62,168 @@ impl Game {
self.msg("msg.order_attack", vec![n.to_string()]);
}
// guard: a few creatures take positions in the room at (x, y)
// guard: a guard marker at (x, y) (three creatures hold it, see update_marks)
pub fn order_guard(&mut self, x: i32, y: i32) {
self.place_mark(x, y, false);
}
// a sword (attack) or shield (guard) marker on tile (x, y)
pub fn place_mark(&mut self, x: i32, y: i32, attack: bool) {
if !inb(x, y) {
return;
}
let i = idx(x, y);
let t = self.map.t[i];
// a guard marker stands on own ground, an attack marker anywhere one can walk
let ok = if attack { t.walkable() || t.bridge() } else { t.owner == self.me && t.walkable() };
if !ok {
self.hint(31, if attack { "hint.order_attack" } else { "hint.order_guard" });
self.sfx_ui(Sfx::Error);
return;
}
let marks = if attack { &self.attack_marks } else { &self.guard_marks };
if marks.iter().any(|&m| {
let (mx, my) = xy(m);
(mx - x).abs() <= 1 && (my - y).abs() <= 1
}) {
return; // there is one already
}
if marks.len() >= MAX_MARKS {
self.hint(31, "hint.marks_full");
self.sfx_ui(Sfx::Error);
return;
}
if attack {
self.attack_marks.push(i);
} else {
self.guard_marks.push(i);
}
self.marks_t = 0.0; // creatures are sent at once
self.fx(7, x as f32 + 0.5, y as f32 + 0.5, 0.0, 0.0, 0.6, if attack { 0xff3040ff } else { 0xff40d0ff });
self.sfx_ui(Sfx::FlagSet);
self.msg(if attack { "msg.mark_attack" } else { "msg.mark_guard" }, vec![]);
}
// a right click with an order tool: the marker there goes (its creatures are free again);
// without a marker there the guards stand down
pub fn unmark(&mut self, wx: f32, wy: f32) {
let near = |m: &usize| {
let (mx, my) = xy(*m);
(mx as f32 + 0.5 - wx).powi(2) + (my as f32 + 0.5 - wy).powi(2) < 1.6 * 1.6
};
if let Some(k) = self.attack_marks.iter().position(near) {
let m = self.attack_marks.remove(k);
for e in self.ents.iter_mut().filter(|e| e.job == Job::March(m)) {
e.job = Job::None;
e.path.clear();
}
} else if let Some(k) = self.guard_marks.iter().position(near) {
let m = self.guard_marks.remove(k);
for j in 0..self.ents.len() {
if let Job::Guard(spot) = self.ents[j].job {
if guard_of(m, spot) && self.ents[j].creature() {
let me = self.ents[j].id + 1;
self.unreserve(me);
self.ents[j].job = Job::None;
}
}
}
} else {
self.cancel_orders();
return;
}
self.msg("msg.mark_removed", vec![]);
self.sfx_ui(Sfx::Click);
}
// every second: three creatures for every shield, the ready ones shared out over the swords
pub(crate) fn update_marks(&mut self, dt: f32) {
self.marks_t -= dt;
if self.marks_t > 0.0 || (self.guard_marks.is_empty() && self.attack_marks.is_empty()) {
return;
}
self.marks_t = 1.0;
// markers on ground that is gone (dug in, lost) go
let me = self.me;
let map = &self.map;
self.guard_marks.retain(|&m| map.t[m].owner == me && map.t[m].walkable());
self.attack_marks.retain(|&m| map.t[m].walkable() || map.t[m].bridge());
// shields
for m in self.guard_marks.clone() {
let have = self.ents.iter().filter(|e| e.alive && e.creature() && matches!(e.job, Job::Guard(s) if guard_of(m, s))).count();
for _ in have..GUARDS {
if !self.send_guard(m) {
break;
}
}
}
// swords: every ready creature that is free goes to the nearest sword with room
if self.attack_marks.is_empty() {
return;
}
let free: Vec<usize> = (0..self.ents.len())
.filter(|&j| {
let e = &self.ents[j];
ready(e) && e.hp > e.max_hp * 0.5 && e.energy > 0.15 && matches!(e.job, Job::None | Job::Wander | Job::Train(_) | Job::Study(_) | Job::March(_))
})
.collect();
let cap = free.len().div_ceil(self.attack_marks.len()).max(1);
let mut count: Vec<usize> = self.attack_marks.iter().map(|&m| self.ents.iter().filter(|e| e.alive && e.job == Job::March(m)).count()).collect();
for j in free {
if matches!(self.ents[j].job, Job::March(_)) {
continue;
}
let (ex, ey) = (self.ents[j].x, self.ents[j].y);
let best = (0..self.attack_marks.len()).filter(|&k| count[k] < cap).min_by(|&a, &b| {
let d = |k: usize| {
let (mx, my) = xy(self.attack_marks[k]);
(mx as f32 + 0.5 - ex).powi(2) + (my as f32 + 0.5 - ey).powi(2)
};
d(a).partial_cmp(&d(b)).unwrap_or(std::cmp::Ordering::Equal)
});
let Some(k) = best else { break };
count[k] += 1;
let mark = self.attack_marks[k];
let me = self.ents[j].id + 1;
self.unreserve(me);
let e = &mut self.ents[j];
e.job = Job::March(mark);
e.path.clear();
e.repath = 0.0;
}
}
// the nearest free creature takes a free spot next to shield m
fn send_guard(&mut self, m: usize) -> bool {
let (x, y) = xy(m);
let spot = (-2..=2).flat_map(|dy| (-2..=2).map(move |dx| (dx, dy))).filter(|&(dx, dy)| inb(x + dx, y + dy)).map(|(dx, dy)| idx(x + dx, y + dy)).filter(|&j| {
let t = &self.map.t[j];
t.walkable() && t.owner == self.me && t.reserved == 0 && !self.ents.iter().any(|e| e.job == Job::Guard(j))
}).min_by_key(|&j| {
let (sx, sy) = xy(j);
(sx - x).pow(2) + (sy - y).pow(2)
});
let Some(spot) = spot else { return false };
let (fx, fy) = (x as f32 + 0.5, y as f32 + 0.5);
let cand = (0..self.ents.len()).filter(|&j| ready(&self.ents[j]) && !matches!(self.ents[j].job, Job::Attack(_) | Job::Guard(_) | Job::March(_) | Job::Rally)).min_by(|&a, &b| {
let d = |j: usize| (self.ents[j].x - fx).powi(2) + (self.ents[j].y - fy).powi(2);
d(a).partial_cmp(&d(b)).unwrap_or(std::cmp::Ordering::Equal)
});
let Some(j) = cand else { return false };
let Some(p) = self.map.creature_path(flies(self.ents[j].kind), self.ents[j].tile(), spot) else { return false };
let me = self.ents[j].id + 1;
self.unreserve(me);
self.map.t[spot].reserved = me;
let e = &mut self.ents[j];
e.job = Job::Guard(spot);
e.path = p;
e.timer = 1.0e9; // until the marker goes
true
}
// the old one-off guard order in a room (kept for scripts and tests)
#[allow(dead_code)]
pub fn order_guard_room(&mut self, x: i32, y: i32) {
if !inb(x, y) {
return;
}
@@ -134,6 +297,13 @@ impl Game {
}
}
// is guard spot s next to shield m?
fn guard_of(m: usize, s: usize) -> bool {
let (mx, my) = xy(m);
let (sx, sy) = xy(s);
(mx - sx).abs() <= 2 && (my - sy).abs() <= 2
}
#[cfg(test)]
mod tests {
use super::*;
@@ -152,10 +322,46 @@ mod tests {
let j = g.find(id).unwrap();
g.ents[j].job = Job::None;
}
g.order_guard(cx - 1, cy - 1);
g.order_guard_room(cx - 1, cy - 1);
let guards = g.ents.iter().filter(|e| matches!(e.job, Job::Guard(_))).count();
assert_eq!(guards, 3);
g.cancel_orders();
assert_eq!(g.ents.iter().filter(|e| matches!(e.job, Job::Guard(_))).count(), 0);
}
#[test]
fn shields_are_held_and_swords_share_the_creatures() {
let mut g = Game::new(14, 64, 64);
let (cx, cy) = g.map.core;
for y in cy - 6..=cy + 8 {
for x in cx - 6..=cx + 10 {
let t = &mut g.map.t[idx(x, y)];
if t.kind != Kind::Core {
t.kind = Kind::Floor;
t.owner = Owner::Player;
}
}
}
let ids: Vec<u32> = (0..6).map(|k| g.spawn(EK::Ogre, false, cx as f32 - 2.5, cy as f32 + 0.5 + k as f32 * 0.3, 2)).collect();
for &id in &ids {
let j = g.find(id).unwrap();
g.ents[j].settled = true;
g.ents[j].job = Job::None;
}
g.order_guard(cx + 5, cy + 5);
g.update_marks(1.0);
let guards = g.ents.iter().filter(|e| matches!(e.job, Job::Guard(_))).count();
assert_eq!(guards, 3, "three hold the shield");
g.place_mark(cx + 8, cy - 4, true);
g.place_mark(cx - 4, cy - 5, true);
g.update_marks(1.0);
let a = g.ents.iter().filter(|e| e.job == Job::March(g.attack_marks[0])).count();
let b = g.ents.iter().filter(|e| e.job == Job::March(g.attack_marks[1])).count();
assert!(a + b == 3 && a.abs_diff(b) <= 1, "{} and {}", a, b);
// a right click on a sword frees its creatures
let (sx, sy) = xy(g.attack_marks[0]);
g.unmark(sx as f32 + 0.5, sy as f32 + 0.5);
assert_eq!(g.attack_marks.len(), 1);
assert!(!g.ents.iter().any(|e| e.job == Job::March(idx(sx, sy))));
}
}
+81 -1
View File
@@ -312,6 +312,35 @@ fn lit_material() -> Material {
}
// Unlit, alpha-blended material for glows, shadows and effects
// the world target onto the virtual screen: fully opaque. Translucent glows (flames, the heart)
// leave the target's alpha below one; blended, the black behind would shine through and dim them.
pub(crate) fn opaque_material() -> Material {
const V: &str = r#"#version 100
attribute vec3 position;
attribute vec2 texcoord;
attribute vec4 color0;
varying lowp vec2 uv;
varying lowp vec4 color;
uniform mat4 Model;
uniform mat4 Projection;
void main() {
gl_Position = Projection * Model * vec4(position, 1.0);
color = color0 / 255.0;
uv = texcoord;
}
"#;
const F: &str = r#"#version 100
precision mediump float;
varying lowp vec4 color;
varying lowp vec2 uv;
uniform sampler2D Texture;
void main() {
gl_FragColor = vec4(color.rgb * texture2D(Texture, uv).rgb, 1.0);
}
"#;
load_material(ShaderSource::Glsl { vertex: V, fragment: F }, MaterialParams { pipeline_params: PipelineParams::default(), uniforms: vec![], textures: vec![] }).expect("opaque material")
}
fn glow_material() -> Material {
const V: &str = r#"#version 100
attribute vec3 position;
@@ -1122,6 +1151,7 @@ pub struct R3d {
pub outlines: bool, // tile outlines on/off
wall_sub: usize, // wall face subdivisions (detail)
pub max_lights: usize,
opaque: Material, // the world target onto the screen without blending
pub pixel_rows: u32, // pixel look: the world is rendered this many rows high (0: off)
target: Option<(RenderTarget, u32, u32)>, // low resolution render target
lava: Vec<f32>, // lava light per tile (see lava_field)
@@ -1174,7 +1204,7 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32) -> u64 {
impl 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, pixel_rows: 360, target: None, lava: Vec::new() }
R3d { lit: lit_material(), glow: glow_material(), opaque: opaque_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, pixel_rows: 360, target: None, lava: Vec::new() }
}
// Changing the wall detail rebuilds all chunks
@@ -1505,6 +1535,12 @@ impl R3d {
if let Some(f) = g.flag {
draw_flag(f, t);
}
for &m in &g.guard_marks {
draw_shield_mark(m, t);
}
for &m in &g.attack_marks {
draw_sword_mark(m, t);
}
draw_marks(g, t);
draw_overlay(&g.map, ov, self.wall_sub);
draw_entry_warnings(g, t, self.wall_sub);
@@ -1523,7 +1559,9 @@ impl R3d {
crate::ui::begin_ui();
if let (Some((_, _, k)), Some((rt, tw, th))) = (pix, self.target.as_ref()) {
// the world onto the virtual screen, every pixel k x k (scaled to the device at the end)
gl_use_material(&self.opaque);
draw_texture_ex(&rt.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(vec2(*tw as f32 * k, *th as f32 * k)), flip_y: true, ..Default::default() });
gl_use_default_material();
}
// ---- 2D overlay in device pixels: health bars, sleep marks, levels, gate HP
@@ -2569,6 +2607,48 @@ fn draw_flag(f: usize, t: f32) {
draw_mesh(&Mesh { vertices: v, indices: vec![0, 1, 2], texture: None });
}
// a flat polygon in a vertical plane (x along `right`, y up) around `base`
fn draw_plate(base: Vec3, right: Vec3, pts: &[(f32, f32)], c: [u8; 4]) {
let v: Vec<Vertex> = pts.iter().map(|&(x, y)| Vertex { position: base + right * x + Vec3::Y * y, uv: Vec2::ZERO, color: c, normal: Vec4::Y }).collect();
let mut idx = Vec::new();
for k in 1..pts.len() as u16 - 1 {
idx.extend([0, k, k + 1]);
}
draw_mesh(&Mesh { vertices: v, indices: idx, texture: None });
}
// guard marker: a shield on a short pole, gently bobbing
fn draw_shield_mark(m: usize, t: f32) {
let (x, z) = xy(m);
let base = vec3(x as f32 + 0.5, 0.0, z as f32 + 0.5);
draw_cube(base + vec3(0.0, 0.45, 0.0), vec3(0.05, 0.9, 0.05), None, Color::from_rgba(120, 90, 60, 255));
let bob = (t * 2.0 + m as f32).sin() * 0.03;
let c = base + vec3(0.0, 0.95 + bob, 0.0);
let right = vec3(0.7071, 0.0, -0.7071);
let outline: [(f32, f32); 7] = [(-0.26, 0.24), (0.26, 0.24), (0.26, 0.02), (0.18, -0.16), (0.0, -0.3), (-0.18, -0.16), (-0.26, 0.02)];
// the rim, the face and a cross (both sides, so it shows from every angle)
for (r, off) in [(right, 0.0f32), (-right, 0.01)] {
let at = c + vec3(0.0, 0.0, 0.0) + right.cross(Vec3::Y) * off;
draw_plate(at, r, &outline, [230, 190, 70, 255]);
let inner: Vec<(f32, f32)> = outline.iter().map(|&(a, b)| (a * 0.8, b * 0.8 + 0.01)).collect();
draw_plate(at + right.cross(Vec3::Y) * 0.004, r, &inner, [50, 80, 170, 255]);
draw_plate(at + right.cross(Vec3::Y) * 0.008, r, &[(-0.03, 0.18), (0.03, 0.18), (0.03, -0.2), (-0.03, -0.2)], [230, 190, 70, 255]);
draw_plate(at + right.cross(Vec3::Y) * 0.008, r, &[(-0.16, 0.06), (0.16, 0.06), (0.16, 0.0), (-0.16, 0.0)], [230, 190, 70, 255]);
}
}
// attack marker: a sword stuck in the ground, its blade glinting
fn draw_sword_mark(m: usize, t: f32) {
let (x, z) = xy(m);
let base = vec3(x as f32 + 0.5, 0.0, z as f32 + 0.5);
let glint = 200 + ((t * 3.0 + m as f32).sin() * 40.0) as i32;
let blade = Color::from_rgba(glint.clamp(0, 255) as u8, glint.clamp(0, 255) as u8, (glint + 15).clamp(0, 255) as u8, 255);
draw_cube(base + vec3(0.0, 0.4, 0.0), vec3(0.07, 0.8, 0.02), None, blade);
draw_cube(base + vec3(0.0, 0.82, 0.0), vec3(0.34, 0.05, 0.06), None, Color::from_rgba(150, 120, 50, 255));
draw_cube(base + vec3(0.0, 0.98, 0.0), vec3(0.05, 0.26, 0.05), None, Color::from_rgba(170, 30, 30, 255));
draw_cube(base + vec3(0.0, 1.13, 0.0), vec3(0.08, 0.06, 0.08), None, Color::from_rgba(230, 190, 70, 255));
}
fn mark_color(kind: u8) -> Color {
match kind {
MARK_DIG => Color::new(0.45, 0.95, 0.55, 1.0),
+10
View File
@@ -24,6 +24,7 @@ thread_local! {
static UI_TARGET: std::cell::RefCell<Option<(RenderTarget, u32, u32)>> = std::cell::RefCell::new(None);
static UP_TARGET: std::cell::RefCell<Option<(RenderTarget, u32, u32)>> = std::cell::RefCell::new(None);
static FRAME_STARTED: std::cell::Cell<bool> = std::cell::Cell::new(false);
static OPAQUE: std::cell::RefCell<Option<Material>> = std::cell::RefCell::new(None);
}
// the rows of the virtual screen for this frame (0: device pixels). Set once per frame before
@@ -188,7 +189,16 @@ pub fn end_frame() {
c.render_target = Some(up.clone());
set_camera(&c);
clear_background(Color::new(0.0, 0.0, 0.0, 1.0));
// opaque: translucent parts must not let the black below shine through
OPAQUE.with(|m| {
let mut m = m.borrow_mut();
if m.is_none() {
*m = Some(crate::render3d::opaque_material());
}
gl_use_material(m.as_ref().unwrap());
});
draw_texture_ex(&rt.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(vec2(uw as f32, uh as f32)), flip_y: true, ..Default::default() });
gl_use_default_material();
set_default_camera();
draw_texture_ex(&up.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(vec2(screen_width(), screen_height())), flip_y: true, ..Default::default() });
}