Fog of war: unexplored darkness until the dungeon's ground, creatures or towers come close
- explored tiles stay known (saved compactly as bits), enemies are only drawn while in sight; sight runs along open ground, walls block it - unexplored tiles are black blocks; units, towers, torches, furniture, portals, gate and rival cores in the dark are hidden; minimap and tile info too - the nearest portal is known from the start; the seer's eye opens a wide area for 20 seconds without remembering it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7f4d5ca7b4
commit
240e89d9cb
11 files changed
+244
-17
No files matched your search
@@ -826,3 +826,4 @@ tool.plague.desc = Eine Krankheit, die an den Feinden rund um die Stelle zehrt u
|
|||||||
tool.quake.label = Beben
|
tool.quake.label = Beben
|
||||||
tool.quake.name = Beben
|
tool.quake.name = Beben
|
||||||
tool.quake.desc = Der Boden bebt: Feinde rund um die Stelle werden verletzt und zwei Sekunden betäubt. Deine Kreaturen bleiben standfest.
|
tool.quake.desc = Der Boden bebt: Feinde rund um die Stelle werden verletzt und zwei Sekunden betäubt. Deine Kreaturen bleiben standfest.
|
||||||
|
tile.unexplored = Unerforschte Finsternis
|
||||||
@@ -827,3 +827,4 @@ tool.plague.desc = A sickness that eats at the enemies around the spot and jumps
|
|||||||
tool.quake.label = Quake
|
tool.quake.label = Quake
|
||||||
tool.quake.name = Quake
|
tool.quake.name = Quake
|
||||||
tool.quake.desc = The ground shakes: enemies around the spot are hurt and stunned for two seconds. Your creatures keep their footing.
|
tool.quake.desc = The ground shakes: enemies around the spot are hurt and stunned for two seconds. Your creatures keep their footing.
|
||||||
|
tile.unexplored = Unexplored darkness
|
||||||
@@ -505,6 +505,7 @@ pub fn start_mission(i: usize, carry: Option<&Carry>) -> Game {
|
|||||||
}
|
}
|
||||||
g.place_dwarf_caves(d.seed);
|
g.place_dwarf_caves(d.seed);
|
||||||
g.init_research(Some(i));
|
g.init_research(Some(i));
|
||||||
|
g.init_fog();
|
||||||
let heart = d.gate || d.forts.iter().any(|f| f.heart);
|
let heart = d.gate || d.forts.iter().any(|f| f.heart);
|
||||||
if !heart {
|
if !heart {
|
||||||
g.gate_hp = 0.0;
|
g.gate_hp = 0.0;
|
||||||
|
|||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
// Fog of war: everything except the start area is black until the dungeon's ground, creatures
|
||||||
|
// or towers come close; explored tiles stay known. Enemies are only seen while they are in
|
||||||
|
// sight of the dungeon's units, towers or ground. The seer's eye opens an area for a while.
|
||||||
|
|
||||||
|
use crate::game::*;
|
||||||
|
use crate::map::*;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
const SIGHT: i32 = 6; // tiles a creature sees along open ground
|
||||||
|
const FOG_EVERY: f32 = 0.4;
|
||||||
|
|
||||||
|
// explored tiles in savegames: one bit per tile, base64
|
||||||
|
pub mod bits {
|
||||||
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
|
||||||
|
pub fn serialize<S: Serializer>(v: &[bool], s: S) -> Result<S::Ok, S::Error> {
|
||||||
|
let mut bytes = vec![0u8; (v.len() + 7) / 8];
|
||||||
|
for (i, &b) in v.iter().enumerate() {
|
||||||
|
if b {
|
||||||
|
bytes[i / 8] |= 1 << (i % 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.serialize_str(&format!("{}:{}", v.len(), crate::map::b64_encode(&bytes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<bool>, D::Error> {
|
||||||
|
let s = String::deserialize(d)?;
|
||||||
|
let Some((n, data)) = s.split_once(':') else { return Ok(Vec::new()) };
|
||||||
|
let n: usize = n.parse().unwrap_or(0);
|
||||||
|
let bytes = crate::map::b64_decode(data).unwrap_or_default();
|
||||||
|
Ok((0..n).map(|i| bytes.get(i / 8).map_or(false, |b| b & (1 << (i % 8)) != 0)).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Map {
|
||||||
|
// drawn at all: explored, or seen right now (no fog: everything)
|
||||||
|
pub fn shown(&self, i: usize) -> bool {
|
||||||
|
self.explored.is_empty() || self.explored.get(i).copied().unwrap_or(true) || self.visible.get(i).copied().unwrap_or(false)
|
||||||
|
}
|
||||||
|
// in sight right now (enemies are only drawn there)
|
||||||
|
pub fn in_sight(&self, i: usize) -> bool {
|
||||||
|
self.explored.is_empty() || self.visible.get(i).copied().unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Game {
|
||||||
|
pub fn init_fog(&mut self) {
|
||||||
|
let n = self.map.t.len();
|
||||||
|
self.map.explored = vec![false; n];
|
||||||
|
self.map.visible = vec![false; n];
|
||||||
|
self.fog_t = 0.0;
|
||||||
|
// the nearest portal is known from the start (the dungeon feels where creatures come from)
|
||||||
|
let (cx, cy) = self.map.core;
|
||||||
|
if let Some(&(px, py)) = self.map.portals.iter().min_by_key(|&&(px, py)| (px - cx).pow(2) + (py - cy).pow(2)) {
|
||||||
|
for y in py - 3..=py + 5 {
|
||||||
|
for x in px - 3..=px + 5 {
|
||||||
|
if inb(x, y) {
|
||||||
|
self.map.explored[idx(x, y)] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.update_fog(1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_fog(&mut self, dt: f32) {
|
||||||
|
if self.map.explored.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.fog_t -= dt;
|
||||||
|
if self.fog_t > 0.0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.fog_t = FOG_EVERY;
|
||||||
|
let n = self.map.t.len();
|
||||||
|
if self.map.visible.len() != n {
|
||||||
|
self.map.visible = vec![false; n];
|
||||||
|
}
|
||||||
|
let mut vis = vec![false; n];
|
||||||
|
let (w, h) = (mw() as i32, mh() as i32);
|
||||||
|
// own ground and everything around it
|
||||||
|
for i in 0..n {
|
||||||
|
if self.map.t[i].owner == Owner::Player {
|
||||||
|
let (x, y) = xy(i);
|
||||||
|
for dy in -1..=1 {
|
||||||
|
for dx in -1..=1 {
|
||||||
|
if inb(x + dx, y + dy) {
|
||||||
|
vis[idx(x + dx, y + dy)] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// what the creatures and towers see along open ground (walls stop the view)
|
||||||
|
let mut eyes: Vec<(usize, i32)> = self.ents.iter().filter(|e| e.alive && !e.held && !e.hero && e.side == 0 && !e.chained).map(|e| (e.tile(), SIGHT)).collect();
|
||||||
|
eyes.extend(self.towers.iter().filter(|t| !t.hero).map(|t| (t.tile, t.range() as i32 + 1)));
|
||||||
|
let mut stamp = vec![0u32; n];
|
||||||
|
let mut q = VecDeque::new();
|
||||||
|
for (sid, &(start, r)) in eyes.iter().enumerate() {
|
||||||
|
let sid = sid as u32 + 1;
|
||||||
|
q.clear();
|
||||||
|
q.push_back((start, 0));
|
||||||
|
stamp[start] = sid;
|
||||||
|
while let Some((i, d)) = q.pop_front() {
|
||||||
|
vis[i] = true;
|
||||||
|
if (self.map.t[i].solid() && i != start) || d >= r {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (x, y) = xy(i);
|
||||||
|
for &(dx, dy) in &DIR8 {
|
||||||
|
let (nx, ny) = (x + dx, y + dy);
|
||||||
|
if nx >= 0 && ny >= 0 && nx < w && ny < h {
|
||||||
|
let j = idx(nx, ny);
|
||||||
|
if stamp[j] != sid {
|
||||||
|
stamp[j] = sid;
|
||||||
|
q.push_back((j, d + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in 0..n {
|
||||||
|
if vis[i] {
|
||||||
|
self.map.explored[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the seer's eye: a wide view for a while (not remembered)
|
||||||
|
for &(ex, ey, _) in &self.reveals {
|
||||||
|
let r = crate::spells::EYE_RADIUS as i32;
|
||||||
|
for y in ey as i32 - r..=ey as i32 + r {
|
||||||
|
for x in ex as i32 - r..=ex as i32 + r {
|
||||||
|
if inb(x, y) && ((x as f32 + 0.5 - ex).powi(2) + (y as f32 + 0.5 - ey).powi(2)) <= (r * r) as f32 {
|
||||||
|
vis[idx(x, y)] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.map.visible = vis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fog_lifts_around_the_dungeon_and_is_saved() {
|
||||||
|
let mut g = Game::new(6, 64, 64);
|
||||||
|
let (cx, cy) = g.map.core;
|
||||||
|
assert!(g.map.shown(idx(cx + 1, cy + 1)));
|
||||||
|
assert!(!g.map.shown(idx(60, 60)), "far away is unexplored");
|
||||||
|
let explored = g.map.explored.iter().filter(|&&b| b).count();
|
||||||
|
// a creature walking out explores more
|
||||||
|
g.spawn(EK::Kobold, false, cx as f32 + 1.5, cy as f32 - 2.5, 1);
|
||||||
|
g.update_fog(1.0);
|
||||||
|
assert!(g.map.explored.iter().filter(|&&b| b).count() >= explored);
|
||||||
|
// savegame round trip
|
||||||
|
let json = crate::save::to_json(&g, &crate::render3d::Cam::new(0.0, 0.0), &crate::settings::Settings::default());
|
||||||
|
let l = crate::save::from_bytes(json.as_bytes()).unwrap();
|
||||||
|
assert_eq!(l.game.map.explored, g.map.explored);
|
||||||
|
// the seer's eye opens a far area for a while
|
||||||
|
g.reveals.push((40.0, 40.0, 5.0));
|
||||||
|
g.update_fog(1.0);
|
||||||
|
assert!(g.map.in_sight(idx(40, 40)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -483,6 +483,8 @@ pub struct Game {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub reveals: Vec<(f32, f32, f32)>, // seer's eye: x, y, seconds left
|
pub reveals: Vec<(f32, f32, f32)>, // seer's eye: x, y, seconds left
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
pub fog_t: f32, // fog of war: seconds until the next update
|
||||||
|
#[serde(skip)]
|
||||||
pub forge_work: f32, // work done in the forge this frame
|
pub forge_work: f32, // work done in the forge this frame
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub demo: Option<crate::demo::Demo>, // the endless bridge battle behind the title menu
|
pub demo: Option<crate::demo::Demo>, // the endless bridge battle behind the title menu
|
||||||
@@ -576,6 +578,7 @@ impl Game {
|
|||||||
g.guard_treasures();
|
g.guard_treasures();
|
||||||
g.place_dwarf_caves(seed);
|
g.place_dwarf_caves(seed);
|
||||||
g.init_research(None);
|
g.init_research(None);
|
||||||
|
g.init_fog();
|
||||||
g.msg("msg.welcome1", vec![]);
|
g.msg("msg.welcome1", vec![]);
|
||||||
g.msg("msg.welcome2", vec![]);
|
g.msg("msg.welcome2", vec![]);
|
||||||
g.msg("msg.welcome3", vec![]);
|
g.msg("msg.welcome3", vec![]);
|
||||||
@@ -652,6 +655,7 @@ impl Game {
|
|||||||
rollers: Vec::new(),
|
rollers: Vec::new(),
|
||||||
clouds: Vec::new(),
|
clouds: Vec::new(),
|
||||||
forge_work: 0.0,
|
forge_work: 0.0,
|
||||||
|
fog_t: 0.0,
|
||||||
reveals: Vec::new(),
|
reveals: Vec::new(),
|
||||||
known: Vec::new(),
|
known: Vec::new(),
|
||||||
researchable: Vec::new(),
|
researchable: Vec::new(),
|
||||||
@@ -1105,6 +1109,7 @@ impl Game {
|
|||||||
self.update_forge(dt);
|
self.update_forge(dt);
|
||||||
self.update_research();
|
self.update_research();
|
||||||
self.update_reveals(dt);
|
self.update_reveals(dt);
|
||||||
|
self.update_fog(dt);
|
||||||
if !self.rivals.is_empty() {
|
if !self.rivals.is_empty() {
|
||||||
self.update_rivals(dt);
|
self.update_rivals(dt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ mod campaign;
|
|||||||
mod cloud;
|
mod cloud;
|
||||||
mod demo;
|
mod demo;
|
||||||
mod dwarves;
|
mod dwarves;
|
||||||
|
mod fog;
|
||||||
mod fortress;
|
mod fortress;
|
||||||
mod game;
|
mod game;
|
||||||
mod gfx;
|
mod gfx;
|
||||||
|
|||||||
+7
-3
@@ -290,6 +290,10 @@ pub struct Map {
|
|||||||
pub gate: (i32, i32),
|
pub gate: (i32, i32),
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub treasures: Vec<(i32, i32)>, // centres of the treasure caves
|
pub treasures: Vec<(i32, i32)>, // centres of the treasure caves
|
||||||
|
#[serde(default, with = "crate::fog::bits")]
|
||||||
|
pub explored: Vec<bool>, // fog of war: tiles the dungeon knows (empty: no fog)
|
||||||
|
#[serde(skip)]
|
||||||
|
pub visible: Vec<bool>, // fog of war: tiles in sight right now
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_size() -> usize {
|
fn default_size() -> usize {
|
||||||
@@ -527,7 +531,7 @@ impl Map {
|
|||||||
tl.hp = 6.0;
|
tl.hp = 6.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Map { w, h, t, core, portals, legacy_portal: None, gate, treasures }
|
Map { w, h, t, core, portals, legacy_portal: None, gate, treasures, explored: Vec::new(), visible: Vec::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carves a meandering river; the player's area, the core and portals stay dry.
|
// Carves a meandering river; the player's area, the core and portals stay dry.
|
||||||
@@ -712,7 +716,7 @@ impl Map {
|
|||||||
|
|
||||||
const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
fn b64_encode(data: &[u8]) -> String {
|
pub fn b64_encode(data: &[u8]) -> String {
|
||||||
let mut out = String::with_capacity(data.len() * 4 / 3 + 4);
|
let mut out = String::with_capacity(data.len() * 4 / 3 + 4);
|
||||||
for chunk in data.chunks(3) {
|
for chunk in data.chunks(3) {
|
||||||
let b = [chunk[0], *chunk.get(1).unwrap_or(&0), *chunk.get(2).unwrap_or(&0)];
|
let b = [chunk[0], *chunk.get(1).unwrap_or(&0), *chunk.get(2).unwrap_or(&0)];
|
||||||
@@ -728,7 +732,7 @@ fn b64_encode(data: &[u8]) -> String {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
fn b64_decode(s: &str) -> Option<Vec<u8>> {
|
pub fn b64_decode(s: &str) -> Option<Vec<u8>> {
|
||||||
let mut val = [255u8; 256];
|
let mut val = [255u8; 256];
|
||||||
for (i, &c) in B64.iter().enumerate() {
|
for (i, &c) in B64.iter().enumerate() {
|
||||||
val[c as usize] = i as u8;
|
val[c as usize] = i as u8;
|
||||||
|
|||||||
@@ -114,6 +114,9 @@ pub fn draw_props_solid(g: &Game, cam: &Cam, t: f32) {
|
|||||||
for z in z0..=z1 {
|
for z in z0..=z1 {
|
||||||
for x in x0..=x1 {
|
for x in x0..=x1 {
|
||||||
let i = idx(x, z);
|
let i = idx(x, z);
|
||||||
|
if !g.map.shown(i) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let tile = &g.map.t[i];
|
let tile = &g.map.t[i];
|
||||||
let l = light_of(tile);
|
let l = light_of(tile);
|
||||||
let c = vec3(x as f32 + 0.5, 0.0, z as f32 + 0.5);
|
let c = vec3(x as f32 + 0.5, 0.0, z as f32 + 0.5);
|
||||||
@@ -200,6 +203,9 @@ pub fn draw_props_glow(g: &Game, cam: &Cam, t: f32) {
|
|||||||
for z in z0..=z1 {
|
for z in z0..=z1 {
|
||||||
for x in x0..=x1 {
|
for x in x0..=x1 {
|
||||||
let i = idx(x, z);
|
let i = idx(x, z);
|
||||||
|
if !g.map.shown(i) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let tile = &g.map.t[i];
|
let tile = &g.map.t[i];
|
||||||
let c = vec3(x as f32 + 0.5, 0.0, z as f32 + 0.5);
|
let c = vec3(x as f32 + 0.5, 0.0, z as f32 + 0.5);
|
||||||
let h = hash(x, z, 77);
|
let h = hash(x, z, 77);
|
||||||
|
|||||||
+47
-12
@@ -401,6 +401,12 @@ fn amb_at(f: &[f32], x: f32, z: f32) -> f32 {
|
|||||||
|
|
||||||
const LIGHT_DIR: Vec3 = Vec3::new(0.45, 0.8, 0.35);
|
const LIGHT_DIR: Vec3 = Vec3::new(0.45, 0.8, 0.35);
|
||||||
|
|
||||||
|
// fog of war: units in unexplored tiles, and enemies out of sight, are not drawn
|
||||||
|
pub fn unit_hidden(g: &Game, e: &Ent) -> bool {
|
||||||
|
let i = e.tile();
|
||||||
|
!g.map.shown(i) || (e.side() != 0 && !g.map.in_sight(i))
|
||||||
|
}
|
||||||
|
|
||||||
fn solid_at(map: &Map, x: i32, z: i32) -> bool {
|
fn solid_at(map: &Map, x: i32, z: i32) -> bool {
|
||||||
map.get(x, z).map_or(true, |t| t.solid())
|
map.get(x, z).map_or(true, |t| t.solid())
|
||||||
}
|
}
|
||||||
@@ -494,6 +500,26 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, atlas: &Texture2D, wall
|
|||||||
let tex = Gfx::tile_tex(t.kind, t.owner, t.var, t.reinforced);
|
let tex = Gfx::tile_tex(t.kind, t.owner, t.var, t.reinforced);
|
||||||
let (ua, ub) = cell_uv(tex);
|
let (ua, ub) = cell_uv(tex);
|
||||||
let (xf, zf) = (x as f32, z as f32);
|
let (xf, zf) = (x as f32, z as f32);
|
||||||
|
if !map.shown(idx(x, z)) {
|
||||||
|
// fog of war: a black block, with black sides towards known open ground
|
||||||
|
let black = col(Vec3::splat(0.0), 1.0);
|
||||||
|
mb.quad([vec3(xf, WALL_H, zf), vec3(xf + 1.0, WALL_H, zf), vec3(xf + 1.0, WALL_H, zf + 1.0), vec3(xf, WALL_H, zf + 1.0)], [ua, ua, ua, ua], [black; 4], [Vec3::Y; 4]);
|
||||||
|
for (d, &(dx, dz)) in DIR4.iter().enumerate() {
|
||||||
|
let open = map.get(x + dx, z + dz).map_or(false, |o| !o.solid()) && map.shown(idx((x + dx).max(0), (z + dz).max(0)));
|
||||||
|
if !open {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (p, q) = match d {
|
||||||
|
0 => (vec3(xf + 1.0, 0.0, zf), vec3(xf, 0.0, zf)),
|
||||||
|
1 => (vec3(xf + 1.0, 0.0, zf + 1.0), vec3(xf + 1.0, 0.0, zf)),
|
||||||
|
2 => (vec3(xf, 0.0, zf + 1.0), vec3(xf + 1.0, 0.0, zf + 1.0)),
|
||||||
|
_ => (vec3(xf, 0.0, zf), vec3(xf, 0.0, zf + 1.0)),
|
||||||
|
};
|
||||||
|
let out = vec3(dx as f32, 0.0, dz as f32);
|
||||||
|
mb.quad([p + Vec3::Y * WALL_H, q + Vec3::Y * WALL_H, q - Vec3::Y * LIQ_D, p - Vec3::Y * LIQ_D], [ua, ua, ua, ua], [black; 4], [out; 4]);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if let Some(river) = t.river() {
|
if let Some(river) = t.river() {
|
||||||
// ---- river bed: sunken surface, banks where the ground or a wall begins
|
// ---- river bed: sunken surface, banks where the ground or a wall begins
|
||||||
let lava = river == Kind::Lava;
|
let lava = river == Kind::Lava;
|
||||||
@@ -798,7 +824,8 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32) -> u64 {
|
|||||||
Kind::Treasury => (t.gold / 50) as u64 | (t.res as u64) << 5,
|
Kind::Treasury => (t.gold / 50) as u64 | (t.res as u64) << 5,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
(t.kind as u64) | (t.owner as u64) << 5 | (t.marked as u64) << 7 | (t.reinforced as u64) << 8 | extra << 9
|
let shown = map.shown(idx(x, z)) as u64;
|
||||||
|
(t.kind as u64) | (t.owner as u64) << 5 | (t.marked as u64) << 8 | (t.reinforced as u64) << 9 | shown << 10 | extra << 11
|
||||||
}
|
}
|
||||||
None => 0xff,
|
None => 0xff,
|
||||||
};
|
};
|
||||||
@@ -1021,7 +1048,7 @@ impl R3d {
|
|||||||
}
|
}
|
||||||
// solid unlit objects (default material, depth tested)
|
// solid unlit objects (default material, depth tested)
|
||||||
gl_use_default_material();
|
gl_use_default_material();
|
||||||
for tr in &g.torches {
|
for tr in g.torches.iter().filter(|tr| g.map.shown(tr.wall)) {
|
||||||
let (x, z) = tr.pos();
|
let (x, z) = tr.pos();
|
||||||
draw_cube(vec3(x, 0.55, z), vec3(0.07, 0.3, 0.07), None, Color::from_rgba(70, 45, 28, 255));
|
draw_cube(vec3(x, 0.55, z), vec3(0.07, 0.3, 0.07), None, Color::from_rgba(70, 45, 28, 255));
|
||||||
}
|
}
|
||||||
@@ -1046,7 +1073,7 @@ impl R3d {
|
|||||||
draw_mesh(k);
|
draw_mesh(k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (k, tr) in g.torches.iter().enumerate() {
|
for (k, tr) in g.torches.iter().enumerate().filter(|(_, tr)| g.map.shown(tr.wall)) {
|
||||||
let (x, z) = tr.pos();
|
let (x, z) = tr.pos();
|
||||||
let fl = 1.0 + 0.15 * (t * 11.0 + k as f32 * 2.3).sin();
|
let fl = 1.0 + 0.15 * (t * 11.0 + k as f32 * 2.3).sin();
|
||||||
draw_sphere(vec3(x, 0.78, z), 0.09 * fl, None, Color::new(1.0, 0.55, 0.15, 0.9));
|
draw_sphere(vec3(x, 0.78, z), 0.09 * fl, None, Color::new(1.0, 0.55, 0.15, 0.9));
|
||||||
@@ -1098,7 +1125,7 @@ impl R3d {
|
|||||||
let sw = SPRITE_TEX_W;
|
let sw = SPRITE_TEX_W;
|
||||||
let sh = SPRITE_TEX_H;
|
let sh = SPRITE_TEX_H;
|
||||||
for e in &g.ents {
|
for e in &g.ents {
|
||||||
if !e.alive || e.held {
|
if !e.alive || e.held || unit_hidden(g, e) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let w = sprite_size(e.kind);
|
let w = sprite_size(e.kind);
|
||||||
@@ -1253,7 +1280,7 @@ impl R3d {
|
|||||||
let cw = GW * small; // width of one small block character
|
let cw = GW * small; // width of one small block character
|
||||||
let px_per_tile = screen_height() / cam.zoom * d;
|
let px_per_tile = screen_height() / cam.zoom * d;
|
||||||
for e in &g.ents {
|
for e in &g.ents {
|
||||||
if !e.alive || e.held {
|
if !e.alive || e.held || unit_hidden(g, e) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let big = matches!(e.kind, EK::Ogre | EK::Paladin | EK::Jailer | EK::Chainbreaker | EK::Wyrm | EK::Gargoyle);
|
let big = matches!(e.kind, EK::Ogre | EK::Paladin | EK::Jailer | EK::Chainbreaker | EK::Wyrm | EK::Gargoyle);
|
||||||
@@ -1512,7 +1539,7 @@ fn draw_dark_tower(tw: &Tower) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_towers_solid(g: &Game) {
|
fn draw_towers_solid(g: &Game) {
|
||||||
for tw in &g.towers {
|
for tw in g.towers.iter().filter(|t| g.map.shown(t.tile)) {
|
||||||
if !tw.hero && tw.kind != TowerKind::Rune {
|
if !tw.hero && tw.kind != TowerKind::Rune {
|
||||||
draw_dark_tower(tw);
|
draw_dark_tower(tw);
|
||||||
continue;
|
continue;
|
||||||
@@ -1634,7 +1661,7 @@ fn draw_towers_solid(g: &Game) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_towers_glow(g: &Game, t: f32) {
|
fn draw_towers_glow(g: &Game, t: f32) {
|
||||||
for (k, tw) in g.towers.iter().enumerate() {
|
for (k, tw) in g.towers.iter().enumerate().filter(|(_, t)| g.map.shown(t.tile)) {
|
||||||
if !tw.hero && tw.kind != TowerKind::Rune {
|
if !tw.hero && tw.kind != TowerKind::Rune {
|
||||||
// red glowing joints, arrow slits and the hellmouth's fire
|
// red glowing joints, arrow slits and the hellmouth's fire
|
||||||
let (x, z) = tw.pos();
|
let (x, z) = tw.pos();
|
||||||
@@ -1816,7 +1843,7 @@ fn draw_landmarks_solid(g: &Game, t: f32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// rival masters' cores: obsidian pedestal (the orb glows in the glow pass)
|
// rival masters' cores: obsidian pedestal (the orb glows in the glow pass)
|
||||||
for rv in g.rivals.iter().filter(|r| r.alive) {
|
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 (rx, rz) = rv.centre();
|
||||||
let c = vec3(rx, 0.0, rz);
|
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.08, 0.0), vec3(2.6, 0.16, 2.6), None, Color::from_rgba(24, 22, 30, 255));
|
||||||
@@ -1828,6 +1855,9 @@ fn draw_landmarks_solid(g: &Game, t: f32) {
|
|||||||
// portals: a demon gate - an arch of stone blocks with bone ribs and horns on a stepped
|
// 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
|
// platform (the vortex glows in the glow pass); grey and dead until claimed
|
||||||
for (pk, &(px, py)) in g.map.portals.iter().enumerate() {
|
for (pk, &(px, py)) in g.map.portals.iter().enumerate() {
|
||||||
|
if !g.map.shown(idx(px + 1, py + 1)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let owned = g.map.portal_owned(pk);
|
let owned = g.map.portal_owned(pk);
|
||||||
let pc = vec3(px as f32 + 1.5, 0.0, py as f32 + 1.5);
|
let pc = vec3(px as f32 + 1.5, 0.0, py as f32 + 1.5);
|
||||||
let stone = |k: u8| if owned { Color::from_rgba(46 + k, 30 + k / 2, 58 + k, 255) } else { Color::from_rgba(56 + k, 54 + k, 60 + k, 255) };
|
let stone = |k: u8| if owned { Color::from_rgba(46 + k, 30 + k / 2, 58 + k, 255) } else { Color::from_rgba(56 + k, 54 + k, 60 + k, 255) };
|
||||||
@@ -1867,7 +1897,9 @@ fn draw_landmarks_solid(g: &Game, t: f32) {
|
|||||||
}
|
}
|
||||||
// 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 && !g.map.shown(idx(gx as i32, gz as i32)) {
|
||||||
|
// unexplored: not drawn
|
||||||
|
} else if g.gate_hp > 0.0 {
|
||||||
let stone = Color::from_rgba(150, 155, 172, 255);
|
let stone = Color::from_rgba(150, 155, 172, 255);
|
||||||
let dark = Color::from_rgba(112, 116, 132, 255);
|
let dark = Color::from_rgba(112, 116, 132, 255);
|
||||||
let gold = Color::from_rgba(230, 200, 100, 255);
|
let gold = Color::from_rgba(230, 200, 100, 255);
|
||||||
@@ -1968,7 +2000,7 @@ fn draw_landmarks_glow(g: &Game, t: f32, cam_r: Vec3) {
|
|||||||
}
|
}
|
||||||
let _ = hot;
|
let _ = hot;
|
||||||
// rival masters' cores in their colours
|
// rival masters' cores in their colours
|
||||||
for rv in g.rivals.iter().filter(|r| r.alive) {
|
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 (rx, rz) = rv.centre();
|
||||||
let hp = (rv.hp / crate::rival::RIVAL_CORE_HP).clamp(0.0, 1.0);
|
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 col = if rv.side == 2 { vec3(0.3, 0.95, 0.4) } else { vec3(0.35, 0.55, 1.0) };
|
||||||
@@ -1983,6 +2015,9 @@ fn draw_landmarks_glow(g: &Game, t: f32, cam_r: Vec3) {
|
|||||||
}
|
}
|
||||||
// portals: runes on the arch and a vertical vortex inside it, sparks pouring out and falling
|
// 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() {
|
for (pk, &(px, py)) in g.map.portals.iter().enumerate() {
|
||||||
|
if !g.map.shown(idx(px + 1, py + 1)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let owned = g.map.portal_owned(pk);
|
let owned = g.map.portal_owned(pk);
|
||||||
let pc = vec3(px as f32 + 1.5, 0.0, py as f32 + 1.5);
|
let pc = vec3(px as f32 + 1.5, 0.0, py as f32 + 1.5);
|
||||||
let arm = if owned { Color::new(0.8, 0.35, 1.0, 1.0) } else { Color::new(0.45, 0.42, 0.5, 1.0) };
|
let arm = if owned { Color::new(0.8, 0.35, 1.0, 1.0) } else { Color::new(0.45, 0.42, 0.5, 1.0) };
|
||||||
@@ -2025,7 +2060,7 @@ fn draw_landmarks_glow(g: &Game, t: f32, cam_r: Vec3) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// gate light
|
// gate light
|
||||||
if g.gate_hp > 0.0 {
|
if g.gate_hp > 0.0 && g.map.shown(idx(g.gate_center().0 as i32, g.gate_center().1 as i32)) {
|
||||||
let (gx, gz) = g.gate_center();
|
let (gx, gz) = g.gate_center();
|
||||||
let gc = vec3(gx, 1.0, gz);
|
let gc = vec3(gx, 1.0, gz);
|
||||||
draw_sphere(gc, 0.45 + 0.04 * (t * 3.0).sin(), None, Color::new(0.95, 0.97, 1.0, 0.95));
|
draw_sphere(gc, 0.45 + 0.04 * (t * 3.0).sin(), None, Color::new(0.95, 0.97, 1.0, 0.95));
|
||||||
@@ -2048,7 +2083,7 @@ fn floor_y(g: &Game, x: f32, z: f32) -> f32 {
|
|||||||
|
|
||||||
fn draw_shadows(g: &Game) {
|
fn draw_shadows(g: &Game) {
|
||||||
for e in &g.ents {
|
for e in &g.ents {
|
||||||
if !e.alive || e.held {
|
if !e.alive || e.held || unit_hidden(g, e) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let r = if matches!(e.kind, EK::Ogre | EK::Paladin | EK::Jailer | EK::Chainbreaker) { 0.32 } else { 0.24 };
|
let r = if matches!(e.kind, EK::Ogre | EK::Paladin | EK::Jailer | EK::Chainbreaker) { 0.32 } else { 0.24 };
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ impl Game {
|
|||||||
g.msg("msg.welcome2", vec![]);
|
g.msg("msg.welcome2", vec![]);
|
||||||
g.msg(if rivals == 0 { "msg.welcome_humans" } else if humans { "msg.welcome_both" } else { "msg.welcome_rivals" }, vec![rivals.to_string()]);
|
g.msg(if rivals == 0 { "msg.welcome_humans" } else if humans { "msg.welcome_both" } else { "msg.welcome_rivals" }, vec![rivals.to_string()]);
|
||||||
g.init_research(None);
|
g.init_research(None);
|
||||||
|
g.init_fog();
|
||||||
g.place_dwarf_caves(seed);
|
g.place_dwarf_caves(seed);
|
||||||
g
|
g
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -704,7 +704,9 @@ impl Ui {
|
|||||||
for (i, t) in g.map.t.iter().enumerate() {
|
for (i, t) in g.map.t.iter().enumerate() {
|
||||||
let (x, y) = xy(i);
|
let (x, y) = xy(i);
|
||||||
let mut c = minimap_color(t);
|
let mut c = minimap_color(t);
|
||||||
if t.marked {
|
if !g.map.shown(i) {
|
||||||
|
c = [0, 0, 0, 255];
|
||||||
|
} else if t.marked {
|
||||||
c = [255, 230, 90, 255];
|
c = [255, 230, 90, 255];
|
||||||
}
|
}
|
||||||
self.minimap_img.set_pixel(x as u32, y as u32, Color::from_rgba(c[0], c[1], c[2], c[3]));
|
self.minimap_img.set_pixel(x as u32, y as u32, Color::from_rgba(c[0], c[1], c[2], c[3]));
|
||||||
@@ -717,7 +719,7 @@ impl Ui {
|
|||||||
draw_texture_ex(&self.minimap_tex, mrx, mry, WHITE, DrawTextureParams { dest_size: Some(vec2(map_w, map_h)), ..Default::default() });
|
draw_texture_ex(&self.minimap_tex, mrx, mry, WHITE, DrawTextureParams { dest_size: Some(vec2(map_w, map_h)), ..Default::default() });
|
||||||
let dot = (2.0 * px).max(ms);
|
let dot = (2.0 * px).max(ms);
|
||||||
for e in &g.ents {
|
for e in &g.ents {
|
||||||
if e.alive && !e.held {
|
if e.alive && !e.held && !crate::render3d::unit_hidden(g, e) {
|
||||||
let c = if e.hero {
|
let c = if e.hero {
|
||||||
Color::new(0.6, 0.85, 1.0, 1.0)
|
Color::new(0.6, 0.85, 1.0, 1.0)
|
||||||
} else if e.dwarf() {
|
} else if e.dwarf() {
|
||||||
@@ -1146,6 +1148,10 @@ pub fn hover_info(g: &Game, ent: Option<usize>, tile: Option<(i32, i32)>) -> Vec
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
let Some((x, y)) = tile else { return out };
|
let Some((x, y)) = tile else { return out };
|
||||||
|
if !g.map.shown(idx(x, y)) {
|
||||||
|
out.push((tr("tile.unexplored"), DIM));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
if let Some(tr) = g.trap_at(idx(x, y)) {
|
if let Some(tr) = g.trap_at(idx(x, y)) {
|
||||||
use crate::traps::*;
|
use crate::traps::*;
|
||||||
out.push((tr_name(tr.kind), FRAME_HI));
|
out.push((tr_name(tr.kind), FRAME_HI));
|
||||||
|
|||||||
Reference in new issue
Block a user