update things
This commit is contained in:
parent
459245bf8b
commit
beac4bdb42
8 changed files with 128 additions and 39 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue