Merge branch 'new_cultist' into 'master'

new_cultist

See merge request veloren/veloren!4447
This commit is contained in:
flo 2024-05-12 18:33:11 +00:00
commit 5e9cbdf754
41 changed files with 1318 additions and 125 deletions

View File

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added reworked Cultist Dungeon
- Petting animals tamed by you or someone else!
- Updated furniture sprites.
- Added Abyssal rings

View File

@ -477,6 +477,13 @@
secondary: Simple(None, "common.abilities.custom.quadmedbasic.triplestrike"),
abilities: [],
),
Custom("Darkhound"): (
primary: Simple(None, "common.abilities.custom.darkhound.leap"),
secondary: Simple(None, "common.abilities.custom.darkhound.doublestrike"),
abilities: [
Simple(None, "common.abilities.custom.darkhound.quickleap"),
],
),
Custom("Frostfang"): (
primary: Simple(None, "common.abilities.custom.frostfang.singlestrike"),
secondary: Simple(None, "common.abilities.custom.frostfang.triplestrike"),
@ -1124,4 +1131,24 @@
secondary: Simple(None, "common.abilities.custom.jiangshi.teleport"),
abilities: [],
),
Custom("BipedLargeCultistStaff"): (
primary: Simple(None, "common.abilities.custom.biped_large_cultist.staff.firebomb"),
secondary: Simple(None, "common.abilities.custom.biped_large_cultist.staff.flamethrower"),
abilities: [],
),
Custom("BipedLargeCultistSword"): (
primary: Simple(None, "common.abilities.custom.biped_large_cultist.sword.doublestrike"),
secondary: Simple(None, "common.abilities.custom.biped_large_cultist.sword.dash"),
abilities: [],
),
Custom("BipedLargeCultistHammer"): (
primary: Simple(None, "common.abilities.custom.biped_large_cultist.hammer.doublestrike"),
secondary: Simple(None, "common.abilities.custom.biped_large_cultist.hammer.doublestrike"),
abilities: [],
),
Custom("BipedLargeCultistBow"): (
primary: Simple(None, "common.abilities.custom.biped_large_cultist.bow.basic"),
secondary: Simple(None, "common.abilities.custom.biped_large_cultist.bow.basic"),
abilities: [],
),
})

View File

@ -0,0 +1,16 @@
BasicRanged(
energy_cost: 0,
buildup_duration: 0.9,
recover_duration: 0.3,
projectile: Arrow(
damage: 12.0,
knockback: 5.0,
energy_regen: 4.0,
),
projectile_body: Object(Arrow),
projectile_light: None,
projectile_speed: 100.0,
num_projectiles: 1,
projectile_spread: 0.0,
move_efficiency: 0.3,
)

View File

@ -0,0 +1,45 @@
ComboMelee2(
strikes: [
(
melee_constructor: (
kind: Bash(
damage: 16,
poise: 40,
knockback: 4,
energy_regen: 0,
),
range: 4.5,
angle: 50.0,
),
buildup_duration: 1.0,
swing_duration: 0.28,
hit_timing: 0.5,
recover_duration: 1.0,
movement: (
swing: Some(Forward(0.5)),
),
ori_modifier: 0.65,
),
(
melee_constructor: (
kind: Bash(
damage: 32,
poise: 40,
knockback: 16,
energy_regen: 0,
),
range: 2.5,
angle: 30.0,
),
buildup_duration: 0.6,
swing_duration: 0.25,
hit_timing: 0.5,
recover_duration: 1.2,
movement: (
swing: Some(Forward(0.5)),
),
ori_modifier: 0.65,
),
],
energy_cost_per_strike: 0,
)

View File

@ -0,0 +1,20 @@
BasicRanged(
energy_cost: 0,
buildup_duration: 1.0,
recover_duration: 1.35,
projectile: Fireball(
damage: 15.0,
radius: 5.0,
energy_regen: 5.0,
min_falloff: 0.5,
),
projectile_body: Object(BoltFire),
/*projectile_light: Some(LightEmitter {
col: (1.0, 0.75, 0.11).into(),
..Default::default()
}),*/
projectile_speed: 60.0,
num_projectiles: 1,
projectile_spread: 0.0,
move_efficiency: 0.3,
)

View File

@ -0,0 +1,19 @@
BasicBeam(
buildup_duration: 0.5,
recover_duration: 0.5,
beam_duration: 1.0,
damage: 3.5,
tick_rate: 3.0,
range: 20.0,
max_angle: 15.0,
damage_effect: Some(Buff((
kind: Burning,
dur_secs: 10.0,
strength: DamageFraction(0.5),
chance: 0.25,
))),
energy_regen: 0,
energy_drain: 35.0,
ori_rate: 0.3,
specifier: Flamethrower,
)

View File

@ -0,0 +1,28 @@
DashMelee(
energy_cost: 10.0,
melee_constructor: (
kind: Stab(
damage: 9.0,
poise: 40.0,
knockback: 8.0,
energy_regen: 0.0,
),
scaled: Some((
kind: Stab(
damage: 16.0,
poise: 0.0,
knockback: 7.0,
energy_regen: 0.0,
))),
range: 5.0,
angle: 45.0,
),
energy_drain: 0,
forward_speed: 4.0,
buildup_duration: 0.6,
charge_duration: 1.2,
swing_duration: 0.1,
recover_duration: 0.9,
ori_modifier: 0.3,
auto_charge: false,
)

View File

@ -0,0 +1,57 @@
ComboMelee2(
strikes: [
(
melee_constructor: (
kind: Slash(
damage: 16,
poise: 15,
knockback: 8,
energy_regen: 0,
),
range: 3.5,
angle: 50.0,
damage_effect: Some(Buff((
kind: Bleeding,
dur_secs: 10.0,
strength: DamageFraction(0.1),
chance: 0.1,
))),
),
buildup_duration: 0.8,
swing_duration: 0.18,
hit_timing: 0.5,
recover_duration: 0.9,
movement: (
swing: Some(Forward(0.5)),
),
ori_modifier: 0.7,
),
(
melee_constructor: (
kind: Slash(
damage: 32,
poise: 20,
knockback: 12,
energy_regen: 0,
),
range: 3.5,
angle: 30.0,
damage_effect: Some(Buff((
kind: Bleeding,
dur_secs: 10.0,
strength: DamageFraction(0.1),
chance: 0.1,
))),
),
buildup_duration: 0.7,
swing_duration: 0.1,
hit_timing: 0.5,
recover_duration: 0.7,
movement: (
swing: Some(Forward(0.5)),
),
ori_modifier: 0.7,
),
],
energy_cost_per_strike: 0,
)

View File

@ -0,0 +1,45 @@
ComboMelee2(
strikes: [
(
melee_constructor: (
kind: Bash(
damage: 16,
poise: 15,
knockback: 4,
energy_regen: 0,
),
range: 2.2,
angle: 30.0,
),
buildup_duration: 0.7,
swing_duration: 0.5,
hit_timing: 0.5,
recover_duration: 0.6,
movement: (
swing: Some(Forward(0.2)),
),
ori_modifier: 0.8,
),
(
melee_constructor: (
kind: Bash(
damage: 16,
poise: 15,
knockback: 4,
energy_regen: 0,
),
range: 2.2,
angle: 30.0,
),
buildup_duration: 0.85,
swing_duration: 0.45,
hit_timing: 0.5,
recover_duration: 0.6,
movement: (
swing: Some(Forward(0.4)),
),
ori_modifier: 0.8,
),
],
energy_cost_per_strike: 0,
)

View File

@ -0,0 +1,21 @@
LeapMelee(
energy_cost: 0,
buildup_duration: 0.95,
movement_duration: 0.8,
swing_duration: 0.55,
recover_duration: 0.4,
melee_constructor: (
kind: Bash(
damage: 36.0,
poise: 30.0,
knockback: 4.0,
energy_regen: 0.0,
),
range: 6.75,
angle: 180.0,
multi_target: Some(Normal),
),
forward_leap_strength: 45.0,
vertical_leap_strength: 10.0,
specifier: None,
)

