mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat(pumpkin): add /save-all, /save-off and /save-on commands (#2394)
Implements the three vanilla save-control commands (tracked in #15): - /save-all saves all online players' data and advancements, then requests a chunk save from every world's chunk scheduler. It works even while autosaving is disabled, matching Vanilla. - /save-off disables periodic autosaving via a new save_enabled flag on Level; running it again fails with commands.save.alreadyOff. - /save-on re-enables autosaving; running it again fails with commands.save.alreadyOn.
This commit is contained in:
@@ -91,6 +91,9 @@ pub struct Level {
|
||||
pub shut_down_chunk_system: AtomicBool,
|
||||
pub should_save: AtomicBool,
|
||||
pub should_unload: AtomicBool,
|
||||
/// Whether periodic autosaving is enabled. Toggled by `/save-off` and `/save-on`;
|
||||
/// a manual `/save-all` still saves while this is `false`.
|
||||
pub save_enabled: AtomicBool,
|
||||
/// Number of ticks between autosave checks. If 0, autosave is disabled.
|
||||
pub autosave_ticks: u64,
|
||||
|
||||
@@ -262,6 +265,7 @@ impl Level {
|
||||
shut_down_chunk_system: AtomicBool::new(false),
|
||||
should_save: AtomicBool::new(false),
|
||||
should_unload: AtomicBool::new(false),
|
||||
save_enabled: AtomicBool::new(true),
|
||||
autosave_ticks: level_config.autosave_ticks,
|
||||
pending_entity_generations,
|
||||
level_channel: level_channel.clone(),
|
||||
|
||||
@@ -40,6 +40,9 @@ mod plugins;
|
||||
mod pumpkin;
|
||||
mod random;
|
||||
mod rotate;
|
||||
mod saveall;
|
||||
mod saveoff;
|
||||
mod saveon;
|
||||
mod say;
|
||||
mod seed;
|
||||
mod setblock;
|
||||
@@ -156,6 +159,9 @@ pub async fn default_dispatcher(
|
||||
random::register(&mut dispatcher, registry);
|
||||
list::register(&mut dispatcher, registry);
|
||||
seed::register(&mut dispatcher, registry);
|
||||
saveall::register(&mut dispatcher, registry);
|
||||
saveoff::register(&mut dispatcher, registry);
|
||||
saveon::register(&mut dispatcher, registry);
|
||||
setidletimeout::register(&mut dispatcher, registry);
|
||||
spreadplayers::register(&mut dispatcher, registry);
|
||||
stop::register(&mut dispatcher, registry);
|
||||
|
||||
93
pumpkin/src/command/commands/saveall.rs
Normal file
93
pumpkin/src/command/commands/saveall.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::PermissionLvl;
|
||||
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use tracing::error;
|
||||
|
||||
use crate::command::argument_builder::{ArgumentBuilder, command};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
use crate::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
|
||||
const DESCRIPTION: &str = "Saves the server to disk.";
|
||||
|
||||
const PERMISSION: &str = "minecraft:command.save-all";
|
||||
|
||||
const SAVE_FAILED_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_SAVE_FAILED,
|
||||
translation::bedrock::COMMANDS_SAVE_FAILED,
|
||||
);
|
||||
|
||||
struct SaveAllExecutor;
|
||||
|
||||
impl CommandExecutor for SaveAllExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SAVE_SAVING,
|
||||
translation::bedrock::COMMANDS_SAVE_START,
|
||||
[],
|
||||
),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
let server = context.server();
|
||||
|
||||
if let Err(err) = server.player_data_storage.save_all_players(server).await {
|
||||
error!("Failed to save player data: {err}");
|
||||
return Err(SAVE_FAILED_ERROR_TYPE.create_without_context());
|
||||
}
|
||||
|
||||
if let Err(err) = server
|
||||
.advancement_manager
|
||||
.save_all_players(&server.get_all_players())
|
||||
.await
|
||||
{
|
||||
error!("Failed to save player advancements: {err}");
|
||||
return Err(SAVE_FAILED_ERROR_TYPE.create_without_context());
|
||||
}
|
||||
|
||||
// Request a save from every world's chunk scheduler. This works even
|
||||
// while autosaving is disabled via /save-off, matching Vanilla.
|
||||
for world in server.worlds.load().iter() {
|
||||
world.level.should_save.store(true, Relaxed);
|
||||
world.level.level_channel.notify();
|
||||
}
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SAVE_SUCCESS,
|
||||
translation::bedrock::COMMANDS_SAVE_SUCCESS,
|
||||
[],
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(dispatcher: &mut CommandDispatcher, registry: &mut PermissionRegistry) {
|
||||
registry.register_permission_or_panic(Permission::new(
|
||||
PERMISSION,
|
||||
DESCRIPTION,
|
||||
PermissionDefault::Op(PermissionLvl::Four),
|
||||
));
|
||||
|
||||
dispatcher.register(
|
||||
command("save-all", DESCRIPTION)
|
||||
.requires(PERMISSION)
|
||||
.executes(SaveAllExecutor),
|
||||
);
|
||||
}
|
||||
68
pumpkin/src/command/commands/saveoff.rs
Normal file
68
pumpkin/src/command/commands/saveoff.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::PermissionLvl;
|
||||
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
use crate::command::argument_builder::{ArgumentBuilder, command};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
use crate::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
|
||||
const DESCRIPTION: &str = "Disables automatic server saves.";
|
||||
|
||||
const PERMISSION: &str = "minecraft:command.save-off";
|
||||
|
||||
const ALREADY_OFF_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_SAVE_ALREADYOFF,
|
||||
translation::bedrock::COMMANDS_SAVE_OFF_ALREADYOFF,
|
||||
);
|
||||
|
||||
struct SaveOffExecutor;
|
||||
|
||||
impl CommandExecutor for SaveOffExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
let mut any_disabled = false;
|
||||
for world in context.server().worlds.load().iter() {
|
||||
if world.level.save_enabled.swap(false, Relaxed) {
|
||||
any_disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !any_disabled {
|
||||
return Err(ALREADY_OFF_ERROR_TYPE.create_without_context());
|
||||
}
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SAVE_DISABLED,
|
||||
translation::bedrock::COMMANDS_SAVE_DISABLED,
|
||||
[],
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(dispatcher: &mut CommandDispatcher, registry: &mut PermissionRegistry) {
|
||||
registry.register_permission_or_panic(Permission::new(
|
||||
PERMISSION,
|
||||
DESCRIPTION,
|
||||
PermissionDefault::Op(PermissionLvl::Four),
|
||||
));
|
||||
|
||||
dispatcher.register(
|
||||
command("save-off", DESCRIPTION)
|
||||
.requires(PERMISSION)
|
||||
.executes(SaveOffExecutor),
|
||||
);
|
||||
}
|
||||
68
pumpkin/src/command/commands/saveon.rs
Normal file
68
pumpkin/src/command/commands/saveon.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::PermissionLvl;
|
||||
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
use crate::command::argument_builder::{ArgumentBuilder, command};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
use crate::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
|
||||
const DESCRIPTION: &str = "Enables automatic server saves.";
|
||||
|
||||
const PERMISSION: &str = "minecraft:command.save-on";
|
||||
|
||||
const ALREADY_ON_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_SAVE_ALREADYON,
|
||||
translation::bedrock::COMMANDS_SAVE_ON_ALREADYON,
|
||||
);
|
||||
|
||||
struct SaveOnExecutor;
|
||||
|
||||
impl CommandExecutor for SaveOnExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
let mut any_enabled = false;
|
||||
for world in context.server().worlds.load().iter() {
|
||||
if !world.level.save_enabled.swap(true, Relaxed) {
|
||||
any_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !any_enabled {
|
||||
return Err(ALREADY_ON_ERROR_TYPE.create_without_context());
|
||||
}
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SAVE_ENABLED,
|
||||
translation::bedrock::COMMANDS_SAVE_ENABLED,
|
||||
[],
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(dispatcher: &mut CommandDispatcher, registry: &mut PermissionRegistry) {
|
||||
registry.register_permission_or_panic(Permission::new(
|
||||
PERMISSION,
|
||||
DESCRIPTION,
|
||||
PermissionDefault::Op(PermissionLvl::Four),
|
||||
));
|
||||
|
||||
dispatcher.register(
|
||||
command("save-on", DESCRIPTION)
|
||||
.requires(PERMISSION)
|
||||
.executes(SaveOnExecutor),
|
||||
);
|
||||
}
|
||||
@@ -1132,7 +1132,7 @@ impl World {
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.level.autosave_ticks > 0 {
|
||||
if self.level.autosave_ticks > 0 && self.level.save_enabled.load(Relaxed) {
|
||||
let autosave = self.level.autosave_ticks as i64;
|
||||
if autosave > 0 && level_time.world_age % autosave == 0 {
|
||||
self.level.should_save.store(true, Relaxed);
|
||||
|
||||
Reference in New Issue
Block a user