feat(plugin-api): add entity attributes
This commit is contained in:
parent
0fe84c75d8
commit
c13c515081
@ -3,7 +3,7 @@ name = "pumpkin-plugin-api"
|
||||
# This should be different from the pumpkin server version.
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen = { workspace = true }
|
||||
|
||||
176
crates/pumpkin-plugin-api/LICENSE-APACHE
Normal file
176
crates/pumpkin-plugin-api/LICENSE-APACHE
Normal file
@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or exemplary damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
21
crates/pumpkin-plugin-api/LICENSE-MIT
Normal file
21
crates/pumpkin-plugin-api/LICENSE-MIT
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-2026 Pumpkin Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
31
crates/pumpkin-plugin-api/src/ext/attributes.rs
Normal file
31
crates/pumpkin-plugin-api/src/ext/attributes.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use crate::wit::pumpkin::plugin::attributes::{AttributeModifier, ModifierOperation};
|
||||
|
||||
impl AttributeModifier {
|
||||
/// Creates a new attribute modifier.
|
||||
#[must_use]
|
||||
pub fn new(id: impl Into<String>, amount: f64, operation: ModifierOperation) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
amount,
|
||||
operation,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an additive attribute modifier.
|
||||
#[must_use]
|
||||
pub fn add(id: impl Into<String>, amount: f64) -> Self {
|
||||
Self::new(id, amount, ModifierOperation::Add)
|
||||
}
|
||||
|
||||
/// Creates a multiplicative base attribute modifier (`base * (1 + amount)`).
|
||||
#[must_use]
|
||||
pub fn multiply_base(id: impl Into<String>, amount: f64) -> Self {
|
||||
Self::new(id, amount, ModifierOperation::MultiplyBase)
|
||||
}
|
||||
|
||||
/// Creates a multiplicative total attribute modifier (`total * (1 + amount)`).
|
||||
#[must_use]
|
||||
pub fn multiply_total(id: impl Into<String>, amount: f64) -> Self {
|
||||
Self::new(id, amount, ModifierOperation::MultiplyTotal)
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
//! WIT-generated plugin type extensions belong in here. For example [Display](std::fmt::Display) implementations.
|
||||
|
||||
mod attributes;
|
||||
mod player;
|
||||
mod server_list_ping;
|
||||
mod uuid;
|
||||
|
||||
110
crates/pumpkin-plugin-api/src/ext/player.rs
Normal file
110
crates/pumpkin-plugin-api/src/ext/player.rs
Normal file
@ -0,0 +1,110 @@
|
||||
use crate::wit::pumpkin::plugin::player::{
|
||||
BanIpOptions, BanPlayerOptions, BedrockDisconnectReason, BedrockKickOptions, JavaKickOptions,
|
||||
SocketTeardownPolicy,
|
||||
};
|
||||
use crate::wit::pumpkin::plugin::text::TextComponent;
|
||||
|
||||
impl JavaKickOptions {
|
||||
/// Creates a new `JavaKickOptions` with the given reason and default settings.
|
||||
#[must_use]
|
||||
pub fn new(reason: TextComponent) -> Self {
|
||||
Self {
|
||||
reason,
|
||||
log_to_console: true,
|
||||
teardown_policy: SocketTeardownPolicy::Graceful,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BedrockKickOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reason: BedrockDisconnectReason::Kicked,
|
||||
message: String::new(),
|
||||
skip_message: false,
|
||||
filtered_message: String::new(),
|
||||
log_to_console: true,
|
||||
teardown_policy: SocketTeardownPolicy::Graceful,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BedrockKickOptions {
|
||||
/// Creates a new `BedrockKickOptions` with the given reason, message, and default settings.
|
||||
#[must_use]
|
||||
pub fn new(reason: BedrockDisconnectReason, message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
reason,
|
||||
message: message.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BanPlayerOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reason: None,
|
||||
source: None,
|
||||
expires_at_utc: None,
|
||||
duration_seconds: None,
|
||||
kick_if_online: true,
|
||||
log_to_console: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BanPlayerOptions {
|
||||
/// Creates a new permanent ban with an optional reason and default settings.
|
||||
#[must_use]
|
||||
pub fn new(reason: Option<TextComponent>) -> Self {
|
||||
Self {
|
||||
reason,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a temporary ban with a specific duration in seconds.
|
||||
#[must_use]
|
||||
pub fn temporary(reason: Option<TextComponent>, duration_seconds: u64) -> Self {
|
||||
Self {
|
||||
reason,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BanIpOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reason: None,
|
||||
source: None,
|
||||
expires_at_utc: None,
|
||||
duration_seconds: None,
|
||||
kick_matching_players: true,
|
||||
log_to_console: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BanIpOptions {
|
||||
/// Creates a new permanent IP ban with an optional reason and default settings.
|
||||
#[must_use]
|
||||
pub fn new(reason: Option<TextComponent>) -> Self {
|
||||
Self {
|
||||
reason,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a temporary IP ban with a specific duration in seconds.
|
||||
#[must_use]
|
||||
pub fn temporary(reason: Option<TextComponent>, duration_seconds: u64) -> Self {
|
||||
Self {
|
||||
reason,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +1 @@
|
||||
Subproject commit 2f91c555a4e902bf3e69d5cd0a60835232b2a92d
|
||||
Subproject commit d6a49a2451c6e813d79cd965196dd0ec325444cc
|
||||
@ -3654,25 +3654,45 @@ impl Player {
|
||||
}
|
||||
|
||||
pub async fn ban(&self, server: &Server, reason: Option<TextComponent>) {
|
||||
let mut banned_players = server.data.banned_player_list.write().await;
|
||||
self.ban_explicit(server, reason, None, None, true, true)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn ban_explicit(
|
||||
&self,
|
||||
server: &Server,
|
||||
reason: Option<TextComponent>,
|
||||
source: Option<String>,
|
||||
expires: Option<time::OffsetDateTime>,
|
||||
kick_if_online: bool,
|
||||
log_to_console: bool,
|
||||
) {
|
||||
let string_reason = reason.clone().map_or_else(
|
||||
|| "Banned by an operator.".to_string(),
|
||||
pumpkin_util::text::TextComponent::get_text,
|
||||
);
|
||||
let source_str = source.unwrap_or_else(|| "Plugin".to_string());
|
||||
|
||||
if banned_players
|
||||
.banned_players
|
||||
.iter()
|
||||
.any(|entry| entry.uuid == self.gameprofile.id)
|
||||
{
|
||||
return;
|
||||
if log_to_console {
|
||||
tracing::info!(
|
||||
"Banning player {} ({}) by {}: {}",
|
||||
self.gameprofile.name,
|
||||
self.gameprofile.id,
|
||||
source_str,
|
||||
string_reason
|
||||
);
|
||||
}
|
||||
|
||||
let mut banned_players = server.data.banned_player_list.write().await;
|
||||
banned_players
|
||||
.banned_players
|
||||
.retain(|entry| entry.uuid != self.gameprofile.id);
|
||||
|
||||
banned_players.banned_players.push(
|
||||
crate::data::banlist_serializer::BannedPlayerEntry::new(
|
||||
&self.gameprofile,
|
||||
"Plugin".to_string(),
|
||||
None,
|
||||
source_str,
|
||||
expires,
|
||||
string_reason,
|
||||
),
|
||||
);
|
||||
@ -3680,54 +3700,79 @@ impl Player {
|
||||
banned_players.save();
|
||||
drop(banned_players);
|
||||
|
||||
let kick_reason = reason.unwrap_or_else(|| {
|
||||
TextComponent::translate_cross(
|
||||
translation::java::MULTIPLAYER_DISCONNECT_BANNED,
|
||||
translation::bedrock::DISCONNECTIONSCREEN_TITLE_BANNEDBYHOST,
|
||||
[],
|
||||
)
|
||||
});
|
||||
if kick_if_online {
|
||||
let kick_reason = reason.unwrap_or_else(|| {
|
||||
TextComponent::translate_cross(
|
||||
translation::java::MULTIPLAYER_DISCONNECT_BANNED,
|
||||
translation::bedrock::DISCONNECTIONSCREEN_TITLE_BANNEDBYHOST,
|
||||
[],
|
||||
)
|
||||
});
|
||||
|
||||
self.kick(DisconnectReason::Kicked, kick_reason).await;
|
||||
self.kick(DisconnectReason::Kicked, kick_reason).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ban_ip(&self, server: &Server, reason: Option<TextComponent>) {
|
||||
let mut banned_ips = server.data.banned_ip_list.write().await;
|
||||
self.ban_ip_explicit(server, reason, None, None, true, true)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn ban_ip_explicit(
|
||||
&self,
|
||||
server: &Server,
|
||||
reason: Option<TextComponent>,
|
||||
source: Option<String>,
|
||||
expires: Option<time::OffsetDateTime>,
|
||||
kick_matching_players: bool,
|
||||
log_to_console: bool,
|
||||
) {
|
||||
let string_reason = reason.clone().map_or_else(
|
||||
|| "Banned by an operator.".to_string(),
|
||||
pumpkin_util::text::TextComponent::get_text,
|
||||
);
|
||||
let source_str = source.unwrap_or_else(|| "Plugin".to_string());
|
||||
let target_ip = self.client.address().ip();
|
||||
|
||||
if banned_ips.get_entry(&target_ip).is_some() {
|
||||
return;
|
||||
if log_to_console {
|
||||
tracing::info!(
|
||||
"Banning IP {} by {}: {}",
|
||||
target_ip,
|
||||
source_str,
|
||||
string_reason
|
||||
);
|
||||
}
|
||||
|
||||
let mut banned_ips = server.data.banned_ip_list.write().await;
|
||||
banned_ips.banned_ips.retain(|entry| entry.ip != target_ip);
|
||||
|
||||
banned_ips
|
||||
.banned_ips
|
||||
.push(crate::data::banlist_serializer::BannedIpEntry::new(
|
||||
target_ip,
|
||||
"Plugin".to_string(),
|
||||
None,
|
||||
source_str,
|
||||
expires,
|
||||
string_reason,
|
||||
));
|
||||
|
||||
banned_ips.save();
|
||||
drop(banned_ips);
|
||||
|
||||
let kick_reason = reason.unwrap_or_else(|| {
|
||||
TextComponent::translate_cross(
|
||||
translation::java::MULTIPLAYER_DISCONNECT_IP_BANNED,
|
||||
translation::java::MULTIPLAYER_DISCONNECT_IP_BANNED,
|
||||
[],
|
||||
)
|
||||
});
|
||||
if kick_matching_players {
|
||||
let kick_reason = reason.unwrap_or_else(|| {
|
||||
TextComponent::translate_cross(
|
||||
translation::java::MULTIPLAYER_DISCONNECT_IP_BANNED,
|
||||
translation::java::MULTIPLAYER_DISCONNECT_IP_BANNED,
|
||||
[],
|
||||
)
|
||||
});
|
||||
|
||||
let affected = server.get_players_by_ip(target_ip);
|
||||
for target in affected {
|
||||
target
|
||||
.kick(DisconnectReason::Kicked, kick_reason.clone())
|
||||
.await;
|
||||
let affected = server.get_players_by_ip(target_ip);
|
||||
for target in affected {
|
||||
target
|
||||
.kick(DisconnectReason::Kicked, kick_reason.clone())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -271,6 +271,26 @@ impl BedrockClient {
|
||||
self.close().await;
|
||||
}
|
||||
|
||||
pub async fn kick_explicit(
|
||||
&self,
|
||||
reason: DisconnectReason,
|
||||
message: String,
|
||||
skip_message: bool,
|
||||
filtered_message: String,
|
||||
send_packet: bool,
|
||||
) {
|
||||
if send_packet {
|
||||
self.send_game_packet(&CDisconnectPlayer {
|
||||
reason: pumpkin_protocol::codec::var_int::VarInt(reason as i32),
|
||||
skip_message,
|
||||
message,
|
||||
filtered_message,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
self.close().await;
|
||||
}
|
||||
|
||||
pub async fn send_chunks(&self, chunks: &[SyncChunk]) {
|
||||
let player = self.player.load_full();
|
||||
let Some(player) = player.as_ref() else {
|
||||
|
||||
@ -425,20 +425,26 @@ impl JavaClient {
|
||||
}
|
||||
|
||||
pub async fn kick(&self, reason: TextComponent) {
|
||||
match self.connection_state.load() {
|
||||
ConnectionState::Login => {
|
||||
// TextComponent implements Serialize and writes in bytes instead of String, that's the reasib we only use content
|
||||
self.send_packet_now(&CLoginDisconnect::new(
|
||||
serde_json::to_string(&reason.0).unwrap_or_else(|_| String::new()),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
ConnectionState::Config => {
|
||||
self.send_packet_now(&CConfigDisconnect::new(&reason.get_text()))
|
||||
self.kick_explicit(&reason, true).await;
|
||||
}
|
||||
|
||||
pub async fn kick_explicit(&self, reason: &TextComponent, send_packet: bool) {
|
||||
if send_packet {
|
||||
match self.connection_state.load() {
|
||||
ConnectionState::Login => {
|
||||
// TextComponent implements Serialize and writes in bytes instead of String, that's the reason we only use content
|
||||
self.send_packet_now(&CLoginDisconnect::new(
|
||||
serde_json::to_string(&reason.0).unwrap_or_else(|_| String::new()),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
ConnectionState::Config => {
|
||||
self.send_packet_now(&CConfigDisconnect::new(&reason.clone().get_text()))
|
||||
.await;
|
||||
}
|
||||
ConnectionState::Play => self.send_packet_now(&CPlayDisconnect::new(reason)).await,
|
||||
_ => {}
|
||||
}
|
||||
ConnectionState::Play => self.send_packet_now(&CPlayDisconnect::new(&reason)).await,
|
||||
_ => {}
|
||||
}
|
||||
debug!("Closing connection for {}", self.id);
|
||||
self.close();
|
||||
|
||||
@ -387,7 +387,7 @@ fn is_valid_player_name(name: &str) -> bool {
|
||||
!name.chars().any(|c| c.is_control() || c == ' ')
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum DisconnectReason {
|
||||
Unknown = 0,
|
||||
CantConnectNoInternet = 1,
|
||||
|
||||
@ -65,6 +65,9 @@ pub type ConsumedArgsResource = WasmResource<OwnedConsumedArgs>;
|
||||
pub type CommandNodeResource = WasmResource<NonLeafNodeBuilder>;
|
||||
pub type ItemStackResource = WasmResource<Arc<Mutex<pumpkin_data::item_stack::ItemStack>>>;
|
||||
pub type RecipeManagerResource = WasmResource<Arc<RecipeManager>>;
|
||||
pub type OpManagerResource = WasmResource<Arc<Server>>;
|
||||
pub type BanManagerResource = WasmResource<Arc<Server>>;
|
||||
pub type WhitelistManagerResource = WasmResource<Arc<Server>>;
|
||||
pub type BlockEntityResource = WasmResource<Arc<dyn crate::block::entities::BlockEntity>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -294,6 +297,32 @@ impl PluginHostState {
|
||||
Ok(wasmtime::component::Resource::new_own(resource.rep()))
|
||||
}
|
||||
|
||||
pub fn add_op_manager<T>(
|
||||
&mut self,
|
||||
provider: Arc<Server>,
|
||||
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
|
||||
let resource = self.resource_table.push(OpManagerResource { provider })?;
|
||||
Ok(wasmtime::component::Resource::new_own(resource.rep()))
|
||||
}
|
||||
|
||||
pub fn add_ban_manager<T>(
|
||||
&mut self,
|
||||
provider: Arc<Server>,
|
||||
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
|
||||
let resource = self.resource_table.push(BanManagerResource { provider })?;
|
||||
Ok(wasmtime::component::Resource::new_own(resource.rep()))
|
||||
}
|
||||
|
||||
pub fn add_whitelist_manager<T>(
|
||||
&mut self,
|
||||
provider: Arc<Server>,
|
||||
) -> wasmtime::Result<wasmtime::component::Resource<T>> {
|
||||
let resource = self
|
||||
.resource_table
|
||||
.push(WhitelistManagerResource { provider })?;
|
||||
Ok(wasmtime::component::Resource::new_own(resource.rep()))
|
||||
}
|
||||
|
||||
pub fn add_block_entity<T>(
|
||||
&mut self,
|
||||
provider: Arc<dyn crate::block::entities::BlockEntity>,
|
||||
|
||||
@ -10,14 +10,20 @@ use crate::plugin::loader::wasm::wasm_host::{
|
||||
state::{EntityResource, PluginHostState},
|
||||
wit::v0_1::events::to_wasm_position,
|
||||
wit::v0_1::pumpkin::plugin::{
|
||||
attributes::{
|
||||
Attribute, AttributeModifier as WitAttributeModifier,
|
||||
ModifierOperation as WitModifierOperation,
|
||||
},
|
||||
common::{EntityPose, Position},
|
||||
entity::Host,
|
||||
entity_types,
|
||||
item_stack::ItemStack as WitHostItemStack,
|
||||
text::TextComponent,
|
||||
uuid::Uuid,
|
||||
world::{
|
||||
BlockPos as WitBlockPos, BoundingBox as WitBoundingBox, Entity, HostEntity,
|
||||
RaycastResult as WitRaycastResult, World,
|
||||
BlockPos as WitBlockPos, BoundingBox as WitBoundingBox, Entity,
|
||||
EquipmentSlot as WitEquipmentSlot, HostEntity, RaycastResult as WitRaycastResult,
|
||||
World,
|
||||
},
|
||||
},
|
||||
wit::v0_1::uuid::UuidExt,
|
||||
@ -62,6 +68,100 @@ const fn map_entity_pose(pose: InternalEntityPose) -> EntityPose {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn from_wit_attribute(attr: Attribute) -> &'static pumpkin_data::attributes::Attributes {
|
||||
use pumpkin_data::attributes::Attributes;
|
||||
match attr {
|
||||
Attribute::AirDragModifier => &Attributes::AIR_DRAG_MODIFIER,
|
||||
Attribute::Armor => &Attributes::ARMOR,
|
||||
Attribute::ArmorToughness => &Attributes::ARMOR_TOUGHNESS,
|
||||
Attribute::AttackDamage => &Attributes::ATTACK_DAMAGE,
|
||||
Attribute::AttackKnockback => &Attributes::ATTACK_KNOCKBACK,
|
||||
Attribute::AttackSpeed => &Attributes::ATTACK_SPEED,
|
||||
Attribute::BelowNameDistance => &Attributes::BELOW_NAME_DISTANCE,
|
||||
Attribute::BlockBreakSpeed => &Attributes::BLOCK_BREAK_SPEED,
|
||||
Attribute::BlockInteractionRange => &Attributes::BLOCK_INTERACTION_RANGE,
|
||||
Attribute::Bounciness => &Attributes::BOUNCINESS,
|
||||
Attribute::BurningTime => &Attributes::BURNING_TIME,
|
||||
Attribute::CameraDistance => &Attributes::CAMERA_DISTANCE,
|
||||
Attribute::ExplosionKnockbackResistance => &Attributes::EXPLOSION_KNOCKBACK_RESISTANCE,
|
||||
Attribute::EntityInteractionRange => &Attributes::ENTITY_INTERACTION_RANGE,
|
||||
Attribute::FallDamageMultiplier => &Attributes::FALL_DAMAGE_MULTIPLIER,
|
||||
Attribute::FlyingSpeed => &Attributes::FLYING_SPEED,
|
||||
Attribute::FollowRange => &Attributes::FOLLOW_RANGE,
|
||||
Attribute::FrictionModifier => &Attributes::FRICTION_MODIFIER,
|
||||
Attribute::Gravity => &Attributes::GRAVITY,
|
||||
Attribute::JumpStrength => &Attributes::JUMP_STRENGTH,
|
||||
Attribute::KnockbackResistance => &Attributes::KNOCKBACK_RESISTANCE,
|
||||
Attribute::Luck => &Attributes::LUCK,
|
||||
Attribute::MaxAbsorption => &Attributes::MAX_ABSORPTION,
|
||||
Attribute::MaxHealth => &Attributes::MAX_HEALTH,
|
||||
Attribute::MiningEfficiency => &Attributes::MINING_EFFICIENCY,
|
||||
Attribute::MovementEfficiency => &Attributes::MOVEMENT_EFFICIENCY,
|
||||
Attribute::MovementSpeed => &Attributes::MOVEMENT_SPEED,
|
||||
Attribute::NameTagDistance => &Attributes::NAME_TAG_DISTANCE,
|
||||
Attribute::OxygenBonus => &Attributes::OXYGEN_BONUS,
|
||||
Attribute::SafeFallDistance => &Attributes::SAFE_FALL_DISTANCE,
|
||||
Attribute::Scale => &Attributes::SCALE,
|
||||
Attribute::SneakingSpeed => &Attributes::SNEAKING_SPEED,
|
||||
Attribute::SpawnReinforcements => &Attributes::SPAWN_REINFORCEMENTS,
|
||||
Attribute::StepHeight => &Attributes::STEP_HEIGHT,
|
||||
Attribute::SubmergedMiningSpeed => &Attributes::SUBMERGED_MINING_SPEED,
|
||||
Attribute::SweepingDamageRatio => &Attributes::SWEEPING_DAMAGE_RATIO,
|
||||
Attribute::TemptRange => &Attributes::TEMPT_RANGE,
|
||||
Attribute::WaterMovementEfficiency => &Attributes::WATER_MOVEMENT_EFFICIENCY,
|
||||
Attribute::WaypointTransmitRange => &Attributes::WAYPOINT_TRANSMIT_RANGE,
|
||||
Attribute::WaypointReceiveRange => &Attributes::WAYPOINT_RECEIVE_RANGE,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn from_wit_modifier_op(
|
||||
op: WitModifierOperation,
|
||||
) -> crate::entity::attributes::ModifierOperation {
|
||||
match op {
|
||||
WitModifierOperation::Add => crate::entity::attributes::ModifierOperation::Add,
|
||||
WitModifierOperation::MultiplyBase => {
|
||||
crate::entity::attributes::ModifierOperation::MultiplyBase
|
||||
}
|
||||
WitModifierOperation::MultiplyTotal => {
|
||||
crate::entity::attributes::ModifierOperation::MultiplyTotal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn to_wit_modifier_op(
|
||||
op: crate::entity::attributes::ModifierOperation,
|
||||
) -> WitModifierOperation {
|
||||
match op {
|
||||
crate::entity::attributes::ModifierOperation::Add => WitModifierOperation::Add,
|
||||
crate::entity::attributes::ModifierOperation::MultiplyBase => {
|
||||
WitModifierOperation::MultiplyBase
|
||||
}
|
||||
crate::entity::attributes::ModifierOperation::MultiplyTotal => {
|
||||
WitModifierOperation::MultiplyTotal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn from_wit_equipment_slot(
|
||||
slot: WitEquipmentSlot,
|
||||
) -> pumpkin_data::data_component_impl::EquipmentSlot {
|
||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
match slot {
|
||||
WitEquipmentSlot::MainHand => EquipmentSlot::MAIN_HAND,
|
||||
WitEquipmentSlot::OffHand => EquipmentSlot::OFF_HAND,
|
||||
WitEquipmentSlot::Feet => EquipmentSlot::FEET,
|
||||
WitEquipmentSlot::Legs => EquipmentSlot::LEGS,
|
||||
WitEquipmentSlot::Chest => EquipmentSlot::CHEST,
|
||||
WitEquipmentSlot::Head => EquipmentSlot::HEAD,
|
||||
WitEquipmentSlot::Body => EquipmentSlot::BODY,
|
||||
WitEquipmentSlot::Saddle => EquipmentSlot::SADDLE,
|
||||
}
|
||||
}
|
||||
|
||||
impl HostEntity for PluginHostState {
|
||||
async fn get_id(&mut self, entity: Resource<Entity>) -> wasmtime::Result<u32> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
@ -468,6 +568,212 @@ impl HostEntity for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_attribute_value(
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
attr: Attribute,
|
||||
) -> wasmtime::Result<f64> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
let attribute = from_wit_attribute(attr);
|
||||
Ok(entity
|
||||
.get_living_entity()
|
||||
.map_or(attribute.default_value, |living| {
|
||||
living.get_attribute_value(attribute)
|
||||
}))
|
||||
}
|
||||
|
||||
async fn get_attribute_base(
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
attr: Attribute,
|
||||
) -> wasmtime::Result<f64> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
let attribute = from_wit_attribute(attr);
|
||||
Ok(entity
|
||||
.get_living_entity()
|
||||
.map_or(attribute.default_value, |living| {
|
||||
living.get_attribute_base(attribute)
|
||||
}))
|
||||
}
|
||||
|
||||
async fn set_attribute_base(
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
attr: Attribute,
|
||||
value: f64,
|
||||
) -> wasmtime::Result<()> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
let attribute = from_wit_attribute(attr);
|
||||
if let Some(living) = entity.get_living_entity() {
|
||||
living.set_attribute_base(attribute, value);
|
||||
crate::entity::attributes::send_attribute_updates_for_living(
|
||||
living,
|
||||
vec![attribute.clone()],
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_attribute_modifier(
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
attr: Attribute,
|
||||
modifier: WitAttributeModifier,
|
||||
) -> wasmtime::Result<()> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
let attribute = from_wit_attribute(attr);
|
||||
if let Some(living) = entity.get_living_entity() {
|
||||
let internal_mod = crate::entity::attributes::Modifier {
|
||||
id: modifier.id,
|
||||
amount: modifier.amount,
|
||||
operation: from_wit_modifier_op(modifier.operation),
|
||||
};
|
||||
living.update_attribute(attribute, |inst| inst.add_or_replace_modifier(internal_mod));
|
||||
crate::entity::attributes::send_attribute_updates_for_living(
|
||||
living,
|
||||
vec![attribute.clone()],
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_attribute_modifier(
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
attr: Attribute,
|
||||
id: String,
|
||||
) -> wasmtime::Result<()> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
let attribute = from_wit_attribute(attr);
|
||||
if let Some(living) = entity.get_living_entity() {
|
||||
living.update_attribute(attribute, |inst| inst.remove_modifier(&id));
|
||||
crate::entity::attributes::send_attribute_updates_for_living(
|
||||
living,
|
||||
vec![attribute.clone()],
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_attribute_modifiers(
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
attr: Attribute,
|
||||
) -> wasmtime::Result<Vec<WitAttributeModifier>> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
let attribute = from_wit_attribute(attr);
|
||||
if let Some(living) = entity.get_living_entity() {
|
||||
let map = living.attributes.read().unwrap();
|
||||
if let Some(inst) = map.get(&attribute.id) {
|
||||
return Ok(inst
|
||||
.modifiers
|
||||
.iter()
|
||||
.map(|m| WitAttributeModifier {
|
||||
id: m.id.clone(),
|
||||
amount: m.amount,
|
||||
operation: to_wit_modifier_op(m.operation),
|
||||
})
|
||||
.collect());
|
||||
}
|
||||
}
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
async fn reset_attribute(
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
attr: Attribute,
|
||||
) -> wasmtime::Result<()> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
let attribute = from_wit_attribute(attr);
|
||||
if let Some(living) = entity.get_living_entity() {
|
||||
{
|
||||
let mut map = living.attributes.write().unwrap();
|
||||
map.remove(&attribute.id);
|
||||
};
|
||||
crate::entity::attributes::send_attribute_updates_for_living(
|
||||
living,
|
||||
vec![attribute.clone()],
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn reset_all_attributes(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
if let Some(living) = entity.get_living_entity() {
|
||||
living.reset_effects_and_attributes().await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_equipment(
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
slot: WitEquipmentSlot,
|
||||
) -> wasmtime::Result<Option<Resource<WitHostItemStack>>> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
if let Some(living) = entity.get_living_entity() {
|
||||
let slot = from_wit_equipment_slot(slot);
|
||||
let equipment = living.entity_equipment.lock().await;
|
||||
let stack = equipment.get(&slot);
|
||||
if !stack.is_empty() {
|
||||
return Ok(Some(
|
||||
self.add_item_stack(Arc::new(tokio::sync::Mutex::new(stack)))?,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn set_equipment(
|
||||
&mut self,
|
||||
entity: Resource<Entity>,
|
||||
slot: WitEquipmentSlot,
|
||||
stack: Option<Resource<WitHostItemStack>>,
|
||||
) -> wasmtime::Result<()> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
if let Some(living) = entity.get_living_entity() {
|
||||
let slot = from_wit_equipment_slot(slot);
|
||||
let item_stack = if let Some(stack_res) = stack {
|
||||
self.get_item_stack(&stack_res)?.lock().await.clone()
|
||||
} else {
|
||||
pumpkin_data::item_stack::ItemStack::EMPTY.clone()
|
||||
};
|
||||
|
||||
{
|
||||
let mut equipment = living.entity_equipment.lock().await;
|
||||
equipment.put(&slot, item_stack.clone());
|
||||
};
|
||||
|
||||
living.send_equipment_changes(&[(slot, item_stack)]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn clear_equipment(&mut self, entity: Resource<Entity>) -> wasmtime::Result<()> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
if let Some(living) = entity.get_living_entity() {
|
||||
let mut equipment = living.entity_equipment.lock().await;
|
||||
let slots_to_clear: Vec<(
|
||||
pumpkin_data::data_component_impl::EquipmentSlot,
|
||||
pumpkin_data::item_stack::ItemStack,
|
||||
)> = equipment
|
||||
.equipment
|
||||
.drain()
|
||||
.map(|(slot, _)| (slot, pumpkin_data::item_stack::ItemStack::EMPTY.clone()))
|
||||
.collect();
|
||||
drop(equipment);
|
||||
|
||||
living.send_equipment_changes(&slots_to_clear);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_age(&mut self, entity: Resource<Entity>) -> wasmtime::Result<i32> {
|
||||
let entity = entity_from_resource(self, &entity)?;
|
||||
Ok(entity
|
||||
|
||||
@ -46,6 +46,7 @@ impl pumpkin::plugin::bedrock_packets::Host for PluginHostState {}
|
||||
impl pumpkin::plugin::data_components::Host for PluginHostState {}
|
||||
impl pumpkin::plugin::enchantments::Host for PluginHostState {}
|
||||
impl pumpkin::plugin::biomes::Host for PluginHostState {}
|
||||
impl pumpkin::plugin::attributes::Host for PluginHostState {}
|
||||
|
||||
pub fn add_to_linker(linker: &mut Linker<PluginHostState>) -> wasmtime::Result<()> {
|
||||
Plugin::add_to_linker::<_, HasSelf<_>>(linker, |state: &mut PluginHostState| state)?;
|
||||
|
||||
@ -480,7 +480,7 @@ fn world_from_resource(
|
||||
.clone()
|
||||
}
|
||||
|
||||
const fn to_wit_permission_level(
|
||||
pub(crate) const fn to_wit_permission_level(
|
||||
level: PermissionLvl,
|
||||
) -> pumpkin::plugin::permission::PermissionLevel {
|
||||
match level {
|
||||
@ -492,7 +492,7 @@ const fn to_wit_permission_level(
|
||||
}
|
||||
}
|
||||
|
||||
const fn from_wit_permission_level(
|
||||
pub(crate) const fn from_wit_permission_level(
|
||||
level: pumpkin::plugin::permission::PermissionLevel,
|
||||
) -> PermissionLvl {
|
||||
match level {
|
||||
@ -504,6 +504,469 @@ const fn from_wit_permission_level(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_ban_expiry(
|
||||
expires_at_utc: Option<String>,
|
||||
duration_seconds: Option<u64>,
|
||||
) -> Option<time::OffsetDateTime> {
|
||||
if let Some(dur) = duration_seconds {
|
||||
let seconds = i64::try_from(dur).unwrap_or(i64::MAX);
|
||||
return Some(time::OffsetDateTime::now_utc() + time::Duration::seconds(seconds));
|
||||
}
|
||||
if let Some(s) = expires_at_utc {
|
||||
if s.eq_ignore_ascii_case("forever") || s.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if let Ok(parsed) =
|
||||
time::OffsetDateTime::parse(&s, &time::format_description::well_known::Rfc3339)
|
||||
{
|
||||
return Some(parsed);
|
||||
}
|
||||
if let Ok(parsed) = time::OffsetDateTime::parse(
|
||||
&s,
|
||||
time::macros::format_description!(
|
||||
"[year]-[month]-[day] [hour]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]"
|
||||
),
|
||||
) {
|
||||
return Some(parsed);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
const fn from_wasm_bedrock_disconnect_reason(
|
||||
reason: pumpkin::plugin::player::BedrockDisconnectReason,
|
||||
) -> DisconnectReason {
|
||||
match reason {
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::Unknown => DisconnectReason::Unknown,
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::CantConnectNoInternet => {
|
||||
DisconnectReason::CantConnectNoInternet
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NoPermissions => {
|
||||
DisconnectReason::NoPermissions
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::UnrecoverableError => {
|
||||
DisconnectReason::UnrecoverableError
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ThirdPartyBlocked => {
|
||||
DisconnectReason::ThirdPartyBlocked
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ThirdPartyNoInternet => {
|
||||
DisconnectReason::ThirdPartyNoInternet
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ThirdPartyBadIp => {
|
||||
DisconnectReason::ThirdPartyBadIP
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ThirdPartyNoServerOrServerLocked => {
|
||||
DisconnectReason::ThirdPartyNoServerOrServerLocked
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::VersionMismatch => {
|
||||
DisconnectReason::VersionMismatch
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::SkinIssue => DisconnectReason::SkinIssue,
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::InviteSessionNotFound => {
|
||||
DisconnectReason::InviteSessionNotFound
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EduLevelSettingsMissing => {
|
||||
DisconnectReason::EduLevelSettingsMissing
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::LocalServerNotFound => {
|
||||
DisconnectReason::LocalServerNotFound
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::LegacyDisconnect => {
|
||||
DisconnectReason::LegacyDisconnect
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::UserLeaveGameAttempted => {
|
||||
DisconnectReason::UserLeaveGameAttempted
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::PlatformLockedSkinsError => {
|
||||
DisconnectReason::PlatformLockedSkinsError
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::RealmsWorldUnassigned => {
|
||||
DisconnectReason::RealmsWorldUnassigned
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::RealmsServerCantConnect => {
|
||||
DisconnectReason::RealmsServerCantConnect
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::RealmsServerHidden => {
|
||||
DisconnectReason::RealmsServerHidden
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::RealmsServerDisabledBeta => {
|
||||
DisconnectReason::RealmsServerDisabledBeta
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::RealmsServerDisabled => {
|
||||
DisconnectReason::RealmsServerDisabled
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::CrossPlatformDisabled => {
|
||||
DisconnectReason::CrossPlatformDisabled
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::CantConnect => {
|
||||
DisconnectReason::CantConnect
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::SessionNotFound => {
|
||||
DisconnectReason::SessionNotFound
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ClientSettingsIncompatibleWithServer => {
|
||||
DisconnectReason::ClientSettingsIncompatibleWithServer
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ServerFull => {
|
||||
DisconnectReason::ServerFull
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::InvalidPlatformSkin => {
|
||||
DisconnectReason::InvalidPlatformSkin
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EditionVersionMismatch => {
|
||||
DisconnectReason::EditionVersionMismatch
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EditionMismatch => {
|
||||
DisconnectReason::EditionMismatch
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::LevelNewerThanExeVersion => {
|
||||
DisconnectReason::LevelNewerThanExeVersion
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NoFailOccurred => {
|
||||
DisconnectReason::NoFailOccurred
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::BannedSkin => {
|
||||
DisconnectReason::BannedSkin
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::Timeout => DisconnectReason::Timeout,
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ServerNotFound => {
|
||||
DisconnectReason::ServerNotFound
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::OutdatedServer => {
|
||||
DisconnectReason::OutdatedServer
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::OutdatedClient => {
|
||||
DisconnectReason::OutdatedClient
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NoPremiumPlatform => {
|
||||
DisconnectReason::NoPremiumPlatform
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::MultiplayerDisabled => {
|
||||
DisconnectReason::MultiplayerDisabled
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NoWifi => DisconnectReason::NoWiFi,
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::WorldCorruption => {
|
||||
DisconnectReason::WorldCorruption
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NoReason => DisconnectReason::NoReason,
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::Disconnected => {
|
||||
DisconnectReason::Disconnected
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::InvalidPlayer => {
|
||||
DisconnectReason::InvalidPlayer
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::LoggedInOtherLocation => {
|
||||
DisconnectReason::LoggedInOtherLocation
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ServerIdConflict => {
|
||||
DisconnectReason::ServerIdConflict
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NotAllowed => {
|
||||
DisconnectReason::NotAllowed
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NotAuthenticated => {
|
||||
DisconnectReason::NotAuthenticated
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::InvalidTenant => {
|
||||
DisconnectReason::InvalidTenant
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::UnknownPacket => {
|
||||
DisconnectReason::UnknownPacket
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::UnexpectedPacket => {
|
||||
DisconnectReason::UnexpectedPacket
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::InvalidCommandRequestPacket => {
|
||||
DisconnectReason::InvalidCommandRequestPacket
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::HostSuspended => {
|
||||
DisconnectReason::HostSuspended
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::LoginPacketNoRequest => {
|
||||
DisconnectReason::LoginPacketNoRequest
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::LoginPacketNoCert => {
|
||||
DisconnectReason::LoginPacketNoCert
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::MissingClient => {
|
||||
DisconnectReason::MissingClient
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::Kicked => DisconnectReason::Kicked,
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::KickedForExploit => {
|
||||
DisconnectReason::KickedForExploit
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::KickedForIdle => {
|
||||
DisconnectReason::KickedForIdle
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ResourcePackProblem => {
|
||||
DisconnectReason::ResourcePackProblem
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::IncompatiblePack => {
|
||||
DisconnectReason::IncompatiblePack
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::OutOfStorage => {
|
||||
DisconnectReason::OutOfStorage
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::InvalidLevel => {
|
||||
DisconnectReason::InvalidLevel
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::DisconnectPacket => {
|
||||
DisconnectReason::DisconnectPacket
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::BlockMismatch => {
|
||||
DisconnectReason::BlockMismatch
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::InvalidHeights => {
|
||||
DisconnectReason::InvalidHeights
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::InvalidWidths => {
|
||||
DisconnectReason::InvalidWidths
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ConnectionLost => {
|
||||
DisconnectReason::ConnectionLost
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ZombieConnection => {
|
||||
DisconnectReason::ZombieConnection
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::Shutdown => DisconnectReason::Shutdown,
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ReasonNotSet => {
|
||||
DisconnectReason::ReasonNotSet
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::LoadingStateTimeout => {
|
||||
DisconnectReason::LoadingStateTimeout
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ResourcePackLoadingFailed => {
|
||||
DisconnectReason::ResourcePackLoadingFailed
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::SearchingForSessionLoadingScreenFailed => {
|
||||
DisconnectReason::SearchingForSessionLoadingScreenFailed
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetProtocolVersion => {
|
||||
DisconnectReason::NetherNetProtocolVersion
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::SubsystemStatusError => {
|
||||
DisconnectReason::SubsystemStatusError
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EmptyAuthFromDiscovery => {
|
||||
DisconnectReason::EmptyAuthFromDiscovery
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EmptyUrlFromDiscovery => {
|
||||
DisconnectReason::EmptyUrlFromDiscovery
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ExpiredAuthFromDiscovery => {
|
||||
DisconnectReason::ExpiredAuthFromDiscovery
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::UnknownSignalServiceSignInFailure => {
|
||||
DisconnectReason::UnknownSignalServiceSignInFailure
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::XblJoinLobbyFailure => {
|
||||
DisconnectReason::XBLJoinLobbyFailure
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::UnspecifiedClientInstanceDisconnection => {
|
||||
DisconnectReason::UnspecifiedClientInstanceDisconnection
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetSessionNotFound => {
|
||||
DisconnectReason::NetherNetSessionNotFound
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetCreatePeerConnection => {
|
||||
DisconnectReason::NetherNetCreatePeerConnection
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetIce => {
|
||||
DisconnectReason::NetherNetICE
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetConnectRequest => {
|
||||
DisconnectReason::NetherNetConnectRequest
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetConnectResponse => {
|
||||
DisconnectReason::NetherNetConnectResponse
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetNegotiationTimeout => {
|
||||
DisconnectReason::NetherNetNegotiationTimeout
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetInactivityTimeout => {
|
||||
DisconnectReason::NetherNetInactivityTimeout
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::StaleConnectionBeingReplaced => {
|
||||
DisconnectReason::StaleConnectionBeingReplaced
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::RealmsSessionNotFound => {
|
||||
DisconnectReason::RealmsSessionNotFound
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::BadPacket => DisconnectReason::BadPacket,
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetFailedToCreateOffer => {
|
||||
DisconnectReason::NetherNetFailedToCreateOffer
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetFailedToCreateAnswer => {
|
||||
DisconnectReason::NetherNetFailedToCreateAnswer
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetFailedToSetLocalDescription => {
|
||||
DisconnectReason::NetherNetFailedToSetLocalDescription
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetFailedToSetRemoteDescription => {
|
||||
DisconnectReason::NetherNetFailedToSetRemoteDescription
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetNegotiationTimeoutWaitingForResponse => {
|
||||
DisconnectReason::NetherNetNegotiationTimeoutWaitingForResponse
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetNegotiationTimeoutWaitingForAccept => {
|
||||
DisconnectReason::NetherNetNegotiationTimeoutWaitingForAccept
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetIncomingConnectionIgnored => {
|
||||
DisconnectReason::NetherNetIncomingConnectionIgnored
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetSignalingParsingFailure => {
|
||||
DisconnectReason::NetherNetSignalingParsingFailure
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetSignalingUnknownError => {
|
||||
DisconnectReason::NetherNetSignalingUnknownError
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetSignalingUnicastDeliveryFailed => {
|
||||
DisconnectReason::NetherNetSignalingUnicastDeliveryFailed
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetSignalingBroadcastDeliveryFailed => {
|
||||
DisconnectReason::NetherNetSignalingBroadcastDeliveryFailed
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetSignalingGenericDeliveryFailed => {
|
||||
DisconnectReason::NetherNetSignalingGenericDeliveryFailed
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EditorMismatchEditorWorld => {
|
||||
DisconnectReason::EditorMismatchEditorWorld
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EditorMismatchVanillaWorld => {
|
||||
DisconnectReason::EditorMismatchVanillaWorld
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::WorldTransferNotPrimaryClient => {
|
||||
DisconnectReason::WorldTransferNotPrimaryClient
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::RequestServerShutdown => {
|
||||
DisconnectReason::RequestServerShutdown
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ClientGameSetupCancelled => {
|
||||
DisconnectReason::ClientGameSetupCancelled
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ClientGameSetupFailed => {
|
||||
DisconnectReason::ClientGameSetupFailed
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NoVenue => DisconnectReason::NoVenue,
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetSignalingSigninFailed => {
|
||||
DisconnectReason::NetherNetSignalingSigninFailed
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::SessionAccessDenied => {
|
||||
DisconnectReason::SessionAccessDenied
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ServiceSigninIssue => {
|
||||
DisconnectReason::ServiceSigninIssue
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetNoSignalingChannel => {
|
||||
DisconnectReason::NetherNetNoSignalingChannel
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetNotLoggedIn => {
|
||||
DisconnectReason::NetherNetNotLoggedIn
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetClientSignalingError => {
|
||||
DisconnectReason::NetherNetClientSignalingError
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::SubClientLoginDisabled => {
|
||||
DisconnectReason::SubClientLoginDisabled
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::DeepLinkTryingToOpenDemoWorldWhileSignedIn => {
|
||||
DisconnectReason::DeepLinkTryingToOpenDemoWorldWhileSignedIn
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::AsyncJoinTaskDenied => {
|
||||
DisconnectReason::AsyncJoinTaskDenied
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::RealmsTimelineRequired => {
|
||||
DisconnectReason::RealmsTimelineRequired
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::GuestWithoutHost => {
|
||||
DisconnectReason::GuestWithoutHost
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::FailedToJoinExperience => {
|
||||
DisconnectReason::FailedToJoinExperience
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetDataChannelClosed => {
|
||||
DisconnectReason::NetherNetDataChannelClosed
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::DiscoveryEnvironmentMismatch => {
|
||||
DisconnectReason::DiscoveryEnvironmentMismatch
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::HostWithoutKeys => {
|
||||
DisconnectReason::HostWithoutKeys
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::HostSignedOut => {
|
||||
DisconnectReason::HostSignedOut
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ScriptWatchdogException => {
|
||||
DisconnectReason::ScriptWatchdogException
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ScriptMemoryLimitExceeded => {
|
||||
DisconnectReason::ScriptMemoryLimitExceeded
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::StorageLowDuringGameplay => {
|
||||
DisconnectReason::StorageLowDuringGameplay
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::StorageFullDuringGameplay => {
|
||||
DisconnectReason::StorageFullDuringGameplay
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::LevelStorageCorruption => {
|
||||
DisconnectReason::LevelStorageCorruption
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EditionMismatchVanillaToEdu => {
|
||||
DisconnectReason::EditionMismatchVanillaToEdu
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EditionMismatchEduToVanilla => {
|
||||
DisconnectReason::EditionMismatchEduToVanilla
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EditorMismatchEditorToVanilla => {
|
||||
DisconnectReason::EditorMismatchEditorToVanilla
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EditorMismatchVanillaToEditor => {
|
||||
DisconnectReason::EditorMismatchVanillaToEditor
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::DenyListed => {
|
||||
DisconnectReason::DenyListed
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NonceMissing => {
|
||||
DisconnectReason::NonceMissing
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NonceNotFound => {
|
||||
DisconnectReason::NonceNotFound
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NonceExpired => {
|
||||
DisconnectReason::NonceExpired
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NonceNotValid => {
|
||||
DisconnectReason::NonceNotValid
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::HostDisconnected => {
|
||||
DisconnectReason::HostDisconnected
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EditorJoinIntentPolicyFailure => {
|
||||
DisconnectReason::EditorJoinIntentPolicyFailure
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NetherNetIdentityNotAllowed => {
|
||||
DisconnectReason::NetherNetIdentityNotAllowed
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::InvalidName => {
|
||||
DisconnectReason::InvalidName
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::ExpiredToken => {
|
||||
DisconnectReason::ExpiredToken
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::HostAcceptsNoTypeOfAuth => {
|
||||
DisconnectReason::HostAcceptsNoTypeOfAuth
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::NotAuthenticatedFastFail => {
|
||||
DisconnectReason::NotAuthenticatedFastFail
|
||||
}
|
||||
pumpkin::plugin::player::BedrockDisconnectReason::EditorNotAllowed => {
|
||||
DisconnectReason::EditorNotAllowed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DowncastResourceExt<PlayerResource> for Resource<Player> {
|
||||
fn downcast_ref<'a>(&'a self, state: &'a mut PluginHostState) -> &'a PlayerResource {
|
||||
state
|
||||
@ -1423,17 +1886,6 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn kick(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
message: wasmtime::component::Resource<pumpkin::plugin::text::TextComponent>,
|
||||
) -> wasmtime::Result<()> {
|
||||
let component = text_component_from_resource(self, &message);
|
||||
let player = player_from_resource(self, &player)?;
|
||||
player.kick(DisconnectReason::Kicked, component).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn respawn(&mut self, player: Resource<Player>) -> wasmtime::Result<()> {
|
||||
let player = player_from_resource(self, &player)?;
|
||||
player.respawn().await;
|
||||
@ -1471,24 +1923,50 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
||||
async fn ban(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
reason: Option<Resource<pumpkin::plugin::text::TextComponent>>,
|
||||
options: pumpkin::plugin::player::BanPlayerOptions,
|
||||
) -> wasmtime::Result<()> {
|
||||
let reason = options
|
||||
.reason
|
||||
.as_ref()
|
||||
.map(|r| text_component_from_resource(self, r));
|
||||
let expires = parse_ban_expiry(options.expires_at_utc, options.duration_seconds);
|
||||
let player = player_from_resource(self, &player)?;
|
||||
let server = self.server.as_ref().expect("server not available");
|
||||
let reason = reason.map(|r| text_component_from_resource(self, &r));
|
||||
player.ban(server, reason).await;
|
||||
player
|
||||
.ban_explicit(
|
||||
server,
|
||||
reason,
|
||||
options.source,
|
||||
expires,
|
||||
options.kick_if_online,
|
||||
options.log_to_console,
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn ban_ip(
|
||||
&mut self,
|
||||
player: Resource<Player>,
|
||||
reason: Option<Resource<pumpkin::plugin::text::TextComponent>>,
|
||||
options: pumpkin::plugin::player::BanIpOptions,
|
||||
) -> wasmtime::Result<()> {
|
||||
let reason = options
|
||||
.reason
|
||||
.as_ref()
|
||||
.map(|r| text_component_from_resource(self, r));
|
||||
let expires = parse_ban_expiry(options.expires_at_utc, options.duration_seconds);
|
||||
let player = player_from_resource(self, &player)?;
|
||||
let server = self.server.as_ref().expect("server not available");
|
||||
let reason = reason.map(|r| text_component_from_resource(self, &r));
|
||||
player.ban_ip(server, reason).await;
|
||||
player
|
||||
.ban_ip_explicit(
|
||||
server,
|
||||
reason,
|
||||
options.source,
|
||||
expires,
|
||||
options.kick_matching_players,
|
||||
options.log_to_console,
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -2357,6 +2835,52 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn kick(
|
||||
&mut self,
|
||||
player: Resource<pumpkin::plugin::player::JavaPlayer>,
|
||||
options: pumpkin::plugin::player::JavaKickOptions,
|
||||
) -> wasmtime::Result<()> {
|
||||
let player = self
|
||||
.resource_table
|
||||
.get::<crate::plugin::loader::wasm::wasm_host::state::JavaPlayerResource>(
|
||||
&Resource::new_own(player.rep()),
|
||||
)
|
||||
.map_err(|_| wasmtime::Error::msg("invalid java-player resource handle"))?
|
||||
.provider
|
||||
.clone();
|
||||
|
||||
let reason = text_component_from_resource(self, &options.reason);
|
||||
if options.log_to_console {
|
||||
tracing::info!(
|
||||
"Kicking Java player {} ({}): {}",
|
||||
player.gameprofile.name,
|
||||
player.gameprofile.id,
|
||||
reason.clone().to_pretty_console()
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(server) = player.world().server.upgrade()
|
||||
&& let Some(player_arc) = player.world().get_player_by_uuid(player.gameprofile.id)
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_kick::PlayerKickEvent::new(
|
||||
player_arc,
|
||||
reason.clone().to_pretty_console(),
|
||||
);
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
if event.cancelled {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let send_packet = options.teardown_policy
|
||||
!= pumpkin::plugin::player::SocketTeardownPolicy::DropConnection;
|
||||
if let crate::net::ClientPlatform::Java(java) = player.client.as_ref() {
|
||||
java.kick_explicit(&reason, send_packet).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn drop(
|
||||
&mut self,
|
||||
rep: Resource<pumpkin::plugin::player::JavaPlayer>,
|
||||
@ -2787,6 +3311,61 @@ impl pumpkin::plugin::player::HostBedrockPlayer for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn kick(
|
||||
&mut self,
|
||||
player: Resource<pumpkin::plugin::player::BedrockPlayer>,
|
||||
options: pumpkin::plugin::player::BedrockKickOptions,
|
||||
) -> wasmtime::Result<()> {
|
||||
let player = self
|
||||
.resource_table
|
||||
.get::<crate::plugin::loader::wasm::wasm_host::state::BedrockPlayerResource>(
|
||||
&Resource::new_own(player.rep()),
|
||||
)
|
||||
.map_err(|_| wasmtime::Error::msg("invalid bedrock-player resource handle"))?
|
||||
.provider
|
||||
.clone();
|
||||
|
||||
let disconnect_reason = from_wasm_bedrock_disconnect_reason(options.reason);
|
||||
if options.log_to_console {
|
||||
tracing::info!(
|
||||
"Kicking Bedrock player {} ({}) [Reason: {:?}]: {}",
|
||||
player.gameprofile.name,
|
||||
player.gameprofile.id,
|
||||
disconnect_reason,
|
||||
options.message
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(server) = player.world().server.upgrade()
|
||||
&& let Some(player_arc) = player.world().get_player_by_uuid(player.gameprofile.id)
|
||||
{
|
||||
let mut event = crate::plugin::api::events::player::player_kick::PlayerKickEvent::new(
|
||||
player_arc,
|
||||
options.message.clone(),
|
||||
);
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
if event.cancelled {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let send_packet = options.teardown_policy
|
||||
!= pumpkin::plugin::player::SocketTeardownPolicy::DropConnection;
|
||||
if let crate::net::ClientPlatform::Bedrock(bedrock) = player.client.as_ref() {
|
||||
bedrock
|
||||
.kick_explicit(
|
||||
disconnect_reason,
|
||||
options.message,
|
||||
options.skip_message,
|
||||
options.filtered_message,
|
||||
send_packet,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn drop(
|
||||
&mut self,
|
||||
rep: Resource<pumpkin::plugin::player::BedrockPlayer>,
|
||||
|
||||
@ -5,15 +5,23 @@ use crate::command::CommandSender;
|
||||
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::recipe::RecipeManager as WitRecipeManager;
|
||||
use pumpkin::plugin::server::CommandSender as WasmCommandSender;
|
||||
|
||||
use super::player::text_component_from_resource;
|
||||
use super::player::{
|
||||
from_wit_permission_level, parse_ban_expiry, text_component_from_resource,
|
||||
to_wit_permission_level,
|
||||
};
|
||||
use crate::data::SaveJSONConfiguration;
|
||||
use crate::plugin::{
|
||||
loader::wasm::wasm_host::{
|
||||
state::{PluginHostState, ServerResource},
|
||||
wit::v0_1::pumpkin::{
|
||||
self,
|
||||
plugin::{
|
||||
player::Player,
|
||||
server::{Difficulty, Dimension, Server, SysInfo},
|
||||
player::{BanIpOptions, BanPlayerOptions, Player},
|
||||
server::{
|
||||
BanManager as WitBanManager, BannedIpEntry, BannedPlayerEntry, Difficulty,
|
||||
Dimension, OpEntry, OpManager as WitOpManager, Server, SysInfo,
|
||||
WhitelistEntry as WitWhitelistEntry, WhitelistManager as WitWhitelistManager,
|
||||
},
|
||||
uuid::Uuid as WitUuid,
|
||||
},
|
||||
},
|
||||
@ -397,6 +405,39 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
||||
self.add_recipe_manager(server.recipe_manager.clone())
|
||||
}
|
||||
|
||||
async fn get_op_manager(
|
||||
&mut self,
|
||||
_rep: Resource<Server>,
|
||||
) -> wasmtime::Result<Resource<WitOpManager>> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
self.add_op_manager(server.clone())
|
||||
}
|
||||
|
||||
async fn get_ban_manager(
|
||||
&mut self,
|
||||
_rep: Resource<Server>,
|
||||
) -> wasmtime::Result<Resource<WitBanManager>> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
self.add_ban_manager(server.clone())
|
||||
}
|
||||
|
||||
async fn get_whitelist_manager(
|
||||
&mut self,
|
||||
_rep: Resource<Server>,
|
||||
) -> wasmtime::Result<Resource<WitWhitelistManager>> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
self.add_whitelist_manager(server.clone())
|
||||
}
|
||||
|
||||
async fn drop(&mut self, rep: Resource<Server>) -> wasmtime::Result<()> {
|
||||
self.resource_table
|
||||
.delete::<ServerResource>(Resource::new_own(rep.rep()))
|
||||
@ -404,3 +445,634 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl pumpkin::plugin::server::HostOpManager for PluginHostState {
|
||||
async fn is_op(&mut self, _res: Resource<WitOpManager>, id: WitUuid) -> wasmtime::Result<bool> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let uuid = WitUuid::from_wit(&id);
|
||||
let ops = server.data.operator_config.read().await;
|
||||
Ok(ops.get_entry(&uuid).is_some())
|
||||
}
|
||||
|
||||
async fn get_op(
|
||||
&mut self,
|
||||
_res: Resource<WitOpManager>,
|
||||
id: WitUuid,
|
||||
) -> wasmtime::Result<Option<OpEntry>> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let uuid = WitUuid::from_wit(&id);
|
||||
let ops = server.data.operator_config.read().await;
|
||||
Ok(ops.get_entry(&uuid).map(|entry| OpEntry {
|
||||
uuid: WitUuid::to_wit(&entry.uuid),
|
||||
name: entry.name.clone(),
|
||||
level: to_wit_permission_level(entry.level),
|
||||
bypasses_player_limit: entry.bypasses_player_limit,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn get_permission_level(
|
||||
&mut self,
|
||||
_res: Resource<WitOpManager>,
|
||||
id: WitUuid,
|
||||
) -> wasmtime::Result<pumpkin::plugin::permission::PermissionLevel> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let uuid = WitUuid::from_wit(&id);
|
||||
let ops = server.data.operator_config.read().await;
|
||||
Ok(ops.get_entry(&uuid).map_or(
|
||||
pumpkin::plugin::permission::PermissionLevel::Zero,
|
||||
|entry| to_wit_permission_level(entry.level),
|
||||
))
|
||||
}
|
||||
|
||||
async fn op_player(
|
||||
&mut self,
|
||||
_res: Resource<WitOpManager>,
|
||||
name: String,
|
||||
id: WitUuid,
|
||||
level: pumpkin::plugin::permission::PermissionLevel,
|
||||
bypasses_player_limit: bool,
|
||||
) -> wasmtime::Result<()> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let uuid = WitUuid::from_wit(&id);
|
||||
let internal_level = from_wit_permission_level(level);
|
||||
|
||||
let mut config = server.data.operator_config.write().await;
|
||||
if let Some(existing) = config.ops.iter_mut().find(|o| o.uuid == uuid) {
|
||||
existing.level = internal_level;
|
||||
existing.name.clone_from(&name);
|
||||
existing.bypasses_player_limit = bypasses_player_limit;
|
||||
} else {
|
||||
let op_entry = pumpkin_config::op::Op::new(
|
||||
uuid,
|
||||
name.clone(),
|
||||
internal_level,
|
||||
bypasses_player_limit,
|
||||
);
|
||||
config.ops.push(op_entry);
|
||||
}
|
||||
config.save();
|
||||
drop(config);
|
||||
|
||||
if let Some(player) = server.get_player_by_uuid(uuid) {
|
||||
let command_dispatcher = server.command_dispatcher.read().await;
|
||||
player
|
||||
.set_permission_lvl(server, internal_level, &command_dispatcher)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn deop_player(
|
||||
&mut self,
|
||||
_res: Resource<WitOpManager>,
|
||||
id: WitUuid,
|
||||
) -> wasmtime::Result<bool> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let uuid = WitUuid::from_wit(&id);
|
||||
|
||||
let mut config = server.data.operator_config.write().await;
|
||||
if let Some(op_index) = config.ops.iter().position(|o| o.uuid == uuid) {
|
||||
config.ops.remove(op_index);
|
||||
config.save();
|
||||
drop(config);
|
||||
|
||||
if let Some(player) = server.get_player_by_uuid(uuid) {
|
||||
let command_dispatcher = server.command_dispatcher.read().await;
|
||||
player
|
||||
.set_permission_lvl(
|
||||
server,
|
||||
pumpkin_util::PermissionLvl::Zero,
|
||||
&command_dispatcher,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_ops(&mut self, _res: Resource<WitOpManager>) -> wasmtime::Result<Vec<OpEntry>> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let config = server.data.operator_config.read().await;
|
||||
Ok(config
|
||||
.ops
|
||||
.iter()
|
||||
.map(|entry| OpEntry {
|
||||
uuid: WitUuid::to_wit(&entry.uuid),
|
||||
name: entry.name.clone(),
|
||||
level: to_wit_permission_level(entry.level),
|
||||
bypasses_player_limit: entry.bypasses_player_limit,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn drop(&mut self, rep: Resource<WitOpManager>) -> wasmtime::Result<()> {
|
||||
let _ = self
|
||||
.resource_table
|
||||
.delete::<crate::plugin::loader::wasm::wasm_host::state::OpManagerResource>(
|
||||
Resource::new_own(rep.rep()),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl pumpkin::plugin::server::HostBanManager for PluginHostState {
|
||||
async fn is_player_banned(
|
||||
&mut self,
|
||||
_res: Resource<WitBanManager>,
|
||||
id: WitUuid,
|
||||
) -> wasmtime::Result<bool> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let uuid = WitUuid::from_wit(&id);
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
let mut list = server.data.banned_player_list.write().await;
|
||||
list.banned_players
|
||||
.retain(|entry| entry.expires.is_none_or(|expires| expires > now));
|
||||
list.save();
|
||||
Ok(list.banned_players.iter().any(|e| e.uuid == uuid))
|
||||
}
|
||||
|
||||
async fn get_player_ban(
|
||||
&mut self,
|
||||
_res: Resource<WitBanManager>,
|
||||
id: WitUuid,
|
||||
) -> wasmtime::Result<Option<BannedPlayerEntry>> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let uuid = WitUuid::from_wit(&id);
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
let mut list = server.data.banned_player_list.write().await;
|
||||
list.banned_players
|
||||
.retain(|entry| entry.expires.is_none_or(|expires| expires > now));
|
||||
list.save();
|
||||
Ok(list
|
||||
.banned_players
|
||||
.iter()
|
||||
.find(|e| e.uuid == uuid)
|
||||
.map(|e| BannedPlayerEntry {
|
||||
uuid: WitUuid::to_wit(&e.uuid),
|
||||
name: e.name.clone(),
|
||||
created: e
|
||||
.created
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.unwrap_or_default(),
|
||||
source: e.source.clone(),
|
||||
expires: e.expires.and_then(|exp| {
|
||||
exp.format(&time::format_description::well_known::Rfc3339)
|
||||
.ok()
|
||||
}),
|
||||
reason: e.reason.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn ban_player(
|
||||
&mut self,
|
||||
_res: Resource<WitBanManager>,
|
||||
name: String,
|
||||
id: WitUuid,
|
||||
options: BanPlayerOptions,
|
||||
) -> wasmtime::Result<()> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let uuid = WitUuid::from_wit(&id);
|
||||
let reason_text = options
|
||||
.reason
|
||||
.as_ref()
|
||||
.map(|res| text_component_from_resource(self, res))
|
||||
.map_or_else(
|
||||
|| "Banned by plugin.".to_string(),
|
||||
pumpkin_util::text::TextComponent::to_pretty_console,
|
||||
);
|
||||
let source_name = options.source.unwrap_or_else(|| "Plugin".to_string());
|
||||
let expires = parse_ban_expiry(options.expires_at_utc, options.duration_seconds);
|
||||
|
||||
let mut list = server.data.banned_player_list.write().await;
|
||||
if let Some(existing) = list.banned_players.iter_mut().find(|e| e.uuid == uuid) {
|
||||
existing.name.clone_from(&name);
|
||||
existing.source = source_name;
|
||||
existing.expires = expires;
|
||||
existing.reason.clone_from(&reason_text);
|
||||
} else {
|
||||
let entry = crate::data::banlist_serializer::BannedPlayerEntry {
|
||||
uuid,
|
||||
name: name.clone(),
|
||||
created: time::OffsetDateTime::now_utc(),
|
||||
source: source_name,
|
||||
expires,
|
||||
reason: reason_text.clone(),
|
||||
};
|
||||
list.banned_players.push(entry);
|
||||
}
|
||||
list.save();
|
||||
drop(list);
|
||||
|
||||
if options.kick_if_online
|
||||
&& let Some(player) = server.get_player_by_uuid(uuid)
|
||||
{
|
||||
player
|
||||
.kick(
|
||||
crate::net::DisconnectReason::Kicked,
|
||||
pumpkin_util::text::TextComponent::text(reason_text.clone()),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
if options.log_to_console {
|
||||
tracing::info!("Banned player {} ({}): {}", name, uuid, reason_text);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn unban_player(
|
||||
&mut self,
|
||||
_res: Resource<WitBanManager>,
|
||||
id: WitUuid,
|
||||
) -> wasmtime::Result<bool> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let uuid = WitUuid::from_wit(&id);
|
||||
let mut list = server.data.banned_player_list.write().await;
|
||||
Ok(list
|
||||
.banned_players
|
||||
.iter()
|
||||
.position(|e| e.uuid == uuid)
|
||||
.is_some_and(|pos| {
|
||||
list.banned_players.remove(pos);
|
||||
list.save();
|
||||
true
|
||||
}))
|
||||
}
|
||||
|
||||
async fn list_player_bans(
|
||||
&mut self,
|
||||
_res: Resource<WitBanManager>,
|
||||
) -> wasmtime::Result<Vec<BannedPlayerEntry>> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
let mut list = server.data.banned_player_list.write().await;
|
||||
list.banned_players
|
||||
.retain(|entry| entry.expires.is_none_or(|expires| expires > now));
|
||||
list.save();
|
||||
Ok(list
|
||||
.banned_players
|
||||
.iter()
|
||||
.map(|e| BannedPlayerEntry {
|
||||
uuid: WitUuid::to_wit(&e.uuid),
|
||||
name: e.name.clone(),
|
||||
created: e
|
||||
.created
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.unwrap_or_default(),
|
||||
source: e.source.clone(),
|
||||
expires: e.expires.and_then(|exp| {
|
||||
exp.format(&time::format_description::well_known::Rfc3339)
|
||||
.ok()
|
||||
}),
|
||||
reason: e.reason.clone(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn is_ip_banned(
|
||||
&mut self,
|
||||
_res: Resource<WitBanManager>,
|
||||
ip: String,
|
||||
) -> wasmtime::Result<bool> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let ip_addr: std::net::IpAddr = ip
|
||||
.parse()
|
||||
.map_err(|_| wasmtime::Error::msg("Invalid IP address"))?;
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
let mut list = server.data.banned_ip_list.write().await;
|
||||
list.banned_ips
|
||||
.retain(|entry| entry.expires.is_none_or(|expires| expires > now));
|
||||
list.save();
|
||||
Ok(list.banned_ips.iter().any(|e| e.ip == ip_addr))
|
||||
}
|
||||
|
||||
async fn get_ip_ban(
|
||||
&mut self,
|
||||
_res: Resource<WitBanManager>,
|
||||
ip: String,
|
||||
) -> wasmtime::Result<Option<BannedIpEntry>> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let ip_addr: std::net::IpAddr = ip
|
||||
.parse()
|
||||
.map_err(|_| wasmtime::Error::msg("Invalid IP address"))?;
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
let mut list = server.data.banned_ip_list.write().await;
|
||||
list.banned_ips
|
||||
.retain(|entry| entry.expires.is_none_or(|expires| expires > now));
|
||||
list.save();
|
||||
Ok(list
|
||||
.banned_ips
|
||||
.iter()
|
||||
.find(|e| e.ip == ip_addr)
|
||||
.map(|e| BannedIpEntry {
|
||||
ip: e.ip.to_string(),
|
||||
created: e
|
||||
.created
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.unwrap_or_default(),
|
||||
source: e.source.clone(),
|
||||
expires: e.expires.and_then(|exp| {
|
||||
exp.format(&time::format_description::well_known::Rfc3339)
|
||||
.ok()
|
||||
}),
|
||||
reason: e.reason.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn ban_ip(
|
||||
&mut self,
|
||||
_res: Resource<WitBanManager>,
|
||||
ip: String,
|
||||
options: BanIpOptions,
|
||||
) -> wasmtime::Result<()> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let ip_addr: std::net::IpAddr = ip
|
||||
.parse()
|
||||
.map_err(|_| wasmtime::Error::msg("Invalid IP address"))?;
|
||||
let reason_text = options
|
||||
.reason
|
||||
.as_ref()
|
||||
.map(|res| text_component_from_resource(self, res))
|
||||
.map_or_else(
|
||||
|| "Banned by plugin.".to_string(),
|
||||
pumpkin_util::text::TextComponent::to_pretty_console,
|
||||
);
|
||||
let source_name = options.source.unwrap_or_else(|| "Plugin".to_string());
|
||||
let expires = parse_ban_expiry(options.expires_at_utc, options.duration_seconds);
|
||||
|
||||
let mut list = server.data.banned_ip_list.write().await;
|
||||
if let Some(existing) = list.banned_ips.iter_mut().find(|e| e.ip == ip_addr) {
|
||||
existing.source = source_name;
|
||||
existing.expires = expires;
|
||||
existing.reason.clone_from(&reason_text);
|
||||
} else {
|
||||
let entry = crate::data::banlist_serializer::BannedIpEntry {
|
||||
ip: ip_addr,
|
||||
created: time::OffsetDateTime::now_utc(),
|
||||
source: source_name,
|
||||
expires,
|
||||
reason: reason_text.clone(),
|
||||
};
|
||||
list.banned_ips.push(entry);
|
||||
}
|
||||
list.save();
|
||||
drop(list);
|
||||
|
||||
if options.kick_matching_players {
|
||||
for player in server.get_all_players() {
|
||||
if player.client.address().ip() == ip_addr {
|
||||
player
|
||||
.kick(
|
||||
crate::net::DisconnectReason::Kicked,
|
||||
pumpkin_util::text::TextComponent::text(reason_text.clone()),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if options.log_to_console {
|
||||
tracing::info!("Banned IP {}: {}", ip_addr, reason_text);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn unban_ip(
|
||||
&mut self,
|
||||
_res: Resource<WitBanManager>,
|
||||
ip: String,
|
||||
) -> wasmtime::Result<bool> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let ip_addr: std::net::IpAddr = ip
|
||||
.parse()
|
||||
.map_err(|_| wasmtime::Error::msg("Invalid IP address"))?;
|
||||
let mut list = server.data.banned_ip_list.write().await;
|
||||
Ok(list
|
||||
.banned_ips
|
||||
.iter()
|
||||
.position(|e| e.ip == ip_addr)
|
||||
.is_some_and(|pos| {
|
||||
list.banned_ips.remove(pos);
|
||||
list.save();
|
||||
true
|
||||
}))
|
||||
}
|
||||
|
||||
async fn list_ip_bans(
|
||||
&mut self,
|
||||
_res: Resource<WitBanManager>,
|
||||
) -> wasmtime::Result<Vec<BannedIpEntry>> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
let mut list = server.data.banned_ip_list.write().await;
|
||||
list.banned_ips
|
||||
.retain(|entry| entry.expires.is_none_or(|expires| expires > now));
|
||||
list.save();
|
||||
Ok(list
|
||||
.banned_ips
|
||||
.iter()
|
||||
.map(|e| BannedIpEntry {
|
||||
ip: e.ip.to_string(),
|
||||
created: e
|
||||
.created
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.unwrap_or_default(),
|
||||
source: e.source.clone(),
|
||||
expires: e.expires.and_then(|exp| {
|
||||
exp.format(&time::format_description::well_known::Rfc3339)
|
||||
.ok()
|
||||
}),
|
||||
reason: e.reason.clone(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn drop(&mut self, rep: Resource<WitBanManager>) -> wasmtime::Result<()> {
|
||||
let _ = self
|
||||
.resource_table
|
||||
.delete::<crate::plugin::loader::wasm::wasm_host::state::BanManagerResource>(
|
||||
Resource::new_own(rep.rep()),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl pumpkin::plugin::server::HostWhitelistManager for PluginHostState {
|
||||
async fn is_enabled(&mut self, _res: Resource<WitWhitelistManager>) -> wasmtime::Result<bool> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
Ok(server.white_list.load(std::sync::atomic::Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn set_enabled(
|
||||
&mut self,
|
||||
_res: Resource<WitWhitelistManager>,
|
||||
enabled: bool,
|
||||
) -> wasmtime::Result<()> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
server
|
||||
.white_list
|
||||
.store(enabled, std::sync::atomic::Ordering::Relaxed);
|
||||
if enabled && server.basic_config.enforce_whitelist {
|
||||
let whitelist = server.data.whitelist_config.read().await;
|
||||
for player in server.get_all_players() {
|
||||
if !whitelist.is_whitelisted(&player.gameprofile) {
|
||||
player
|
||||
.kick(
|
||||
crate::net::DisconnectReason::Kicked,
|
||||
pumpkin_macros::translate_cross!(
|
||||
pumpkin_data::translation::java::MULTIPLAYER_DISCONNECT_NOT_WHITELISTED,
|
||||
pumpkin_data::translation::bedrock::DISCONNECT_KICKED
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn is_whitelisted(
|
||||
&mut self,
|
||||
_res: Resource<WitWhitelistManager>,
|
||||
id: WitUuid,
|
||||
) -> wasmtime::Result<bool> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let uuid = WitUuid::from_wit(&id);
|
||||
let whitelist = server.data.whitelist_config.read().await;
|
||||
Ok(whitelist.whitelist.iter().any(|e| e.uuid == uuid))
|
||||
}
|
||||
|
||||
async fn add_player(
|
||||
&mut self,
|
||||
_res: Resource<WitWhitelistManager>,
|
||||
name: String,
|
||||
id: WitUuid,
|
||||
) -> wasmtime::Result<bool> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let uuid = WitUuid::from_wit(&id);
|
||||
let mut config = server.data.whitelist_config.write().await;
|
||||
if config.whitelist.iter().any(|e| e.uuid == uuid) {
|
||||
Ok(false)
|
||||
} else {
|
||||
config
|
||||
.whitelist
|
||||
.push(pumpkin_config::whitelist::WhitelistEntry::new(uuid, name));
|
||||
config.save();
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
async fn remove_player(
|
||||
&mut self,
|
||||
_res: Resource<WitWhitelistManager>,
|
||||
id: WitUuid,
|
||||
) -> wasmtime::Result<bool> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let uuid = WitUuid::from_wit(&id);
|
||||
let mut config = server.data.whitelist_config.write().await;
|
||||
Ok(config
|
||||
.whitelist
|
||||
.iter()
|
||||
.position(|e| e.uuid == uuid)
|
||||
.is_some_and(|pos| {
|
||||
config.whitelist.remove(pos);
|
||||
config.save();
|
||||
true
|
||||
}))
|
||||
}
|
||||
|
||||
async fn list_entries(
|
||||
&mut self,
|
||||
_res: Resource<WitWhitelistManager>,
|
||||
) -> wasmtime::Result<Vec<WitWhitelistEntry>> {
|
||||
let server = self
|
||||
.server
|
||||
.as_ref()
|
||||
.ok_or_else(|| wasmtime::Error::msg("Server not available"))?;
|
||||
let config = server.data.whitelist_config.read().await;
|
||||
Ok(config
|
||||
.whitelist
|
||||
.iter()
|
||||
.map(|e| WitWhitelistEntry {
|
||||
uuid: WitUuid::to_wit(&e.uuid),
|
||||
name: e.name.clone(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn drop(&mut self, rep: Resource<WitWhitelistManager>) -> wasmtime::Result<()> {
|
||||
let _ = self
|
||||
.resource_table
|
||||
.delete::<crate::plugin::loader::wasm::wasm_host::state::WhitelistManagerResource>(
|
||||
Resource::new_own(rep.rep()),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
66
tools/pumpkin-codegen/src/wit/attribute.rs
Normal file
66
tools/pumpkin-codegen/src/wit/attribute.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use semver::Version;
|
||||
use serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs;
|
||||
use wit_encoder::{Enum, Interface, Package, PackageName, Record, TypeDef, TypeDefKind};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AttributeEntry {
|
||||
id: u8,
|
||||
default_value: f64,
|
||||
}
|
||||
|
||||
pub fn build() -> String {
|
||||
let attributes: BTreeMap<String, AttributeEntry> =
|
||||
serde_json::from_str(&fs::read_to_string("../../assets/attributes.json").unwrap())
|
||||
.expect("Failed to parse attributes.json");
|
||||
|
||||
let mut package = Package::new(PackageName::new(
|
||||
"pumpkin",
|
||||
"plugin",
|
||||
Some(Version::new(0, 1, 0)),
|
||||
));
|
||||
let mut interface = Interface::new("attributes");
|
||||
|
||||
let mut sorted_attributes: Vec<(String, AttributeEntry)> = attributes.into_iter().collect();
|
||||
sorted_attributes.sort_by_key(|(_, raw)| raw.id);
|
||||
|
||||
let mut attribute_enum = Enum::empty();
|
||||
for (raw_name, _) in sorted_attributes {
|
||||
let name = raw_name
|
||||
.strip_prefix("minecraft:")
|
||||
.unwrap_or(&raw_name)
|
||||
.replace('_', "-");
|
||||
attribute_enum.case(name);
|
||||
}
|
||||
|
||||
interface.type_def(TypeDef::new("attribute", TypeDefKind::Enum(attribute_enum)));
|
||||
|
||||
let mut modifier_op_enum = Enum::empty();
|
||||
modifier_op_enum.case("add");
|
||||
modifier_op_enum.case("multiply-base");
|
||||
modifier_op_enum.case("multiply-total");
|
||||
|
||||
interface.type_def(TypeDef::new(
|
||||
"modifier-operation",
|
||||
TypeDefKind::Enum(modifier_op_enum),
|
||||
));
|
||||
|
||||
let modifier_record = Record::new([
|
||||
wit_encoder::Field::new("id", wit_encoder::Type::String),
|
||||
wit_encoder::Field::new("amount", wit_encoder::Type::F64),
|
||||
wit_encoder::Field::new(
|
||||
"operation",
|
||||
wit_encoder::Type::Named(wit_encoder::Ident::new("modifier-operation")),
|
||||
),
|
||||
]);
|
||||
|
||||
interface.type_def(TypeDef::new(
|
||||
"attribute-modifier",
|
||||
TypeDefKind::Record(modifier_record),
|
||||
));
|
||||
|
||||
package.interface(interface);
|
||||
|
||||
package.to_string()
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod attribute;
|
||||
pub mod bedrock_packet;
|
||||
pub mod biome;
|
||||
pub mod data_component;
|
||||
@ -32,6 +33,7 @@ pub fn main() {
|
||||
(data_component::build, "data-components.wit"),
|
||||
(enchantment::build, "enchantments.wit"),
|
||||
(biome::build, "biomes.wit"),
|
||||
(attribute::build, "attributes.wit"),
|
||||
];
|
||||
|
||||
for (build_fn, file) in build_functions {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user