update things

This commit is contained in:
TriMill 2023-01-01 15:28:35 -05:00
parent 459245bf8b
commit beac4bdb42
8 changed files with 128 additions and 39 deletions

View file

@ -6,30 +6,32 @@ Quectocraft plugins are written in Lua. Examples can be seen in the [plugins dir
All information about a plugin is stored in a table, which must be returned at the end of the plugin. This table contains both information about the plugin and functions that act as event handlers.
| Field | Description |
|--------------------|---------------------------------------------------------------------------------------------------------|
| `id` | The plugin's ID. This should consist of lowercase letters and underscores only. |
| `name` | The plugin's human-readable name. |
| `description` | The plugin's description. |
| `authors` | A list of the plugin's authors. |
| `version` | The plugin's version (semantic versioning encouraged). |
| `init` | Called when the plugin is initialized. The `server` table is available at this point. |
| `registerCommands` | Called to register the plugin's commands. Arguments: a `registry` table. |
| `playerJoin` | Called when a player joins. Arguments: the player's name, the player's UUID. |
| `playerLeave` | Called when a player leaves. Arguments: the player's name, the player's UUID. |
| `chatMessage` | Called when a player sends a chat message. Arguments: the message, the player's name and UUID. |
| `command` | Called when a player runs a command. Arguments: the command, the arguments, the player's name and UUID. |
| Field | Description |
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | The plugin's ID. This should consist of lowercase letters and underscores only. |
| `name` | The plugin's human-readable name. |
| `description` | The plugin's description. |
| `authors` | A list of the plugin's authors. |
| `version` | The plugin's version (semantic versioning encouraged). |
| `init` | Called when the plugin is initialized. The `server` table is available at this point. |
| `registerCommands` | Called to register the plugin's commands. Arguments: a `registry` table. |
| `playerJoin` | Called when a player joins. Arguments: the player's name, the player's UUID. |
| `playerLeave` | Called when a player leaves. Arguments: the player's name, the player's UUID. |
| `chatMessage` | Called when a player sends a chat message. Arguments: the message, the player's name and UUID. |
| `pluginMessage` | Called when a client sends a [plugin message](https://wiki.vg/Plugin_channels). Arguments: the channel, the message, the player's name and UUID. |
| `command` | Called when a player runs a command. Arguments: the command, the arguments, the player's name and UUID. |
## The `server` table
The `server` table is used to interact with the server. It has the following fields:
| Field | Description |
|---------------|--------------------------------------------------------------------------------------------------|
| `players` | A map from UUIDs to player names. |
| `sendMessage` | Send a player a message. Arguments: the player (name or UUID), the message. |
| `broadcast` | Broadcast a message to all online players. Arguments: the message. |
| `disconnect` | Disconnect a player from the server. Arguments: the player (name or UUID), the reason (optional) |
| Field | Description |
|---------------------|------------------------------------------------------------------------------------------------------------------------------------|
| `players` | A map from UUIDs to player names. |
| `sendPluginMessage` | Send a player a [plugin message](https://wiki.vg/Plugin_channels). Arguments: the player (name or UUID), the channel, the message. |
| `sendMessage` | Send a player a message. Arguments: the player (name or UUID), the message. |
| `broadcast` | Broadcast a message to all online players. Arguments: the message. |
| `disconnect` | Disconnect a player from the server. Arguments: the player (name or UUID), the reason (optional) |
## The `registry` table

View file

@ -20,4 +20,8 @@ function plugin.command(command, args, name, uuid)
logger.info("player " .. name .. " ran /" .. command .. " " .. args)
end
function plugin.playerJoin(name, uuid)
logger.info("player joined: " .. name .. " with uuid " .. uuid)
end
return plugin

View file

@ -135,7 +135,17 @@ impl <'lua> NetworkServer<'lua> {
}
}
}
},
Response::PluginMessage { player, channel, data } => {
for client in self.clients.iter_mut() {
if let Some(pl) = &client.player {
if pl.name == player || pl.uuid.to_string() == player {
client.send_packet(CPluginMessage { channel: channel.clone(), data: data.clone() })?;
}
}
}
}
,
}
Ok(())
}
@ -181,6 +191,9 @@ impl <'lua> NetworkServer<'lua> {
}
}
}
ServerBoundPacket::PluginMessage(SPluginMessage { channel, data }) => {
self.plugins.plugin_message(client.player.as_ref().unwrap(), &channel, &data);
}
}
Ok(())
}
@ -193,10 +206,12 @@ impl <'lua> NetworkServer<'lua> {
client.close();
return Ok(())
}
client.player = Some(Player {
name: login_start.name.clone(),
uuid: login_start.uuid,
});
match self.config.login {
LoginMode::Offline => {
client.verified = true;
@ -226,9 +241,11 @@ impl <'lua> NetworkServer<'lua> {
client.close();
return Ok(());
};
let (sig, data) = data.split_at(32);
let mut mac = Hmac::<Sha256>::new_from_slice(self.config.velocity_secret.clone().unwrap().as_bytes())?;
mac.update(data);
if mac.verify_slice(sig).is_err() {
client.send_packet(Disconnect { reason: json!({
"text": "Could not verify secret. Ensure that the secrets configured for Velocity and Quectocraft match."
@ -236,15 +253,21 @@ impl <'lua> NetworkServer<'lua> {
client.close();
return Ok(())
}
client.verified = true;
client.send_packet(LoginPluginRequest{
id: -1,
channel: "qc:init".to_owned(),
data: Vec::new()
})?;
Ok(())
}
//
// Handle the end of "login" and beginning of "play"
//
fn login(&mut self, client: &mut NetworkClient) -> std::io::Result<()> {
if !client.verified {
client.send_packet(Disconnect { reason: json!({
@ -254,11 +277,14 @@ impl <'lua> NetworkServer<'lua> {
client.close();
return Ok(())
}
client.send_packet(LoginSuccess {
name: client.player.as_ref().unwrap().name.to_owned(),
uuid: client.player.as_ref().unwrap().uuid,
})?;
self.plugins.player_join(client.player.as_ref().unwrap());
client.send_packet(LoginPlay {
eid: client.id,
is_hardcore: false,
@ -280,7 +306,8 @@ impl <'lua> NetworkServer<'lua> {
is_flat: false,
death_location: None,
})?;
client.send_packet(PluginMessage {
client.send_packet(CPluginMessage {
channel: "minecraft:brand".to_owned(),
data: {
let mut data = Vec::new();
@ -288,7 +315,10 @@ impl <'lua> NetworkServer<'lua> {
data
}
})?;
client.send_packet(self.commands.clone())?;
// Send 3x3 square of empty chunks.
let mut chunk_data: Vec<u8> = Vec::new();
for _ in 0..(384 / 16) {
// number of non-air blocks
@ -305,15 +335,21 @@ impl <'lua> NetworkServer<'lua> {
let hmdata = vec![0i64; 37];
let mut heightmap = nbt::Blob::new();
heightmap.insert("MOTION_BLOCKING", hmdata).unwrap();
client.send_packet(ChunkData {
x: 0,
z: 0,
heightmap,
chunk_data,
})?;
for x in -1..=1 {
for z in -1..=1 {
client.send_packet(ChunkData {
x,
z,
heightmap: heightmap.clone(),
chunk_data: chunk_data.clone(),
})?;
}
}
client.send_packet(SetDefaultSpawnPosition {
pos: Position { x: 0, y: 0, z: 0 }, angle: 0.0
})?;
client.send_packet(SyncPlayerPosition {
x: 0.0,
y: 64.0,
@ -324,8 +360,7 @@ impl <'lua> NetworkServer<'lua> {
teleport_id: 0,
dismount: false
})?;
// TODO why doesn't this work with quilt?
// client.send_packet(ClientBoundPacket::PlayerAbilities(0x0f, 0.05, 0.1))?;
Ok(())
}

View file

@ -32,6 +32,16 @@ function server.broadcast(message)
table.insert(_qc.responses, { type = "broadcast", message = message })
end
function server.sendPluginMessage(player, channel, data)
if type(player) ~= "string" then
error("player must be a string")
end
if type(channel) ~= "string" then
error("channel must be a string")
end
table.insert(_qc.responses, {type = "plugin_message", player = player, channel = channel, data = data})
end
function server.disconnect(player, reason)
local reason = assert(to_chat(reason, { translate = "multiplayer.disconnect.generic" }))
table.insert(_qc.responses, { type = "disconnect", player = player, reason = reason })

View file

@ -17,6 +17,8 @@ mod plugin;
pub enum Response {
#[serde(rename = "message")]
Message { player: String, message: serde_json::Value },
#[serde(rename = "plugin_message")]
PluginMessage { player: String, channel: String, data: Vec<u8> },
#[serde(rename = "broadcast")]
Broadcast { message: serde_json::Value },
#[serde(rename = "disconnect")]
@ -180,4 +182,14 @@ impl <'lua> Plugins<'lua> {
}
}
}
pub fn plugin_message(&self, player: &Player, channel: &str, data: &[u8]) {
for pl in &self.plugins {
if let Some(func) = &pl.event_handlers.plugin_message {
if let Err(e) = func.call::<_, ()>((channel, data, player.name.as_str(), player.uuid.to_string())) {
warn!("Error in plugin {}: {}", pl.name, e);
}
}
}
}
}

View file

@ -9,6 +9,7 @@ pub struct EventHandlers<'lua> {
pub player_leave: Option<Function<'lua>>,
pub chat_message: Option<Function<'lua>>,
pub command: Option<Function<'lua>>,
pub plugin_message: Option<Function<'lua>>,
}
pub struct Plugin<'lua> {
@ -33,8 +34,17 @@ impl <'lua> Plugin<'lua> {
let player_leave: Option<Function<'lua>> = module.get("playerLeave").ok();
let chat_message: Option<Function<'lua>> = module.get("chatMessage").ok();
let command: Option<Function<'lua>> = module.get("command").ok();
let plugin_message: Option<Function<'lua>> = module.get("pluginMessage").ok();
let event_handlers = EventHandlers { init, register_commands, player_join, player_leave, chat_message, command };
let event_handlers = EventHandlers {
init,
register_commands,
player_join,
player_leave,
chat_message,
command,
plugin_message,
};
Ok(Plugin { id, name, version, event_handlers })
}
}

View file

@ -151,12 +151,12 @@ impl ClientBoundPacket for LoginPlay {
}
#[derive(Debug)]
pub struct PluginMessage {
pub struct CPluginMessage {
pub channel: String,
pub data: Vec<u8>,
}
impl ClientBoundPacket for PluginMessage {
impl ClientBoundPacket for CPluginMessage {
fn encode(&self, encoder: &mut impl PacketEncoder) {
encoder.write_string(32767, &self.channel);
encoder.write_bytes(&self.data);

View file

@ -79,6 +79,20 @@ impl LoginPluginResponse {
}
}
#[derive(Debug)]
pub struct SPluginMessage {
pub channel: String,
pub data: Vec<u8>,
}
impl SPluginMessage {
pub fn decode(mut decoder: PacketDecoder) -> Self {
let channel = decoder.read_string();
let data = decoder.read_to_end().to_vec();
Self { channel, data }
}
}
#[derive(Debug)]
pub enum ServerBoundPacket {
Unknown(i32),
@ -94,13 +108,14 @@ pub enum ServerBoundPacket {
// play
ChatMessage(ChatMessage),
ChatCommand(ChatMessage),
PluginMessage(SPluginMessage),
}
impl ServerBoundPacket {
pub fn decode(state: &mut NetworkState, mut decoder: PacketDecoder) -> ServerBoundPacket {
use NetworkState as NS;
match (*state, decoder.packet_id()) {
(NS::Handshake, 0) => {
(NS::Handshake, 0x00) => {
let hs = Handshake::decode(decoder);
match hs.next_state {
1 => *state = NS::Status,
@ -109,23 +124,24 @@ impl ServerBoundPacket {
}
ServerBoundPacket::Handshake(hs)
},
(NS::Status, 0)
(NS::Status, 0x00)
=> ServerBoundPacket::StatusRequest(),
(NS::Status, 1)
(NS::Status, 0x01)
=> ServerBoundPacket::PingRequest(decoder.read_long()),
(NS::Login, 0) => {
(NS::Login, 0x00) => {
ServerBoundPacket::LoginStart(LoginStart::decode(decoder))
},
(NS::Login, 2) => {
(NS::Login, 0x02) => {
let lpr = LoginPluginResponse::decode(decoder);
if lpr.id == -1 {
*state = NetworkState::Play;
}
ServerBoundPacket::LoginPluginResponse(lpr)
},
(NS::Play, 4) => ServerBoundPacket::ChatCommand(ChatMessage::decode(decoder)),
(NS::Play, 5) => ServerBoundPacket::ChatMessage(ChatMessage::decode(decoder)),
(NS::Play, id @ (17 | 19 | 20 | 21 | 29)) => ServerBoundPacket::Ignored(id),
(NS::Play, 0x04) => ServerBoundPacket::ChatCommand(ChatMessage::decode(decoder)),
(NS::Play, 0x05) => ServerBoundPacket::ChatMessage(ChatMessage::decode(decoder)),
(NS::Play, 0x0C) => ServerBoundPacket::PluginMessage(SPluginMessage::decode(decoder)),
(NS::Play, id @ (0x11 | 0x13 | 0x14 | 0x15 | 0x1d)) => ServerBoundPacket::Ignored(id),
(_, id) => ServerBoundPacket::Unknown(id),
}
}