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 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::<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 {
ast: AST,
engine: Engine,
commands: HashMap<String, FnPtr>,
event_listeners: HashMap<EventType, FnPtr>,
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<Self>) {
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<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)]
commands: HashMap<String, FnPtr>,
#[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)]
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<Self>) {
@ -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<Self>) {
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<Extensions, AppError> {
pub fn init(players: PlayersWrapper, world: WorldWrapper) -> Result<Extensions, AppError> {
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::<Version>();
engine.build_type::<ExtensionMetadata>();
engine.build_type::<RhaiPlayer>();
engine.build_type::<PlayersWrapper>();
engine.build_type::<WorldWrapper>();
engine.build_type::<Context>();
engine.build_type::<Vec3>();
engine.build_type::<Event>();
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,
&current_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,

View File

@ -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

View File

@ -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| {

View File

@ -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| {

View File

@ -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);

View File

@ -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,18 +157,19 @@ 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);
if !is_cancelled {
world_dat.data[world_offset as usize] = block_type;
} else {
previous_block = world_dat.data[world_offset as usize];
}
}
if !is_cancelled {
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));
@ -166,6 +185,16 @@ pub fn handle_client(
.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 => {
let mut payload_buffer = [0; 9]; // SByte + FShort (2B) + FShort + FShort +
@ -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

Binary file not shown.