Wild beasts: cave bears and giant beetles in dens, tamed when beaten while a prison has room
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
af21c1ae46
commit
655e625eb0
28 files changed
+289
-5
No files matched your search
+113
@@ -0,0 +1,113 @@
|
||||
// Wild beasts: cave bears and giant beetles live in the open caverns of random maps. They defend
|
||||
// their den like the dwarves defend their cave (they never stray far) and count for nobody. A
|
||||
// master with room in a prison tames a beast he beats: instead of dying it gives in and joins.
|
||||
|
||||
use crate::game::*;
|
||||
use crate::map::*;
|
||||
|
||||
pub fn beast(k: EK) -> bool {
|
||||
matches!(k, EK::CaveBear | EK::Beetle)
|
||||
}
|
||||
|
||||
impl Game {
|
||||
// a few dens in the caverns of a random map (deterministic, from the seed)
|
||||
pub fn place_beasts(&mut self, seed: u32) {
|
||||
let mut rng = Rng(seed.wrapping_mul(0x85eb_ca6b) ^ 0x27d4_eb2f | 1);
|
||||
let (cx, cy) = (self.map.core.0 + 1, self.map.core.1 + 1);
|
||||
let area = (mw() * mh()) as f32;
|
||||
// none on small maps (no room), one per ~95x95 tiles on bigger ones
|
||||
let dens = (area / 9000.0) as usize;
|
||||
let open: Vec<usize> = (0..self.map.t.len())
|
||||
.filter(|&i| {
|
||||
let (x, y) = xy(i);
|
||||
let t = &self.map.t[i];
|
||||
t.kind == Kind::Floor && t.owner == Owner::None && !self.map.sky(i) && (x - cx).abs().max((y - cy).abs()) > 18
|
||||
&& DIR8.iter().all(|&(dx, dy)| self.map.walk(x + dx, y + dy))
|
||||
&& !self.dwarf_caves.iter().any(|c| (c.at.0 - x).abs() + (c.at.1 - y).abs() < 12)
|
||||
})
|
||||
.collect();
|
||||
if open.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut placed: Vec<(i32, i32)> = Vec::new();
|
||||
for _ in 0..dens * 6 {
|
||||
if placed.len() >= dens {
|
||||
break;
|
||||
}
|
||||
let i = open[(rng.next() as usize) % open.len()];
|
||||
let (x, y) = xy(i);
|
||||
if placed.iter().any(|&(px, py)| (px - x).abs() + (py - y).abs() < 20) {
|
||||
continue;
|
||||
}
|
||||
placed.push((x, y));
|
||||
let kind = if rng.f() < 0.5 { EK::CaveBear } else { EK::Beetle };
|
||||
let n = 1 + (rng.next() % 2) as usize;
|
||||
for k in 0..n {
|
||||
let level = 2 + rng.next() % 3;
|
||||
let id = self.spawn(kind, false, x as f32 + 0.5 + k as f32 * 0.6, y as f32 + 0.5, level);
|
||||
if let Some(e) = self.ents.iter_mut().find(|e| e.id == id) {
|
||||
e.side = crate::dwarves::DWARF_SIDE;
|
||||
e.home = Some((x as f32 + 0.5, y as f32 + 0.5));
|
||||
e.settled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a beaten wild beast: with room in a prison it is tamed instead of killed (true)
|
||||
pub(crate) fn try_tame(&mut self, j: usize) -> bool {
|
||||
let e = &self.ents[j];
|
||||
if !beast(e.kind) || e.side != crate::dwarves::DWARF_SIDE || self.prison_space() == 0 {
|
||||
return false;
|
||||
}
|
||||
let side = self.me_side;
|
||||
let e = &mut self.ents[j];
|
||||
e.side = side;
|
||||
e.home = None;
|
||||
e.guard = false;
|
||||
e.hp = e.max_hp * 0.4;
|
||||
e.happy = 0.6;
|
||||
e.hunger = 0.5;
|
||||
e.energy = 0.5;
|
||||
e.job = Job::None;
|
||||
e.path.clear();
|
||||
e.bed = None;
|
||||
e.settled = false;
|
||||
let (x, y, kind) = (e.x, e.y, e.kind);
|
||||
self.fx(7, x, y, 0.0, 0.0, 1.0, 0xff40c0ff);
|
||||
self.sfx(Sfx::Convert, x, y);
|
||||
self.msg("msg.beast_tamed", vec![format!("@{}", st(kind).name)]);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn beasts_live_in_caverns_and_can_be_tamed() {
|
||||
let g0 = Game::new(9, 128, 96);
|
||||
let beasts: Vec<&Ent> = g0.ents.iter().filter(|e| e.alive && beast(e.kind)).collect();
|
||||
assert!(!beasts.is_empty(), "a den on a bigger map");
|
||||
assert!(beasts.iter().all(|e| e.dwarf() && e.home.is_some()), "neutral, at home");
|
||||
let mut g = g0;
|
||||
let j = g.ents.iter().position(|e| e.alive && beast(e.kind)).unwrap();
|
||||
// without a prison the beast dies
|
||||
assert!(!g.try_tame(j));
|
||||
// a prison: the beaten beast joins
|
||||
let (cx, cy) = g.map.core;
|
||||
g.build_rect(9, cx - 6, cy - 6, cx - 3, cy - 4);
|
||||
g.gold = 99999.0;
|
||||
let t = &mut g.map.t[idx(cx - 5, cy - 5)];
|
||||
if t.kind != Kind::Prison {
|
||||
t.kind = Kind::Prison;
|
||||
t.owner = Owner::Player;
|
||||
}
|
||||
g.recount();
|
||||
assert!(g.prison_space() > 0);
|
||||
g.ents[j].hp = 0.0;
|
||||
g.kill(j);
|
||||
assert!(g.ents[j].alive && g.ents[j].side == 0 && !g.ents[j].dwarf(), "tamed");
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -10,9 +10,9 @@ use serde::{Deserialize, Serialize};
|
||||
pub const SIDES: [i32; 6] = [-1, 0, 1, 2, 3, 4];
|
||||
|
||||
// creature and hero kinds a block may name ("" = any)
|
||||
pub const KINDS: [&str; 24] = [
|
||||
pub const KINDS: [&str; 26] = [
|
||||
"Worker", "Kobold", "Spider", "Mage", "Ogre", "Jailer", "Priestess", "Chainbreaker", "Bat", "Gargoyle", "Owl", "Wyrm", "Cogling", "Revenant", "Knight", "Archer", "Priest", "Dwarf", "Paladin", "Giant", "Fairy",
|
||||
"BattleMage", "Ram", "Catapult",
|
||||
"BattleMage", "Ram", "Catapult", "CaveBear", "Beetle",
|
||||
];
|
||||
|
||||
// ground a Terrain block can make
|
||||
|
||||
@@ -127,6 +127,9 @@ pub enum EK {
|
||||
// siege engines (heroes bring them; a master can build his own, see siege.rs)
|
||||
Ram, // smashes doors, walls and towers, ignores creatures
|
||||
Catapult, // hurls stones at towers from afar
|
||||
// wild beasts of the caverns (beasts.rs): neutral until tamed
|
||||
CaveBear, // strong and shaggy, likes the mushroom garden
|
||||
Beetle, // a giant armoured beetle, likes the lair
|
||||
}
|
||||
|
||||
pub struct St {
|
||||
@@ -171,6 +174,8 @@ pub fn st(k: EK) -> St {
|
||||
EK::BattleMage => s("unit.battlemage", 120.0, 18.0, 5.0, 1.8, 1.8, 120, 250),
|
||||
EK::Ram => s("unit.ram", 900.0, 45.0, 1.1, 1.0, 1.8, 0, 200),
|
||||
EK::Catapult => s("unit.catapult", 320.0, 40.0, 7.5, 0.9, 3.5, 0, 200),
|
||||
EK::CaveBear => s("unit.cavebear", 420.0, 24.0, 1.0, 1.9, 1.4, 110, 0),
|
||||
EK::Beetle => s("unit.beetle", 300.0, 13.0, 1.0, 1.7, 1.1, 70, 0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2782,6 +2787,9 @@ impl Game {
|
||||
|
||||
pub(crate) fn kill(&mut self, j: usize) {
|
||||
self.boss_loot(j);
|
||||
if self.try_tame(j) {
|
||||
return;
|
||||
}
|
||||
let e = &self.ents[j];
|
||||
if e.hero && e.captive == 0 && self.prison_space() > 0 {
|
||||
// knocked out instead of killed: imps carry the hero to the torture chamber
|
||||
|
||||
+29
-1
@@ -1553,6 +1553,32 @@ fn sprite_rows(k: EK) -> [&'static str; 10] {
|
||||
"...y..y...",
|
||||
"..........",
|
||||
],
|
||||
// cave bear: shaggy brown fur, small yellow eyes
|
||||
EK::CaveBear => [
|
||||
".kk....kk.",
|
||||
"kBBk..kBBk",
|
||||
"kBBBBBBBBk",
|
||||
"kBeBBBBeBk",
|
||||
"kBBBkkBBBk",
|
||||
".kBBffBBk.",
|
||||
"kBBBBBBBBk",
|
||||
"kBbBBBBbBk",
|
||||
".kBBkkBBk.",
|
||||
".kkk..kkk.",
|
||||
],
|
||||
// giant beetle: a shiny dark-green shell, mandibles, six legs
|
||||
EK::Beetle => [
|
||||
"..k....k..",
|
||||
"...k..k...",
|
||||
"..kGGGGk..",
|
||||
".kGeGGeGk.",
|
||||
"kGGgGGgGGk",
|
||||
"kGgGGGGgGk",
|
||||
"kGGgGGgGGk",
|
||||
".kGGGGGGk.",
|
||||
"k.kGGGGk.k",
|
||||
".k.k..k.k.",
|
||||
],
|
||||
// battering ram: a roofed frame on wheels, an iron-capped trunk
|
||||
EK::Ram => [
|
||||
"..........",
|
||||
@@ -1661,7 +1687,7 @@ fn pal(ch: char) -> Option<Color> {
|
||||
})
|
||||
}
|
||||
|
||||
pub const SPRITE_KINDS: [EK; 24] = [
|
||||
pub const SPRITE_KINDS: [EK; 26] = [
|
||||
EK::Worker,
|
||||
EK::Kobold,
|
||||
EK::Spider,
|
||||
@@ -1686,6 +1712,8 @@ pub const SPRITE_KINDS: [EK; 24] = [
|
||||
EK::BattleMage,
|
||||
EK::Ram,
|
||||
EK::Catapult,
|
||||
EK::CaveBear,
|
||||
EK::Beetle,
|
||||
];
|
||||
// width of the sprite texture in pixels
|
||||
pub const SPRITE_TEX_W: f32 = (SPRITE_KINDS.len() * 12) as f32;
|
||||
|
||||
@@ -31,6 +31,7 @@ mod rooms;
|
||||
mod save;
|
||||
#[cfg(test)]
|
||||
mod scenes;
|
||||
mod beasts;
|
||||
mod blocks;
|
||||
mod bosses;
|
||||
mod bridges;
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::ui::{tool_cost_static, BLUE as BLUE_C, GREEN as GREEN_C, RED as RED_C
|
||||
|
||||
pub const TABS: [&str; 4] = ["best.tab.creatures", "best.tab.humans", "best.tab.dungeon", "best.tab.ranks"];
|
||||
|
||||
const CREATURES: [EK; 14] = [EK::Worker, EK::Kobold, EK::Spider, EK::Mage, EK::Ogre, EK::Jailer, EK::Priestess, EK::Chainbreaker, EK::Cogling, EK::Revenant, EK::Bat, EK::Gargoyle, EK::Owl, EK::Wyrm];
|
||||
const CREATURES: [EK; 16] = [EK::Worker, EK::Kobold, EK::Spider, EK::Mage, EK::Ogre, EK::Jailer, EK::Priestess, EK::Chainbreaker, EK::Cogling, EK::Revenant, EK::Bat, EK::Gargoyle, EK::Owl, EK::Wyrm, EK::CaveBear, EK::Beetle];
|
||||
const HUMANS: [EK; 10] = [EK::Knight, EK::Archer, EK::Priest, EK::Dwarf, EK::Fairy, EK::BattleMage, EK::Giant, EK::Paladin, EK::Ram, EK::Catapult];
|
||||
// buildings: indices into ui::TOOLS
|
||||
const BUILDINGS: [usize; 32] = [2, 3, 4, 5, 6, 12, 40, 13, 18, 21, 41, 42, 43, 44, 17, 47, 49, 19, 14, 15, 16, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
|
||||
|
||||
@@ -755,6 +755,8 @@ mod tests {
|
||||
e.side = 3;
|
||||
}
|
||||
assert_eq!(g.nearest_enemy(&red_e, 3.0), Some(blue), "the other team is");
|
||||
// the blue test unit next to green's heart has done its job (it would see the heart)
|
||||
g.ents.retain(|e| e.id != blue);
|
||||
}
|
||||
for _ in 0..40 {
|
||||
turn_all(&mut games, &[]);
|
||||
|
||||
@@ -28,6 +28,8 @@ pub fn persona(k: EK) -> Persona {
|
||||
EK::Wyrm => p(Kind::Treasury, &[EK::Bat], Some((7, "inferno"))),
|
||||
EK::Cogling => p(Kind::Forge, &[EK::Ogre], Some((4, "bomb"))),
|
||||
EK::Revenant => p(Kind::Graveyard, &[EK::Priestess], Some((5, "drain"))),
|
||||
EK::CaveBear => p(Kind::Farm, &[EK::Spider], None),
|
||||
EK::Beetle => p(Kind::Lair, &[EK::Bat], None),
|
||||
_ => p(Kind::Lair, &[], None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,8 @@ impl Game {
|
||||
}
|
||||
// giant mushrooms in the open: the underground's little wood
|
||||
self.grow_mushrooms(seed);
|
||||
// and now and then a den of wild beasts
|
||||
self.place_beasts(seed);
|
||||
}
|
||||
|
||||
// a cavern: a few overlapping blobs, ragged edges
|
||||
|
||||
Reference in new issue
Block a user