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
+11
@@ -2282,6 +2282,17 @@ impl Game {
|
|||||||
}
|
}
|
||||||
if e.think <= 0.0 {
|
if e.think <= 0.0 {
|
||||||
e.think = 0.4 + self.rng.f() * 0.3;
|
e.think = 0.4 + self.rng.f() * 0.3;
|
||||||
|
// a new creature builds its own bed in the lair soon after its arrival
|
||||||
|
if e.bed.is_none() && self.counts[1] > 0 && !matches!(e.job, Job::Attack(_) | Job::Leave | Job::Rally) && self.rng.f() < 0.15 {
|
||||||
|
let cur = e.tile();
|
||||||
|
if let Some(p) = self.map.search(cur, self.map.walk_cost(), |i| self.map.t[i].kind == Kind::Lair && self.map.t[i].bed == 0, true) {
|
||||||
|
let b = p.last().copied().unwrap_or(cur);
|
||||||
|
self.map.t[b].bed = me;
|
||||||
|
e.bed = Some(b);
|
||||||
|
let (bx, by) = xy(b);
|
||||||
|
self.fx(8, bx as f32 + 0.5, by as f32 + 0.5, 0.0, 0.0, 0.6, 0xff40d0ff);
|
||||||
|
}
|
||||||
|
}
|
||||||
if !matches!(e.job, Job::Attack(_) | Job::Leave) {
|
if !matches!(e.job, Job::Attack(_) | Job::Leave) {
|
||||||
let radius = if e.job == Job::Rally { 8.5 } else { 7.0 };
|
let radius = if e.job == Job::Rally { 8.5 } else { 7.0 };
|
||||||
if let Some(id) = self.nearest_enemy(e, radius) {
|
if let Some(id) = self.nearest_enemy(e, radius) {
|
||||||
|
|||||||
+48
-1
@@ -62,6 +62,8 @@ pub const T_DIAMOND: u32 = 119;
|
|||||||
pub const D_SILVER: u32 = 121; // treasury content
|
pub const D_SILVER: u32 = 121; // treasury content
|
||||||
pub const D_RUBY: u32 = 122;
|
pub const D_RUBY: u32 = 122;
|
||||||
pub const D_DIAMOND: u32 = 123;
|
pub const D_DIAMOND: u32 = 123;
|
||||||
|
pub const D_RUBBLE: u32 = 124; // floor decals: pebbles and fragments
|
||||||
|
pub const D_CRACK: u32 = 125; // a long crack through several stones
|
||||||
|
|
||||||
pub struct Gfx {
|
pub struct Gfx {
|
||||||
pub atlas: Texture2D,
|
pub atlas: Texture2D,
|
||||||
@@ -149,7 +151,7 @@ impl Canvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// flagstone pattern with grout
|
// flagstone pattern with grout; stones are whole, cracked or broken (chipped corner with grit)
|
||||||
fn stones(x: i32, y: i32, v: u32, base: (i32, i32, i32), grout: (i32, i32, i32), var: i32) -> Color {
|
fn stones(x: i32, y: i32, v: u32, base: (i32, i32, i32), grout: (i32, i32, i32), var: i32) -> Color {
|
||||||
let row = y / 8;
|
let row = y / 8;
|
||||||
let off = if row % 2 == 1 { 4 } else { 0 };
|
let off = if row % 2 == 1 { 4 } else { 0 };
|
||||||
@@ -157,6 +159,31 @@ fn stones(x: i32, y: i32, v: u32, base: (i32, i32, i32), grout: (i32, i32, i32),
|
|||||||
if y % 8 == 0 || sx % 8 == 0 {
|
if y % 8 == 0 || sx % 8 == 0 {
|
||||||
return shade(grout, (rnd(x, y, v + 7) * 6.0) as i32);
|
return shade(grout, (rnd(x, y, v + 7) * 6.0) as i32);
|
||||||
}
|
}
|
||||||
|
let (lx, ly) = (sx % 8, y % 8); // position inside the stone (1..7)
|
||||||
|
let damage = hash((x + off) / 8, row, v + 17) % 9;
|
||||||
|
match damage {
|
||||||
|
// a crack running across the stone
|
||||||
|
0 | 1 => {
|
||||||
|
let k = (hash((x + off) / 8, row, v + 18) % 3) as i32;
|
||||||
|
let on = if damage == 0 { lx + k == ly || lx + k == ly + 1 && ly % 3 == 0 } else { 8 - lx == ly + k - 1 };
|
||||||
|
if on {
|
||||||
|
return shade(grout, -6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// broken corner: the piece is missing, grit lies in the hole
|
||||||
|
2 => {
|
||||||
|
if lx + ly < 5 {
|
||||||
|
return if hash(x, y, v + 19) % 3 == 0 { shade(base, -18) } else { shade(grout, (rnd(x, y, v + 20) * 10.0) as i32 - 4) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// shattered: several small fragments
|
||||||
|
3 => {
|
||||||
|
if (lx * 3 + ly * 5 + v as i32) % 7 == 0 || lx == ly / 2 + 2 {
|
||||||
|
return shade(grout, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
let stone = hash((x + off) / 8, row, v + 3) % 5;
|
let stone = hash((x + off) / 8, row, v + 3) % 5;
|
||||||
let d = (rnd(x, y, v) * var as f32) as i32 - var / 2 + stone as i32 * 3 - 6;
|
let d = (rnd(x, y, v) * var as f32) as i32 - var / 2 + stone as i32 * 3 - 6;
|
||||||
// highlight top/left, shadow bottom/right
|
// highlight top/left, shadow bottom/right
|
||||||
@@ -699,6 +726,26 @@ fn gen_atlas() -> Image {
|
|||||||
let h = hash(cu, cv, seed) % 3;
|
let h = hash(cu, cv, seed) % 3;
|
||||||
Some(if e { lo } else if h == 0 || (u % 4 == 1 && v % 4 == 1) { hi } else if d > 0.65 { lo } else { mid })
|
Some(if e { lo } else if h == 0 || (u % 4 == 1 && v % 4 == 1) { hi } else if d > 0.65 { lo } else { mid })
|
||||||
};
|
};
|
||||||
|
// floor decals
|
||||||
|
cv.cell(D_RUBBLE, |x, y| {
|
||||||
|
for k in 0..9 {
|
||||||
|
let gx = 1 + (hash(k, 5, 220) % 14) as i32;
|
||||||
|
let gy = 1 + (hash(k, 6, 220) % 14) as i32;
|
||||||
|
let s = (hash(k, 7, 220) % 3) as i32;
|
||||||
|
if (x - gx).abs() + (y - gy).abs() <= s {
|
||||||
|
return Some(if y < gy { c(120, 110, 104) } else if (x + y) % 2 == 0 { c(84, 76, 72) } else { c(66, 60, 56) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
});
|
||||||
|
cv.cell(D_CRACK, |x, y| {
|
||||||
|
let cx = 8.0 + ((y as f32) * 0.7).sin() * 3.0 + (y as f32 * 1.9).sin();
|
||||||
|
if (x as f32 - cx).abs() < 0.7 || (y > 9 && (x as f32 - cx - (y - 9) as f32 * 0.8).abs() < 0.6) {
|
||||||
|
Some(Color::from_rgba(20, 14, 14, 200))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
cv.cell(D_RUBY, |x, y| gems(x, y, 200, c(255, 150, 170), c(215, 30, 60), c(120, 12, 36)));
|
cv.cell(D_RUBY, |x, y| gems(x, y, 200, c(255, 150, 170), c(215, 30, 60), c(120, 12, 36)));
|
||||||
cv.cell(D_DIAMOND, |x, y| gems(x, y, 210, c(255, 255, 255), c(170, 232, 255), c(80, 150, 200)));
|
cv.cell(D_DIAMOND, |x, y| gems(x, y, 210, c(255, 255, 255), c(170, 232, 255), c(80, 150, 200)));
|
||||||
cv.img
|
cv.img
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ mod i18n;
|
|||||||
mod map;
|
mod map;
|
||||||
mod menu;
|
mod menu;
|
||||||
mod platform;
|
mod platform;
|
||||||
|
mod props;
|
||||||
mod render3d;
|
mod render3d;
|
||||||
mod save;
|
mod save;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
|||||||
+439
@@ -0,0 +1,439 @@
|
|||||||
|
// Room furniture and creature beds, drawn every frame near the camera so they can move:
|
||||||
|
// training dummies spin while in use, torture wheels turn, candles and braziers flicker,
|
||||||
|
// books float above the lecterns. Every creature builds its own bed in the lair (a web for
|
||||||
|
// the spider, a fur pile for the ogre, ...); it disappears with its owner.
|
||||||
|
// Solid parts are drawn with the default material (flat colours), glowing parts in the glow pass.
|
||||||
|
|
||||||
|
use crate::game::*;
|
||||||
|
use crate::map::*;
|
||||||
|
use crate::render3d::{disc, line3, Cam};
|
||||||
|
use macroquad::prelude::*;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
fn hash(x: i32, y: i32, s: u32) -> u32 {
|
||||||
|
let mut h = (x as u32).wrapping_mul(374761393) ^ (y as u32).wrapping_mul(668265263) ^ s.wrapping_mul(2246822519);
|
||||||
|
h = (h ^ (h >> 13)).wrapping_mul(1274126177);
|
||||||
|
h ^ (h >> 16)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rgb(r: u8, g: u8, b: u8, light: f32) -> Color {
|
||||||
|
Color::new(r as f32 / 255.0 * light, g as f32 / 255.0 * light, b as f32 / 255.0 * light, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cube(p: Vec3, s: Vec3, c: Color) {
|
||||||
|
draw_cube(p, s, None, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// what is going on in the rooms this frame
|
||||||
|
struct Use {
|
||||||
|
busy: HashSet<usize>, // tiles where a creature trains, studies or prays
|
||||||
|
tortured: HashSet<usize>, // torture tiles with a prisoner on them
|
||||||
|
beds: HashMap<u32, EK>, // bed owner (entity id + 1) -> kind
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(g: &Game) -> Use {
|
||||||
|
let mut u = Use { busy: HashSet::new(), tortured: HashSet::new(), beds: HashMap::new() };
|
||||||
|
for e in &g.ents {
|
||||||
|
if !e.alive {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match e.job {
|
||||||
|
Job::Train(t) | Job::Study(t) | Job::Pray(t) if e.working || e.path.is_empty() => {
|
||||||
|
u.busy.insert(t);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if e.captive == 3 {
|
||||||
|
u.tortured.insert(e.tile());
|
||||||
|
}
|
||||||
|
if !e.hero || e.captive == 0 {
|
||||||
|
u.beds.insert(e.id + 1, e.kind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u
|
||||||
|
}
|
||||||
|
|
||||||
|
// tiles around the camera (the view is a rotated rectangle; a square around it is enough)
|
||||||
|
fn visible(cam: &Cam) -> (i32, i32, i32, i32) {
|
||||||
|
let r = (cam.zoom * 1.1) as i32 + 2;
|
||||||
|
let (cx, cz) = (cam.tx as i32, cam.tz as i32);
|
||||||
|
((cx - r).max(0), (cz - r).max(0), (cx + r).min(mw() as i32 - 1), (cz + r).min(mh() as i32 - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn light_of(t: &Tile) -> f32 {
|
||||||
|
if t.owner == Owner::Player {
|
||||||
|
0.85
|
||||||
|
} else {
|
||||||
|
0.55
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_props_solid(g: &Game, cam: &Cam, t: f32) {
|
||||||
|
let u = usage(g);
|
||||||
|
let (x0, z0, x1, z1) = visible(cam);
|
||||||
|
for z in z0..=z1 {
|
||||||
|
for x in x0..=x1 {
|
||||||
|
let i = idx(x, z);
|
||||||
|
let tile = &g.map.t[i];
|
||||||
|
let l = light_of(tile);
|
||||||
|
let c = vec3(x as f32 + 0.5, 0.0, z as f32 + 0.5);
|
||||||
|
let h = hash(x, z, 77);
|
||||||
|
match tile.kind {
|
||||||
|
Kind::Lair if tile.bed != 0 => {
|
||||||
|
if let Some(&kind) = u.beds.get(&tile.bed) {
|
||||||
|
bed(kind, c, h, l, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kind::Treasury if chest_tile(x, z) => chest(c, h, l, tile.gold as f32 / TREASURY_TILE, tile.res),
|
||||||
|
Kind::Training if (x + z) % 2 == 0 => training(c, h, l, t, u.busy.contains(&i) || u.busy.iter().any(|&b| adj8(b, i))),
|
||||||
|
Kind::Torture if (x + z) % 2 == 1 => torture(c, h, l, t, u.tortured.contains(&i) || u.tortured.iter().any(|&b| adj8(b, i))),
|
||||||
|
Kind::Temple if x % 3 == 1 && z % 3 == 1 => temple(c, h, l),
|
||||||
|
Kind::Temple if (x + z) % 2 == 0 && h % 3 == 0 => candle_stand(c + vec3(((h >> 4) % 5) as f32 * 0.1 - 0.2, 0.0, ((h >> 8) % 5) as f32 * 0.1 - 0.2), l),
|
||||||
|
Kind::Library if z % 2 == 0 => shelf(c, h, l),
|
||||||
|
Kind::Library if h % 3 == 0 => lectern(c, l),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_props_glow(g: &Game, cam: &Cam, t: f32) {
|
||||||
|
let u = usage(g);
|
||||||
|
let (x0, z0, x1, z1) = visible(cam);
|
||||||
|
for z in z0..=z1 {
|
||||||
|
for x in x0..=x1 {
|
||||||
|
let i = idx(x, z);
|
||||||
|
let tile = &g.map.t[i];
|
||||||
|
let c = vec3(x as f32 + 0.5, 0.0, z as f32 + 0.5);
|
||||||
|
let h = hash(x, z, 77);
|
||||||
|
let k = (x * 13 + z * 7) as f32;
|
||||||
|
match tile.kind {
|
||||||
|
Kind::Torture if (x + z) % 2 == 1 && h % 3 == 2 => {
|
||||||
|
// brazier coals
|
||||||
|
for n in 0..4 {
|
||||||
|
let a = n as f32 * 1.6 + k;
|
||||||
|
let f = 0.7 + 0.3 * (t * 7.0 + a).sin();
|
||||||
|
draw_sphere(c + vec3(a.cos() * 0.1, 0.55 + 0.03 * f, a.sin() * 0.1), 0.06 * f, None, Color::new(1.0, 0.35 + 0.2 * f, 0.05, 0.95));
|
||||||
|
}
|
||||||
|
draw_sphere(c + vec3(0.0, 0.62, 0.0), 0.16, None, Color::new(1.0, 0.4, 0.1, 0.25));
|
||||||
|
}
|
||||||
|
Kind::Temple if x % 3 == 1 && z % 3 == 1 => {
|
||||||
|
// candles on the altar and the eternal flame
|
||||||
|
for n in [-0.32f32, 0.32] {
|
||||||
|
flame(c + vec3(n, 0.68, -0.18), t, k + n);
|
||||||
|
}
|
||||||
|
let f = 1.0 + 0.15 * (t * 5.0 + k).sin();
|
||||||
|
draw_sphere(c + vec3(0.0, 0.62, 0.15), 0.1 * f, None, Color::new(1.0, 0.45, 0.1, 0.9));
|
||||||
|
draw_sphere(c + vec3(0.0, 0.66, 0.15), 0.2 * f, None, Color::new(1.0, 0.3, 0.05, 0.2));
|
||||||
|
}
|
||||||
|
Kind::Temple if (x + z) % 2 == 0 && h % 3 == 0 => {
|
||||||
|
let off = vec3(((h >> 4) % 5) as f32 * 0.1 - 0.2, 0.0, ((h >> 8) % 5) as f32 * 0.1 - 0.2);
|
||||||
|
flame(c + off + vec3(0.0, 0.52, 0.0), t, k);
|
||||||
|
}
|
||||||
|
Kind::Library if z % 2 != 0 && h % 3 == 0 => {
|
||||||
|
// books floating above the lectern while someone studies, otherwise resting
|
||||||
|
let busy = u.busy.contains(&i) || u.busy.iter().any(|&b| adj8(b, i));
|
||||||
|
let n = if busy { 3 } else { 1 };
|
||||||
|
for b in 0..n {
|
||||||
|
let a = t * 0.9 + b as f32 * 2.1 + k;
|
||||||
|
let y = 0.85 + (t * 1.7 + b as f32).sin() * 0.06 + b as f32 * 0.1;
|
||||||
|
let r = if busy { 0.32 } else { 0.0 };
|
||||||
|
let col = [Color::new(0.9, 0.3, 0.3, 1.0), Color::new(0.3, 0.5, 0.95, 1.0), Color::new(0.4, 0.85, 0.4, 1.0)][b % 3];
|
||||||
|
draw_cube(c + vec3(a.cos() * r, y, a.sin() * r), vec3(0.14, 0.04, 0.1), None, col);
|
||||||
|
if busy {
|
||||||
|
draw_sphere(c + vec3(a.cos() * r, y, a.sin() * r), 0.09, None, Color::new(0.7, 0.6, 1.0, 0.2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kind::Lair if tile.bed != 0 => {
|
||||||
|
// the flame priestess sleeps on embers
|
||||||
|
if let Some(EK::Priestess) = u.beds.get(&tile.bed) {
|
||||||
|
for n in 0..3 {
|
||||||
|
let a = n as f32 * 2.1 + k;
|
||||||
|
let f = 0.6 + 0.4 * (t * 4.0 + a).sin();
|
||||||
|
draw_sphere(c + vec3(a.cos() * 0.22, 0.06, a.sin() * 0.22), 0.05 * f, None, Color::new(1.0, 0.45, 0.1, 0.9));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// treasury tiles with a chest (instead of a heap)
|
||||||
|
pub fn chest_tile(x: i32, z: i32) -> bool {
|
||||||
|
(x * 7 + z * 13).rem_euclid(3) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// a chest: closed when empty, open and filled up to its content otherwise
|
||||||
|
fn chest(c: Vec3, h: u32, l: f32, fill: f32, res: u8) {
|
||||||
|
// quarter turns only: positions are rotated, box sizes swap width and depth
|
||||||
|
let q = h % 4;
|
||||||
|
let rot = q as f32 * std::f32::consts::FRAC_PI_2;
|
||||||
|
let (ca, sa) = (rot.cos(), rot.sin());
|
||||||
|
let at = |dx: f32, dz: f32, y: f32| c + vec3(dx * ca - dz * sa, y, dx * sa + dz * ca);
|
||||||
|
let cube = |p: Vec3, s: Vec3, col: Color| cube(p, if q % 2 == 1 { vec3(s.z, s.y, s.x) } else { s }, col);
|
||||||
|
let wood = rgb(110, 70, 36, l);
|
||||||
|
let band = rgb(170, 140, 70, l);
|
||||||
|
cube(at(0.0, 0.0, 0.16), vec3(0.56, 0.32, 0.4), wood);
|
||||||
|
cube(at(0.0, 0.0, 0.16), vec3(0.58, 0.05, 0.42), band);
|
||||||
|
cube(at(0.0, 0.21, 0.18), vec3(0.08, 0.1, 0.02), band);
|
||||||
|
let fill = fill.clamp(0.0, 1.0);
|
||||||
|
if fill < 0.02 {
|
||||||
|
cube(at(0.0, 0.0, 0.36), vec3(0.58, 0.08, 0.42), wood);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// open lid leaning back
|
||||||
|
cube(at(0.0, -0.24, 0.5), vec3(0.58, 0.32, 0.06), wood);
|
||||||
|
// content: the treasure kind decides the colour, the amount the height (heaped when full)
|
||||||
|
let col = match res {
|
||||||
|
1 => rgb(205, 210, 225, 1.0),
|
||||||
|
2 => rgb(220, 40, 70, 1.0),
|
||||||
|
3 => rgb(180, 235, 255, 1.0),
|
||||||
|
_ => rgb(240, 196, 60, 1.0),
|
||||||
|
};
|
||||||
|
let top = 0.06 + fill * 0.26;
|
||||||
|
cube(at(0.0, 0.0, top * 0.5 + 0.02), vec3(0.5, top, 0.34), col);
|
||||||
|
if fill > 0.7 {
|
||||||
|
cube(at(0.05, 0.02, top + 0.05), vec3(0.3, 0.08, 0.22), col);
|
||||||
|
for n in 0..3 {
|
||||||
|
let (dx, dz) = (((h >> (n * 3)) % 5) as f32 * 0.08 - 0.16, ((h >> (n * 2)) % 3) as f32 * 0.06 - 0.06);
|
||||||
|
cube(at(dx, dz, top + 0.1), vec3(0.06, 0.03, 0.06), rgb(255, 240, 180, 1.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adj8(a: usize, b: usize) -> bool {
|
||||||
|
let (ax, ay) = xy(a);
|
||||||
|
let (bx, by) = xy(b);
|
||||||
|
(ax - bx).abs() <= 1 && (ay - by).abs() <= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flame(p: Vec3, t: f32, k: f32) {
|
||||||
|
let f = 1.0 + 0.2 * (t * 11.0 + k).sin() + 0.1 * (t * 23.0 + k * 3.0).sin();
|
||||||
|
draw_sphere(p, 0.035 * f, None, Color::new(1.0, 0.85, 0.4, 1.0));
|
||||||
|
draw_sphere(p + vec3(0.0, 0.03, 0.0), 0.06 * f, None, Color::new(1.0, 0.5, 0.1, 0.35));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- beds
|
||||||
|
|
||||||
|
fn bed(kind: EK, c: Vec3, h: u32, l: f32, t: f32) {
|
||||||
|
let rot = (h % 4) as f32 * 0.4 - 0.6;
|
||||||
|
let (ca, sa) = (rot.cos(), rot.sin());
|
||||||
|
let at = |dx: f32, dz: f32, y: f32| c + vec3(dx * ca - dz * sa, y, dx * sa + dz * ca);
|
||||||
|
match kind {
|
||||||
|
EK::Kobold => {
|
||||||
|
// straw pile
|
||||||
|
for n in 0..7 {
|
||||||
|
let (dx, dz) = (((h >> (n * 3)) % 7) as f32 * 0.08 - 0.24, ((h >> (n * 2 + 1)) % 7) as f32 * 0.08 - 0.24);
|
||||||
|
cube(at(dx, dz, 0.04), vec3(0.28, 0.06, 0.12), rgb(200, 170, 70, l * (0.85 + (n % 3) as f32 * 0.08)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EK::Spider => {
|
||||||
|
// a web spanned flat over the floor and up to an imaginary corner
|
||||||
|
let wc = Color::new(0.85 * l + 0.1, 0.85 * l + 0.1, 0.9 * l + 0.1, 0.9);
|
||||||
|
let centre = c + vec3(0.0, 0.05, 0.0);
|
||||||
|
for n in 0..8 {
|
||||||
|
let a = n as f32 * std::f32::consts::FRAC_PI_4 + rot;
|
||||||
|
line3(centre, centre + vec3(a.cos() * 0.45, 0.0, a.sin() * 0.45), 0.015, wc);
|
||||||
|
}
|
||||||
|
for ring in 1..=3 {
|
||||||
|
let r = ring as f32 * 0.14;
|
||||||
|
for n in 0..8 {
|
||||||
|
let a0 = n as f32 * std::f32::consts::FRAC_PI_4 + rot;
|
||||||
|
let a1 = a0 + std::f32::consts::FRAC_PI_4;
|
||||||
|
line3(centre + vec3(a0.cos() * r, 0.0, a0.sin() * r), centre + vec3(a1.cos() * r, 0.0, a1.sin() * r), 0.012, wc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// a few strands going up
|
||||||
|
for n in 0..3 {
|
||||||
|
let a = n as f32 * 2.1 + rot;
|
||||||
|
line3(centre + vec3(a.cos() * 0.4, 0.0, a.sin() * 0.4), centre + vec3(a.cos() * 0.15, 0.6, a.sin() * 0.15), 0.012, wc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EK::Mage => {
|
||||||
|
// cushion with a starry blanket
|
||||||
|
cube(at(0.0, 0.0, 0.06), vec3(0.62, 0.12, 0.5), rgb(70, 50, 120, l));
|
||||||
|
cube(at(0.0, 0.08, 0.13), vec3(0.6, 0.03, 0.34), rgb(40, 30, 90, l));
|
||||||
|
for n in 0..4 {
|
||||||
|
let (dx, dz) = (((h >> (n * 4)) % 5) as f32 * 0.1 - 0.2, ((h >> (n * 4 + 2)) % 3) as f32 * 0.1 - 0.0);
|
||||||
|
cube(at(dx, dz, 0.15), vec3(0.04, 0.02, 0.04), rgb(240, 210, 90, 1.0));
|
||||||
|
}
|
||||||
|
cube(at(0.0, -0.18, 0.15), vec3(0.3, 0.08, 0.14), rgb(200, 190, 220, l));
|
||||||
|
}
|
||||||
|
EK::Ogre => {
|
||||||
|
// heap of furs with a gnawed bone
|
||||||
|
for n in 0..5 {
|
||||||
|
let a = n as f32 * 1.3 + rot;
|
||||||
|
cube(c + vec3(a.cos() * 0.15, 0.05 + (n % 2) as f32 * 0.04, a.sin() * 0.15), vec3(0.38, 0.1, 0.3), rgb(110 + (n * 12) as u8, 76, 44, l));
|
||||||
|
}
|
||||||
|
cube(at(0.32, 0.2, 0.06), vec3(0.24, 0.04, 0.05), rgb(230, 220, 200, l));
|
||||||
|
}
|
||||||
|
EK::Jailer => {
|
||||||
|
// plank bunk with chains
|
||||||
|
cube(at(0.0, 0.0, 0.12), vec3(0.72, 0.06, 0.4), rgb(90, 66, 44, l));
|
||||||
|
for (dx, dz) in [(-0.3, -0.15), (0.3, -0.15), (-0.3, 0.15), (0.3, 0.15)] {
|
||||||
|
cube(at(dx, dz, 0.05), vec3(0.06, 0.1, 0.06), rgb(70, 50, 34, l));
|
||||||
|
}
|
||||||
|
for n in 0..6 {
|
||||||
|
cube(at(-0.3 + n as f32 * 0.12, 0.24, 0.03 + (n % 2) as f32 * 0.02), vec3(0.08, 0.03, 0.04), rgb(120, 120, 130, l));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EK::Priestess => {
|
||||||
|
// ember cushion (glowing coals in the glow pass)
|
||||||
|
cube(at(0.0, 0.0, 0.05), vec3(0.6, 0.1, 0.48), rgb(150, 40, 30, l));
|
||||||
|
cube(at(0.0, 0.0, 0.11), vec3(0.4, 0.03, 0.3), rgb(220, 110, 40, l));
|
||||||
|
}
|
||||||
|
EK::Chainbreaker => {
|
||||||
|
// a hollow of broken rocks and a burst chain
|
||||||
|
for n in 0..7 {
|
||||||
|
let a = n as f32 * 0.9 + rot;
|
||||||
|
cube(c + vec3(a.cos() * 0.36, 0.08, a.sin() * 0.36), vec3(0.18, 0.16 + (n % 3) as f32 * 0.05, 0.18), rgb(90, 86, 92, l * (0.8 + (n % 2) as f32 * 0.2)));
|
||||||
|
}
|
||||||
|
for n in 0..3 {
|
||||||
|
cube(at(-0.1 + n as f32 * 0.1, 0.05, 0.03), vec3(0.08, 0.03, 0.04), rgb(130, 130, 140, l));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// defected heroes: a proper camp bed with a white sheet
|
||||||
|
cube(at(0.0, 0.0, 0.14), vec3(0.42, 0.05, 0.78), rgb(120, 86, 52, l));
|
||||||
|
cube(at(0.0, 0.05, 0.18), vec3(0.38, 0.04, 0.5), rgb(220, 220, 228, l));
|
||||||
|
cube(at(0.0, -0.28, 0.2), vec3(0.3, 0.06, 0.14), rgb(240, 240, 245, l));
|
||||||
|
for (dx, dz) in [(-0.18, -0.35), (0.18, -0.35), (-0.18, 0.35), (0.18, 0.35)] {
|
||||||
|
cube(at(dx, dz, 0.06), vec3(0.05, 0.12, 0.05), rgb(100, 70, 44, l));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- training hall
|
||||||
|
|
||||||
|
fn training(c: Vec3, h: u32, l: f32, t: f32, busy: bool) {
|
||||||
|
match h % 3 {
|
||||||
|
0 => {
|
||||||
|
// spinning dummy: post, straw body, two arms
|
||||||
|
let spin = if busy { t * 6.0 } else { (h % 7) as f32 };
|
||||||
|
cube(c + vec3(0.0, 0.35, 0.0), vec3(0.08, 0.7, 0.08), rgb(100, 70, 44, l));
|
||||||
|
cube(c + vec3(0.0, 0.5, 0.0), vec3(0.2, 0.28, 0.2), rgb(200, 170, 100, l));
|
||||||
|
cube(c + vec3(0.0, 0.72, 0.0), vec3(0.14, 0.14, 0.14), rgb(210, 180, 120, l));
|
||||||
|
let (ax, az) = (spin.cos() * 0.3, spin.sin() * 0.3);
|
||||||
|
line3(c + vec3(-ax, 0.58, -az), c + vec3(ax, 0.58, az), 0.06, rgb(120, 84, 50, l));
|
||||||
|
cube(c + vec3(0.0, 0.03, 0.0), vec3(0.34, 0.06, 0.34), rgb(80, 56, 36, l));
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
// weapon rack: frame with swords and an axe
|
||||||
|
cube(c + vec3(-0.3, 0.3, 0.0), vec3(0.06, 0.6, 0.06), rgb(100, 70, 44, l));
|
||||||
|
cube(c + vec3(0.3, 0.3, 0.0), vec3(0.06, 0.6, 0.06), rgb(100, 70, 44, l));
|
||||||
|
cube(c + vec3(0.0, 0.56, 0.0), vec3(0.66, 0.05, 0.08), rgb(110, 78, 48, l));
|
||||||
|
for n in 0..3 {
|
||||||
|
let x = -0.15 + n as f32 * 0.15;
|
||||||
|
cube(c + vec3(x, 0.3, 0.03), vec3(0.03, 0.46, 0.02), rgb(190, 195, 205, l));
|
||||||
|
cube(c + vec3(x, 0.12, 0.03), vec3(0.1, 0.02, 0.03), rgb(110, 80, 40, l));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// target board on a stand, wobbling when hit
|
||||||
|
let wob = if busy { (t * 14.0).sin() * 0.05 } else { 0.0 };
|
||||||
|
cube(c + vec3(0.0, 0.25, 0.1), vec3(0.06, 0.5, 0.06), rgb(100, 70, 44, l));
|
||||||
|
let p = c + vec3(wob, 0.55, 0.0);
|
||||||
|
for (k, (s, col)) in [(0.5, rgb(230, 225, 210, l)), (0.36, rgb(200, 40, 40, l)), (0.22, rgb(230, 225, 210, l)), (0.1, rgb(200, 40, 40, l))].iter().enumerate() {
|
||||||
|
cube(p - vec3(0.0, 0.0, k as f32 * 0.01), vec3(*s, *s, 0.04), *col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- torture chamber
|
||||||
|
|
||||||
|
fn torture(c: Vec3, h: u32, l: f32, t: f32, busy: bool) {
|
||||||
|
match h % 3 {
|
||||||
|
0 => {
|
||||||
|
// the wheel turns while a prisoner is on it
|
||||||
|
let a0 = if busy { t * 1.5 } else { 0.3 };
|
||||||
|
let hub = c + vec3(0.0, 0.5, 0.0);
|
||||||
|
cube(c + vec3(-0.28, 0.25, 0.0), vec3(0.06, 0.5, 0.06), rgb(80, 56, 36, l));
|
||||||
|
cube(c + vec3(0.28, 0.25, 0.0), vec3(0.06, 0.5, 0.06), rgb(80, 56, 36, l));
|
||||||
|
let col = rgb(120, 84, 50, l);
|
||||||
|
for n in 0..8 {
|
||||||
|
let a = a0 + n as f32 * std::f32::consts::FRAC_PI_4;
|
||||||
|
let b = a + std::f32::consts::FRAC_PI_4;
|
||||||
|
let p = |a: f32| hub + vec3(0.0, a.sin() * 0.36, a.cos() * 0.36);
|
||||||
|
line3(hub, p(a), 0.03, col);
|
||||||
|
line3(p(a), p(b), 0.05, col);
|
||||||
|
}
|
||||||
|
cube(hub, vec3(0.62, 0.06, 0.06), rgb(90, 90, 100, l));
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
// iron maiden, slightly open
|
||||||
|
cube(c + vec3(0.0, 0.42, 0.06), vec3(0.4, 0.84, 0.2), rgb(70, 70, 80, l));
|
||||||
|
cube(c + vec3(0.0, 0.78, 0.06), vec3(0.3, 0.12, 0.2), rgb(90, 90, 100, l));
|
||||||
|
cube(c + vec3(-0.12, 0.42, -0.14), vec3(0.18, 0.8, 0.04), rgb(80, 80, 92, l));
|
||||||
|
for n in 0..4 {
|
||||||
|
cube(c + vec3(0.08, 0.2 + n as f32 * 0.16, -0.06), vec3(0.03, 0.03, 0.12), rgb(170, 170, 180, l));
|
||||||
|
}
|
||||||
|
cube(c + vec3(0.0, 0.5, 0.17), vec3(0.08, 0.08, 0.02), rgb(160, 40, 40, l));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// brazier on three legs with pokers (the coals glow in the glow pass)
|
||||||
|
for n in 0..3 {
|
||||||
|
let a = n as f32 * 2.094;
|
||||||
|
line3(c + vec3(a.cos() * 0.2, 0.0, a.sin() * 0.2), c + vec3(a.cos() * 0.08, 0.45, a.sin() * 0.08), 0.03, rgb(60, 60, 66, l));
|
||||||
|
}
|
||||||
|
cube(c + vec3(0.0, 0.5, 0.0), vec3(0.32, 0.08, 0.32), rgb(50, 46, 46, l));
|
||||||
|
line3(c + vec3(0.05, 0.52, 0.0), c + vec3(0.3, 0.8, 0.1), 0.02, rgb(120, 120, 130, l));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- temple
|
||||||
|
|
||||||
|
fn temple(c: Vec3, h: u32, l: f32) {
|
||||||
|
// altar: stone block, red cloth, two candles (flames in the glow pass), a fire bowl behind
|
||||||
|
cube(c + vec3(0.0, 0.22, -0.1), vec3(0.9, 0.44, 0.42), rgb(70, 56, 72, l));
|
||||||
|
cube(c + vec3(0.0, 0.45, -0.1), vec3(0.94, 0.04, 0.46), rgb(90, 74, 92, l));
|
||||||
|
cube(c + vec3(0.0, 0.3, 0.12), vec3(0.4, 0.3, 0.02), rgb(160, 40, 40, l));
|
||||||
|
cube(c + vec3(0.0, 0.3, 0.13), vec3(0.08, 0.08, 0.01), rgb(230, 190, 80, 1.0));
|
||||||
|
for n in [-0.32f32, 0.32] {
|
||||||
|
cube(c + vec3(n, 0.56, -0.18), vec3(0.05, 0.18, 0.05), rgb(230, 220, 200, l));
|
||||||
|
}
|
||||||
|
cube(c + vec3(0.0, 0.3, 0.15), vec3(0.1, 0.6, 0.1), rgb(60, 50, 62, l));
|
||||||
|
cube(c + vec3(0.0, 0.58, 0.15), vec3(0.24, 0.06, 0.24), rgb(90, 70, 40, l));
|
||||||
|
let _ = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn candle_stand(c: Vec3, l: f32) {
|
||||||
|
cube(c + vec3(0.0, 0.2, 0.0), vec3(0.04, 0.4, 0.04), rgb(150, 120, 60, l));
|
||||||
|
cube(c + vec3(0.0, 0.02, 0.0), vec3(0.14, 0.04, 0.14), rgb(150, 120, 60, l));
|
||||||
|
cube(c + vec3(0.0, 0.45, 0.0), vec3(0.05, 0.1, 0.05), rgb(230, 220, 200, l));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- library
|
||||||
|
|
||||||
|
fn shelf(c: Vec3, h: u32, l: f32) {
|
||||||
|
cube(c + vec3(0.0, 0.45, 0.0), vec3(0.86, 0.9, 0.3), rgb(70, 44, 28, l));
|
||||||
|
let colors = [(150, 40, 40), (40, 80, 140), (60, 120, 60), (160, 130, 50), (100, 60, 120), (180, 170, 150)];
|
||||||
|
for row in 0..3 {
|
||||||
|
let y = 0.18 + row as f32 * 0.28;
|
||||||
|
cube(c + vec3(0.0, y - 0.1, 0.0), vec3(0.8, 0.03, 0.32), rgb(90, 60, 38, l));
|
||||||
|
let mut x = -0.36;
|
||||||
|
let mut n = 0;
|
||||||
|
while x < 0.34 {
|
||||||
|
let w = 0.05 + ((h >> ((n + row * 5) % 24)) % 3) as f32 * 0.02;
|
||||||
|
let (r, gg, b) = colors[(hash(n, row, h) % 6) as usize];
|
||||||
|
let ht = 0.16 + ((h >> (n % 20)) % 3) as f32 * 0.025;
|
||||||
|
cube(c + vec3(x + w * 0.5, y - 0.08 + ht * 0.5, -0.02), vec3(w * 0.9, ht, 0.24), rgb(r, gg, b, l));
|
||||||
|
x += w;
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lectern(c: Vec3, l: f32) {
|
||||||
|
cube(c + vec3(0.0, 0.3, 0.0), vec3(0.1, 0.6, 0.1), rgb(90, 60, 38, l));
|
||||||
|
cube(c + vec3(0.0, 0.62, 0.0), vec3(0.42, 0.04, 0.3), rgb(110, 74, 46, l));
|
||||||
|
cube(c + vec3(0.0, 0.66, 0.0), vec3(0.3, 0.02, 0.2), rgb(230, 222, 200, l));
|
||||||
|
cube(c + vec3(0.0, 0.67, 0.0), vec3(0.01, 0.02, 0.2), rgb(120, 40, 40, l));
|
||||||
|
disc(c + vec3(0.0, 0.01, 0.0), 0.25, Color::new(0.0, 0.0, 0.0, 0.25), 12);
|
||||||
|
}
|
||||||
+110
-41
@@ -587,16 +587,25 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, atlas: &Texture2D, wall
|
|||||||
}
|
}
|
||||||
// room decor
|
// room decor
|
||||||
let dc = col(Vec3::splat(amb_at(amb, xf + 0.5, zf + 0.5)), 1.0);
|
let dc = col(Vec3::splat(amb_at(amb, xf + 0.5, zf + 0.5)), 1.0);
|
||||||
match t.kind {
|
// wear and tear: grit and cracks on plain floors
|
||||||
Kind::Lair => {
|
if matches!(t.kind, Kind::Floor) {
|
||||||
let (a, b) = cell_uv(D_BED);
|
let hsh = (x as u32).wrapping_mul(2654435761) ^ (z as u32).wrapping_mul(2246822519);
|
||||||
mb.quad(
|
let hsh = (hsh ^ (hsh >> 15)).wrapping_mul(2246822519);
|
||||||
[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)],
|
let decal = match hsh % 11 {
|
||||||
[vec2(a.x, a.y), vec2(b.x, a.y), vec2(b.x, b.y), vec2(a.x, b.y)],
|
0 | 1 => Some(D_RUBBLE),
|
||||||
[dc; 4],
|
2 => Some(D_CRACK),
|
||||||
[Vec3::Y; 4],
|
_ => 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 => {
|
Kind::Farm => {
|
||||||
let pos = [(0.3, 0.3), (0.7, 0.4), (0.45, 0.72)];
|
let pos = [(0.3, 0.3), (0.7, 0.4), (0.45, 0.72)];
|
||||||
for (k, &(ox, oz)) in pos.iter().enumerate() {
|
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),
|
// training, torture, temple and library furniture and the beds: props.rs
|
||||||
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),
|
|
||||||
// each treasury tile shows what it holds (up to TREASURY_TILE)
|
// 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 fill = (t.gold as f32 / TREASURY_TILE).clamp(0.0, 1.0);
|
||||||
let s = 0.35 + 0.55 * fill;
|
let s = 0.35 + 0.55 * fill;
|
||||||
let sprite = [D_COINS, D_SILVER, D_RUBY, D_DIAMOND][(t.res & 3) as usize];
|
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 (cx, cy) = g.map.core;
|
||||||
let hp = (g.core_hp / CORE_HP).clamp(0.0, 1.0);
|
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() {
|
for (k, &(px, py)) in g.map.portals.iter().enumerate() {
|
||||||
let owned = g.map.portal_owned(k);
|
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);
|
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_landmarks_solid(g, t);
|
||||||
draw_towers_solid(g);
|
draw_towers_solid(g);
|
||||||
|
crate::props::draw_props_solid(g, cam, t);
|
||||||
|
|
||||||
// ---- unlit glow / overlays
|
// ---- unlit glow / overlays
|
||||||
gl_use_material(&self.glow);
|
gl_use_material(&self.glow);
|
||||||
@@ -1044,6 +1052,7 @@ impl R3d {
|
|||||||
// translucent glows and effects last (they write depth, see glow_material)
|
// translucent glows and effects last (they write depth, see glow_material)
|
||||||
draw_landmarks_glow(g, t);
|
draw_landmarks_glow(g, t);
|
||||||
draw_towers_glow(g, t);
|
draw_towers_glow(g, t);
|
||||||
|
crate::props::draw_props_glow(g, cam, t);
|
||||||
for f in &g.fx {
|
for f in &g.fx {
|
||||||
draw_fx(f, t);
|
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)
|
// 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 eye = EYE.with(|e| e.get());
|
||||||
let side = (b - a).cross(eye).normalize_or_zero() * (w * 0.5);
|
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];
|
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 });
|
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 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];
|
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 });
|
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) {
|
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 (cx, cy) = g.map.core;
|
||||||
let c = vec3(cx as f32 + 1.5, 0.0, cy as f32 + 1.5);
|
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.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.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.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
|
// gate pillars
|
||||||
let (gx, gz) = g.gate_center();
|
let (gx, gz) = g.gate_center();
|
||||||
if g.gate_hp > 0.0 {
|
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) {
|
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 (cx, cy) = g.map.core;
|
||||||
let hp = (g.core_hp / CORE_HP).clamp(0.0, 1.0);
|
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 beat = t * (2.0 + (1.0 - hp) * 6.0);
|
||||||
let c = vec3(cx as f32 + 1.5, 1.15 + (t * 1.3).sin() * 0.06, cy as f32 + 1.5);
|
let pulse = 1.0 + beat.sin() * 0.06 + if hp < 0.4 { (t * 31.0).sin() * 0.03 } else { 0.0 };
|
||||||
draw_sphere(c, 0.75 * pulse, None, Color::new(0.4 + 0.5 * hp, 0.05, 0.08, 1.0));
|
let base = vec3(cx as f32 + 1.5, 0.0, cy as f32 + 1.5);
|
||||||
draw_sphere(c, 0.95 * pulse, None, Color::new(1.0, 0.2, 0.15, 0.25));
|
let c = base + vec3(0.0, 1.6 + (t * 1.3).sin() * 0.06, 0.0);
|
||||||
draw_sphere(c + vec3(-0.2, 0.25, 0.2), 0.18, None, Color::new(1.0, 0.85, 0.7, 0.8));
|
draw_sphere(c, 0.62 * pulse, None, Color::new(0.4 + 0.5 * hp, 0.04, 0.07, 1.0));
|
||||||
// portals
|
draw_sphere(c, 0.32 * pulse, None, Color::new(1.0, 0.5 + 0.3 * hp, 0.4, 0.55));
|
||||||
for (pk, &(px, py)) in g.map.portals.iter().enumerate() {
|
draw_sphere(c, 0.85 * pulse, None, Color::new(1.0, 0.2, 0.15, 0.22));
|
||||||
let owned = g.map.portal_owned(pk);
|
draw_sphere(c, 1.15 * pulse, None, Color::new(1.0, 0.1, 0.1, 0.08));
|
||||||
let pc = vec3(px as f32 + 1.5, 0.03, py as f32 + 1.5);
|
draw_sphere(c + vec3(-0.18, 0.2, 0.18), 0.13, None, Color::new(1.0, 0.85, 0.7, 0.8));
|
||||||
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) };
|
for (k, (dx, dz)) in [(-1.15f32, -1.15f32), (1.15, -1.15), (1.15, 1.15), (-1.15, 1.15)].iter().enumerate() {
|
||||||
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 tip = base + vec3(*dx, 1.33, *dz);
|
||||||
let speed = if owned { 1.6 } else { 0.3 };
|
let flick = 0.5 + 0.5 * (t * 5.0 + k as f32 * 1.7).sin();
|
||||||
for a in 0..3 {
|
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);
|
||||||
for k in 0..18 {
|
let col = Color::new(1.0, 0.25 + 0.3 * flick, 0.2, 0.35 + 0.45 * flick * hp.max(0.3));
|
||||||
let f = k as f32 / 18.0;
|
line3(tip, mid, 0.04, col);
|
||||||
let ang = t * speed + a as f32 * 2.094 + f * 4.0;
|
line3(mid, c, 0.04, col);
|
||||||
let r = f * 1.3;
|
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));
|
||||||
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)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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
|
// gate light
|
||||||
if g.gate_hp > 0.0 {
|
if g.gate_hp > 0.0 {
|
||||||
|
|||||||
Reference in new issue
Block a user