Fourth master: dwarves get their own side so the yellow AI works; yellow colours and texts; AI cores use the player's throne and heart in their colour

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-06 09:01:57 +01:00
1 parent 450e55d693
commit b29e312e6b
25 files changed
+136 -55

No files matched your search

+55 -43
View File
@@ -1336,7 +1336,7 @@ impl R3d {
}
for rv in g.rivals.iter().filter(|r| r.alive) {
let (rx, rz) = rv.centre();
let c = if rv.side == 2 { vec3(0.3, 1.0, 0.4) } else { vec3(0.35, 0.55, 1.0) };
let c = master_rgb(rv.side);
push(vec3(rx, 1.4, rz), 7.0, c, 1.1, 50.0);
}
// glowing lava near the camera (every third tile)
@@ -2319,10 +2319,8 @@ fn ring(c: Vec3, r: f32, color: Color) {
}
}
fn draw_landmarks_solid(g: &Game, t: f32) {
// soul core: an obsidian throne whose four curved spikes hold the crystal heart in chains
let (cx, cy) = g.map.core;
let c = vec3(cx as f32 + 1.5, 0.0, cy as f32 + 1.5);
// the obsidian throne of a soul core (centre c on the floor)
fn draw_throne(c: Vec3, t: f32) {
let obs = |k: u8| Color::from_rgba(28 + k, 14 + k / 2, 20 + k / 2, 255);
draw_cube(c + vec3(0.0, 0.08, 0.0), vec3(2.9, 0.16, 2.9), None, obs(6));
draw_cube(c + vec3(0.0, 0.22, 0.0), vec3(2.3, 0.14, 2.3), None, obs(14));
@@ -2352,15 +2350,16 @@ fn draw_landmarks_solid(g: &Game, t: f32) {
line3(p0, p1, if q % 2 == 0 { 0.06 } else { 0.035 }, iron);
}
}
// rival masters' cores: obsidian pedestal (the orb glows in the glow pass)
}
fn draw_landmarks_solid(g: &Game, t: f32) {
// soul core: an obsidian throne whose four curved spikes hold the crystal heart in chains
let (cx, cy) = g.map.core;
draw_throne(vec3(cx as f32 + 1.5, 0.0, cy as f32 + 1.5), t);
// the other masters' cores: the same throne (their hearts glow in their colours)
for rv in g.rivals.iter().filter(|r| r.alive && g.map.shown(idx(r.core.0 + 1, r.core.1 + 1))) {
let (rx, rz) = rv.centre();
let c = vec3(rx, 0.0, rz);
draw_cube(c + vec3(0.0, 0.08, 0.0), vec3(2.6, 0.16, 2.6), None, Color::from_rgba(24, 22, 30, 255));
draw_cube(c + vec3(0.0, 0.22, 0.0), vec3(1.8, 0.14, 1.8), None, Color::from_rgba(34, 32, 42, 255));
for (dx, dz) in [(-1.1f32, -1.1f32), (1.1, -1.1), (1.1, 1.1), (-1.1, 1.1)] {
draw_cube(c + vec3(dx, 0.5, dz), vec3(0.22, 1.0, 0.22), None, Color::from_rgba(30, 28, 38, 255));
}
draw_throne(vec3(rx, 0.0, rz), t + rv.side as f32);
}
// portals: a demon gate - an arch of stone blocks with bone ribs and horns on a stepped
// platform (the vortex glows in the glow pass); grey and dead until claimed
@@ -2447,46 +2446,64 @@ fn draw_landmarks_solid(g: &Game, t: f32) {
let _ = t;
}
fn draw_landmarks_glow(g: &Game, t: f32, cam_r: Vec3) {
// soul core: a faceted crystal heart beating lub-dub (faster when hurt, cracked when badly
// hurt), glowing veins across the floor, rising embers
let (cx, cy) = g.map.core;
let hp = (g.core_hp / CORE_HP).clamp(0.0, 1.0);
// the colour of a master side (the first master red, rivals green, blue and yellow)
pub fn master_rgb(side: u8) -> Vec3 {
match side {
2 => vec3(0.3, 0.95, 0.4),
3 => vec3(0.35, 0.55, 1.0),
4 => vec3(1.0, 0.85, 0.25),
_ => vec3(1.0, 0.2, 0.2),
}
}
// recolours a red-tinted colour into `tint`: keeps its brightness and whiteness, swaps the hue
fn hue_to(tint: Option<Vec3>, c: Color) -> Color {
let Some(t) = tint else { return c };
let hi = c.r.max(c.g).max(c.b);
let lo = c.r.min(c.g).min(c.b);
let m = t.x.max(t.y).max(t.z).max(0.001);
let k = (hi - lo) / m;
Color::new(lo + t.x * k, lo + t.y * k, lo + t.z * k, c.a)
}
// a soul core's crystal heart beating lub-dub (faster when hurt, cracked when badly hurt),
// glowing veins across the floor, rising embers; `tint`: another master's colour for the red
fn draw_heart(base: Vec3, hp: f32, t: f32, cam_r: Vec3, tint: Option<Vec3>) {
let tc = |r: f32, g: f32, b: f32, a: f32| hue_to(tint, Color::new(r, g, b, a));
let rate = 0.9 + (1.0 - hp) * 1.6;
let ph = (t * rate).fract();
let beat = (-((ph - 0.02) / 0.06).powi(2)).exp() + 0.65 * (-((ph - 0.2) / 0.06).powi(2)).exp();
let pulse = 1.0 + beat * 0.09;
let base = vec3(cx as f32 + 1.5, 0.0, cy as f32 + 1.5);
let hc = base + vec3(0.0, 1.55 + (t * 1.1).sin() * 0.03, 0.0);
let right = vec3(cam_r.x, 0.0, cam_r.z).normalize_or_zero();
let up = Vec3::Y;
let eye = EYE.with(|e| e.get());
let front = vec3(eye.x, 0.0, eye.z).normalize_or_zero(); // towards the viewer
let red = Color::new(0.38 + 0.38 * hp + 0.2 * beat, 0.02 + 0.04 * beat, 0.07, 1.0);
let hot = Color::new(1.0, 0.35 + 0.25 * beat, 0.3, 0.7);
let red = tc(0.38 + 0.38 * hp + 0.2 * beat, 0.02 + 0.04 * beat, 0.07, 1.0);
let hot = tc(1.0, 0.35 + 0.25 * beat, 0.3, 0.7);
// two lobes and the point below, then the facets as bright chips
let s0 = 0.36 * pulse;
for sgn in [-1.0f32, 1.0] {
draw_sphere(hc + right * (sgn * 0.21 * pulse) + up * (0.12 * pulse), s0 * 0.85, None, red);
}
for (k, (y, r)) in [(-0.05f32, 0.36f32), (-0.22, 0.28), (-0.36, 0.18), (-0.46, 0.09)].iter().enumerate() {
draw_sphere(hc + up * (y * pulse), r * pulse, None, if k == 0 { red } else { Color::new(red.r * 0.9, red.g, red.b, 1.0) });
draw_sphere(hc + up * (y * pulse), r * pulse, None, if k == 0 { red } else { tc(red.r * 0.9, red.g, red.b, 1.0) });
}
draw_sphere(hc + right * (-0.12 * pulse) + up * (0.2 * pulse), 0.1 * pulse, None, Color::new(1.0, 0.75, 0.7, 0.75));
draw_sphere(hc + right * (-0.12 * pulse) + up * (0.2 * pulse), 0.1 * pulse, None, tc(1.0, 0.75, 0.7, 0.75));
for k in 0..7 {
let a = k as f32 * 0.9 + 0.4;
let p = hc + right * (a.cos() * 0.22 * pulse) + up * (a.sin() * 0.2 * pulse - 0.05) + front * 0.3;
draw_cube(p, Vec3::splat(0.05), None, Color::new(1.0, 0.5 + 0.3 * ((t * 3.0 + k as f32).sin() * 0.5 + 0.5), 0.45, 0.8));
draw_cube(p, Vec3::splat(0.05), None, tc(1.0, 0.5 + 0.3 * ((t * 3.0 + k as f32).sin() * 0.5 + 0.5), 0.45, 0.8));
}
draw_sphere(hc, 0.75 * pulse, None, Color::new(1.0, 0.15, 0.15, 0.16 + 0.12 * beat));
draw_sphere(hc, 1.1 * pulse, None, Color::new(1.0, 0.1, 0.1, 0.06 + 0.05 * beat));
draw_sphere(hc, 0.75 * pulse, None, tc(1.0, 0.15, 0.15, 0.16 + 0.12 * beat));
draw_sphere(hc, 1.1 * pulse, None, tc(1.0, 0.1, 0.1, 0.06 + 0.05 * beat));
// cracks when hurt
let cracks = ((1.0 - hp) * 7.0) as usize;
for k in 0..cracks.min(6) {
let a = k as f32 * 2.3;
let p0 = hc + right * (a.cos() * 0.08) + up * (a.sin() * 0.1);
let p1 = p0 + right * (a.cos() * 0.25) + up * ((a * 1.7).sin() * 0.22);
line3(p0 + front * 0.37, p1 + front * 0.33, 0.025, Color::new(0.08, 0.0, 0.02, 1.0));
line3(p0 + front * 0.37, p1 + front * 0.33, 0.025, tc(0.08, 0.0, 0.02, 1.0));
}
// veins across the floor, a pulse running outwards with every beat
for k in 0..8 {
@@ -2497,7 +2514,7 @@ fn draw_landmarks_glow(g: &Game, t: f32, cam_r: Vec3) {
let wig = ((k * 7 + q * 3) as f32).sin() * 0.18;
let next = base + vec3(a.cos() * (1.3 + f * 2.2) - a.sin() * wig, 0.02, a.sin() * (1.3 + f * 2.2) + a.cos() * wig);
let wave = (-((f - ph * 1.6) / 0.18).powi(2)).exp();
line3(prev, next, 0.05, Color::new(1.0, 0.15 + 0.3 * wave, 0.1, (0.25 + 0.7 * wave) * (1.0 - f * 0.5) * (0.4 + 0.6 * hp)));
line3(prev, next, 0.05, tc(1.0, 0.15 + 0.3 * wave, 0.1, (0.25 + 0.7 * wave) * (1.0 - f * 0.5) * (0.4 + 0.6 * hp)));
prev = next;
}
}
@@ -2506,22 +2523,20 @@ fn draw_landmarks_glow(g: &Game, t: f32, cam_r: Vec3) {
let e = (t * 0.35 + k as f32 * 0.137).fract();
let a = k as f32 * 2.4;
let p = hc + vec3(a.cos() * (0.2 + e * 0.5), -0.2 + e * 2.0, a.sin() * (0.2 + e * 0.5));
draw_cube(p, Vec3::splat(0.04 * (1.0 - e) + 0.01), None, Color::new(1.0, 0.45 + 0.4 * (1.0 - e), 0.15, 1.0 - e));
draw_cube(p, Vec3::splat(0.04 * (1.0 - e) + 0.01), None, tc(1.0, 0.45 + 0.4 * (1.0 - e), 0.15, 1.0 - e));
}
let _ = hot;
// rival masters' cores in their colours
}
fn draw_landmarks_glow(g: &Game, t: f32, cam_r: Vec3) {
let (cx, cy) = g.map.core;
let hp = (g.core_hp / CORE_HP).clamp(0.0, 1.0);
draw_heart(vec3(cx as f32 + 1.5, 0.0, cy as f32 + 1.5), hp, t, cam_r, None);
// the other masters' hearts: the same heart in their colours
for rv in g.rivals.iter().filter(|r| r.alive && g.map.shown(idx(r.core.0 + 1, r.core.1 + 1))) {
let (rx, rz) = rv.centre();
let hp = (rv.hp / crate::rival::RIVAL_CORE_HP).clamp(0.0, 1.0);
let col = if rv.side == 2 { vec3(0.3, 0.95, 0.4) } else { vec3(0.35, 0.55, 1.0) };
let pulse = 1.0 + (t * (2.0 + (1.0 - hp) * 6.0) + rv.side as f32).sin() * 0.06;
let c = vec3(rx, 1.3 + (t * 1.2).sin() * 0.05, rz);
draw_sphere(c, 0.55 * pulse, None, Color::new(col.x * (0.4 + 0.6 * hp), col.y * (0.4 + 0.6 * hp), col.z * (0.4 + 0.6 * hp), 1.0));
draw_sphere(c, 0.85 * pulse, None, Color::new(col.x, col.y, col.z, 0.22));
for k in 0..4 {
let a = t * 0.8 + k as f32 * 1.57;
draw_cube(c + vec3(a.cos() * 0.9, (t + k as f32).sin() * 0.2, a.sin() * 0.9), vec3(0.08, 0.14, 0.04), None, Color::new(col.x, col.y, col.z, 0.8));
}
draw_heart(vec3(rx, 0.0, rz), hp, t + rv.side as f32 * 0.37, cam_r, Some(master_rgb(rv.side)));
}
// portals: runes on the arch and a vertical vortex inside it, sparks pouring out and falling
for (pk, &(px, py)) in g.map.portals.iter().enumerate() {
@@ -2604,11 +2619,8 @@ fn draw_shadows(g: &Game) {
} else if e.dwarf() {
Color::new(0.85, 0.55, 0.25, 0.85)
} else if e.rival() {
if e.side == 2 {
Color::new(0.35, 0.95, 0.4, 0.85)
} else {
Color::new(0.4, 0.6, 1.0, 0.85)
}
let c = master_rgb(e.side);
Color::new(c.x, c.y, c.z, 0.85)
} else if e.guard {
Color::new(1.0, 0.85, 0.3, 0.9)
} else if e.hero {