More graphical finesse: own beds per creature, animated room furniture, treasure chests, elaborate core and portals, worn floors
- props.rs: furniture drawn near the camera every frame
- beds built by each creature on arrival, matching its kind (straw, web, starry cushion,
fur pile, chain bunk, ember cushion, rock hollow, camp bed); gone when the owner is
- training: spinning dummies, weapon racks, wobbling targets; torture: turning wheel,
iron maiden, brazier with glowing coals; temple: altar with candles and fire bowl,
candle stands; library: book shelves, lecterns with floating books while studying
- treasure chests in the treasury, closed when empty, filled with their treasure otherwise
- Soul core: stepped obsidian pedestal, four spikes with energy threads, circling runes,
unsteady pulse when hurt
- Portals: circle of standing stones with runes, column of light and rising sparks
- Floors: cracked, chipped and shattered stones, grit and crack decals
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
036c13577c
commit
75232d95ee
5 files changed
+609
-42
No files matched your search
+110
-41
@@ -587,16 +587,25 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, atlas: &Texture2D, wall
|
||||
}
|
||||
// room decor
|
||||
let dc = col(Vec3::splat(amb_at(amb, xf + 0.5, zf + 0.5)), 1.0);
|
||||
match t.kind {
|
||||
Kind::Lair => {
|
||||
let (a, b) = cell_uv(D_BED);
|
||||
mb.quad(
|
||||
[vec3(xf + 0.05, 0.02, zf + 0.05), vec3(xf + 0.95, 0.02, zf + 0.05), vec3(xf + 0.95, 0.02, zf + 0.95), vec3(xf + 0.05, 0.02, zf + 0.95)],
|
||||
[vec2(a.x, a.y), vec2(b.x, a.y), vec2(b.x, b.y), vec2(a.x, b.y)],
|
||||
[dc; 4],
|
||||
[Vec3::Y; 4],
|
||||
);
|
||||
// wear and tear: grit and cracks on plain floors
|
||||
if matches!(t.kind, Kind::Floor) {
|
||||
let hsh = (x as u32).wrapping_mul(2654435761) ^ (z as u32).wrapping_mul(2246822519);
|
||||
let hsh = (hsh ^ (hsh >> 15)).wrapping_mul(2246822519);
|
||||
let decal = match hsh % 11 {
|
||||
0 | 1 => Some(D_RUBBLE),
|
||||
2 => Some(D_CRACK),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(d) = decal {
|
||||
let (a, b) = cell_uv(d);
|
||||
// rotate by a quarter turn for variety
|
||||
let uv = [vec2(a.x, a.y), vec2(b.x, a.y), vec2(b.x, b.y), vec2(a.x, b.y)];
|
||||
let r = (hsh >> 8) as usize % 4;
|
||||
let uv = [uv[r % 4], uv[(r + 1) % 4], uv[(r + 2) % 4], uv[(r + 3) % 4]];
|
||||
mb.quad([vec3(xf, 0.012, zf), vec3(xf + 1.0, 0.012, zf), vec3(xf + 1.0, 0.012, zf + 1.0), vec3(xf, 0.012, zf + 1.0)], uv, [dc; 4], [Vec3::Y; 4]);
|
||||
}
|
||||
}
|
||||
match t.kind {
|
||||
Kind::Farm => {
|
||||
let pos = [(0.3, 0.3), (0.7, 0.4), (0.45, 0.72)];
|
||||
for (k, &(ox, oz)) in pos.iter().enumerate() {
|
||||
@@ -605,12 +614,10 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, atlas: &Texture2D, wall
|
||||
}
|
||||
}
|
||||
}
|
||||
Kind::Training if (x + z) % 2 == 0 => mb.cross(xf + 0.5, zf + 0.5, 0.7, 0.75, D_DUMMY, dc),
|
||||
Kind::Library if z % 2 == 0 => mb.cross(xf + 0.5, zf + 0.5, 0.9, 0.85, D_SHELF, dc),
|
||||
Kind::Torture if (x + z) % 2 == 1 => mb.cross(xf + 0.5, zf + 0.5, 0.8, 0.8, D_RACK, dc),
|
||||
Kind::Temple if x % 3 == 1 && z % 3 == 1 => mb.cross(xf + 0.5, zf + 0.5, 0.8, 0.8, D_ALTAR, dc),
|
||||
// training, torture, temple and library furniture and the beds: props.rs
|
||||
// each treasury tile shows what it holds (up to TREASURY_TILE)
|
||||
Kind::Treasury if t.gold > 10 => {
|
||||
// (every third treasury tile holds a chest instead, see props.rs)
|
||||
Kind::Treasury if t.gold > 10 && !crate::props::chest_tile(x, z) => {
|
||||
let fill = (t.gold as f32 / TREASURY_TILE).clamp(0.0, 1.0);
|
||||
let s = 0.35 + 0.55 * fill;
|
||||
let sprite = [D_COINS, D_SILVER, D_RUBY, D_DIAMOND][(t.res & 3) as usize];
|
||||
@@ -885,7 +892,7 @@ impl R3d {
|
||||
}
|
||||
let (cx, cy) = g.map.core;
|
||||
let hp = (g.core_hp / CORE_HP).clamp(0.0, 1.0);
|
||||
push(vec3(cx as f32 + 1.5, 1.2, cy as f32 + 1.5), 7.0, vec3(1.0, 0.25, 0.2), 1.1 + 0.2 * (t * 3.0).sin() * (1.5 - hp), 100.0);
|
||||
push(vec3(cx as f32 + 1.5, 1.6, cy as f32 + 1.5), 7.0, vec3(1.0, 0.25, 0.2), 1.1 + 0.2 * (t * 3.0).sin() * (1.5 - hp), 100.0);
|
||||
for (k, &(px, py)) in g.map.portals.iter().enumerate() {
|
||||
let owned = g.map.portal_owned(k);
|
||||
push(vec3(px as f32 + 1.5, 0.8, py as f32 + 1.5), 6.0, vec3(0.7, 0.3, 1.0), if owned { 1.0 } else { 0.35 }, 20.0);
|
||||
@@ -1013,6 +1020,7 @@ impl R3d {
|
||||
}
|
||||
draw_landmarks_solid(g, t);
|
||||
draw_towers_solid(g);
|
||||
crate::props::draw_props_solid(g, cam, t);
|
||||
|
||||
// ---- unlit glow / overlays
|
||||
gl_use_material(&self.glow);
|
||||
@@ -1044,6 +1052,7 @@ impl R3d {
|
||||
// translucent glows and effects last (they write depth, see glow_material)
|
||||
draw_landmarks_glow(g, t);
|
||||
draw_towers_glow(g, t);
|
||||
crate::props::draw_props_glow(g, cam, t);
|
||||
for f in &g.fx {
|
||||
draw_fx(f, t);
|
||||
}
|
||||
@@ -1265,7 +1274,7 @@ thread_local! {
|
||||
}
|
||||
|
||||
// Thick 3D line as a camera-facing quad (GL lines don't work with custom materials)
|
||||
fn line3(a: Vec3, b: Vec3, w: f32, color: Color) {
|
||||
pub fn line3(a: Vec3, b: Vec3, w: f32, color: Color) {
|
||||
let eye = EYE.with(|e| e.get());
|
||||
let side = (b - a).cross(eye).normalize_or_zero() * (w * 0.5);
|
||||
let c = [(color.r * 255.0) as u8, (color.g * 255.0) as u8, (color.b * 255.0) as u8, (color.a * 255.0) as u8];
|
||||
@@ -1273,7 +1282,7 @@ fn line3(a: Vec3, b: Vec3, w: f32, color: Color) {
|
||||
draw_mesh(&Mesh { vertices: vec![v(a - side), v(a + side), v(b + side), v(b - side)], indices: vec![0, 1, 2, 0, 2, 3], texture: None });
|
||||
}
|
||||
|
||||
fn disc(c: Vec3, r: f32, color: Color, seg: usize) {
|
||||
pub fn disc(c: Vec3, r: f32, color: Color, seg: usize) {
|
||||
let mut v = Vec::with_capacity(seg + 1);
|
||||
let cc = [(color.r * 255.0) as u8, (color.g * 255.0) as u8, (color.b * 255.0) as u8, (color.a * 255.0) as u8];
|
||||
v.push(Vertex { position: c, uv: Vec2::ZERO, color: cc, normal: Vec4::Y });
|
||||
@@ -1298,11 +1307,31 @@ fn ring(c: Vec3, r: f32, color: Color) {
|
||||
}
|
||||
|
||||
fn draw_landmarks_solid(g: &Game, t: f32) {
|
||||
// core pedestal
|
||||
// soul core: stepped obsidian pedestal with four spikes holding the orb
|
||||
let (cx, cy) = g.map.core;
|
||||
let c = vec3(cx as f32 + 1.5, 0.0, cy as f32 + 1.5);
|
||||
draw_cube(c + vec3(0.0, 0.15, 0.0), vec3(2.4, 0.3, 2.4), None, Color::from_rgba(45, 16, 20, 255));
|
||||
draw_cube(c + vec3(0.0, 0.4, 0.0), vec3(1.4, 0.2, 1.4), None, Color::from_rgba(70, 26, 30, 255));
|
||||
draw_cube(c + vec3(0.0, 0.08, 0.0), vec3(2.8, 0.16, 2.8), None, Color::from_rgba(34, 14, 18, 255));
|
||||
draw_cube(c + vec3(0.0, 0.22, 0.0), vec3(2.1, 0.14, 2.1), None, Color::from_rgba(48, 18, 24, 255));
|
||||
draw_cube(c + vec3(0.0, 0.35, 0.0), vec3(1.3, 0.14, 1.3), None, Color::from_rgba(66, 24, 30, 255));
|
||||
for (k, (dx, dz)) in [(-1.15f32, -1.15f32), (1.15, -1.15), (1.15, 1.15), (-1.15, 1.15)].iter().enumerate() {
|
||||
let b = c + vec3(*dx, 0.0, *dz);
|
||||
draw_cube(b + vec3(0.0, 0.45, 0.0), vec3(0.26, 0.9, 0.26), None, Color::from_rgba(28, 20, 26, 255));
|
||||
draw_cube(b + vec3(0.0, 1.05, 0.0), vec3(0.16, 0.3, 0.16), None, Color::from_rgba(40, 28, 36, 255));
|
||||
draw_cube(b + vec3(0.0, 1.26, 0.0), vec3(0.08, 0.14, 0.08), None, Color::from_rgba(180, 140, 60, 255));
|
||||
// claws reaching for the orb
|
||||
line3(b + vec3(0.0, 1.0, 0.0), c + vec3(dx * 0.45, 1.55 - (k % 2) as f32 * 0.1, dz * 0.45), 0.07, Color::from_rgba(36, 24, 30, 255));
|
||||
}
|
||||
// portal circles: eight standing stones of different height
|
||||
for &(px, py) in &g.map.portals {
|
||||
let pc = vec3(px as f32 + 1.5, 0.0, py as f32 + 1.5);
|
||||
for k in 0..8 {
|
||||
let a = k as f32 * 0.785 + 0.39;
|
||||
let h = 0.8 + ((k * 5 + px as usize) % 4) as f32 * 0.15;
|
||||
let p = pc + vec3(a.cos() * 1.5, h * 0.5, a.sin() * 1.5);
|
||||
draw_cube(p, vec3(0.28, h, 0.28), None, Color::from_rgba(52 + (k * 3) as u8, 44, 62, 255));
|
||||
draw_cube(p + vec3(0.0, h * 0.5 + 0.04, 0.0), vec3(0.22, 0.08, 0.22), None, Color::from_rgba(70, 60, 82, 255));
|
||||
}
|
||||
}
|
||||
// gate pillars
|
||||
let (gx, gz) = g.gate_center();
|
||||
if g.gate_hp > 0.0 {
|
||||
@@ -1321,30 +1350,70 @@ fn draw_landmarks_solid(g: &Game, t: f32) {
|
||||
}
|
||||
|
||||
fn draw_landmarks_glow(g: &Game, t: f32) {
|
||||
// soul core
|
||||
// soul core: pulsing orb (faster and more unsteady when hurt), energy threads to the
|
||||
// spikes, runes circling around it
|
||||
let (cx, cy) = g.map.core;
|
||||
let hp = (g.core_hp / CORE_HP).clamp(0.0, 1.0);
|
||||
let pulse = 1.0 + (t * (2.0 + (1.0 - hp) * 6.0)).sin() * 0.06;
|
||||
let c = vec3(cx as f32 + 1.5, 1.15 + (t * 1.3).sin() * 0.06, cy as f32 + 1.5);
|
||||
draw_sphere(c, 0.75 * pulse, None, Color::new(0.4 + 0.5 * hp, 0.05, 0.08, 1.0));
|
||||
draw_sphere(c, 0.95 * pulse, None, Color::new(1.0, 0.2, 0.15, 0.25));
|
||||
draw_sphere(c + vec3(-0.2, 0.25, 0.2), 0.18, None, Color::new(1.0, 0.85, 0.7, 0.8));
|
||||
// portals
|
||||
for (pk, &(px, py)) in g.map.portals.iter().enumerate() {
|
||||
let owned = g.map.portal_owned(pk);
|
||||
let pc = vec3(px as f32 + 1.5, 0.03, py as f32 + 1.5);
|
||||
let arm = if owned { Color::new(0.8, 0.4, 1.0, 1.0) } else { Color::new(0.45, 0.42, 0.5, 1.0) };
|
||||
disc(pc, 1.35, if owned { Color::new(0.2, 0.05, 0.3, 0.9) } else { Color::new(0.1, 0.1, 0.12, 0.9) }, 32);
|
||||
let speed = if owned { 1.6 } else { 0.3 };
|
||||
for a in 0..3 {
|
||||
for k in 0..18 {
|
||||
let f = k as f32 / 18.0;
|
||||
let ang = t * speed + a as f32 * 2.094 + f * 4.0;
|
||||
let r = f * 1.3;
|
||||
let p = pc + vec3(ang.cos() * r, 0.1 + (1.0 - f) * 0.5, ang.sin() * r);
|
||||
draw_cube(p, Vec3::splat(0.05 + 0.06 * (1.0 - f)), None, Color::new(arm.r, arm.g, arm.b, 0.4 + 0.6 * (1.0 - f)));
|
||||
}
|
||||
let beat = t * (2.0 + (1.0 - hp) * 6.0);
|
||||
let pulse = 1.0 + beat.sin() * 0.06 + if hp < 0.4 { (t * 31.0).sin() * 0.03 } else { 0.0 };
|
||||
let base = vec3(cx as f32 + 1.5, 0.0, cy as f32 + 1.5);
|
||||
let c = base + vec3(0.0, 1.6 + (t * 1.3).sin() * 0.06, 0.0);
|
||||
draw_sphere(c, 0.62 * pulse, None, Color::new(0.4 + 0.5 * hp, 0.04, 0.07, 1.0));
|
||||
draw_sphere(c, 0.32 * pulse, None, Color::new(1.0, 0.5 + 0.3 * hp, 0.4, 0.55));
|
||||
draw_sphere(c, 0.85 * pulse, None, Color::new(1.0, 0.2, 0.15, 0.22));
|
||||
draw_sphere(c, 1.15 * pulse, None, Color::new(1.0, 0.1, 0.1, 0.08));
|
||||
draw_sphere(c + vec3(-0.18, 0.2, 0.18), 0.13, None, Color::new(1.0, 0.85, 0.7, 0.8));
|
||||
for (k, (dx, dz)) in [(-1.15f32, -1.15f32), (1.15, -1.15), (1.15, 1.15), (-1.15, 1.15)].iter().enumerate() {
|
||||
let tip = base + vec3(*dx, 1.33, *dz);
|
||||
let flick = 0.5 + 0.5 * (t * 5.0 + k as f32 * 1.7).sin();
|
||||
let mid = (tip + c) * 0.5 + vec3((t * 7.0 + k as f32).sin() * 0.08, 0.15, (t * 6.0 + k as f32).cos() * 0.08);
|
||||
let col = Color::new(1.0, 0.25 + 0.3 * flick, 0.2, 0.35 + 0.45 * flick * hp.max(0.3));
|
||||
line3(tip, mid, 0.04, col);
|
||||
line3(mid, c, 0.04, col);
|
||||
draw_sphere(tip + vec3(0.0, 0.05, 0.0), 0.07, None, Color::new(1.0, 0.6, 0.3, 0.6 + 0.4 * flick));
|
||||
}
|
||||
for k in 0..6 {
|
||||
let a = t * 0.7 + k as f32 * 1.047;
|
||||
let p = c + vec3(a.cos() * 1.05, (t * 1.1 + k as f32).sin() * 0.25, a.sin() * 1.05);
|
||||
draw_cube(p, vec3(0.1, 0.16, 0.04), None, Color::new(1.0, 0.7, 0.3, 0.85));
|
||||
}
|
||||
// portals: a circle of standing stones with runes, the vortex, a column of light, sparks
|
||||
for (pk, &(px, py)) in g.map.portals.iter().enumerate() {
|
||||
let owned = g.map.portal_owned(pk);
|
||||
let pc = vec3(px as f32 + 1.5, 0.03, py as f32 + 1.5);
|
||||
let arm = if owned { Color::new(0.8, 0.4, 1.0, 1.0) } else { Color::new(0.45, 0.42, 0.5, 1.0) };
|
||||
disc(pc, 1.35, if owned { Color::new(0.2, 0.05, 0.3, 0.9) } else { Color::new(0.1, 0.1, 0.12, 0.9) }, 32);
|
||||
let speed = if owned { 1.6 } else { 0.3 };
|
||||
for a in 0..3 {
|
||||
for k in 0..18 {
|
||||
let f = k as f32 / 18.0;
|
||||
let ang = t * speed + a as f32 * 2.094 + f * 4.0;
|
||||
let r = f * 1.3;
|
||||
let p = pc + vec3(ang.cos() * r, 0.1 + (1.0 - f) * 0.5, ang.sin() * r);
|
||||
draw_cube(p, Vec3::splat(0.05 + 0.06 * (1.0 - f)), None, Color::new(arm.r, arm.g, arm.b, 0.4 + 0.6 * (1.0 - f)));
|
||||
}
|
||||
}
|
||||
// runes on the stones
|
||||
for k in 0..8 {
|
||||
let a = k as f32 * 0.785 + 0.39;
|
||||
let glow = 0.5 + 0.5 * (t * 2.0 + k as f32 * 0.8).sin();
|
||||
let h = 0.45 + (k % 3) as f32 * 0.12;
|
||||
let p = pc + vec3(a.cos() * 1.4, h, a.sin() * 1.4);
|
||||
draw_cube(p, vec3(0.08, 0.14, 0.08), None, Color::new(arm.r, arm.g, arm.b, if owned { 0.4 + 0.6 * glow } else { 0.25 }));
|
||||
}
|
||||
if owned {
|
||||
// column of light and rising sparks
|
||||
for k in 0..5 {
|
||||
let y = 0.4 + k as f32 * 0.5;
|
||||
draw_sphere(pc + vec3(0.0, y, 0.0), 0.35 - k as f32 * 0.05, None, Color::new(0.75, 0.45, 1.0, 0.1));
|
||||
}
|
||||
for k in 0..10 {
|
||||
let ph = (t * 0.6 + k as f32 * 0.37).fract();
|
||||
let a = k as f32 * 2.4 + t * 0.5;
|
||||
let p = pc + vec3(a.cos() * (0.3 + 0.5 * ph), 0.2 + ph * 2.4, a.sin() * (0.3 + 0.5 * ph));
|
||||
draw_cube(p, Vec3::splat(0.05), None, Color::new(0.9, 0.7, 1.0, 1.0 - ph));
|
||||
}
|
||||
}
|
||||
}
|
||||
// gate light
|
||||
if g.gate_hp > 0.0 {
|
||||
|
||||
Reference in new issue
Block a user