Three new heroes: giant, fairy spy, battle mage
- giant (from wave 6): slow and enormous, smashes doors three times as hard and wrecks traps by standing next to them - fairy spy (from wave 3): flies, finds the dungeon's traps (marked with a glint) so the heroes wreck them, and heals heroes around it - battle mage (from wave 4): throws fireballs with splash and blasts reinforced walls (steel resists him better) - sprites, bestiary texts EN/DE, test: fairies find traps, giants wreck them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a58497e3a9
commit
4ce2c2919e
7 files changed
+185
-14
No files matched your search
@@ -893,3 +893,12 @@ best.b.arena.text = Sand, Schweiß und gelegentlich ein Zahn. Erfahrung kommt hi
|
|||||||
best.b.altar.text = Schwarzer Marmor, der trinkt. Man sagt, er höre auf Paare. Oger und Spinne, Magier und Fledermaus, Kerkermeister und Priesterin, Kobold und Cogling...
|
best.b.altar.text = Schwarzer Marmor, der trinkt. Man sagt, er höre auf Paare. Oger und Spinne, Magier und Fledermaus, Kerkermeister und Priesterin, Kobold und Cogling...
|
||||||
ability.drain.name = Lebensraub
|
ability.drain.name = Lebensraub
|
||||||
ability.drain.desc = heilt sich um ein Drittel des Schadens, den er austeilt.
|
ability.drain.desc = heilt sich um ein Drittel des Schadens, den er austeilt.
|
||||||
|
|
||||||
|
# more heroes
|
||||||
|
unit.giant = Riese
|
||||||
|
unit.fairy = Feenspion
|
||||||
|
unit.battlemage = Kampfmagier
|
||||||
|
msg.trap_wrecked = Die Helden haben eine Falle zerstört: {0}.
|
||||||
|
best.giant.text = Ein Riese ist, was passiert, wenn ein Ritter dreihundert Jahre lang sein Gemüse isst. Langsam, gewaltig, und Türen hält er für einen Vorschlag. | Fallen zertrümmert er, indem er einfach danebensteht. Widerliche Manieren. Bearbeite ihn mit allem, am besten aus der Ferne.
|
||||||
|
best.fairy.text = Der kleine Spitzel des Ordens: eine leuchtende Mücke mit Flügeln, vorausgeschickt, um deine Fallen zu finden und es allen zu erzählen. Hat sie eine Falle gesehen, schlagen die Helden sie kaputt. | Außerdem streut sie Heilstaub auf die Helden. Erschlag sie zuerst. Zweimal, sicherheitshalber.
|
||||||
|
best.battlemage.text = Ein Magier in der Robe des Ordens, der Mauern als persönliche Beleidigung empfindet. Verstärkte Wände zerplatzen unter seinen Feuerbällen, und er hört nicht auf, davon zu reden. | Aus der Nähe zerbrechlich. Stahlwände ärgern ihn am meisten - gut so.
|
||||||
@@ -894,3 +894,12 @@ best.b.arena.text = Sand, sweat and the occasional tooth. Experience comes fast
|
|||||||
best.b.altar.text = Black marble that drinks. Some say it listens to pairs. Ogre and spider, mage and bat, jailer and priestess, kobold and cogling...
|
best.b.altar.text = Black marble that drinks. Some say it listens to pairs. Ogre and spider, mage and bat, jailer and priestess, kobold and cogling...
|
||||||
ability.drain.name = Life drain
|
ability.drain.name = Life drain
|
||||||
ability.drain.desc = heals itself by a third of the damage it deals.
|
ability.drain.desc = heals itself by a third of the damage it deals.
|
||||||
|
|
||||||
|
# more heroes
|
||||||
|
unit.giant = Giant
|
||||||
|
unit.fairy = Fairy Spy
|
||||||
|
unit.battlemage = Battle Mage
|
||||||
|
msg.trap_wrecked = The heroes wrecked a trap: {0}.
|
||||||
|
best.giant.text = A giant is what happens when a knight eats his vegetables for three hundred years. Slow, enormous, and it treats doors as a suggestion. | It wrecks traps simply by standing next to them. Revolting manners. Hit it with everything, preferably from afar.
|
||||||
|
best.fairy.text = The Order's little informer: a glowing gnat with wings, sent ahead to find your traps and tell everybody. Once it has seen a trap, the heroes smash it. | It also sprinkles healing dust on the heroes. Swat it first. Twice, to be sure.
|
||||||
|
best.battlemage.text = A mage in the Order's robes who thinks walls are a personal insult. Reinforced walls burst under his fireballs, and he never stops talking about it. | Fragile up close. Steel walls annoy him most - good.
|
||||||
+43
-5
@@ -109,6 +109,10 @@ pub enum EK {
|
|||||||
Wyrm, // big aerie + treasury: rare, very strong, fire breath
|
Wyrm, // big aerie + treasury: rare, very strong, fire breath
|
||||||
Cogling, // forge: a sooty little tinkerer, builds traps and doors fastest
|
Cogling, // forge: a sooty little tinkerer, builds traps and doors fastest
|
||||||
Revenant, // rises from the graveyard: undead, needs no food and no bed
|
Revenant, // rises from the graveyard: undead, needs no food and no bed
|
||||||
|
// more heroes
|
||||||
|
Giant, // slow, huge, smashes doors and traps
|
||||||
|
Fairy, // flies, finds the dungeon's traps and heals heroes a little
|
||||||
|
BattleMage, // blasts reinforced walls from afar, throws fireballs
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct St {
|
pub struct St {
|
||||||
@@ -147,12 +151,15 @@ pub fn st(k: EK) -> St {
|
|||||||
EK::Wyrm => s("unit.wyrm", 650.0, 30.0, 3.0, 2.5, 1.4, 380, 0),
|
EK::Wyrm => s("unit.wyrm", 650.0, 30.0, 3.0, 2.5, 1.4, 380, 0),
|
||||||
EK::Cogling => s("unit.cogling", 90.0, 9.0, 1.0, 2.4, 0.9, 70, 0),
|
EK::Cogling => s("unit.cogling", 90.0, 9.0, 1.0, 2.4, 0.9, 70, 0),
|
||||||
EK::Revenant => s("unit.revenant", 160.0, 14.0, 1.0, 1.9, 1.2, 30, 0),
|
EK::Revenant => s("unit.revenant", 160.0, 14.0, 1.0, 1.9, 1.2, 30, 0),
|
||||||
|
EK::Giant => s("unit.giant", 900.0, 34.0, 1.1, 1.3, 1.6, 200, 600),
|
||||||
|
EK::Fairy => s("unit.fairy", 70.0, 5.0, 3.5, 3.2, 1.2, 60, 150),
|
||||||
|
EK::BattleMage => s("unit.battlemage", 120.0, 18.0, 5.0, 1.8, 1.8, 120, 250),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// flying creatures cross water and lava and sleep in the aerie
|
// flying creatures cross water and lava and sleep in the aerie
|
||||||
pub fn flies(k: EK) -> bool {
|
pub fn flies(k: EK) -> bool {
|
||||||
matches!(k, EK::Bat | EK::Gargoyle | EK::Owl | EK::Wyrm)
|
matches!(k, EK::Bat | EK::Gargoyle | EK::Owl | EK::Wyrm | EK::Fairy)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
@@ -1599,6 +1606,13 @@ impl Game {
|
|||||||
}
|
}
|
||||||
if w >= 3 {
|
if w >= 3 {
|
||||||
pool.push((Priest, 2));
|
pool.push((Priest, 2));
|
||||||
|
pool.push((Fairy, 1));
|
||||||
|
}
|
||||||
|
if w >= 4 {
|
||||||
|
pool.push((BattleMage, 1));
|
||||||
|
}
|
||||||
|
if w >= 6 {
|
||||||
|
pool.push((Giant, 1));
|
||||||
}
|
}
|
||||||
let boss = if w % 5 == 0 { Some(Paladin) } else { None };
|
let boss = if w % 5 == 0 { Some(Paladin) } else { None };
|
||||||
let base_level = 1 + (w - 1) / 3;
|
let base_level = 1 + (w - 1) / 3;
|
||||||
@@ -1822,15 +1836,27 @@ impl Game {
|
|||||||
e.timer = 0.0;
|
e.timer = 0.0;
|
||||||
let pow = match e.kind {
|
let pow = match e.kind {
|
||||||
EK::Dwarf => 3.0,
|
EK::Dwarf => 3.0,
|
||||||
EK::Paladin => 2.0,
|
EK::Paladin | EK::Giant => 2.0,
|
||||||
_ => 1.0,
|
_ => 1.0,
|
||||||
} * if tile.kind == Kind::Steel {
|
} * if e.kind == EK::BattleMage && (tile.reinforced || tile.kind == Kind::Steel) {
|
||||||
|
// the battle mage blasts reinforced walls (steel resists better)
|
||||||
|
if tile.kind == Kind::Steel {
|
||||||
|
0.4
|
||||||
|
} else {
|
||||||
|
1.6
|
||||||
|
}
|
||||||
|
} else if tile.kind == Kind::Steel {
|
||||||
0.05
|
0.05
|
||||||
} else if tile.reinforced {
|
} else if tile.reinforced {
|
||||||
0.2
|
0.2
|
||||||
} else {
|
} else {
|
||||||
1.0
|
1.0
|
||||||
};
|
};
|
||||||
|
if e.kind == EK::BattleMage {
|
||||||
|
let (sx, sy) = (e.x, e.y);
|
||||||
|
self.shot(sx, sy, 0.6, tx as f32 + 0.5, ty as f32 + 0.5, 0.5, 0.3, 0xff3080ff);
|
||||||
|
self.style_last_fx(crate::particles::ST_FIRE);
|
||||||
|
}
|
||||||
self.fx(6, tx as f32 + 0.5, ty as f32 + 0.5, 0.0, 0.0, 0.4, 0xff3a5a7a);
|
self.fx(6, tx as f32 + 0.5, ty as f32 + 0.5, 0.0, 0.0, 0.4, 0xff3a5a7a);
|
||||||
self.sfx(Sfx::Dig, tx as f32 + 0.5, ty as f32 + 0.5);
|
self.sfx(Sfx::Dig, tx as f32 + 0.5, ty as f32 + 0.5);
|
||||||
let t = &mut self.map.t[next];
|
let t = &mut self.map.t[next];
|
||||||
@@ -2020,7 +2046,7 @@ impl Game {
|
|||||||
EK::Archer => ST_ARROW,
|
EK::Archer => ST_ARROW,
|
||||||
EK::Mage => ST_SHADOW,
|
EK::Mage => ST_SHADOW,
|
||||||
EK::Priest => ST_HOLY,
|
EK::Priest => ST_HOLY,
|
||||||
EK::Priestess | EK::Wyrm => ST_FIRE,
|
EK::Priestess | EK::Wyrm | EK::BattleMage => ST_FIRE,
|
||||||
EK::Owl => ST_ARCANE,
|
EK::Owl => ST_ARCANE,
|
||||||
_ => ST_PLAIN,
|
_ => ST_PLAIN,
|
||||||
});
|
});
|
||||||
@@ -2051,7 +2077,7 @@ impl Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if e.kind == EK::Priestess || e.kind == EK::Wyrm {
|
if e.kind == EK::Priestess || e.kind == EK::Wyrm || e.kind == EK::BattleMage {
|
||||||
// fireball / fire breath: half damage to enemies around the target
|
// fireball / fire breath: half damage to enemies around the target
|
||||||
let me = e.side();
|
let me = e.side();
|
||||||
let splash: Vec<usize> = self
|
let splash: Vec<usize> = self
|
||||||
@@ -3746,6 +3772,18 @@ impl Game {
|
|||||||
// ---------------------------------------------------------------- heroes
|
// ---------------------------------------------------------------- heroes
|
||||||
|
|
||||||
fn hero_ai(&mut self, e: &mut Ent, dt: f32) {
|
fn hero_ai(&mut self, e: &mut Ent, dt: f32) {
|
||||||
|
if e.kind == EK::Fairy {
|
||||||
|
// fairy dust: the heroes around are healed a little all the time
|
||||||
|
for o in self.ents.iter_mut() {
|
||||||
|
if o.alive && o.active_hero() && o.hp < o.max_hp && (o.x - e.x).powi(2) + (o.y - e.y).powi(2) < 9.0 {
|
||||||
|
o.hp = (o.hp + 6.0 * dt).min(o.max_hp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.vrng.f() < 5.0 * dt {
|
||||||
|
let vx = self.vrng.f() * 0.6 - 0.3;
|
||||||
|
self.part(crate::particles::EMBER, (e.x, e.y, 0.9), (vx, 0.0, 0.2), 1.0, crate::particles::abgr(255, 240, 180), 0.03);
|
||||||
|
}
|
||||||
|
}
|
||||||
if e.think <= 0.0 {
|
if e.think <= 0.0 {
|
||||||
e.think = 0.5;
|
e.think = 0.5;
|
||||||
if e.kind == EK::Priest && e.cd <= 0.0 {
|
if e.kind == EK::Priest && e.cd <= 0.0 {
|
||||||
|
|||||||
+44
-2
@@ -1278,6 +1278,45 @@ fn sprite_rows(k: EK) -> [&'static str; 10] {
|
|||||||
"..kCk.kCk.",
|
"..kCk.kCk.",
|
||||||
"..kk...kk.",
|
"..kk...kk.",
|
||||||
],
|
],
|
||||||
|
// giant: a huge brute in a leather jerkin with a tree-trunk club
|
||||||
|
EK::Giant => [
|
||||||
|
"...kkkk...",
|
||||||
|
"..kffffk..",
|
||||||
|
"..kfefek.b",
|
||||||
|
"..kffffk.b",
|
||||||
|
".kBBBBBBkb",
|
||||||
|
"kfBuuuuBfb",
|
||||||
|
"kfBuuuuBf.",
|
||||||
|
".kBBBBBBk.",
|
||||||
|
".kBBk.kBBk",
|
||||||
|
".kkk...kkk",
|
||||||
|
],
|
||||||
|
// fairy: a tiny glowing spy with dragonfly wings
|
||||||
|
EK::Fairy => [
|
||||||
|
"..........",
|
||||||
|
"ww.....ww.",
|
||||||
|
"www.kk.www",
|
||||||
|
".wwkffkww.",
|
||||||
|
"..wkefkw..",
|
||||||
|
"...kuuk...",
|
||||||
|
"...kuuk...",
|
||||||
|
"....kk....",
|
||||||
|
"...y..y...",
|
||||||
|
"..........",
|
||||||
|
],
|
||||||
|
// battle mage: robes of the order, a burning staff
|
||||||
|
EK::BattleMage => [
|
||||||
|
"....kk...o",
|
||||||
|
"...kuuk.oy",
|
||||||
|
"..kuuuuk.o",
|
||||||
|
".kkffffk.b",
|
||||||
|
"..kfefek.b",
|
||||||
|
"..kwuuwk.b",
|
||||||
|
".kuwuuwukb",
|
||||||
|
".kuuwwuuk.",
|
||||||
|
".kuuuuuuk.",
|
||||||
|
"..kkkkkk..",
|
||||||
|
],
|
||||||
EK::Wyrm => [
|
EK::Wyrm => [
|
||||||
"R.......kw",
|
"R.......kw",
|
||||||
"RR....krrk",
|
"RR....krrk",
|
||||||
@@ -1347,7 +1386,7 @@ fn pal(ch: char) -> Option<Color> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const SPRITE_KINDS: [EK; 19] = [
|
pub const SPRITE_KINDS: [EK; 22] = [
|
||||||
EK::Worker,
|
EK::Worker,
|
||||||
EK::Kobold,
|
EK::Kobold,
|
||||||
EK::Spider,
|
EK::Spider,
|
||||||
@@ -1367,6 +1406,9 @@ pub const SPRITE_KINDS: [EK; 19] = [
|
|||||||
EK::Wyrm,
|
EK::Wyrm,
|
||||||
EK::Cogling,
|
EK::Cogling,
|
||||||
EK::Revenant,
|
EK::Revenant,
|
||||||
|
EK::Giant,
|
||||||
|
EK::Fairy,
|
||||||
|
EK::BattleMage,
|
||||||
];
|
];
|
||||||
// width of the sprite texture in pixels
|
// width of the sprite texture in pixels
|
||||||
pub const SPRITE_TEX_W: f32 = (SPRITE_KINDS.len() * 12) as f32;
|
pub const SPRITE_TEX_W: f32 = (SPRITE_KINDS.len() * 12) as f32;
|
||||||
@@ -1384,7 +1426,7 @@ pub fn gear_tier(level: u32) -> u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_hero_kind(k: EK) -> bool {
|
fn is_hero_kind(k: EK) -> bool {
|
||||||
matches!(k, EK::Knight | EK::Archer | EK::Priest | EK::Dwarf | EK::Paladin)
|
matches!(k, EK::Knight | EK::Archer | EK::Priest | EK::Dwarf | EK::Paladin | EK::Giant | EK::Fairy | EK::BattleMage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Views of a sprite for eight directions (the others are mirrored): 0 front, 1 front
|
// Views of a sprite for eight directions (the others are mirrored): 0 front, 1 front
|
||||||
|
|||||||
@@ -8,7 +8,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"];
|
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; 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 HUMANS: [EK; 5] = [EK::Knight, EK::Archer, EK::Priest, EK::Dwarf, EK::Paladin];
|
const HUMANS: [EK; 8] = [EK::Knight, EK::Archer, EK::Priest, EK::Dwarf, EK::Fairy, EK::BattleMage, EK::Giant, EK::Paladin];
|
||||||
// buildings: indices into ui::TOOLS
|
// buildings: indices into ui::TOOLS
|
||||||
const BUILDINGS: [usize; 30] = [2, 3, 4, 5, 6, 12, 40, 13, 18, 21, 41, 42, 43, 44, 17, 19, 14, 15, 16, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
|
const BUILDINGS: [usize; 30] = [2, 3, 4, 5, 6, 12, 40, 13, 18, 21, 41, 42, 43, 44, 17, 19, 14, 15, 16, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
|
||||||
|
|
||||||
|
|||||||
+8
-3
@@ -1283,7 +1283,7 @@ impl R3d {
|
|||||||
if !e.alive || e.held || unit_hidden(g, e) {
|
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 | EK::Giant);
|
||||||
let lift = if flies(e.kind) && !(matches!(e.job, Job::Sleep(_)) && !e.moving) { 0.45 } else { 0.0 };
|
let lift = if flies(e.kind) && !(matches!(e.job, Job::Sleep(_)) && !e.moving) { 0.45 } else { 0.0 };
|
||||||
let head = proj(vec3(e.x, lift + if big { 0.9 } else { 0.72 }, e.y));
|
let head = proj(vec3(e.x, lift + if big { 0.9 } else { 0.72 }, e.y));
|
||||||
// bar width in block characters, depending on the zoom
|
// bar width in block characters, depending on the zoom
|
||||||
@@ -1363,7 +1363,7 @@ fn shade_c(r: u8, g: u8, b: u8, f: f32) -> Color {
|
|||||||
|
|
||||||
// size of a unit's sprite (same as in the billboards)
|
// size of a unit's sprite (same as in the billboards)
|
||||||
fn sprite_size(k: EK) -> f32 {
|
fn sprite_size(k: EK) -> f32 {
|
||||||
if matches!(k, EK::Chainbreaker | EK::Wyrm) {
|
if matches!(k, EK::Chainbreaker | EK::Wyrm | EK::Giant) {
|
||||||
1.0
|
1.0
|
||||||
} else if matches!(k, EK::Ogre | EK::Paladin | EK::Jailer | EK::Gargoyle) {
|
} else if matches!(k, EK::Ogre | EK::Paladin | EK::Jailer | EK::Gargoyle) {
|
||||||
0.8
|
0.8
|
||||||
@@ -2086,7 +2086,7 @@ fn draw_shadows(g: &Game) {
|
|||||||
if !e.alive || e.held || unit_hidden(g, e) {
|
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 | EK::Giant) { 0.32 } else { 0.24 };
|
||||||
let fy = floor_y(g, e.x, e.y);
|
let fy = floor_y(g, e.x, e.y);
|
||||||
disc(vec3(e.x, fy + 0.012, e.y), r, Color::new(0.0, 0.0, 0.0, 0.4), 12);
|
disc(vec3(e.x, fy + 0.012, e.y), r, Color::new(0.0, 0.0, 0.0, 0.4), 12);
|
||||||
let rc = if e.captive != 0 {
|
let rc = if e.captive != 0 {
|
||||||
@@ -2296,6 +2296,11 @@ fn draw_traps_glow(g: &Game, t: f32) {
|
|||||||
if tr.stage != ARMED {
|
if tr.stage != ARMED {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if tr.revealed && !tr.kind.door() {
|
||||||
|
// found by a fairy: a white warning glint above it
|
||||||
|
let a = 0.5 + 0.5 * (t * 6.0).sin();
|
||||||
|
draw_cube(c + vec3(0.0, 0.7 + 0.05 * (t * 3.0).sin(), 0.0), vec3(0.06, 0.18, 0.06), None, Color::new(1.0, 1.0, 1.0, a));
|
||||||
|
}
|
||||||
match tr.kind {
|
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::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 => {
|
TrapKind::Fire => {
|
||||||
|
|||||||
+71
-3
@@ -98,6 +98,8 @@ pub struct Trap {
|
|||||||
pub carrier: u32, // imp carrying or installing it (entity id + 1)
|
pub carrier: u32, // imp carrying or installing it (entity id + 1)
|
||||||
pub fired: f32, // time since it last went off (for drawing)
|
pub fired: f32, // time since it last went off (for drawing)
|
||||||
pub aim: f32, // direction of the last shot (fire jet, roller)
|
pub aim: f32, // direction of the last shot (fire jet, roller)
|
||||||
|
#[serde(default)]
|
||||||
|
pub revealed: bool, // a fairy has found it: heroes wreck it
|
||||||
}
|
}
|
||||||
|
|
||||||
// a boulder rolling down a corridor
|
// a boulder rolling down a corridor
|
||||||
@@ -122,8 +124,12 @@ pub struct Cloud {
|
|||||||
const CLOUD_TIME: f32 = 6.0;
|
const CLOUD_TIME: f32 = 6.0;
|
||||||
|
|
||||||
// how hard a unit hits doors
|
// how hard a unit hits doors
|
||||||
pub fn door_breaker(_k: EK) -> f32 {
|
pub fn door_breaker(k: EK) -> f32 {
|
||||||
|
if k == EK::Giant {
|
||||||
|
3.0
|
||||||
|
} else {
|
||||||
1.0
|
1.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enemy(e: &Ent) -> bool {
|
fn enemy(e: &Ent) -> bool {
|
||||||
@@ -172,7 +178,7 @@ impl Game {
|
|||||||
}
|
}
|
||||||
let id = self.next_id;
|
let id = self.next_id;
|
||||||
self.next_id += 1;
|
self.next_id += 1;
|
||||||
self.traps.push(Trap { id, kind, tile: i, stage: ORDERED, crate_tile: 0, progress: 0.0, cd: 0.0, hp: kind.hp(), locked: false, carrier: 0, fired: 99.0, aim: 0.0 });
|
self.traps.push(Trap { id, kind, tile: i, stage: ORDERED, crate_tile: 0, progress: 0.0, cd: 0.0, hp: kind.hp(), locked: false, carrier: 0, fired: 99.0, aim: 0.0, revealed: false });
|
||||||
self.fx(8, x as f32 + 0.5, y as f32 + 0.5, 0.0, 0.0, 0.4, 0xff40d0ff);
|
self.fx(8, x as f32 + 0.5, y as f32 + 0.5, 0.0, 0.0, 0.4, 0xff40d0ff);
|
||||||
self.sfx_ui(Sfx::FlagSet);
|
self.sfx_ui(Sfx::FlagSet);
|
||||||
}
|
}
|
||||||
@@ -408,6 +414,45 @@ impl Game {
|
|||||||
}
|
}
|
||||||
rollers.retain(|r| r.t < 50.0);
|
rollers.retain(|r| r.t < 50.0);
|
||||||
self.rollers = rollers;
|
self.rollers = rollers;
|
||||||
|
// fairies find traps, giants and heroes wreck the traps they know about
|
||||||
|
let mut wrecked = Vec::new();
|
||||||
|
for k in 0..self.traps.len() {
|
||||||
|
let t = &self.traps[k];
|
||||||
|
if t.stage != ARMED || t.kind.door() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (tx, ty) = xy(t.tile);
|
||||||
|
let (cx, cy) = (tx as f32 + 0.5, ty as f32 + 0.5);
|
||||||
|
let mut damage = 0.0;
|
||||||
|
let mut found = t.revealed;
|
||||||
|
for e in self.ents.iter().filter(|e| e.alive && !e.held && e.active_hero()) {
|
||||||
|
let d2 = (e.x - cx).powi(2) + (e.y - cy).powi(2);
|
||||||
|
if e.kind == EK::Fairy && d2 < 9.0 {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
if d2 < 1.6 && (found || e.kind == EK::Giant) {
|
||||||
|
damage += if e.kind == EK::Giant { 40.0 } else { 15.0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let t = &mut self.traps[k];
|
||||||
|
if found && !t.revealed {
|
||||||
|
t.revealed = true;
|
||||||
|
}
|
||||||
|
if damage > 0.0 {
|
||||||
|
t.hp -= damage * dt;
|
||||||
|
t.fired = 0.0;
|
||||||
|
if t.hp <= 0.0 {
|
||||||
|
wrecked.push(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &k in wrecked.iter().rev() {
|
||||||
|
let t = self.traps.remove(k);
|
||||||
|
let (tx, ty) = xy(t.tile);
|
||||||
|
self.fx(3, tx as f32 + 0.5, ty as f32 + 0.5, 0.0, 0.0, 0.8, 0xff505070);
|
||||||
|
self.fx(6, tx as f32 + 0.5, ty as f32 + 0.5, 0.0, 0.0, 0.5, 0xff3a5a7a);
|
||||||
|
self.msg("msg.trap_wrecked", vec![format!("@trap.{}", t.kind.id())]);
|
||||||
|
}
|
||||||
for k in 0..self.traps.len() {
|
for k in 0..self.traps.len() {
|
||||||
if k >= self.traps.len() {
|
if k >= self.traps.len() {
|
||||||
break;
|
break;
|
||||||
@@ -583,7 +628,7 @@ mod tests {
|
|||||||
fn doors_stop_enemies_until_smashed_and_lock() {
|
fn doors_stop_enemies_until_smashed_and_lock() {
|
||||||
let (mut g, cx, cy) = forge_game();
|
let (mut g, cx, cy) = forge_game();
|
||||||
let i = idx(cx + 10, cy);
|
let i = idx(cx + 10, cy);
|
||||||
g.traps.push(Trap { id: 999, kind: TrapKind::WoodDoor, tile: i, stage: ARMED, crate_tile: 0, progress: 0.0, cd: 0.0, hp: 400.0, locked: false, carrier: 0, fired: 99.0, aim: 0.0 });
|
g.traps.push(Trap { id: 999, kind: TrapKind::WoodDoor, tile: i, stage: ARMED, crate_tile: 0, progress: 0.0, cd: 0.0, hp: 400.0, locked: false, carrier: 0, fired: 99.0, aim: 0.0, revealed: false });
|
||||||
g.map.t[i].kind = Kind::Door;
|
g.map.t[i].kind = Kind::Door;
|
||||||
assert!(g.map.walk_cost()(i).is_some());
|
assert!(g.map.walk_cost()(i).is_some());
|
||||||
g.place_trap(TrapKind::WoodDoor, cx + 10, cy);
|
g.place_trap(TrapKind::WoodDoor, cx + 10, cy);
|
||||||
@@ -601,4 +646,27 @@ mod tests {
|
|||||||
assert!(g.trap_at(i).is_none(), "door smashed");
|
assert!(g.trap_at(i).is_none(), "door smashed");
|
||||||
assert_eq!(g.map.t[i].kind, Kind::Floor);
|
assert_eq!(g.map.t[i].kind, Kind::Floor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fairies_find_traps_and_giants_wreck_them() {
|
||||||
|
let (mut g, cx, cy) = forge_game();
|
||||||
|
let a = idx(cx + 10, cy);
|
||||||
|
let b = idx(cx + 10, cy + 4);
|
||||||
|
for (id, i, kind) in [(1001, a, TrapKind::Frost), (1002, b, TrapKind::Alarm)] {
|
||||||
|
g.traps.push(Trap { id, kind, tile: i, stage: ARMED, crate_tile: 0, progress: 0.0, cd: 99.0, hp: kind.hp(), locked: false, carrier: 0, fired: 99.0, aim: 0.0, revealed: false });
|
||||||
|
}
|
||||||
|
g.spawn(EK::Fairy, true, cx as f32 + 11.5, cy as f32 + 0.5, 1);
|
||||||
|
g.spawn(EK::Giant, true, cx as f32 + 10.5, cy as f32 + 5.5, 1);
|
||||||
|
for j in 0..g.ents.len() {
|
||||||
|
g.ents[j].stun = 999.0; // stand still
|
||||||
|
}
|
||||||
|
for _ in 0..(20 * 15) {
|
||||||
|
g.update(0.05);
|
||||||
|
for j in 0..g.ents.len() {
|
||||||
|
g.ents[j].stun = 999.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(g.traps.iter().all(|t| t.id != 1002), "the giant wrecked the alarm");
|
||||||
|
assert!(g.traps.iter().find(|t| t.id == 1001).map_or(true, |t| t.revealed), "the fairy found the frost glow");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user