mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added dungeon bosses, boss loot, boss arenas
This commit is contained in:
parent
71dd520cd6
commit
aac28d04d5
@ -73,6 +73,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added tab completion in chat for player names and chat commands
|
- Added tab completion in chat for player names and chat commands
|
||||||
- Added server persistence for character stats
|
- Added server persistence for character stats
|
||||||
- Added a popup when setting your character's waypoint
|
- Added a popup when setting your character's waypoint
|
||||||
|
- Added dungeon arenas
|
||||||
|
- Added dungeon bosses and rare boss loot
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
12
assets/common/items/boss_drops/lantern.ron
Normal file
12
assets/common/items/boss_drops/lantern.ron
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Item(
|
||||||
|
name: "Magic Lantern",
|
||||||
|
description: "Illuminates even the darkest dungeon\nA great monster was slain for this item",
|
||||||
|
kind: Lantern(
|
||||||
|
(
|
||||||
|
kind: Blue0,
|
||||||
|
color: (r: 220, g: 220, b: 255),
|
||||||
|
strength_thousandths: 6500,
|
||||||
|
flicker_thousandths: 300,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
12
assets/common/items/boss_drops/potions.ron
Normal file
12
assets/common/items/boss_drops/potions.ron
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Item(
|
||||||
|
name: "Powerful Potion",
|
||||||
|
description: "Restores 100 Health\nA great monster was slain for this item\n\n<Right-Click to use>",
|
||||||
|
kind: Consumable(
|
||||||
|
kind: Potion,
|
||||||
|
effect: Health((
|
||||||
|
amount: 100,
|
||||||
|
cause: Item,
|
||||||
|
)),
|
||||||
|
amount: 15,
|
||||||
|
),
|
||||||
|
)
|
8
assets/common/items/boss_drops/xp_potion.ron
Normal file
8
assets/common/items/boss_drops/xp_potion.ron
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Item(
|
||||||
|
name: "Potion of Skill",
|
||||||
|
description: "Provides 250 XP to the drinker\n\n<Right-Click to use>",
|
||||||
|
kind: Consumable(
|
||||||
|
kind: Potion,
|
||||||
|
effect: Xp(250),
|
||||||
|
),
|
||||||
|
)
|
@ -3,7 +3,7 @@ Item(
|
|||||||
description: "Two-Hand Staff\n\nPower: 2-10\n\nWalking stick with a sharpened end\n\n<Right-Click to use>",
|
description: "Two-Hand Staff\n\nPower: 2-10\n\nWalking stick with a sharpened end\n\n<Right-Click to use>",
|
||||||
kind: Tool(
|
kind: Tool(
|
||||||
(
|
(
|
||||||
kind: Staff(BasicStaff),
|
kind: Staff(BasicStaff),
|
||||||
equip_time_millis: 200,
|
equip_time_millis: 200,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
@ -123,6 +123,8 @@ impl Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expect_from_asset(asset: &str) -> Self { (*assets::load_expect::<Self>(asset)).clone() }
|
||||||
|
|
||||||
pub fn set_amount(&mut self, give_amount: u32) -> Result<(), assets::Error> {
|
pub fn set_amount(&mut self, give_amount: u32) -> Result<(), assets::Error> {
|
||||||
use ItemKind::*;
|
use ItemKind::*;
|
||||||
match self.kind {
|
match self.kind {
|
||||||
@ -235,3 +237,10 @@ impl Item {
|
|||||||
impl Component for Item {
|
impl Component for Item {
|
||||||
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
|
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct ItemDrop(pub Item);
|
||||||
|
|
||||||
|
impl Component for ItemDrop {
|
||||||
|
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
|
||||||
|
}
|
||||||
|
@ -31,7 +31,9 @@ pub use controller::{
|
|||||||
pub use energy::{Energy, EnergySource};
|
pub use energy::{Energy, EnergySource};
|
||||||
pub use inputs::CanBuild;
|
pub use inputs::CanBuild;
|
||||||
pub use inventory::{
|
pub use inventory::{
|
||||||
item, item::Item, slot, Inventory, InventoryUpdate, InventoryUpdateEvent, MAX_PICKUP_RANGE_SQR,
|
item,
|
||||||
|
item::{Item, ItemDrop},
|
||||||
|
slot, Inventory, InventoryUpdate, InventoryUpdateEvent, MAX_PICKUP_RANGE_SQR,
|
||||||
};
|
};
|
||||||
pub use last::Last;
|
pub use last::Last;
|
||||||
pub use location::{Waypoint, WaypointArea};
|
pub use location::{Waypoint, WaypointArea};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::{comp, sync::Uid, util::Dir};
|
use crate::{comp, sync::Uid, util::Dir};
|
||||||
use comp::{item::ToolKind, InventoryUpdateEvent};
|
use comp::{item::ToolKind, InventoryUpdateEvent, Item};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use specs::Entity as EcsEntity;
|
use specs::Entity as EcsEntity;
|
||||||
@ -107,6 +107,7 @@ pub enum ServerEvent {
|
|||||||
agent: comp::Agent,
|
agent: comp::Agent,
|
||||||
alignment: comp::Alignment,
|
alignment: comp::Alignment,
|
||||||
scale: comp::Scale,
|
scale: comp::Scale,
|
||||||
|
drop_item: Option<Item>,
|
||||||
},
|
},
|
||||||
CreateWaypoint(Vec3<f32>),
|
CreateWaypoint(Vec3<f32>),
|
||||||
ClientDisconnect(EcsEntity),
|
ClientDisconnect(EcsEntity),
|
||||||
|
@ -16,6 +16,9 @@ pub struct EntityInfo {
|
|||||||
pub body: Body,
|
pub body: Body,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub main_tool: Option<Item>,
|
pub main_tool: Option<Item>,
|
||||||
|
pub scale: f32,
|
||||||
|
pub level: Option<u32>,
|
||||||
|
pub loot_drop: Option<Item>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EntityInfo {
|
impl EntityInfo {
|
||||||
@ -28,6 +31,9 @@ impl EntityInfo {
|
|||||||
body: Body::Humanoid(humanoid::Body::random()),
|
body: Body::Humanoid(humanoid::Body::random()),
|
||||||
name: None,
|
name: None,
|
||||||
main_tool: Some(Item::empty()),
|
main_tool: Some(Item::empty()),
|
||||||
|
scale: 1.0,
|
||||||
|
level: None,
|
||||||
|
loot_drop: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +74,21 @@ impl EntityInfo {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_loot_drop(mut self, loot_drop: Item) -> Self {
|
||||||
|
self.loot_drop = Some(loot_drop);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_scale(mut self, scale: f32) -> Self {
|
||||||
|
self.scale = scale;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_level(mut self, level: u32) -> Self {
|
||||||
|
self.level = Some(level);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_automatic_name(mut self) -> Self {
|
pub fn with_automatic_name(mut self) -> Self {
|
||||||
self.name = match &self.body {
|
self.name = match &self.body {
|
||||||
Body::Humanoid(body) => Some(get_npc_name(&NPC_NAMES.humanoid, body.race)),
|
Body::Humanoid(body) => Some(get_npc_name(&NPC_NAMES.humanoid, body.race)),
|
||||||
|
@ -153,6 +153,7 @@ impl State {
|
|||||||
ecs.register::<comp::Waypoint>();
|
ecs.register::<comp::Waypoint>();
|
||||||
ecs.register::<comp::Projectile>();
|
ecs.register::<comp::Projectile>();
|
||||||
ecs.register::<comp::Attacking>();
|
ecs.register::<comp::Attacking>();
|
||||||
|
ecs.register::<comp::ItemDrop>();
|
||||||
|
|
||||||
// Register synced resources used by the ECS.
|
// Register synced resources used by the ECS.
|
||||||
ecs.insert(TimeOfDay(0.0));
|
ecs.insert(TimeOfDay(0.0));
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::{sys, Server, StateExt};
|
use crate::{sys, Server, StateExt};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
self, Agent, Alignment, Body, Gravity, LightEmitter, Loadout, Pos, Projectile, Scale,
|
self, Agent, Alignment, Body, Gravity, Item, ItemDrop, LightEmitter, Loadout, Pos,
|
||||||
Stats, Vel, WaypointArea,
|
Projectile, Scale, Stats, Vel, WaypointArea,
|
||||||
},
|
},
|
||||||
util::Dir,
|
util::Dir,
|
||||||
};
|
};
|
||||||
@ -32,14 +32,22 @@ pub fn handle_create_npc(
|
|||||||
agent: Agent,
|
agent: Agent,
|
||||||
alignment: Alignment,
|
alignment: Alignment,
|
||||||
scale: Scale,
|
scale: Scale,
|
||||||
|
drop_item: Option<Item>,
|
||||||
) {
|
) {
|
||||||
server
|
let entity = server
|
||||||
.state
|
.state
|
||||||
.create_npc(pos, stats, loadout, body)
|
.create_npc(pos, stats, loadout, body)
|
||||||
.with(agent)
|
.with(agent)
|
||||||
.with(scale)
|
.with(scale)
|
||||||
.with(alignment)
|
.with(alignment);
|
||||||
.build();
|
|
||||||
|
let entity = if let Some(drop_item) = drop_item {
|
||||||
|
entity.with(ItemDrop(drop_item))
|
||||||
|
} else {
|
||||||
|
entity
|
||||||
|
};
|
||||||
|
|
||||||
|
entity.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_shoot(
|
pub fn handle_shoot(
|
||||||
|
@ -106,10 +106,17 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
|
|||||||
.ecs()
|
.ecs()
|
||||||
.write_storage()
|
.write_storage()
|
||||||
.insert(entity, Body::Object(object::Body::Pouch));
|
.insert(entity, Body::Object(object::Body::Pouch));
|
||||||
let _ = state.ecs().write_storage().insert(
|
|
||||||
entity,
|
let mut item_drops = state.ecs().write_storage::<comp::ItemDrop>();
|
||||||
assets::load_expect_cloned::<Item>("common.items.cheese"),
|
let item = if let Some(item_drop) = item_drops.get(entity).cloned() {
|
||||||
);
|
item_drops.remove(entity);
|
||||||
|
item_drop.0
|
||||||
|
} else {
|
||||||
|
assets::load_expect_cloned::<Item>("common.items.cheese")
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = state.ecs().write_storage().insert(entity, item);
|
||||||
|
|
||||||
state.ecs().write_storage::<comp::Stats>().remove(entity);
|
state.ecs().write_storage::<comp::Stats>().remove(entity);
|
||||||
state.ecs().write_storage::<comp::Agent>().remove(entity);
|
state.ecs().write_storage::<comp::Agent>().remove(entity);
|
||||||
state
|
state
|
||||||
|
@ -84,7 +84,10 @@ impl Server {
|
|||||||
agent,
|
agent,
|
||||||
alignment,
|
alignment,
|
||||||
scale,
|
scale,
|
||||||
} => handle_create_npc(self, pos, stats, loadout, body, agent, alignment, scale),
|
drop_item,
|
||||||
|
} => handle_create_npc(
|
||||||
|
self, pos, stats, loadout, body, agent, alignment, scale, drop_item,
|
||||||
|
),
|
||||||
ServerEvent::CreateWaypoint(pos) => handle_create_waypoint(self, pos),
|
ServerEvent::CreateWaypoint(pos) => handle_create_waypoint(self, pos),
|
||||||
ServerEvent::ClientDisconnect(entity) => {
|
ServerEvent::ClientDisconnect(entity) => {
|
||||||
frontend_events.push(handle_client_disconnect(self, entity))
|
frontend_events.push(handle_client_disconnect(self, entity))
|
||||||
|
@ -245,11 +245,15 @@ impl<'a> System<'a> for Sys {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut scale = 1.0;
|
let mut scale = entity.scale;
|
||||||
|
|
||||||
// TODO: Remove this and implement scaling or level depending on stuff like
|
// TODO: Remove this and implement scaling or level depending on stuff like
|
||||||
// species instead
|
// species instead
|
||||||
stats.level.set_level(rand::thread_rng().gen_range(1, 9));
|
stats.level.set_level(
|
||||||
|
entity.level.unwrap_or_else(|| {
|
||||||
|
(rand::thread_rng().gen_range(1, 9) as f32 * scale) as u32
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
// Replace stuff if it's a boss
|
// Replace stuff if it's a boss
|
||||||
if entity.is_giant {
|
if entity.is_giant {
|
||||||
@ -336,6 +340,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
alignment,
|
alignment,
|
||||||
agent: comp::Agent::default().with_patrol_origin(entity.pos),
|
agent: comp::Agent::default().with_patrol_origin(entity.pos),
|
||||||
scale: comp::Scale(scale),
|
scale: comp::Scale(scale),
|
||||||
|
drop_item: entity.loot_drop,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
37
world/examples/namegen.rs
Normal file
37
world/examples/namegen.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use rand::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cons = vec![
|
||||||
|
"d", "f", "ph", "r", "st", "t", "s", "p", "sh", "th", "br", "tr", "m", "k", "st", "w", "y",
|
||||||
|
];
|
||||||
|
let mut start = cons.clone();
|
||||||
|
start.extend(vec![
|
||||||
|
"cr", "thr", "str", "br", "ivy", "est", "ost", "ing", "kr", "in", "on", "tr", "tw", "wh",
|
||||||
|
"eld", "ar", "or", "ear", "ir",
|
||||||
|
]);
|
||||||
|
let mut middle = cons.clone();
|
||||||
|
middle.extend(vec!["tt"]);
|
||||||
|
let vowel = vec!["o", "e", "a", "i", "u", "au", "ee", "ow", "ay", "ey", "oe"];
|
||||||
|
let end = vec![
|
||||||
|
"et", "ige", "age", "ist", "en", "on", "og", "end", "ind", "ock", "een", "edge", "ist",
|
||||||
|
"ed", "est", "eed", "ast", "olt", "ey", "ean", "ead", "onk", "ink", "eon", "er", "ow",
|
||||||
|
"cot", "in", "on",
|
||||||
|
];
|
||||||
|
|
||||||
|
let gen_name = || {
|
||||||
|
let mut name = String::new();
|
||||||
|
|
||||||
|
name += start.choose(&mut thread_rng()).unwrap();
|
||||||
|
if thread_rng().gen() {
|
||||||
|
name += vowel.choose(&mut thread_rng()).unwrap();
|
||||||
|
name += middle.choose(&mut thread_rng()).unwrap();
|
||||||
|
}
|
||||||
|
name += end.choose(&mut thread_rng()).unwrap();
|
||||||
|
|
||||||
|
name
|
||||||
|
};
|
||||||
|
|
||||||
|
for _ in 0..20 {
|
||||||
|
println!("{}", gen_name());
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ use common::{
|
|||||||
astar::Astar,
|
astar::Astar,
|
||||||
comp,
|
comp,
|
||||||
generation::{ChunkSupplement, EntityInfo},
|
generation::{ChunkSupplement, EntityInfo},
|
||||||
|
npc,
|
||||||
store::{Id, Store},
|
store::{Id, Store},
|
||||||
terrain::{Block, BlockKind, Structure, TerrainChunkSize},
|
terrain::{Block, BlockKind, Structure, TerrainChunkSize},
|
||||||
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
|
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
|
||||||
@ -49,6 +50,8 @@ pub struct GenCtx<'a, R: Rng> {
|
|||||||
|
|
||||||
const ALT_OFFSET: i32 = -2;
|
const ALT_OFFSET: i32 = -2;
|
||||||
|
|
||||||
|
const LEVELS: usize = 5;
|
||||||
|
|
||||||
impl Dungeon {
|
impl Dungeon {
|
||||||
pub fn generate(wpos: Vec2<i32>, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self {
|
pub fn generate(wpos: Vec2<i32>, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self {
|
||||||
let mut ctx = GenCtx { sim, rng };
|
let mut ctx = GenCtx { sim, rng };
|
||||||
@ -61,9 +64,9 @@ impl Dungeon {
|
|||||||
+ 6,
|
+ 6,
|
||||||
seed: ctx.rng.gen(),
|
seed: ctx.rng.gen(),
|
||||||
noise: RandomField::new(ctx.rng.gen()),
|
noise: RandomField::new(ctx.rng.gen()),
|
||||||
floors: (0..6)
|
floors: (0..LEVELS)
|
||||||
.scan(Vec2::zero(), |stair_tile, level| {
|
.scan(Vec2::zero(), |stair_tile, level| {
|
||||||
let (floor, st) = Floor::generate(&mut ctx, *stair_tile, level);
|
let (floor, st) = Floor::generate(&mut ctx, *stair_tile, level as i32);
|
||||||
*stair_tile = st;
|
*stair_tile = st;
|
||||||
Some(floor)
|
Some(floor)
|
||||||
})
|
})
|
||||||
@ -184,7 +187,7 @@ const TILE_SIZE: i32 = 13;
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Tile {
|
pub enum Tile {
|
||||||
UpStair,
|
UpStair,
|
||||||
DownStair,
|
DownStair(Id<Room>),
|
||||||
Room(Id<Room>),
|
Room(Id<Room>),
|
||||||
Tunnel,
|
Tunnel,
|
||||||
Solid,
|
Solid,
|
||||||
@ -194,7 +197,7 @@ impl Tile {
|
|||||||
fn is_passable(&self) -> bool {
|
fn is_passable(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Tile::UpStair => true,
|
Tile::UpStair => true,
|
||||||
Tile::DownStair => true,
|
Tile::DownStair(_) => true,
|
||||||
Tile::Room(_) => true,
|
Tile::Room(_) => true,
|
||||||
Tile::Tunnel => true,
|
Tile::Tunnel => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
@ -206,7 +209,10 @@ pub struct Room {
|
|||||||
seed: u32,
|
seed: u32,
|
||||||
loot_density: f32,
|
loot_density: f32,
|
||||||
enemy_density: Option<f32>,
|
enemy_density: Option<f32>,
|
||||||
|
boss: bool,
|
||||||
area: Rect<i32, i32>,
|
area: Rect<i32, i32>,
|
||||||
|
height: i32,
|
||||||
|
pillars: Option<i32>, // Pillars with the given separation
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Floor {
|
pub struct Floor {
|
||||||
@ -227,40 +233,69 @@ impl Floor {
|
|||||||
stair_tile: Vec2<i32>,
|
stair_tile: Vec2<i32>,
|
||||||
level: i32,
|
level: i32,
|
||||||
) -> (Self, Vec2<i32>) {
|
) -> (Self, Vec2<i32>) {
|
||||||
let new_stair_tile = std::iter::from_fn(|| {
|
let final_level = level == LEVELS as i32 - 1;
|
||||||
Some(FLOOR_SIZE.map(|sz| ctx.rng.gen_range(-sz / 2 + 2, sz / 2 - 1)))
|
|
||||||
})
|
let new_stair_tile = if final_level {
|
||||||
.filter(|pos| *pos != stair_tile)
|
Vec2::zero()
|
||||||
.take(8)
|
} else {
|
||||||
.max_by_key(|pos| (*pos - stair_tile).map(|e| e.abs()).sum())
|
std::iter::from_fn(|| {
|
||||||
.unwrap();
|
Some(FLOOR_SIZE.map(|sz| ctx.rng.gen_range(-sz / 2 + 2, sz / 2 - 1)))
|
||||||
|
})
|
||||||
|
.filter(|pos| *pos != stair_tile)
|
||||||
|
.take(8)
|
||||||
|
.max_by_key(|pos| (*pos - stair_tile).map(|e| e.abs()).sum())
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
let tile_offset = -FLOOR_SIZE / 2;
|
let tile_offset = -FLOOR_SIZE / 2;
|
||||||
let mut this = Floor {
|
let mut this = Floor {
|
||||||
tile_offset,
|
tile_offset,
|
||||||
tiles: Grid::new(FLOOR_SIZE, Tile::Solid),
|
tiles: Grid::new(FLOOR_SIZE, Tile::Solid),
|
||||||
rooms: Store::default(),
|
rooms: Store::default(),
|
||||||
solid_depth: if level == 0 { 80 } else { 13 * 2 },
|
solid_depth: if level == 0 { 80 } else { 32 },
|
||||||
hollow_depth: 13,
|
hollow_depth: 30,
|
||||||
stair_tile: new_stair_tile - tile_offset,
|
stair_tile: new_stair_tile - tile_offset,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const STAIR_ROOM_HEIGHT: i32 = 13;
|
||||||
// Create rooms for entrance and exit
|
// Create rooms for entrance and exit
|
||||||
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: None,
|
enemy_density: None,
|
||||||
|
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,
|
||||||
|
pillars: None,
|
||||||
});
|
});
|
||||||
this.tiles.set(stair_tile - tile_offset, Tile::UpStair);
|
this.tiles.set(stair_tile - tile_offset, Tile::UpStair);
|
||||||
this.create_room(Room {
|
if final_level {
|
||||||
seed: ctx.rng.gen(),
|
// Boss room
|
||||||
loot_density: 0.0,
|
this.create_room(Room {
|
||||||
enemy_density: None,
|
seed: ctx.rng.gen(),
|
||||||
area: Rect::from((new_stair_tile - tile_offset - 1, Extent2::broadcast(3))),
|
loot_density: 0.0,
|
||||||
});
|
enemy_density: Some(0.001), // Minions!
|
||||||
this.tiles
|
boss: true,
|
||||||
.set(new_stair_tile - tile_offset, Tile::DownStair);
|
area: Rect::from((new_stair_tile - tile_offset - 4, Extent2::broadcast(9))),
|
||||||
|
height: 30,
|
||||||
|
pillars: Some(2),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Create downstairs room
|
||||||
|
let downstair_room = this.create_room(Room {
|
||||||
|
seed: ctx.rng.gen(),
|
||||||
|
loot_density: 0.0,
|
||||||
|
enemy_density: None,
|
||||||
|
boss: false,
|
||||||
|
area: Rect::from((new_stair_tile - tile_offset - 1, Extent2::broadcast(3))),
|
||||||
|
height: STAIR_ROOM_HEIGHT,
|
||||||
|
pillars: None,
|
||||||
|
});
|
||||||
|
this.tiles.set(
|
||||||
|
new_stair_tile - tile_offset,
|
||||||
|
Tile::DownStair(downstair_room),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.create_rooms(ctx, level, 7);
|
this.create_rooms(ctx, level, 7);
|
||||||
// Create routes between all rooms
|
// Create routes between all rooms
|
||||||
@ -316,8 +351,11 @@ impl Floor {
|
|||||||
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.00004),
|
enemy_density: Some(0.001 + level as f32 * 0.00006),
|
||||||
|
boss: false,
|
||||||
area,
|
area,
|
||||||
|
height: ctx.rng.gen_range(10, 15),
|
||||||
|
pillars: None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -334,7 +372,7 @@ impl Floor {
|
|||||||
let transition = |_a: &Vec2<i32>, b: &Vec2<i32>| match self.tiles.get(*b) {
|
let transition = |_a: &Vec2<i32>, b: &Vec2<i32>| match self.tiles.get(*b) {
|
||||||
Some(Tile::Room(_)) | Some(Tile::Tunnel) => 1.0,
|
Some(Tile::Room(_)) | Some(Tile::Tunnel) => 1.0,
|
||||||
Some(Tile::Solid) => 25.0,
|
Some(Tile::Solid) => 25.0,
|
||||||
Some(Tile::UpStair) | Some(Tile::DownStair) => 0.0,
|
Some(Tile::UpStair) | Some(Tile::DownStair(_)) => 0.0,
|
||||||
_ => 100000.0,
|
_ => 100000.0,
|
||||||
};
|
};
|
||||||
let satisfied = |l: &Vec2<i32>| *l == b;
|
let satisfied = |l: &Vec2<i32>| *l == b;
|
||||||
@ -367,6 +405,7 @@ impl Floor {
|
|||||||
for x in area.min.x..area.max.x {
|
for x in area.min.x..area.max.x {
|
||||||
for y in area.min.y..area.max.y {
|
for y in area.min.y..area.max.y {
|
||||||
let tile_pos = Vec2::new(x, y).map(|e| e.div_euclid(TILE_SIZE)) - self.tile_offset;
|
let tile_pos = Vec2::new(x, y).map(|e| e.div_euclid(TILE_SIZE)) - self.tile_offset;
|
||||||
|
let wpos2d = origin.xy() + Vec2::new(x, y);
|
||||||
if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) {
|
if let Some(Tile::Room(room)) = self.tiles.get(tile_pos) {
|
||||||
let room = &self.rooms[*room];
|
let room = &self.rooms[*room];
|
||||||
|
|
||||||
@ -376,10 +415,20 @@ 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
|
||||||
|
.pillars
|
||||||
|
.map(|pillar_space| {
|
||||||
|
tile_pos
|
||||||
|
.map(|e| e.rem_euclid(pillar_space) == 0)
|
||||||
|
.reduce_and()
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
if room
|
if room
|
||||||
.enemy_density
|
.enemy_density
|
||||||
.map(|density| rng.gen_range(0, density.recip() as usize) == 0)
|
.map(|density| rng.gen_range(0, density.recip() as usize) == 0)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
|
&& !tile_is_pillar
|
||||||
{
|
{
|
||||||
// Bad
|
// Bad
|
||||||
let entity = EntityInfo::at(
|
let entity = EntityInfo::at(
|
||||||
@ -389,7 +438,7 @@ impl Floor {
|
|||||||
.map(|e| (RandomField::new(room.seed.wrapping_add(10 + e)).get(Vec3::from(tile_pos)) % 32) as i32 - 16)
|
.map(|e| (RandomField::new(room.seed.wrapping_add(10 + e)).get(Vec3::from(tile_pos)) % 32) as i32 - 16)
|
||||||
.map(|e| e as f32 / 16.0),
|
.map(|e| e as f32 / 16.0),
|
||||||
)
|
)
|
||||||
.do_if(RandomField::new(room.seed.wrapping_add(1)).chance(Vec3::from(tile_pos), 0.2), |e| e.into_giant())
|
.do_if(RandomField::new(room.seed.wrapping_add(1)).chance(Vec3::from(tile_pos), 0.2) && !room.boss, |e| e.into_giant())
|
||||||
.with_alignment(comp::Alignment::Enemy)
|
.with_alignment(comp::Alignment::Enemy)
|
||||||
.with_body(comp::Body::Humanoid(comp::humanoid::Body::random()))
|
.with_body(comp::Body::Humanoid(comp::humanoid::Body::random()))
|
||||||
.with_automatic_name()
|
.with_automatic_name()
|
||||||
@ -404,6 +453,46 @@ impl Floor {
|
|||||||
|
|
||||||
supplement.add_entity(entity);
|
supplement.add_entity(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if room.boss {
|
||||||
|
let boss_spawn_tile = room.area.center();
|
||||||
|
// Don't spawn the boss in a pillar
|
||||||
|
let boss_spawn_tile = boss_spawn_tile + if tile_is_pillar { 1 } else { 0 };
|
||||||
|
|
||||||
|
if tile_pos == boss_spawn_tile && tile_wcenter.xy() == wpos2d {
|
||||||
|
let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32))
|
||||||
|
.with_scale(4.0)
|
||||||
|
.with_level(rng.gen_range(50, 70))
|
||||||
|
.with_alignment(comp::Alignment::Enemy)
|
||||||
|
.with_body(comp::Body::Humanoid(comp::humanoid::Body::random()))
|
||||||
|
.with_name(format!(
|
||||||
|
"{}, Destroyer of Worlds",
|
||||||
|
npc::get_npc_name(npc::NpcKind::Humanoid)
|
||||||
|
))
|
||||||
|
.with_main_tool(assets::load_expect_cloned(
|
||||||
|
match rng.gen_range(0, 5) {
|
||||||
|
0 => "common.items.weapons.sword.starter_sword",
|
||||||
|
1 => "common.items.weapons.sword.short_sword_0",
|
||||||
|
2 => "common.items.weapons.sword.wood_sword",
|
||||||
|
3 => "common.items.weapons.sword.zweihander_sword_0",
|
||||||
|
_ => "common.items.weapons.hammer.hammer_1",
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_loot_drop(match rng.gen_range(0, 3) {
|
||||||
|
0 => comp::Item::expect_from_asset(
|
||||||
|
"common.items.boss_drops.lantern",
|
||||||
|
),
|
||||||
|
1 => comp::Item::expect_from_asset(
|
||||||
|
"common.items.boss_drops.potions",
|
||||||
|
),
|
||||||
|
_ => comp::Item::expect_from_asset(
|
||||||
|
"common.items.boss_drops.xp_potion",
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
supplement.add_entity(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -477,9 +566,20 @@ impl Floor {
|
|||||||
BlockMask::nothing()
|
BlockMask::nothing()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(Tile::Room(_)) | Some(Tile::DownStair)
|
Some(Tile::Room(room)) | Some(Tile::DownStair(room))
|
||||||
if dist_to_wall < wall_thickness
|
if dist_to_wall < wall_thickness
|
||||||
|| z as f32 >= self.hollow_depth as f32 - 13.0 * tunnel_dist.powf(4.0) =>
|
|| z as f32
|
||||||
|
>= self.rooms[*room].height as f32 * (1.0 - tunnel_dist.powf(4.0))
|
||||||
|
|| self.rooms[*room]
|
||||||
|
.pillars
|
||||||
|
.map(|pillar_space| {
|
||||||
|
tile_pos
|
||||||
|
.map(|e| e.rem_euclid(pillar_space) == 0)
|
||||||
|
.reduce_and()
|
||||||
|
&& rtile_pos.map(|e| e as f32).magnitude_squared()
|
||||||
|
< 3.5f32.powf(2.0)
|
||||||
|
})
|
||||||
|
.unwrap_or(false) =>
|
||||||
{
|
{
|
||||||
BlockMask::nothing()
|
BlockMask::nothing()
|
||||||
},
|
},
|
||||||
@ -492,7 +592,7 @@ impl Floor {
|
|||||||
empty
|
empty
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(Tile::DownStair) => {
|
Some(Tile::DownStair(_)) => {
|
||||||
make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), 0.0, 0.5, 9.0)
|
make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), 0.0, 0.5, 9.0)
|
||||||
.resolve_with(empty)
|
.resolve_with(empty)
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user