mirror of
https://github.com/zyllian/classics.git
synced 2025-01-18 11:47:14 -08:00
implement server ticks, block updates, and classic fluid mechanics
This commit is contained in:
parent
8dc89d959e
commit
492da31ce0
6 changed files with 285 additions and 39 deletions
56
src/level.rs
56
src/level.rs
|
@ -1,3 +1,9 @@
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use crate::{packet::server::ServerPacket, util::neighbors};
|
||||||
|
|
||||||
|
use self::block::BLOCK_INFO;
|
||||||
|
|
||||||
pub mod block;
|
pub mod block;
|
||||||
pub mod generation;
|
pub mod generation;
|
||||||
|
|
||||||
|
@ -13,6 +19,10 @@ pub struct Level {
|
||||||
|
|
||||||
/// the blocks which make up the level
|
/// the blocks which make up the level
|
||||||
pub blocks: Vec<u8>,
|
pub blocks: Vec<u8>,
|
||||||
|
/// index of blocks which need to be updated in the next tick
|
||||||
|
pub awaiting_update: BTreeSet<usize>,
|
||||||
|
/// list of updates to apply to the world on the next tick
|
||||||
|
pub updates: Vec<BlockUpdate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Level {
|
impl Level {
|
||||||
|
@ -23,6 +33,8 @@ impl Level {
|
||||||
y_size,
|
y_size,
|
||||||
z_size,
|
z_size,
|
||||||
blocks: vec![0; x_size * y_size * z_size],
|
blocks: vec![0; x_size * y_size * z_size],
|
||||||
|
awaiting_update: Default::default(),
|
||||||
|
updates: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +43,14 @@ impl Level {
|
||||||
x + z * self.x_size + y * self.x_size * self.z_size
|
x + z * self.x_size + y * self.x_size * self.z_size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// gets the coordinates for the given index
|
||||||
|
pub fn coordinates(&self, index: usize) -> (usize, usize, usize) {
|
||||||
|
let y = index / (self.x_size * self.z_size);
|
||||||
|
let z = (index / self.x_size) % self.z_size;
|
||||||
|
let x = index % self.z_size;
|
||||||
|
(x, y, z)
|
||||||
|
}
|
||||||
|
|
||||||
/// gets the block at the given position
|
/// gets the block at the given position
|
||||||
pub fn get_block(&self, x: usize, y: usize, z: usize) -> u8 {
|
pub fn get_block(&self, x: usize, y: usize, z: usize) -> u8 {
|
||||||
self.blocks[self.index(x, y, z)]
|
self.blocks[self.index(x, y, z)]
|
||||||
|
@ -41,4 +61,40 @@ impl Level {
|
||||||
let index = self.index(x, y, z);
|
let index = self.index(x, y, z);
|
||||||
self.blocks[index] = block;
|
self.blocks[index] = block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// applies the level's queued updates
|
||||||
|
pub fn apply_updates(&mut self) -> Vec<ServerPacket> {
|
||||||
|
self.updates.dedup_by(|a, b| a.index == b.index);
|
||||||
|
let mut packets = Vec::with_capacity(self.updates.len());
|
||||||
|
|
||||||
|
for update in std::mem::take(&mut self.updates) {
|
||||||
|
let (x, y, z) = self.coordinates(update.index);
|
||||||
|
self.blocks[update.index] = update.block;
|
||||||
|
packets.push(ServerPacket::SetBlock {
|
||||||
|
x: x as i16,
|
||||||
|
y: y as i16,
|
||||||
|
z: z as i16,
|
||||||
|
block_type: update.block,
|
||||||
|
});
|
||||||
|
for (nx, ny, nz) in neighbors(self, x, y, z) {
|
||||||
|
let info = BLOCK_INFO
|
||||||
|
.get(&self.get_block(nx, ny, nz))
|
||||||
|
.expect("missing block");
|
||||||
|
if info.block_type.needs_update_when_neighbor_changed() {
|
||||||
|
self.awaiting_update.insert(self.index(nx, ny, nz));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// struct describing a block update for the level to handle
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BlockUpdate {
|
||||||
|
/// the index of the block to be updated
|
||||||
|
pub index: usize,
|
||||||
|
/// the block type to set the block to
|
||||||
|
pub block: u8,
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,10 @@ pub static BLOCK_INFO: LazyLock<BTreeMap<u8, BlockInfo>> = LazyLock::new(|| {
|
||||||
(
|
(
|
||||||
0x08,
|
0x08,
|
||||||
BlockInfo::new("water_flowing")
|
BlockInfo::new("water_flowing")
|
||||||
.block_type(BlockType::FluidFlowing { stationary: 0x09 })
|
.block_type(BlockType::FluidFlowing {
|
||||||
|
stationary: 0x09,
|
||||||
|
ticks_to_spread: 1,
|
||||||
|
})
|
||||||
.perm(PlayerType::Operator, PlayerType::Normal),
|
.perm(PlayerType::Operator, PlayerType::Normal),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -36,7 +39,10 @@ pub static BLOCK_INFO: LazyLock<BTreeMap<u8, BlockInfo>> = LazyLock::new(|| {
|
||||||
(
|
(
|
||||||
0x0a,
|
0x0a,
|
||||||
BlockInfo::new("lava_flowing")
|
BlockInfo::new("lava_flowing")
|
||||||
.block_type(BlockType::FluidFlowing { stationary: 0x0b })
|
.block_type(BlockType::FluidFlowing {
|
||||||
|
stationary: 0x0b,
|
||||||
|
ticks_to_spread: 5,
|
||||||
|
})
|
||||||
.perm(PlayerType::Operator, PlayerType::Normal),
|
.perm(PlayerType::Operator, PlayerType::Normal),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -152,7 +158,30 @@ pub enum BlockType {
|
||||||
/// a slab
|
/// a slab
|
||||||
Slab,
|
Slab,
|
||||||
/// fluid which is actively flowing
|
/// fluid which is actively flowing
|
||||||
FluidFlowing { stationary: u8 },
|
FluidFlowing {
|
||||||
|
stationary: u8,
|
||||||
|
ticks_to_spread: usize,
|
||||||
|
},
|
||||||
/// fluid which is stationary
|
/// fluid which is stationary
|
||||||
FluidStationary { moving: u8 },
|
FluidStationary { moving: u8 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BlockType {
|
||||||
|
/// gets whether this block type needs an update after being placed
|
||||||
|
#[allow(clippy::match_like_matches_macro)]
|
||||||
|
pub fn needs_update_on_place(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
BlockType::FluidFlowing { .. } => true,
|
||||||
|
_ => 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ mod level;
|
||||||
mod packet;
|
mod packet;
|
||||||
mod player;
|
mod player;
|
||||||
mod server;
|
mod server;
|
||||||
|
mod util;
|
||||||
|
|
||||||
const CONFIG_FILE: &str = "./server-config.json";
|
const CONFIG_FILE: &str = "./server-config.json";
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,19 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use tokio::{net::TcpListener, sync::RwLock};
|
use tokio::{net::TcpListener, sync::RwLock};
|
||||||
|
|
||||||
use crate::{level::Level, player::Player};
|
use crate::{
|
||||||
|
level::{
|
||||||
|
block::{BlockType, BLOCK_INFO},
|
||||||
|
BlockUpdate, Level,
|
||||||
|
},
|
||||||
|
player::Player,
|
||||||
|
util::neighbors_minus_up,
|
||||||
|
};
|
||||||
|
|
||||||
use self::config::ServerConfig;
|
use self::config::ServerConfig;
|
||||||
|
|
||||||
|
const TICK_DURATION: std::time::Duration = std::time::Duration::from_millis(50);
|
||||||
|
|
||||||
/// the server
|
/// the server
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
|
@ -64,6 +73,10 @@ impl Server {
|
||||||
|
|
||||||
/// starts the server
|
/// starts the server
|
||||||
pub async fn run(&mut self) -> std::io::Result<()> {
|
pub async fn run(&mut self) -> std::io::Result<()> {
|
||||||
|
let data = self.data.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
handle_ticks(data).await;
|
||||||
|
});
|
||||||
loop {
|
loop {
|
||||||
let (stream, addr) = self.listener.accept().await?;
|
let (stream, addr) = self.listener.accept().await?;
|
||||||
println!("connection from {addr}");
|
println!("connection from {addr}");
|
||||||
|
@ -76,3 +89,85 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// function to tick the server
|
||||||
|
async fn handle_ticks(data: Arc<RwLock<ServerData>>) {
|
||||||
|
let mut current_tick = 0;
|
||||||
|
loop {
|
||||||
|
tick(&mut *data.write().await, current_tick);
|
||||||
|
current_tick = current_tick.wrapping_add(1);
|
||||||
|
tokio::time::sleep(TICK_DURATION).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// function which ticks the server once
|
||||||
|
fn tick(data: &mut ServerData, tick: usize) {
|
||||||
|
let level = &mut data.level;
|
||||||
|
|
||||||
|
let mut packets = level.apply_updates();
|
||||||
|
|
||||||
|
let awaiting_update = std::mem::take(&mut level.awaiting_update);
|
||||||
|
if !awaiting_update.is_empty() {
|
||||||
|
println!("hm");
|
||||||
|
}
|
||||||
|
for index in awaiting_update {
|
||||||
|
let (x, y, z) = level.coordinates(index);
|
||||||
|
let block_id = level.get_block(x, y, z);
|
||||||
|
let block = BLOCK_INFO.get(&block_id).expect("should never fail");
|
||||||
|
println!("{block:#?}");
|
||||||
|
match &block.block_type {
|
||||||
|
BlockType::FluidFlowing {
|
||||||
|
stationary,
|
||||||
|
ticks_to_spread,
|
||||||
|
} => {
|
||||||
|
if tick % ticks_to_spread == 0 {
|
||||||
|
let update = BlockUpdate {
|
||||||
|
index,
|
||||||
|
block: *stationary,
|
||||||
|
};
|
||||||
|
level.updates.push(update);
|
||||||
|
for (nx, ny, nz) in neighbors_minus_up(level, x, y, z) {
|
||||||
|
let block_at = level.get_block(nx, ny, nz);
|
||||||
|
let update = if block_at == 0 {
|
||||||
|
level.awaiting_update.insert(level.index(nx, ny, nz));
|
||||||
|
BlockUpdate {
|
||||||
|
index: level.index(nx, ny, nz),
|
||||||
|
block: block_id,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
level.updates.push(update);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
level.awaiting_update.insert(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlockType::FluidStationary { moving } => {
|
||||||
|
let mut needs_update = false;
|
||||||
|
for (nx, ny, nz) in neighbors_minus_up(level, x, y, z) {
|
||||||
|
if level.get_block(nx, ny, nz) == 0 {
|
||||||
|
needs_update = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if needs_update {
|
||||||
|
let index = level.index(x, y, z);
|
||||||
|
level.updates.push(BlockUpdate {
|
||||||
|
index,
|
||||||
|
block: *moving,
|
||||||
|
});
|
||||||
|
level.awaiting_update.insert(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
packets.extend(level.apply_updates());
|
||||||
|
for packet in packets {
|
||||||
|
for player in &mut data.players {
|
||||||
|
player.packets_to_send.push(packet.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use tokio::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
level::{block::BLOCK_INFO, Level},
|
level::{block::BLOCK_INFO, BlockUpdate, Level},
|
||||||
packet::{client::ClientPacket, server::ServerPacket, PacketWriter, ARRAY_LENGTH},
|
packet::{client::ClientPacket, server::ServerPacket, PacketWriter, ARRAY_LENGTH},
|
||||||
player::{Player, PlayerType},
|
player::{Player, PlayerType},
|
||||||
server::config::ServerProtectionMode,
|
server::config::ServerProtectionMode,
|
||||||
|
@ -233,7 +233,18 @@ async fn handle_stream_inner(
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_block_info = BLOCK_INFO.get(&block_type);
|
let new_block_info = BLOCK_INFO.get(&block_type);
|
||||||
let mut cancel = new_block_info.is_none();
|
if new_block_info.is_none() {
|
||||||
|
reply_queue.push_back(ServerPacket::Message {
|
||||||
|
player_id: -1,
|
||||||
|
message: format!(
|
||||||
|
"Unknown block ID: 0x{:0x}",
|
||||||
|
block_type
|
||||||
|
),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let new_block_info = new_block_info.expect("will never fail");
|
||||||
|
let mut cancel = false;
|
||||||
let block =
|
let block =
|
||||||
data.level.get_block(x as usize, y as usize, z as usize);
|
data.level.get_block(x as usize, y as usize, z as usize);
|
||||||
let block_info = BLOCK_INFO
|
let block_info = BLOCK_INFO
|
||||||
|
@ -241,29 +252,23 @@ async fn handle_stream_inner(
|
||||||
.expect("missing block information for block!");
|
.expect("missing block information for block!");
|
||||||
|
|
||||||
// check if player has ability to place/break these blocks
|
// check if player has ability to place/break these blocks
|
||||||
if let Some(new_block_info) = new_block_info {
|
let player_type = data
|
||||||
let player_type = data
|
.players
|
||||||
.players
|
.iter()
|
||||||
.iter()
|
.find_map(|p| (p.id == *own_id).then_some(p.player_type))
|
||||||
.find_map(|p| {
|
.unwrap_or_default();
|
||||||
(p.id == *own_id).then_some(p.player_type)
|
if player_type < new_block_info.place_permissions {
|
||||||
})
|
cancel = true;
|
||||||
.unwrap_or_default();
|
reply_queue.push_back(ServerPacket::Message {
|
||||||
if player_type < new_block_info.place_permissions {
|
player_id: -1,
|
||||||
cancel = true;
|
message: "Not allowed to place this block.".to_string(),
|
||||||
reply_queue.push_back(ServerPacket::Message {
|
});
|
||||||
player_id: -1,
|
} else if player_type < block_info.break_permissions {
|
||||||
message: "Not allowed to place this block."
|
cancel = true;
|
||||||
.to_string(),
|
reply_queue.push_back(ServerPacket::Message {
|
||||||
});
|
player_id: -1,
|
||||||
} else if player_type < block_info.break_permissions {
|
message: "Not allowed to break this block.".to_string(),
|
||||||
cancel = true;
|
});
|
||||||
reply_queue.push_back(ServerPacket::Message {
|
|
||||||
player_id: -1,
|
|
||||||
message: "Not allowed to break this block."
|
|
||||||
.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cancel {
|
if cancel {
|
||||||
|
@ -275,16 +280,14 @@ async fn handle_stream_inner(
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let packet = ServerPacket::SetBlock {
|
let (x, y, z) = (x as usize, y as usize, z as usize);
|
||||||
x,
|
let index = data.level.index(x, y, z);
|
||||||
y,
|
data.level.updates.push(BlockUpdate {
|
||||||
z,
|
index,
|
||||||
block_type,
|
block: block_type,
|
||||||
};
|
});
|
||||||
data.level
|
if new_block_info.block_type.needs_update_on_place() {
|
||||||
.set_block(x as usize, y as usize, z as usize, block_type);
|
data.level.awaiting_update.insert(index);
|
||||||
for player in &mut data.players {
|
|
||||||
player.packets_to_send.push(packet.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClientPacket::PositionOrientation {
|
ClientPacket::PositionOrientation {
|
||||||
|
|
62
src/util.rs
Normal file
62
src/util.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use crate::level::Level;
|
||||||
|
|
||||||
|
const NEIGHBORS: &[(isize, isize, isize)] = &[
|
||||||
|
(0, 1, 0),
|
||||||
|
(0, -1, 0),
|
||||||
|
(-1, 0, 0),
|
||||||
|
(1, 0, 0),
|
||||||
|
(0, 0, -1),
|
||||||
|
(0, 0, 1),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// 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)> {
|
||||||
|
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
|
||||||
|
pub fn neighbors_minus_up(
|
||||||
|
level: &Level,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
z: usize,
|
||||||
|
) -> Vec<(usize, usize, usize)> {
|
||||||
|
get_many_relative_coords(level, x, y, z, NEIGHBORS.iter().skip(1).copied())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(
|
||||||
|
level: &Level,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
z: usize,
|
||||||
|
rx: isize,
|
||||||
|
ry: isize,
|
||||||
|
rz: isize,
|
||||||
|
) -> Option<(usize, usize, usize)> {
|
||||||
|
Some((
|
||||||
|
x.checked_add_signed(rx)
|
||||||
|
.and_then(|x| (x < level.x_size).then_some(x))?,
|
||||||
|
y.checked_add_signed(ry)
|
||||||
|
.and_then(|y| (y < level.y_size).then_some(y))?,
|
||||||
|
z.checked_add_signed(rz)
|
||||||
|
.and_then(|z| (z < level.z_size).then_some(z))?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// takes an iterator of relative changes to apply to the given coordinates and returns a `Vec` of the ones in bounds of the level
|
||||||
|
pub fn get_many_relative_coords<T>(
|
||||||
|
level: &Level,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
z: usize,
|
||||||
|
coords: T,
|
||||||
|
) -> Vec<(usize, usize, usize)>
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = (isize, isize, isize)>,
|
||||||
|
{
|
||||||
|
coords
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(rx, ry, rz)| get_relative_coords(level, x, y, z, rx, ry, rz))
|
||||||
|
.collect()
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue