Traps and doors made in a new forge room, installed by imps; new creature: the cogling
- forge room: creatures work there (coglings fastest), the work goes into the oldest planned trap or door; finished pieces wait as crates, imps carry and install them - traps: spike pit, poison vent (cloud), fire jet, stone roller, alarm bell (calls creatures), rune mine (one big blast), frost glow (slows around it) - doors: wood, iron, magic (hurls attackers back); own creatures pass, enemies smash them on their way; clicking a door with a door tool locks it for everybody - new 'Traps' category in the toolbar, forge in the rooms; texts EN/DE, bestiary entries; atlas grown for new textures and icons - tests: forge to installed trap, spikes hurt, doors lock and get smashed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1696297ac3
commit
4f20223c93
13 files changed
+1267
-24
No files matched your search
+172
-1
@@ -14,7 +14,7 @@ pub const WALL_H: f32 = 0.95;
|
||||
const CHUNK: i32 = 16;
|
||||
pub const MAX_LIGHTS: usize = 24;
|
||||
const ATLAS_W: f32 = 256.0;
|
||||
const ATLAS_H: f32 = 144.0;
|
||||
const ATLAS_H: f32 = (crate::gfx::ATLAS_ROWS * 16) as f32;
|
||||
const DISP_AMP: f32 = 0.11; // wall surface irregularity
|
||||
|
||||
// ------------------------------------------------------------------ camera
|
||||
@@ -1027,6 +1027,7 @@ impl R3d {
|
||||
draw_towers_solid(g);
|
||||
crate::props::draw_props_solid(g, cam, t);
|
||||
draw_parts_solid(g);
|
||||
draw_traps_solid(g, t);
|
||||
|
||||
// ---- unlit glow / overlays
|
||||
gl_use_material(&self.glow);
|
||||
@@ -1063,6 +1064,7 @@ impl R3d {
|
||||
for f in &g.fx {
|
||||
draw_fx(f, t);
|
||||
}
|
||||
draw_traps_glow(g, t);
|
||||
draw_parts_glow(g);
|
||||
gl_use_default_material();
|
||||
crate::ui::begin_ui();
|
||||
@@ -2119,6 +2121,173 @@ fn draw_projectile(f: &Fx, p: f32, t: f32) {
|
||||
}
|
||||
}
|
||||
|
||||
// a crate with a finished trap or door (in the forge or carried by an imp)
|
||||
fn trap_crate(at: Vec3) {
|
||||
draw_cube(at + vec3(0.0, 0.14, 0.0), vec3(0.34, 0.28, 0.34), None, Color::from_rgba(120, 82, 46, 255));
|
||||
for dy in [0.04f32, 0.24] {
|
||||
draw_cube(at + vec3(0.0, dy, 0.0), vec3(0.36, 0.035, 0.36), None, Color::from_rgba(70, 70, 76, 255));
|
||||
}
|
||||
draw_cube(at + vec3(0.0, 0.29, 0.0), vec3(0.1, 0.02, 0.1), None, Color::from_rgba(200, 60, 40, 255));
|
||||
}
|
||||
|
||||
// traps and doors: the installed pieces, crates in the forge and on the way
|
||||
fn draw_traps_solid(g: &Game, t: f32) {
|
||||
use crate::traps::*;
|
||||
for tr in &g.traps {
|
||||
let (tx, tz) = xy(tr.tile);
|
||||
let c = vec3(tx as f32 + 0.5, 0.0, tz as f32 + 0.5);
|
||||
match tr.stage {
|
||||
CRATED => {
|
||||
let (x, z) = xy(tr.crate_tile);
|
||||
let k = (tr.id % 3) as f32 * 0.2 - 0.2;
|
||||
trap_crate(vec3(x as f32 + 0.5 + k, 0.0, z as f32 + 0.5 - k));
|
||||
}
|
||||
CARRIED => {
|
||||
if let Some(e) = g.ents.iter().find(|e| e.id + 1 == tr.carrier) {
|
||||
trap_crate(vec3(e.x, 0.42, e.y));
|
||||
}
|
||||
}
|
||||
ARMED => {
|
||||
let iron = Color::from_rgba(70, 70, 78, 255);
|
||||
let fresh = (1.0 - tr.fired / 0.6).clamp(0.0, 1.0);
|
||||
match tr.kind {
|
||||
TrapKind::Spikes => {
|
||||
draw_cube(c + vec3(0.0, 0.02, 0.0), vec3(0.8, 0.04, 0.8), None, iron);
|
||||
let up = 0.06 + 0.3 * fresh;
|
||||
for q in 0..9 {
|
||||
let (dx, dz) = ((q % 3) as f32 * 0.24 - 0.24, (q / 3) as f32 * 0.24 - 0.24);
|
||||
line3(c + vec3(dx, 0.03, dz), c + vec3(dx, 0.03 + up, dz), 0.05, Color::from_rgba(200, 200, 212, 255));
|
||||
}
|
||||
}
|
||||
TrapKind::Poison => {
|
||||
draw_cube(c + vec3(0.0, 0.03, 0.0), vec3(0.6, 0.06, 0.6), None, iron);
|
||||
for q in 0..3 {
|
||||
draw_cube(c + vec3(q as f32 * 0.16 - 0.16, 0.065, 0.0), vec3(0.06, 0.02, 0.5), None, Color::from_rgba(30, 40, 26, 255));
|
||||
}
|
||||
}
|
||||
TrapKind::Fire => {
|
||||
// a demon head on a pedestal, its mouth towards the last target
|
||||
draw_cube(c + vec3(0.0, 0.15, 0.0), vec3(0.36, 0.3, 0.36), None, Color::from_rgba(50, 40, 44, 255));
|
||||
let (ax, az) = (tr.aim.cos(), tr.aim.sin());
|
||||
draw_cube(c + vec3(0.0, 0.4, 0.0), vec3(0.3, 0.22, 0.3), None, Color::from_rgba(70, 56, 52, 255));
|
||||
draw_cube(c + vec3(ax * 0.16, 0.38, az * 0.16), vec3(0.14, 0.1, 0.14), None, Color::from_rgba(30, 20, 20, 255));
|
||||
for sgn in [-1.0f32, 1.0] {
|
||||
line3(c + vec3(-az * 0.12 * sgn, 0.5, ax * 0.12 * sgn), c + vec3(-az * 0.2 * sgn - ax * 0.08, 0.68, ax * 0.2 * sgn - az * 0.08), 0.04, Color::from_rgba(214, 204, 178, 255));
|
||||
}
|
||||
}
|
||||
TrapKind::Roller => {
|
||||
// the boulder waits in its niche (until it rolls)
|
||||
if tr.fired > 18.0 || tr.cd <= 0.0 {
|
||||
draw_sphere(c + vec3(0.0, 0.3, 0.0), 0.3, None, Color::from_rgba(120, 112, 104, 255));
|
||||
}
|
||||
draw_cube(c + vec3(0.0, 0.03, 0.0), vec3(0.8, 0.06, 0.8), None, Color::from_rgba(60, 56, 52, 255));
|
||||
}
|
||||
TrapKind::Alarm => {
|
||||
draw_cube(c + vec3(0.0, 0.35, 0.0), vec3(0.06, 0.7, 0.06), None, Color::from_rgba(90, 64, 40, 255));
|
||||
draw_cube(c + vec3(0.0, 0.7, 0.0), vec3(0.34, 0.05, 0.06), None, Color::from_rgba(90, 64, 40, 255));
|
||||
let swing = (t * 18.0).sin() * 0.06 * fresh.max(((tr.fired < 3.0) as u8) as f32);
|
||||
draw_cube(c + vec3(swing, 0.58, 0.0), vec3(0.18, 0.18, 0.18), None, Color::from_rgba(220, 180, 70, 255));
|
||||
}
|
||||
TrapKind::Mine => {
|
||||
draw_cube(c + vec3(0.0, 0.015, 0.0), vec3(0.4, 0.03, 0.4), None, Color::from_rgba(50, 54, 66, 255));
|
||||
}
|
||||
TrapKind::Frost => {
|
||||
draw_cube(c + vec3(0.0, 0.06, 0.0), vec3(0.4, 0.12, 0.4), None, Color::from_rgba(80, 90, 110, 255));
|
||||
}
|
||||
_ => {
|
||||
// doors: posts, lintel and the leaf across the corridor
|
||||
let across_x = g.map.get(tx - 1, tz).map_or(true, |t| t.solid()) || g.map.get(tx + 1, tz).map_or(true, |t| t.solid());
|
||||
let (len, thin) = if across_x { (vec3(1.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0)) } else { (vec3(0.0, 0.0, 1.0), vec3(1.0, 0.0, 0.0)) };
|
||||
let size = |l: f32, h: f32, w: f32| len * l + Vec3::Y * h + thin * w;
|
||||
let (leaf, trim) = match tr.kind {
|
||||
TrapKind::WoodDoor => (Color::from_rgba(120, 80, 44, 255), Color::from_rgba(70, 68, 74, 255)),
|
||||
TrapKind::IronDoor => (Color::from_rgba(96, 98, 108, 255), Color::from_rgba(150, 150, 160, 255)),
|
||||
_ => (Color::from_rgba(70, 36, 100, 255), Color::from_rgba(30, 20, 40, 255)),
|
||||
};
|
||||
for sgn in [-1.0f32, 1.0] {
|
||||
draw_cube(c + len * (0.44 * sgn) + Vec3::Y * 0.6, size(0.12, 1.2, 0.3), None, Color::from_rgba(40, 32, 36, 255));
|
||||
}
|
||||
draw_cube(c + Vec3::Y * 1.22, size(1.0, 0.14, 0.32), None, Color::from_rgba(40, 32, 36, 255));
|
||||
let shake = if tr.fired < 0.2 { (t * 60.0).sin() * 0.02 } else { 0.0 };
|
||||
draw_cube(c + Vec3::Y * 0.56 + thin * shake, size(0.76, 1.1, 0.1), None, leaf);
|
||||
for y in [0.25f32, 0.85] {
|
||||
draw_cube(c + Vec3::Y * y, size(0.78, 0.06, 0.12), None, trim);
|
||||
}
|
||||
if tr.locked {
|
||||
draw_cube(c + Vec3::Y * 0.6, size(0.86, 0.08, 0.16), None, Color::from_rgba(60, 60, 66, 255));
|
||||
draw_cube(c + Vec3::Y * 0.6 + thin * 0.09, vec3(0.1, 0.12, 0.1), None, Color::from_rgba(230, 190, 70, 255));
|
||||
}
|
||||
// damage: darker the more it is beaten
|
||||
let _ = tr.hp / tr.kind.hp();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
for r in &g.rollers {
|
||||
let spin = r.t * 9.0;
|
||||
let at = vec3(r.x, 0.3, r.y);
|
||||
draw_sphere(at, 0.3, None, Color::from_rgba(120, 112, 104, 255));
|
||||
draw_cube(at + vec3(spin.cos() * 0.22 * r.dy.abs(), spin.sin() * 0.22, spin.cos() * 0.22 * r.dx.abs()), Vec3::splat(0.1), None, Color::from_rgba(80, 74, 70, 255));
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_traps_glow(g: &Game, t: f32) {
|
||||
use crate::traps::*;
|
||||
for tr in &g.traps {
|
||||
let (tx, tz) = xy(tr.tile);
|
||||
let c = vec3(tx as f32 + 0.5, 0.0, tz as f32 + 0.5);
|
||||
if tr.stage == ORDERED || tr.stage == CRATED || tr.stage == CARRIED {
|
||||
// the planned spot: a pulsing outline with a small progress bar
|
||||
let a = 0.4 + 0.3 * (t * 3.0 + tr.id as f32).sin();
|
||||
let col = Color::new(0.4, 0.85, 1.0, a);
|
||||
let y = 0.05;
|
||||
for (p0, p1) in [((-0.42f32, -0.42f32), (0.42f32, -0.42f32)), ((0.42, -0.42), (0.42, 0.42)), ((0.42, 0.42), (-0.42, 0.42)), ((-0.42, 0.42), (-0.42, -0.42))] {
|
||||
line3(c + vec3(p0.0, y, p0.1), c + vec3(p1.0, y, p1.1), 0.04, col);
|
||||
}
|
||||
let f = if tr.stage == ORDERED { (tr.progress / tr.kind.work()).clamp(0.0, 1.0) } else { 1.0 };
|
||||
line3(c + vec3(-0.35, y, 0.0), c + vec3(-0.35 + 0.7 * f, y, 0.0), 0.08, Color::new(1.0, 0.7, 0.3, 0.8));
|
||||
continue;
|
||||
}
|
||||
if tr.stage != ARMED {
|
||||
continue;
|
||||
}
|
||||
match tr.kind {
|
||||
TrapKind::Poison => disc(c + vec3(0.0, 0.07, 0.0), 0.22, Color::new(0.4, 1.0, 0.3, 0.3 + 0.15 * (t * 2.0).sin()), 12),
|
||||
TrapKind::Fire => {
|
||||
let (ax, az) = (tr.aim.cos(), tr.aim.sin());
|
||||
let f = 0.5 + 0.5 * (t * 6.0).sin();
|
||||
draw_sphere(c + vec3(ax * 0.24, 0.38, az * 0.24), 0.05 + 0.03 * f, None, Color::new(1.0, 0.5, 0.1, 0.9));
|
||||
}
|
||||
TrapKind::Mine => {
|
||||
let glow = 0.4 + 0.4 * (t * 2.5 + tr.id as f32).sin();
|
||||
ring(c + vec3(0.0, 0.04, 0.0), 0.16, Color::new(0.35, 0.8, 1.0, glow));
|
||||
line3(c + vec3(-0.1, 0.04, 0.0), c + vec3(0.1, 0.04, 0.0), 0.03, Color::new(0.35, 0.8, 1.0, glow));
|
||||
line3(c + vec3(0.0, 0.04, -0.1), c + vec3(0.0, 0.04, 0.1), 0.03, Color::new(0.35, 0.8, 1.0, glow));
|
||||
}
|
||||
TrapKind::Frost => {
|
||||
let p = 1.0 + 0.1 * (t * 2.0).sin();
|
||||
draw_cube(c + vec3(0.0, 0.3, 0.0), vec3(0.12, 0.36 * p, 0.12), None, Color::new(0.75, 0.92, 1.0, 0.9));
|
||||
draw_cube(c + vec3(0.1, 0.22, 0.05), vec3(0.07, 0.2, 0.07), None, Color::new(0.7, 0.9, 1.0, 0.85));
|
||||
draw_cube(c + vec3(-0.08, 0.2, -0.06), vec3(0.07, 0.16, 0.07), None, Color::new(0.7, 0.9, 1.0, 0.85));
|
||||
disc(c + vec3(0.0, 0.02, 0.0), 2.5, Color::new(0.6, 0.85, 1.0, 0.06), 24);
|
||||
}
|
||||
TrapKind::MagicDoor => {
|
||||
let across_x = g.map.get(tx - 1, tz).map_or(true, |t| t.solid()) || g.map.get(tx + 1, tz).map_or(true, |t| t.solid());
|
||||
let thin = if across_x { vec3(0.0, 0.0, 1.0) } else { vec3(1.0, 0.0, 0.0) };
|
||||
let glow = 0.5 + 0.5 * (t * 2.0).sin();
|
||||
for side in [-1.0f32, 1.0] {
|
||||
let p = c + Vec3::Y * 0.6 + thin * (0.06 * side);
|
||||
draw_cube(p, vec3(0.06, 0.4, 0.06), None, Color::new(0.9, 0.5, 1.0, 0.5 + 0.4 * glow));
|
||||
draw_cube(p + Vec3::Y * 0.06, vec3(0.22, 0.06, 0.06), None, Color::new(0.9, 0.5, 1.0, 0.5 + 0.4 * glow));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// particles that glow (embers, sparks, smoke, flashes, shock waves)
|
||||
fn draw_parts_glow(g: &Game) {
|
||||
use crate::particles::*;
|
||||
@@ -2361,6 +2530,8 @@ pub fn minimap_color(tile: &Tile) -> [u8; 4] {
|
||||
Kind::Steel => [150, 160, 180, 255],
|
||||
Kind::ManaSpring => [90, 230, 255, 255],
|
||||
Kind::RuneChest => [80, 200, 255, 255],
|
||||
Kind::Forge => [150, 70, 40, 255],
|
||||
Kind::Door => [140, 90, 50, 255],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user