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.
|
# This should be different from the pumpkin server version.
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wit-bindgen = { workspace = true }
|
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.
|
//! WIT-generated plugin type extensions belong in here. For example [Display](std::fmt::Display) implementations.
|
||||||
|
|
||||||
|
mod attributes;
|
||||||
|
mod player;
|
||||||
mod server_list_ping;
|
mod server_list_ping;
|
||||||
mod uuid;
|
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>) {
|
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(
|
let string_reason = reason.clone().map_or_else(
|
||||||
|| "Banned by an operator.".to_string(),
|
|| "Banned by an operator.".to_string(),
|
||||||
pumpkin_util::text::TextComponent::get_text,
|
pumpkin_util::text::TextComponent::get_text,
|
||||||
);
|
);
|
||||||
|
let source_str = source.unwrap_or_else(|| "Plugin".to_string());
|
||||||
|
|
||||||
if banned_players
|
if log_to_console {
|
||||||
.banned_players
|
tracing::info!(
|
||||||
.iter()
|
"Banning player {} ({}) by {}: {}",
|
||||||
.any(|entry| entry.uuid == self.gameprofile.id)
|
self.gameprofile.name,
|
||||||
{
|
self.gameprofile.id,
|
||||||
return;
|
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(
|
banned_players.banned_players.push(
|
||||||
crate::data::banlist_serializer::BannedPlayerEntry::new(
|
crate::data::banlist_serializer::BannedPlayerEntry::new(
|
||||||
&self.gameprofile,
|
&self.gameprofile,
|
||||||
"Plugin".to_string(),
|
source_str,
|
||||||
None,
|
expires,
|
||||||
string_reason,
|
string_reason,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -3680,6 +3700,7 @@ impl Player {
|
|||||||
banned_players.save();
|
banned_players.save();
|
||||||
drop(banned_players);
|
drop(banned_players);
|
||||||
|
|
||||||
|
if kick_if_online {
|
||||||
let kick_reason = reason.unwrap_or_else(|| {
|
let kick_reason = reason.unwrap_or_else(|| {
|
||||||
TextComponent::translate_cross(
|
TextComponent::translate_cross(
|
||||||
translation::java::MULTIPLAYER_DISCONNECT_BANNED,
|
translation::java::MULTIPLAYER_DISCONNECT_BANNED,
|
||||||
@ -3690,31 +3711,54 @@ impl Player {
|
|||||||
|
|
||||||
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>) {
|
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(
|
let string_reason = reason.clone().map_or_else(
|
||||||
|| "Banned by an operator.".to_string(),
|
|| "Banned by an operator.".to_string(),
|
||||||
pumpkin_util::text::TextComponent::get_text,
|
pumpkin_util::text::TextComponent::get_text,
|
||||||
);
|
);
|
||||||
|
let source_str = source.unwrap_or_else(|| "Plugin".to_string());
|
||||||
let target_ip = self.client.address().ip();
|
let target_ip = self.client.address().ip();
|
||||||
|
|
||||||
if banned_ips.get_entry(&target_ip).is_some() {
|
if log_to_console {
|
||||||
return;
|
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
|
||||||
.banned_ips
|
.banned_ips
|
||||||
.push(crate::data::banlist_serializer::BannedIpEntry::new(
|
.push(crate::data::banlist_serializer::BannedIpEntry::new(
|
||||||
target_ip,
|
target_ip,
|
||||||
"Plugin".to_string(),
|
source_str,
|
||||||
None,
|
expires,
|
||||||
string_reason,
|
string_reason,
|
||||||
));
|
));
|
||||||
|
|
||||||
banned_ips.save();
|
banned_ips.save();
|
||||||
drop(banned_ips);
|
drop(banned_ips);
|
||||||
|
|
||||||
|
if kick_matching_players {
|
||||||
let kick_reason = reason.unwrap_or_else(|| {
|
let kick_reason = reason.unwrap_or_else(|| {
|
||||||
TextComponent::translate_cross(
|
TextComponent::translate_cross(
|
||||||
translation::java::MULTIPLAYER_DISCONNECT_IP_BANNED,
|
translation::java::MULTIPLAYER_DISCONNECT_IP_BANNED,
|
||||||
@ -3730,6 +3774,7 @@ impl Player {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn tick_client_load_timeout(&self) {
|
pub fn tick_client_load_timeout(&self) {
|
||||||
if !self.client_loaded.load(Ordering::Relaxed) {
|
if !self.client_loaded.load(Ordering::Relaxed) {
|
||||||
|
|||||||
@ -271,6 +271,26 @@ impl BedrockClient {
|
|||||||
self.close().await;
|
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]) {
|
pub async fn send_chunks(&self, chunks: &[SyncChunk]) {
|
||||||
let player = self.player.load_full();
|
let player = self.player.load_full();
|
||||||
let Some(player) = player.as_ref() else {
|
let Some(player) = player.as_ref() else {
|
||||||
|
|||||||
@ -425,21 +425,27 @@ impl JavaClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn kick(&self, reason: TextComponent) {
|
pub async fn kick(&self, reason: TextComponent) {
|
||||||
|
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() {
|
match self.connection_state.load() {
|
||||||
ConnectionState::Login => {
|
ConnectionState::Login => {
|
||||||
// TextComponent implements Serialize and writes in bytes instead of String, that's the reasib we only use content
|
// TextComponent implements Serialize and writes in bytes instead of String, that's the reason we only use content
|
||||||
self.send_packet_now(&CLoginDisconnect::new(
|
self.send_packet_now(&CLoginDisconnect::new(
|
||||||
serde_json::to_string(&reason.0).unwrap_or_else(|_| String::new()),
|
serde_json::to_string(&reason.0).unwrap_or_else(|_| String::new()),
|
||||||
))
|
))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
ConnectionState::Config => {
|
ConnectionState::Config => {
|
||||||
self.send_packet_now(&CConfigDisconnect::new(&reason.get_text()))
|
self.send_packet_now(&CConfigDisconnect::new(&reason.clone().get_text()))
|
||||||
.await;
|
.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);
|
debug!("Closing connection for {}", self.id);
|
||||||
self.close();
|
self.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -387,7 +387,7 @@ fn is_valid_player_name(name: &str) -> bool {
|
|||||||
!name.chars().any(|c| c.is_control() || c == ' ')
|
!name.chars().any(|c| c.is_control() || c == ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum DisconnectReason {
|
pub enum DisconnectReason {
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
CantConnectNoInternet = 1,
|
CantConnectNoInternet = 1,
|
||||||
|
|||||||
@ -65,6 +65,9 @@ pub type ConsumedArgsResource = WasmResource<OwnedConsumedArgs>;
|
|||||||
pub type CommandNodeResource = WasmResource<NonLeafNodeBuilder>;
|
pub type CommandNodeResource = WasmResource<NonLeafNodeBuilder>;
|
||||||
pub type ItemStackResource = WasmResource<Arc<Mutex<pumpkin_data::item_stack::ItemStack>>>;
|
pub type ItemStackResource = WasmResource<Arc<Mutex<pumpkin_data::item_stack::ItemStack>>>;
|
||||||
pub type RecipeManagerResource = WasmResource<Arc<RecipeManager>>;
|
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>>;
|
pub type BlockEntityResource = WasmResource<Arc<dyn crate::block::entities::BlockEntity>>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -294,6 +297,32 @@ impl PluginHostState {
|
|||||||
Ok(wasmtime::component::Resource::new_own(resource.rep()))
|
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>(
|
pub fn add_block_entity<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
provider: Arc<dyn crate::block::entities::BlockEntity>,
|
provider: Arc<dyn crate::block::entities::BlockEntity>,
|
||||||
|
|||||||
@ -10,14 +10,20 @@ use crate::plugin::loader::wasm::wasm_host::{
|
|||||||
state::{EntityResource, PluginHostState},
|
state::{EntityResource, PluginHostState},
|
||||||
wit::v0_1::events::to_wasm_position,
|
wit::v0_1::events::to_wasm_position,
|
||||||
wit::v0_1::pumpkin::plugin::{
|
wit::v0_1::pumpkin::plugin::{
|
||||||
|
attributes::{
|
||||||
|
Attribute, AttributeModifier as WitAttributeModifier,
|
||||||
|
ModifierOperation as WitModifierOperation,
|
||||||
|
},
|
||||||
common::{EntityPose, Position},
|
common::{EntityPose, Position},
|
||||||
entity::Host,
|
entity::Host,
|
||||||
entity_types,
|
entity_types,
|
||||||
|
item_stack::ItemStack as WitHostItemStack,
|
||||||
text::TextComponent,
|
text::TextComponent,
|
||||||
uuid::Uuid,
|
uuid::Uuid,
|
||||||
world::{
|
world::{
|
||||||
BlockPos as WitBlockPos, BoundingBox as WitBoundingBox, Entity, HostEntity,
|
BlockPos as WitBlockPos, BoundingBox as WitBoundingBox, Entity,
|
||||||
RaycastResult as WitRaycastResult, World,
|
EquipmentSlot as WitEquipmentSlot, HostEntity, RaycastResult as WitRaycastResult,
|
||||||
|
World,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wit::v0_1::uuid::UuidExt,
|
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 {
|
impl HostEntity for PluginHostState {
|
||||||
async fn get_id(&mut self, entity: Resource<Entity>) -> wasmtime::Result<u32> {
|
async fn get_id(&mut self, entity: Resource<Entity>) -> wasmtime::Result<u32> {
|
||||||
let entity = entity_from_resource(self, &entity)?;
|
let entity = entity_from_resource(self, &entity)?;
|
||||||
@ -468,6 +568,212 @@ impl HostEntity for PluginHostState {
|
|||||||
Ok(())
|
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> {
|
async fn get_age(&mut self, entity: Resource<Entity>) -> wasmtime::Result<i32> {
|
||||||
let entity = entity_from_resource(self, &entity)?;
|
let entity = entity_from_resource(self, &entity)?;
|
||||||
Ok(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::data_components::Host for PluginHostState {}
|
||||||
impl pumpkin::plugin::enchantments::Host for PluginHostState {}
|
impl pumpkin::plugin::enchantments::Host for PluginHostState {}
|
||||||
impl pumpkin::plugin::biomes::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<()> {
|
pub fn add_to_linker(linker: &mut Linker<PluginHostState>) -> wasmtime::Result<()> {
|
||||||
Plugin::add_to_linker::<_, HasSelf<_>>(linker, |state: &mut PluginHostState| state)?;
|
Plugin::add_to_linker::<_, HasSelf<_>>(linker, |state: &mut PluginHostState| state)?;
|
||||||
|
|||||||
@ -480,7 +480,7 @@ fn world_from_resource(
|
|||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn to_wit_permission_level(
|
pub(crate) const fn to_wit_permission_level(
|
||||||
level: PermissionLvl,
|
level: PermissionLvl,
|
||||||
) -> pumpkin::plugin::permission::PermissionLevel {
|
) -> pumpkin::plugin::permission::PermissionLevel {
|
||||||
match level {
|
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,
|
level: pumpkin::plugin::permission::PermissionLevel,
|
||||||
) -> PermissionLvl {
|
) -> PermissionLvl {
|
||||||
match level {
|
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> {
|
impl DowncastResourceExt<PlayerResource> for Resource<Player> {
|
||||||
fn downcast_ref<'a>(&'a self, state: &'a mut PluginHostState) -> &'a PlayerResource {
|
fn downcast_ref<'a>(&'a self, state: &'a mut PluginHostState) -> &'a PlayerResource {
|
||||||
state
|
state
|
||||||
@ -1423,17 +1886,6 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
Ok(())
|
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<()> {
|
async fn respawn(&mut self, player: Resource<Player>) -> wasmtime::Result<()> {
|
||||||
let player = player_from_resource(self, &player)?;
|
let player = player_from_resource(self, &player)?;
|
||||||
player.respawn().await;
|
player.respawn().await;
|
||||||
@ -1471,24 +1923,50 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
|
|||||||
async fn ban(
|
async fn ban(
|
||||||
&mut self,
|
&mut self,
|
||||||
player: Resource<Player>,
|
player: Resource<Player>,
|
||||||
reason: Option<Resource<pumpkin::plugin::text::TextComponent>>,
|
options: pumpkin::plugin::player::BanPlayerOptions,
|
||||||
) -> wasmtime::Result<()> {
|
) -> 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 player = player_from_resource(self, &player)?;
|
||||||
let server = self.server.as_ref().expect("server not available");
|
let server = self.server.as_ref().expect("server not available");
|
||||||
let reason = reason.map(|r| text_component_from_resource(self, &r));
|
player
|
||||||
player.ban(server, reason).await;
|
.ban_explicit(
|
||||||
|
server,
|
||||||
|
reason,
|
||||||
|
options.source,
|
||||||
|
expires,
|
||||||
|
options.kick_if_online,
|
||||||
|
options.log_to_console,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ban_ip(
|
async fn ban_ip(
|
||||||
&mut self,
|
&mut self,
|
||||||
player: Resource<Player>,
|
player: Resource<Player>,
|
||||||
reason: Option<Resource<pumpkin::plugin::text::TextComponent>>,
|
options: pumpkin::plugin::player::BanIpOptions,
|
||||||
) -> wasmtime::Result<()> {
|
) -> 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 player = player_from_resource(self, &player)?;
|
||||||
let server = self.server.as_ref().expect("server not available");
|
let server = self.server.as_ref().expect("server not available");
|
||||||
let reason = reason.map(|r| text_component_from_resource(self, &r));
|
player
|
||||||
player.ban_ip(server, reason).await;
|
.ban_ip_explicit(
|
||||||
|
server,
|
||||||
|
reason,
|
||||||
|
options.source,
|
||||||
|
expires,
|
||||||
|
options.kick_matching_players,
|
||||||
|
options.log_to_console,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2357,6 +2835,52 @@ impl pumpkin::plugin::player::HostJavaPlayer for PluginHostState {
|
|||||||
Ok(())
|
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(
|
async fn drop(
|
||||||
&mut self,
|
&mut self,
|
||||||
rep: Resource<pumpkin::plugin::player::JavaPlayer>,
|
rep: Resource<pumpkin::plugin::player::JavaPlayer>,
|
||||||
@ -2787,6 +3311,61 @@ impl pumpkin::plugin::player::HostBedrockPlayer for PluginHostState {
|
|||||||
Ok(())
|
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(
|
async fn drop(
|
||||||
&mut self,
|
&mut self,
|
||||||
rep: Resource<pumpkin::plugin::player::BedrockPlayer>,
|
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 crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::recipe::RecipeManager as WitRecipeManager;
|
||||||
use pumpkin::plugin::server::CommandSender as WasmCommandSender;
|
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::{
|
use crate::plugin::{
|
||||||
loader::wasm::wasm_host::{
|
loader::wasm::wasm_host::{
|
||||||
state::{PluginHostState, ServerResource},
|
state::{PluginHostState, ServerResource},
|
||||||
wit::v0_1::pumpkin::{
|
wit::v0_1::pumpkin::{
|
||||||
self,
|
self,
|
||||||
plugin::{
|
plugin::{
|
||||||
player::Player,
|
player::{BanIpOptions, BanPlayerOptions, Player},
|
||||||
server::{Difficulty, Dimension, Server, SysInfo},
|
server::{
|
||||||
|
BanManager as WitBanManager, BannedIpEntry, BannedPlayerEntry, Difficulty,
|
||||||
|
Dimension, OpEntry, OpManager as WitOpManager, Server, SysInfo,
|
||||||
|
WhitelistEntry as WitWhitelistEntry, WhitelistManager as WitWhitelistManager,
|
||||||
|
},
|
||||||
uuid::Uuid as WitUuid,
|
uuid::Uuid as WitUuid,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -397,6 +405,39 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
|||||||
self.add_recipe_manager(server.recipe_manager.clone())
|
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<()> {
|
async fn drop(&mut self, rep: Resource<Server>) -> wasmtime::Result<()> {
|
||||||
self.resource_table
|
self.resource_table
|
||||||
.delete::<ServerResource>(Resource::new_own(rep.rep()))
|
.delete::<ServerResource>(Resource::new_own(rep.rep()))
|
||||||
@ -404,3 +445,634 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
|
|||||||
Ok(())
|
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 bedrock_packet;
|
||||||
pub mod biome;
|
pub mod biome;
|
||||||
pub mod data_component;
|
pub mod data_component;
|
||||||
@ -32,6 +33,7 @@ pub fn main() {
|
|||||||
(data_component::build, "data-components.wit"),
|
(data_component::build, "data-components.wit"),
|
||||||
(enchantment::build, "enchantments.wit"),
|
(enchantment::build, "enchantments.wit"),
|
||||||
(biome::build, "biomes.wit"),
|
(biome::build, "biomes.wit"),
|
||||||
|
(attribute::build, "attributes.wit"),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (build_fn, file) in build_functions {
|
for (build_fn, file) in build_functions {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user