View File

@ -0,0 +1,21 @@
LeapMelee(
energy_cost: 0,
buildup_duration: 0.5,
movement_duration: 0.6,
swing_duration: 0.3,
recover_duration: 0.25,
melee_constructor: (
kind: Bash(
damage: 16.0,
poise: 15.0,
knockback: 2.0,
energy_regen: 0.0,
),
range: 4.5,
angle: 180.0,
multi_target: Some(Normal),
),
forward_leap_strength: 20.0,
vertical_leap_strength: 5.0,
specifier: None,
)

View File

@ -11,8 +11,8 @@ ComboMelee2(
range: 2.5,
angle: 60.0,
),
buildup_duration: 0.5,
swing_duration: 0.07,
buildup_duration: 1.2,
swing_duration: 0.27,
hit_timing: 0.5,
recover_duration: 0.5,
movement: (

View File

@ -1,8 +1,8 @@
BasicBeam(
buildup_duration: 0.3,
buildup_duration: 0.9,
recover_duration: 1.0,
beam_duration: 1.0,
damage: 20.0,
damage: 12.0,
tick_rate: 5.0,
range: 22.0,
max_angle: 15.0,

View File

@ -1,5 +1,5 @@
RapidMelee(
buildup_duration: 1.2,
buildup_duration: 1.8,
swing_duration: 0.5,
recover_duration: 1.2,
melee_constructor: (

View File

@ -2673,6 +2673,9 @@
Simple(
"common.items.npc_weapons.unique.quadsmallbasic",
): "common-items-npc_weapons-unique-quadsmallbasic",
Simple(
"common.items.npc_weapons.unique.darkhound",
): "common-items-npc_weapons-unique-darkhound",
Simple(
"common.items.npc_weapons.unique.roshwalr",
): "common-items-npc_weapons-unique-roshwalr",

View File

@ -16,5 +16,5 @@ ItemDef(
)),
quality: Epic,
tags: [],
ability_spec: Some(Custom("Bow Simple")),
ability_spec: Some(Custom("BipedLargeCultistBow")),
)

View File

@ -16,5 +16,5 @@ ItemDef(
)),
quality: Epic,
tags: [],
ability_spec: Some(Custom("Hammer Simple")),
ability_spec: Some(Custom("BipedLargeCultistHammer")),
)

View File

@ -16,5 +16,5 @@ ItemDef(
)),
quality: Epic,
tags: [],
ability_spec: Some(Custom("Staff Simple")),
ability_spec: Some(Custom("BipedLargeCultistStaff")),
)

View File

@ -16,5 +16,5 @@ ItemDef(
)),
quality: Epic,
tags: [],
ability_spec: Some(Custom("Sword Simple")),
ability_spec: Some(Custom("BipedLargeCultistSword")),
)

View File

@ -0,0 +1,20 @@
ItemDef(
legacy_name: "Quad Med Jump",
legacy_description: "testing123",
kind: Tool((
kind: Natural,
hands: Two,
stats: (
equip_time_secs: 0.01,
power: 1.0,
effect_power: 1.0,
speed: 1.0,
range: 1.0,
energy_efficiency: 1.0,
buff_strength: 1.0,
),
)),
quality: Low,
tags: [],
ability_spec: Some(Custom("Darkhound")),
)

View File

@ -36,5 +36,6 @@ hud-map-gnarling = Gnarling Fortification
hud-map-chapel_site = Sea Chapel
hud-map-adlet = Adlet Stronghold
hud-map-haniwa = Haniwa Catacomb
hud-map-cultist = Cultist Dungeon
hud-map-terracotta = Terracotta Ruins
hud-map-placed_by = Placed by { $name }

View File

@ -280,6 +280,9 @@ common-items-npc_weapons-unique-quadmedquick = Quad Med Quick
common-items-npc_weapons-unique-quadsmallbasic = Quad Small Basic
.desc = testing123
common-items-npc_weapons-unique-darkhound = Darkhound
.desc = testing123
common-items-npc_weapons-unique-roshwalr = Roshwalr
.desc = testing123

View File

@ -18,5 +18,4 @@
([
(2, 0.20),
(4, 0.10),
(5, 0.10),
])

View File

@ -154,6 +154,7 @@ pub enum SiteKind {
Adlet,
Haniwa,
DwarvenMine,
Cultist,
}
#[derive(Debug, Clone, Serialize, Deserialize)]

View File

