diff --git a/src/extensions.rs b/src/extensions.rs index 4fe89b0..bade05a 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,4 +1,4 @@ -use log::{error, info, warn, debug}; +use log::{debug, error, info, warn}; use regex::Regex; use rhai::{CustomType, Engine, EvalAltResult, FnPtr, Scope, TypeBuilder, AST}; use std::{ @@ -13,7 +13,8 @@ use std::{ use crate::{ error::AppError, player::Player, - utils::{send_chat_message, write_chat_stream}, + utils::{send_chat_message, stream_write_short, write_chat_stream}, + world::World, }; pub struct Extensions { @@ -88,12 +89,33 @@ impl Extensions { Ok(false) } + + pub fn run_event(&self, event_type: EventType, event: Event) -> Event { + let mut is_cancelled = false; + + for extension in &self.extensions { + if let Some(key_value) = extension.event_listeners.get(&event_type) { + is_cancelled = + match key_value.call::(&extension.engine, &extension.ast, (event,)) { + Ok(result) => result.is_cancelled, + Err(err) => { + error!("{} raised: {}", extension.metadata.name, err); + break; + } + }; + } + } + let mut response = Event::new(); + response.is_cancelled = is_cancelled; + response + } } pub struct Extension { ast: AST, engine: Engine, commands: HashMap, + event_listeners: HashMap, metadata: ExtensionMetadata, } @@ -236,18 +258,69 @@ impl PlayersWrapper { for i in 0..255 { if players[i].id != 255 { - players[i] - .outgoing_data - .extend_from_slice(data); + players[i].outgoing_data.extend_from_slice(data); } } Ok(()) } + fn username(self, player: u8) -> String { + let players = self.0.lock().unwrap(); + + players[player as usize].username.clone() + } + fn build_extra(builder: &mut TypeBuilder) { builder.with_fn("send_message", Self::send_message); builder.with_fn("send_all", Self::send_all); + builder.with_fn("username", Self::username); + } +} + +#[derive(Debug, Clone, CustomType)] +#[rhai_type(name = "WorldWrapper", extra=Self::build_extra)] +pub struct WorldWrapper(Arc>); + +impl WorldWrapper { + pub fn new(world: Arc>) -> Self { + Self(world) + } + + pub fn set_block(self, players_wrapper: PlayersWrapper, position: Vec3, block_type: u8) { + let mut world_dat = self.0.lock().unwrap(); + + let world_offset: u32 = position.x as u32 + + (position.z as u32 * world_dat.size_x as u32) + + (position.y as u32 * world_dat.size_x as u32 * world_dat.size_z as u32); + + world_dat.data[world_offset as usize] = block_type; + + let mut update_block_bytes: Vec = Vec::new(); + update_block_bytes.push(0x06); + update_block_bytes.extend_from_slice(&stream_write_short(position.x)); + update_block_bytes.extend_from_slice(&stream_write_short(position.y)); + update_block_bytes.extend_from_slice(&stream_write_short(position.z)); + update_block_bytes.push(block_type); + + let mut players = players_wrapper.0.lock().unwrap(); + for i in 0..players.len() { + if players[i].id != 255 { + players[i] + .outgoing_data + .extend_from_slice(&update_block_bytes); + } + } + } + + // TODO: Finish this + // pub fn get_block(&self, position: Vec3) -> u8 { + // let mut world = self.0.lock().unwrap(); + // } + + fn build_extra(builder: &mut TypeBuilder) { + builder.with_fn("set_block", Self::set_block); + // builder.with_fn("get_block", Self::get_block); } } @@ -257,21 +330,21 @@ struct Context { #[rhai_type(skip)] commands: HashMap, #[rhai_type(skip)] - event_listener: HashMap, + event_listener: HashMap, } -#[derive(Debug, Clone, PartialEq, CustomType)] +#[derive(Debug, Clone, Copy, PartialEq, CustomType)] #[rhai_type(name = "Vec3", extra = Self::build_extra)] -struct Vec3 { - x: i64, - y: i64, - z: i64, +pub struct Vec3 { + pub x: i16, + pub y: i16, + pub z: i16, } // Custom type API impl Vec3 { fn new(x: i64, y: i64, z: i64) -> Self { - Self { x, y, z } + Self { x: x.try_into().unwrap(), y: y.try_into().unwrap(), z: z.try_into().unwrap() } } fn build_extra(builder: &mut TypeBuilder) { @@ -280,8 +353,38 @@ impl Vec3 { } #[derive(Debug, Clone, Eq, Hash, PartialEq)] -enum EventListener { +pub enum EventType { BlockBreak, + PlayerLeave, +} + +#[derive(Debug, Clone, Copy, CustomType)] +#[rhai_type(name = "Event", extra = Self::build_extra)] +pub struct Event { + pub player: u8, + pub position: Vec3, + pub selected_block: u8, + pub is_cancelled: bool, +} + +impl Event { + pub fn new() -> Event { + Event { + player: 255, + position: (Vec3 { x: 0, y: 0, z: 0 }), + selected_block: 0, + is_cancelled: false, + } + } + + pub fn cancel(&mut self) { + self.is_cancelled = true; + } + + fn build_extra(builder: &mut TypeBuilder) { + builder.with_fn("Event", Self::new); + builder.with_fn("cancel", Self::cancel); + } } impl Context { @@ -297,9 +400,10 @@ impl Context { } fn add_event_listener(&mut self, event: &str, callback: FnPtr) { - let event_listener: EventListener = match event { - "block_break" => EventListener::BlockBreak, - _ => return, + let event_listener: EventType = match event { + "block_break" => EventType::BlockBreak, + "player_leave" => EventType::PlayerLeave, + _ => {warn!("An event listener was created with invalid type: {}", event); return}, }; self.event_listener.insert(event_listener, callback); } @@ -313,7 +417,7 @@ impl Context { ////// END RHAI DEFINITIONS ////// impl Extensions { - pub fn init(players: PlayersWrapper) -> Result { + pub fn init(players: PlayersWrapper, world: WorldWrapper) -> Result { if !Path::new("./extensions/").exists() { let _ = fs::create_dir("./extensions/"); } @@ -333,11 +437,15 @@ impl Extensions { info!("Loading extension {}", extension_path.display()); let mut engine = Engine::new(); + engine.set_max_expr_depths(50, 50); engine.build_type::(); engine.build_type::(); engine.build_type::(); engine.build_type::(); + engine.build_type::(); engine.build_type::(); + engine.build_type::(); + engine.build_type::(); engine.register_fn("info", info); engine.register_fn("warn", warn); engine.register_fn("error", error); @@ -348,9 +456,10 @@ impl Extensions { Err(error) => { error!( "Rhai plugin compilation failed for {}, reason: {}", - extension_path.display(), error + extension_path.display(), + error ); - break; + continue; } }; let mut scope = Scope::new(); @@ -364,7 +473,7 @@ impl Extensions { extension_path.display(), error ); - break; + continue; } }; @@ -372,6 +481,7 @@ impl Extensions { ast, engine, commands: HashMap::new(), + event_listeners: HashMap::new(), metadata: extension_metadata, }; @@ -379,7 +489,10 @@ impl Extensions { &mut scope, ¤t_extension.ast, "init", - (PlayersWrapper::new(players.0.clone()),), + ( + PlayersWrapper::new(players.0.clone()), + WorldWrapper::new(world.0.clone()), + ), ) { Ok(result) => result, Err(error) => { @@ -387,7 +500,7 @@ impl Extensions { "Plugin {} failed to init: {}", current_extension.metadata.name, error ); - break; + continue; } }; @@ -397,6 +510,12 @@ impl Extensions { .insert(key.to_string(), value.clone()); } + for (key, value) in ctx.event_listener.iter() { + current_extension + .event_listeners + .insert(key.clone(), value.clone()); + } + info!( "Loaded {} v{}", current_extension.metadata.name, current_extension.metadata.version, diff --git a/src/extensions/fill.rhai b/src/extensions/fill.rhai index 4c429cf..c3040ac 100644 --- a/src/extensions/fill.rhai +++ b/src/extensions/fill.rhai @@ -2,42 +2,81 @@ fn metadata() { Metadata("fill", "illegitimate-egg", Version("1.0.0")) // Version test util } -fn init(players) { +fn init(players, world) { let ctx = Context(); let playerData = #{}; ctx.register_command("fill", |player, argv| { players.send_message(player, "Break two blocks to select area"); - playerData[player].command_step == 1; + playerData[player.to_string()] = #{command_step: 1}; }); ctx.add_event_listener("block_break", |event| { - if playerData[event.player].command_step == () { - playerData[event.player].command_step == 0; - } else { - switch playerData[event.player].command_step { + let player_data = playerData[event.player.to_string()]; + if player_data != () { + switch player_data.command_step { 0 => { // Do nothing, the command isn't in use } 1 => { - // Record the block position - playerData[event.player].firstBlock = event.position; + player_data.command_step += 1; + event.cancel(); + + player_data.firstBlock = event.position; + players.send_message(event.player, "Position 1 {" + event.position.x + ", " + event.position.y + ", " + event.position.z + "}"); } 2 => { - playerData[event.player].secondBlock = event.position; - // TODO: Implement the fill + player_data.command_step = 0; + event.cancel(); + + player_data.secondBlock = event.position; + + players.send_message(event.player, "Position 2 {" + event.position.x + ", " + event.position.y + ", " + event.position.z + "}"); + + if (player_data.firstBlock.x > player_data.secondBlock.x) { + let buffer = player_data.firstBlock.x; + player_data.firstBlock.x = player_data.secondBlock.x; + player_data.secondBlock.x = buffer; + } + if (player_data.firstBlock.y > player_data.secondBlock.y) { + let buffer = player_data.firstBlock.y; + player_data.firstBlock.y = player_data.secondBlock.y; + player_data.secondBlock.y = buffer; + } + if (player_data.firstBlock.z > player_data.secondBlock.z) { + let buffer = player_data.firstBlock.z; + player_data.firstBlock.z = player_data.secondBlock.z; + player_data.secondBlock.z = buffer; + } + + let filled_blocks = 0; + + for x in (player_data.firstBlock.x.to_int())..=(player_data.secondBlock.x.to_int()) { + for y in (player_data.firstBlock.y.to_int())..=(player_data.secondBlock.y.to_int()) { + for z in (player_data.firstBlock.z.to_int())..=(player_data.secondBlock.z.to_int()) { + world.set_block(players, Vec3(x, y, z), event.selected_block); + filled_blocks = filled_blocks + 1; + } + } + } + + players.send_message(event.player, "Filled " + filled_blocks + " blocks"); } _ => { error("Unreachable reached"); } } } + + + playerData[event.player.to_string()] = player_data; + return event; }); // NOTE: Currently this doesn't work, as player_leave isn't implemented ctx.add_event_listener("player_leave", |event| { - playerData[event.player].command_step = 0; + playerData[event.player.to_string()].command_step = 0; }); ctx diff --git a/src/extensions/ping-pong.rhai b/src/extensions/ping-pong.rhai index 2042aa1..cb7a69b 100644 --- a/src/extensions/ping-pong.rhai +++ b/src/extensions/ping-pong.rhai @@ -2,7 +2,7 @@ fn metadata() { Metadata("ping-pong", "illegitimate-egg", Version("1.0.0")) } -fn init(players) { +fn init(players, world) { let ctx = Context(); ctx.register_command("ping", |player, argv| { diff --git a/src/extensions/utils.rhai b/src/extensions/utils.rhai index 595227b..fe49b22 100644 --- a/src/extensions/utils.rhai +++ b/src/extensions/utils.rhai @@ -2,7 +2,7 @@ fn metadata() { Metadata("utils", "illegitimate-egg", Version("1.2.3-testp+testb")) // Version test util } -fn init(players) { +fn init(players, world) { let ctx = Context(); ctx.register_command("args", |player, argv| { diff --git a/src/main.rs b/src/main.rs index 108eb92..4118a09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ mod utils; mod world; use error::AppError; -use extensions::{Extensions, PlayersWrapper}; +use extensions::{Extensions, PlayersWrapper, WorldWrapper}; use network::handle_client; use player::{Player, SpecialPlayers}; use world::World; @@ -47,7 +47,10 @@ fn run() -> Result<(), AppError> { }) .expect("Error handling control C, save on exit will not work"); - let extensions = Arc::new(Extensions::init(PlayersWrapper::new(players_arc.clone()))?); + let extensions = Arc::new(Extensions::init( + PlayersWrapper::new(players_arc.clone()), + WorldWrapper::new(world_arc.clone()), + )?); info!("Server listening on {}", 25565); diff --git a/src/network.rs b/src/network.rs index 1dfe98e..2168229 100644 --- a/src/network.rs +++ b/src/network.rs @@ -1,4 +1,4 @@ -use log::{info, warn}; +use log::{debug, info, warn}; use std::io::prelude::*; use std::net::TcpStream; use std::sync::{Arc, Mutex}; @@ -7,7 +7,7 @@ use std::thread::sleep; use std::time::Duration; use crate::command::handle_command; -use crate::extensions::Extensions; +use crate::extensions::{Event, EventType, Extensions}; use crate::player::{Player, PlayerStatus, SpecialPlayers}; use crate::utils::*; use crate::world::World; @@ -115,6 +115,9 @@ pub fn handle_client( let mut payload_buffer = [0; 8]; // Short (2) + Short (2) + Short (2) + Byte (1) + Byte (1) let _ = stream.read(&mut payload_buffer); + let mut is_cancelled = false; + let mut previous_block: u8 = 0; + let position_x = ((payload_buffer[0] as i16) << (8 as i16)) + payload_buffer[1] as i16; let position_y = @@ -125,6 +128,21 @@ pub fn handle_client( let mode = payload_buffer[6]; let mut block_type = payload_buffer[7]; { + if mode == 0x00 { + // EVENT: BLOCK BREAK + let mut event = Event::new(); + event.player = client_number; + event.position.x = position_x; + event.position.y = position_y; + event.position.z = position_z; + event.selected_block = block_type; + event = extensions.run_event(EventType::BlockBreak, event); + + is_cancelled = event.is_cancelled; + + block_type = 0x00; // Air + } + let mut world_dat = world_arc_clone.lock().unwrap(); // Sanity check (Stop losers from losing) @@ -139,32 +157,43 @@ pub fn handle_client( break; } - if mode == 0x00 { - block_type = 0x00; // Air - } - let world_offset: u32 = position_x as u32 + (position_z as u32 * world_dat.size_x as u32) + (position_y as u32 * world_dat.size_x as u32 * world_dat.size_z as u32); - world_dat.data[world_offset as usize] = block_type; + if !is_cancelled { + world_dat.data[world_offset as usize] = block_type; + } else { + previous_block = world_dat.data[world_offset as usize]; + } } - let mut update_block_bytes: Vec = Vec::new(); - update_block_bytes.push(0x06); - update_block_bytes.extend_from_slice(&stream_write_short(position_x)); - update_block_bytes.extend_from_slice(&stream_write_short(position_y)); - update_block_bytes.extend_from_slice(&stream_write_short(position_z)); - update_block_bytes.push(block_type); + if !is_cancelled { + let mut update_block_bytes: Vec = Vec::new(); + update_block_bytes.push(0x06); + update_block_bytes.extend_from_slice(&stream_write_short(position_x)); + update_block_bytes.extend_from_slice(&stream_write_short(position_y)); + update_block_bytes.extend_from_slice(&stream_write_short(position_z)); + update_block_bytes.push(block_type); - let mut players = players_arc_clone.lock().unwrap(); - for i in 0..players.len() { - if players[i].id != 255 && players[i].id != client_number { - players[i] - .outgoing_data - .extend_from_slice(&update_block_bytes); + let mut players = players_arc_clone.lock().unwrap(); + for i in 0..players.len() { + if players[i].id != 255 && players[i].id != client_number { + players[i] + .outgoing_data + .extend_from_slice(&update_block_bytes); + } } + } else { + let mut update_block_bytes: Vec = Vec::new(); + update_block_bytes.push(0x06); + update_block_bytes.extend_from_slice(&stream_write_short(position_x)); + update_block_bytes.extend_from_slice(&stream_write_short(position_y)); + update_block_bytes.extend_from_slice(&stream_write_short(position_z)); + update_block_bytes.push(previous_block); + + let _ = stream.write(&update_block_bytes); } } 0x08 => { @@ -305,6 +334,9 @@ pub fn handle_client( current_player.id = SpecialPlayers::SelfPlayer as u8; } + let mut event = Event::new(); + event.player = client_number; + extensions.run_event(EventType::PlayerLeave, event); info!( "Client {} disconnected, thread shutting down!", client_number diff --git a/src/world.wrld b/src/world.wrld index 2d6c30d..df0c445 100644 Binary files a/src/world.wrld and b/src/world.wrld differ