Mushroom groves: unclaimable moss where giant mushrooms grow slowly and glow when ready to fell
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
368a338ccf
commit
f8eda88bb3
26 files changed
+321
-43
No files matched your search
+9
-1
@@ -35,7 +35,7 @@ enum Cat {
|
||||
|
||||
const CATS: [Cat; 9] = [Cat::Terrain, Cat::Rooms, Cat::Places, Cat::Units, Cat::Towers, Cat::Traps, Cat::Forts, Cat::Zones, Cat::Erase];
|
||||
|
||||
const TERRAIN: [&str; 16] = ["floor", "earth", "reinforced", "rock", "water", "lava", "bridge", "lavabridge", "gold", "silver", "ruby", "diamond", "steel", "mushroom", "grass", "tree"];
|
||||
const TERRAIN: [&str; 17] = ["floor", "earth", "reinforced", "rock", "water", "lava", "bridge", "lavabridge", "gold", "silver", "ruby", "diamond", "steel", "mushroom", "grove", "grass", "tree"];
|
||||
const PLACES: [&str; 7] = ["core", "portal", "gate", "spring", "shard", "chest", "chained"];
|
||||
const OWNERS: [Owner; 6] = [Owner::Player, Owner::Hero, Owner::Rival1, Owner::Rival2, Owner::Rival3, Owner::None];
|
||||
const ZONE_SHAPES: [&str; 4] = ["rect", "circle", "tiles", "line"];
|
||||
@@ -76,6 +76,7 @@ fn kind_of_terrain(t: &str) -> (Kind, bool) {
|
||||
"bridge" => (Kind::Bridge, false),
|
||||
"lavabridge" => (Kind::LavaBridge, false),
|
||||
"mushroom" => (Kind::Mushroom, false),
|
||||
"grove" => (Kind::Grove, false),
|
||||
"grass" => (Kind::Floor, false),
|
||||
"tree" => (Kind::Tree, false),
|
||||
"gold" => (Kind::Gold, false),
|
||||
@@ -218,6 +219,13 @@ impl MapEdit {
|
||||
self.game.torches.retain(|tr| tr.wall != i);
|
||||
let owner = if k == Kind::Floor || reinforced || matches!(k, Kind::Bridge | Kind::LavaBridge) { OWNERS[self.owner] } else { Owner::None };
|
||||
let tl = &mut self.game.map.t[i];
|
||||
// a mushroom painted into a grove belongs to it (it leaves moss when felled); fully grown
|
||||
if k == Kind::Mushroom {
|
||||
tl.res = if tl.kind == Kind::Grove || tl.res & crate::map::MUSH_GROVE != 0 { crate::map::MUSH_GROVE } else { 0 };
|
||||
tl.food = 0.0;
|
||||
} else if tl.kind == Kind::Mushroom {
|
||||
tl.res = 0;
|
||||
}
|
||||
tl.kind = k;
|
||||
tl.reinforced = reinforced;
|
||||
tl.owner = if reinforced && owner == Owner::None { Owner::Player } else { owner };
|
||||
|
||||
+6
-1
@@ -2609,7 +2609,12 @@ impl Game {
|
||||
pub fn dig_out(&mut self, i: usize) {
|
||||
self.torches.retain(|t| t.wall != i);
|
||||
let t = &mut self.map.t[i];
|
||||
t.kind = Kind::Floor;
|
||||
// a grove's mushroom leaves its moss behind
|
||||
let grove = t.kind == Kind::Mushroom && t.res & MUSH_GROVE != 0;
|
||||
t.kind = if grove { Kind::Grove } else { Kind::Floor };
|
||||
if grove {
|
||||
t.res = 0;
|
||||
}
|
||||
t.owner = Owner::None;
|
||||
t.marked = false;
|
||||
t.reinforced = false;
|
||||
|
||||
+18
@@ -93,6 +93,7 @@ pub const I_RAM: u32 = 209; // battering ram tool
|
||||
pub const I_CATAPULT: u32 = 210; // catapult tool
|
||||
pub const T_SNOW: u32 = 211; // 4 variants: snow on the land above ground (winter)
|
||||
pub const T_PUMPKIN: u32 = 215; // a carved pumpkin (Halloween)
|
||||
pub const T_MOSS: u32 = 216; // 4 variants: mushroom grove ground, moss with glowing spores
|
||||
|
||||
pub struct Gfx {
|
||||
pub atlas: Texture2D,
|
||||
@@ -943,6 +944,22 @@ fn gen_atlas() -> Image {
|
||||
Some(if r % 11 == 0 { c(176, 200, 96) } else if r % 6 == 0 { c(112, 150, 62) } else if r % 23 == 0 { c(236, 220, 110) } else if r % 37 == 0 { c(230, 236, 240) } else { c(140, 176, 78) })
|
||||
});
|
||||
}
|
||||
// mushroom grove: dark stones overgrown with teal moss, a few glowing spores
|
||||
for v in 0..4u32 {
|
||||
cv.cell(T_MOSS + v, |x, y| {
|
||||
let r = hash(x, y, 520 + v);
|
||||
let moss = (hash(x / 3, y / 3, 530 + v) % 5) < 3;
|
||||
Some(if r % 41 == 0 {
|
||||
c(150, 255, 220) // a spore glows
|
||||
} else if r % 29 == 0 {
|
||||
c(200, 150, 255)
|
||||
} else if moss {
|
||||
if r % 4 == 0 { c(46, 112, 96) } else if r % 3 == 0 { c(30, 84, 74) } else { c(38, 98, 84) }
|
||||
} else {
|
||||
stones(x, y, v, (52, 58, 56), (26, 30, 30), 8)
|
||||
})
|
||||
});
|
||||
}
|
||||
// obsidian: cooled lava, dark glass with a few glowing seams
|
||||
for v in 0..4u32 {
|
||||
cv.cell(T_OBSIDIAN + v, |x, y| {
|
||||
@@ -2159,6 +2176,7 @@ impl Gfx {
|
||||
Kind::Obsidian => T_OBSIDIAN + v,
|
||||
Kind::Mushroom => T_FLOOR + v,
|
||||
Kind::Tree => T_GRASS + v,
|
||||
Kind::Grove => T_MOSS + v,
|
||||
Kind::LavaBridge => T_STONEBRIDGE + (v & 1),
|
||||
Kind::Shard => T_HEROFLOOR + v,
|
||||
Kind::Aerie => T_AERIE + v,
|
||||
|
||||
+17
-3
@@ -70,10 +70,14 @@ pub enum Kind {
|
||||
Altar, // room: sacrifices
|
||||
Drawbridge, // bridge that can be raised (res: see DB_RAISED, DB_AUTO, DB_LAVA)
|
||||
Obsidian, // lava cooled by frost: walkable until it melts again
|
||||
Mushroom, // giant mushroom on open ground: imps fell it for wood (grows back)
|
||||
Mushroom, // giant mushroom: imps fell it for wood once it is grown (food: growth left, 0 = grown; res: MUSH_GROVE)
|
||||
Tree, // tree above ground: imps fell it for wood
|
||||
Grove, // mushroom grove: mossy ground nobody can claim, giant mushrooms grow (back) here
|
||||
}
|
||||
|
||||
// a giant mushroom of a grove: when felled, the ground turns back into grove (Tile::res)
|
||||
pub const MUSH_GROVE: u8 = 1;
|
||||
|
||||
// drawbridge state in Tile::res
|
||||
pub const DB_RAISED: u8 = 1; // raised: nobody can cross
|
||||
pub const DB_AUTO: u8 = 2; // rises by itself when enemies come near
|
||||
@@ -105,7 +109,7 @@ pub struct River {
|
||||
}
|
||||
|
||||
// order matters for the packed savegame codec: only append new kinds
|
||||
const KINDS: [Kind; 38] = [
|
||||
const KINDS: [Kind; 39] = [
|
||||
Kind::Rock,
|
||||
Kind::Earth,
|
||||
Kind::Gold,
|
||||
@@ -144,6 +148,7 @@ const KINDS: [Kind; 38] = [
|
||||
Kind::Obsidian,
|
||||
Kind::Mushroom,
|
||||
Kind::Tree,
|
||||
Kind::Grove,
|
||||
];
|
||||
|
||||
// treasures (veins, treasury content, imp loads): 0 gold, 1 silver, 2 ruby, 3 diamond
|
||||
@@ -229,7 +234,15 @@ impl Tile {
|
||||
matches!(self.kind, Kind::Rock | Kind::Earth | Kind::Gold | Kind::Silver | Kind::Ruby | Kind::Diamond | Kind::Steel)
|
||||
}
|
||||
pub fn diggable(&self) -> bool {
|
||||
matches!(self.kind, Kind::Earth | Kind::Gold | Kind::Silver | Kind::Ruby | Kind::Diamond | Kind::Steel | Kind::Mushroom | Kind::Tree)
|
||||
matches!(self.kind, Kind::Earth | Kind::Gold | Kind::Silver | Kind::Ruby | Kind::Diamond | Kind::Steel | Kind::Tree) || (self.kind == Kind::Mushroom && self.grown())
|
||||
}
|
||||
// a giant mushroom is felled only when it is fully grown (everything else counts as grown)
|
||||
pub fn grown(&self) -> bool {
|
||||
self.kind != Kind::Mushroom || self.food <= 0.0
|
||||
}
|
||||
// how far a giant mushroom has grown: 0 a sprout .. 1 fully grown
|
||||
pub fn growth(&self) -> f32 {
|
||||
if self.kind == Kind::Mushroom { (1.0 - self.food).clamp(0.0, 1.0) } else { 1.0 }
|
||||
}
|
||||
// a giant mushroom or a tree: felled like a wall is dug, gives wood
|
||||
pub fn plant(&self) -> bool {
|
||||
@@ -272,6 +285,7 @@ impl Tile {
|
||||
| Kind::Arena
|
||||
| Kind::Altar
|
||||
| Kind::Obsidian
|
||||
| Kind::Grove
|
||||
) || (self.kind == Kind::Drawbridge && self.res & DB_RAISED == 0)
|
||||
}
|
||||
// river tiles (open, but nobody can walk there)
|
||||
|
||||
+27
-3
@@ -526,6 +526,10 @@ fn ambient_field(map: &Map) -> Vec<f32> {
|
||||
if t.river() == Some(Kind::Lava) {
|
||||
f[i] = f[i].max(0.8); // lava lights up its surroundings
|
||||
}
|
||||
// the spores of a mushroom grove glow faintly
|
||||
if t.kind == Kind::Grove || (t.kind == Kind::Mushroom && t.res & MUSH_GROVE != 0) {
|
||||
f[i] = f[i].max(0.45);
|
||||
}
|
||||
if t.kind == Kind::Core {
|
||||
f[i] = 1.0;
|
||||
}
|
||||
@@ -765,6 +769,10 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T
|
||||
if map.sky(idx(x, z)) && t.kind == Kind::Floor && t.owner == Owner::None {
|
||||
tex = if season() == 2 { T_SNOW } else { T_GRASS } + (t.var as u32 & 3);
|
||||
}
|
||||
// a grove's mushroom stands on moss
|
||||
if t.kind == Kind::Mushroom && t.res & MUSH_GROVE != 0 {
|
||||
tex = crate::gfx::T_MOSS + (t.var as u32 & 3);
|
||||
}
|
||||
let (ua, ub) = cell_uv(tex);
|
||||
let (xf, zf) = (x as f32, z as f32);
|
||||
if !map.shown(idx(x, z)) {
|
||||
@@ -976,11 +984,16 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T
|
||||
let tint = |v: Vec3| col(lava_lit(if t.marked { v * vec3(1.3, 1.15, 0.5) } else { v }, lg), 1.0);
|
||||
let s = 0.85 + 0.1 * (t.var & 3) as f32;
|
||||
if t.kind == Kind::Mushroom {
|
||||
// it grows from a sprout to full size in steps; grown, its cap glows teal
|
||||
let stage = mushroom_stage(&t);
|
||||
let s = s * (0.3 + 0.7 * stage as f32 / 4.0);
|
||||
let h = 0.75 * s;
|
||||
let sc = tint(Vec3::splat(a.max(0.35)));
|
||||
mb.boxy(vec3(cx - 0.12, 0.0, cz - 0.12), vec3(cx + 0.12, h, cz + 0.12), T_STEM, sc, sc);
|
||||
// the cap glows a little in the dark
|
||||
let cc = tint(Vec3::splat(a.max(0.7)));
|
||||
let w = 0.12 * s.max(0.6);
|
||||
mb.boxy(vec3(cx - w, 0.0, cz - w), vec3(cx + w, h, cz + w), T_STEM, sc, sc);
|
||||
// the cap glows a little in the dark; a grown one in its own colour
|
||||
let hue = if stage >= 4 { vec3(0.55, 1.35, 1.25) } else { vec3(0.95, 0.8, 0.62) };
|
||||
let cc = tint(Vec3::splat(a.max(0.7)) * hue);
|
||||
mb.boxy(vec3(cx - 0.42 * s, h, cz - 0.42 * s), vec3(cx + 0.42 * s, h + 0.26, cz + 0.42 * s), T_CAP, cc, cc);
|
||||
mb.boxy(vec3(cx - 0.26 * s, h + 0.26, cz - 0.26 * s), vec3(cx + 0.26 * s, h + 0.38, cz + 0.26 * s), T_CAP, cc, cc);
|
||||
} else {
|
||||
@@ -1202,6 +1215,7 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32) -> u64 {
|
||||
Kind::Farm => t.food as u64,
|
||||
Kind::Treasury => (t.gold / 50) as u64 | (t.res as u64) << 5,
|
||||
Kind::Drawbridge => t.res as u64,
|
||||
Kind::Mushroom => mushroom_stage(&t) as u64 | (t.res as u64) << 3,
|
||||
_ => 0,
|
||||
};
|
||||
let shown = map.shown(idx(x, z)) as u64 | (map.sky(idx(x, z)) as u64) << 1;
|
||||
@@ -2449,6 +2463,15 @@ fn draw_landmarks_solid(g: &Game, t: f32) {
|
||||
let _ = t;
|
||||
}
|
||||
|
||||
// growth stage of a giant mushroom: 0 a sprout .. 4 fully grown (felling only then)
|
||||
fn mushroom_stage(t: &Tile) -> u32 {
|
||||
if t.grown() {
|
||||
4
|
||||
} else {
|
||||
((t.growth() * 4.0) as u32).min(3)
|
||||
}
|
||||
}
|
||||
|
||||
// the colour of a master side (the first master red, rivals green, blue and yellow)
|
||||
pub fn master_rgb(side: u8) -> Vec3 {
|
||||
match side {
|
||||
@@ -3337,6 +3360,7 @@ pub fn minimap_color(tile: &Tile) -> [u8; 4] {
|
||||
Kind::Drawbridge => [120, 80, 50, 255],
|
||||
Kind::Obsidian => [60, 40, 70, 255],
|
||||
Kind::Mushroom => [150, 90, 200, 255],
|
||||
Kind::Grove => [50, 110, 92, 255],
|
||||
Kind::Tree => [40, 100, 40, 255],
|
||||
Kind::Shard => [255, 60, 120, 255],
|
||||
Kind::Aerie => [170, 140, 90, 255],
|
||||
|
||||
@@ -224,3 +224,22 @@ fn export_scenes() {
|
||||
run(&mut g, 1.0);
|
||||
save("room-levels", &mut g, cx as f32 + 1.5, cy as f32 - 2.0, 16.0, 0.0, false);
|
||||
}
|
||||
|
||||
// a mushroom grove: moss, grown (glowing) and young mushrooms
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn export_grove_scene() {
|
||||
std::fs::create_dir_all("/tmp/scenes").unwrap();
|
||||
let mut g = Game::new(9, 96, 96);
|
||||
// the grove tile with the most grove around it
|
||||
let best = (0..g.map.t.len())
|
||||
.filter(|&i| g.map.t[i].kind == Kind::Grove)
|
||||
.max_by_key(|&i| {
|
||||
let (x, y) = xy(i);
|
||||
(-3..=3).flat_map(|dy| (-3..=3).map(move |dx| (dx, dy))).filter(|&(dx, dy)| g.map.get(x + dx, y + dy).map_or(false, |t| matches!(t.kind, Kind::Grove | Kind::Mushroom))).count()
|
||||
})
|
||||
.unwrap();
|
||||
let (x, y) = xy(best);
|
||||
run(&mut g, 1.0);
|
||||
save("mushroom-grove", &mut g, x as f32 + 0.5, y as f32 + 0.5, 12.0, 0.0, false);
|
||||
}
|
||||
@@ -1804,8 +1804,20 @@ pub fn hover_info(g: &Game, ent: Option<usize>, tile: Option<(i32, i32)>) -> Vec
|
||||
Kind::Mushroom | Kind::Tree => {
|
||||
let tree = t.kind == Kind::Tree;
|
||||
out.push((tr(if tree { "tile.tree" } else { "tile.mushroom" }), WOOD_C));
|
||||
let wood = if tree { crate::wood::WOOD_TREE } else { crate::wood::WOOD_MUSHROOM };
|
||||
out.push((trf("tile.plant_info", &[(wood as i32).to_string()]), DIM));
|
||||
if t.grown() {
|
||||
if !tree {
|
||||
out.push((tr("tile.mushroom_grown"), GREEN));
|
||||
}
|
||||
let wood = if tree { crate::wood::WOOD_TREE } else { crate::wood::WOOD_MUSHROOM };
|
||||
out.push((trf("tile.plant_info", &[(wood as i32).to_string()]), DIM));
|
||||
} else {
|
||||
// a young mushroom: not yet to be felled
|
||||
out.push((trf("tile.mushroom_growing", &[((t.growth() * 100.0) as i32).to_string()]), DIM));
|
||||
}
|
||||
}
|
||||
Kind::Grove => {
|
||||
out.push((tr("tile.grove"), GREEN));
|
||||
out.push((tr("tile.grove_info"), DIM));
|
||||
}
|
||||
Kind::Obsidian => {
|
||||
out.push((tr("tile.obsidian"), FRAME_HI));
|
||||
|
||||
+121
-33
@@ -16,62 +16,106 @@ pub const WOOD_TREE: f32 = 3.0;
|
||||
pub const MUSHROOM_HP: f32 = 4.0;
|
||||
#[allow(dead_code)] // trees come with the surface world
|
||||
pub const TREE_HP: f32 = 6.0;
|
||||
// seconds until a felled giant mushroom grows back
|
||||
pub const REGROW: f32 = 240.0;
|
||||
// seconds until a felled giant mushroom sprouts again, and from sprout to fully grown
|
||||
pub const REGROW: f32 = 90.0;
|
||||
pub const GROW_TIME: f32 = 300.0;
|
||||
// wood a wooden bridge tile, a wooden drawbridge tile and a wooden door cost
|
||||
pub const BRIDGE_WOOD: f32 = 2.0;
|
||||
pub const DRAWBRIDGE_WOOD: f32 = 4.0;
|
||||
pub const DOOR_WOOD: f32 = 3.0;
|
||||
// giant mushrooms in a cavern of a random map: one per this many open tiles
|
||||
const MUSHROOM_SHARE: usize = 70;
|
||||
// mushroom groves of a random map: one per this many map tiles (at least 2, at most 6)
|
||||
const GROVE_SHARE: usize = 2200;
|
||||
const GROVE_RADIUS: i32 = 4;
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct WoodWork {
|
||||
pub regrow: Vec<(usize, f32)>, // felled mushroom tile, seconds until it grows back
|
||||
pub regrow: Vec<(usize, f32)>, // felled mushroom tile, seconds until it sprouts again
|
||||
#[serde(default)]
|
||||
pub growing: Vec<usize>, // giant mushrooms still growing (Tile::food: growth left)
|
||||
}
|
||||
|
||||
// a tile where a plant stands alone: all eight neighbours open ground, no other plant next to
|
||||
// it (so a plant never closes a corridor)
|
||||
// open ground of nobody (or a grove's moss)
|
||||
fn open_ground(t: &Tile) -> bool {
|
||||
(t.kind == Kind::Floor && t.owner == Owner::None) || t.kind == Kind::Grove
|
||||
}
|
||||
|
||||
// a tile where a plant may stand: all eight neighbours open, no other plant next to it (so a
|
||||
// plant never closes a corridor)
|
||||
fn lonely_spot(map: &Map, x: i32, y: i32) -> bool {
|
||||
if !inb(x, y) || map.t[idx(x, y)].kind != Kind::Floor || map.t[idx(x, y)].owner != Owner::None {
|
||||
if !inb(x, y) || !open_ground(&map.t[idx(x, y)]) {
|
||||
return false;
|
||||
}
|
||||
DIR8.iter().all(|&(dx, dy)| map.get(x + dx, y + dy).map_or(false, |n| n.walkable() && !n.bridge() && n.kind != Kind::Door))
|
||||
&& (-2..=2).all(|dy| (-2..=2).all(|dx| !map.get(x + dx, y + dy).map_or(false, |n| n.plant())))
|
||||
DIR8.iter().all(|&(dx, dy)| map.get(x + dx, y + dy).map_or(false, |n| n.walkable() && !n.bridge() && n.kind != Kind::Door && !n.plant()))
|
||||
}
|
||||
|
||||
impl Game {
|
||||
// giant mushrooms in the open caverns of a random map (deterministic, from the seed)
|
||||
// mushroom groves in the open caverns of a random map (deterministic, from the seed): mossy
|
||||
// ground nobody can claim, giant mushrooms in a loose grid that grow back when felled
|
||||
pub fn grow_mushrooms(&mut self, seed: u32) {
|
||||
let mut rng = Rng(seed.wrapping_mul(0x9e37_79b9) ^ 0x6d2b_79f5 | 1);
|
||||
let (cx, cy) = (self.map.core.0 + 1, self.map.core.1 + 1);
|
||||
let open: Vec<usize> = (0..self.map.t.len())
|
||||
let open = |g: &Game, x: i32, y: i32| inb(x, y) && !g.map.sky(idx(x, y)) && open_ground(&g.map.t[idx(x, y)]) && !g.map.t[idx(x, y)].bridge();
|
||||
// centres: open ground with open ground all around, away from the heart
|
||||
let r = GROVE_RADIUS;
|
||||
let centres: Vec<usize> = (0..self.map.t.len())
|
||||
.filter(|&i| {
|
||||
let (x, y) = xy(i);
|
||||
(x - cx).abs().max((y - cy).abs()) > 10 && lonely_spot(&self.map, x, y)
|
||||
(x - cx).abs().max((y - cy).abs()) > 12 && (-2..=2).all(|dy| (-2..=2).all(|dx| open(self, x + dx, y + dy)))
|
||||
})
|
||||
.collect();
|
||||
let want = (open.len() / MUSHROOM_SHARE).max(6);
|
||||
let mut placed = 0;
|
||||
let want = (self.map.t.len() / GROVE_SHARE).clamp(2, 6);
|
||||
let mut groves: Vec<(i32, i32)> = Vec::new();
|
||||
let mut tries = 0;
|
||||
while placed < want && tries < want * 20 && !open.is_empty() {
|
||||
while groves.len() < want && tries < 200 && !centres.is_empty() {
|
||||
tries += 1;
|
||||
let i = open[(rng.next() as usize) % open.len()];
|
||||
let (x, y) = xy(i);
|
||||
if self.ents.iter().any(|e| e.alive && e.tile() == i) || !lonely_spot(&self.map, x, y) {
|
||||
let (x, y) = xy(centres[(rng.next() as usize) % centres.len()]);
|
||||
if groves.iter().any(|&(gx, gy)| (gx - x).abs().max((gy - y).abs()) < 3 * r) {
|
||||
continue;
|
||||
}
|
||||
self.plant_mushroom(i);
|
||||
placed += 1;
|
||||
groves.push((x, y));
|
||||
}
|
||||
for &(gx, gy) in &groves {
|
||||
// the moss: the open ground of a rough disc
|
||||
for y in gy - r..=gy + r {
|
||||
for x in gx - r..=gx + r {
|
||||
let d2 = (x - gx).pow(2) + (y - gy).pow(2);
|
||||
if d2 <= r * r + (hash32(x, y) % 3) as i32 - 1 && open(self, x, y) {
|
||||
let t = &mut self.map.t[idx(x, y)];
|
||||
t.kind = Kind::Grove;
|
||||
t.owner = Owner::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
// the mushrooms: every other tile in both directions, some still growing
|
||||
for y in gy - r + 1..gy + r {
|
||||
for x in gx - r + 1..gx + r {
|
||||
if (x - gx) % 2 != 0 || (y - gy) % 2 != 0 {
|
||||
continue;
|
||||
}
|
||||
let i = idx(x, y);
|
||||
if self.map.t[i].kind != Kind::Grove || !lonely_spot(&self.map, x, y) || self.ents.iter().any(|e| e.alive && e.tile() == i) {
|
||||
continue;
|
||||
}
|
||||
let left = if rng.f() < 0.6 { 0.0 } else { rng.f() };
|
||||
self.plant_mushroom(i, left);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plant_mushroom(&mut self, i: usize) {
|
||||
// a giant mushroom on tile i; `left`: how much of its growth is still to come (0 = grown)
|
||||
pub fn plant_mushroom(&mut self, i: usize, left: f32) {
|
||||
let grove = self.map.t[i].kind == Kind::Grove || self.map.t[i].res & MUSH_GROVE != 0;
|
||||
let t = &mut self.map.t[i];
|
||||
t.kind = Kind::Mushroom;
|
||||
t.owner = Owner::None;
|
||||
t.hp = MUSHROOM_HP;
|
||||
t.marked = false;
|
||||
t.food = left.clamp(0.0, 1.0);
|
||||
t.res = if grove { MUSH_GROVE } else { 0 };
|
||||
if t.food > 0.0 && !self.woodwork.growing.contains(&i) {
|
||||
self.woodwork.growing.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
// an imp felled the plant at tile i: wood for its master, a mushroom grows back later
|
||||
@@ -96,8 +140,23 @@ impl Game {
|
||||
true
|
||||
}
|
||||
|
||||
// the shared step: felled mushrooms grow back where there is room
|
||||
// the shared step: young mushrooms grow, felled ones sprout again where there is room
|
||||
pub(crate) fn update_wood(&mut self, dt: f32) {
|
||||
let mut k = 0;
|
||||
while k < self.woodwork.growing.len() {
|
||||
let i = self.woodwork.growing[k];
|
||||
let t = &mut self.map.t[i];
|
||||
if t.kind != Kind::Mushroom {
|
||||
self.woodwork.growing.swap_remove(k);
|
||||
continue;
|
||||
}
|
||||
t.food = (t.food - dt / GROW_TIME).max(0.0);
|
||||
if t.food <= 0.0 {
|
||||
self.woodwork.growing.swap_remove(k);
|
||||
} else {
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
let mut k = 0;
|
||||
while k < self.woodwork.regrow.len() {
|
||||
self.woodwork.regrow[k].1 -= dt;
|
||||
@@ -108,9 +167,10 @@ impl Game {
|
||||
let i = self.woodwork.regrow[k].0;
|
||||
let (x, y) = xy(i);
|
||||
if lonely_spot(&self.map, x, y) && !self.ents.iter().any(|e| e.alive && e.tile() == i) {
|
||||
self.plant_mushroom(i);
|
||||
// a sprout: it takes GROW_TIME until it can be felled again
|
||||
self.plant_mushroom(i, 1.0);
|
||||
self.woodwork.regrow.swap_remove(k);
|
||||
} else if self.map.t[i].kind != Kind::Floor || self.map.t[i].owner != Owner::None {
|
||||
} else if !open_ground(&self.map.t[i]) {
|
||||
// built over or claimed: it does not come back
|
||||
self.woodwork.regrow.swap_remove(k);
|
||||
} else {
|
||||
@@ -121,37 +181,65 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
fn hash32(x: i32, y: i32) -> u32 {
|
||||
let h = (x as u32).wrapping_mul(2654435761) ^ (y as u32).wrapping_mul(2246822519);
|
||||
(h ^ (h >> 15)).wrapping_mul(2246822519) >> 7
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn random_maps_have_lonely_giant_mushrooms() {
|
||||
fn random_maps_have_mushroom_groves() {
|
||||
let g = Game::new(9, 96, 96);
|
||||
let groves = g.map.t.iter().filter(|t| t.kind == Kind::Grove).count();
|
||||
let n = g.map.t.iter().filter(|t| t.kind == Kind::Mushroom).count();
|
||||
assert!(groves >= 30, "{} grove tiles", groves);
|
||||
assert!(n >= 6, "{} mushrooms", n);
|
||||
assert!((g.wood - WOOD_START).abs() < 1e-3);
|
||||
// none closes a corridor: every neighbour is open ground
|
||||
// none closes a corridor: every neighbour is open ground; all stand in a grove
|
||||
for i in 0..g.map.t.len() {
|
||||
if g.map.t[i].kind == Kind::Mushroom {
|
||||
let (x, y) = xy(i);
|
||||
assert!(DIR8.iter().all(|&(dx, dy)| g.map.get(x + dx, y + dy).map_or(false, |n| n.walkable() || n.plant())));
|
||||
assert!(DIR8.iter().all(|&(dx, dy)| g.map.get(x + dx, y + dy).map_or(false, |n| n.walkable())));
|
||||
assert!(g.map.t[i].res & MUSH_GROVE != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn felling_gives_wood_and_mushrooms_grow_back() {
|
||||
fn grove_mushrooms_grow_slowly_are_felled_only_when_grown_and_come_back() {
|
||||
let mut g = Game::new(9, 96, 96);
|
||||
let i = g.map.t.iter().position(|t| t.kind == Kind::Mushroom).unwrap();
|
||||
let i = g.map.t.iter().position(|t| t.kind == Kind::Mushroom && t.grown()).unwrap();
|
||||
assert!(g.map.t[i].diggable());
|
||||
let w0 = g.wood;
|
||||
g.felled(i);
|
||||
g.dig_out(i);
|
||||
assert_eq!(g.wood, w0 + WOOD_MUSHROOM);
|
||||
assert_eq!(g.map.t[i].kind, Kind::Floor);
|
||||
g.woodwork.regrow[0].1 = 0.01;
|
||||
assert_eq!(g.map.t[i].kind, Kind::Grove, "the moss stays");
|
||||
// the grove can't be claimed or marked
|
||||
let (x, y) = xy(i);
|
||||
g.mark_rect(x, y, x, y, true);
|
||||
assert!(!g.map.t[i].marked);
|
||||
assert!(!g.claimable(i, 1));
|
||||
// it sprouts again: small, and not to be felled yet
|
||||
let idx_r = g.woodwork.regrow.iter().position(|r| r.0 == i).unwrap();
|
||||
g.woodwork.regrow[idx_r].1 = 0.01;
|
||||
g.update_wood(0.05);
|
||||
assert_eq!(g.map.t[i].kind, Kind::Mushroom, "grown back");
|
||||
assert_eq!(g.map.t[i].kind, Kind::Mushroom, "a sprout");
|
||||
assert!(!g.map.t[i].grown() && !g.map.t[i].diggable());
|
||||
g.mark_rect(x, y, x, y, true);
|
||||
assert!(!g.map.t[i].marked, "a young mushroom can't be marked");
|
||||
// halfway
|
||||
for _ in 0..(GROW_TIME as usize / 2) {
|
||||
g.update_wood(1.0);
|
||||
}
|
||||
assert!(!g.map.t[i].grown() && g.map.t[i].growth() > 0.4);
|
||||
for _ in 0..(GROW_TIME as usize / 2 + 2) {
|
||||
g.update_wood(1.0);
|
||||
}
|
||||
assert!(g.map.t[i].grown() && g.map.t[i].diggable(), "fully grown: ready to fell");
|
||||
assert!(g.spend_wood(3.0) && !g.spend_wood(1000.0));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user