@ -798,6 +798,7 @@ impl Body {
match self {
Body::BipedLarge(biped_large) => match biped_large.species {
biped_large::Species::Dullahan => 400,
biped_large::Species::Cultistwarlord | biped_large::Species::Cultistwarlock => 240,
_ => 300,
},
Body::BirdLarge(body) => match body.species {
@ -853,6 +854,7 @@ impl Body {
// T1
quadruped_medium::Species::Alpaca => 55,
quadruped_medium::Species::Antelope => 70,
quadruped_medium::Species::Darkhound => 80,
quadruped_medium::Species::Camel => 100,
quadruped_medium::Species::Cattle => 90,
quadruped_medium::Species::Deer => 55,
@ -889,7 +891,6 @@ impl Body {
quadruped_medium::Species::Ngoubou => 590,
quadruped_medium::Species::Roshwalr => 640,
quadruped_medium::Species::Tarasque => 370,
_ => 100,
},
Body::FishMedium(fish_medium) => match fish_medium.species {
// T2
@ -943,7 +944,7 @@ impl Body {
biped_large::Species::Mountaintroll => 240,
biped_large::Species::Swamptroll => 240,
biped_large::Species::Dullahan => 600,
biped_large::Species::Mindflayer => 1250,
biped_large::Species::Mindflayer => 2000,
biped_large::Species::Tidalwarrior => 1600,
biped_large::Species::Yeti => 1800,
biped_large::Species::Minotaur => 3000,
@ -951,8 +952,8 @@ impl Body {
biped_large::Species::Blueoni => 240,
biped_large::Species::Redoni => 240,
biped_large::Species::Huskbrute => 800,
biped_large::Species::Cultistwarlord => 250,
biped_large::Species::Cultistwarlock => 250,
biped_large::Species::Cultistwarlord => 200,
biped_large::Species::Cultistwarlock => 200,
biped_large::Species::Gigasfrost => 30000,
biped_large::Species::AdletElder => 1500,
biped_large::Species::Tursus => 300,

View File

@ -599,11 +599,13 @@ fn default_main_tool(body: &Body) -> Item {
)),
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",
)),
quadruped_medium::Species::Darkhound => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.darkhound",
)),
// Below uniques still follow quadmedcharge just with stat alterations
quadruped_medium::Species::Moose | quadruped_medium::Species::Tuskram => {
Some(Item::new_from_asset_expect(

View File

@ -18,6 +18,7 @@ pub enum DungeonKindMeta {
Haniwa,
SeaChapel,
Terracotta,
Cultist,
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)]

View File

@ -32,6 +32,7 @@ impl Site {
| SiteKind::ChapelSite(_)
| SiteKind::Terracotta(_)
| SiteKind::Gnarling(_)
| SiteKind::Cultist(_)
| SiteKind::PirateHideout(_)
| SiteKind::JungleRuin(_)
| SiteKind::RockCircle(_)

View File

@ -1076,17 +1076,20 @@ impl<'a> AgentData<'a> {
if let Some(ability_spec) = item.ability_spec() {
match &*ability_spec {
AbilitySpec::Custom(spec) => match spec.as_str() {
"Oni" | "Sword Simple" => Tactic::SwordSimple,
"Staff Simple" => Tactic::Staff,
"Oni" | "Sword Simple" | "BipedLargeCultistSword" => {
Tactic::SwordSimple
},
"Staff Simple" | "BipedLargeCultistStaff" => Tactic::Staff,
"BipedLargeCultistHammer" => Tactic::Hammer,
"Simple Flying Melee" => Tactic::SimpleFlyingMelee,
"Bow Simple" | "Boreal Bow" => Tactic::Bow,
"Bow Simple" | "Boreal Bow" | "BipedLargeCultistBow" => Tactic::Bow,
"Stone Golem" | "Coral Golem" => Tactic::StoneGolem,
"Iron Golem" => Tactic::IronGolem,
"Quad Med Quick" => Tactic::CircleCharge {
radius: 3,
circle_time: 2,
},
"Quad Med Jump" => Tactic::QuadMedJump,
"Quad Med Jump" | "Darkhound" => Tactic::QuadMedJump,
"Quad Med Charge" => Tactic::CircleCharge {
radius: 6,
circle_time: 1,

View File

@ -114,7 +114,8 @@ impl Animation for ComboAnimation {
},
Some(
"common.abilities.custom.cyclops.doublestrike"
| "common.abilities.hammersimple.doublestrike",
| "common.abilities.hammersimple.doublestrike"
| "common.abilities.custom.biped_large_cultist.hammer.doublestrike",
) => {
let speed = Vec2::<f32>::from(velocity).magnitude();
match strike {
@ -152,6 +153,7 @@ impl Animation for ComboAnimation {
Some(
"common.abilities.custom.dullahan.melee"
| "common.abilities.swordsimple.doublestrike"
| "common.abilities.custom.biped_large_cultist.sword.doublestrike"
| "common.abilities.custom.terracotta_pursuer.doublestrike"
| "common.abilities.custom.terracotta_demolisher.triplestrike",
) => {

View File

@ -585,6 +585,9 @@ image_ids! {
mmap_site_haniwa: "voxygen.element.ui.map.buttons.haniwa",
mmap_site_haniwa_hover: "voxygen.element.ui.map.buttons.haniwa_hover",
mmap_site_haniwa_bg: "voxygen.element.ui.map.buttons.haniwa_bg",
mmap_site_cultist: "voxygen.element.ui.map.buttons.mindflayer",
mmap_site_cultist_hover: "voxygen.element.ui.map.buttons.mindflayer_hover",
mmap_site_cultist_bg: "voxygen.element.ui.map.buttons.mindflayer_bg",
mmap_site_minotaur: "voxygen.element.ui.map.buttons.minotaur",
mmap_site_minotaur_hover: "voxygen.element.ui.map.buttons.minotaur_hover",
mmap_site_minotaur_bg: "voxygen.element.ui.map.buttons.minotaur_bg",

View File

@ -927,6 +927,7 @@ impl<'a> Widget for Map<'a> {
SiteKind::Bridge => i18n.get_msg("hud-map-bridge"),
SiteKind::Adlet => i18n.get_msg("hud-map-adlet"),
SiteKind::Haniwa => i18n.get_msg("hud-map-haniwa"),
SiteKind::Cultist => i18n.get_msg("hud-map-cultist"),
SiteKind::DwarvenMine => i18n.get_msg("hud-map-df_mine"),
});
let (difficulty, desc) = match &site.kind {
@ -957,6 +958,7 @@ impl<'a> Widget for Map<'a> {
SiteKind::Bridge => (None, i18n.get_msg("hud-map-bridge")),
SiteKind::Adlet => (Some(1), i18n.get_msg("hud-map-adlet")),
SiteKind::Haniwa => (Some(3), i18n.get_msg("hud-map-haniwa")),
SiteKind::Cultist => (Some(5), i18n.get_msg("hud-map-cultist")),
SiteKind::DwarvenMine => (Some(5), i18n.get_msg("hud-map-df_mine")),
};
let desc = desc.into_owned() + &get_site_economy(site_rich);
@ -970,6 +972,7 @@ impl<'a> Widget for Map<'a> {
SiteKind::Gnarling => self.imgs.mmap_site_gnarling,
SiteKind::Adlet => self.imgs.mmap_site_adlet,
SiteKind::Haniwa => self.imgs.mmap_site_haniwa,
SiteKind::Cultist => self.imgs.mmap_site_cultist,
SiteKind::DwarvenMine => self.imgs.mmap_site_mine,
SiteKind::Dungeon { difficulty } => match difficulty {
4 => self.imgs.mmap_site_minotaur,
@ -994,6 +997,7 @@ impl<'a> Widget for Map<'a> {
SiteKind::Gnarling => self.imgs.mmap_site_gnarling_hover,
SiteKind::Adlet => self.imgs.mmap_site_adlet_hover,
SiteKind::Haniwa => self.imgs.mmap_site_haniwa_hover,
SiteKind::Cultist => self.imgs.mmap_site_cultist_hover,
SiteKind::DwarvenMine => self.imgs.mmap_site_mine_hover,
SiteKind::Dungeon { difficulty } => match difficulty {
4 => self.imgs.mmap_site_minotaur_hover,
@ -1015,6 +1019,7 @@ impl<'a> Widget for Map<'a> {
| SiteKind::Terracotta
| SiteKind::Adlet
| SiteKind::Haniwa
| SiteKind::Cultist
| SiteKind::DwarvenMine => match difficulty {
Some(0) => QUALITY_LOW,
Some(1) => QUALITY_COMMON,
@ -1043,6 +1048,7 @@ impl<'a> Widget for Map<'a> {
| SiteKind::ChapelSite
| SiteKind::DwarvenMine
| SiteKind::Haniwa
| SiteKind::Cultist
| SiteKind::Terracotta
| SiteKind::Adlet => show_dungeons,
SiteKind::Castle => show_castles,
@ -1105,6 +1111,7 @@ impl<'a> Widget for Map<'a> {
| SiteKind::Gnarling
| SiteKind::ChapelSite
| SiteKind::Haniwa
| SiteKind::Cultist
| SiteKind::Terracotta
| SiteKind::Adlet => {
if show_dungeons {

View File

@ -708,6 +708,7 @@ impl<'a> Widget for MiniMap<'a> {
SiteKind::Bridge => None,
SiteKind::Adlet => Some(1),
SiteKind::Haniwa => Some(3),
SiteKind::Cultist => Some(5),
SiteKind::DwarvenMine => Some(5),
};
@ -723,6 +724,7 @@ impl<'a> Widget for MiniMap<'a> {
SiteKind::Bridge => self.imgs.mmap_site_bridge_bg,
SiteKind::Adlet => self.imgs.mmap_site_adlet_bg,
SiteKind::Haniwa => self.imgs.mmap_site_haniwa_bg,
SiteKind::Cultist => self.imgs.mmap_site_cultist_bg,
SiteKind::DwarvenMine => self.imgs.mmap_site_mine_bg,
})
.x_y_position_relative_to(
@ -753,6 +755,7 @@ impl<'a> Widget for MiniMap<'a> {
SiteKind::Bridge => self.imgs.mmap_site_bridge,
SiteKind::Adlet => self.imgs.mmap_site_adlet,
SiteKind::Haniwa => self.imgs.mmap_site_haniwa,
SiteKind::Cultist => self.imgs.mmap_site_cultist,
SiteKind::DwarvenMine => self.imgs.mmap_site_mine,
})
.middle_of(state.ids.mmap_site_icons_bgs[i])

View File

@ -414,7 +414,17 @@ impl Civs {
)?,
SiteKind::DwarvenMine,
),
/*86..=97 => (
87..=92 => (
find_site_loc(
&mut ctx,
&ProximityRequirementsBuilder::new()
.avoid_all_of(this.cultist_enemies(), 40)
.finalize(&world_dims),
&SiteKind::Cultist,
)?,
SiteKind::Cultist,
),
/*97..=102 => (
find_site_loc(
&mut ctx,
&ProximityRequirementsBuilder::new()
@ -425,7 +435,7 @@ impl Civs {
)?,
SiteKind::Castle,
),
98..=103 => (SiteKind::Citadel, (&castle_enemies, 20)),
103..=108 => (SiteKind::Citadel, (&castle_enemies, 20)),
*/
_ => (
find_site_loc(
@ -480,6 +490,7 @@ impl Civs {
SiteKind::TrollCave => (4i32, 1.5),
SiteKind::Camp => (4i32, 1.5),
SiteKind::DwarvenMine => (8i32, 3.0),
SiteKind::Cultist => (24i32, 10.0),
};
let (raise, raise_dist, make_waypoint): (f32, i32, bool) = match &site.kind {
@ -663,6 +674,11 @@ impl Civs {
&mut rng,
wpos,
)),
SiteKind::Cultist => WorldSite::cultist(site2::Site::generate_cultist(
&Land::from_sim(ctx.sim),
&mut rng,
wpos,
)),
}
});
sim_site.site_tmp = Some(site);
@ -1550,6 +1566,13 @@ impl Civs {
})
}
fn cultist_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
self.sites().filter_map(|s| match s.kind {
SiteKind::Tree | SiteKind::GiantTree => None,
_ => Some(s.center),
})
}
fn dungeon_enemies(&self) -> impl Iterator<Item = Vec2<i32>> + '_ {
self.sites().filter_map(|s| match s.kind {
SiteKind::Tree | SiteKind::GiantTree => None,
@ -1964,6 +1987,7 @@ pub enum SiteKind {
Camp,
DwarvenMine,
JungleRuin,
Cultist,
}
impl SiteKind {
@ -2073,6 +2097,7 @@ impl SiteKind {
&& !chunk.river.near_water()
&& !chunk.near_cliffs()
},
SiteKind::Cultist => on_land() && chunk.temp < 0.5 && chunk.near_cliffs(),
SiteKind::Castle => {
if chunk.tree_density > 0.4 || chunk.river.near_water() || chunk.near_cliffs() {
return false;

View File

@ -216,6 +216,7 @@ impl World {
civ::SiteKind::Terracotta => world_msg::SiteKind::Terracotta,
civ::SiteKind::Citadel => world_msg::SiteKind::Castle,
civ::SiteKind::Bridge(_, _) => world_msg::SiteKind::Bridge,
civ::SiteKind::Cultist => world_msg::SiteKind::Cultist,
civ::SiteKind::Adlet => world_msg::SiteKind::Adlet,
civ::SiteKind::Haniwa => world_msg::SiteKind::Haniwa,
},

View File

@ -131,6 +131,7 @@ impl Environment {
SiteKind::GiantTree(_) => (),
SiteKind::Gnarling(_) => {},
SiteKind::Adlet(_) => {},
SiteKind::Cultist(_) => {},
SiteKind::Haniwa(_) => {},
SiteKind::JungleRuin(_) => {},
SiteKind::ChapelSite(_) => {},

View File

@ -84,6 +84,7 @@ pub enum SiteKind {
RockCircle(site2::Site),
TrollCave(site2::Site),
Camp(site2::Site),
Cultist(site2::Site),
}
impl Site {
@ -206,6 +207,13 @@ impl Site {
}
}
pub fn cultist(cl: site2::Site) -> Self {
Self {
kind: SiteKind::Cultist(cl),
economy: Economy::default(),
}
}
pub fn dwarven_mine(dm: site2::Site) -> Self {
Self {
kind: SiteKind::DwarvenMine(dm),
@ -265,6 +273,7 @@ impl Site {
SiteKind::Bridge(b) => b.radius(),
SiteKind::Adlet(ad) => ad.radius(),
SiteKind::Haniwa(ha) => ha.radius(),
SiteKind::Cultist(cl) => cl.radius(),
}
}
@ -292,6 +301,7 @@ impl Site {
SiteKind::Bridge(b) => b.origin,
SiteKind::Adlet(ad) => ad.origin,
SiteKind::Haniwa(ha) => ha.origin,
SiteKind::Cultist(cl) => cl.origin,
}
}
@ -319,6 +329,7 @@ impl Site {
SiteKind::Bridge(b) => b.spawn_rules(wpos),
SiteKind::Adlet(ad) => ad.spawn_rules(wpos),
SiteKind::Haniwa(ha) => ha.spawn_rules(wpos),
SiteKind::Cultist(cl) => cl.spawn_rules(wpos),
}
}
@ -346,6 +357,7 @@ impl Site {
SiteKind::Bridge(b) => b.name(),
SiteKind::Adlet(ad) => ad.name(),
SiteKind::Haniwa(ha) => ha.name(),
SiteKind::Cultist(cl) => cl.name(),
}
}
@ -393,6 +405,7 @@ impl Site {
SiteKind::Bridge(b) => b.render(canvas, dynamic_rng),
SiteKind::Adlet(ad) => ad.render(canvas, dynamic_rng),
SiteKind::Haniwa(ha) => ha.render(canvas, dynamic_rng),
SiteKind::Cultist(cl) => cl.render(canvas, dynamic_rng),
}
}
@ -434,6 +447,7 @@ impl Site {
SiteKind::Bridge(b) => b.apply_supplement(dynamic_rng, wpos2d, supplement),
SiteKind::Adlet(ad) => ad.apply_supplement(dynamic_rng, wpos2d, supplement),
SiteKind::Haniwa(ha) => ha.apply_supplement(dynamic_rng, wpos2d, supplement),
SiteKind::Cultist(cl) => cl.apply_supplement(dynamic_rng, wpos2d, supplement),
}
}
@ -475,6 +489,7 @@ impl Site {
SiteKind::Bridge(site2) => Some(site2),
SiteKind::Adlet(site2) => Some(site2),
SiteKind::Haniwa(site2) => Some(site2),
SiteKind::Cultist(site2) => Some(site2),
}
}
}

View File

@ -113,7 +113,8 @@ impl Site {
PlotKind::TerracottaPalace(tp) => Some(tp.spawn_rules(wpos)),
PlotKind::TerracottaHouse(th) => Some(th.spawn_rules(wpos)),
PlotKind::TerracottaYard(ty) => Some(ty.spawn_rules(wpos)),
//PlotKind::DwarvenMine(m) => Some(m.spawn_rules(wpos)),
PlotKind::Cultist(cl) => Some(cl.spawn_rules(wpos)),
PlotKind::DwarvenMine(dm) => Some(dm.spawn_rules(wpos)),
_ => None,
})
.fold(base_spawn_rules, |a, b| a.combine(b))
@ -1726,6 +1727,49 @@ impl Site {
site
}
pub fn generate_cultist(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
let mut rng = reseed(rng);
let mut site = Site {
origin,
name: {
let name = NameGen::location(&mut rng).generate();
match rng.gen_range(0..5) {
0 => format!("{} Dungeon", name),
1 => format!("{} Lair", name),
2 => format!("{} Crib", name),
3 => format!("{} Catacombs", name),
_ => format!("{} Pit", name),
}
},
//name: NameGen::location(&mut rng).generate_adlet(),
..Site::default()
};
let size = 22.0 as i32;
let aabr = Aabr {
min: Vec2::broadcast(-size),
max: Vec2::broadcast(size),
};
{
let cultist = plot::Cultist::generate(land, &mut reseed(&mut rng), &site, aabr);
let cultist_alt = cultist.alt;
let plot = site.create_plot(Plot {
kind: PlotKind::Cultist(cultist),
root_tile: aabr.center(),
tiles: aabr_tiles(aabr).collect(),
seed: rng.gen(),
});
site.blit_aabr(aabr, Tile {
kind: TileKind::Building,
plot: Some(plot),
hard_alt: Some(cultist_alt),
});
}
site
}
pub fn generate_bridge(
land: &Land,
index: IndexRef,
@ -2083,6 +2127,7 @@ impl Site {
PlotKind::TerracottaYard(terracotta_yard) => {
terracotta_yard.render_collect(self, canvas)
},
PlotKind::Cultist(cultist) => cultist.render_collect(self, canvas),
PlotKind::DesertCityMultiPlot(desert_city_multi_plot) => {
desert_city_multi_plot.render_collect(self, canvas)
},

View File

@ -7,6 +7,7 @@ mod citadel;
mod cliff_tower;
mod coastal_house;
mod coastal_workshop;
mod cultist;
mod desert_city_arena;
mod desert_city_multiplot;
mod desert_city_temple;
@ -33,7 +34,7 @@ mod workshop;
pub use self::{
adlet::AdletStronghold, airship_dock::AirshipDock, bridge::Bridge, camp::Camp, castle::Castle,
citadel::Citadel, cliff_tower::CliffTower, coastal_house::CoastalHouse,
coastal_workshop::CoastalWorkshop, desert_city_arena::DesertCityArena,
coastal_workshop::CoastalWorkshop, cultist::Cultist, desert_city_arena::DesertCityArena,
desert_city_multiplot::DesertCityMultiPlot, desert_city_temple::DesertCityTemple,
dungeon::Dungeon, dwarven_mine::DwarvenMine, giant_tree::GiantTree,
gnarling::GnarlingFortification, haniwa::Haniwa, house::House, jungle_ruin::JungleRuin,
@ -94,6 +95,7 @@ pub enum PlotKind {
JungleRuin(JungleRuin),
Plaza,
Castle(Castle),
Cultist(Cultist),
Road(Path<Vec2<i32>>),
Dungeon(Dungeon),
Gnarling(GnarlingFortification),

View File

@ -0,0 +1,855 @@
use super::*;
use crate::{
site2::plot::dungeon::inscribed_polystar,
util::{sampler::Sampler, RandomField, DIAGONALS},
Land,
};
use common::{
comp::misc::PortalData,
generation::{EntityInfo, SpecialEntity},
resources::Secs,
terrain::SpriteKind,
};
use rand::prelude::*;
use std::{f32::consts::TAU, sync::Arc};
use vek::*;
pub struct Room {
room_base: i32,
room_center: Vec2<i32>,
clear_center: Vec2<i32>,
mob_room: bool,
boss_room: bool,
}
pub struct Cultist {
base: i32,
bounds: Aabr<i32>,
pub(crate) alt: i32,
pub(crate) center: Vec2<i32>,
pub(crate) room_data: Vec<Room>,
room_size: i32,
floors: i32,
}
impl Cultist {
pub fn generate(land: &Land, _rng: &mut impl Rng, site: &Site, tile_aabr: Aabr<i32>) -> Self {
let bounds = Aabr {
min: site.tile_wpos(tile_aabr.min),
max: site.tile_wpos(tile_aabr.max),
};
let center = bounds.center();
let base = land.get_alt_approx(center) as i32;
let room_size = 30;
let mut room_data = vec![];
let floors = 3;
for f in 0..=floors {
for s in 0..=1 {
// rooms
let rooms = [1, 2];
if rooms.contains(&f) {
for dir in DIAGONALS {
let room_base = base - (f * (2 * (room_size))) - (s * room_size);
let room_center = center + (dir * ((room_size * 2) - 5 + (10 * s)));
let clear_center = center + (dir * ((room_size * 2) - 6 + (10 * s)));
let mob_room = s < 1;
room_data.push(Room {
room_base,
room_center,
clear_center,
mob_room,
boss_room: false,
});
}
}
}
}
let boss_room_base = base - (6 * room_size);
room_data.push(Room {
room_base: boss_room_base,
room_center: center,
clear_center: center,
mob_room: false,
boss_room: true,
});
Self {
bounds,
alt: land.get_alt_approx(site.tile_center_wpos((tile_aabr.max - tile_aabr.min) / 2))
as i32
+ 2,
base,
center,
room_size,
room_data,
floors,
}
}
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
SpawnRules {
waypoints: false,
trees: wpos.distance_squared(self.bounds.center()) > (75_i32).pow(2),
..SpawnRules::default()
}
}
}
impl Structure for Cultist {
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"render_cultist\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "render_cultist")]
fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
let center = self.center;
let base = self.base;
let room_size = self.room_size;
let floors = self.floors;
let mut thread_rng = thread_rng();
let candles_lite = Fill::Sampling(Arc::new(|wpos| {
Some(match (RandomField::new(0).get(wpos)) % 30 {
0 => Block::air(SpriteKind::Candle),
_ => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
})
}));
let mut tower_positions = vec![];
let mut clear_positions = vec![];
let room_data = &self.room_data;
let mut star_positions = vec![];
let mut sprite_positions = vec![];
let mut random_npcs = vec![];
let rock_broken = Fill::Sampling(Arc::new(|center| {
Some(match (RandomField::new(0).get(center)) % 52 {
0..=8 => Block::new(BlockKind::Rock, Rgb::new(60, 55, 65)),
9..=17 => Block::new(BlockKind::Rock, Rgb::new(65, 60, 70)),
18..=26 => Block::new(BlockKind::Rock, Rgb::new(70, 65, 75)),
27..=35 => Block::new(BlockKind::Rock, Rgb::new(75, 70, 80)),
36..=37 => Block::new(BlockKind::Air, Rgb::new(0, 0, 0)),
_ => Block::new(BlockKind::Rock, Rgb::new(55, 50, 60)),
})
}));
let rock = Fill::Brick(BlockKind::Rock, Rgb::new(55, 50, 60), 24);
let water = Fill::Block(Block::new(BlockKind::Water, Rgb::zero()));
let key_door = Fill::Block(Block::air(SpriteKind::KeyDoor));
let key_hole = Fill::Block(Block::air(SpriteKind::Keyhole));
let gold_chain = Fill::Block(Block::air(SpriteKind::SeaDecorChain));
for room in room_data {
let (room_base, room_center) = (room.room_base, room.room_center);
// rooms
// encapsulation
painter
.aabb(Aabb {
min: (room_center - room_size - 23).with_z(room_base - room_size - 1),
max: (room_center + room_size + 23).with_z(room_base - 2),
})
.fill(rock.clone());
painter
.aabb(Aabb {
min: (room_center - room_size - 2).with_z(room_base - room_size - 1),
max: (room_center + room_size + 2).with_z(room_base - 2),
})
.fill(rock_broken.clone());
// solid floor
painter
.aabb(Aabb {
min: (room_center - room_size - 10).with_z(room_base - room_size - 2),
max: (room_center + room_size + 10).with_z(room_base - room_size - 1),
})
.fill(rock.clone());
// floor candles
painter
.cylinder(Aabb {
min: (room_center - room_size + 1).with_z(room_base - room_size - 1),
max: (room_center + room_size - 1).with_z(room_base - room_size),
})
.fill(candles_lite.clone());
}
for s in 0..=floors {
let room_base = base - (s * (2 * room_size));
// center pit
for p in 3..=5 {
let pos = 3 * p;
let radius = pos * 2;
let amount = pos;
let clear_radius = radius - 8;
let tower_pos = place_circular(center, radius as f32, amount);
let clear_pos = place_circular(center, clear_radius as f32, amount);
tower_positions.extend(tower_pos);
clear_positions.extend(clear_pos);
}
for tower_center in &tower_positions {
let height_var =
(RandomField::new(0).get(tower_center.with_z(room_base)) % 15) as i32;
let height = height_var * 3;
let size = height_var / 3;
// towers
// encapsulation if under ground
if room_base < base {
painter
.aabb(Aabb {
min: (tower_center - 9 - size).with_z(room_base - 2),
max: (tower_center + 9 + size).with_z(room_base + 10 + height),
})
.fill(rock.clone());
painter
.aabb(Aabb {
min: (tower_center - 9 - (size / 2)).with_z(room_base + 8 + height),
max: (tower_center + 9 + (size / 2))
.with_z(room_base + 10 + height + 5 + (height / 2)),
})
.fill(rock.clone());
}
painter
.aabb(Aabb {
min: (tower_center - 8 - size).with_z(room_base),
max: (tower_center + 8 + size).with_z(room_base + 10 + height),
})
.fill(rock_broken.clone());
painter
.aabb(Aabb {
min: (tower_center - 8 - (size / 2)).with_z(room_base + 10 + height),
max: (tower_center + 8 + (size / 2))
.with_z(room_base + 10 + height + 5 + (height / 2)),
})
.fill(rock_broken.clone());
// vault carves floor 0
painter
.vault(
Aabb {
min: Vec2::new(tower_center.x - 8 - size, tower_center.y - 4 - size)
.with_z(room_base + size),
max: Vec2::new(tower_center.x + 8 + size, tower_center.y + 4 + size)
.with_z(room_base + height),
},
Dir::X,
)
.clear();
painter
.vault(
Aabb {
min: Vec2::new(tower_center.x - 4 - size, tower_center.y - 8 - size)
.with_z(room_base + size),
max: Vec2::new(tower_center.x + 4 + size, tower_center.y + 8 + size)
.with_z(room_base + height),
},
Dir::Y,
)
.clear();
// vault carves floor 1
painter
.vault(
Aabb {
min: Vec2::new(
tower_center.x - 8 - (size / 2),
tower_center.y - 4 - (size / 2),
)
.with_z(room_base + 10 + height),
max: Vec2::new(
tower_center.x + 8 + (size / 2),
tower_center.y + 4 + (size / 2),
)
.with_z(room_base + 10 + height + 5 + (height / 4) + (size / 2)),
},
Dir::X,
)
.clear();
painter
.vault(
Aabb {
min: Vec2::new(
tower_center.x - 4 - (size / 2),
tower_center.y - 8 - (size / 2),
)
.with_z(room_base + 10 + height),
max: Vec2::new(
tower_center.x + 4 + (size / 2),
tower_center.y + 8 + (size / 2),
)
.with_z(room_base + 10 + height + 5 + (height / 4) + (size / 2)),
},
Dir::Y,
)
.clear();
}
// tower clears
for (tower_center, clear_center) in tower_positions.iter().zip(&clear_positions) {
let height_var =
(RandomField::new(0).get(tower_center.with_z(room_base)) % 15) as i32;
let height = height_var * 3;
let size = height_var / 3;
// tower clears
painter
.aabb(Aabb {
min: (clear_center - 9 - size).with_z(room_base + size),
max: (clear_center + 9 + size).with_z(room_base + 8 + height),
})
.clear();
painter
.aabb(Aabb {
min: (clear_center - 8 - (size / 2)).with_z(room_base + 10 + height),
max: (clear_center + 8 + (size / 2))
.with_z(room_base + 8 + height + 5 + (height / 2)),
})
.clear();
// decay
let decay_size = 8 + size;
painter
.cylinder(Aabb {
min: (clear_center - decay_size).with_z(room_base + 8 + height),
max: (clear_center + decay_size).with_z(room_base + 10 + height),
})
.clear();
painter
.cylinder(Aabb {
min: (clear_center - decay_size + 5)
.with_z(room_base + 8 + height + 5 + (height / 2)),
max: (clear_center + decay_size - 5)
.with_z(room_base + 10 + height + 5 + (height / 2)),
})
.clear();
}
// center clear
painter
.cylinder(Aabb {
min: (center - room_size + 10).with_z(room_base),
max: (center + room_size - 10).with_z(room_base + (4 * (room_size))),
})
.clear();
}
// room clears
for room in room_data {
let (room_base, room_center, clear_center, mob_room, boss_room) = (
room.room_base,
room.room_center,
room.clear_center,
room.mob_room,
room.boss_room,
);
painter
.cylinder(Aabb {
min: (clear_center - room_size - 1).with_z(room_base - room_size),
max: (clear_center + room_size + 1).with_z(room_base - 4),
})
.clear();
// room decor
let decor_var = RandomField::new(0).get(room_center.with_z(room_base)) % 4;
if mob_room {
// room_center platform or water basin
if decor_var < 3 {
painter
.aabb(Aabb {
min: (room_center - room_size + 10).with_z(room_base - room_size - 1),
max: (room_center + room_size - 10).with_z(room_base - 2),
})
.fill(rock_broken.clone());
}
}
// carves
let spacing = 12;
let carve_length = room_size + 8;
let carve_width = 3;
for f in 0..3 {
for c in 0..5 {
// candles & chest & npcs
let sprite_pos_1 = Vec2::new(
room_center.x - room_size + (spacing / 2) + (spacing * c) - carve_width + 2,
room_center.y - carve_length + 2,
)
.with_z(room_base - room_size - 1 + ((room_size / 3) * f));
sprite_positions.push(sprite_pos_1);
let sprite_pos_2 = Vec2::new(
room_center.x - room_size + (spacing / 2) + (spacing * c) + carve_width - 2,
room_center.y + carve_length - 2,
)
.with_z(room_base - room_size - 1 + ((room_size / 3) * f));
sprite_positions.push(sprite_pos_2);
let sprite_pos_3 = Vec2::new(
room_center.x - carve_length + 2,
room_center.y - room_size + (spacing / 2) + (spacing * c) - carve_width + 2,
)
.with_z(room_base - room_size - 1 + ((room_size / 3) * f));
sprite_positions.push(sprite_pos_3);
let sprite_pos_4 = Vec2::new(
room_center.x + carve_length - 2,
room_center.y - room_size + (spacing / 2) + (spacing * c) + carve_width - 2,
)
.with_z(room_base - room_size - 1 + ((room_size / 3) * f));
sprite_positions.push(sprite_pos_4);
let candle_limiter = painter.aabb(Aabb {
min: (room_center - room_size + 10)
.with_z(room_base - room_size - 2 + ((room_size / 3) * f)),
max: (room_center + room_size - 10)
.with_z(room_base - room_size + ((room_size / 3) * f)),
});
painter
.vault(
Aabb {
min: Vec2::new(
room_center.x - room_size + (spacing / 2) + (spacing * c)
- carve_width,
room_center.y - carve_length,
)
.with_z(room_base - room_size - 1 + ((room_size / 3) * f)),
max: Vec2::new(
room_center.x - room_size
+ (spacing / 2)
+ (spacing * c)
+ carve_width,
room_center.y + carve_length,
)
.with_z(
room_base - room_size - 3
+ (room_size / 3)
+ ((room_size / 3) * f),
),
},
Dir::Y,
)
.clear();
if mob_room {
painter
.aabb(Aabb {
min: Vec2::new(
room_center.x - room_size + (spacing / 2) + (spacing * c)
- carve_width,
room_center.y - carve_length,
)
.with_z(room_base - room_size - 2 + ((room_size / 3) * f)),
max: Vec2::new(
room_center.x - room_size
+ (spacing / 2)
+ (spacing * c)
+ carve_width,
room_center.y + carve_length,
)
.with_z(room_base - room_size - 1 + ((room_size / 3) * f)),
})
.intersect(candle_limiter)
.fill(rock.clone());
painter
.aabb(Aabb {
min: Vec2::new(
room_center.x - room_size + (spacing / 2) + (spacing * c)
- carve_width,
room_center.y - carve_length,
)
.with_z(room_base - room_size - 1 + ((room_size / 3) * f)),
max: Vec2::new(
room_center.x - room_size
+ (spacing / 2)
+ (spacing * c)
+ carve_width,
room_center.y + carve_length,
)
.with_z(room_base - room_size + ((room_size / 3) * f)),
})
.intersect(candle_limiter)
.fill(candles_lite.clone());
}
painter
.vault(
Aabb {
min: Vec2::new(
room_center.x - carve_length,
room_center.y - room_size + (spacing / 2) + (spacing * c)
- carve_width,
)
.with_z(room_base - room_size - 1 + ((room_size / 3) * f)),
max: Vec2::new(
room_center.x + carve_length,
room_center.y - room_size
+ (spacing / 2)
+ (spacing * c)
+ carve_width,
)
.with_z(
room_base - room_size - 3
+ (room_size / 3)
+ ((room_size / 3) * f),
),
},
Dir::X,
)
.clear();
if mob_room {
painter
.aabb(Aabb {
min: Vec2::new(
room_center.x - carve_length,
room_center.y - room_size + (spacing / 2) + (spacing * c)
- carve_width,
)
.with_z(room_base - room_size - 2 + ((room_size / 3) * f)),
max: Vec2::new(
room_center.x + carve_length,
room_center.y - room_size
+ (spacing / 2)
+ (spacing * c)
+ carve_width,
)
.with_z(room_base - room_size - 1 + ((room_size / 3) * f)),
})
.intersect(candle_limiter)
.fill(rock.clone());
painter
.aabb(Aabb {
min: Vec2::new(
room_center.x - carve_length,
room_center.y - room_size + (spacing / 2) + (spacing * c)
- carve_width,
)
.with_z(room_base - room_size - 1 + ((room_size / 3) * f)),
max: Vec2::new(
room_center.x + carve_length,
room_center.y - room_size
+ (spacing / 2)
+ (spacing * c)
+ carve_width,
)
.with_z(room_base - room_size + ((room_size / 3) * f)),
})
.intersect(candle_limiter)
.fill(candles_lite.clone());
}
}
if mob_room {
// mob room npcs
for dir in CARDINALS {
for d in 1..=4 {
let npc_pos = (room_center + dir * ((spacing / 2) * d))
.with_z(room_base - room_size + ((room_size / 3) * f));
let pos_var = RandomField::new(0).get(npc_pos) % 10;
if pos_var < 1 {
painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
"common.entity.dungeon.cultist.cultist",
&mut thread_rng,
None,
))
} else if pos_var > 2 && f > 0 {
painter.sphere_with_radius(npc_pos, 5_f32).clear();
}
}
}
let decor_var = RandomField::new(0).get(room_center.with_z(room_base)) % 4;
if decor_var < 3 {
// portal platform
painter
.cylinder(Aabb {
min: (room_center - 3).with_z(room_base - (room_size / 4) - 5),
max: (room_center + 3).with_z(room_base - (room_size / 4) - 4),
})
.fill(rock.clone());
} else {
painter
.cylinder(Aabb {
min: (room_center - room_size + 10)
.with_z(room_base - room_size - 3),
max: (room_center + room_size - 10)
.with_z(room_base - room_size - 2),
})
.fill(rock.clone());
painter
.cylinder(Aabb {
min: (room_center - room_size + 10)
.with_z(room_base - room_size - 2),
max: (room_center + room_size - 10)
.with_z(room_base - room_size - 1),
})
.fill(water.clone());
painter
.aabb(Aabb {
min: (room_center - room_size + 10)
.with_z(room_base - room_size - 1),
max: (room_center + room_size - 10)
.with_z(room_base - (room_size / 4) - 3),
})
.clear();
}
}
}
// room portals
let mob_portal = room_center.with_z(room_base - (room_size / 4));
let mob_portal_target = (room_center + 10).with_z(room_base - (room_size * 2));
let mini_boss_portal = room_center.with_z(room_base - (room_size * 2));
let exit_position = (center - 10).with_z(base - (6 * room_size));
let boss_position = (center - 10).with_z(base - (7 * room_size));
let boss_portal = center.with_z(base - (7 * room_size));
let mob_portal_pos = Vec3::new(
mob_portal.x as f32,
mob_portal.y as f32,
mob_portal.z as f32,
);
let mob_portal_target_pos = Vec3::new(
mob_portal_target.x as f32,
mob_portal_target.y as f32,
mob_portal_target.z as f32,
);
let mini_boss_portal_pos = Vec3::new(
mini_boss_portal.x as f32,
mini_boss_portal.y as f32,
mini_boss_portal.z as f32,
);
let exit_pos = Vec3::new(
exit_position.x as f32,
exit_position.y as f32,
exit_position.z as f32,
);
let boss_pos = Vec3::new(
boss_position.x as f32,
boss_position.y as f32,
boss_position.z as f32,
);
let boss_portal_pos = Vec3::new(
boss_portal.x as f32,
boss_portal.y as f32,
boss_portal.z as f32,
);
let mini_boss_portal_target = [
exit_pos, exit_pos, exit_pos, exit_pos, exit_pos, boss_pos, boss_pos, boss_pos,
];
if mob_room {
painter.spawn(EntityInfo::at(mob_portal_pos).into_special(
SpecialEntity::Teleporter(PortalData {
target: mob_portal_target_pos,
requires_no_aggro: true,
buildup_time: Secs(5.),
}),
));
let mini_boss_portal_target_index = RandomField::new(0).get(mini_boss_portal)
as usize
% mini_boss_portal_target.len();
let mini_boss_portal_target_pos =
mini_boss_portal_target[mini_boss_portal_target_index];
painter.spawn(EntityInfo::at(mini_boss_portal_pos).into_special(
SpecialEntity::Teleporter(PortalData {
target: mini_boss_portal_target_pos,
requires_no_aggro: true,
buildup_time: Secs(5.),
}),
));
} else if boss_room {
painter.spawn(EntityInfo::at(boss_portal_pos).into_special(
SpecialEntity::Teleporter(PortalData {
target: exit_pos,
requires_no_aggro: true,
buildup_time: Secs(5.),
}),
));
}
if !mob_room {
if boss_room {
let npc_pos = room_center.with_z(room_base - room_size);
painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
"common.entity.dungeon.cultist.mindflayer",
&mut thread_rng,
None,
));
} else {
let npc_pos = (room_center - 2).with_z(room_base - room_size);
painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
"common.entity.dungeon.cultist.warlock",
&mut thread_rng,
None,
));
painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
"common.entity.dungeon.cultist.warlord",
&mut thread_rng,
None,
));
painter.spawn(
EntityInfo::at(((room_center + 5).with_z(room_base - room_size)).as_())
.with_asset_expect(
"common.entity.dungeon.cultist.beastmaster",
&mut thread_rng,
None,
),
);
}
// gold chains
let chain_positions = place_circular(room_center, 15.0, 10);
for pos in chain_positions {
painter
.aabb(Aabb {
min: pos.with_z(room_base - 12),
max: (pos + 1).with_z(room_base - 4),
})
.fill(gold_chain.clone());
}
}
let down = if mob_room && decor_var < 3 {
0
} else if mob_room && decor_var > 2 {
room_size
} else {
10
};
let magic_circle_bb = painter.cylinder(Aabb {
min: (room_center - 15).with_z(room_base - 3 - down),
max: (room_center + 16).with_z(room_base - 2 - down),
});
star_positions.push((magic_circle_bb, room_center));
}
// candles & chests & npcs
for sprite_pos in sprite_positions {
// keep center pit clear
if sprite_pos.xy().distance_squared(center) > 40_i32.pow(2)
|| sprite_pos.z < (base - (6 * room_size))
{
match (RandomField::new(0).get(sprite_pos + 1)) % 16 {
0 => {
if sprite_pos.z > (base - (6 * room_size)) {
random_npcs.push(sprite_pos)
}
},
1 => {
// prisoners
painter
.aabb(Aabb {
min: (sprite_pos - 1).with_z(sprite_pos.z),
max: (sprite_pos + 2).with_z(sprite_pos.z + 3),
})
.fill(key_door.clone());
painter
.aabb(Aabb {
min: sprite_pos.with_z(sprite_pos.z + 3),
max: (sprite_pos + 1).with_z(sprite_pos.z + 4),
})
.fill(key_hole.clone());
painter
.aabb(Aabb {
min: (sprite_pos).with_z(sprite_pos.z),
max: (sprite_pos + 1).with_z(sprite_pos.z + 2),
})
.clear();
painter.spawn(EntityInfo::at(sprite_pos.as_()).with_asset_expect(
match (RandomField::new(0).get(sprite_pos)) % 10 {
0 => "common.entity.village.farmer",
1 => "common.entity.village.guard",
2 => "common.entity.village.hunter",
3 => "common.entity.village.skinner",
_ => "common.entity.village.villager",
},
&mut thread_rng,
None,
));
},
_ => {
painter.sprite(
sprite_pos,
match (RandomField::new(0).get(sprite_pos)) % 20 {
0 => SpriteKind::DungeonChest5,
_ => SpriteKind::Candle,
},
);
},
}
}
}
// random_npcs around upper entrance and bottom portal
for s in 0..=1 {
let radius = 62.0 - (s * 50) as f32;
let npcs = place_circular(center, radius, 8 - (s * 4));
for npc_pos in npcs {
random_npcs.push(npc_pos.with_z(base + 8 - ((6 * room_size) * s) - (s * 8)));
}
}
for pos in random_npcs {
let entities = [
"common.entity.dungeon.cultist.cultist",
"common.entity.dungeon.cultist.turret",
"common.entity.dungeon.cultist.husk",
"common.entity.dungeon.cultist.husk_brute",
"common.entity.dungeon.cultist.hound",
];
let npc = entities[(RandomField::new(0).get(pos) % entities.len() as u32) as usize];
painter.spawn(EntityInfo::at(pos.as_()).with_asset_expect(npc, &mut thread_rng, None));
}
// outside portal
let top_position = (center - 20).with_z(base + 125);
let bottom_position = center.with_z(base - (6 * room_size));
let top_pos = Vec3::new(
top_position.x as f32,
top_position.y as f32,
top_position.z as f32,
);
let bottom_pos = Vec3::new(
bottom_position.x as f32,
bottom_position.y as f32,
bottom_position.z as f32,
);
painter.spawn(
EntityInfo::at(bottom_pos).into_special(SpecialEntity::Teleporter(PortalData {
target: top_pos,
requires_no_aggro: true,
buildup_time: Secs(5.),
})),
);
let stone_purple = Block::new(BlockKind::GlowingRock, Rgb::new(96, 0, 128));
let magic_circle_bb = painter.cylinder(Aabb {
min: (center - 15).with_z(base - (floors * (2 * room_size)) - 1),
max: (center + 16).with_z(base - (floors * (2 * room_size))),
});
let magic_circle_bb_boss = painter.cylinder(Aabb {
min: (center - 15).with_z(base - (7 * room_size) - 2),
max: (center + 16).with_z(base - (7 * room_size) - 1),
});
star_positions.push((magic_circle_bb, center));
star_positions.push((magic_circle_bb_boss, center));
for (magic_circle_bb, position) in star_positions {
let magic_circle = painter.prim(Primitive::sampling(
magic_circle_bb,
inscribed_polystar(position, 15.0, 7),
));
painter.fill(magic_circle, Fill::Block(stone_purple));
}
// base floor
painter
.cylinder(Aabb {
min: (center - room_size - 15).with_z(base - (floors * (2 * room_size)) - 3),
max: (center + room_size + 15).with_z(base - (floors * (2 * room_size)) - 2),
})
.fill(rock.clone());
}
}
fn place_circular(center: Vec2<i32>, radius: f32, amount: i32) -> Vec<Vec2<i32>> {
let phi = TAU / amount as f32;
let mut positions = vec![];
for n in 1..=amount {
let pos = Vec2::new(
center.x + (radius * ((n as f32 * phi).cos())) as i32,
center.y + (radius * ((n as f32 * phi).sin())) as i32,
);
positions.push(pos);
}
positions
}

View File

@ -231,31 +231,12 @@ impl Room {
let entities = match self.difficulty {
2 => enemy_2(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);
}
} 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);
if self.difficulty == 5 {
let turret = turret_5(dynamic_rng, pos);
supplement.add_entity(turret);
};
}
}
}
@ -280,7 +261,6 @@ impl Room {
let entities = match self.difficulty {
2 => mini_boss_2(dynamic_rng, tile_wcenter),
4 => mini_boss_4(dynamic_rng, tile_wcenter),
5 => mini_boss_5(dynamic_rng, tile_wcenter),
_ => mini_boss_fallback(dynamic_rng, tile_wcenter),
};
@ -311,7 +291,6 @@ impl Room {
let entities = match self.difficulty {
2 => boss_2(dynamic_rng, tile_wcenter),
4 => boss_4(dynamic_rng, tile_wcenter),
5 => boss_5(dynamic_rng, tile_wcenter),
_ => boss_fallback(dynamic_rng, tile_wcenter),
};
@ -744,28 +723,6 @@ fn enemy_4(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInf
entities
}
fn enemy_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
let number = dynamic_rng.gen_range(1..=3);
let mut entities = Vec::new();
entities.resize_with(number, || {
// TODO: give enemies health skills?
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.cultist.warlock", dynamic_rng, None)
},
1 => {
entity.with_asset_expect("common.entity.dungeon.cultist.warlord", dynamic_rng, None)
},
_ => {
entity.with_asset_expect("common.entity.dungeon.cultist.cultist", dynamic_rng, None)
},
}
});
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();
@ -777,10 +734,6 @@ fn enemy_fallback(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<En
entities
}
fn turret_5(dynamic_rng: &mut impl Rng, pos: Vec3<f32>) -> EntityInfo {
EntityInfo::at(pos).with_asset_expect("common.entity.dungeon.cultist.turret", dynamic_rng, None)
}
fn boss_2(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
@ -801,16 +754,6 @@ fn boss_4(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo
]
}
fn boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.cultist.mindflayer",
dynamic_rng,
None,
),
]
}
fn boss_fallback(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
@ -843,40 +786,6 @@ fn mini_boss_4(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<Entit
]
}
fn mini_boss_5(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
let mut entities = Vec::new();
match dynamic_rng.gen_range(0..=2) {
0 => {
entities.push(
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.cultist.beastmaster",
dynamic_rng,
None,
),
);
},
1 => {
entities.resize_with(2, || {
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.cultist.husk_brute",
dynamic_rng,
None,
)
});
},
_ => {
entities.resize_with(10, || {
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
"common.entity.dungeon.cultist.husk",
dynamic_rng,
None,
)
});
},
}
entities
}
fn mini_boss_fallback(dynamic_rng: &mut impl Rng, tile_wcenter: Vec3<i32>) -> Vec<EntityInfo> {
vec![
EntityInfo::at(tile_wcenter.map(|e| e as f32)).with_asset_expect(
@ -1469,7 +1378,6 @@ mod tests {
let tile_wcenter = Vec3::new(0, 0, 0);
boss_2(&mut dynamic_rng, tile_wcenter);
boss_4(&mut dynamic_rng, tile_wcenter);
boss_5(&mut dynamic_rng, tile_wcenter);
boss_fallback(&mut dynamic_rng, tile_wcenter);
}
@ -1480,7 +1388,6 @@ mod tests {
let random_position = Vec3::new(0, 0, 0);
enemy_2(&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);
}
@ -1491,14 +1398,6 @@ mod tests {
let tile_wcenter = Vec3::new(0, 0, 0);
mini_boss_2(&mut dynamic_rng, tile_wcenter);
mini_boss_4(&mut dynamic_rng, tile_wcenter);
mini_boss_5(&mut dynamic_rng, tile_wcenter);
mini_boss_fallback(&mut dynamic_rng, tile_wcenter);
}
#[test]
fn test_creating_turrets() {
let mut dynamic_rng = thread_rng();
let pos = Vec3::new(0.0, 0.0, 0.0);
turret_5(&mut dynamic_rng, pos);
}
}