Merge branch 'juliancoffee/dungeon_rebalance' into 'master'

Make cultist great again

See merge request veloren/veloren!2513
This commit is contained in:
Samuel Keiffer 2021-07-01 02:37:39 +00:00
commit 099ddea165
27 changed files with 645 additions and 420 deletions

View File

@ -7,7 +7,7 @@ RepeaterRanged(
half_speed_at: 1, half_speed_at: 1,
projectile: Arrow( projectile: Arrow(
damage: 30.0, damage: 30.0,
knockback: 5.0, knockback: 2.0,
energy_regen: 0, energy_regen: 0,
), ),
projectile_body: Object(Arrow), projectile_body: Object(Arrow),

View File

@ -2,17 +2,17 @@ ComboMelee(
stage_data: [ stage_data: [
( (
stage: 1, stage: 1,
base_damage: 160, base_damage: 200,
damage_increase: 0, damage_increase: 0,
base_poise_damage: 12, base_poise_damage: 12,
poise_damage_increase: 0, poise_damage_increase: 0,
knockback: 5.0, knockback: 5.0,
range: 3.5, range: 3.5,
angle: 60.0, angle: 60.0,
base_buildup_duration: 0.25, base_buildup_duration: 0.5,
base_swing_duration: 0.07, base_swing_duration: 0.2,
hit_timing: 0.5, hit_timing: 0.5,
base_recover_duration: 0.25, base_recover_duration: 0.5,
forward_movement: 0.5, forward_movement: 0.5,
damage_kind: Crushing, damage_kind: Crushing,
), ),

View File

@ -2,8 +2,8 @@ BasicBeam(
buildup_duration: 0.40, buildup_duration: 0.40,
recover_duration: 0.50, recover_duration: 0.50,
beam_duration: 1.0, beam_duration: 1.0,
damage: 400, damage: 150,
tick_rate: 0.9, tick_rate: 5.0,
range: 22.0, range: 22.0,
max_angle: 15.0, max_angle: 15.0,
damage_effect: Some(Buff(( damage_effect: Some(Buff((
@ -14,7 +14,7 @@ BasicBeam(
))), ))),
energy_regen: 0, energy_regen: 0,
energy_drain: 0, energy_drain: 0,
orientation_behavior: Normal, orientation_behavior: FromOri,
ori_rate: 0.6, ori_rate: 0.2,
specifier: Cultist, specifier: Cultist,
) )

View File

@ -3,10 +3,10 @@ ChargedMelee(
energy_drain: 300, energy_drain: 300,
initial_damage: 10, initial_damage: 10,
scaled_damage: 160, scaled_damage: 160,
initial_poise_damage: 60, initial_poise_damage: 20,
scaled_poise_damage: 130, scaled_poise_damage: 60,
initial_knockback: 10.0, initial_knockback: 5.0,
scaled_knockback: 50.0, scaled_knockback: 20.0,
range: 3.5, range: 3.5,
max_angle: 30.0, max_angle: 30.0,
speed: 1.0, speed: 1.0,

View File

@ -14,7 +14,7 @@ BasicBeam(
))), ))),
energy_regen: 0, energy_regen: 0,
energy_drain: 350, energy_drain: 350,
orientation_behavior: Normal, orientation_behavior: FromOri,
ori_rate: 0.6, ori_rate: 0.3,
specifier: Flamethrower, specifier: Flamethrower,
) )

View File

@ -4,11 +4,11 @@ ItemDef(
kind: Armor(( kind: Armor((
kind: Belt("Cultist"), kind: Belt("Cultist"),
stats: ( stats: (
protection: Normal(6.0), protection: Normal(8.0),
poise_resilience: Normal(0.0), poise_resilience: Normal(1.0),
energy_max: 0, energy_max: 20,
energy_reward: 0.0, energy_reward: 0.025,
crit_power: 0.0, crit_power: 0.02,
stealth: 0.0, stealth: 0.0,
), ),
)), )),
@ -16,4 +16,4 @@ ItemDef(
tags: [ tags: [
Cultist, Cultist,
], ],
) )

View File

@ -4,11 +4,11 @@ ItemDef(
kind: Armor(( kind: Armor((
kind: Chest("Cultist"), kind: Chest("Cultist"),
stats: ( stats: (
protection: Normal(30.0), protection: Normal(48.0),
poise_resilience: Normal(0.0), poise_resilience: Normal(6.0),
energy_max: 0, energy_max: 135,
energy_reward: 0.0, energy_reward: 0.135,
crit_power: 0.0, crit_power: 0.125,
stealth: 0.0, stealth: 0.0,
), ),
)), )),
@ -16,4 +16,4 @@ ItemDef(
tags: [ tags: [
Cultist, Cultist,
], ],
) )

View File

@ -4,11 +4,11 @@ ItemDef(
kind: Armor(( kind: Armor((
kind: Foot("Cultist"), kind: Foot("Cultist"),
stats: ( stats: (
protection: Normal(6.0), protection: Normal(16.0),
poise_resilience: Normal(0.0), poise_resilience: Normal(2.0),
energy_max: 0, energy_max: 45,
energy_reward: 0.0, energy_reward: 0.045,
crit_power: 0.0, crit_power: 0.04,
stealth: 0.0, stealth: 0.0,
), ),
)), )),
@ -16,4 +16,4 @@ ItemDef(
tags: [ tags: [
Cultist, Cultist,
], ],
) )

View File

@ -4,11 +4,11 @@ ItemDef(
kind: Armor(( kind: Armor((
kind: Hand("Cultist"), kind: Hand("Cultist"),
stats: ( stats: (
protection: Normal(12.0), protection: Normal(16.0),
poise_resilience: Normal(0.0), poise_resilience: Normal(2.0),
energy_max: 0, energy_max: 45,
energy_reward: 0.0, energy_reward: 0.045,
crit_power: 0.0, crit_power: 0.04,
stealth: 0.0, stealth: 0.0,
), ),
)), )),
@ -16,4 +16,4 @@ ItemDef(
tags: [ tags: [
Cultist, Cultist,
], ],
) )

View File

