mirror of
https://github.com/zyllian/classics.git
synced 2025-05-10 12:16:40 -07:00
implement grass spread, resolves #9
This commit is contained in:
parent
8c96ecbbbd
commit
7940dbf7d4
5 changed files with 186 additions and 29 deletions
43
src/level.rs
43
src/level.rs
|
@ -9,7 +9,8 @@ use bevy_reflect::{PartialReflect, Struct};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::GeneralError, packet::server::ServerPacket, player::SavablePlayerData, util::neighbors,
|
error::GeneralError, packet::server::ServerPacket, player::SavablePlayerData,
|
||||||
|
util::neighbors_full,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::block::BLOCK_INFO;
|
use self::block::BLOCK_INFO;
|
||||||
|
@ -40,7 +41,11 @@ pub struct Level {
|
||||||
pub level_rules: LevelRules,
|
pub level_rules: LevelRules,
|
||||||
|
|
||||||
/// index of blocks which need to be updated in the next tick
|
/// index of blocks which need to be updated in the next tick
|
||||||
|
#[serde(default)]
|
||||||
pub awaiting_update: BTreeSet<usize>,
|
pub awaiting_update: BTreeSet<usize>,
|
||||||
|
/// index of blocks which are eligible for random tick updates
|
||||||
|
#[serde(default)]
|
||||||
|
pub possible_random_updates: Vec<usize>,
|
||||||
/// list of updates to apply to the world on the next tick
|
/// list of updates to apply to the world on the next tick
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub updates: Vec<BlockUpdate>,
|
pub updates: Vec<BlockUpdate>,
|
||||||
|
@ -62,6 +67,7 @@ impl Level {
|
||||||
weather: WeatherType::Sunny,
|
weather: WeatherType::Sunny,
|
||||||
level_rules: Default::default(),
|
level_rules: Default::default(),
|
||||||
awaiting_update: Default::default(),
|
awaiting_update: Default::default(),
|
||||||
|
possible_random_updates: Default::default(),
|
||||||
updates: Default::default(),
|
updates: Default::default(),
|
||||||
save_now: false,
|
save_now: false,
|
||||||
player_data: Default::default(),
|
player_data: Default::default(),
|
||||||
|
@ -106,11 +112,11 @@ impl Level {
|
||||||
z: z as i16,
|
z: z as i16,
|
||||||
block_type: update.block,
|
block_type: update.block,
|
||||||
});
|
});
|
||||||
for (nx, ny, nz) in neighbors(self, x, y, z) {
|
for (nx, ny, nz) in neighbors_full(self, x, y, z) {
|
||||||
let info = BLOCK_INFO
|
let info = BLOCK_INFO
|
||||||
.get(&self.get_block(nx, ny, nz))
|
.get(&self.get_block(nx, ny, nz))
|
||||||
.expect("missing block");
|
.expect("missing block");
|
||||||
if info.block_type.needs_update_when_neighbor_changed() {
|
if info.needs_update_when_neighbor_changed {
|
||||||
self.awaiting_update.insert(self.index(nx, ny, nz));
|
self.awaiting_update.insert(self.index(nx, ny, nz));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,7 +229,14 @@ impl From<u8> for WeatherType {
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, bevy_reflect::Reflect)]
|
#[derive(Debug, Clone, Serialize, Deserialize, bevy_reflect::Reflect)]
|
||||||
pub struct LevelRules {
|
pub struct LevelRules {
|
||||||
/// whether fluids should spread in the level
|
/// whether fluids should spread in the level
|
||||||
|
#[serde(default = "level_rules::fluid_spread")]
|
||||||
pub fluid_spread: bool,
|
pub fluid_spread: bool,
|
||||||
|
/// the number of blocks which should receive random tick updates
|
||||||
|
#[serde(default = "level_rules::random_tick_updates")]
|
||||||
|
pub random_tick_updates: u64,
|
||||||
|
/// the chance that grass will spread to an adjacent dirt block when randomly updated
|
||||||
|
#[serde(default = "level_rules::grass_spread_chance")]
|
||||||
|
pub grass_spread_chance: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LevelRules {
|
impl LevelRules {
|
||||||
|
@ -251,6 +264,7 @@ impl LevelRules {
|
||||||
pub fn set_rule(&mut self, name: &str, value: &str) -> Result<(), String> {
|
pub fn set_rule(&mut self, name: &str, value: &str) -> Result<(), String> {
|
||||||
let bool_type_id = TypeId::of::<bool>();
|
let bool_type_id = TypeId::of::<bool>();
|
||||||
let f64_type_id = TypeId::of::<f64>();
|
let f64_type_id = TypeId::of::<f64>();
|
||||||
|
let u64_type_id = TypeId::of::<u64>();
|
||||||
let string_type_id = TypeId::of::<String>();
|
let string_type_id = TypeId::of::<String>();
|
||||||
|
|
||||||
fn parse_and_apply<T>(value: &str, field_mut: &mut dyn PartialReflect) -> Result<(), String>
|
fn parse_and_apply<T>(value: &str, field_mut: &mut dyn PartialReflect) -> Result<(), String>
|
||||||
|
@ -278,6 +292,8 @@ impl LevelRules {
|
||||||
parse_and_apply::<bool>(value, field_mut)?;
|
parse_and_apply::<bool>(value, field_mut)?;
|
||||||
} else if id == f64_type_id {
|
} else if id == f64_type_id {
|
||||||
parse_and_apply::<f64>(value, field_mut)?;
|
parse_and_apply::<f64>(value, field_mut)?;
|
||||||
|
} else if id == u64_type_id {
|
||||||
|
parse_and_apply::<u64>(value, field_mut)?;
|
||||||
} else if id == string_type_id {
|
} else if id == string_type_id {
|
||||||
parse_and_apply::<String>(value, field_mut)?;
|
parse_and_apply::<String>(value, field_mut)?;
|
||||||
} else {
|
} else {
|
||||||
|
@ -288,8 +304,27 @@ impl LevelRules {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod level_rules {
|
||||||
|
pub fn fluid_spread() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_tick_updates() -> u64 {
|
||||||
|
1000
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grass_spread_chance() -> u64 {
|
||||||
|
2048
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for LevelRules {
|
impl Default for LevelRules {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { fluid_spread: true }
|
use level_rules::*;
|
||||||
|
Self {
|
||||||
|
fluid_spread: fluid_spread(),
|
||||||
|
random_tick_updates: random_tick_updates(),
|
||||||
|
grass_spread_chance: grass_spread_chance(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,10 @@ use crate::player::PlayerType;
|
||||||
/// the level of custom blocks supported by the server
|
/// the level of custom blocks supported by the server
|
||||||
pub const CUSTOM_BLOCKS_SUPPORT_LEVEL: u8 = 1;
|
pub const CUSTOM_BLOCKS_SUPPORT_LEVEL: u8 = 1;
|
||||||
|
|
||||||
|
pub const ID_AIR: u8 = 0x00;
|
||||||
pub const ID_STONE: u8 = 0x01;
|
pub const ID_STONE: u8 = 0x01;
|
||||||
|
pub const ID_GRASS: u8 = 0x02;
|
||||||
|
pub const ID_DIRT: u8 = 0x03;
|
||||||
pub const ID_WATER_FLOWING: u8 = 0x08;
|
pub const ID_WATER_FLOWING: u8 = 0x08;
|
||||||
pub const ID_WATER_STATIONARY: u8 = 0x09;
|
pub const ID_WATER_STATIONARY: u8 = 0x09;
|
||||||
pub const ID_LAVA_FLOWING: u8 = 0x0a;
|
pub const ID_LAVA_FLOWING: u8 = 0x0a;
|
||||||
|
@ -16,10 +19,21 @@ pub const ID_LAVA_STATIONARY: u8 = 0x0b;
|
||||||
/// information about all blocks implemented
|
/// information about all blocks implemented
|
||||||
pub static BLOCK_INFO: LazyLock<BTreeMap<u8, BlockInfo>> = LazyLock::new(|| {
|
pub static BLOCK_INFO: LazyLock<BTreeMap<u8, BlockInfo>> = LazyLock::new(|| {
|
||||||
[
|
[
|
||||||
(0x00, BlockInfo::new("air").block_type(BlockType::NonSolid)),
|
(
|
||||||
|
ID_AIR,
|
||||||
|
BlockInfo::new("air").block_type(BlockType::NonSolid),
|
||||||
|
),
|
||||||
(ID_STONE, BlockInfo::new("stone")),
|
(ID_STONE, BlockInfo::new("stone")),
|
||||||
(0x02, BlockInfo::new("grass")),
|
(
|
||||||
(0x03, BlockInfo::new("dirt")),
|
ID_GRASS,
|
||||||
|
BlockInfo::new("grass")
|
||||||
|
.needs_update_when_neighbor_changed()
|
||||||
|
.may_receive_random_ticks(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ID_DIRT,
|
||||||
|
BlockInfo::new("dirt").needs_update_when_neighbor_changed(),
|
||||||
|
),
|
||||||
(0x04, BlockInfo::new("cobblestone")),
|
(0x04, BlockInfo::new("cobblestone")),
|
||||||
(0x05, BlockInfo::new("planks")),
|
(0x05, BlockInfo::new("planks")),
|
||||||
(
|
(
|
||||||
|
@ -43,7 +57,8 @@ pub static BLOCK_INFO: LazyLock<BTreeMap<u8, BlockInfo>> = LazyLock::new(|| {
|
||||||
ID_WATER_STATIONARY,
|
ID_WATER_STATIONARY,
|
||||||
BlockInfo::new("water_stationary")
|
BlockInfo::new("water_stationary")
|
||||||
.block_type(BlockType::FluidStationary { moving: 0x08 })
|
.block_type(BlockType::FluidStationary { moving: 0x08 })
|
||||||
.perm(PlayerType::Moderator, PlayerType::Normal),
|
.perm(PlayerType::Moderator, PlayerType::Normal)
|
||||||
|
.needs_update_when_neighbor_changed(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
ID_LAVA_FLOWING,
|
ID_LAVA_FLOWING,
|
||||||
|
@ -58,7 +73,8 @@ pub static BLOCK_INFO: LazyLock<BTreeMap<u8, BlockInfo>> = LazyLock::new(|| {
|
||||||
ID_LAVA_STATIONARY,
|
ID_LAVA_STATIONARY,
|
||||||
BlockInfo::new("lava_stationary")
|
BlockInfo::new("lava_stationary")
|
||||||
.block_type(BlockType::FluidStationary { moving: 0x0a })
|
.block_type(BlockType::FluidStationary { moving: 0x0a })
|
||||||
.perm(PlayerType::Moderator, PlayerType::Normal),
|
.perm(PlayerType::Moderator, PlayerType::Normal)
|
||||||
|
.needs_update_when_neighbor_changed(),
|
||||||
),
|
),
|
||||||
(0x0c, BlockInfo::new("sand")),
|
(0x0c, BlockInfo::new("sand")),
|
||||||
(0x0d, BlockInfo::new("gravel")),
|
(0x0d, BlockInfo::new("gravel")),
|
||||||
|
@ -169,6 +185,10 @@ pub struct BlockInfo {
|
||||||
pub break_permissions: PlayerType,
|
pub break_permissions: PlayerType,
|
||||||
/// the block used as fallback if the client doesn't support it
|
/// the block used as fallback if the client doesn't support it
|
||||||
pub fallback: Option<u8>,
|
pub fallback: Option<u8>,
|
||||||
|
/// whether this block needs an update when its neighbor is changed
|
||||||
|
pub needs_update_when_neighbor_changed: bool,
|
||||||
|
/// whether this block may receive random ticks
|
||||||
|
pub may_receive_random_ticks: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockInfo {
|
impl BlockInfo {
|
||||||
|
@ -180,6 +200,8 @@ impl BlockInfo {
|
||||||
place_permissions: PlayerType::Normal,
|
place_permissions: PlayerType::Normal,
|
||||||
break_permissions: PlayerType::Normal,
|
break_permissions: PlayerType::Normal,
|
||||||
fallback: None,
|
fallback: None,
|
||||||
|
needs_update_when_neighbor_changed: false,
|
||||||
|
may_receive_random_ticks: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +224,18 @@ impl BlockInfo {
|
||||||
self.fallback = Some(fallback);
|
self.fallback = Some(fallback);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// marks this block as needing updates when its neighbor is changed
|
||||||
|
pub const fn needs_update_when_neighbor_changed(mut self) -> Self {
|
||||||
|
self.needs_update_when_neighbor_changed = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// makes this block capable of receiving random ticks and marks it as eligible when placed
|
||||||
|
pub const fn may_receive_random_ticks(mut self) -> Self {
|
||||||
|
self.may_receive_random_ticks = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// types of blocks
|
/// types of blocks
|
||||||
|
@ -233,13 +267,4 @@ impl BlockType {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// gets whether this block type needs an update when one of it's direct neighbors changes
|
|
||||||
#[allow(clippy::match_like_matches_macro)]
|
|
||||||
pub fn needs_update_when_neighbor_changed(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
BlockType::FluidStationary { .. } => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,20 +3,23 @@ pub(crate) mod network;
|
||||||
|
|
||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
|
use rand::{seq::SliceRandom, Rng};
|
||||||
use tokio::{net::TcpListener, sync::RwLock};
|
use tokio::{net::TcpListener, sync::RwLock};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::GeneralError,
|
error::GeneralError,
|
||||||
level::{
|
level::{
|
||||||
block::{
|
block::{
|
||||||
BlockType, BLOCK_INFO, ID_LAVA_FLOWING, ID_LAVA_STATIONARY, ID_STONE, ID_WATER_FLOWING,
|
BlockType, BLOCK_INFO, ID_DIRT, ID_GRASS, ID_LAVA_FLOWING, ID_LAVA_STATIONARY,
|
||||||
ID_WATER_STATIONARY,
|
ID_STONE, ID_WATER_FLOWING, ID_WATER_STATIONARY,
|
||||||
},
|
},
|
||||||
BlockUpdate, Level,
|
BlockUpdate, Level,
|
||||||
},
|
},
|
||||||
packet::server::ServerPacket,
|
packet::server::ServerPacket,
|
||||||
player::Player,
|
player::Player,
|
||||||
util::neighbors_minus_up,
|
util::{
|
||||||
|
get_relative_coords, neighbors_full, neighbors_minus_up, neighbors_with_vertical_diagonals,
|
||||||
|
},
|
||||||
CONFIG_FILE,
|
CONFIG_FILE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -209,12 +212,68 @@ fn tick(data: &mut ServerData, tick: usize) {
|
||||||
|
|
||||||
let mut packets = level.apply_updates();
|
let mut packets = level.apply_updates();
|
||||||
|
|
||||||
|
// apply random tick updates
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
level.possible_random_updates.shuffle(&mut rng);
|
||||||
|
for _ in 0..level.level_rules.random_tick_updates {
|
||||||
|
if let Some(index) = level.possible_random_updates.pop() {
|
||||||
|
level.awaiting_update.insert(index);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let awaiting_update = std::mem::take(&mut level.awaiting_update);
|
let awaiting_update = std::mem::take(&mut level.awaiting_update);
|
||||||
for index in awaiting_update {
|
for index in awaiting_update {
|
||||||
let (x, y, z) = level.coordinates(index);
|
let (x, y, z) = level.coordinates(index);
|
||||||
let block_id = level.get_block(x, y, z);
|
let block_id = level.get_block(x, y, z);
|
||||||
let block = BLOCK_INFO.get(&block_id).expect("should never fail");
|
let block = BLOCK_INFO.get(&block_id).expect("should never fail");
|
||||||
match &block.block_type {
|
match &block.block_type {
|
||||||
|
BlockType::Solid => {
|
||||||
|
if block_id == ID_GRASS {
|
||||||
|
let mut dirt_count = 0;
|
||||||
|
for (nx, ny, nz) in neighbors_with_vertical_diagonals(level, x, y, z) {
|
||||||
|
if level.get_block(nx, ny, nz) == ID_DIRT {
|
||||||
|
// only turn dirt into grass if there's empty space above it
|
||||||
|
if get_relative_coords(level, nx, ny, nz, 0, 1, 0)
|
||||||
|
.map(|(x, y, z)| level.get_block(x, y, z))
|
||||||
|
.is_none_or(|id| id == 0x00)
|
||||||
|
{
|
||||||
|
dirt_count += 1;
|
||||||
|
if rng.gen_range(0..level.level_rules.grass_spread_chance) == 0 {
|
||||||
|
dirt_count -= 1;
|
||||||
|
level.updates.push(BlockUpdate {
|
||||||
|
index: level.index(nx, ny, nz),
|
||||||
|
block: ID_GRASS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if get_relative_coords(level, x, y, z, 0, 1, 0)
|
||||||
|
.map(|(x, y, z)| level.get_block(x, y, z))
|
||||||
|
.is_some_and(|id| id != 0x00)
|
||||||
|
{
|
||||||
|
dirt_count += 1;
|
||||||
|
if rng.gen_range(0..level.level_rules.grass_spread_chance) == 0 {
|
||||||
|
dirt_count -= 1;
|
||||||
|
level.updates.push(BlockUpdate {
|
||||||
|
index: level.index(x, y, z),
|
||||||
|
block: ID_DIRT,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dirt_count > 0 {
|
||||||
|
level.possible_random_updates.push(level.index(x, y, z));
|
||||||
|
}
|
||||||
|
} else if block_id == ID_DIRT {
|
||||||
|
for (nx, ny, nz) in neighbors_full(level, x, y, z) {
|
||||||
|
if level.get_block(nx, ny, nz) == ID_GRASS {
|
||||||
|
level.possible_random_updates.push(level.index(nx, ny, nz));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BlockType::FluidFlowing {
|
BlockType::FluidFlowing {
|
||||||
stationary,
|
stationary,
|
||||||
ticks_to_spread,
|
ticks_to_spread,
|
||||||
|
|
|
@ -14,7 +14,10 @@ use tokio::{
|
||||||
use crate::{
|
use crate::{
|
||||||
command::Command,
|
command::Command,
|
||||||
error::GeneralError,
|
error::GeneralError,
|
||||||
level::{block::BLOCK_INFO, BlockUpdate, Level},
|
level::{
|
||||||
|
block::{BLOCK_INFO, ID_AIR},
|
||||||
|
BlockUpdate, Level,
|
||||||
|
},
|
||||||
packet::{
|
packet::{
|
||||||
client::ClientPacket, server::ServerPacket, ExtBitmask, PacketWriter, ARRAY_LENGTH,
|
client::ClientPacket, server::ServerPacket, ExtBitmask, PacketWriter, ARRAY_LENGTH,
|
||||||
EXTENSION_MAGIC_NUMBER, STRING_LENGTH,
|
EXTENSION_MAGIC_NUMBER, STRING_LENGTH,
|
||||||
|
@ -326,7 +329,7 @@ async fn handle_stream_inner(
|
||||||
mode,
|
mode,
|
||||||
block_type,
|
block_type,
|
||||||
} => {
|
} => {
|
||||||
let block_type = if mode == 0x00 { 0 } else { block_type };
|
let block_type = if mode == 0x00 { ID_AIR } else { block_type };
|
||||||
let mut data = data.write().await;
|
let mut data = data.write().await;
|
||||||
|
|
||||||
// kick players if they attempt to place a block out of bounds
|
// kick players if they attempt to place a block out of bounds
|
||||||
|
@ -383,6 +386,9 @@ async fn handle_stream_inner(
|
||||||
if new_block_info.block_type.needs_update_on_place() {
|
if new_block_info.block_type.needs_update_on_place() {
|
||||||
data.level.awaiting_update.insert(index);
|
data.level.awaiting_update.insert(index);
|
||||||
}
|
}
|
||||||
|
if new_block_info.may_receive_random_ticks {
|
||||||
|
data.level.possible_random_updates.push(index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ClientPacket::PositionOrientation {
|
ClientPacket::PositionOrientation {
|
||||||
_player_id_or_held_block: _,
|
_player_id_or_held_block: _,
|
||||||
|
|
40
src/util.rs
40
src/util.rs
|
@ -9,10 +9,10 @@ const NEIGHBORS: &[(isize, isize, isize)] = &[
|
||||||
(0, 0, 1),
|
(0, 0, 1),
|
||||||
];
|
];
|
||||||
|
|
||||||
/// gets a block's direct neighbors which are in the bounds of the level
|
// /// gets a block's direct neighbors which are in the bounds of the level
|
||||||
pub fn neighbors(level: &Level, x: usize, y: usize, z: usize) -> Vec<(usize, usize, usize)> {
|
// pub fn neighbors(level: &Level, x: usize, y: usize, z: usize) -> Vec<(usize, usize, usize)> {
|
||||||
get_many_relative_coords(level, x, y, z, NEIGHBORS.iter().copied())
|
// get_many_relative_coords(level, x, y, z, NEIGHBORS.iter().copied())
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// gets a blocks direct neighbors (excluding above the block) which are in the bounds of the level
|
/// gets a blocks direct neighbors (excluding above the block) which are in the bounds of the level
|
||||||
pub fn neighbors_minus_up(
|
pub fn neighbors_minus_up(
|
||||||
|
@ -24,6 +24,38 @@ pub fn neighbors_minus_up(
|
||||||
get_many_relative_coords(level, x, y, z, NEIGHBORS.iter().skip(1).copied())
|
get_many_relative_coords(level, x, y, z, NEIGHBORS.iter().skip(1).copied())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// gets a block's neighbors (including vertical diagonals) which are in the bounds of the level, i.e. for grass spread
|
||||||
|
pub fn neighbors_with_vertical_diagonals(
|
||||||
|
level: &Level,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
z: usize,
|
||||||
|
) -> Vec<(usize, usize, usize)> {
|
||||||
|
let down = NEIGHBORS
|
||||||
|
.iter()
|
||||||
|
.skip(2)
|
||||||
|
.copied()
|
||||||
|
.map(|(x, _, z)| (x, -1isize, z));
|
||||||
|
let up = NEIGHBORS
|
||||||
|
.iter()
|
||||||
|
.skip(2)
|
||||||
|
.copied()
|
||||||
|
.map(|(x, _, z)| (x, 1isize, z));
|
||||||
|
get_many_relative_coords(
|
||||||
|
level,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
z,
|
||||||
|
NEIGHBORS.iter().skip(2).copied().chain(down).chain(up),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// gets a block's neighbors, including all diagonals
|
||||||
|
pub fn neighbors_full(level: &Level, x: usize, y: usize, z: usize) -> Vec<(usize, usize, usize)> {
|
||||||
|
let iter = (-1..=1).flat_map(|x| (-1..=1).flat_map(move |y| (-1..=1).map(move |z| (x, y, z))));
|
||||||
|
get_many_relative_coords(level, x, y, z, iter)
|
||||||
|
}
|
||||||
|
|
||||||
/// adds relative coordinates to the given ones, returning `None` if the coordinates would be out of bounds for hte level
|
/// adds relative coordinates to the given ones, returning `None` if the coordinates would be out of bounds for hte level
|
||||||
pub fn get_relative_coords(
|
pub fn get_relative_coords(
|
||||||
level: &Level,
|
level: &Level,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue