Umstellung auf macroquad: endlose Wellen, Heldentor mit Verteidigern, Angriffsflagge, Sound-Events, Grafikmodul mit DINOBYTE-Bitmap-Font (CC BY 4.0) und CP437-Rahmen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ec9cab9453
commit
14e481cffb
7 files changed
+1182
-45
No files matched your search
+407
-39
@@ -2,11 +2,65 @@
|
||||
|
||||
use crate::map::*;
|
||||
|
||||
pub const WAVES: u32 = 6;
|
||||
pub const CORE_HP: f32 = 2000.0;
|
||||
pub const GATE_HP: f32 = 3000.0;
|
||||
pub const PAYDAY: f32 = 150.0;
|
||||
pub const FIRST_WAVE: f32 = 300.0;
|
||||
pub const WAVE_GAP: f32 = 210.0;
|
||||
pub const WAVE_GAP: f32 = 180.0;
|
||||
pub const MAX_SQUAD: usize = 18;
|
||||
|
||||
// Sound-Ereignisse. Die Spiellogik meldet sie, das Sound-Modul spielt sie ab.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Sfx {
|
||||
Dig,
|
||||
GoldDig,
|
||||
DigDone,
|
||||
Claim,
|
||||
Deposit,
|
||||
Build,
|
||||
Sell,
|
||||
MeleeHit,
|
||||
Shoot,
|
||||
Death,
|
||||
HeroDeath,
|
||||
Lightning,
|
||||
Heal,
|
||||
Summon,
|
||||
PortalArrive,
|
||||
WaveAlarm,
|
||||
WaveStart,
|
||||
CoreHit,
|
||||
GateHit,
|
||||
GateDefenders,
|
||||
LevelUp,
|
||||
Pickup,
|
||||
Drop,
|
||||
Payday,
|
||||
FlagSet,
|
||||
Victory,
|
||||
Defeat,
|
||||
Click,
|
||||
Error,
|
||||
}
|
||||
|
||||
pub const SFX_ALL: [Sfx; 29] = [
|
||||
Sfx::Dig, Sfx::GoldDig, Sfx::DigDone, Sfx::Claim, Sfx::Deposit, Sfx::Build, Sfx::Sell, Sfx::MeleeHit,
|
||||
Sfx::Shoot, Sfx::Death, Sfx::HeroDeath, Sfx::Lightning, Sfx::Heal, Sfx::Summon, Sfx::PortalArrive,
|
||||
Sfx::WaveAlarm, Sfx::WaveStart, Sfx::CoreHit, Sfx::GateHit, Sfx::GateDefenders, Sfx::LevelUp,
|
||||
Sfx::Pickup, Sfx::Drop, Sfx::Payday, Sfx::FlagSet, Sfx::Victory, Sfx::Defeat, Sfx::Click, Sfx::Error,
|
||||
];
|
||||
|
||||
impl Sfx {
|
||||
pub fn index(self) -> usize {
|
||||
self as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SfxEvent {
|
||||
pub sfx: Sfx,
|
||||
pub pos: Option<(f32, f32)>, // Weltposition (für Lautstärke nach Entfernung), None = UI
|
||||
}
|
||||
pub const MANA_MAX: f32 = 400.0;
|
||||
pub const CARRY_MAX: i32 = 300;
|
||||
|
||||
@@ -65,6 +119,7 @@ pub enum Job {
|
||||
Attack(u32),
|
||||
AttackCore,
|
||||
Leave,
|
||||
Rally,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -96,6 +151,7 @@ pub struct Ent {
|
||||
pub face: f32,
|
||||
pub moving: bool,
|
||||
pub working: bool,
|
||||
pub guard: bool, // Torverteidiger
|
||||
}
|
||||
|
||||
impl Ent {
|
||||
@@ -128,6 +184,7 @@ impl Ent {
|
||||
face: 1.0,
|
||||
moving: false,
|
||||
working: false,
|
||||
guard: false,
|
||||
}
|
||||
}
|
||||
pub fn tile(&self) -> usize {
|
||||
@@ -173,6 +230,12 @@ pub struct Game {
|
||||
pub paused: bool,
|
||||
pub hand: Vec<u32>,
|
||||
pub counts: [usize; 5], // Schatzkammer, Quartier, Pilzgarten, Trainingshalle, Bibliothek
|
||||
pub gate_hp: f32,
|
||||
pub gate_hit_t: f32,
|
||||
pub gate_stage: u32, // ausgelöste Verteidiger-Schwellen (0..3)
|
||||
pub flag: Option<usize>,
|
||||
pub events: Vec<SfxEvent>,
|
||||
pub last_wave_value: f32,
|
||||
hint_t: [f32; 16],
|
||||
}
|
||||
|
||||
@@ -221,6 +284,12 @@ impl Game {
|
||||
paused: false,
|
||||
hand: Vec::new(),
|
||||
counts: [0; 5],
|
||||
gate_hp: GATE_HP,
|
||||
gate_hit_t: -100.0,
|
||||
gate_stage: 0,
|
||||
flag: None,
|
||||
events: Vec::new(),
|
||||
last_wave_value: 0.0,
|
||||
hint_t: [-1000.0; 16],
|
||||
};
|
||||
let (cx, cy) = g.map.core;
|
||||
@@ -228,9 +297,10 @@ impl Game {
|
||||
g.spawn(EK::Worker, false, cx as f32 - 0.5, cy as f32 + k as f32 * 0.7 + 0.3, 1);
|
||||
}
|
||||
g.recount();
|
||||
g.msg("Willkommen, Herrscher des Unterreichs! Beschütze deinen Seelenkern.".into());
|
||||
g.msg("Willkommen, Herrscher des Unterreichs! Beschütze deinen Seelenkern und zerstöre das Heldentor.".into());
|
||||
g.msg("Markiere Erde und Goldadern mit dem Werkzeug »Graben« (Taste 1). Deine Grubenwichte erledigen den Rest.".into());
|
||||
g.msg("Grabe dich zum Portal durch und erobere es. Kreaturen brauchen ein Quartier und einen Pilzgarten.".into());
|
||||
g.msg("Mit der Angriffsflagge (Taste 9) schickst du deine Kreaturen in den Kampf, auch gegen das Heldentor.".into());
|
||||
g
|
||||
}
|
||||
|
||||
@@ -241,6 +311,34 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sfx(&mut self, sfx: Sfx, x: f32, y: f32) {
|
||||
if self.events.len() < 64 {
|
||||
self.events.push(SfxEvent { sfx, pos: Some((x, y)) });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sfx_ui(&mut self, sfx: Sfx) {
|
||||
if self.events.len() < 64 {
|
||||
self.events.push(SfxEvent { sfx, pos: None });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gate_center(&self) -> (f32, f32) {
|
||||
(self.map.gate.0 as f32 + 1.5, self.map.gate.1 as f32 + 1.5)
|
||||
}
|
||||
|
||||
// Armeewert: sqrt(Summe HP * Summe Schaden/s) aller eigenen Kreaturen.
|
||||
pub fn army_value(&self) -> f32 {
|
||||
let (mut h, mut d) = (0.0, 0.0);
|
||||
for e in &self.ents {
|
||||
if e.alive && e.creature() {
|
||||
h += e.max_hp;
|
||||
d += e.atk() / st(e.kind).interval;
|
||||
}
|
||||
}
|
||||
(h * d).sqrt()
|
||||
}
|
||||
|
||||
fn hint(&mut self, key: usize, s: &str) {
|
||||
if self.time - self.hint_t[key] > 30.0 {
|
||||
self.hint_t[key] = self.time;
|
||||
@@ -359,18 +457,20 @@ impl Game {
|
||||
self.do_payday();
|
||||
}
|
||||
|
||||
if self.wave < WAVES {
|
||||
self.wave_timer -= dt;
|
||||
if !self.warned && self.wave_timer < 30.0 {
|
||||
self.warned = true;
|
||||
self.msg(format!("Späher melden: Welle {} der Helden erreicht in 30 Sekunden das Tor!", self.wave + 1));
|
||||
}
|
||||
if self.wave_timer <= 0.0 {
|
||||
self.spawn_wave();
|
||||
self.wave += 1;
|
||||
self.wave_timer = WAVE_GAP;
|
||||
self.warned = false;
|
||||
}
|
||||
self.wave_timer -= dt;
|
||||
if !self.warned && self.wave_timer < 30.0 {
|
||||
self.warned = true;
|
||||
self.msg(format!("Späher melden: Welle {} der Helden erreicht in 30 Sekunden das Tor!", self.wave + 1));
|
||||
self.sfx_ui(Sfx::WaveAlarm);
|
||||
}
|
||||
if self.wave_timer <= 0.0 {
|
||||
self.spawn_wave();
|
||||
self.wave += 1;
|
||||
self.wave_timer = WAVE_GAP + self.rng.f() * 40.0;
|
||||
self.warned = false;
|
||||
}
|
||||
if self.time - self.gate_hit_t > 10.0 {
|
||||
self.gate_hp = (self.gate_hp + 8.0 * dt).min(GATE_HP);
|
||||
}
|
||||
|
||||
for i in 0..self.ents.len() {
|
||||
@@ -387,9 +487,12 @@ impl Game {
|
||||
self.core_hp = 0.0;
|
||||
self.state = 2;
|
||||
self.msg("Dein Seelenkern ist zerbrochen. Das Unterreich ist verloren.".into());
|
||||
} else if self.wave >= WAVES && self.hero_count() == 0 {
|
||||
self.sfx_ui(Sfx::Defeat);
|
||||
} else if self.gate_hp <= 0.0 {
|
||||
self.gate_hp = 0.0;
|
||||
self.state = 1;
|
||||
self.msg("Alle Heldenwellen sind vernichtet. Das Unterreich gehört dir!".into());
|
||||
self.msg("Das Heldentor ist zerstört. Das Unterreich gehört dir!".into());
|
||||
self.sfx_ui(Sfx::Victory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,6 +536,7 @@ impl Game {
|
||||
let (px, py) = self.map.portal;
|
||||
self.spawn(kind, false, px as f32 + 1.5, py as f32 + 1.5, 1);
|
||||
self.fx(3, px as f32 + 1.5, py as f32 + 1.5, 0.0, 0.0, 0.8, 0xffcc44ff);
|
||||
self.sfx(Sfx::PortalArrive, px as f32 + 1.5, py as f32 + 1.5);
|
||||
self.msg(format!("Ein {} ist durch das Portal gekommen.", st(kind).name));
|
||||
}
|
||||
|
||||
@@ -458,6 +562,7 @@ impl Game {
|
||||
}
|
||||
}
|
||||
if paid + unpaid > 0 {
|
||||
self.sfx_ui(Sfx::Payday);
|
||||
if unpaid > 0 {
|
||||
self.msg(format!("Zahltag! {} Kreaturen bezahlt ({} Gold), {} gingen leer aus und sind wütend!", paid, total as i32, unpaid));
|
||||
} else {
|
||||
@@ -466,32 +571,129 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
// Stärke eines Trupps (gleiche Formel wie der Armeewert)
|
||||
fn squad_value(list: &[(EK, u32)]) -> f32 {
|
||||
let (mut h, mut d) = (0.0, 0.0);
|
||||
for &(k, l) in list {
|
||||
let s = st(k);
|
||||
h += s.hp * (1.0 + 0.15 * (l - 1) as f32);
|
||||
d += s.atk * (1.0 + 0.12 * (l - 1) as f32) / s.interval;
|
||||
}
|
||||
(h * d).sqrt()
|
||||
}
|
||||
|
||||
// Stellt einen Heldentrupp mit mindestens der Zielstärke zusammen.
|
||||
fn build_squad(&mut self, target: f32, base_level: u32, pool: &[(EK, u32)], boss: Option<EK>, min: usize) -> Vec<(EK, u32)> {
|
||||
let total: u32 = pool.iter().map(|p| p.1).sum();
|
||||
let mut list = Vec::new();
|
||||
for level in base_level.min(10)..=10 {
|
||||
list.clear();
|
||||
if let Some(b) = boss {
|
||||
list.push((b, level));
|
||||
}
|
||||
while list.len() < MAX_SQUAD && (list.len() < min || Self::squad_value(&list) < target) {
|
||||
let mut r = self.rng.next() % total;
|
||||
for &(k, w) in pool {
|
||||
if r < w {
|
||||
list.push((k, level));
|
||||
break;
|
||||
}
|
||||
r -= w;
|
||||
}
|
||||
}
|
||||
if Self::squad_value(&list) >= target {
|
||||
break;
|
||||
}
|
||||
}
|
||||
list
|
||||
}
|
||||
|
||||
fn spawn_squad(&mut self, list: &[(EK, u32)], guard: bool) {
|
||||
let (gx, gy) = self.map.gate;
|
||||
for &(k, l) in list {
|
||||
let x = gx as f32 + 0.5 + self.rng.f() * 2.0;
|
||||
let y = gy as f32 + 0.5 + self.rng.f() * 2.0;
|
||||
let id = self.spawn(k, true, x, y, l);
|
||||
if let Some(e) = self.ents.iter_mut().find(|e| e.id == id) {
|
||||
e.guard = guard;
|
||||
}
|
||||
}
|
||||
self.fx(3, gx as f32 + 1.5, gy as f32 + 1.5, 0.0, 0.0, 1.0, 0xffffeecc);
|
||||
}
|
||||
|
||||
// Endlose Wellen: Stärke 60 % -> 100 % des Armeewerts, mit etwas Zufall, mindestens 2 Ritter.
|
||||
fn spawn_wave(&mut self) {
|
||||
use EK::*;
|
||||
let w = self.wave + 1;
|
||||
let list: Vec<EK> = match w {
|
||||
1 => vec![Knight, Knight, Archer],
|
||||
2 => vec![Knight, Knight, Archer, Archer, Dwarf],
|
||||
3 => vec![Knight, Knight, Knight, Archer, Archer, Priest, Dwarf],
|
||||
4 => vec![Knight, Knight, Knight, Knight, Archer, Archer, Archer, Priest, Dwarf, Dwarf],
|
||||
5 => vec![Knight, Knight, Knight, Knight, Knight, Archer, Archer, Archer, Archer, Priest, Priest, Dwarf, Dwarf],
|
||||
_ => vec![Paladin, Knight, Knight, Knight, Knight, Archer, Archer, Archer, Archer, Priest, Priest, Dwarf],
|
||||
};
|
||||
let (gx, gy) = self.map.gate;
|
||||
let n = list.len();
|
||||
for k in list {
|
||||
let x = gx as f32 + 0.5 + self.rng.f() * 2.0;
|
||||
let y = gy as f32 + 0.5 + self.rng.f() * 2.0;
|
||||
self.spawn(k, true, x, y, w);
|
||||
let ratio = (0.6 + 0.05 * (w - 1) as f32).min(1.0) * (0.88 + self.rng.f() * 0.24);
|
||||
let min_value = Self::squad_value(&[(Knight, 1), (Knight, 1)]);
|
||||
let target = (self.army_value() * ratio).max(min_value);
|
||||
let mut pool = vec![(Knight, 4), (Archer, 3)];
|
||||
if w >= 2 {
|
||||
pool.push((Dwarf, 2));
|
||||
}
|
||||
self.fx(3, gx as f32 + 1.5, gy as f32 + 1.5, 0.0, 0.0, 1.0, 0xffffeecc);
|
||||
if w == WAVES {
|
||||
self.msg(format!("Letzte Welle: Der Lichtpaladin führt {} Helden in dein Reich!", n));
|
||||
if w >= 3 {
|
||||
pool.push((Priest, 2));
|
||||
}
|
||||
let boss = if w % 5 == 0 { Some(Paladin) } else { None };
|
||||
let base_level = 1 + (w - 1) / 3;
|
||||
let list = self.build_squad(target, base_level, &pool, boss, 2);
|
||||
self.last_wave_value = Self::squad_value(&list);
|
||||
self.spawn_squad(&list, false);
|
||||
self.sfx_ui(Sfx::WaveStart);
|
||||
if boss.is_some() {
|
||||
self.msg(format!("Welle {}: Ein Lichtpaladin führt {} Helden in dein Reich!", w, list.len()));
|
||||
} else {
|
||||
self.msg(format!("Welle {}: {} Helden dringen durch das Tor!", w, n));
|
||||
self.msg(format!("Welle {}: {} Helden dringen durch das Tor!", w, list.len()));
|
||||
}
|
||||
}
|
||||
|
||||
// Verteidiger bei 75 / 50 / 25 % Tor-HP
|
||||
fn check_gate_stage(&mut self) {
|
||||
use EK::*;
|
||||
let frac = self.gate_hp / GATE_HP;
|
||||
let want = if frac <= 0.25 { 3 } else if frac <= 0.5 { 2 } else if frac <= 0.75 { 1 } else { 0 };
|
||||
while self.gate_stage < want {
|
||||
self.gate_stage += 1;
|
||||
let st_i = self.gate_stage;
|
||||
let ratio = [0.5, 0.65, 0.85][(st_i - 1) as usize] * (0.9 + self.rng.f() * 0.2);
|
||||
let min_value = Self::squad_value(&[(Knight, 2), (Knight, 2), (Archer, 2)]);
|
||||
let target = (self.army_value() * ratio).max(min_value);
|
||||
let pool = [(Knight, 4), (Archer, 3), (Priest, 2), (Dwarf, 1)];
|
||||
let boss = if st_i == 3 { Some(Paladin) } else { None };
|
||||
let base_level = 2 + self.wave / 3 + st_i;
|
||||
let list = self.build_squad(target, base_level, &pool, boss, 3);
|
||||
self.spawn_squad(&list, true);
|
||||
self.sfx_ui(Sfx::GateDefenders);
|
||||
if st_i == 3 {
|
||||
self.msg(format!("Das Tor wankt! Der Torwächter erscheint mit {} Verteidigern!", list.len() - 1));
|
||||
} else {
|
||||
self.msg(format!("Das Tor ruft {} Verteidiger herbei!", list.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hit_gate(&mut self, e: &mut Ent) {
|
||||
let s = st(e.kind);
|
||||
e.cd = s.interval;
|
||||
let dmg = e.atk() * (0.85 + self.rng.f() * 0.3);
|
||||
self.gate_hp -= dmg;
|
||||
self.gate_hit_t = self.time;
|
||||
e.xp += dmg * 0.3;
|
||||
let (gx, gy) = self.gate_center();
|
||||
if s.range > 1.5 {
|
||||
let col = if e.kind == EK::Mage { 0xffff44cc } else { 0xff70b0d0 };
|
||||
self.fx(0, e.x, e.y - 0.3, gx, gy, 0.25, col);
|
||||
} else {
|
||||
let fx = (e.x + gx) * 0.5;
|
||||
let fy = (e.y + gy) * 0.5;
|
||||
self.fx(2, fx, fy, 0.0, 0.0, 0.25, 0xffffffff);
|
||||
}
|
||||
self.sfx(Sfx::GateHit, gx, gy);
|
||||
self.hint(10, "Deine Kreaturen greifen das Heldentor an!");
|
||||
self.check_gate_stage();
|
||||
}
|
||||
|
||||
fn update_ent(&mut self, e: &mut Ent, dt: f32) {
|
||||
e.anim += dt;
|
||||
e.flash = (e.flash - dt).max(0.0);
|
||||
@@ -508,6 +710,9 @@ impl Game {
|
||||
e.max_hp = s.hp * (1.0 + 0.15 * (e.level - 1) as f32);
|
||||
e.hp += e.max_hp - old;
|
||||
self.fx(7, e.x, e.y, 0.0, 0.0, 0.8, 0xff44ddff);
|
||||
if !e.hero {
|
||||
self.sfx(Sfx::LevelUp, e.x, e.y);
|
||||
}
|
||||
if !e.hero && e.level >= 3 {
|
||||
self.msg(format!("{} erreicht Stufe {}.", s.name, e.level));
|
||||
}
|
||||
@@ -543,6 +748,7 @@ impl Game {
|
||||
_ => 1.0,
|
||||
};
|
||||
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);
|
||||
let t = &mut self.map.t[next];
|
||||
t.hp -= pow;
|
||||
if t.hp <= 0.0 {
|
||||
@@ -588,6 +794,7 @@ impl Game {
|
||||
t.gold = 0;
|
||||
let (x, y) = xy(i);
|
||||
self.fx(3, x as f32 + 0.5, y as f32 + 0.5, 0.0, 0.0, 0.6, 0xff304a66);
|
||||
self.sfx(Sfx::DigDone, x as f32 + 0.5, y as f32 + 0.5);
|
||||
}
|
||||
|
||||
fn unreserve(&mut self, me: u32) {
|
||||
@@ -694,8 +901,10 @@ impl Game {
|
||||
_ => 0xff70b0d0,
|
||||
};
|
||||
self.fx(0, e.x, e.y - 0.3, tx, ty - 0.3, 0.25, col);
|
||||
self.sfx(Sfx::Shoot, e.x, e.y);
|
||||
} else {
|
||||
self.fx(2, tx, ty - 0.2, 0.0, 0.0, 0.25, 0xffffffff);
|
||||
self.sfx(Sfx::MeleeHit, tx, ty);
|
||||
}
|
||||
e.xp += dmg * 0.6;
|
||||
let my_id = e.id;
|
||||
@@ -722,8 +931,10 @@ impl Game {
|
||||
let b = s.bounty as f32;
|
||||
let got = self.add_gold(b);
|
||||
self.fx(5, x, y, 0.0, 0.0, 1.0, 0xff30d0ff);
|
||||
self.sfx(Sfx::HeroDeath, x, y);
|
||||
self.msg(format!("{} besiegt! Beute: {} Gold.", s.name, got as i32));
|
||||
} else {
|
||||
self.sfx(Sfx::Death, x, y);
|
||||
self.msg(format!("Dein {} ist gefallen.", s.name));
|
||||
}
|
||||
}
|
||||
@@ -823,10 +1034,12 @@ impl Game {
|
||||
e.carry += take;
|
||||
done = tl.gold <= 0;
|
||||
self.fx(5, fxx, fxy, 0.0, 0.0, 0.5, 0xff30d0ff);
|
||||
self.sfx(Sfx::GoldDig, fxx, fxy);
|
||||
} else {
|
||||
tl.hp -= 1.0;
|
||||
done = tl.hp <= 0.0;
|
||||
self.fx(6, fxx, fxy, 0.0, 0.0, 0.4, 0xff3a5a7a);
|
||||
self.sfx(Sfx::Dig, fxx, fxy);
|
||||
}
|
||||
if done {
|
||||
self.dig_out(t);
|
||||
@@ -864,6 +1077,7 @@ impl Game {
|
||||
}
|
||||
let (x, y) = xy(t);
|
||||
self.fx(8, x as f32 + 0.5, y as f32 + 0.5, 0.0, 0.0, 0.5, 0xff4040e0);
|
||||
self.sfx(Sfx::Claim, x as f32 + 0.5, y as f32 + 0.5);
|
||||
}
|
||||
}
|
||||
Job::Deliver => {
|
||||
@@ -875,6 +1089,7 @@ impl Game {
|
||||
}
|
||||
if got > 0.0 {
|
||||
self.fx(5, e.x, e.y, 0.0, 0.0, 0.8, 0xff30d0ff);
|
||||
self.sfx(Sfx::Deposit, e.x, e.y);
|
||||
}
|
||||
e.carry = 0;
|
||||
e.job = Job::None;
|
||||
@@ -950,7 +1165,8 @@ impl Game {
|
||||
if e.think <= 0.0 {
|
||||
e.think = 0.4 + self.rng.f() * 0.3;
|
||||
if !matches!(e.job, Job::Attack(_) | Job::Leave) {
|
||||
if let Some(id) = self.nearest_enemy(e, 7.0) {
|
||||
let radius = if e.job == Job::Rally { 8.5 } else { 7.0 };
|
||||
if let Some(id) = self.nearest_enemy(e, radius) {
|
||||
self.unreserve(me);
|
||||
e.job = Job::Attack(id);
|
||||
e.path.clear();
|
||||
@@ -965,6 +1181,10 @@ impl Game {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if e.job == Job::Rally {
|
||||
self.rally(e, dt);
|
||||
return;
|
||||
}
|
||||
if self.step_path(e, dt) {
|
||||
return;
|
||||
}
|
||||
@@ -1060,6 +1280,104 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
fn wants_rally(&self, e: &Ent) -> bool {
|
||||
self.flag.is_some() && e.hp > e.max_hp * 0.5 && e.energy > 0.15 && e.hunger < 1.8 && e.happy > 0.0
|
||||
}
|
||||
|
||||
// Angriffsflagge: zur Flagge marschieren; liegt das Tor in der Nähe, wird es angegriffen.
|
||||
fn rally(&mut self, e: &mut Ent, dt: f32) {
|
||||
let Some(f) = self.flag else {
|
||||
e.job = Job::None;
|
||||
e.path.clear();
|
||||
return;
|
||||
};
|
||||
if e.hp < e.max_hp * 0.3 || e.energy < 0.1 || e.hunger > 2.2 {
|
||||
// erschöpft oder schwer verletzt: zurückziehen
|
||||
e.job = Job::None;
|
||||
e.path.clear();
|
||||
return;
|
||||
}
|
||||
let (fx, fy) = xy(f);
|
||||
let (fx, fy) = (fx as f32 + 0.5, fy as f32 + 0.5);
|
||||
let (gx, gy) = self.gate_center();
|
||||
let gate_near = self.gate_hp > 0.0 && (fx - gx).powi(2) + (fy - gy).powi(2) <= 8.0 * 8.0;
|
||||
let s = st(e.kind);
|
||||
if gate_near {
|
||||
let dg = ((e.x - gx).powi(2) + (e.y - gy).powi(2)).sqrt();
|
||||
if dg <= s.range + 1.6 && (s.range < 1.5 || self.los(e.x, e.y, gx, gy)) {
|
||||
e.path.clear();
|
||||
e.face = sgn(gx - e.x);
|
||||
e.working = true;
|
||||
if e.cd <= 0.0 {
|
||||
self.hit_gate(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
let (target, tx, ty, near) = if gate_near {
|
||||
(idx(gx as i32, gy as i32), gx, gy, s.range + 1.2)
|
||||
} else {
|
||||
(f, fx, fy, 1.5)
|
||||
};
|
||||
let d = ((e.x - tx).powi(2) + (e.y - ty).powi(2)).sqrt();
|
||||
if d <= near && e.path.is_empty() {
|
||||
return; // wartet an der Flagge
|
||||
}
|
||||
if e.path.is_empty() || e.repath <= 0.0 {
|
||||
e.repath = 1.5;
|
||||
match self.map.path_to(e.tile(), target) {
|
||||
Some(p) => e.path = p,
|
||||
None => {
|
||||
self.hint(11, "Die Angriffsflagge ist für deine Kreaturen nicht erreichbar.");
|
||||
e.job = Job::Wander;
|
||||
e.timer = 3.0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.step_path(e, dt);
|
||||
}
|
||||
|
||||
pub fn set_flag(&mut self, x: i32, y: i32) {
|
||||
if !inb(x, y) {
|
||||
return;
|
||||
}
|
||||
let i = idx(x, y);
|
||||
if self.flag == Some(i) {
|
||||
self.flag = None;
|
||||
self.msg("Angriffsflagge entfernt. Deine Kreaturen kehren zurück.".into());
|
||||
self.sfx_ui(Sfx::Click);
|
||||
for e in self.ents.iter_mut() {
|
||||
if e.alive && e.creature() && e.job == Job::Rally {
|
||||
e.job = Job::None;
|
||||
e.path.clear();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if !self.map.t[i].walkable() {
|
||||
self.hint(12, "Die Flagge muss auf begehbarem Boden stehen.");
|
||||
self.sfx_ui(Sfx::Error);
|
||||
return;
|
||||
}
|
||||
self.flag = Some(i);
|
||||
self.sfx(Sfx::FlagSet, x as f32 + 0.5, y as f32 + 0.5);
|
||||
self.msg("Angriffsflagge gesetzt! Kampfbereite Kreaturen sammeln sich dort.".into());
|
||||
let mut ids = Vec::new();
|
||||
for e in self.ents.iter() {
|
||||
if e.alive && !e.held && e.creature() && matches!(e.job, Job::None | Job::Wander | Job::Train(_) | Job::Study(_) | Job::Rally) {
|
||||
ids.push(e.id);
|
||||
}
|
||||
}
|
||||
for id in ids {
|
||||
self.unreserve(id + 1);
|
||||
if let Some(e) = self.ents.iter_mut().find(|e| e.id == id) {
|
||||
e.job = Job::None;
|
||||
e.path.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn creature_choose(&mut self, e: &mut Ent) {
|
||||
let me = e.id + 1;
|
||||
let cur = e.tile();
|
||||
@@ -1104,7 +1422,7 @@ impl Game {
|
||||
e.job = Job::Sleep(cur);
|
||||
return;
|
||||
}
|
||||
if e.hunger > 1.0 {
|
||||
if e.hunger > 1.0 && !(self.flag.is_some() && e.hunger < 1.5) {
|
||||
if let Some(p) = self.find_room_tile(cur, Kind::Farm, me, true) {
|
||||
let t = p.last().copied().unwrap_or(cur);
|
||||
self.map.t[t].reserved = me;
|
||||
@@ -1117,6 +1435,18 @@ impl Game {
|
||||
self.hint(5, "Deine Kreaturen hungern! Vergrößere den Pilzgarten.");
|
||||
}
|
||||
}
|
||||
if self.wants_rally(e) {
|
||||
e.job = Job::Rally;
|
||||
e.path.clear();
|
||||
e.repath = 0.0;
|
||||
return;
|
||||
}
|
||||
if e.hp < e.max_hp * 0.5 && e.energy >= 0.25 && self.counts[1] > 0 {
|
||||
// verletzt: im Quartier ausruhen
|
||||
e.energy = e.energy.min(0.2);
|
||||
self.creature_choose(e);
|
||||
return;
|
||||
}
|
||||
let r = self.rng.f();
|
||||
if (e.kind == EK::Mage && r < 0.8) || r < 0.2 {
|
||||
if let Some(p) = self.find_room_tile(cur, Kind::Library, me, false) {
|
||||
@@ -1172,7 +1502,8 @@ impl Game {
|
||||
}
|
||||
}
|
||||
if !matches!(e.job, Job::Attack(_)) {
|
||||
if let Some(id) = self.nearest_enemy(e, 6.5) {
|
||||
let radius = if e.guard { 8.0 } else { 6.5 };
|
||||
if let Some(id) = self.nearest_enemy(e, radius) {
|
||||
e.job = Job::Attack(id);
|
||||
e.path.clear();
|
||||
e.repath = 0.0;
|
||||
@@ -1180,13 +1511,29 @@ impl Game {
|
||||
}
|
||||
}
|
||||
if let Job::Attack(id) = e.job {
|
||||
if self.attack_job(e, id, dt) {
|
||||
let (gx, gy) = self.gate_center();
|
||||
let far = e.guard && (e.x - gx).powi(2) + (e.y - gy).powi(2) > 14.0 * 14.0;
|
||||
if !far && self.attack_job(e, id, dt) {
|
||||
return;
|
||||
}
|
||||
e.job = Job::AttackCore;
|
||||
e.path.clear();
|
||||
e.think = 1.5;
|
||||
}
|
||||
if e.guard {
|
||||
// Torwächter bleiben am Tor
|
||||
let (gx, gy) = self.gate_center();
|
||||
let d2 = (e.x - gx).powi(2) + (e.y - gy).powi(2);
|
||||
if d2 > 2.5 * 2.5 && e.path.is_empty() && e.repath <= 0.0 {
|
||||
e.repath = 1.0;
|
||||
let target = idx(gx as i32, gy as i32);
|
||||
if let Some(p) = self.map.path_to(e.tile(), target) {
|
||||
e.path = p;
|
||||
}
|
||||
}
|
||||
self.step_path(e, dt);
|
||||
return;
|
||||
}
|
||||
let cur = e.tile();
|
||||
if e.path.is_empty() && self.map.adj_core(cur) {
|
||||
e.working = true;
|
||||
@@ -1198,6 +1545,7 @@ impl Game {
|
||||
let fx = (e.x + cx as f32 + 1.5) * 0.5;
|
||||
let fy = (e.y + cy as f32 + 1.5) * 0.5;
|
||||
self.fx(2, fx, fy, 0.0, 0.0, 0.3, 0xff4040ff);
|
||||
self.sfx(Sfx::CoreHit, fx, fy);
|
||||
self.hint(6, "Dein Seelenkern wird angegriffen!");
|
||||
}
|
||||
return;
|
||||
@@ -1250,11 +1598,15 @@ impl Game {
|
||||
t.bed = 0;
|
||||
built += 1;
|
||||
self.fx(8, x as f32 + 0.5, y as f32 + 0.5, 0.0, 0.0, 0.5, 0xff40d0ff);
|
||||
if built == 1 {
|
||||
self.sfx(Sfx::Build, x as f32 + 0.5, y as f32 + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if broke {
|
||||
self.msg("Nicht genug Gold!".into());
|
||||
self.sfx_ui(Sfx::Error);
|
||||
}
|
||||
if built == 0 && !broke {
|
||||
self.hint(7, "Räume können nur auf eroberten, leeren Bodenfeldern gebaut werden.");
|
||||
@@ -1283,6 +1635,7 @@ impl Game {
|
||||
self.recount();
|
||||
if refund > 0.0 {
|
||||
let got = self.add_gold(refund);
|
||||
self.sfx_ui(Sfx::Sell);
|
||||
self.msg(format!("Räume verkauft: +{} Gold.", got as i32));
|
||||
}
|
||||
}
|
||||
@@ -1317,6 +1670,7 @@ impl Game {
|
||||
e.job = Job::None;
|
||||
e.path.clear();
|
||||
self.hand.push(id);
|
||||
self.sfx_ui(Sfx::Pickup);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
@@ -1343,6 +1697,7 @@ impl Game {
|
||||
e.path.clear();
|
||||
}
|
||||
self.fx(3, wx, wy, 0.0, 0.0, 0.4, 0xff707090);
|
||||
self.sfx(Sfx::Drop, wx, wy);
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1350,9 +1705,11 @@ impl Game {
|
||||
const COST: f32 = 50.0;
|
||||
if self.mana < COST {
|
||||
self.msg("Nicht genug Mana für den Blitz.".into());
|
||||
self.sfx_ui(Sfx::Error);
|
||||
return;
|
||||
}
|
||||
self.mana -= COST;
|
||||
self.sfx(Sfx::Lightning, wx, wy);
|
||||
self.fx(1, wx, wy, 0.0, 0.0, 0.5, 0xffffeeaa);
|
||||
let mut kills = Vec::new();
|
||||
for (j, e) in self.ents.iter_mut().enumerate() {
|
||||
@@ -1373,9 +1730,11 @@ impl Game {
|
||||
const COST: f32 = 60.0;
|
||||
if self.mana < COST {
|
||||
self.msg("Nicht genug Mana für die Heilung.".into());
|
||||
self.sfx_ui(Sfx::Error);
|
||||
return;
|
||||
}
|
||||
self.mana -= COST;
|
||||
self.sfx(Sfx::Heal, wx, wy);
|
||||
let mut fxs = Vec::new();
|
||||
for e in self.ents.iter_mut() {
|
||||
if e.alive && !e.held && !e.hero && (e.x - wx).powi(2) + (e.y - wy).powi(2) < 6.5 {
|
||||
@@ -1398,9 +1757,11 @@ impl Game {
|
||||
}
|
||||
if self.mana < cost {
|
||||
self.msg(format!("Nicht genug Mana ({} benötigt).", cost as i32));
|
||||
self.sfx_ui(Sfx::Error);
|
||||
return;
|
||||
}
|
||||
self.mana -= cost;
|
||||
self.sfx(Sfx::Summon, wx, wy);
|
||||
self.spawn(EK::Worker, false, wx, wy, 1);
|
||||
self.fx(7, wx, wy, 0.0, 0.0, 0.6, 0xffff66cc);
|
||||
}
|
||||
@@ -1432,5 +1793,12 @@ pub fn job_name(e: &Ent) -> &'static str {
|
||||
}
|
||||
}
|
||||
Job::Leave => "Verlässt das Reich",
|
||||
Job::Rally => {
|
||||
if e.working {
|
||||
"Greift das Heldentor an"
|
||||
} else {
|
||||
"Folgt der Angriffsflagge"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user