AAAH MEGA EXTENSIONSSS RUUURH

This commit is contained in:
illegitimate-egg 2025-03-03 00:32:33 +00:00
parent 54d23ed073
commit 1666ef4cf2
7 changed files with 249 additions and 56 deletions

View File

@ -1,4 +1,4 @@
use log::{error, info, warn, debug}; use log::{debug, error, info, warn};
use regex::Regex; use regex::Regex;
use rhai::{CustomType, Engine, EvalAltResult, FnPtr, Scope, TypeBuilder, AST}; use rhai::{CustomType, Engine, EvalAltResult, FnPtr, Scope, TypeBuilder, AST};
use std::{ use std::{
@ -13,7 +13,8 @@ use std::{
use crate::{ use crate::{
error::AppError, error::AppError,
player::Player, player::Player,
utils::{send_chat_message, write_chat_stream}, utils::{send_chat_message, stream_write_short, write_chat_stream},
world::World,
}; };
pub struct Extensions { pub struct Extensions {
@ -88,12 +89,33 @@ impl Extensions {
Ok(false) 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::<Event>(&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 { pub struct Extension {
ast: AST, ast: AST,
engine: Engine, engine: Engine,
commands: HashMap<String, FnPtr>, commands: HashMap<String, FnPtr>,
event_listeners: HashMap<EventType, FnPtr>,
metadata: ExtensionMetadata, metadata: ExtensionMetadata,
} }
@ -236,18 +258,69 @@ impl PlayersWrapper {
for i in 0..255 { for i in 0..255 {
if players[i].id != 255 { if players[i].id != 255 {
players[i] players[i].outgoing_data.extend_from_slice(data);
.outgoing_data
.extend_from_slice(data);
} }
} }
Ok(()) 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<Self>) { fn build_extra(builder: &mut TypeBuilder<Self>) {
builder.with_fn("send_message", Self::send_message); builder.with_fn("send_message", Self::send_message);
builder.with_fn("send_all", Self::send_all); 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<Mutex<World>>);
impl WorldWrapper {
pub fn new(world: Arc<Mutex<World>>) -> 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<u8> = 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<Self>) {
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)] #[rhai_type(skip)]
commands: HashMap<String, FnPtr>, commands: HashMap<String, FnPtr>,
#[rhai_type(skip)] #[rhai_type(skip)]
event_listener: HashMap<EventListener, FnPtr>, event_listener: HashMap<EventType, FnPtr>,
} }
#[derive(Debug, Clone, PartialEq, CustomType)] #[derive(Debug, Clone, Copy, PartialEq, CustomType)]
#[rhai_type(name = "Vec3", extra = Self::build_extra)] #[rhai_type(name = "Vec3", extra = Self::build_extra)]
struct Vec3 { pub struct Vec3 {
x: i64, pub x: i16,
y: i64, pub y: i16,
z: i64, pub z: i16,
} }
// Custom type API // Custom type API
impl Vec3 { impl Vec3 {
fn new(x: i64, y: i64, z: i64) -> Self { 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<Self>) { fn build_extra(builder: &mut TypeBuilder<Self>) {
@ -280,8 +353,38 @@ impl Vec3 {
} }
#[derive(Debug, Clone, Eq, Hash, PartialEq)] #[derive(Debug, Clone, Eq, Hash, PartialEq)]
enum EventListener { pub enum EventType {
BlockBreak, 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<Self>) {
builder.with_fn("Event", Self::new);
builder.with_fn("cancel", Self::cancel);
}
} }
impl Context { impl Context {
@ -297,9 +400,10 @@ impl Context {
} }
fn add_event_listener(&mut self, event: &str, callback: FnPtr) { fn add_event_listener(&mut self, event: &str, callback: FnPtr) {
let event_listener: EventListener = match event { let event_listener: EventType = match event {
"block_break" => EventListener::BlockBreak, "block_break" => EventType::BlockBreak,
_ => return, "player_leave" => EventType::PlayerLeave,
_ => {warn!("An event listener was created with invalid type: {}", event); return},
}; };
self.event_listener.insert(event_listener, callback); self.event_listener.insert(event_listener, callback);
} }
@ -313,7 +417,7 @@ impl Context {
////// END RHAI DEFINITIONS ////// ////// END RHAI DEFINITIONS //////
impl Extensions { impl Extensions {
pub fn init(players: PlayersWrapper) -> Result<Extensions, AppError> { pub fn init(players: PlayersWrapper, world: WorldWrapper) -> Result<Extensions, AppError> {
if !Path::new("./extensions/").exists() { if !Path::new("./extensions/").exists() {
let _ = fs::create_dir("./extensions/"); let _ = fs::create_dir("./extensions/");
} }
@ -333,11 +437,15 @@ impl Extensions {
info!("Loading extension {}", extension_path.display()); info!("Loading extension {}", extension_path.display());
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.set_max_expr_depths(50, 50);
engine.build_type::<Version>(); engine.build_type::<Version>();
engine.build_type::<ExtensionMetadata>(); engine.build_type::<ExtensionMetadata>();
engine.build_type::<RhaiPlayer>(); engine.build_type::<RhaiPlayer>();
engine.build_type::<PlayersWrapper>(); engine.build_type::<PlayersWrapper>();
engine.build_type::<WorldWrapper>();
engine.build_type::<Context>(); engine.build_type::<Context>();
engine.build_type::<Vec3>();
engine.build_type::<Event>();
engine.register_fn("info", info); engine.register_fn("info", info);
engine.register_fn("warn", warn); engine.register_fn("warn", warn);
engine.register_fn("error", error); engine.register_fn("error", error);
@ -348,9 +456,10 @@ impl Extensions {
Err(error) => { Err(error) => {
error!( error!(
"Rhai plugin compilation failed for {}, reason: {}", "Rhai plugin compilation failed for {}, reason: {}",
extension_path.display(), error extension_path.display(),
error
); );
break; continue;
} }
}; };
let mut scope = Scope::new(); let mut scope = Scope::new();
@ -364,7 +473,7 @@ impl Extensions {
extension_path.display(), extension_path.display(),
error error
); );
break; continue;
} }
}; };
@ -372,6 +481,7 @@ impl Extensions {
ast, ast,
engine, engine,
commands: HashMap::new(), commands: HashMap::new(),
event_listeners: HashMap::new(),
metadata: extension_metadata, metadata: extension_metadata,
}; };
@ -379,7 +489,10 @@ impl Extensions {
&mut scope, &mut scope,
&current_extension.ast, &current_extension.ast,
"init", "init",
(PlayersWrapper::new(players.0.clone()),), (
PlayersWrapper::new(players.0.clone()),
WorldWrapper::new(world.0.clone()),
),
) { ) {
Ok(result) => result, Ok(result) => result,
Err(error) => { Err(error) => {
@ -387,7 +500,7 @@ impl Extensions {
"Plugin {} failed to init: {}", "Plugin {} failed to init: {}",
current_extension.metadata.name, error current_extension.metadata.name, error
); );
break; continue;
} }
}; };
@ -397,6 +510,12 @@ impl Extensions {
.insert(key.to_string(), value.clone()); .insert(key.to_string(), value.clone());
} }
for (key, value) in ctx.event_listener.iter() {
current_extension
.event_listeners
.insert(key.clone(), value.clone());
}
info!( info!(
"Loaded {} v{}", "Loaded {} v{}",
current_extension.metadata.name, current_extension.metadata.version, current_extension.metadata.name, current_extension.metadata.version,

View File

@ -2,42 +2,81 @@ fn metadata() {
Metadata("fill", "illegitimate-egg", Version("1.0.0")) // Version test util Metadata("fill", "illegitimate-egg", Version("1.0.0")) // Version test util
} }
fn init(players) { fn init(players, world) {
let ctx = Context(); let ctx = Context();
let playerData = #{}; let playerData = #{};
ctx.register_command("fill", |player, argv| { ctx.register_command("fill", |player, argv| {
players.send_message(player, "Break two blocks to select area"); 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| { ctx.add_event_listener("block_break", |event| {
if playerData[event.player].command_step == () { let player_data = playerData[event.player.to_string()];
playerData[event.player].command_step == 0; if player_data != () {
} else { switch player_data.command_step {
switch playerData[event.player].command_step {
0 => { 0 => {
// Do nothing, the command isn't in use // Do nothing, the command isn't in use
} }
1 => { 1 => {
// Record the block position player_data.command_step += 1;
playerData[event.player].firstBlock = event.position; event.cancel();
player_data.firstBlock = event.position;
players.send_message(event.player, "Position 1 {" + event.position.x + ", " + event.position.y + ", " + event.position.z + "}");
} }
2 => { 2 => {
playerData[event.player].secondBlock = event.position; player_data.command_step = 0;
// TODO: Implement the fill 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"); error("Unreachable reached");
} }
} }
} }
playerData[event.player.to_string()] = player_data;
return event;
}); });
// NOTE: Currently this doesn't work, as player_leave isn't implemented // NOTE: Currently this doesn't work, as player_leave isn't implemented
ctx.add_event_listener("player_leave", |event| { ctx.add_event_listener("player_leave", |event| {
playerData[event.player].command_step = 0; playerData[event.player.to_string()].command_step = 0;
}); });
ctx ctx

View File

@ -2,7 +2,7 @@ fn metadata() {
Metadata("ping-pong", "illegitimate-egg", Version("1.0.0")) Metadata("ping-pong", "illegitimate-egg", Version("1.0.0"))
} }
fn init(players) { fn init(players, world) {
let ctx = Context(); let ctx = Context();
ctx.register_command("ping", |player, argv| { ctx.register_command("ping", |player, argv| {

View File

@ -2,7 +2,7 @@ fn metadata() {
Metadata("utils", "illegitimate-egg", Version("1.2.3-testp+testb")) // Version test util Metadata("utils", "illegitimate-egg", Version("1.2.3-testp+testb")) // Version test util
} }
fn init(players) { fn init(players, world) {
let ctx = Context(); let ctx = Context();
ctx.register_command("args", |player, argv| { ctx.register_command("args", |player, argv| {

View File

@ -12,7 +12,7 @@ mod utils;
mod world; mod world;
use error::AppError; use error::AppError;
use extensions::{Extensions, PlayersWrapper}; use extensions::{Extensions, PlayersWrapper, WorldWrapper};
use network::handle_client; use network::handle_client;
use player::{Player, SpecialPlayers}; use player::{Player, SpecialPlayers};
use world::World; use world::World;
@ -47,7 +47,10 @@ fn run() -> Result<(), AppError> {
}) })
.expect("Error handling control C, save on exit will not work"); .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); info!("Server listening on {}", 25565);

View File

@ -1,4 +1,4 @@
use log::{info, warn}; use log::{debug, info, warn};
use std::io::prelude::*; use std::io::prelude::*;
use std::net::TcpStream; use std::net::TcpStream;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -7,7 +7,7 @@ use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use crate::command::handle_command; use crate::command::handle_command;
use crate::extensions::Extensions; use crate::extensions::{Event, EventType, Extensions};
use crate::player::{Player, PlayerStatus, SpecialPlayers}; use crate::player::{Player, PlayerStatus, SpecialPlayers};
use crate::utils::*; use crate::utils::*;
use crate::world::World; 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 mut payload_buffer = [0; 8]; // Short (2) + Short (2) + Short (2) + Byte (1) + Byte (1)
let _ = stream.read(&mut payload_buffer); let _ = stream.read(&mut payload_buffer);
let mut is_cancelled = false;
let mut previous_block: u8 = 0;
let position_x = let position_x =
((payload_buffer[0] as i16) << (8 as i16)) + payload_buffer[1] as i16; ((payload_buffer[0] as i16) << (8 as i16)) + payload_buffer[1] as i16;
let position_y = let position_y =
@ -125,6 +128,21 @@ pub fn handle_client(
let mode = payload_buffer[6]; let mode = payload_buffer[6];
let mut block_type = payload_buffer[7]; 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(); let mut world_dat = world_arc_clone.lock().unwrap();
// Sanity check (Stop losers from losing) // Sanity check (Stop losers from losing)
@ -139,32 +157,43 @@ pub fn handle_client(
break; break;
} }
if mode == 0x00 {
block_type = 0x00; // Air
}
let world_offset: u32 = position_x as u32 let world_offset: u32 = position_x as u32
+ (position_z as u32 * world_dat.size_x as u32) + (position_z as u32 * world_dat.size_x as u32)
+ (position_y as u32 + (position_y as u32
* world_dat.size_x as u32 * world_dat.size_x as u32
* world_dat.size_z 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<u8> = Vec::new(); if !is_cancelled {
update_block_bytes.push(0x06); let mut update_block_bytes: Vec<u8> = Vec::new();
update_block_bytes.extend_from_slice(&stream_write_short(position_x)); update_block_bytes.push(0x06);
update_block_bytes.extend_from_slice(&stream_write_short(position_y)); update_block_bytes.extend_from_slice(&stream_write_short(position_x));
update_block_bytes.extend_from_slice(&stream_write_short(position_z)); update_block_bytes.extend_from_slice(&stream_write_short(position_y));
update_block_bytes.push(block_type); 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(); let mut players = players_arc_clone.lock().unwrap();
for i in 0..players.len() { for i in 0..players.len() {
if players[i].id != 255 && players[i].id != client_number { if players[i].id != 255 && players[i].id != client_number {
players[i] players[i]
.outgoing_data .outgoing_data
.extend_from_slice(&update_block_bytes); .extend_from_slice(&update_block_bytes);
}
} }
} else {
let mut update_block_bytes: Vec<u8> = 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 => { 0x08 => {
@ -305,6 +334,9 @@ pub fn handle_client(
current_player.id = SpecialPlayers::SelfPlayer as u8; current_player.id = SpecialPlayers::SelfPlayer as u8;
} }
let mut event = Event::new();
event.player = client_number;
extensions.run_event(EventType::PlayerLeave, event);
info!( info!(
"Client {} disconnected, thread shutting down!", "Client {} disconnected, thread shutting down!",
client_number client_number

Binary file not shown.