mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Use new dungeons in dungeon_voxel_export, remove old dungeon sampling function, and add more of world/examples to CI.
This commit is contained in:
parent
f6782e21b8
commit
957ba5d218
@ -6,7 +6,7 @@ code-quality:
|
||||
script:
|
||||
- ln -s /dockercache/target target
|
||||
- rm -r target/debug/incremental/* || echo "all good" # TMP FIX FOR 2021-03-22-nightly
|
||||
- cargo clippy --all-targets --locked --features="bin_csv,bin_graphviz,bin_bot,asset_tweak" -- -D warnings
|
||||
- cargo clippy --all-targets --locked --features="bin_compression,bin_csv,bin_graphviz,bin_bot,asset_tweak" -- -D warnings
|
||||
# Ensure that the veloren-voxygen default-publish feature builds as it excludes some default features
|
||||
- cargo clippy -p veloren-voxygen --locked --no-default-features --features="default-publish" -- -D warnings
|
||||
- cargo fmt --all -- --check
|
||||
|
@ -1,3 +1,4 @@
|
||||
#![allow(clippy::type_complexity)]
|
||||
use common::{
|
||||
spiral::Spiral2d,
|
||||
terrain::{chonk::Chonk, Block, BlockKind, SpriteKind},
|
||||
@ -15,6 +16,7 @@ use common_net::msg::compression::{
|
||||
use hashbrown::HashMap;
|
||||
use image::ImageBuffer;
|
||||
use num_traits::cast::FromPrimitive;
|
||||
use rayon::ThreadPoolBuilder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
@ -56,8 +58,7 @@ fn do_deflate_rle(data: &[u8]) -> Vec<u8> {
|
||||
|
||||
let mut encoder = DeflateEncoder::new(Vec::new(), CompressionOptions::rle());
|
||||
encoder.write_all(data).expect("Write error!");
|
||||
let compressed_data = encoder.finish().expect("Failed to finish compression!");
|
||||
compressed_data
|
||||
encoder.finish().expect("Failed to finish compression!")
|
||||
}
|
||||
|
||||
// Separate function so that it shows up differently on the flamegraph
|
||||
@ -66,8 +67,7 @@ fn do_deflate_flate2_zero(data: &[u8]) -> Vec<u8> {
|
||||
|
||||
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::new(0));
|
||||
encoder.write_all(data).expect("Write error!");
|
||||
let compressed_data = encoder.finish().expect("Failed to finish compression!");
|
||||
compressed_data
|
||||
encoder.finish().expect("Failed to finish compression!")
|
||||
}
|
||||
|
||||
fn do_deflate_flate2<const LEVEL: u32>(data: &[u8]) -> Vec<u8> {
|
||||
@ -75,8 +75,7 @@ fn do_deflate_flate2<const LEVEL: u32>(data: &[u8]) -> Vec<u8> {
|
||||
|
||||
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::new(LEVEL));
|
||||
encoder.write_all(data).expect("Write error!");
|
||||
let compressed_data = encoder.finish().expect("Failed to finish compression!");
|
||||
compressed_data
|
||||
encoder.finish().expect("Failed to finish compression!")
|
||||
}
|
||||
|
||||
fn chonk_to_dyna<V: Clone, S: RectVolSize, M: Clone, A: Access>(
|
||||
@ -489,6 +488,7 @@ impl VoxelImageEncoding for MixedEncodingDenseSprites {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn histogram_to_dictionary(histogram: &HashMap<Vec<u8>, usize>, dictionary: &mut Vec<u8>) {
|
||||
let mut tmp: Vec<(Vec<u8>, usize)> = histogram.iter().map(|(k, v)| (k.clone(), *v)).collect();
|
||||
tmp.sort_by_key(|(_, count)| *count);
|
||||
@ -507,13 +507,17 @@ fn histogram_to_dictionary(histogram: &HashMap<Vec<u8>, usize>, dictionary: &mut
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let pool = ThreadPoolBuilder::new().build().unwrap();
|
||||
common_frontend::init_stdout(None);
|
||||
println!("Loading world");
|
||||
let (world, index) = World::generate(59686, WorldOpts {
|
||||
seed_elements: true,
|
||||
world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()),
|
||||
..WorldOpts::default()
|
||||
});
|
||||
let (world, index) = World::generate(
|
||||
59686,
|
||||
WorldOpts {
|
||||
seed_elements: true,
|
||||
world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()),
|
||||
},
|
||||
&pool,
|
||||
);
|
||||
println!("Loaded world");
|
||||
const HISTOGRAMS: bool = false;
|
||||
let mut histogram: HashMap<Vec<u8>, usize> = HashMap::new();
|
||||
@ -523,45 +527,45 @@ fn main() {
|
||||
let k = 32;
|
||||
let sz = world.sim().get_size();
|
||||
|
||||
let mut sites = Vec::new();
|
||||
|
||||
sites.push(("center", sz / 2));
|
||||
sites.push((
|
||||
"dungeon",
|
||||
world
|
||||
.civs()
|
||||
.sites()
|
||||
.find(|s| s.is_dungeon())
|
||||
.map(|s| s.center.as_())
|
||||
.unwrap(),
|
||||
));
|
||||
sites.push((
|
||||
"town",
|
||||
world
|
||||
.civs()
|
||||
.sites()
|
||||
.find(|s| s.is_settlement())
|
||||
.map(|s| s.center.as_())
|
||||
.unwrap(),
|
||||
));
|
||||
sites.push((
|
||||
"castle",
|
||||
world
|
||||
.civs()
|
||||
.sites()
|
||||
.find(|s| s.is_castle())
|
||||
.map(|s| s.center.as_())
|
||||
.unwrap(),
|
||||
));
|
||||
sites.push((
|
||||
"tree",
|
||||
world
|
||||
.civs()
|
||||
.sites()
|
||||
.find(|s| matches!(s.kind, SiteKind::Tree))
|
||||
.map(|s| s.center.as_())
|
||||
.unwrap(),
|
||||
));
|
||||
let sites = vec![
|
||||
("center", sz / 2),
|
||||
(
|
||||
"dungeon",
|
||||
world
|
||||
.civs()
|
||||
.sites()
|
||||
.find(|s| s.is_dungeon())
|
||||
.map(|s| s.center.as_())
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
"town",
|
||||
world
|
||||
.civs()
|
||||
.sites()
|
||||
.find(|s| s.is_settlement())
|
||||
.map(|s| s.center.as_())
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
"castle",
|
||||
world
|
||||
.civs()
|
||||
.sites()
|
||||
.find(|s| s.is_castle())
|
||||
.map(|s| s.center.as_())
|
||||
.unwrap(),
|
||||
),
|
||||
(
|
||||
"tree",
|
||||
world
|
||||
.civs()
|
||||
.sites()
|
||||
.find(|s| matches!(s.kind, SiteKind::Tree))
|
||||
.map(|s| s.center.as_())
|
||||
.unwrap(),
|
||||
),
|
||||
];
|
||||
|
||||
const SKIP_DEFLATE_2_5: bool = false;
|
||||
const SKIP_DYNA: bool = true;
|
||||
@ -600,6 +604,7 @@ fn main() {
|
||||
let lz4chonk_pre = Instant::now();
|
||||
let lz4_chonk = lz4_with_dictionary(&bincode::serialize(&chunk).unwrap(), &[]);
|
||||
let lz4chonk_post = Instant::now();
|
||||
#[allow(clippy::reversed_empty_ranges)]
|
||||
for _ in 0..ITERS {
|
||||
let _deflate0_chonk =
|
||||
do_deflate_flate2_zero(&bincode::serialize(&chunk).unwrap());
|
||||
@ -1024,7 +1029,7 @@ fn main() {
|
||||
for (name, value) in totals.iter() {
|
||||
println!("Average {}: {}", name, *value / count as f32);
|
||||
}
|
||||
println!("");
|
||||
println!();
|
||||
for (name, time) in total_timings.iter() {
|
||||
println!("Average {} nanos: {:02}", name, *time / count as f32);
|
||||
}
|
||||
|
@ -6,28 +6,65 @@ use std::{
|
||||
type Result = std::io::Result<()>;
|
||||
|
||||
use common::{
|
||||
terrain::{Block, BlockKind},
|
||||
terrain::{Block, BlockKind, SpriteKind},
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
|
||||
};
|
||||
use rayon::ThreadPoolBuilder;
|
||||
use vek::{Vec2, Vec3};
|
||||
use veloren_world::{index::Index, IndexOwned, Land};
|
||||
use veloren_world::{
|
||||
sim::{FileOpts, WorldOpts, DEFAULT_WORLD_MAP},
|
||||
site2::{plot::PlotKind, Structure},
|
||||
CanvasInfo, Land, World,
|
||||
};
|
||||
|
||||
/// This exports a dungeon (structure only, no entities or sprites) to a
|
||||
/// MagicaVoxel .vox file
|
||||
|
||||
fn main() -> Result {
|
||||
common_frontend::init_stdout(None);
|
||||
let pool = ThreadPoolBuilder::new().build().unwrap();
|
||||
println!("Loading world");
|
||||
let (world, index) = World::generate(
|
||||
59686,
|
||||
WorldOpts {
|
||||
seed_elements: true,
|
||||
world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()),
|
||||
},
|
||||
&pool,
|
||||
);
|
||||
println!("Loaded world");
|
||||
let export_path = "dungeon.vox";
|
||||
let seed = 0;
|
||||
|
||||
println!("Saving into {}", export_path);
|
||||
let mut volume = ExportVol::new();
|
||||
let index = IndexOwned::new(Index::new(seed));
|
||||
let dungeon = veloren_world::site2::plot::Dungeon::generate(
|
||||
volume.size_xy().map(|p| p as i32 / 2),
|
||||
&Land::empty(),
|
||||
&mut rand::thread_rng(),
|
||||
);
|
||||
dungeon.apply_to(index.as_index_ref(), Vec2::new(0, 0), |_| None, &mut volume);
|
||||
let wpos = volume.size_xy().map(|p| p as i32 / 2);
|
||||
let site =
|
||||
veloren_world::site2::Site::generate_dungeon(&Land::empty(), &mut rand::thread_rng(), wpos);
|
||||
CanvasInfo::with_mock_canvas_info(index.as_index_ref(), world.sim(), |canvas| {
|
||||
for plot in site.plots() {
|
||||
if let PlotKind::Dungeon(dungeon) = plot.kind() {
|
||||
let (prim_tree, fills) = dungeon.render_collect(&site);
|
||||
|
||||
for (prim, fill) in fills {
|
||||
let aabb = fill.get_bounds(&prim_tree, prim);
|
||||
|
||||
for x in aabb.min.x..aabb.max.x {
|
||||
for y in aabb.min.y..aabb.max.y {
|
||||
for z in aabb.min.z..aabb.max.z {
|
||||
let pos = Vec3::new(x, y, z);
|
||||
|
||||
if let Some(block) = fill.sample_at(&prim_tree, prim, pos, &canvas)
|
||||
{
|
||||
let _ = volume.set(pos, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
volume.write(&mut File::create(export_path)?)
|
||||
}
|
||||
|
||||
@ -157,7 +194,9 @@ impl ExportVol {
|
||||
write_chunk(file, "RGBA", &|file| {
|
||||
file.write_all(&[220, 220, 255, 0])?; // Air
|
||||
file.write_all(&[100, 100, 100, 0])?; // Rock
|
||||
file.write_all(&[0; 4 * (256 - 2)])
|
||||
file.write_all(&[255, 0, 0, 0])?; // Sprite
|
||||
file.write_all(&[255, 0, 255, 0])?; // GlowingRock
|
||||
file.write_all(&[0; 4 * (256 - 4)])
|
||||
})?;
|
||||
|
||||
let chunks_end = file.stream_position()?;
|
||||
@ -200,9 +239,16 @@ impl WriteVol for ExportVol {
|
||||
.entry(model_pos)
|
||||
.or_default()
|
||||
.extend_from_slice(&[rel_pos.x, rel_pos.y, rel_pos.z, match vox.kind() {
|
||||
BlockKind::Air => 1,
|
||||
BlockKind::Air => {
|
||||
if !matches!(vox.get_sprite(), Some(SpriteKind::Empty)) {
|
||||
3
|
||||
} else {
|
||||
1
|
||||
}
|
||||
},
|
||||
BlockKind::Rock => 2,
|
||||
_ => 3,
|
||||
BlockKind::GlowingRock => 4,
|
||||
_ => 5,
|
||||
}]);
|
||||
Ok(vox)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ use image::{
|
||||
codecs::png::{CompressionType, FilterType, PngEncoder},
|
||||
ImageBuffer,
|
||||
};
|
||||
use rayon::ThreadPoolBuilder;
|
||||
use std::{fs::File, io::Write};
|
||||
use vek::*;
|
||||
use veloren_world::{
|
||||
@ -116,12 +117,16 @@ fn image_with_autorange<F: Fn(f32, f32, f32) -> [u8; 3], G: FnMut(u32, u32) -> f
|
||||
|
||||
fn main() {
|
||||
common_frontend::init_stdout(None);
|
||||
let pool = ThreadPoolBuilder::new().build().unwrap();
|
||||
println!("Loading world");
|
||||
let (world, _index) = World::generate(59686, WorldOpts {
|
||||
seed_elements: true,
|
||||
world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()),
|
||||
..WorldOpts::default()
|
||||
});
|
||||
let (world, _index) = World::generate(
|
||||
59686,
|
||||
WorldOpts {
|
||||
seed_elements: true,
|
||||
world_file: FileOpts::LoadAsset(DEFAULT_WORLD_MAP.into()),
|
||||
},
|
||||
&pool,
|
||||
);
|
||||
println!("Loaded world");
|
||||
|
||||
let land = Land::from_sim(world.sim());
|
||||
|
@ -49,6 +49,44 @@ impl<'a> CanvasInfo<'a> {
|
||||
pub fn chunks(&self) -> &'a WorldSim { self.chunks }
|
||||
|
||||
pub fn land(&self) -> Land<'_> { Land::from_sim(self.chunks) }
|
||||
|
||||
pub fn with_mock_canvas_info<A, F: for<'b> FnOnce(&CanvasInfo<'b>) -> A>(
|
||||
index: IndexRef<'a>,
|
||||
sim: &'a WorldSim,
|
||||
f: F,
|
||||
) -> A {
|
||||
let zcache_grid = Grid::populate_from(Vec2::broadcast(1), |_| None);
|
||||
let sim_chunk = SimChunk {
|
||||
chaos: 0.0,
|
||||
alt: 0.0,
|
||||
basement: 0.0,
|
||||
water_alt: 0.0,
|
||||
downhill: None,
|
||||
flux: 0.0,
|
||||
temp: 0.0,
|
||||
humidity: 0.0,
|
||||
rockiness: 0.0,
|
||||
tree_density: 0.0,
|
||||
forest_kind: crate::all::ForestKind::Palm,
|
||||
spawn_rate: 0.0,
|
||||
river: Default::default(),
|
||||
surface_veg: 0.0,
|
||||
sites: Vec::new(),
|
||||
place: None,
|
||||
path: Default::default(),
|
||||
cave: Default::default(),
|
||||
cliff_height: 0.0,
|
||||
contains_waypoint: false,
|
||||
};
|
||||
f(&CanvasInfo {
|
||||
wpos: Vec2::broadcast(0),
|
||||
column_grid: &zcache_grid,
|
||||
column_grid_border: 0,
|
||||
chunks: &sim,
|
||||
index,
|
||||
chunk: &sim_chunk,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Canvas<'a> {
|
||||
|
@ -157,7 +157,7 @@ impl Fill {
|
||||
tree: &Store<Primitive>,
|
||||
prim: Id<Primitive>,
|
||||
pos: Vec3<i32>,
|
||||
canvas: &Canvas,
|
||||
canvas_info: &crate::CanvasInfo,
|
||||
) -> Option<Block> {
|
||||
if self.contains_at(tree, prim, pos) {
|
||||
match self {
|
||||
@ -169,10 +169,9 @@ impl Fill {
|
||||
% *range as u32) as u8,
|
||||
)),
|
||||
Fill::Prefab(p, tr, seed) => p.get(pos - tr).ok().and_then(|sb| {
|
||||
let info = canvas.info;
|
||||
let col_sample = info.col(info.wpos)?;
|
||||
let col_sample = canvas_info.col(canvas_info.wpos)?;
|
||||
block_from_structure(
|
||||
canvas.index,
|
||||
canvas_info.index,
|
||||
*sb,
|
||||
pos - tr,
|
||||
p.get_bounds().center().xy(),
|
||||
|
@ -2,10 +2,10 @@ mod gen;
|
||||
pub mod plot;
|
||||
mod tile;
|
||||
|
||||
use self::{
|
||||
use self::tile::{HazardKind, KeepKind, Ori, RoofKind, Tile, TileGrid, TileKind, TILE_SIZE};
|
||||
pub use self::{
|
||||
gen::{aabr_with_z, Fill, Primitive, Structure},
|
||||
plot::{Plot, PlotKind},
|
||||
tile::{HazardKind, KeepKind, Ori, RoofKind, Tile, TileGrid, TileKind, TILE_SIZE},
|
||||
};
|
||||
use crate::{
|
||||
site::SpawnRules,
|
||||
@ -772,7 +772,8 @@ impl Site {
|
||||
for z in aabb.min.z..aabb.max.z {
|
||||
let pos = Vec3::new(x, y, z);
|
||||
|
||||
if let Some(block) = fill.sample_at(&prim_tree, prim, pos, &canvas) {
|
||||
if let Some(block) = fill.sample_at(&prim_tree, prim, pos, &canvas.info)
|
||||
{
|
||||
canvas.set(pos, block);
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ impl Plot {
|
||||
b.expanded_to_contain_point(*t)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> &PlotKind { &self.kind }
|
||||
}
|
||||
|
||||
pub enum PlotKind {
|
||||
|
@ -1,11 +1,9 @@
|
||||
use super::SpawnRules;
|
||||
use crate::{
|
||||
block::block_from_structure,
|
||||
column::ColumnSample,
|
||||
site::{namegen::NameGen, BlockMask},
|
||||
site::namegen::NameGen,
|
||||
site2::{self, aabr_with_z, Fill, Primitive, Structure as SiteStructure},
|
||||
util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS},
|
||||
IndexRef, Land,
|
||||
Land,
|
||||
};
|
||||
|
||||
use common::{
|
||||
@ -15,7 +13,7 @@ use common::{
|
||||
generation::{ChunkSupplement, EntityInfo},
|
||||
store::{Id, Store},
|
||||
terrain::{Block, BlockKind, SpriteKind, Structure, StructuresGroup, TerrainChunkSize},
|
||||
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, WriteVol},
|
||||
vol::RectVolSize,
|
||||
};
|
||||
use core::{f32, hash::BuildHasherDefault};
|
||||
use fxhash::FxHasher64;
|
||||
@ -128,79 +126,6 @@ impl Dungeon {
|
||||
|
||||
pub fn difficulty(&self) -> u32 { self.difficulty }
|
||||
|
||||
pub fn apply_to<'a>(
|
||||
&'a self,
|
||||
index: IndexRef,
|
||||
wpos2d: Vec2<i32>,
|
||||
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
||||
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
||||
) {
|
||||
lazy_static! {
|
||||
pub static ref ENTRANCES: AssetHandle<StructuresGroup> =
|
||||
Structure::load_group("dungeon_entrances");
|
||||
}
|
||||
|
||||
let entrances = ENTRANCES.read();
|
||||
let entrance = &entrances[self.seed as usize % entrances.len()];
|
||||
|
||||
for y in 0..vol.size_xy().y as i32 {
|
||||
for x in 0..vol.size_xy().x as i32 {
|
||||
let offs = Vec2::new(x, y);
|
||||
|
||||
let wpos2d = wpos2d + offs;
|
||||
let rpos = wpos2d - self.origin;
|
||||
|
||||
// Apply the dungeon entrance
|
||||
if let Some(col_sample) = get_column(offs) {
|
||||
for z in entrance.get_bounds().min.z..entrance.get_bounds().max.z {
|
||||
let wpos = Vec3::new(offs.x, offs.y, self.alt + z + ALT_OFFSET);
|
||||
let spos = Vec3::new(rpos.x - TILE_SIZE / 2, rpos.y - TILE_SIZE / 2, z);
|
||||
if let Some(block) = entrance
|
||||
.get(spos)
|
||||
.ok()
|
||||
.copied()
|
||||
.map(|sb| {
|
||||
block_from_structure(
|
||||
index,
|
||||
sb,
|
||||
spos,
|
||||
self.origin,
|
||||
self.seed,
|
||||
col_sample,
|
||||
// TODO: Take environment into account.
|
||||
Block::air,
|
||||
)
|
||||
})
|
||||
.unwrap_or(None)
|
||||
{
|
||||
let _ = vol.set(wpos, block);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Apply the dungeon internals
|
||||
let mut z = self.alt + ALT_OFFSET;
|
||||
for floor in &self.floors {
|
||||
z -= floor.total_depth();
|
||||
|
||||
let mut sampler = floor.col_sampler(
|
||||
index,
|
||||
rpos,
|
||||
z,
|
||||
// TODO: Take environment into account.
|
||||
Block::air,
|
||||
);
|
||||
|
||||
for rz in 0..floor.total_depth() {
|
||||
if let Some(block) = sampler(rz).finish() {
|
||||
let _ = vol.set(Vec3::new(offs.x, offs.y, z + rz), block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||
pub fn apply_supplement<'a>(
|
||||
&'a self,
|
||||
@ -695,10 +620,6 @@ impl Floor {
|
||||
|
||||
fn total_depth(&self) -> i32 { self.solid_depth + self.hollow_depth }
|
||||
|
||||
fn nearest_wall(&self, rpos: Vec2<i32>) -> Option<Vec2<i32>> {
|
||||
tilegrid_nearest_wall(&self.tiles, rpos)
|
||||
}
|
||||
|
||||
// Find orientation of a position relative to another position
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
fn relative_ori(pos1: Vec2<i32>, pos2: Vec2<i32>) -> u8 {
|
||||
@ -708,223 +629,6 @@ impl Floor {
|
||||
if pos1.x > pos2.x { 2 } else { 6 }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unnested_or_patterns)] // TODO: Pending review in #587
|
||||
fn col_sampler<'a>(
|
||||
&'a self,
|
||||
index: IndexRef<'a>,
|
||||
pos: Vec2<i32>,
|
||||
_floor_z: i32,
|
||||
mut with_sprite: impl FnMut(SpriteKind) -> Block,
|
||||
) -> impl FnMut(i32) -> BlockMask + 'a {
|
||||
let rpos = pos - self.tile_offset * TILE_SIZE;
|
||||
let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
|
||||
let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2;
|
||||
let rtile_pos = rpos - tile_center;
|
||||
|
||||
let colors = &index.colors.site.dungeon;
|
||||
|
||||
let vacant = BlockMask::new(with_sprite(SpriteKind::Empty), 1);
|
||||
let stone = BlockMask::new(Block::new(BlockKind::Rock, colors.stone.into()), 5);
|
||||
|
||||
let make_spiral_staircase =
|
||||
move |pos: Vec3<i32>, radius: f32, inner_radius: f32, stretch: f32| {
|
||||
if (pos.xy().magnitude_squared() as f32) < inner_radius.powi(2) {
|
||||
stone
|
||||
} else if (pos.xy().magnitude_squared() as f32) < radius.powi(2) {
|
||||
if ((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch
|
||||
+ pos.z as f32)
|
||||
.rem_euclid(stretch)
|
||||
< 1.5
|
||||
{
|
||||
stone
|
||||
} else {
|
||||
vacant
|
||||
}
|
||||
} else {
|
||||
BlockMask::nothing()
|
||||
}
|
||||
};
|
||||
let make_wall_staircase =
|
||||
move |pos: Vec3<i32>, radius: f32, stretch: f32, height_limit: i32| {
|
||||
if (pos.x.abs().max(pos.y.abs())) as f32 > 0.6 * radius && pos.z <= height_limit {
|
||||
if ((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch
|
||||
+ pos.z as f32)
|
||||
.rem_euclid(stretch)
|
||||
< 1.0
|
||||
{
|
||||
stone
|
||||
} else {
|
||||
vacant
|
||||
}
|
||||
} else {
|
||||
vacant
|
||||
}
|
||||
};
|
||||
let make_staircase = move |kind: &StairsKind,
|
||||
pos: Vec3<i32>,
|
||||
radius: f32,
|
||||
inner_radius: f32,
|
||||
stretch: f32,
|
||||
height_limit: i32| {
|
||||
match kind {
|
||||
StairsKind::Spiral => make_spiral_staircase(pos, radius, inner_radius, stretch),
|
||||
StairsKind::WallSpiral => {
|
||||
make_wall_staircase(pos, radius, stretch * 3.0, height_limit)
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let wall_thickness = 3.0;
|
||||
let dist_to_wall = self
|
||||
.nearest_wall(rpos)
|
||||
.map(|nearest| (nearest.distance_squared(rpos) as f32).sqrt())
|
||||
.unwrap_or(TILE_SIZE as f32);
|
||||
let tunnel_dist =
|
||||
1.0 - (dist_to_wall - wall_thickness).max(0.0) / (TILE_SIZE as f32 - wall_thickness);
|
||||
|
||||
let floor_sprite = if RandomField::new(7331).chance(Vec3::from(pos), 0.001) {
|
||||
BlockMask::new(
|
||||
with_sprite(
|
||||
match (RandomField::new(1337).get(Vec3::from(pos)) / 2) % 30 {
|
||||
0 => SpriteKind::Apple,
|
||||
1 => SpriteKind::VeloriteFrag,
|
||||
2 => SpriteKind::Velorite,
|
||||
3..=8 => SpriteKind::Mushroom,
|
||||
9..=15 => SpriteKind::FireBowlGround,
|
||||
_ => SpriteKind::ShortGrass,
|
||||
},
|
||||
),
|
||||
1,
|
||||
)
|
||||
} else if let Some(Tile::Room(room)) | Some(Tile::DownStair(room)) =
|
||||
self.tiles.get(tile_pos)
|
||||
{
|
||||
let room = &self.rooms[*room];
|
||||
if RandomField::new(room.seed).chance(Vec3::from(pos), room.loot_density * 0.5) {
|
||||
match room.difficulty {
|
||||
0 => BlockMask::new(with_sprite(SpriteKind::DungeonChest0), 1),
|
||||
1 => BlockMask::new(with_sprite(SpriteKind::DungeonChest1), 1),
|
||||
2 => BlockMask::new(with_sprite(SpriteKind::DungeonChest2), 1),
|
||||
3 => BlockMask::new(with_sprite(SpriteKind::DungeonChest3), 1),
|
||||
4 => BlockMask::new(with_sprite(SpriteKind::DungeonChest4), 1),
|
||||
5 => BlockMask::new(with_sprite(SpriteKind::DungeonChest5), 1),
|
||||
_ => BlockMask::new(with_sprite(SpriteKind::Chest), 1),
|
||||
}
|
||||
} else {
|
||||
vacant
|
||||
}
|
||||
} else {
|
||||
vacant
|
||||
};
|
||||
|
||||
let tunnel_height = if self.final_level { 16.0 } else { 8.0 };
|
||||
let pillar_thickness: i32 = 4;
|
||||
|
||||
move |z| match self.tiles.get(tile_pos) {
|
||||
Some(Tile::Solid) => BlockMask::nothing(),
|
||||
Some(Tile::Tunnel) => {
|
||||
let light_offset: i32 = 7;
|
||||
if (dist_to_wall - wall_thickness) as i32 == 1
|
||||
&& rtile_pos.map(|e| e % light_offset == 0).reduce_bitxor()
|
||||
&& z == 1
|
||||
{
|
||||
let ori =
|
||||
Floor::relative_ori(rpos, self.nearest_wall(rpos).unwrap_or_default());
|
||||
let furniture = SpriteKind::WallSconce;
|
||||
BlockMask::new(Block::air(furniture).with_ori(ori).unwrap(), 1)
|
||||
} else if dist_to_wall >= wall_thickness
|
||||
&& (z as f32) < tunnel_height * (1.0 - tunnel_dist.powi(4))
|
||||
{
|
||||
if z == 0 { floor_sprite } else { vacant }
|
||||
} else {
|
||||
BlockMask::nothing()
|
||||
}
|
||||
},
|
||||
Some(Tile::Room(room)) | Some(Tile::DownStair(room))
|
||||
if dist_to_wall < wall_thickness
|
||||
|| z as f32
|
||||
>= self.rooms[*room].height as f32 * (1.0 - tunnel_dist.powi(4)) =>
|
||||
{
|
||||
BlockMask::nothing()
|
||||
},
|
||||
|
||||
Some(Tile::Room(room)) | Some(Tile::DownStair(room))
|
||||
if 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()
|
||||
< (pillar_thickness as f32 + 0.5).powi(2)
|
||||
})
|
||||
.unwrap_or(false) =>
|
||||
{
|
||||
if z == 1 && rtile_pos.product() == 0 && rtile_pos.sum().abs() == pillar_thickness {
|
||||
let ori = Floor::relative_ori(rtile_pos, Vec2::zero());
|
||||
let furniture = SpriteKind::WallSconce;
|
||||
BlockMask::new(Block::air(furniture).with_ori(ori).unwrap(), 1)
|
||||
} else if z < self.rooms[*room].height
|
||||
&& rtile_pos.map(|e| e as f32).magnitude_squared()
|
||||
> (pillar_thickness as f32 - 0.5).powi(2)
|
||||
{
|
||||
vacant
|
||||
} else {
|
||||
BlockMask::nothing()
|
||||
}
|
||||
}
|
||||
|
||||
Some(Tile::Room(_)) => {
|
||||
let light_offset = 7;
|
||||
if z == 0 {
|
||||
floor_sprite
|
||||
} else if dist_to_wall as i32 == 4
|
||||
&& rtile_pos.map(|e| e % light_offset == 0).reduce_bitxor()
|
||||
&& z == 1
|
||||
{
|
||||
let ori = Floor::relative_ori(
|
||||
rpos,
|
||||
self.nearest_wall(rpos).unwrap_or_else(Vec2::zero),
|
||||
);
|
||||
let furniture = SpriteKind::WallSconce;
|
||||
BlockMask::new(Block::air(furniture).with_ori(ori).unwrap(), 1)
|
||||
} else {
|
||||
vacant
|
||||
}
|
||||
},
|
||||
Some(Tile::DownStair(_)) => vacant,
|
||||
Some(Tile::UpStair(room, kind)) => {
|
||||
let inner_radius: f32 = 0.5;
|
||||
let stretch = 9;
|
||||
let block = make_staircase(
|
||||
kind,
|
||||
Vec3::new(rtile_pos.x, rtile_pos.y, z),
|
||||
TILE_SIZE as f32 / 2.0,
|
||||
inner_radius,
|
||||
stretch as f32,
|
||||
self.total_depth(),
|
||||
);
|
||||
let furniture = SpriteKind::WallSconce;
|
||||
let ori = Floor::relative_ori(Vec2::zero(), rtile_pos);
|
||||
if z < self.rooms[*room].height {
|
||||
block.resolve_with(vacant)
|
||||
} else if z % stretch == 0 && rtile_pos.x == 0 && rtile_pos.y == -TILE_SIZE / 2 {
|
||||
BlockMask::new(Block::air(furniture).with_ori(ori).unwrap(), 1)
|
||||
} else {
|
||||
make_staircase(
|
||||
kind,
|
||||
Vec3::new(rtile_pos.x, rtile_pos.y, z),
|
||||
TILE_SIZE as f32 / 2.0,
|
||||
inner_radius,
|
||||
stretch as f32,
|
||||
self.total_depth(),
|
||||
)
|
||||
}
|
||||
},
|
||||
None => BlockMask::nothing(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enemy_0(dynamic_rng: &mut impl Rng, entity: EntityInfo) -> EntityInfo {
|
||||
|
Loading…
Reference in New Issue
Block a user