software
This commit is contained in:
parent
ff4423d593
commit
06a0beba24
25 changed files with 952 additions and 250 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -440,9 +440,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mlua"
|
name = "mlua"
|
||||||
version = "0.9.0-rc.1"
|
version = "0.9.0-rc.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f5369212118d0f115c9adbe7f7905e36fb3ef2994266039c51fc3e96374d705d"
|
checksum = "01a6500a9fb74b519a85ac206cd57f9f91b270ce39d6cb12ab06a8ed29c3563d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bstr",
|
"bstr",
|
||||||
"erased-serde",
|
"erased-serde",
|
||||||
|
@ -457,9 +457,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mlua-sys"
|
name = "mlua-sys"
|
||||||
version = "0.2.1"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3daecc55a656cae8e54fc599701ab597b67cdde68f780cac8c1c49b9cfff2f5"
|
checksum = "aa5b61f6c943d77dd6ab5f670865670f65b978400127c8bf31c2df7d6e76289a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
|
16
Cargo.toml
16
Cargo.toml
|
@ -4,14 +4,14 @@ version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "1.29", features = ["full"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde = "1.0"
|
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
uuid = { version = "1.4", features = ["v5"] }
|
|
||||||
log = "0.4"
|
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
mlua = { version = "0.9.0-rc.1", features = ["luajit52", "async", "send", "serialize"] }
|
log = "0.4"
|
||||||
semver = "1.0"
|
mlua = { version = "0.9.0-rc.3", features = ["luajit52", "async", "send", "serialize"] }
|
||||||
reqwest = { version = "0.11", features = ["rustls-tls", "json"], default-features = false }
|
|
||||||
once_cell = "1.18"
|
once_cell = "1.18"
|
||||||
|
reqwest = { version = "0.11", features = ["rustls-tls", "json"], default-features = false }
|
||||||
|
semver = "1.0"
|
||||||
|
serde = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
tokio = { version = "1.29", features = ["full"] }
|
||||||
|
uuid = { version = "1.4", features = ["v5"] }
|
||||||
|
|
97
PLUGINS.md
Normal file
97
PLUGINS.md
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
# quectocraft plugins
|
||||||
|
|
||||||
|
## File structure
|
||||||
|
|
||||||
|
Plugins are located in the `plugins` directory. Plugins may either be stored as a single file in the `plugins` directory or in a subdirectory, in which case the plugin will be loaded from a file named `main.lua`.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
Server and plugin versions use [Semantic Versioning](https://semver.org/) to specify versions, specifically the variety used by Cargo, rust's package manager. Refer to [the Cargo docs](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html) for information on specifying versions and version requirements
|
||||||
|
|
||||||
|
## Plugin table
|
||||||
|
|
||||||
|
The plugin's main file returns a table representing the plugin. The `id` property is required or the plugin will fail to load. The `server_version` property is recommended to ensure that the plugin is running under a compatible server version. The `priority` field, which defaults to 50 and must be between 1 and 100 inclusive, controls the order that events are sent to plugins; plugins with higher priority will be initialized and recieve events before those with lower priority.
|
||||||
|
|
||||||
|
All other fields listed are optional and are currently unused by the server, although they may be used by the server in the future or by other plugins.
|
||||||
|
|
||||||
|
| Key | Type | Required? | Description |
|
||||||
|
|------------------|---------|-------------|-------------------------------------------------------------------|
|
||||||
|
| `id` | string | required | The plugin's id, which should be a unique `snake_case` identifier |
|
||||||
|
| `server_version` | string | recommended | The version requirement for the server |
|
||||||
|
| `priority` | integer | optional | Determines the order in which plugins recieve events |
|
||||||
|
| `name` | string | optional | The plugin's name |
|
||||||
|
| `version` | string | optional | The plugin's version |
|
||||||
|
| `description` | string | optional | The plugin's description |
|
||||||
|
| `authors` | table | optional | A list of plugin authors |
|
||||||
|
| `license` | string | optional | The plugin's license |
|
||||||
|
|
||||||
|
The table may also contain the following functions that serve as event handlers. All functions take an initial `self` argument holding the plugin's own table.
|
||||||
|
|
||||||
|
- `init(self)` - run for each plugin after all have been loaded
|
||||||
|
- `on_join(self, uuid, name)` - a player joined the server
|
||||||
|
- `on_leave(self, uuid, name)` - a player left the server
|
||||||
|
- `on_message(self, message, uuid, name)` - a player sent a message
|
||||||
|
- `on_command(self, command, args, full_command, uuid, name)` - a player ran a command
|
||||||
|
- `command`: the command that was run (with the plugin namespace removed if present)
|
||||||
|
- `args`: a table of the arguments to the command created by naïvely splitting the command string on whitespace
|
||||||
|
- `full_command`: the full command message containing the (possibly namespaced) name and arguments but not including the initial slash
|
||||||
|
|
||||||
|
Returning `true` from any of these methods besides `init` will cancel the event: it will not be sent to any other plugins. This does not prevent the action that caused the event.
|
||||||
|
|
||||||
|
Plugins may use their tables to store arbitrary extra data.
|
||||||
|
|
||||||
|
## Data types
|
||||||
|
|
||||||
|
- `uuid`: A UUID, probably of a player. Supports testing for equality via `==` and conversion to a string via `tostring()`.
|
||||||
|
- `srv`: The interface to the server, as described below.
|
||||||
|
- `msg`: A Minecraft JSON chat component represented as a table. A string may also be used if no formatting is needed, or `nil` may be passed to use an empty or default message.
|
||||||
|
|
||||||
|
## `srv`
|
||||||
|
|
||||||
|
Plugins can use the global `srv` object to perform logging and communicate to and query the server.
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
The methods `trace`, `debug`, `info`, `warn`, and `error` can be used for logging. Each takes a single string argument and logs it to the terminal using the same logging interface as used by the server.
|
||||||
|
|
||||||
|
### Players
|
||||||
|
|
||||||
|
The method `players` retrieves a table listing the users connected to the server where the keys are UUIDs and the values are player names.
|
||||||
|
|
||||||
|
The method `get_name(uuid)` will get a player's name given a UUID, and the method `get_uuid(name)` will get a player's UUID given their name.
|
||||||
|
|
||||||
|
The method `get_info(uuid)` returns a table containing the following:
|
||||||
|
- `addr` - the address the player connected to the server with
|
||||||
|
- `port` - the port the player connected with
|
||||||
|
- `proto_name` - the name of the protocol the player is using (ex. `1.20.1`)
|
||||||
|
- `proto_version` - the numerical version of the protocol (ex. `763`)
|
||||||
|
|
||||||
|
`kick(uuid, msg)` can be used to disconnect a player from the server. If `msg` is `nil`, the default, localized kick message will be used.
|
||||||
|
|
||||||
|
### Chat
|
||||||
|
|
||||||
|
- `send_msg(msg, target)` sends a system message to the player specified by the `target` UUID, or to all players if no UUID is specified
|
||||||
|
- `send_chat(msg, target)` does the same but as a chat message
|
||||||
|
|
||||||
|
Chat messages should be used for messages with content originating from players, system messages should be used for information from the server or in response to commands.
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
Plugins will only recieve `on_command` events for commands they have registered.
|
||||||
|
|
||||||
|
- `register_command(name)` will register the commands `id:name`, where `id` is the plugin's id, and `name`, provided that another plugin has not already registered a command named `name`.
|
||||||
|
- `add_command(name)` is a convenience method for registering a command and creating basic Brigadier nodes to indicate to clients that the command exists. (note: full Brigadier API is WIP)
|
||||||
|
- `update_commands()` will resend the command data to connected clients. This is only necessary if adding new commands after the `init` phase completes, which should be avoided if possible.
|
||||||
|
|
||||||
|
### Utility
|
||||||
|
|
||||||
|
NOTE: these are functions, not methods.
|
||||||
|
|
||||||
|
- `parse_uuid(str)` will attempt to parse a UUID from a string, returning `nil` if the argument is not a valid string representation of a UUID.
|
||||||
|
- `check_semver(ver, req)` checks if the semantic version `ver` matches the version requirement `req`. It will error if either the version or version requirement fails to parse.
|
||||||
|
|
||||||
|
## Fields
|
||||||
|
|
||||||
|
- `version` is the quectocraft server version (this is not a Minecraft protocol version)
|
||||||
|
- `plugins` is a table containing all loaded plugins, where the keys are plugin IDs.
|
||||||
|
|
10
README.md
10
README.md
|
@ -1,9 +1,15 @@
|
||||||
# Building
|
# quectocraft
|
||||||
|
|
||||||
|
## Plugin API
|
||||||
|
|
||||||
|
see [PLUGINS.md](PLUGINS.md)
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
quectocraft requires that `luajit` is installed. it should be
|
quectocraft requires that `luajit` is installed. it should be
|
||||||
available via your package manager.
|
available via your package manager.
|
||||||
|
|
||||||
## Alpine
|
### Alpine
|
||||||
|
|
||||||
install `luajit`, `luajit-dev`, and `pkgconfig` and build with
|
install `luajit`, `luajit-dev`, and `pkgconfig` and build with
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,47 @@ return {
|
||||||
server_version = "0.2",
|
server_version = "0.2",
|
||||||
|
|
||||||
init = function(self)
|
init = function(self)
|
||||||
srv:info("quectocraft version " .. srv.version)
|
srv:add_command("selfkick")
|
||||||
for k, _ in pairs(srv.plugins) do
|
srv:add_command("fail")
|
||||||
srv:info("found plugin: " .. k)
|
end,
|
||||||
|
|
||||||
|
on_join = function(self, uuid, name)
|
||||||
|
local info = srv:get_info(uuid)
|
||||||
|
for k, v in pairs(info) do
|
||||||
|
srv:warn(k .. ": " .. tostring(v))
|
||||||
|
end
|
||||||
|
srv:send_msg({
|
||||||
|
{
|
||||||
|
text = "welcome, ",
|
||||||
|
color = "green",
|
||||||
|
}, {
|
||||||
|
text = name,
|
||||||
|
color = "yellow",
|
||||||
|
}, {
|
||||||
|
text = ", to the server!\nthere are many things to do, such as ",
|
||||||
|
color = "green",
|
||||||
|
}, {
|
||||||
|
text = "leaving",
|
||||||
|
color = "light_purple"
|
||||||
|
},
|
||||||
|
}, uuid)
|
||||||
|
end,
|
||||||
|
|
||||||
|
on_command = function(self, cmd, args, _, uuid)
|
||||||
|
if cmd == "fail" then
|
||||||
|
error("failure has occured")
|
||||||
|
elseif cmd == "selfkick" then
|
||||||
|
srv:kick(uuid, {
|
||||||
|
{
|
||||||
|
text = "be",
|
||||||
|
color = "dark_red",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = "gone",
|
||||||
|
color = "red",
|
||||||
|
bold = true,
|
||||||
|
}
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,21 @@ return {
|
||||||
id = "mcchat",
|
id = "mcchat",
|
||||||
name = "MCChat",
|
name = "MCChat",
|
||||||
version = "0.1.0",
|
version = "0.1.0",
|
||||||
|
priority = 80,
|
||||||
server_version = "0.2",
|
server_version = "0.2",
|
||||||
authors = {"trimill"},
|
authors = {"trimill"},
|
||||||
description = "Adds basic chat and join/leave messages",
|
description = "Adds basic chat and join/leave messages",
|
||||||
license = "MIT",
|
license = "MIT",
|
||||||
|
|
||||||
|
init = function(self)
|
||||||
|
srv:add_command("tell")
|
||||||
|
srv:add_command("msg")
|
||||||
|
srv:add_command("me")
|
||||||
|
end,
|
||||||
|
|
||||||
on_join = function(self, _, name)
|
on_join = function(self, _, name)
|
||||||
srv:info(name .. " joined the game")
|
srv:info(name .. " joined the game")
|
||||||
srv:broadcast_msg({
|
srv:send_msg({
|
||||||
translate = "multiplayer.player.joined",
|
translate = "multiplayer.player.joined",
|
||||||
with = { { text = name } },
|
with = { { text = name } },
|
||||||
color = "yellow",
|
color = "yellow",
|
||||||
|
@ -19,7 +26,7 @@ return {
|
||||||
|
|
||||||
on_leave = function(self, _, name)
|
on_leave = function(self, _, name)
|
||||||
srv:info(name .. " left the game")
|
srv:info(name .. " left the game")
|
||||||
srv:broadcast_msg({
|
srv:send_msg({
|
||||||
translate = "multiplayer.player.left",
|
translate = "multiplayer.player.left",
|
||||||
with = { { text = name } },
|
with = { { text = name } },
|
||||||
color = "yellow",
|
color = "yellow",
|
||||||
|
@ -28,12 +35,74 @@ return {
|
||||||
|
|
||||||
on_message = function(self, msg, _, name)
|
on_message = function(self, msg, _, name)
|
||||||
srv:info("<" .. name .. "> " .. msg)
|
srv:info("<" .. name .. "> " .. msg)
|
||||||
srv:broadcast_chat({
|
srv:send_chat({
|
||||||
translate = "chat.type.text",
|
translate = "chat.type.text",
|
||||||
with = {
|
with = {
|
||||||
{ text = name },
|
{ text = name },
|
||||||
{ text = msg },
|
{ text = msg },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
return true
|
||||||
end,
|
end,
|
||||||
|
|
||||||
|
on_command = function(self, cmd, args, _, uuid, name)
|
||||||
|
if cmd == "me" then
|
||||||
|
|
||||||
|
local msg = table.concat(args, " ")
|
||||||
|
srv:info("* " .. name .. " " .. msg)
|
||||||
|
srv:send_chat({
|
||||||
|
translate = "chat.type.emote",
|
||||||
|
with = {
|
||||||
|
{ text = name },
|
||||||
|
{ text = msg }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
elseif cmd == "tell" or cmd == "msg" then
|
||||||
|
|
||||||
|
local target = args[1]
|
||||||
|
|
||||||
|
if target == nil then
|
||||||
|
srv:send_msg({
|
||||||
|
text = cmd .. ": no target specified",
|
||||||
|
color = "red",
|
||||||
|
}, uuid)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local target_uuid = srv:get_uuid(target)
|
||||||
|
|
||||||
|
if target_uuid == nil then
|
||||||
|
srv:send_msg({
|
||||||
|
text = cmd .. ": player not found: " .. target,
|
||||||
|
color = "red",
|
||||||
|
}, uuid)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local msg = table.concat(args, " ", 2)
|
||||||
|
|
||||||
|
srv:info(name .. " whispers to " .. target .. ": " .. msg)
|
||||||
|
|
||||||
|
srv:send_chat({
|
||||||
|
translate = "commands.message.display.outgoing",
|
||||||
|
with = {
|
||||||
|
{ text = target },
|
||||||
|
{ text = msg }
|
||||||
|
},
|
||||||
|
color = "gray",
|
||||||
|
italic = true,
|
||||||
|
}, uuid)
|
||||||
|
srv:send_chat({
|
||||||
|
translate = "commands.message.display.incoming",
|
||||||
|
with = {
|
||||||
|
{ text = name },
|
||||||
|
{ text = msg }
|
||||||
|
},
|
||||||
|
color = "gray",
|
||||||
|
italic = true,
|
||||||
|
}, target_uuid);
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
use crate::{Player, ClientInfo, JsonValue};
|
use crate::{Player, JsonValue, command::Commands};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ClientInfo {
|
||||||
|
pub addr: String,
|
||||||
|
pub port: u16,
|
||||||
|
pub proto_version: i32,
|
||||||
|
pub proto_name: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ClientEvent {
|
pub enum ClientEvent {
|
||||||
|
@ -12,8 +20,9 @@ pub enum ClientEvent {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ServerEvent {
|
pub enum ServerEvent {
|
||||||
Disconnect(Option<String>),
|
Disconnect(Option<JsonValue>),
|
||||||
KeepAlive(i64),
|
KeepAlive(i64),
|
||||||
SystemMessage(JsonValue, bool),
|
SystemMessage(JsonValue, bool),
|
||||||
ChatMessage(JsonValue, JsonValue),
|
ChatMessage(JsonValue, JsonValue),
|
||||||
|
UpdateCommands(Commands),
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use serde_json::json;
|
||||||
use tokio::{net::{TcpStream, tcp::{OwnedReadHalf, OwnedWriteHalf}}, select, io::{AsyncReadExt, AsyncWriteExt}};
|
use tokio::{net::{TcpStream, tcp::{OwnedReadHalf, OwnedWriteHalf}}, select, io::{AsyncReadExt, AsyncWriteExt}};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{varint::VarInt, ser::{Deserializable, Position}, protocol::{self, Protocol, ProtocolState, ClientPacket, ServerPacket}, Player, ClientInfo, JsonValue, HTTP_CLIENT};
|
use crate::{varint::VarInt, ser::{Deserializable, Position}, protocol::{self, Protocol, ProtocolState, ClientPacket, ServerPacket}, Player, JsonValue, HTTP_CLIENT, CONFIG, client::event::ClientInfo};
|
||||||
use event::{ClientEvent, ServerEvent};
|
use event::{ClientEvent, ServerEvent};
|
||||||
|
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
@ -185,6 +185,7 @@ impl ClientState {
|
||||||
} else {
|
} else {
|
||||||
"server list ping"
|
"server list ping"
|
||||||
};
|
};
|
||||||
|
let max_players = CONFIG.get().unwrap().max_players;
|
||||||
self.write_packet(ServerPacket::StatusResponse(json!(
|
self.write_packet(ServerPacket::StatusResponse(json!(
|
||||||
{
|
{
|
||||||
"version": {
|
"version": {
|
||||||
|
@ -192,7 +193,7 @@ impl ClientState {
|
||||||
"protocol": self.proto.version,
|
"protocol": self.proto.version,
|
||||||
},
|
},
|
||||||
"players": {
|
"players": {
|
||||||
"max": 1337,
|
"max": max_players,
|
||||||
"online": 0,
|
"online": 0,
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
|
@ -217,7 +218,10 @@ impl ClientState {
|
||||||
if self.proto == protocol::COMMON {
|
if self.proto == protocol::COMMON {
|
||||||
self.write_packet(ServerPacket::LoginDisconnect(json!(
|
self.write_packet(ServerPacket::LoginDisconnect(json!(
|
||||||
{
|
{
|
||||||
"text": "Unsupported client version"
|
"translate": "multiplayer.disconnect.outdated_client",
|
||||||
|
"with": [
|
||||||
|
{ "text": "1.13.2, 1.14.4, 1.15.2, 1.16.5, 1.17.1, 1.18.2, 1.19.4, 1.20.1" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
))).await?;
|
))).await?;
|
||||||
debug!("#{} disconnecting due to unsupported version", self.id);
|
debug!("#{} disconnecting due to unsupported version", self.id);
|
||||||
|
@ -226,12 +230,11 @@ impl ClientState {
|
||||||
loop { select! {
|
loop { select! {
|
||||||
ev = rx.recv() => {
|
ev = rx.recv() => {
|
||||||
if let Some(ServerEvent::Disconnect(msg)) = ev {
|
if let Some(ServerEvent::Disconnect(msg)) = ev {
|
||||||
self.write_packet(ServerPacket::LoginDisconnect(json!(
|
let msg = msg.unwrap_or_else(|| json!({
|
||||||
{
|
"translate": "multiplayer.disconnected.generic"
|
||||||
"text": msg
|
}));
|
||||||
}
|
info!("#{} disconnecting: {}", self.id, msg);
|
||||||
))).await?;
|
self.write_packet(ServerPacket::LoginDisconnect(msg)).await?;
|
||||||
info!("#{} disconnecting: {}", self.id, msg.unwrap_or_default());
|
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -293,12 +296,11 @@ impl ClientState {
|
||||||
loop { select! {
|
loop { select! {
|
||||||
ev = rx.recv() => match ev {
|
ev = rx.recv() => match ev {
|
||||||
Some(ServerEvent::Disconnect(msg)) => {
|
Some(ServerEvent::Disconnect(msg)) => {
|
||||||
self.write_packet(ServerPacket::PlayDisconnect(json!(
|
let msg = msg.unwrap_or_else(|| json!({
|
||||||
{
|
"translate": "multiplayer.disconnect.generic"
|
||||||
"text": msg
|
}));
|
||||||
}
|
info!("#{} disconnecting: {}", self.id, msg);
|
||||||
))).await?;
|
self.write_packet(ServerPacket::PlayDisconnect(msg)).await?;
|
||||||
info!("#{} disconnecting: {}", self.id, msg.unwrap_or_default());
|
|
||||||
return Ok(())
|
return Ok(())
|
||||||
},
|
},
|
||||||
Some(ServerEvent::SystemMessage(msg, overlay)) => {
|
Some(ServerEvent::SystemMessage(msg, overlay)) => {
|
||||||
|
@ -313,6 +315,10 @@ impl ClientState {
|
||||||
debug!("#{} sending keepalive: {data}", self.id);
|
debug!("#{} sending keepalive: {data}", self.id);
|
||||||
self.write_packet(ServerPacket::KeepAlive(data)).await?;
|
self.write_packet(ServerPacket::KeepAlive(data)).await?;
|
||||||
},
|
},
|
||||||
|
Some(ServerEvent::UpdateCommands(data)) => {
|
||||||
|
debug!("#{} sending command data", self.id);
|
||||||
|
self.write_packet(ServerPacket::CommandData(data)).await?;
|
||||||
|
},
|
||||||
None => (),
|
None => (),
|
||||||
},
|
},
|
||||||
pk = self.read_packet() => match pk? {
|
pk = self.read_packet() => match pk? {
|
||||||
|
|
263
src/command.rs
Normal file
263
src/command.rs
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
use crate::{ser::Serializable, varint::VarInt};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Commands(Vec<Node>);
|
||||||
|
|
||||||
|
impl Commands {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let root = Node { children: Vec::new(), data: NodeData::Root };
|
||||||
|
let basic = Node {
|
||||||
|
children: Vec::new(),
|
||||||
|
data: NodeData::Argument {
|
||||||
|
redirect: None,
|
||||||
|
name: "args".to_owned(),
|
||||||
|
parser: Parser::String(StringType::GreedyPhrase),
|
||||||
|
suggestion: None,
|
||||||
|
executable: true,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Self(vec![root, basic])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_node(&mut self, data: NodeData) -> Result<i32, &'static str> {
|
||||||
|
if let NodeData::Root = data {
|
||||||
|
return Err("node data cannot be a root")
|
||||||
|
}
|
||||||
|
if let NodeData::Argument { redirect: Some(redir), .. } = data {
|
||||||
|
if redir < 0 || (redir as usize) >= self.0.len() {
|
||||||
|
return Err("invalid redirect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let NodeData::Literal { redirect: Some(redir), .. } = data {
|
||||||
|
if redir < 0 || (redir as usize) >= self.0.len() {
|
||||||
|
return Err("invalid redirect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let node = Node {
|
||||||
|
children: Vec::new(),
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
self.0.push(node);
|
||||||
|
Ok(self.0.len() as i32 - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_child(&mut self, parent: i32, child: i32) -> Result<(), &'static str> {
|
||||||
|
if parent < 0 || (parent as usize) >= self.0.len() {
|
||||||
|
return Err("invalid parent node")
|
||||||
|
}
|
||||||
|
if child < 0 || (child as usize) >= self.0.len() {
|
||||||
|
return Err("invalid child node")
|
||||||
|
}
|
||||||
|
self.0[parent as usize].children.push(child);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_simple_command(&mut self, name: String) -> Result<(), &'static str> {
|
||||||
|
let new_id = self.add_node(NodeData::Literal {
|
||||||
|
redirect: None,
|
||||||
|
name,
|
||||||
|
executable: true
|
||||||
|
})?;
|
||||||
|
self.add_child(Commands::root(), new_id)?;
|
||||||
|
self.add_child(new_id, Commands::basic_arg())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> i32 {
|
||||||
|
self.0.len() as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(&self, w: &mut impl std::io::Write, new: bool) -> anyhow::Result<()> {
|
||||||
|
for node in &self.0 {
|
||||||
|
node.serialize(w, new)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn root() -> i32 { 0 }
|
||||||
|
|
||||||
|
pub const fn basic_arg() -> i32 { 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Node {
|
||||||
|
children: Vec<i32>,
|
||||||
|
data: NodeData,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node {
|
||||||
|
pub fn serialize(&self, w: &mut impl std::io::Write, new: bool) -> anyhow::Result<()> {
|
||||||
|
match &self.data {
|
||||||
|
NodeData::Root => {
|
||||||
|
// flags (root node)
|
||||||
|
0u8.serialize(w)?;
|
||||||
|
// children
|
||||||
|
VarInt(self.children.len() as i32).serialize(w)?;
|
||||||
|
for child in &self.children {
|
||||||
|
VarInt(*child).serialize(w)?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NodeData::Literal { redirect, name, executable } => {
|
||||||
|
// flags:
|
||||||
|
let flags: u8 = 0x01 // literal node
|
||||||
|
| (*executable as u8 * 0x04) // is executable
|
||||||
|
| (redirect.is_some() as u8 * 0x08); // has redirect
|
||||||
|
flags.serialize(w)?;
|
||||||
|
// children
|
||||||
|
VarInt(self.children.len() as i32).serialize(w)?;
|
||||||
|
for child in &self.children {
|
||||||
|
VarInt(*child).serialize(w)?;
|
||||||
|
}
|
||||||
|
// redirect
|
||||||
|
if let Some(redir) = redirect {
|
||||||
|
VarInt(*redir).serialize(w)?;
|
||||||
|
}
|
||||||
|
// name
|
||||||
|
name.serialize(w)?;
|
||||||
|
},
|
||||||
|
NodeData::Argument { redirect, name, parser, suggestion, executable } => {
|
||||||
|
// flags:
|
||||||
|
let flags: u8 = 0x02 // argument node
|
||||||
|
| (*executable as u8 * 0x04) // is executable
|
||||||
|
| (redirect.is_some() as u8 * 0x08) // has redirect
|
||||||
|
| (suggestion.is_some() as u8 * 0x10); // has suggestion
|
||||||
|
flags.serialize(w)?;
|
||||||
|
// children
|
||||||
|
VarInt(self.children.len() as i32).serialize(w)?;
|
||||||
|
for child in &self.children {
|
||||||
|
VarInt(*child).serialize(w)?;
|
||||||
|
}
|
||||||
|
// redirect
|
||||||
|
if let Some(redir) = redirect {
|
||||||
|
VarInt(*redir).serialize(w)?;
|
||||||
|
}
|
||||||
|
// name
|
||||||
|
name.serialize(w)?;
|
||||||
|
// parser
|
||||||
|
parser.serialize(w, new)?;
|
||||||
|
// suggestion
|
||||||
|
if let Some(sug) = suggestion {
|
||||||
|
sug.serialize(w)?;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum NodeData {
|
||||||
|
Root,
|
||||||
|
Literal {
|
||||||
|
redirect: Option<i32>,
|
||||||
|
name: String,
|
||||||
|
executable: bool,
|
||||||
|
},
|
||||||
|
Argument {
|
||||||
|
redirect: Option<i32>,
|
||||||
|
name: String,
|
||||||
|
parser: Parser,
|
||||||
|
suggestion: Option<Suggestion>,
|
||||||
|
executable: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Suggestion {
|
||||||
|
AskServer
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serializable for Suggestion {
|
||||||
|
fn serialize(&self, w: &mut impl std::io::Write) -> anyhow::Result<()> {
|
||||||
|
match self {
|
||||||
|
Suggestion::AskServer => "minecraft:ask_server".serialize(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Parser {
|
||||||
|
Bool,
|
||||||
|
Float { min: Option<f32>, max: Option<f32> },
|
||||||
|
Double { min: Option<f64>, max: Option<f64> },
|
||||||
|
Integer { min: Option<i32>, max: Option<i32> },
|
||||||
|
Long { min: Option<i64>, max: Option<i64> },
|
||||||
|
String(StringType),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser {
|
||||||
|
pub fn serialize(&self, w: &mut impl std::io::Write, new: bool) -> anyhow::Result<()> {
|
||||||
|
match (self, new) {
|
||||||
|
(Parser::Bool, true) => VarInt(0).serialize(w)?,
|
||||||
|
(Parser::Bool, false) => "brigadier:bool".serialize(w)?,
|
||||||
|
(Parser::Float { .. }, true) => VarInt(1).serialize(w)?,
|
||||||
|
(Parser::Float { .. }, false) => "brigadier:float".serialize(w)?,
|
||||||
|
(Parser::Double { .. }, true) => VarInt(2).serialize(w)?,
|
||||||
|
(Parser::Double { .. }, false) => "brigadier:double".serialize(w)?,
|
||||||
|
(Parser::Integer { .. }, true) => VarInt(3).serialize(w)?,
|
||||||
|
(Parser::Integer { .. }, false) => "brigadier:integer".serialize(w)?,
|
||||||
|
(Parser::Long { .. }, true) => VarInt(4).serialize(w)?,
|
||||||
|
(Parser::Long { .. }, false) => "brigadier:long".serialize(w)?,
|
||||||
|
(Parser::String(_), true) => VarInt(5).serialize(w)?,
|
||||||
|
(Parser::String(_), false) => "brigadier:string".serialize(w)?,
|
||||||
|
};
|
||||||
|
match self {
|
||||||
|
Parser::Bool => (),
|
||||||
|
Parser::Float { min, max } => {
|
||||||
|
let flags: u8 = (min.is_some() as u8) | (max.is_some() as u8 * 0x02);
|
||||||
|
flags.serialize(w)?;
|
||||||
|
if let Some(min) = min {
|
||||||
|
min.serialize(w)?;
|
||||||
|
}
|
||||||
|
if let Some(max) = max {
|
||||||
|
max.serialize(w)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Parser::Double { min, max } => {
|
||||||
|
let flags: u8 = (min.is_some() as u8) | (max.is_some() as u8 * 0x02);
|
||||||
|
flags.serialize(w)?;
|
||||||
|
if let Some(min) = min {
|
||||||
|
min.serialize(w)?;
|
||||||
|
}
|
||||||
|
if let Some(max) = max {
|
||||||
|
max.serialize(w)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Parser::Integer { min, max } => {
|
||||||
|
let flags: u8 = (min.is_some() as u8) | (max.is_some() as u8 * 0x02);
|
||||||
|
flags.serialize(w)?;
|
||||||
|
if let Some(min) = min {
|
||||||
|
min.serialize(w)?;
|
||||||
|
}
|
||||||
|
if let Some(max) = max {
|
||||||
|
max.serialize(w)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Parser::Long { min, max } => {
|
||||||
|
let flags: u8 = (min.is_some() as u8) | (max.is_some() as u8 * 0x02);
|
||||||
|
flags.serialize(w)?;
|
||||||
|
if let Some(min) = min {
|
||||||
|
min.serialize(w)?;
|
||||||
|
}
|
||||||
|
if let Some(max) = max {
|
||||||
|
max.serialize(w)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Parser::String(ty) => {
|
||||||
|
match ty {
|
||||||
|
StringType::SingleWord => VarInt(0),
|
||||||
|
StringType::QuotablePhrase => VarInt(1),
|
||||||
|
StringType::GreedyPhrase => VarInt(2),
|
||||||
|
}.serialize(w)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum StringType {
|
||||||
|
SingleWord,
|
||||||
|
QuotablePhrase,
|
||||||
|
GreedyPhrase,
|
||||||
|
}
|
55
src/main.rs
55
src/main.rs
|
@ -1,7 +1,6 @@
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{time::Duration, net::SocketAddr};
|
||||||
|
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use tokio::sync::Mutex;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
mod protocol;
|
mod protocol;
|
||||||
|
@ -10,11 +9,12 @@ mod plugin;
|
||||||
mod ser;
|
mod ser;
|
||||||
mod varint;
|
mod varint;
|
||||||
mod client;
|
mod client;
|
||||||
|
mod command;
|
||||||
|
|
||||||
pub const QC_VERSION: &str = env!("CARGO_PKG_VERSION");
|
pub const QC_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
pub static HTTP_CLIENT: OnceCell<reqwest::Client> = OnceCell::new();
|
pub static HTTP_CLIENT: OnceCell<reqwest::Client> = OnceCell::new();
|
||||||
|
pub static CONFIG: OnceCell<ServerConfig> = OnceCell::new();
|
||||||
|
|
||||||
pub type ArcMutex<T> = Arc<Mutex<T>>;
|
|
||||||
pub type JsonValue = serde_json::Value;
|
pub type JsonValue = serde_json::Value;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -24,45 +24,30 @@ pub struct Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ClientInfo {
|
pub struct ServerConfig {
|
||||||
addr: String,
|
address: SocketAddr,
|
||||||
port: u16,
|
max_players: u32,
|
||||||
proto_version: i32,
|
|
||||||
proto_name: &'static str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
let address: SocketAddr = std::env::var("QC_ADDRESS")
|
||||||
|
.unwrap_or_else(|_| "0.0.0.0:25565".to_owned())
|
||||||
|
.parse().unwrap();
|
||||||
|
|
||||||
|
let max_players: u32 = std::env::var("QC_MAX_PLAYERS")
|
||||||
|
.map(|s| s.parse().unwrap())
|
||||||
|
.unwrap_or(20);
|
||||||
|
|
||||||
|
CONFIG.set(ServerConfig {
|
||||||
|
address,
|
||||||
|
max_players
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
HTTP_CLIENT.set(reqwest::ClientBuilder::new()
|
HTTP_CLIENT.set(reqwest::ClientBuilder::new()
|
||||||
.timeout(Duration::from_secs(5))
|
.timeout(Duration::from_secs(5))
|
||||||
.build().unwrap()
|
.build().unwrap()
|
||||||
).unwrap();
|
).unwrap();
|
||||||
|
|
||||||
server::run_server().await.unwrap()
|
server::run_server().await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary code used to parse packets collected from vanilla Minecraft servers,
|
|
||||||
// mostly to collect dimension/registry codecs
|
|
||||||
#[cfg(no)]
|
|
||||||
fn main() {
|
|
||||||
use varint::VarInt;
|
|
||||||
use ser::Deserializable;
|
|
||||||
use std::io::{Cursor, Write};
|
|
||||||
let packet = include_bytes!("/home/trimill/code/minecraft/joingame/1.16.5_bin");
|
|
||||||
let mut r = Cursor::new(packet);
|
|
||||||
let _packet_id = VarInt::deserialize(&mut r).unwrap();
|
|
||||||
let _eid = i32::deserialize(&mut r).unwrap();
|
|
||||||
let _hc = bool::deserialize(&mut r).unwrap();
|
|
||||||
let _gm = u8::deserialize(&mut r).unwrap();
|
|
||||||
let _pgm = i8::deserialize(&mut r).unwrap();
|
|
||||||
let dc = VarInt::deserialize(&mut r).unwrap().0;
|
|
||||||
for _ in 0..dc {
|
|
||||||
let _dim = String::deserialize(&mut r).unwrap();
|
|
||||||
}
|
|
||||||
let _dim_codec: nbt::Blob = nbt::from_reader(&mut r).unwrap();
|
|
||||||
let init = r.position() as usize;
|
|
||||||
let _dim: nbt::Blob = nbt::from_reader(&mut r).unwrap();
|
|
||||||
let end = r.position() as usize;
|
|
||||||
std::io::stdout().lock().write_all(&packet[init..end]).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,31 +1,23 @@
|
||||||
use mlua::{Value, FromLua, Table};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{Player, ClientInfo, JsonValue};
|
use crate::{Player, JsonValue};
|
||||||
|
|
||||||
use super::UuidUD;
|
|
||||||
|
|
||||||
pub enum PluginEvent {
|
pub enum PluginEvent {
|
||||||
Kick(Uuid, Option<String>),
|
Kick(Uuid, Option<JsonValue>),
|
||||||
Chat(Uuid, JsonValue, JsonValue),
|
Chat(JsonValue, Option<Uuid>),
|
||||||
BroadcastChat(JsonValue, JsonValue),
|
System(JsonValue, Option<Uuid>),
|
||||||
System(Uuid, JsonValue, bool),
|
UpdateCommands,
|
||||||
BroadcastSystem(JsonValue, bool),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for PluginEvent {
|
#[derive(Clone)]
|
||||||
fn from_lua(lua_value: Value<'lua>, lua: &'lua mlua::Lua) -> mlua::Result<Self> {
|
pub struct ConnectionInfo {
|
||||||
let table = Table::from_lua(lua_value, lua)?;
|
pub server_addr: (String, u16),
|
||||||
let ty: Box<str> = table.get("type")?;
|
pub remote_addr: (String, u16),
|
||||||
match ty.as_ref() {
|
pub protocol: (i32, String),
|
||||||
"kick" => Ok(Self::Kick(table.get::<_, UuidUD>("uuid")?.0, table.get("msg")?)),
|
|
||||||
_ => Err(mlua::Error::RuntimeError(format!("unknown event type {ty}"))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ServerEvent {
|
pub enum ServerEvent {
|
||||||
Join(Player, ClientInfo),
|
Join(Player, ConnectionInfo),
|
||||||
Leave(Player),
|
Leave(Player),
|
||||||
Command(Player, String),
|
Command(Player, String),
|
||||||
Message(Player, String),
|
Message(Player, String),
|
||||||
|
|
|
@ -2,9 +2,11 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use mlua::{Lua, Function, IntoLua, FromLua, Value, Table, AnyUserData};
|
use mlua::{Lua, Function, IntoLua, FromLua, Value, Table, AnyUserData};
|
||||||
use tokio::sync::mpsc::{Sender, Receiver};
|
use tokio::sync::{mpsc::{Sender, Receiver}, RwLock};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::command::Commands;
|
||||||
|
|
||||||
use self::{event::{PluginEvent, ServerEvent}, server::{Server, WrappedServer}, plugin::load_plugins};
|
use self::{event::{PluginEvent, ServerEvent}, server::{Server, WrappedServer}, plugin::load_plugins};
|
||||||
|
|
||||||
pub mod event;
|
pub mod event;
|
||||||
|
@ -34,22 +36,41 @@ impl mlua::UserData for UuidUD {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn broadcast_event<'lua, F>(lua: &'lua Lua, fname: &str, callback: F) -> mlua::Result<()>
|
fn broadcast_event<'lua, F>(lua: &'lua Lua, prio: &[String], fname: &str, callback: F) -> mlua::Result<()>
|
||||||
where F: Fn(&str, Table<'lua>, AnyUserData, Function) -> mlua::Result<()> {
|
where F: Fn(&str, Table<'lua>, AnyUserData, Function) -> mlua::Result<Option<bool>> {
|
||||||
let srv: AnyUserData = lua.globals().get("srv")?;
|
let srv: AnyUserData = lua.globals().get("srv")?;
|
||||||
let plugins: Table = srv.nth_user_value(1)?;
|
let plugins: Table = srv.nth_user_value(1)?;
|
||||||
for (id, table) in plugins.pairs::<String, Table>().filter_map(Result::ok) {
|
for id in prio {
|
||||||
|
let table: Table = plugins.get(id.clone())?;
|
||||||
if let Ok(f) = table.get::<_, Function>(fname) {
|
if let Ok(f) = table.get::<_, Function>(fname) {
|
||||||
srv.set_nth_user_value(2, id.clone())?;
|
srv.set_nth_user_value(2, id.clone())?;
|
||||||
if let Err(e) = callback(&id, table.clone(), srv.clone(), f) {
|
let result = callback(id, table.clone(), srv.clone(), f);
|
||||||
warn!("error in {fname} in plugin {}: {e}", id);
|
match result {
|
||||||
|
Ok(Some(true)) => return Ok(()),
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => warn!("error in {fname} in plugin {}: {e}", id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run_plugins(tx: Sender<PluginEvent>, mut rx: Receiver<ServerEvent>) -> mlua::Result<()> {
|
fn run_event<'lua, F>(lua: &'lua Lua, fname: &str, id: &str, callback: F) -> mlua::Result<Option<mlua::Error>>
|
||||||
|
where F: FnOnce(Table<'lua>, AnyUserData, Function) -> mlua::Result<()> {
|
||||||
|
let srv: AnyUserData = lua.globals().get("srv")?;
|
||||||
|
let plugins: Table = srv.nth_user_value(1)?;
|
||||||
|
let table: Table = plugins.get(id)?;
|
||||||
|
if let Ok(f) = table.get::<_, Function>(fname) {
|
||||||
|
srv.set_nth_user_value(2, id.clone())?;
|
||||||
|
if let Err(e) = callback(table.clone(), srv.clone(), f) {
|
||||||
|
warn!("error in {fname} in plugin {}: {e}", id);
|
||||||
|
return Ok(Some(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_plugins(tx: Sender<PluginEvent>, mut rx: Receiver<ServerEvent>, commands: Arc<RwLock<Commands>>) -> mlua::Result<()> {
|
||||||
let lua = unsafe {
|
let lua = unsafe {
|
||||||
mlua::Lua::unsafe_new_with(
|
mlua::Lua::unsafe_new_with(
|
||||||
mlua::StdLib::ALL_SAFE | mlua::StdLib::DEBUG,
|
mlua::StdLib::ALL_SAFE | mlua::StdLib::DEBUG,
|
||||||
|
@ -59,30 +80,36 @@ pub async fn run_plugins(tx: Sender<PluginEvent>, mut rx: Receiver<ServerEvent>)
|
||||||
|
|
||||||
let server = WrappedServer(Arc::new(Server {
|
let server = WrappedServer(Arc::new(Server {
|
||||||
tx,
|
tx,
|
||||||
|
commands,
|
||||||
|
command_ids: Default::default(),
|
||||||
names: Default::default(),
|
names: Default::default(),
|
||||||
info: Default::default(),
|
info: Default::default(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let prio;
|
||||||
{
|
{
|
||||||
let server_any: AnyUserData = FromLua::from_lua(IntoLua::into_lua(server.clone(), &lua)?, &lua)?;
|
let server_any: AnyUserData = FromLua::from_lua(IntoLua::into_lua(server.clone(), &lua)?, &lua)?;
|
||||||
let plugins = load_plugins(&lua)?;
|
let (plugins, priorities) = load_plugins(&lua)?;
|
||||||
|
prio = priorities;
|
||||||
|
|
||||||
server_any.set_nth_user_value(1, plugins)?;
|
server_any.set_nth_user_value(1, plugins)?;
|
||||||
server_any.set_nth_user_value(2, Value::Nil)?;
|
server_any.set_nth_user_value(2, Value::Nil)?;
|
||||||
lua.globals().set("srv", server_any.clone())?;
|
lua.globals().set("srv", server_any.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
broadcast_event(&lua, "init", |_, pl, _, f| {
|
broadcast_event(&lua, &prio, "init", |_, pl, _, f| {
|
||||||
f.call(pl)
|
f.call(pl)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
server.tx.send(PluginEvent::UpdateCommands).await.unwrap();
|
||||||
|
|
||||||
while let Some(ev) = rx.recv().await {
|
while let Some(ev) = rx.recv().await {
|
||||||
match ev {
|
match ev {
|
||||||
ServerEvent::Join(player, info) => {
|
ServerEvent::Join(player, conn_info) => {
|
||||||
server.names.write().await.insert(player.uuid, player.name.clone());
|
server.names.write().await.insert(player.uuid, player.name.clone());
|
||||||
server.info.write().await.insert(player.uuid, info);
|
server.info.write().await.insert(player.uuid, conn_info);
|
||||||
broadcast_event(&lua, "on_join", |_, pl, _, f| {
|
broadcast_event(&lua, &prio, "on_join", |_, pl, _, f| {
|
||||||
f.call::<_, ()>((
|
f.call((
|
||||||
pl,
|
pl,
|
||||||
UuidUD(player.uuid),
|
UuidUD(player.uuid),
|
||||||
player.name.clone()
|
player.name.clone()
|
||||||
|
@ -90,8 +117,8 @@ pub async fn run_plugins(tx: Sender<PluginEvent>, mut rx: Receiver<ServerEvent>)
|
||||||
})?;
|
})?;
|
||||||
},
|
},
|
||||||
ServerEvent::Leave(player) => {
|
ServerEvent::Leave(player) => {
|
||||||
broadcast_event(&lua, "on_leave", |_, pl, _, f| {
|
broadcast_event(&lua, &prio, "on_leave", |_, pl, _, f| {
|
||||||
f.call::<_, ()>((
|
f.call((
|
||||||
pl,
|
pl,
|
||||||
UuidUD(player.uuid),
|
UuidUD(player.uuid),
|
||||||
player.name.clone()
|
player.name.clone()
|
||||||
|
@ -101,8 +128,8 @@ pub async fn run_plugins(tx: Sender<PluginEvent>, mut rx: Receiver<ServerEvent>)
|
||||||
server.info.write().await.remove(&player.uuid);
|
server.info.write().await.remove(&player.uuid);
|
||||||
},
|
},
|
||||||
ServerEvent::Message(player, msg) => {
|
ServerEvent::Message(player, msg) => {
|
||||||
broadcast_event(&lua, "on_message", |_, pl, _, f| {
|
broadcast_event(&lua, &prio, "on_message", |_, pl, _, f| {
|
||||||
f.call::<_, ()>((
|
f.call((
|
||||||
pl,
|
pl,
|
||||||
msg.clone(),
|
msg.clone(),
|
||||||
UuidUD(player.uuid),
|
UuidUD(player.uuid),
|
||||||
|
@ -110,7 +137,51 @@ pub async fn run_plugins(tx: Sender<PluginEvent>, mut rx: Receiver<ServerEvent>)
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
},
|
},
|
||||||
ServerEvent::Command(_, _) => () // TODO command registerment
|
ServerEvent::Command(sender, msg) => {
|
||||||
|
let msg2 = msg.clone();
|
||||||
|
let mut parts = msg2.split_whitespace();
|
||||||
|
let cmd = parts.next().unwrap_or_default();
|
||||||
|
|
||||||
|
if let Some(id) = server.command_ids.read().await.get(cmd) {
|
||||||
|
let cmd = cmd.strip_prefix(&(id.clone() + ":")).unwrap_or(cmd);
|
||||||
|
|
||||||
|
let args = parts.map(|s| s.to_owned()).collect::<Vec<_>>();
|
||||||
|
let result = run_event(&lua, "on_command", id, |pl, _, f| {
|
||||||
|
f.call::<_, ()>((
|
||||||
|
pl,
|
||||||
|
cmd.to_owned(),
|
||||||
|
args,
|
||||||
|
msg,
|
||||||
|
UuidUD(sender.uuid),
|
||||||
|
sender.name,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
if result.is_some() {
|
||||||
|
let msg = serde_json::json!{{
|
||||||
|
"text": "An internal error occured while trying to execute that command",
|
||||||
|
"color": "red",
|
||||||
|
}};
|
||||||
|
server.tx.send(PluginEvent::System(msg, Some(sender.uuid)))
|
||||||
|
.await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// TODO couldn't do vanilla localization keys, so
|
||||||
|
// make this easier for plugins to modify/localize
|
||||||
|
let msg = serde_json::json!{[
|
||||||
|
{
|
||||||
|
"text": "Unknown command: ",
|
||||||
|
"color": "red",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": cmd,
|
||||||
|
"color": "gold",
|
||||||
|
}
|
||||||
|
]};
|
||||||
|
server.tx.send(PluginEvent::System(msg, Some(sender.uuid)))
|
||||||
|
.await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -6,7 +6,13 @@ use mlua::{Lua, Table};
|
||||||
|
|
||||||
use crate::QC_VERSION;
|
use crate::QC_VERSION;
|
||||||
|
|
||||||
fn load_plugin<'lua>(entry: &DirEntry, lua: &'lua Lua) -> anyhow::Result<(String, Table<'lua>)> {
|
struct PluginData<'lua> {
|
||||||
|
id: String,
|
||||||
|
priority: i32,
|
||||||
|
table: Table<'lua>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_plugin<'lua>(entry: &DirEntry, lua: &'lua Lua) -> anyhow::Result<PluginData<'lua>> {
|
||||||
let ty = entry.file_type()?;
|
let ty = entry.file_type()?;
|
||||||
let mut path = entry.path();
|
let mut path = entry.path();
|
||||||
if ty.is_dir() {
|
if ty.is_dir() {
|
||||||
|
@ -19,6 +25,14 @@ fn load_plugin<'lua>(entry: &DirEntry, lua: &'lua Lua) -> anyhow::Result<(String
|
||||||
let id = table.get("id")
|
let id = table.get("id")
|
||||||
.map_err(|e| anyhow!("could not get plugin id: {e}"))?;
|
.map_err(|e| anyhow!("could not get plugin id: {e}"))?;
|
||||||
|
|
||||||
|
let priority = table.get::<_, Option<i32>>("priority")
|
||||||
|
.map_err(|_| anyhow!("invalid priority value"))?
|
||||||
|
.unwrap_or(50);
|
||||||
|
|
||||||
|
if !(0..=100).contains(&priority) {
|
||||||
|
return Err(anyhow!("priority {priority} out of range 0..=100"))
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(qc_ver) = table.get::<_, String>("server_version") {
|
if let Ok(qc_ver) = table.get::<_, String>("server_version") {
|
||||||
debug!("checking plugin version");
|
debug!("checking plugin version");
|
||||||
let req = semver::VersionReq::parse(&qc_ver)?;
|
let req = semver::VersionReq::parse(&qc_ver)?;
|
||||||
|
@ -29,10 +43,10 @@ fn load_plugin<'lua>(entry: &DirEntry, lua: &'lua Lua) -> anyhow::Result<(String
|
||||||
warn!("plugin '{id}' is missing a version reqirement");
|
warn!("plugin '{id}' is missing a version reqirement");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((id, table))
|
Ok(PluginData { id, priority, table })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_plugins(lua: &Lua) -> mlua::Result<Table> {
|
pub fn load_plugins(lua: &Lua) -> mlua::Result<(Table, Vec<String>)> {
|
||||||
let plugins = lua.create_table()?;
|
let plugins = lua.create_table()?;
|
||||||
debug!("loading plugins");
|
debug!("loading plugins");
|
||||||
let entries = match fs::read_dir("./plugins") {
|
let entries = match fs::read_dir("./plugins") {
|
||||||
|
@ -42,13 +56,14 @@ pub fn load_plugins(lua: &Lua) -> mlua::Result<Table> {
|
||||||
if let Err(e) = fs::create_dir("./plugins") {
|
if let Err(e) = fs::create_dir("./plugins") {
|
||||||
warn!("failed to create plugins directory: {e}");
|
warn!("failed to create plugins directory: {e}");
|
||||||
}
|
}
|
||||||
return Ok(plugins)
|
return Ok((plugins, vec![]))
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("failed to load plugins: {e}");
|
warn!("failed to load plugins: {e}");
|
||||||
return Ok(plugins)
|
return Ok((plugins, vec![]))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let mut priorities = Vec::new();
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
let entry = match entry {
|
let entry = match entry {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
|
@ -61,12 +76,21 @@ pub fn load_plugins(lua: &Lua) -> mlua::Result<Table> {
|
||||||
let spath = path.to_str().unwrap_or("<non-unicode path>");
|
let spath = path.to_str().unwrap_or("<non-unicode path>");
|
||||||
debug!("loading plugin at {spath}");
|
debug!("loading plugin at {spath}");
|
||||||
match load_plugin(&entry, lua) {
|
match load_plugin(&entry, lua) {
|
||||||
Ok((id, table)) => {
|
Ok(plugin) => {
|
||||||
info!("loaded plugin {}", id);
|
if let Ok(true) = plugins.contains_key(plugin.id.clone()) {
|
||||||
plugins.set(id, table)?;
|
warn!("a plugin with id {} is already registered, skipping", plugin.id)
|
||||||
|
} else {
|
||||||
|
info!("loaded plugin {}", plugin.id);
|
||||||
|
plugins.set(plugin.id.clone(), plugin.table)?;
|
||||||
|
priorities.push((plugin.priority, plugin.id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(e) => warn!("error loading plugin at {spath}: {e}"),
|
Err(e) => warn!("error loading plugin at {spath}: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(plugins)
|
priorities.sort_by_cached_key(|(i, _)| -i);
|
||||||
|
debug!("plugin priorities: {priorities:?}");
|
||||||
|
let priorities = priorities.into_iter()
|
||||||
|
.map(|(_, id)| id).collect();
|
||||||
|
Ok((plugins, priorities))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::{HashMap, hash_map::Entry}, sync::Arc};
|
||||||
|
|
||||||
use log::{Record, warn};
|
use log::{Record, debug};
|
||||||
use mlua::{Value, IntoLua, Lua, Table, Function, AnyUserData, LuaSerdeExt};
|
use mlua::{Value, IntoLua, Lua, Table, Function, AnyUserData, LuaSerdeExt};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::sync::{mpsc::Sender, RwLock};
|
use tokio::sync::{mpsc::Sender, RwLock};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{ClientInfo, QC_VERSION, JsonValue};
|
use crate::{QC_VERSION, JsonValue, command::Commands};
|
||||||
|
|
||||||
use super::{event::PluginEvent, UuidUD};
|
use super::{event::{PluginEvent, ConnectionInfo}, UuidUD};
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for ClientInfo {
|
impl<'lua> IntoLua<'lua> for ConnectionInfo {
|
||||||
fn into_lua(self, lua: &'lua mlua::Lua) -> mlua::Result<Value<'lua>> {
|
fn into_lua(self, lua: &'lua mlua::Lua) -> mlua::Result<Value<'lua>> {
|
||||||
let table = lua.create_table()?;
|
let table = lua.create_table()?;
|
||||||
table.set("addr", self.addr)?;
|
table.set("server_addr", self.server_addr.0)?;
|
||||||
table.set("port", self.port)?;
|
table.set("server_port", self.server_addr.1)?;
|
||||||
table.set("proto_name", self.proto_name)?;
|
table.set("remote_addr", self.remote_addr.0)?;
|
||||||
table.set("proto_version", self.proto_version)?;
|
table.set("remote_port", self.remote_addr.1)?;
|
||||||
|
table.set("proto_version", self.protocol.0)?;
|
||||||
|
table.set("proto_name", self.protocol.1)?;
|
||||||
Ok(Value::Table(table))
|
Ok(Value::Table(table))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +43,6 @@ fn lua_log(level: log::Level, path: &str, msg: String, lua: &Lua) {
|
||||||
record.file(Some(file));
|
record.file(Some(file));
|
||||||
}
|
}
|
||||||
log::logger().log(&record.args(format_args!("{}", msg)).build());
|
log::logger().log(&record.args(format_args!("{}", msg)).build());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const LEVELS: [(&str, log::Level); 5] = [
|
const LEVELS: [(&str, log::Level); 5] = [
|
||||||
|
@ -54,18 +55,29 @@ const LEVELS: [(&str, log::Level); 5] = [
|
||||||
|
|
||||||
fn value_to_chat<'lua>(lua: &'lua Lua, value: Value<'lua>) -> mlua::Result<JsonValue> {
|
fn value_to_chat<'lua>(lua: &'lua Lua, value: Value<'lua>) -> mlua::Result<JsonValue> {
|
||||||
match value {
|
match value {
|
||||||
|
Value::Table(_) => Ok(lua.from_value(value)?),
|
||||||
Value::String(s) => Ok(json!{{"text": s.to_str()?}}),
|
Value::String(s) => Ok(json!{{"text": s.to_str()?}}),
|
||||||
Value::Nil => Ok(json!{{"text":""}}),
|
Value::Nil => Ok(json!{{"text":""}}),
|
||||||
Value::Table(_) => Ok(lua.from_value(value)?),
|
_ => Err(mlua::Error::RuntimeError("()".to_owned())),
|
||||||
_ => return Err(mlua::Error::RuntimeError("()".to_owned())),
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opt_value_to_chat<'lua>(lua: &'lua Lua, value: Value<'lua>) -> mlua::Result<Option<JsonValue>> {
|
||||||
|
match value {
|
||||||
|
Value::Table(_) => Ok(Some(lua.from_value(value)?)),
|
||||||
|
Value::String(s) => Ok(Some(json!{{"text": s.to_str()?}})),
|
||||||
|
Value::Nil => Ok(None),
|
||||||
|
_ => Err(mlua::Error::RuntimeError("()".to_owned())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
pub tx: Sender<PluginEvent>,
|
pub tx: Sender<PluginEvent>,
|
||||||
|
pub commands: Arc<RwLock<Commands>>,
|
||||||
|
pub command_ids: RwLock<HashMap<String, String>>,
|
||||||
pub names: RwLock<HashMap<Uuid, String>>,
|
pub names: RwLock<HashMap<Uuid, String>>,
|
||||||
pub info: RwLock<HashMap<Uuid, ClientInfo>>,
|
pub info: RwLock<HashMap<Uuid, ConnectionInfo>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -82,8 +94,59 @@ impl std::ops::Deref for WrappedServer {
|
||||||
impl mlua::UserData for WrappedServer {
|
impl mlua::UserData for WrappedServer {
|
||||||
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
// commands
|
// commands
|
||||||
methods.add_async_method("add_command", |_lua, _this, ()| async {
|
methods.add_async_function("add_command", |_lua, (this, name): (AnyUserData, String)| async move {
|
||||||
warn!("TODO add command");
|
let id: String = this.nth_user_value(2)?;
|
||||||
|
|
||||||
|
debug!("registering simple command '{name}' for plugin '{id}'");
|
||||||
|
|
||||||
|
let this_srv: std::cell::Ref<Self> = this.borrow()?;
|
||||||
|
|
||||||
|
let mut cmds_guard = this_srv.commands.write().await;
|
||||||
|
let mut cmd_ids_guard = this_srv.command_ids.write().await;
|
||||||
|
|
||||||
|
let qualified_name = id.clone() + ":" + &name;
|
||||||
|
|
||||||
|
// unqualified name
|
||||||
|
if let Some(other_id) = cmd_ids_guard.get(&name) {
|
||||||
|
debug!("cannot register unqualified command '{name}' for plugin '{id}', as it is already in use by plugin '{other_id}'")
|
||||||
|
} else {
|
||||||
|
cmd_ids_guard.insert(name.clone(), id.clone());
|
||||||
|
|
||||||
|
cmds_guard.add_simple_command(name)
|
||||||
|
.map_err(mlua::Error::runtime)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// qualified name
|
||||||
|
cmd_ids_guard.insert(qualified_name.clone(), id);
|
||||||
|
|
||||||
|
cmds_guard.add_simple_command(qualified_name)
|
||||||
|
.map_err(mlua::Error::runtime)?;
|
||||||
|
|
||||||
|
drop(cmd_ids_guard);
|
||||||
|
drop(cmds_guard);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_async_function("register_command", |_lua, (this, name): (AnyUserData, String)| async move {
|
||||||
|
let id: String = this.nth_user_value(2)?;
|
||||||
|
let this_srv: std::cell::Ref<Self> = this.borrow()?;
|
||||||
|
|
||||||
|
let mut cmd_ids_guard = this_srv.command_ids.write().await;
|
||||||
|
|
||||||
|
let qualified_name = id.clone() + ":" + &name;
|
||||||
|
cmd_ids_guard.insert(qualified_name, id.clone());
|
||||||
|
|
||||||
|
if let Entry::Vacant(entry) = cmd_ids_guard.entry(name) {
|
||||||
|
entry.insert(id.clone());
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_async_method("update_commands", |_lua, this, ()| async {
|
||||||
|
this.tx.send(PluginEvent::UpdateCommands).await.unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -96,11 +159,19 @@ impl mlua::UserData for WrappedServer {
|
||||||
Ok(table)
|
Ok(table)
|
||||||
});
|
});
|
||||||
methods.add_async_method("get_name", |lua, this, UuidUD(uuid)| async move {
|
methods.add_async_method("get_name", |lua, this, UuidUD(uuid)| async move {
|
||||||
match this.info.read().await.get(&uuid) {
|
match this.names.read().await.get(&uuid) {
|
||||||
Some(s) => s.clone().into_lua(lua),
|
Some(s) => s.clone().into_lua(lua),
|
||||||
None => Ok(Value::Nil),
|
None => Ok(Value::Nil),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
methods.add_async_method("get_uuid", |lua, this, name: String| async move {
|
||||||
|
for (uuid, pname) in this.names.read().await.iter() {
|
||||||
|
if pname.eq_ignore_ascii_case(&name) {
|
||||||
|
return UuidUD(*uuid).into_lua(lua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Value::Nil)
|
||||||
|
});
|
||||||
methods.add_async_method("get_info", |lua, this, UuidUD(uuid)| async move {
|
methods.add_async_method("get_info", |lua, this, UuidUD(uuid)| async move {
|
||||||
match this.info.read().await.get(&uuid) {
|
match this.info.read().await.get(&uuid) {
|
||||||
Some(i) => i.clone().into_lua(lua),
|
Some(i) => i.clone().into_lua(lua),
|
||||||
|
@ -109,58 +180,26 @@ impl mlua::UserData for WrappedServer {
|
||||||
});
|
});
|
||||||
|
|
||||||
// events
|
// events
|
||||||
methods.add_async_method("kick", |_, this, (UuidUD(uuid), msg)| async move {
|
methods.add_async_method("kick", |lua, this, (UuidUD(uuid), msg)| async move {
|
||||||
|
let msg = opt_value_to_chat(lua, msg)?;
|
||||||
this.tx
|
this.tx
|
||||||
.send(PluginEvent::Kick(uuid, msg))
|
.send(PluginEvent::Kick(uuid, msg))
|
||||||
.await
|
.await.unwrap();
|
||||||
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))
|
Ok(())
|
||||||
});
|
});
|
||||||
methods.add_async_method("send_msg", |lua, this, (UuidUD(uuid), msg, overlay): (_, Value, Option<bool>)| async move {
|
methods.add_async_method("send_msg", |lua, this, (msg, uuid): (Value, Option<UuidUD>)| async move {
|
||||||
let msg = match msg {
|
|
||||||
Value::String(s) => json!{{"text": s.to_str()?}},
|
|
||||||
Value::Table(_) => lua.from_value(msg)?,
|
|
||||||
_ => return Err(mlua::Error::RuntimeError("()".to_owned())),
|
|
||||||
};
|
|
||||||
this.tx
|
|
||||||
.send(PluginEvent::System(uuid, msg, overlay == Some(true)))
|
|
||||||
.await
|
|
||||||
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))
|
|
||||||
});
|
|
||||||
methods.add_async_method("broadcast_msg", |lua, this, (msg, overlay): (Value, Option<bool>)| async move {
|
|
||||||
let msg = match msg {
|
|
||||||
Value::String(s) => json!{{"text": s.to_str()?}},
|
|
||||||
Value::Table(_) => lua.from_value(msg)?,
|
|
||||||
_ => return Err(mlua::Error::RuntimeError("()".to_owned())),
|
|
||||||
};
|
|
||||||
this.tx
|
|
||||||
.send(PluginEvent::BroadcastSystem(msg, overlay == Some(true)))
|
|
||||||
.await
|
|
||||||
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))
|
|
||||||
});
|
|
||||||
methods.add_async_method("send_chat", |lua, this, (UuidUD(uuid), msg, name): (_, Value, Value)| async move {
|
|
||||||
let msg = value_to_chat(lua, msg)?;
|
let msg = value_to_chat(lua, msg)?;
|
||||||
let name = value_to_chat(lua, name)?;
|
|
||||||
this.tx
|
this.tx
|
||||||
.send(PluginEvent::Chat(uuid, msg, name))
|
.send(PluginEvent::System(msg, uuid.map(|u| u.0)))
|
||||||
.await
|
.await.unwrap();
|
||||||
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))
|
Ok(())
|
||||||
});
|
});
|
||||||
methods.add_async_method("broadcast_chat", |lua, this, (msg, name): (Value, Value)| async move {
|
methods.add_async_method("send_chat", |lua, this, (msg, uuid): (Value, Option<UuidUD>)| async move {
|
||||||
let msg = value_to_chat(lua, msg)?;
|
let msg = value_to_chat(lua, msg)?;
|
||||||
let name = value_to_chat(lua, name)?;
|
|
||||||
this.tx
|
this.tx
|
||||||
.send(PluginEvent::BroadcastChat(msg, name))
|
.send(PluginEvent::Chat(msg, uuid.map(|u| u.0)))
|
||||||
.await
|
.await.unwrap();
|
||||||
.map_err(|e| mlua::Error::RuntimeError(e.to_string()))
|
Ok(())
|
||||||
});
|
|
||||||
|
|
||||||
// utility
|
|
||||||
methods.add_function("check_semver", |_, (ver, req): (String, String)| {
|
|
||||||
let ver = semver::Version::parse(&ver)
|
|
||||||
.map_err(|_| mlua::Error::runtime(format!("invalid version: {ver}")))?;
|
|
||||||
let req = semver::VersionReq::parse(&req)
|
|
||||||
.map_err(|_| mlua::Error::runtime(format!("invalid version request: {req}")))?;
|
|
||||||
Ok(req.matches(&ver))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
|
@ -172,6 +211,22 @@ impl mlua::UserData for WrappedServer {
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// utility
|
||||||
|
methods.add_function("check_semver", |_, (ver, req): (String, String)| {
|
||||||
|
let ver = semver::Version::parse(&ver)
|
||||||
|
.map_err(|_| mlua::Error::runtime(format!("invalid version: {ver}")))?;
|
||||||
|
let req = semver::VersionReq::parse(&req)
|
||||||
|
.map_err(|_| mlua::Error::runtime(format!("invalid version request: {req}")))?;
|
||||||
|
Ok(req.matches(&ver))
|
||||||
|
});
|
||||||
|
methods.add_function("parse_uuid", |lua, uuid: String| {
|
||||||
|
match Uuid::parse_str(&uuid) {
|
||||||
|
Ok(u) => UuidUD(u).into_lua(lua),
|
||||||
|
Err(_) => Ok(Value::Nil),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::io::{Read, Write};
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{JsonValue, ser::Position};
|
use crate::{JsonValue, ser::Position, command::Commands};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
mod v1_20_1;
|
mod v1_20_1;
|
||||||
|
@ -91,6 +91,7 @@ pub enum ServerPacket {
|
||||||
},
|
},
|
||||||
SystemMessage(JsonValue, bool),
|
SystemMessage(JsonValue, bool),
|
||||||
ChatMessage(JsonValue, JsonValue),
|
ChatMessage(JsonValue, JsonValue),
|
||||||
|
CommandData(Commands),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::io::{Write, Read};
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use crate::{ser::{Serializable, Deserializable}, varint::VarInt};
|
use crate::{ser::{Serializable, Deserializable, PositionOld}, varint::VarInt, command::Commands};
|
||||||
|
|
||||||
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
||||||
|
|
||||||
|
@ -35,6 +35,12 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) ->
|
||||||
if overlay { 2u8 } else { 1u8 } // system or overlay
|
if overlay { 2u8 } else { 1u8 } // system or overlay
|
||||||
.serialize(&mut w)?;
|
.serialize(&mut w)?;
|
||||||
},
|
},
|
||||||
|
ServerPacket::CommandData(commands) => {
|
||||||
|
VarInt(0x11).serialize(&mut w)?;
|
||||||
|
VarInt(commands.len()).serialize(&mut w)?;
|
||||||
|
commands.serialize(&mut w, false)?;
|
||||||
|
VarInt(Commands::root()).serialize(&mut w)?;
|
||||||
|
},
|
||||||
ServerPacket::PluginMessage { channel, data } => {
|
ServerPacket::PluginMessage { channel, data } => {
|
||||||
VarInt(0x19).serialize(&mut w)?;
|
VarInt(0x19).serialize(&mut w)?;
|
||||||
channel.serialize(&mut w)?;
|
channel.serialize(&mut w)?;
|
||||||
|
@ -83,7 +89,7 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) ->
|
||||||
}
|
}
|
||||||
ServerPacket::SetDefaultSpawn { pos, angle: _ } => {
|
ServerPacket::SetDefaultSpawn { pos, angle: _ } => {
|
||||||
VarInt(0x49).serialize(&mut w)?;
|
VarInt(0x49).serialize(&mut w)?;
|
||||||
pos.serialize(&mut w)?;
|
PositionOld::from(pos).serialize(&mut w)?;
|
||||||
},
|
},
|
||||||
_ => { (COMMON.encode)(w, state, ev)?; }
|
_ => { (COMMON.encode)(w, state, ev)?; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::io::{Write, Read};
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use crate::{ser::{Serializable, Deserializable}, varint::VarInt};
|
use crate::{ser::{Serializable, Deserializable}, varint::VarInt, command::Commands};
|
||||||
|
|
||||||
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
||||||
|
|
||||||
|
@ -35,6 +35,12 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) ->
|
||||||
if overlay { 2u8 } else { 1u8 } // system or overlay
|
if overlay { 2u8 } else { 1u8 } // system or overlay
|
||||||
.serialize(&mut w)?;
|
.serialize(&mut w)?;
|
||||||
},
|
},
|
||||||
|
ServerPacket::CommandData(commands) => {
|
||||||
|
VarInt(0x11).serialize(&mut w)?;
|
||||||
|
VarInt(commands.len()).serialize(&mut w)?;
|
||||||
|
commands.serialize(&mut w, false)?;
|
||||||
|
VarInt(Commands::root()).serialize(&mut w)?;
|
||||||
|
},
|
||||||
ServerPacket::PluginMessage { channel, data } => {
|
ServerPacket::PluginMessage { channel, data } => {
|
||||||
VarInt(0x18).serialize(&mut w)?;
|
VarInt(0x18).serialize(&mut w)?;
|
||||||
channel.serialize(&mut w)?;
|
channel.serialize(&mut w)?;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::io::{Write, Read};
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use crate::{ser::{Serializable, Deserializable}, varint::VarInt};
|
use crate::{ser::{Serializable, Deserializable}, varint::VarInt, command::Commands};
|
||||||
|
|
||||||
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
||||||
|
|
||||||
|
@ -35,6 +35,12 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) ->
|
||||||
if overlay { 2u8 } else { 1u8 } // system or overlay
|
if overlay { 2u8 } else { 1u8 } // system or overlay
|
||||||
.serialize(&mut w)?;
|
.serialize(&mut w)?;
|
||||||
},
|
},
|
||||||
|
ServerPacket::CommandData(commands) => {
|
||||||
|
VarInt(0x12).serialize(&mut w)?;
|
||||||
|
VarInt(commands.len()).serialize(&mut w)?;
|
||||||
|
commands.serialize(&mut w, false)?;
|
||||||
|
VarInt(Commands::root()).serialize(&mut w)?;
|
||||||
|
},
|
||||||
ServerPacket::PluginMessage { channel, data } => {
|
ServerPacket::PluginMessage { channel, data } => {
|
||||||
VarInt(0x19).serialize(&mut w)?;
|
VarInt(0x19).serialize(&mut w)?;
|
||||||
channel.serialize(&mut w)?;
|
channel.serialize(&mut w)?;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::io::{Write, Read};
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use crate::{ser::{Serializable, Deserializable}, varint::VarInt};
|
use crate::{ser::{Serializable, Deserializable}, varint::VarInt, command::Commands};
|
||||||
|
|
||||||
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
||||||
|
|
||||||
|
@ -37,6 +37,12 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) ->
|
||||||
.serialize(&mut w)?;
|
.serialize(&mut w)?;
|
||||||
0u128.serialize(&mut w)?; // null uuid
|
0u128.serialize(&mut w)?; // null uuid
|
||||||
},
|
},
|
||||||
|
ServerPacket::CommandData(commands) => {
|
||||||
|
VarInt(0x10).serialize(&mut w)?;
|
||||||
|
VarInt(commands.len()).serialize(&mut w)?;
|
||||||
|
commands.serialize(&mut w, false)?;
|
||||||
|
VarInt(Commands::root()).serialize(&mut w)?;
|
||||||
|
},
|
||||||
ServerPacket::PlayDisconnect(msg) => {
|
ServerPacket::PlayDisconnect(msg) => {
|
||||||
VarInt(0x19).serialize(&mut w)?;
|
VarInt(0x19).serialize(&mut w)?;
|
||||||
msg.serialize(&mut w)?;
|
msg.serialize(&mut w)?;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::io::{Write, Read};
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use crate::{ser::{Serializable, Deserializable}, varint::VarInt};
|
use crate::{ser::{Serializable, Deserializable}, varint::VarInt, command::Commands};
|
||||||
|
|
||||||
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
||||||
|
|
||||||
|
@ -37,6 +37,12 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) ->
|
||||||
.serialize(&mut w)?;
|
.serialize(&mut w)?;
|
||||||
0u128.serialize(&mut w)?; // null uuid
|
0u128.serialize(&mut w)?; // null uuid
|
||||||
},
|
},
|
||||||
|
ServerPacket::CommandData(commands) => {
|
||||||
|
VarInt(0x12).serialize(&mut w)?;
|
||||||
|
VarInt(commands.len()).serialize(&mut w)?;
|
||||||
|
commands.serialize(&mut w, false)?;
|
||||||
|
VarInt(Commands::root()).serialize(&mut w)?;
|
||||||
|
},
|
||||||
ServerPacket::PluginMessage { channel, data } => {
|
ServerPacket::PluginMessage { channel, data } => {
|
||||||
VarInt(0x18).serialize(&mut w)?;
|
VarInt(0x18).serialize(&mut w)?;
|
||||||
channel.serialize(&mut w)?;
|
channel.serialize(&mut w)?;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::io::{Write, Read};
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use crate::{ser::{Serializable, Deserializable}, varint::VarInt};
|
use crate::{ser::{Serializable, Deserializable}, varint::VarInt, command::Commands};
|
||||||
|
|
||||||
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
||||||
|
|
||||||
|
@ -37,6 +37,12 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) ->
|
||||||
.serialize(&mut w)?;
|
.serialize(&mut w)?;
|
||||||
0u128.serialize(&mut w)?; // null uuid
|
0u128.serialize(&mut w)?; // null uuid
|
||||||
},
|
},
|
||||||
|
ServerPacket::CommandData(commands) => {
|
||||||
|
VarInt(0x12).serialize(&mut w)?;
|
||||||
|
VarInt(commands.len()).serialize(&mut w)?;
|
||||||
|
commands.serialize(&mut w, false)?;
|
||||||
|
VarInt(Commands::root()).serialize(&mut w)?;
|
||||||
|
},
|
||||||
ServerPacket::PluginMessage { channel, data } => {
|
ServerPacket::PluginMessage { channel, data } => {
|
||||||
VarInt(0x18).serialize(&mut w)?;
|
VarInt(0x18).serialize(&mut w)?;
|
||||||
channel.serialize(&mut w)?;
|
channel.serialize(&mut w)?;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::io::{Write, Read};
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{ser::{Serializable, Deserializable}, varint::VarInt};
|
use crate::{ser::{Serializable, Deserializable}, varint::VarInt, command::Commands};
|
||||||
|
|
||||||
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
||||||
|
|
||||||
|
@ -27,6 +27,12 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) ->
|
||||||
// number of properties (0)
|
// number of properties (0)
|
||||||
VarInt(0x00).serialize(&mut w)?;
|
VarInt(0x00).serialize(&mut w)?;
|
||||||
},
|
},
|
||||||
|
ServerPacket::CommandData(commands) => {
|
||||||
|
VarInt(0x10).serialize(&mut w)?;
|
||||||
|
VarInt(commands.len()).serialize(&mut w)?;
|
||||||
|
commands.serialize(&mut w, true)?;
|
||||||
|
VarInt(Commands::root()).serialize(&mut w)?;
|
||||||
|
},
|
||||||
ServerPacket::PlayDisconnect(msg) => {
|
ServerPacket::PlayDisconnect(msg) => {
|
||||||
VarInt(0x1A).serialize(&mut w)?;
|
VarInt(0x1A).serialize(&mut w)?;
|
||||||
msg.serialize(&mut w)?;
|
msg.serialize(&mut w)?;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::io::{Write, Read};
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{ser::{Serializable, Deserializable}, varint::VarInt};
|
use crate::{ser::{Serializable, Deserializable}, varint::VarInt, command::Commands};
|
||||||
|
|
||||||
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
use super::{ServerPacket, ClientPacket, Protocol, ProtocolState, common::COMMON};
|
||||||
|
|
||||||
|
@ -27,6 +27,12 @@ fn encode(mut w: Box<&mut dyn Write>, state: ProtocolState, ev: ServerPacket) ->
|
||||||
// number of properties (0)
|
// number of properties (0)
|
||||||
VarInt(0x00).serialize(&mut w)?;
|
VarInt(0x00).serialize(&mut w)?;
|
||||||
},
|
},
|
||||||
|
ServerPacket::CommandData(commands) => {
|
||||||
|
VarInt(0x10).serialize(&mut w)?;
|
||||||
|
VarInt(commands.len()).serialize(&mut w)?;
|
||||||
|
commands.serialize(&mut w, true)?;
|
||||||
|
VarInt(Commands::root()).serialize(&mut w)?;
|
||||||
|
},
|
||||||
ServerPacket::PluginMessage { channel, data } => {
|
ServerPacket::PluginMessage { channel, data } => {
|
||||||
VarInt(0x17).serialize(&mut w)?;
|
VarInt(0x17).serialize(&mut w)?;
|
||||||
channel.serialize(&mut w)?;
|
channel.serialize(&mut w)?;
|
||||||
|
|
42
src/ser.rs
42
src/ser.rs
|
@ -214,19 +214,6 @@ impl Deserializable for Uuid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Serializable for nbt::Blob {
|
|
||||||
// fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
|
||||||
// self.to_writer(w)?;
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// impl Deserializable for nbt::Blob {
|
|
||||||
// fn deserialize(r: &mut impl ReadExt) -> anyhow::Result<Self> {
|
|
||||||
// Ok(nbt::Blob::from_reader(r)?)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
|
@ -234,11 +221,34 @@ pub struct Position {
|
||||||
pub z: i32,
|
pub z: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Serializable for Position {
|
impl Serializable for Position {
|
||||||
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
let value = (((self.x & 0x3FFFFFF) as u64) << 38) |
|
let value = (((self.x & 0x3FFFFFF) as u64) << 38)
|
||||||
(((self.z & 0x3FFFFFF) as u64) << 12) |
|
| (((self.z & 0x3FFFFFF) as u64) << 12)
|
||||||
(((self.y & 0xFFF) as u64) << 38);
|
| ((self.y & 0xFFF) as u64);
|
||||||
value.serialize(w)
|
value.serialize(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct PositionOld {
|
||||||
|
pub x: i32,
|
||||||
|
pub z: i32,
|
||||||
|
pub y: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serializable for PositionOld {
|
||||||
|
fn serialize(&self, w: &mut impl Write) -> anyhow::Result<()> {
|
||||||
|
let value = (((self.x & 0x3FFFFFF) as u64) << 38)
|
||||||
|
| (((self.y & 0xFFF) as u64) << 26)
|
||||||
|
| ((self.z & 0x3FFFFFF) as u64);
|
||||||
|
value.serialize(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Position> for PositionOld {
|
||||||
|
fn from(value: Position) -> Self {
|
||||||
|
Self { x: value.x, y: value.y, z: value.z }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
use std::{collections::HashMap, time::Duration, net::{ToSocketAddrs, SocketAddr}};
|
use std::{collections::HashMap, time::Duration, net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
use log::{info, error};
|
use log::{info, error};
|
||||||
use tokio::{net::{TcpListener, TcpStream}, sync::mpsc::{Sender, self}, select};
|
use serde_json::json;
|
||||||
|
use tokio::{net::{TcpListener, TcpStream}, sync::{mpsc::{Sender, self}, RwLock}, select};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
use crate::{client::run_client, Player, ClientInfo, client::event::{ServerEvent, ClientEvent}, plugin::{run_plugins, event::PluginEvent}};
|
use crate::{client::{event::ClientInfo, run_client}, Player, client::event::{ServerEvent, ClientEvent}, plugin::{run_plugins, event::{PluginEvent, ConnectionInfo}}, command::Commands, CONFIG};
|
||||||
|
|
||||||
type PServerEvent = crate::plugin::event::ServerEvent;
|
type PServerEvent = crate::plugin::event::ServerEvent;
|
||||||
|
|
||||||
pub struct Client {
|
|
||||||
|
struct Client {
|
||||||
tx: Sender<ServerEvent>,
|
tx: Sender<ServerEvent>,
|
||||||
|
remote_addr: SocketAddr,
|
||||||
keepalive: Option<i64>,
|
keepalive: Option<i64>,
|
||||||
info: Option<ClientInfo>,
|
info: Option<ClientInfo>,
|
||||||
player: Option<Player>,
|
player: Option<Player>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct Server {
|
struct Server {
|
||||||
|
commands: Arc<RwLock<Commands>>,
|
||||||
gc_tx: Sender<(i32, ClientEvent)>,
|
gc_tx: Sender<(i32, ClientEvent)>,
|
||||||
ps_tx: Sender<PServerEvent>,
|
ps_tx: Sender<PServerEvent>,
|
||||||
next_id: i32,
|
next_id: i32,
|
||||||
|
@ -30,19 +35,22 @@ pub async fn run_server() -> anyhow::Result<()> {
|
||||||
let (ps_tx, ps_rx) = mpsc::channel(256);
|
let (ps_tx, ps_rx) = mpsc::channel(256);
|
||||||
let (pc_tx, mut pc_rx) = mpsc::channel(256);
|
let (pc_tx, mut pc_rx) = mpsc::channel(256);
|
||||||
|
|
||||||
|
let commands = Arc::new(RwLock::new(Commands::new()));
|
||||||
|
let pl_commands = commands.clone();
|
||||||
|
|
||||||
tokio::spawn(async {
|
tokio::spawn(async {
|
||||||
if let Err(e) = run_plugins(pc_tx, ps_rx).await {
|
if let Err(e) = run_plugins(pc_tx, ps_rx, pl_commands).await {
|
||||||
println!("error in plugins: {e}");
|
error!("error in plugins: {e}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let socket_addr = "0.0.0.0:25567".to_socket_addrs()?
|
let server_addr = CONFIG.get().unwrap().address;
|
||||||
.next().expect("invalid server address");
|
let listener = TcpListener::bind(server_addr).await?;
|
||||||
let listener = TcpListener::bind(socket_addr).await?;
|
|
||||||
|
|
||||||
let (gc_tx, mut gc_rx) = mpsc::channel(16);
|
let (gc_tx, mut gc_rx) = mpsc::channel(16);
|
||||||
|
|
||||||
let mut server = Server {
|
let mut server = Server {
|
||||||
|
commands,
|
||||||
gc_tx, ps_tx,
|
gc_tx, ps_tx,
|
||||||
next_id: 0,
|
next_id: 0,
|
||||||
clients: HashMap::new(),
|
clients: HashMap::new(),
|
||||||
|
@ -51,12 +59,12 @@ pub async fn run_server() -> anyhow::Result<()> {
|
||||||
|
|
||||||
let mut keepalive = tokio::time::interval(Duration::from_secs(15));
|
let mut keepalive = tokio::time::interval(Duration::from_secs(15));
|
||||||
|
|
||||||
info!("listening on {socket_addr}");
|
info!("listening on {server_addr}");
|
||||||
|
|
||||||
loop { select! {
|
loop { select! {
|
||||||
conn = listener.accept() => {
|
conn = listener.accept() => {
|
||||||
let (stream, addr) = conn?;
|
let (stream, addr) = conn?;
|
||||||
accept_connection(&mut server, stream, addr).await?;
|
accept_connection(&mut server, stream, addr).await;
|
||||||
},
|
},
|
||||||
_ = keepalive.tick() => handle_keepalive(&mut server).await?,
|
_ = keepalive.tick() => handle_keepalive(&mut server).await?,
|
||||||
ev = pc_rx.recv() => {
|
ev = pc_rx.recv() => {
|
||||||
|
@ -74,15 +82,21 @@ pub async fn run_server() -> anyhow::Result<()> {
|
||||||
} }
|
} }
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn accept_connection(server: &mut Server, stream: TcpStream, addr: SocketAddr) -> anyhow::Result<()> {
|
async fn accept_connection(server: &mut Server, stream: TcpStream, addr: SocketAddr) {
|
||||||
let gc_tx = server.gc_tx.clone();
|
let gc_tx = server.gc_tx.clone();
|
||||||
|
|
||||||
|
let id = loop {
|
||||||
let id = server.next_id;
|
let id = server.next_id;
|
||||||
server.next_id = server.next_id.wrapping_add(1);
|
server.next_id = server.next_id.wrapping_add(1);
|
||||||
|
if !server.clients.contains_key(&id) {
|
||||||
|
break id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let (gs_tx, gs_rx) = mpsc::channel(16);
|
let (gs_tx, gs_rx) = mpsc::channel(16);
|
||||||
server.clients.insert(id, Client {
|
server.clients.insert(id, Client {
|
||||||
tx: gs_tx,
|
tx: gs_tx,
|
||||||
|
remote_addr: addr,
|
||||||
keepalive: None,
|
keepalive: None,
|
||||||
info: None,
|
info: None,
|
||||||
player: None,
|
player: None,
|
||||||
|
@ -96,13 +110,12 @@ async fn accept_connection(server: &mut Server, stream: TcpStream, addr: SocketA
|
||||||
}
|
}
|
||||||
c_tx2.send((id, ClientEvent::Disconnect)).await.unwrap();
|
c_tx2.send((id, ClientEvent::Disconnect)).await.unwrap();
|
||||||
});
|
});
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_keepalive(server: &mut Server) -> anyhow::Result<()> {
|
async fn handle_keepalive(server: &mut Server) -> anyhow::Result<()> {
|
||||||
for client in server.clients.values_mut() {
|
for client in server.clients.values_mut() {
|
||||||
if client.keepalive.is_some() {
|
if client.keepalive.is_some() {
|
||||||
client.tx.send(ServerEvent::Disconnect(Some("Failed to respond to keep alives".to_owned()))).await?;
|
client.tx.send(ServerEvent::Disconnect(None)).await?;
|
||||||
} else if client.player.is_some() {
|
} else if client.player.is_some() {
|
||||||
let data = 1;
|
let data = 1;
|
||||||
client.keepalive = Some(data);
|
client.keepalive = Some(data);
|
||||||
|
@ -121,34 +134,42 @@ async fn handle_plugin_event(server: &mut Server, ev: PluginEvent) -> anyhow::Re
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PluginEvent::Chat(uuid, msg, name) => {
|
PluginEvent::Chat(msg, Some(uuid)) => {
|
||||||
if let Some(id) = server.ids.get(&uuid) {
|
if let Some(id) = server.ids.get(&uuid) {
|
||||||
server.clients[id].tx
|
server.clients[id].tx
|
||||||
.send(ServerEvent::ChatMessage(msg, name))
|
.send(ServerEvent::ChatMessage(msg, serde_json::json!({"text":""})))
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PluginEvent::BroadcastChat(msg, name) => {
|
PluginEvent::Chat(msg, None) => {
|
||||||
for client in server.clients.values() {
|
for client in server.clients.values() {
|
||||||
if client.player.is_some() {
|
if client.player.is_some() {
|
||||||
client.tx.send(ServerEvent::ChatMessage(msg.clone(), name.clone())).await?;
|
client.tx.send(ServerEvent::ChatMessage(msg.clone(), serde_json::json!({"text":""}))).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PluginEvent::System(uuid, msg, overlay) => {
|
PluginEvent::System(msg, Some(uuid)) => {
|
||||||
if let Some(id) = server.ids.get(&uuid) {
|
if let Some(id) = server.ids.get(&uuid) {
|
||||||
server.clients[id].tx
|
server.clients[id].tx
|
||||||
.send(ServerEvent::SystemMessage(msg, overlay))
|
.send(ServerEvent::SystemMessage(msg, false))
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PluginEvent::BroadcastSystem(msg, overlay) => {
|
PluginEvent::System(msg, None) => {
|
||||||
for client in server.clients.values() {
|
for client in server.clients.values() {
|
||||||
if client.player.is_some() {
|
if client.player.is_some() {
|
||||||
client.tx.send(ServerEvent::SystemMessage(msg.clone(), overlay)).await?;
|
client.tx.send(ServerEvent::SystemMessage(msg.clone(), false)).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
PluginEvent::UpdateCommands => {
|
||||||
|
let commands = server.commands.read().await.clone();
|
||||||
|
for client in server.clients.values() {
|
||||||
|
if client.player.is_some() {
|
||||||
|
client.tx.send(ServerEvent::UpdateCommands(commands.clone())).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -165,13 +186,20 @@ async fn handle_client_event(server: &mut Server, id: i32, ev: ClientEvent) -> a
|
||||||
.send(PServerEvent::Leave(other_client.player.unwrap()))
|
.send(PServerEvent::Leave(other_client.player.unwrap()))
|
||||||
.await?;
|
.await?;
|
||||||
other_client.tx
|
other_client.tx
|
||||||
.send(ServerEvent::Disconnect(Some("logged in elsewhere".to_owned())))
|
.send(ServerEvent::Disconnect(Some(json!({"translate": "multiplayer.disconnect.duplicate_login"}))))
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
let client = server.clients.get_mut(&id).unwrap();
|
let client = server.clients.get_mut(&id).unwrap();
|
||||||
client.player = Some(player.clone());
|
client.player = Some(player.clone());
|
||||||
|
client.tx.send(ServerEvent::UpdateCommands(server.commands.read().await.clone())).await?;
|
||||||
server.ids.insert(player.uuid, id);
|
server.ids.insert(player.uuid, id);
|
||||||
server.ps_tx.send(PServerEvent::Join(player, client.info.clone().unwrap())).await?;
|
let info = client.info.as_ref().unwrap();
|
||||||
|
let conn_info = ConnectionInfo {
|
||||||
|
server_addr: (info.addr.to_owned(), info.port),
|
||||||
|
remote_addr: (client.remote_addr.ip().to_string(), client.remote_addr.port()),
|
||||||
|
protocol: (info.proto_version, info.proto_name.to_owned())
|
||||||
|
};
|
||||||
|
server.ps_tx.send(PServerEvent::Join(player, conn_info)).await?;
|
||||||
},
|
},
|
||||||
ClientEvent::Disconnect => if let Some(client) = server.clients.remove(&id) {
|
ClientEvent::Disconnect => if let Some(client) = server.clients.remove(&id) {
|
||||||
if let Some(player) = client.player {
|
if let Some(player) = client.player {
|
||||||
|
|
Loading…
Reference in a new issue