@ -4,11 +4,11 @@ ItemDef(
kind: Armor(( kind: Armor((
kind: Pants("Cultist"), kind: Pants("Cultist"),
stats: ( stats: (
protection: Normal(24.0), protection: Normal(32.0),
poise_resilience: Normal(0.0), poise_resilience: Normal(4.0),
energy_max: 0, energy_max: 90,
energy_reward: 0.0, energy_reward: 0.1,
crit_power: 0.0, crit_power: 0.08,
stealth: 0.0, stealth: 0.0,
), ),
)), )),
@ -16,4 +16,4 @@ ItemDef(
tags: [ tags: [
Cultist, Cultist,
], ],
) )

View File

@ -4,11 +4,11 @@ ItemDef(
kind: Armor(( kind: Armor((
kind: Shoulder("Cultist"), kind: Shoulder("Cultist"),
stats: ( stats: (
protection: Normal(18.0), protection: Normal(32.0),
poise_resilience: Normal(0.0), poise_resilience: Normal(5.0),
energy_max: 0, energy_max: 90,
energy_reward: 0.0, energy_reward: 0.1,
crit_power: 0.0, crit_power: 0.08,
stealth: 0.0, stealth: 0.0,
), ),
)), )),
@ -16,4 +16,4 @@ ItemDef(
tags: [ tags: [
Cultist, Cultist,
], ],
) )

View File

@ -4,14 +4,16 @@ ItemDef(
kind: Armor(( kind: Armor((
kind: Back("DungeonPurple"), kind: Back("DungeonPurple"),
stats: ( stats: (
protection: Normal(3.0), protection: Normal(8.0),
poise_resilience: Normal(0.0), poise_resilience: Normal(1.0),
energy_max: 0, energy_max: 20,
energy_reward: 0.0, energy_reward: 0.025,
crit_power: 0.0, crit_power: 0.02,
stealth: 0.0, stealth: 0.0,
), ),
)), )),
quality: Epic, quality: Epic,
tags: [], tags: [
) Cultist,
],
)

View File

@ -0,0 +1,17 @@
ItemDef(
name: "Giant Warlock Chest",
description: "Made of darkest silk.",
kind: Armor((
kind: Chest("GiantWarlock"),
stats: (
protection: Normal(250.0),
poise_resilience: Normal(1.0),
energy_max: 1000,
energy_reward: 1.0,
crit_power: 0.0,
stealth: 0.0,
),
)),
quality: Moderate,
tags: [],
)

View File

@ -0,0 +1,17 @@
ItemDef(
name: "Giant Warlord Chest",
description: "Made of darkest steel.",
kind: Armor((
kind: Chest("GiantWarlord"),
stats: (
protection: Normal(300.0),
poise_resilience: Normal(1.0),
energy_max: 0,
energy_reward: 0.0,
crit_power: 0.0,
stealth: 0.0,
),
)),
quality: Moderate,
tags: [],
)

View File

@ -15,4 +15,4 @@ ItemDef(
quality: Low, quality: Low,
tags: [], tags: [],
ability_spec: Some(Custom("Husk Brute")), ability_spec: Some(Custom("Husk Brute")),
) )

View File

@ -4,5 +4,4 @@
Tree("common.skillset.dungeon.tier-5.axe"), Tree("common.skillset.dungeon.tier-5.axe"),
Tree("common.skillset.dungeon.tier-5.hammer"), Tree("common.skillset.dungeon.tier-5.hammer"),
Tree("common.skillset.dungeon.tier-5.bow"), Tree("common.skillset.dungeon.tier-5.bow"),
Tree("common.skillset.dungeon.tier-5.staff"),
]) ])

View File

@ -1,21 +0,0 @@
([
Group(Weapon(Staff)),
// Fireball
Skill((Staff(BDamage), Some(1))),
Skill((Staff(BRegen), Some(1))),
Skill((Staff(BRadius), Some(1))),
// Flamethrower
Skill((Staff(FRange), Some(1))),
Skill((Staff(FDrain), Some(1))),
Skill((Staff(FDamage), Some(1))),
Skill((Staff(FVelocity), Some(1))),
// Shockwave
Skill((Staff(UnlockShockwave), None)),
Skill((Staff(SDamage), Some(1))),
Skill((Staff(SKnockback), Some(1))),
Skill((Staff(SRange), Some(1))),
Skill((Staff(SCost), Some(1))),
])

View File

@ -13,7 +13,6 @@
// Spin of death // Spin of death
Skill((Sword(UnlockSpin), None)), Skill((Sword(UnlockSpin), None)),
Skill((Sword(SDamage), Some(1))), Skill((Sword(SDamage), Some(2))),
Skill((Sword(SSpins), Some(2))), Skill((Sword(SCost), Some(2))),
Skill((Sword(SCost), Some(1))),
]) ])

View File

@ -0,0 +1,23 @@
([
Group(Weapon(Axe)),
// DoubleStrike
Skill((Axe(DsCombo), None)),
Skill((Axe(DsDamage), Some(3))),
Skill((Axe(DsRegen), Some(2))),
Skill((Axe(DsSpeed), Some(3))),
// Spin
Skill((Axe(SInfinite), None)),
Skill((Axe(SHelicopter), None)),
Skill((Axe(SDamage), Some(3))),
Skill((Axe(SSpeed), Some(2))),
Skill((Axe(SCost), Some(2))),
// Leap
Skill((Axe(UnlockLeap), None)),
Skill((Axe(LDamage), Some(2))),
Skill((Axe(LKnockback), Some(2))),
Skill((Axe(LCost), Some(2))),
Skill((Axe(LDistance), Some(2))),
])

View File

@ -0,0 +1,25 @@
([
Group(Weapon(Bow)),
// Projectile Speed
Skill((Bow(ProjSpeed), Some(2))),
// Charged
Skill((Bow(CDamage), Some(3))),
Skill((Bow(CKnockback), Some(2))),
Skill((Bow(CSpeed), Some(2))),
Skill((Bow(CRegen), Some(2))),
Skill((Bow(CMove), Some(2))),
// Repeater
Skill((Bow(RDamage), Some(3))),
Skill((Bow(RCost), Some(2))),
Skill((Bow(RSpeed), Some(2))),
// Shotgun
Skill((Bow(UnlockShotgun), None)),
Skill((Bow(SDamage), Some(2))),
Skill((Bow(SCost), Some(2))),
Skill((Bow(SArrows), Some(2))),
Skill((Bow(SSpread), Some(2))),
])

View File

@ -0,0 +1,23 @@
([
Group(Weapon(Hammer)),
// Single Strike, as single as you are
Skill((Hammer(SsKnockback), Some(2))),
Skill((Hammer(SsDamage), Some(3))),
Skill((Hammer(SsRegen), Some(2))),
Skill((Hammer(SsSpeed), Some(3))),
// Charged
Skill((Hammer(CDamage), Some(3))),
Skill((Hammer(CKnockback), Some(3))),
Skill((Hammer(CDrain), Some(2))),
Skill((Hammer(CSpeed), Some(2))),
// Leap
Skill((Hammer(UnlockLeap), None)),
Skill((Hammer(LDamage), Some(2))),
Skill((Hammer(LCost), Some(2))),
Skill((Hammer(LDistance), Some(2))),
Skill((Hammer(LKnockback), Some(2))),
Skill((Hammer(LRange), Some(2))),
])

View File

@ -0,0 +1,21 @@
([
Group(Weapon(Staff)),
// Fireball
Skill((Staff(BDamage), Some(3))),
Skill((Staff(BRegen), Some(2))),
Skill((Staff(BRadius), Some(3))),
// Flamethrower
Skill((Staff(FRange), Some(2))),
Skill((Staff(FDamage), Some(3))),
Skill((Staff(FDrain), Some(2))),
Skill((Staff(FVelocity), Some(2))),
// Shockwave
Skill((Staff(UnlockShockwave), None)),
Skill((Staff(SDamage), Some(2))),
Skill((Staff(SKnockback), Some(2))),
Skill((Staff(SRange), Some(2))),
Skill((Staff(SCost), Some(2))),
])

View File

@ -0,0 +1,26 @@
([
Group(Weapon(Sword)),
Skill((Sword(InterruptingAttacks), None)),
// TripleStrike
Skill((Sword(TsCombo), None)),
Skill((Sword(TsDamage), Some(3))),
Skill((Sword(TsRegen), Some(2))),
Skill((Sword(TsSpeed), Some(3))),
// Dash
Skill((Sword(DCost), Some(2))),
Skill((Sword(DDrain), Some(2))),
Skill((Sword(DDamage), Some(2))),
Skill((Sword(DScaling), Some(3))),
Skill((Sword(DSpeed), None)),
Skill((Sword(DInfinite), None)),
// Spin of death
Skill((Sword(UnlockSpin), None)),
Skill((Sword(SDamage), Some(2))),
Skill((Sword(SSpeed), Some(2))),
Skill((Sword(SSpins), Some(2))),
Skill((Sword(SCost), Some(2))),
])

View File

@ -314,6 +314,15 @@
("common.items.armor.ferocious.back",1), ("common.items.armor.ferocious.back",1),
("common.items.weapons.sword.bloodsteel-1",1), ("common.items.weapons.sword.bloodsteel-1",1),
], ],
"cultist": [
("common.items.armor.cultist.chest",1),
("common.items.armor.cultist.pants",1),
("common.items.armor.cultist.hand",1),
("common.items.armor.cultist.foot",1),
("common.items.armor.cultist.shoulder",1),
("common.items.armor.cultist.belt",1),
("common.items.armor.misc.back.dungeon_purple",1),
],
"admin_cosmetics": [ "admin_cosmetics": [
("common.items.debug.cultist_belt",1), ("common.items.debug.cultist_belt",1),
("common.items.debug.cultist_boots",1), ("common.items.debug.cultist_boots",1),

View File

@ -625,6 +625,7 @@ impl Body {
biped_large::Species::Dullahan => 120, biped_large::Species::Dullahan => 120,
biped_large::Species::Huskbrute => 100, biped_large::Species::Huskbrute => 100,
// Boss enemies have their health set, not adjusted by level. // Boss enemies have their health set, not adjusted by level.
biped_large::Species::Huskbrute => 0,
biped_large::Species::Mindflayer => 0, biped_large::Species::Mindflayer => 0,
biped_large::Species::Minotaur => 0, biped_large::Species::Minotaur => 0,
biped_large::Species::Tidalwarrior => 0, biped_large::Species::Tidalwarrior => 0,

View File

@ -161,14 +161,9 @@ fn default_main_tool(body: &Body) -> Item {
_ => None, _ => None,
}, },
Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species { Body::QuadrupedMedium(quadruped_medium) => match quadruped_medium.species {
quadruped_medium::Species::Wolf quadruped_medium::Species::Wolf | quadruped_medium::Species::Grolgar => Some(
| quadruped_medium::Species::Grolgar Item::new_from_asset_expect("common.items.npc_weapons.unique.quadmedquick"),
| quadruped_medium::Species::Lion ),
| quadruped_medium::Species::Bonerattler
| quadruped_medium::Species::Darkhound
| quadruped_medium::Species::Snowleopard => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.quadmedquick",
)),
quadruped_medium::Species::Donkey quadruped_medium::Species::Donkey
| quadruped_medium::Species::Horse | quadruped_medium::Species::Horse
| quadruped_medium::Species::Zebra | quadruped_medium::Species::Zebra
@ -180,7 +175,11 @@ fn default_main_tool(body: &Body) -> Item {
| quadruped_medium::Species::Alpaca => Some(Item::new_from_asset_expect( | quadruped_medium::Species::Alpaca => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.quadmedhoof", "common.items.npc_weapons.unique.quadmedhoof",
)), )),
quadruped_medium::Species::Saber => Some(Item::new_from_asset_expect( quadruped_medium::Species::Saber
| quadruped_medium::Species::Bonerattler
| quadruped_medium::Species::Darkhound
| quadruped_medium::Species::Lion
| quadruped_medium::Species::Snowleopard => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.quadmedjump", "common.items.npc_weapons.unique.quadmedjump",
)), )),
quadruped_medium::Species::Tuskram quadruped_medium::Species::Tuskram
@ -398,88 +397,73 @@ impl LoadoutBuilder {
#[must_use] #[must_use]
/// Set default equipement based on `body` /// Set default equipement based on `body`
pub fn with_default_equipment(mut self, body: &Body) -> Self { pub fn with_default_equipment(self, body: &Body) -> Self {
self = match body { let chest = match body {
Body::BipedLarge(biped_large::Body { Body::BipedLarge(body) => match body.species {
species: biped_large::Species::Mindflayer, biped_large::Species::Mindflayer => {
.. Some("common.items.npc_armor.biped_large.mindflayer")
}) => self.chest(Some(Item::new_from_asset_expect( },
"common.items.npc_armor.biped_large.mindflayer", biped_large::Species::Minotaur => {
))), Some("common.items.npc_armor.biped_large.minotaur")
Body::BipedLarge(biped_large::Body { },
species: biped_large::Species::Minotaur, biped_large::Species::Tidalwarrior => {
.. Some("common.items.npc_armor.biped_large.tidal_warrior")
}) => self.chest(Some(Item::new_from_asset_expect( },
"common.items.npc_armor.biped_large.minotaur", biped_large::Species::Yeti => Some("common.items.npc_armor.biped_large.yeti"),
))), biped_large::Species::Harvester => {
Body::BipedLarge(biped_large::Body { Some("common.items.npc_armor.biped_large.harvester")
species: biped_large::Species::Tidalwarrior, },
.. biped_large::Species::Ogre
}) => self.chest(Some(Item::new_from_asset_expect( | biped_large::Species::Cyclops
"common.items.npc_armor.biped_large.tidal_warrior", | biped_large::Species::Blueoni
))), | biped_large::Species::Redoni
Body::BipedLarge(biped_large::Body { | biped_large::Species::Cavetroll
species: biped_large::Species::Yeti, | biped_large::Species::Wendigo => {
.. Some("common.items.npc_armor.biped_large.generic")
}) => self.chest(Some(Item::new_from_asset_expect( },
"common.items.npc_armor.biped_large.yeti", biped_large::Species::Cultistwarlord => {
))), Some("common.items.npc_armor.biped_large.warlord")
Body::BipedLarge(biped_large::Body { },
species: biped_large::Species::Harvester, biped_large::Species::Cultistwarlock => {
.. Some("common.items.npc_armor.biped_large.warlock")
}) => self.chest(Some(Item::new_from_asset_expect( },
"common.items.npc_armor.biped_large.harvester", _ => None,
))), },
Body::BipedLarge(biped_large::Body { Body::Golem(body) => match body.species {
species: golem::Species::ClayGolem => Some("common.items.npc_armor.golem.claygolem"),
biped_large::Species::Ogre _ => None,
| biped_large::Species::Cyclops },
| biped_large::Species::Blueoni Body::QuadrupedLow(body) => match body.species {
| biped_large::Species::Redoni quadruped_low::Species::Basilisk
| biped_large::Species::Cavetroll | quadruped_low::Species::Asp
| biped_large::Species::Wendigo, | quadruped_low::Species::Lavadrake
.. | quadruped_low::Species::Maneater
}) => self.chest(Some(Item::new_from_asset_expect( | quadruped_low::Species::Rocksnapper
"common.items.npc_armor.biped_large.generic", | quadruped_low::Species::Sandshark => {
))), Some("common.items.npc_armor.quadruped_low.generic")
Body::Golem(golem::Body { },
species: golem::Species::ClayGolem, quadruped_low::Species::Tortoise => {
.. Some("common.items.npc_armor.quadruped_low.shell")
}) => self.chest(Some(Item::new_from_asset_expect( },
"common.items.npc_armor.golem.claygolem", _ => None,
))), },
Body::QuadrupedLow(quadruped_low::Body { Body::Theropod(body) => match body.species {
species: theropod::Species::Archaeos
quadruped_low::Species::Basilisk | theropod::Species::Yale
| quadruped_low::Species::Asp | theropod::Species::Ntouka
| quadruped_low::Species::Lavadrake | theropod::Species::Odonto => Some("common.items.npc_armor.theropod.rugged"),
| quadruped_low::Species::Maneater _ => None,
| quadruped_low::Species::Rocksnapper },
| quadruped_low::Species::Sandshark, _ => None,
..
}) => self.chest(Some(Item::new_from_asset_expect(
"common.items.npc_armor.quadruped_low.generic",
))),
Body::QuadrupedLow(quadruped_low::Body {
species: quadruped_low::Species::Tortoise,
..
}) => self.chest(Some(Item::new_from_asset_expect(
"common.items.npc_armor.quadruped_low.shell",
))),
Body::Theropod(theropod::Body {
species:
theropod::Species::Archaeos
| theropod::Species::Yale
| theropod::Species::Ntouka
| theropod::Species::Odonto,
..
}) => self.chest(Some(Item::new_from_asset_expect(
"common.items.npc_armor.theropod.rugged",
))),
_ => self,
}; };
self // closures can't be used here, because it moves value
#[allow(clippy::option_if_let_else)]
if let Some(chest) = chest {
self.chest(Some(Item::new_from_asset_expect(chest)))
} else {
self
}
} }
#[must_use] #[must_use]

View File

@ -184,18 +184,195 @@ impl Tile {
} }
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum RoomKind {
Peaceful,
Fight,
Boss,
Miniboss,
}
pub struct Room { pub struct Room {
seed: u32, seed: u32,
loot_density: f32, loot_density: f32,
enemy_density: Option<f32>, kind: RoomKind,
miniboss: bool,
boss: bool,
area: Rect<i32, i32>, area: Rect<i32, i32>,
height: i32, height: i32,
pillars: Option<i32>, // Pillars with the given separation pillars: Option<i32>, // Pillars with the given separation
difficulty: u32, difficulty: u32,
} }
impl Room {
fn fill_fight_cell(
&self,
supplement: &mut ChunkSupplement,
dynamic_rng: &mut impl Rng,
tile_wcenter: Vec3<i32>,
wpos2d: Vec2<i32>,
tile_pos: Vec2<i32>,
) {
let enemy_spawn_tile = self.area.center();
// Don't spawn enemies in a pillar
let enemy_tile_is_pillar = self.pillars.map_or(false, |pillar_space| {
enemy_spawn_tile
.map(|e| e.rem_euclid(pillar_space) == 0)
.reduce_and()
});
let enemy_spawn_tile = enemy_spawn_tile + if enemy_tile_is_pillar { 1 } else { 0 };
// Toss mobs in the center of the room
if tile_pos == enemy_spawn_tile && wpos2d == tile_wcenter.xy() {
let entities = match self.difficulty {
0 => enemy_0(dynamic_rng, tile_wcenter),
1 => enemy_1(dynamic_rng, tile_wcenter),
2 => enemy_2(dynamic_rng, tile_wcenter),
3 => enemy_3(dynamic_rng, tile_wcenter),
4 => enemy_4(dynamic_rng, tile_wcenter),
5 => enemy_5(dynamic_rng, tile_wcenter),
_ => enemy_fallback(dynamic_rng, tile_wcenter),
};
for entity in entities {
supplement.add_entity(
entity
.with_level(
dynamic_rng
.gen_range(
(self.difficulty as f32).powf(1.25) + 3.0
..(self.difficulty as f32).powf(1.5) + 4.0,
)
.round() as u16,
)
.with_alignment(comp::Alignment::Enemy),
);
}
} else {
// Turrets
// Turret has 1/5000 chance to spawn per voxel in fight room
if dynamic_rng.gen_range(0..5000) == 0 {
let pos = tile_wcenter.map(|e| e as f32)
+ Vec3::<u32>::iota()
.map(|e| {
(RandomField::new(self.seed.wrapping_add(10 + e))
.get(Vec3::from(tile_pos))
% 32) as i32
- 16
})
.map(|e| e as f32 / 16.0);
let turret =
EntityInfo::at(pos.map(|e| e as f32)).with_alignment(comp::Alignment::Enemy);
match self.difficulty {
3 => {
let turret = turret
.with_body(comp::Body::Object(comp::object::Body::Crossbow))
.with_asset_expect("common.entity.dungeon.tier-3.sentry");
supplement.add_entity(turret);
},
5 => {
let turret = turret
.with_body(comp::Body::Object(comp::object::Body::Crossbow))
.with_asset_expect("common.entity.dungeon.tier-5.turret");
supplement.add_entity(turret);
},
_ => {},
};
}
}
}
fn fill_miniboss_cell(
&self,
supplement: &mut ChunkSupplement,
dynamic_rng: &mut impl Rng,
tile_wcenter: Vec3<i32>,
wpos2d: Vec2<i32>,
tile_pos: Vec2<i32>,
) {
let miniboss_spawn_tile = self.area.center();
// Don't spawn the miniboss in a pillar
let miniboss_tile_is_pillar = self.pillars.map_or(false, |pillar_space| {
miniboss_spawn_tile
.map(|e| e.rem_euclid(pillar_space) == 0)
.reduce_and()
});
let miniboss_spawn_tile = miniboss_spawn_tile + if miniboss_tile_is_pillar { 1 } else { 0 };
if tile_pos == miniboss_spawn_tile && tile_wcenter.xy() == wpos2d {
let entities = match self.difficulty {
0 => mini_boss_0(tile_wcenter),
1 => mini_boss_1(tile_wcenter),
2 => mini_boss_2(tile_wcenter),
3 => mini_boss_3(tile_wcenter),
4 => mini_boss_4(tile_wcenter),
5 => mini_boss_5(dynamic_rng, tile_wcenter),
_ => mini_boss_fallback(tile_wcenter),
};
for entity in entities {
supplement.add_entity(
entity
.with_level(
dynamic_rng
.gen_range(
(self.difficulty as f32).powf(1.25) + 3.0
..(self.difficulty as f32).powf(1.5) + 4.0,
)
.round() as u16
* 5,
)
.with_alignment(comp::Alignment::Enemy),
);
}
}
}
fn fill_boss_cell(
&self,
supplement: &mut ChunkSupplement,
dynamic_rng: &mut impl Rng,
tile_wcenter: Vec3<i32>,
wpos2d: Vec2<i32>,
tile_pos: Vec2<i32>,
) {
let boss_spawn_tile = self.area.center();
// Don't spawn the boss in a pillar
let boss_tile_is_pillar = self.pillars.map_or(false, |pillar_space| {
boss_spawn_tile
.map(|e| e.rem_euclid(pillar_space) == 0)
.reduce_and()
});
let boss_spawn_tile = boss_spawn_tile + if boss_tile_is_pillar { 1 } else { 0 };
if tile_pos == boss_spawn_tile && wpos2d == tile_wcenter.xy() {
let entities = match self.difficulty {
0 => boss_0(tile_wcenter),
1 => boss_1(tile_wcenter),
2 => boss_2(tile_wcenter),
3 => boss_3(tile_wcenter),
4 => boss_4(tile_wcenter),
5 => boss_5(tile_wcenter),
_ => boss_fallback(tile_wcenter),
};
for entity in entities {
supplement.add_entity(
entity
.with_level(
dynamic_rng
.gen_range(
(self.difficulty as f32).powf(1.25) + 3.0
..(self.difficulty as f32).powf(1.5) + 4.0,
)
.round() as u16
* 5,
)
.with_alignment(comp::Alignment::Enemy),
);
}
}
}
}
struct Floor { struct Floor {
tile_offset: Vec2<i32>, tile_offset: Vec2<i32>,
tiles: Grid<Tile>, tiles: Grid<Tile>,
@ -252,9 +429,7 @@ impl Floor {
let upstair_room = this.create_room(Room { let upstair_room = this.create_room(Room {
seed: ctx.rng.gen(), seed: ctx.rng.gen(),
loot_density: 0.0, loot_density: 0.0,
enemy_density: None, kind: RoomKind::Peaceful,
miniboss: false,
boss: false,
area: Rect::from((stair_tile - tile_offset - 1, Extent2::broadcast(3))), area: Rect::from((stair_tile - tile_offset - 1, Extent2::broadcast(3))),
height: STAIR_ROOM_HEIGHT, height: STAIR_ROOM_HEIGHT,
pillars: None, pillars: None,
@ -265,9 +440,7 @@ impl Floor {
this.create_room(Room { this.create_room(Room {
seed: ctx.rng.gen(), seed: ctx.rng.gen(),
loot_density: 0.0, loot_density: 0.0,
enemy_density: Some((0.0002 * difficulty as f32).min(0.001)), // Minions! kind: RoomKind::Boss,
miniboss: false,
boss: true,
area: Rect::from(( area: Rect::from((
new_stair_tile - tile_offset - MAX_WIDTH as i32 - 1, new_stair_tile - tile_offset - MAX_WIDTH as i32 - 1,
Extent2::broadcast(width as i32 * 2 + 1), Extent2::broadcast(width as i32 * 2 + 1),
@ -281,9 +454,7 @@ impl Floor {
let downstair_room = this.create_room(Room { let downstair_room = this.create_room(Room {
seed: ctx.rng.gen(), seed: ctx.rng.gen(),
loot_density: 0.0, loot_density: 0.0,
enemy_density: None, kind: RoomKind::Peaceful,
miniboss: false,
boss: false,
area: Rect::from((new_stair_tile - tile_offset - 1, Extent2::broadcast(3))), area: Rect::from((new_stair_tile - tile_offset - 1, Extent2::broadcast(3))),
height: STAIR_ROOM_HEIGHT, height: STAIR_ROOM_HEIGHT,
pillars: None, pillars: None,
@ -358,23 +529,21 @@ impl Floor {
let mut dynamic_rng = rand::thread_rng(); let mut dynamic_rng = rand::thread_rng();
match dynamic_rng.gen_range(0..5) { match dynamic_rng.gen_range(0..5) {
// Miniboss room
0 => self.create_room(Room { 0 => self.create_room(Room {
seed: ctx.rng.gen(), seed: ctx.rng.gen(),
loot_density: 0.000025 + level as f32 * 0.00015, loot_density: 0.000025 + level as f32 * 0.00015,
enemy_density: None, kind: RoomKind::Miniboss,
miniboss: true,
boss: false,
area, area,
height: ctx.rng.gen_range(15..20), height: ctx.rng.gen_range(15..20),
pillars: Some(ctx.rng.gen_range(2..=4)), pillars: Some(ctx.rng.gen_range(2..=4)),
difficulty: self.difficulty, difficulty: self.difficulty,
}), }),
// Fight room with enemies in it
_ => self.create_room(Room { _ => self.create_room(Room {
seed: ctx.rng.gen(), seed: ctx.rng.gen(),
loot_density: 0.000025 + level as f32 * 0.00015, loot_density: 0.000025 + level as f32 * 0.00015,
enemy_density: Some(0.001 + level as f32 * 0.00006), kind: RoomKind::Fight,
miniboss: false,
boss: false,
area, area,
height: ctx.rng.gen_range(10..15), height: ctx.rng.gen_range(10..15),
pillars: if ctx.rng.gen_range(0..4) == 0 { pillars: if ctx.rng.gen_range(0..4) == 0 {
@ -433,7 +602,6 @@ impl Floor {
} }
} }
#[allow(clippy::match_single_binding)] // TODO: Pending review in #587
fn apply_supplement( fn apply_supplement(
&self, &self,
// NOTE: Used only for dynamic elements like chests and entities! // NOTE: Used only for dynamic elements like chests and entities!
@ -477,141 +645,29 @@ impl Floor {
.map(|e| e.div_euclid(TILE_SIZE) * TILE_SIZE + TILE_SIZE / 2), .map(|e| e.div_euclid(TILE_SIZE) * TILE_SIZE + TILE_SIZE / 2),
); );
let tile_is_pillar = room match room.kind {
.pillars RoomKind::Fight => room.fill_fight_cell(
.map(|pillar_space| { supplement,
tile_pos dynamic_rng,
.map(|e| e.rem_euclid(pillar_space) == 0) tile_wcenter,
.reduce_and() wpos2d,
}) tile_pos,
.unwrap_or(false); ),
RoomKind::Miniboss => room.fill_miniboss_cell(
if room supplement,
.enemy_density dynamic_rng,
.map(|density| dynamic_rng.gen_range(0..density.recip() as usize) == 0) tile_wcenter,
.unwrap_or(false) wpos2d,
&& !tile_is_pillar tile_pos,
&& !room.boss ),
{ RoomKind::Boss => room.fill_boss_cell(
// Randomly displace them a little supplement,
let raw_entity = EntityInfo::at( dynamic_rng,
tile_wcenter.map(|e| e as f32) tile_wcenter,
+ Vec3::<u32>::iota() wpos2d,
.map(|e| { tile_pos,
(RandomField::new(room.seed.wrapping_add(10 + e)) ),
.get(Vec3::from(tile_pos)) RoomKind::Peaceful => {},
% 32) as i32
- 16
})
.map(|e| e as f32 / 16.0),
);
let entity = match room.difficulty {
0 => enemy_0(dynamic_rng, raw_entity),
1 => enemy_1(dynamic_rng, raw_entity),
2 => enemy_2(dynamic_rng, raw_entity),
3 => enemy_3(dynamic_rng, raw_entity),
4 => enemy_4(dynamic_rng, raw_entity),
5 => enemy_5(dynamic_rng, raw_entity),
_ => enemy_fallback(raw_entity),
};
supplement.add_entity(
entity.with_alignment(comp::Alignment::Enemy).with_level(
dynamic_rng
.gen_range(
(room.difficulty as f32).powf(1.25) + 3.0
..(room.difficulty as f32).powf(1.5) + 4.0,
)
.round() as u16,
),
);
}
if room.boss {
let boss_spawn_tile = room.area.center();
// Don't spawn the boss in a pillar
let boss_tile_is_pillar = room
.pillars
.map(|pillar_space| {
boss_spawn_tile
.map(|e| e.rem_euclid(pillar_space) == 0)
.reduce_and()
})
.unwrap_or(false);
let boss_spawn_tile =
boss_spawn_tile + if boss_tile_is_pillar { 1 } else { 0 };
if tile_pos == boss_spawn_tile && tile_wcenter.xy() == wpos2d {
let entities = match room.difficulty {
0 => boss_0(tile_wcenter),
1 => boss_1(tile_wcenter),
2 => boss_2(tile_wcenter),
3 => boss_3(tile_wcenter),
4 => boss_4(tile_wcenter),
5 => boss_5(tile_wcenter),
_ => boss_fallback(tile_wcenter),
};
for entity in entities {
supplement.add_entity(
entity
.with_level(
dynamic_rng
.gen_range(
(room.difficulty as f32).powf(1.25) + 3.0
..(room.difficulty as f32).powf(1.5) + 4.0,
)
.round()
as u16
* 5,
)
.with_alignment(comp::Alignment::Enemy),
);
}
}
}
if room.miniboss {
let miniboss_spawn_tile = room.area.center();
// Don't spawn the miniboss in a pillar
let miniboss_tile_is_pillar = room
.pillars
.map(|pillar_space| {
miniboss_spawn_tile
.map(|e| e.rem_euclid(pillar_space) == 0)
.reduce_and()
})
.unwrap_or(false);
let miniboss_spawn_tile =
miniboss_spawn_tile + if miniboss_tile_is_pillar { 1 } else { 0 };
if tile_pos == miniboss_spawn_tile && tile_wcenter.xy() == wpos2d {
let entities = match room.difficulty {
0 => mini_boss_0(tile_wcenter),
1 => mini_boss_1(tile_wcenter),
2 => mini_boss_2(tile_wcenter),
3 => mini_boss_3(tile_wcenter),
4 => mini_boss_4(tile_wcenter),
5 => mini_boss_5(dynamic_rng, tile_wcenter),
_ => mini_boss_fallback(tile_wcenter),
};
for entity in entities {
supplement.add_entity(
entity
.with_level(
dynamic_rng
.gen_range(
(room.difficulty as f32).powf(1.25) + 3.0
..(room.difficulty as f32).powf(1.5) + 4.0,
)
.round()
as u16
* 5,
)
.with_alignment(comp::Alignment::Enemy),
);
}
}
} }
} }
} }
@ -631,61 +687,105 @@ impl Floor {
} }
} }
fn enemy_0(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { fn enemy_0(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
match dynamic_rng.gen_range(0..5) { let number = dynamic_rng.gen_range(2..=4);
0 => entity.with_asset_expect("common.entity.dungeon.tier-0.bow"), let mut entities = Vec::new();
1 => entity.with_asset_expect("common.entity.dungeon.tier-0.staff"), entities.resize_with(number, || {
_ => entity.with_asset_expect("common.entity.dungeon.tier-0.spear"), let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
} match dynamic_rng.gen_range(0..=4) {
0 => entity.with_asset_expect("common.entity.dungeon.tier-0.bow"),
1 => entity.with_asset_expect("common.entity.dungeon.tier-0.staff"),
_ => entity.with_asset_expect("common.entity.dungeon.tier-0.spear"),
}
});
entities
} }
fn enemy_1(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { fn enemy_1(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
match dynamic_rng.gen_range(0..5) { let number = dynamic_rng.gen_range(2..=4);
0 => entity.with_asset_expect("common.entity.dungeon.tier-1.bow"), let mut entities = Vec::new();
1 => entity.with_asset_expect("common.entity.dungeon.tier-1.staff"), entities.resize_with(number, || {
_ => entity.with_asset_expect("common.entity.dungeon.tier-1.spear"), let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
} match dynamic_rng.gen_range(0..=4) {
0 => entity.with_asset_expect("common.entity.dungeon.tier-1.bow"),
1 => entity.with_asset_expect("common.entity.dungeon.tier-1.staff"),
_ => entity.with_asset_expect("common.entity.dungeon.tier-1.spear"),
}
});
entities
} }
fn enemy_2(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { fn enemy_2(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
match dynamic_rng.gen_range(0..5) { let number = dynamic_rng.gen_range(2..=4);
0 => entity.with_asset_expect("common.entity.dungeon.tier-2.bow"), let mut entities = Vec::new();
1 => entity.with_asset_expect("common.entity.dungeon.tier-2.staff"), entities.resize_with(number, || {
_ => entity.with_asset_expect("common.entity.dungeon.tier-2.spear"), let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
} match dynamic_rng.gen_range(0..=4) {
0 => entity.with_asset_expect("common.entity.dungeon.tier-2.bow"),
1 => entity.with_asset_expect("common.entity.dungeon.tier-2.staff"),
_ => entity.with_asset_expect("common.entity.dungeon.tier-2.spear"),
}
});
entities
} }
fn enemy_3(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { fn enemy_3(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
match dynamic_rng.gen_range(0..5) { let number = dynamic_rng.gen_range(2..=4);
0 => entity let mut entities = Vec::new();
.with_body(comp::Body::Object(comp::object::Body::HaniwaSentry)) entities.resize_with(number, || {
.with_asset_expect("common.entity.dungeon.tier-3.sentry"), let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
1 => entity.with_asset_expect("common.entity.dungeon.tier-3.bow"), match dynamic_rng.gen_range(0..=4) {
2 => entity.with_asset_expect("common.entity.dungeon.tier-3.staff"), 0 => entity.with_asset_expect("common.entity.dungeon.tier-3.bow"),
_ => entity.with_asset_expect("common.entity.dungeon.tier-3.spear"), 1 => entity.with_asset_expect("common.entity.dungeon.tier-3.staff"),
} _ => entity.with_asset_expect("common.entity.dungeon.tier-3.spear"),
} }
fn enemy_4(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { });
match dynamic_rng.gen_range(0..5) {
0 => entity.with_asset_expect("common.entity.dungeon.tier-4.bow"), entities
1 => entity.with_asset_expect("common.entity.dungeon.tier-4.staff"),
_ => entity.with_asset_expect("common.entity.dungeon.tier-4.spear"),
}
} }
fn enemy_5(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo { fn enemy_4(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
match dynamic_rng.gen_range(0..6) { let number = dynamic_rng.gen_range(2..=4);
0 => entity let mut entities = Vec::new();
.with_body(comp::Body::Object(comp::object::Body::Crossbow)) entities.resize_with(number, || {
.with_asset_expect("common.entity.dungeon.tier-5.turret"), let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
1 => entity.with_asset_expect("common.entity.dungeon.tier-5.warlock"), match dynamic_rng.gen_range(0..=4) {
2 => entity.with_asset_expect("common.entity.dungeon.tier-5.warlord"), 0 => entity.with_asset_expect("common.entity.dungeon.tier-4.bow"),
_ => entity.with_asset_expect("common.entity.dungeon.tier-5.cultist"), 1 => entity.with_asset_expect("common.entity.dungeon.tier-4.staff"),
} _ => entity.with_asset_expect("common.entity.dungeon.tier-4.spear"),
}
});
entities
} }
fn enemy_fallback(entity: EntityInfo) -> EntityInfo { fn enemy_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
entity.with_asset_expect("common.entity.dungeon.fallback.enemy") let number = dynamic_rng.gen_range(1..=3);
let mut entities = Vec::new();
entities.resize_with(number, || {
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
match dynamic_rng.gen_range(0..=4) {
0 => entity.with_asset_expect("common.entity.dungeon.tier-5.warlock"),
1 => entity.with_asset_expect("common.entity.dungeon.tier-5.warlord"),
_ => entity.with_asset_expect("common.entity.dungeon.tier-5.cultist"),
}
});
entities
}
fn enemy_fallback(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
let number = dynamic_rng.gen_range(2..=4);
let mut entities = Vec::new();
entities.resize_with(number, || {
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32));
entity.with_asset_expect("common.entity.dungeon.fallback.enemy")
});
entities
} }
fn boss_0(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> { fn boss_0(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
@ -782,7 +882,7 @@ fn mini_boss_4(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> { fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
let mut entities = Vec::new(); let mut entities = Vec::new();
match dynamic_rng.gen_range(0..3) { match dynamic_rng.gen_range(0..=2) {
0 => { 0 => {
entities.push( entities.push(
EntityInfo::at(tile_wcenter.map(|e| e as f32)) EntityInfo::at(tile_wcenter.map(|e| e as f32))
@ -816,51 +916,6 @@ fn mini_boss_fallback(tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
] ]
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_creating_bosses() {
let tile_wcenter = Vec3::new(0, 0, 0);
boss_0(tile_wcenter);
boss_1(tile_wcenter);
boss_2(tile_wcenter);
boss_3(tile_wcenter);
boss_4(tile_wcenter);
boss_5(tile_wcenter);
boss_fallback(tile_wcenter);
}
#[test]
// FIXME: Uses random, test may be not great
fn test_creating_enemies() {
let mut dynamic_rng = rand::thread_rng();
let raw_entity = EntityInfo::at(Vec3::new(0.0, 0.0, 0.0));
enemy_0(&mut dynamic_rng, raw_entity.clone());
enemy_1(&mut dynamic_rng, raw_entity.clone());
enemy_2(&mut dynamic_rng, raw_entity.clone());
enemy_3(&mut dynamic_rng, raw_entity.clone());
enemy_4(&mut dynamic_rng, raw_entity.clone());
enemy_5(&mut dynamic_rng, raw_entity.clone());
enemy_fallback(raw_entity);
}
#[test]
// FIXME: Uses random, test may be not great
fn test_creating_minibosses() {
let mut dynamic_rng = rand::thread_rng();
let tile_wcenter = Vec3::new(0, 0, 0);
mini_boss_0(tile_wcenter);
mini_boss_1(tile_wcenter);
mini_boss_2(tile_wcenter);
mini_boss_3(tile_wcenter);
mini_boss_4(tile_wcenter);
mini_boss_5(&mut dynamic_rng, tile_wcenter);
mini_boss_fallback(tile_wcenter);
}
}
pub fn tilegrid_nearest_wall(tiles: &Grid<Tile>, rpos: Vec2<i32>) -> Option<Vec2<i32>> { pub fn tilegrid_nearest_wall(tiles: &Grid<Tile>, rpos: Vec2<i32>) -> Option<Vec2<i32>> {
let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE)); let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
@ -1222,7 +1277,7 @@ impl Floor {
prim(Primitive::Scale(pillar, Vec2::broadcast(scale).with_z(1.0))); prim(Primitive::Scale(pillar, Vec2::broadcast(scale).with_z(1.0)));
lights = prim(Primitive::And(lighting_plane, lights)); lights = prim(Primitive::And(lighting_plane, lights));
// Only add the base (and shift the lights up) for boss-room pillars // Only add the base (and shift the lights up) for boss-room pillars
if room.boss { if room.kind == RoomKind::Boss {
lights = prim(Primitive::Translate(lights, 3 * Vec3::unit_z())); lights = prim(Primitive::Translate(lights, 3 * Vec3::unit_z()));
pillar = prim(Primitive::Or(pillar, base)); pillar = prim(Primitive::Or(pillar, base));
} }
@ -1231,7 +1286,7 @@ impl Floor {
} }
// Keep track of the boss room to be able to add decorations later // Keep track of the boss room to be able to add decorations later
if room.boss { if room.kind == RoomKind::Boss {
boss_room_center = boss_room_center =
Some(floor_corner + TILE_SIZE * room.area.center() + TILE_SIZE / 2); Some(floor_corner + TILE_SIZE * room.area.center() + TILE_SIZE / 2);
} }
@ -1334,3 +1389,48 @@ impl SiteStructure for Dungeon {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_creating_bosses() {
let tile_wcenter = Vec3::new(0, 0, 0);
boss_0(tile_wcenter);
boss_1(tile_wcenter);
boss_2(tile_wcenter);
boss_3(tile_wcenter);
boss_4(tile_wcenter);
boss_5(tile_wcenter);
boss_fallback(tile_wcenter);
}
#[test]
// FIXME: Uses random, test may be not great
fn test_creating_enemies() {
let mut dynamic_rng = rand::thread_rng();
let random_position = Vec3::new(0, 0, 0);
enemy_0(&mut dynamic_rng, random_position);
enemy_1(&mut dynamic_rng, random_position);
enemy_2(&mut dynamic_rng, random_position);
enemy_3(&mut dynamic_rng, random_position);
enemy_4(&mut dynamic_rng, random_position);
enemy_5(&mut dynamic_rng, random_position);
enemy_fallback(&mut dynamic_rng, random_position);
}
#[test]
// FIXME: Uses random, test may be not great
fn test_creating_minibosses() {
let mut dynamic_rng = rand::thread_rng();
let tile_wcenter = Vec3::new(0, 0, 0);
mini_boss_0(tile_wcenter);
mini_boss_1(tile_wcenter);
mini_boss_2(tile_wcenter);
mini_boss_3(tile_wcenter);
mini_boss_4(tile_wcenter);
mini_boss_5(&mut dynamic_rng, tile_wcenter);
mini_boss_fallback(tile_wcenter);
}
}