Alexander Medvedev 2026-09-02 13:24:23 +02:00
parent 2be3e8be88
commit d1cfc0d181
41 changed files with 2754 additions and 542 deletions

1
.gitignore vendored
View File

@ -157,3 +157,4 @@ version_history.json
world_nether/ world_nether/
world_the_end/ world_the_end/
patchbukkit/ patchbukkit/
minecraft-sources*/

View File

@ -352,18 +352,33 @@ impl DataComponentImpl for CustomModelDataImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct TooltipDisplayImpl; pub struct TooltipDisplayImpl;
impl TooltipDisplayImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for TooltipDisplayImpl { impl DataComponentImpl for TooltipDisplayImpl {
default_impl!(TooltipDisplay); default_impl!(TooltipDisplay);
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct CreativeSlotLockImpl; pub struct CreativeSlotLockImpl;
impl CreativeSlotLockImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for CreativeSlotLockImpl { impl DataComponentImpl for CreativeSlotLockImpl {
default_impl!(CreativeSlotLock); default_impl!(CreativeSlotLock);
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct EnchantmentGlintOverrideImpl; pub struct EnchantmentGlintOverrideImpl;
impl EnchantmentGlintOverrideImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for EnchantmentGlintOverrideImpl { impl DataComponentImpl for EnchantmentGlintOverrideImpl {
default_impl!(EnchantmentGlintOverride); default_impl!(EnchantmentGlintOverride);
} }
@ -431,18 +446,33 @@ impl DataComponentImpl for BaseColorImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct InstrumentImpl; pub struct InstrumentImpl;
impl InstrumentImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for InstrumentImpl { impl DataComponentImpl for InstrumentImpl {
default_impl!(Instrument); default_impl!(Instrument);
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ProvidesTrimMaterialImpl; pub struct ProvidesTrimMaterialImpl;
impl ProvidesTrimMaterialImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for ProvidesTrimMaterialImpl { impl DataComponentImpl for ProvidesTrimMaterialImpl {
default_impl!(ProvidesTrimMaterial); default_impl!(ProvidesTrimMaterial);
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ProvidesBannerPatternsImpl; pub struct ProvidesBannerPatternsImpl;
impl ProvidesBannerPatternsImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for ProvidesBannerPatternsImpl { impl DataComponentImpl for ProvidesBannerPatternsImpl {
default_impl!(ProvidesBannerPatterns); default_impl!(ProvidesBannerPatterns);
} }
@ -495,6 +525,11 @@ impl DataComponentImpl for BannerPatternsImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct PotDecorationsImpl; pub struct PotDecorationsImpl;
impl PotDecorationsImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for PotDecorationsImpl { impl DataComponentImpl for PotDecorationsImpl {
default_impl!(PotDecorations); default_impl!(PotDecorations);
} }
@ -522,6 +557,11 @@ impl DataComponentImpl for LockImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BreakSoundImpl; pub struct BreakSoundImpl;
impl BreakSoundImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for BreakSoundImpl { impl DataComponentImpl for BreakSoundImpl {
default_impl!(BreakSound); default_impl!(BreakSound);
} }

View File

@ -26,12 +26,22 @@ impl DataComponentImpl for BlockEntityDataImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct EntityDataImpl; pub struct EntityDataImpl;
impl EntityDataImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for EntityDataImpl { impl DataComponentImpl for EntityDataImpl {
default_impl!(EntityData); default_impl!(EntityData);
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BucketEntityDataImpl; pub struct BucketEntityDataImpl;
impl BucketEntityDataImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for BucketEntityDataImpl { impl DataComponentImpl for BucketEntityDataImpl {
default_impl!(BucketEntityData); default_impl!(BucketEntityData);
} }
@ -148,6 +158,11 @@ impl DataComponentImpl for BlockStateImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BeesImpl; pub struct BeesImpl;
impl BeesImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for BeesImpl { impl DataComponentImpl for BeesImpl {
default_impl!(Bees); default_impl!(Bees);
} }
@ -177,6 +192,11 @@ impl DataComponentImpl for ContainerLootImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct SulfurCubeContentImpl; pub struct SulfurCubeContentImpl;
impl SulfurCubeContentImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for SulfurCubeContentImpl { impl DataComponentImpl for SulfurCubeContentImpl {
default_impl!(SulfurCubeContent); default_impl!(SulfurCubeContent);
} }

View File

@ -90,6 +90,11 @@ impl DataComponentImpl for WrittenBookContentImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct DebugStickStateImpl; pub struct DebugStickStateImpl;
impl DebugStickStateImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for DebugStickStateImpl { impl DataComponentImpl for DebugStickStateImpl {
default_impl!(DebugStickState); default_impl!(DebugStickState);
} }

View File

@ -48,6 +48,13 @@ impl Hash for Modifier {
pub struct AttributeModifiersImpl { pub struct AttributeModifiersImpl {
pub attribute_modifiers: Cow<'static, [Modifier]>, pub attribute_modifiers: Cow<'static, [Modifier]>,
} }
impl AttributeModifiersImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self {
attribute_modifiers: Cow::Borrowed(&[]),
})
}
}
impl DataComponentImpl for AttributeModifiersImpl { impl DataComponentImpl for AttributeModifiersImpl {
default_impl!(AttributeModifiers); default_impl!(AttributeModifiers);
} }
@ -742,12 +749,22 @@ impl DataComponentImpl for GliderImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct DeathProtectionImpl; pub struct DeathProtectionImpl;
impl DeathProtectionImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for DeathProtectionImpl { impl DataComponentImpl for DeathProtectionImpl {
default_impl!(DeathProtection); default_impl!(DeathProtection);
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BlocksAttacksImpl; pub struct BlocksAttacksImpl;
impl BlocksAttacksImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for BlocksAttacksImpl { impl DataComponentImpl for BlocksAttacksImpl {
default_impl!(BlocksAttacks); default_impl!(BlocksAttacks);
} }
@ -1037,6 +1054,11 @@ impl DataComponentImpl for SwingAnimationImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct AdditionalTradeCostImpl; pub struct AdditionalTradeCostImpl;
impl AdditionalTradeCostImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for AdditionalTradeCostImpl { impl DataComponentImpl for AdditionalTradeCostImpl {
default_impl!(AdditionalTradeCost); default_impl!(AdditionalTradeCost);
} }

View File

@ -384,12 +384,22 @@ impl Hash for ConsumableImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct UseEffectsImpl; pub struct UseEffectsImpl;
impl UseEffectsImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for UseEffectsImpl { impl DataComponentImpl for UseEffectsImpl {
default_impl!(UseEffects); default_impl!(UseEffects);
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct UseRemainderImpl; pub struct UseRemainderImpl;
impl UseRemainderImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for UseRemainderImpl { impl DataComponentImpl for UseRemainderImpl {
default_impl!(UseRemainder); default_impl!(UseRemainder);
} }

View File

@ -652,7 +652,41 @@ pub fn read_data(id: DataComponent, data: &NbtTag) -> Option<Box<dyn DataCompone
DataComponent::SwingAnimation => Some(SwingAnimationImpl::read_data(data)?.to_dyn()), DataComponent::SwingAnimation => Some(SwingAnimationImpl::read_data(data)?.to_dyn()),
DataComponent::Rarity => Some(RarityImpl::read_data(data)?.to_dyn()), DataComponent::Rarity => Some(RarityImpl::read_data(data)?.to_dyn()),
DataComponent::BannerPatterns => Some(BannerPatternsImpl::read_data(data)?.to_dyn()), DataComponent::BannerPatterns => Some(BannerPatternsImpl::read_data(data)?.to_dyn()),
_ => None, DataComponent::UseEffects => Some(UseEffectsImpl::read_data(data)?.to_dyn()),
DataComponent::UseRemainder => Some(UseRemainderImpl::read_data(data)?.to_dyn()),
DataComponent::Weapon => Some(WeaponImpl::read_data(data)?.to_dyn()),
DataComponent::TooltipDisplay => Some(TooltipDisplayImpl::read_data(data)?.to_dyn()),
DataComponent::CreativeSlotLock => Some(CreativeSlotLockImpl::read_data(data)?.to_dyn()),
DataComponent::EnchantmentGlintOverride => {
Some(EnchantmentGlintOverrideImpl::read_data(data)?.to_dyn())
}
DataComponent::DeathProtection => Some(DeathProtectionImpl::read_data(data)?.to_dyn()),
DataComponent::BlocksAttacks => Some(BlocksAttacksImpl::read_data(data)?.to_dyn()),
DataComponent::AdditionalTradeCost => {
Some(AdditionalTradeCostImpl::read_data(data)?.to_dyn())
}
DataComponent::Dye => Some(DyeImpl::read_data(data)?.to_dyn()),
DataComponent::MapColor => Some(MapColorImpl::read_data(data)?.to_dyn()),
DataComponent::MapDecorations => Some(MapDecorationsImpl::read_data(data)?.to_dyn()),
DataComponent::DebugStickState => Some(DebugStickStateImpl::read_data(data)?.to_dyn()),
DataComponent::EntityData => Some(EntityDataImpl::read_data(data)?.to_dyn()),
DataComponent::BucketEntityData => Some(BucketEntityDataImpl::read_data(data)?.to_dyn()),
DataComponent::Instrument => Some(InstrumentImpl::read_data(data)?.to_dyn()),
DataComponent::ProvidesTrimMaterial => {
Some(ProvidesTrimMaterialImpl::read_data(data)?.to_dyn())
}
DataComponent::JukeboxPlayable => Some(JukeboxPlayableImpl::read_data(data)?.to_dyn()),
DataComponent::ProvidesBannerPatterns => {
Some(ProvidesBannerPatternsImpl::read_data(data)?.to_dyn())
}
DataComponent::Recipes => Some(RecipesImpl::read_data(data)?.to_dyn()),
DataComponent::PotDecorations => Some(PotDecorationsImpl::read_data(data)?.to_dyn()),
DataComponent::Bees => Some(BeesImpl::read_data(data)?.to_dyn()),
DataComponent::SulfurCubeContent => Some(SulfurCubeContentImpl::read_data(data)?.to_dyn()),
DataComponent::BreakSound => Some(BreakSoundImpl::read_data(data)?.to_dyn()),
DataComponent::AttributeModifiers => {
Some(AttributeModifiersImpl::read_data(data)?.to_dyn())
}
} }
} }

View File

@ -6,6 +6,11 @@ use pumpkin_nbt::tag::NbtTag;
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct DyeImpl; pub struct DyeImpl;
impl DyeImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for DyeImpl { impl DataComponentImpl for DyeImpl {
default_impl!(Dye); default_impl!(Dye);
} }
@ -31,6 +36,11 @@ impl DataComponentImpl for DyedColorImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct MapColorImpl; pub struct MapColorImpl;
impl MapColorImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for MapColorImpl { impl DataComponentImpl for MapColorImpl {
default_impl!(MapColor); default_impl!(MapColor);
} }
@ -56,6 +66,11 @@ impl DataComponentImpl for MapIdImpl {
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct MapDecorationsImpl; pub struct MapDecorationsImpl;
impl MapDecorationsImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for MapDecorationsImpl { impl DataComponentImpl for MapDecorationsImpl {
default_impl!(MapDecorations); default_impl!(MapDecorations);
} }
@ -590,12 +605,28 @@ impl DataComponentImpl for ProfileImpl {
pub struct JukeboxPlayableImpl { pub struct JukeboxPlayableImpl {
pub song: &'static str, pub song: &'static str,
} }
impl JukeboxPlayableImpl {
pub fn read_data(data: &NbtTag) -> Option<Self> {
let compound = data.extract_compound()?;
let song = compound.get_string("song")?;
let static_song = crate::jukebox_song::JukeboxSong::from_name(
song.strip_prefix("minecraft:").unwrap_or(song),
)
.map_or("", |s| s.to_name());
Some(Self { song: static_song })
}
}
impl DataComponentImpl for JukeboxPlayableImpl { impl DataComponentImpl for JukeboxPlayableImpl {
default_impl!(JukeboxPlayable); default_impl!(JukeboxPlayable);
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct RecipesImpl; pub struct RecipesImpl;
impl RecipesImpl {
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
Some(Self)
}
}
impl DataComponentImpl for RecipesImpl { impl DataComponentImpl for RecipesImpl {
default_impl!(Recipes); default_impl!(Recipes);
} }

File diff suppressed because it is too large Load Diff

View File

@ -49,8 +49,17 @@ impl GameProfileResult {
/// ///
/// Instead, call this method *before* using `write()`/`read()` on a lock. /// Instead, call this method *before* using `write()`/`read()` on a lock.
pub fn resolve(&self, source: &CommandSource) -> Result<Vec<GameProfile>, CommandSyntaxError> { pub fn resolve(&self, source: &CommandSource) -> Result<Vec<GameProfile>, CommandSyntaxError> {
let players = match self { match self {
Self::Selector(selector) => selector.find_players(source), Self::Selector(selector) => {
let players = selector.find_players(source)?;
if players.is_empty() {
return Err(
crate::command::argument_types::entity::NO_PLAYERS_ERROR_TYPE
.create_without_context(),
);
}
Ok(players.iter().map(|p| p.gameprofile.clone()).collect())
}
Self::Name(name) => { Self::Name(name) => {
let server = source.server(); let server = source.server();
if let Some(player) = server.get_player_by_name(name) { if let Some(player) = server.get_player_by_name(name) {
@ -72,7 +81,7 @@ impl GameProfileResult {
} }
if server.advanced_config.networking.java.online_mode { if server.advanced_config.networking.java.online_mode {
return match lookup_profile_by_name_blocking( match lookup_profile_by_name_blocking(
name, name,
&server.advanced_config.networking.java.authentication, &server.advanced_config.networking.java.authentication,
) { ) {
@ -86,7 +95,7 @@ impl GameProfileResult {
Ok(vec![Self::profile_from_uuid_name(uuid, resolved_name)]) Ok(vec![Self::profile_from_uuid_name(uuid, resolved_name)])
} }
_ => Err(Self::unknown_player_syntax_error()), _ => Err(Self::unknown_player_syntax_error()),
}; }
} else if let Ok(uuid) = offline_uuid(name) { } else if let Ok(uuid) = offline_uuid(name) {
let profile = Self::profile_from_uuid_name(uuid, name.clone()); let profile = Self::profile_from_uuid_name(uuid, name.clone());
server server
@ -95,10 +104,10 @@ impl GameProfileResult {
.write() .write()
.unwrap_or_else(std::sync::PoisonError::into_inner) .unwrap_or_else(std::sync::PoisonError::into_inner)
.upsert(profile.id, profile.name.clone()); .upsert(profile.id, profile.name.clone());
return Ok(vec![profile]); Ok(vec![profile])
} else {
Err(Self::unknown_player_syntax_error())
} }
return Err(Self::unknown_player_syntax_error());
} }
Self::Uuid(uuid) => { Self::Uuid(uuid) => {
let server = source.server(); let server = source.server();
@ -121,11 +130,9 @@ impl GameProfileResult {
return Ok(vec![profile]); return Ok(vec![profile]);
} }
return Err(Self::unknown_player_syntax_error()); Err(Self::unknown_player_syntax_error())
}
} }
}?;
Ok(players.iter().map(|p| &p.gameprofile).cloned().collect())
} }
fn resolve_known_profile_by_name(server: &Server, name: &str) -> Option<GameProfile> { fn resolve_known_profile_by_name(server: &Server, name: &str) -> Option<GameProfile> {

View File

@ -28,7 +28,6 @@ const ARG_TARGETS: &str = "targets";
const ARG_ADVANCEMENT: &str = "advancement"; const ARG_ADVANCEMENT: &str = "advancement";
const ARG_CRITERION: &str = "criterion"; const ARG_CRITERION: &str = "criterion";
#[allow(unused)]
const ERROR_CRITERION_NOT_FOUND: CommandErrorType<2> = CommandErrorType::new( const ERROR_CRITERION_NOT_FOUND: CommandErrorType<2> = CommandErrorType::new(
translation::java::COMMANDS_ADVANCEMENT_CRITERIONNOTFOUND, translation::java::COMMANDS_ADVANCEMENT_CRITERIONNOTFOUND,
translation::java::COMMANDS_ADVANCEMENT_CRITERIONNOTFOUND, translation::java::COMMANDS_ADVANCEMENT_CRITERIONNOTFOUND,
@ -112,7 +111,7 @@ impl Action {
true true
} }
Self::Revoke => { Self::Revoke => {
if !progress.is_done() { if !progress.has_progress() {
return false; return false;
} }
let criteria: Vec<Arc<str>> = progress.get_completed_criteria().collect(); let criteria: Vec<Arc<str>> = progress.get_completed_criteria().collect();
@ -306,11 +305,16 @@ fn perform(
advancements: &[&'static Advancement], advancements: &[&'static Advancement],
show_advancement: bool, show_advancement: bool,
) -> Result<i32, CommandSyntaxError> { ) -> Result<i32, CommandSyntaxError> {
let mut i = 0; let mut advancement_count = 0;
let mut player_count = 0;
for player in targets { for player in targets {
i += action.perform(player, advancements, show_advancement); let changed = action.perform(player, advancements, show_advancement);
if changed > 0 {
player_count += 1;
} }
if i == 0 { advancement_count += changed;
}
if advancement_count == 0 {
return if let [first_advancement] = advancements[..] { return if let [first_advancement] = advancements[..] {
if let [first_player] = targets { if let [first_player] = targets {
Err(match action { Err(match action {
@ -362,7 +366,7 @@ fn perform(
format!("{}.one.to.many.success", action.get_key()), format!("{}.one.to.many.success", action.get_key()),
[ [
first_advancement.name(), first_advancement.name(),
TextComponent::text(targets.len().to_string()), TextComponent::text(player_count.to_string()),
], ],
) )
} }
@ -370,7 +374,7 @@ fn perform(
TextComponent::translate( TextComponent::translate(
format!("{}.many.to.one.success", action.get_key()), format!("{}.many.to.one.success", action.get_key()),
[ [
TextComponent::text(advancements.len().to_string()), TextComponent::text(advancement_count.to_string()),
first.get_display_name(), first.get_display_name(),
], ],
) )
@ -378,13 +382,13 @@ fn perform(
TextComponent::translate( TextComponent::translate(
format!("{}.many.to.many.success", action.get_key()), format!("{}.many.to.many.success", action.get_key()),
[ [
TextComponent::text(advancements.len().to_string()), TextComponent::text(advancement_count.to_string()),
TextComponent::text(targets.len().to_string()), TextComponent::text(player_count.to_string()),
], ],
) )
}; };
context.send_feedback(translate, true); context.send_feedback(translate, true);
Ok(i) Ok(advancement_count)
} }
/// Performs an action (grant or revoke) on a specific advancement criterion for multiple players. /// Performs an action (grant or revoke) on a specific advancement criterion for multiple players.
@ -414,12 +418,21 @@ pub fn perform_criterion(
advancement: &'static Advancement, advancement: &'static Advancement,
criterion: &str, criterion: &str,
) -> Result<i32, CommandSyntaxError> { ) -> Result<i32, CommandSyntaxError> {
if advancement.criteria.contains(&criterion) { if !advancement.criteria.contains(&criterion) {
return Err(
ERROR_CRITERION_NOT_FOUND.create_without_context_args_slice(&[
advancement.name(),
TextComponent::text(criterion.to_owned()),
]),
);
}
let count = targets let count = targets
.iter() .iter()
.map(|player| action.perform_criterion(player, advancement, criterion)) .map(|player| action.perform_criterion(player, advancement, criterion))
.filter(|&success| success) .filter(|&success| success)
.count() as i32; .count() as i32;
if count == 0 { if count == 0 {
if let [first_player] = targets { if let [first_player] = targets {
Err(match action { Err(match action {
@ -458,21 +471,13 @@ pub fn perform_criterion(
[ [
TextComponent::text(criterion.to_owned()), TextComponent::text(criterion.to_owned()),
advancement.name(), advancement.name(),
TextComponent::text(targets.len().to_string()), TextComponent::text(count.to_string()),
], ],
) )
}; };
context.send_feedback(translate, true); context.send_feedback(translate, true);
Ok(count) Ok(count)
} }
} else {
Err(
ERROR_CRITERION_NOT_FOUND.create_without_context_args_slice(&[
advancement.name(),
TextComponent::text(criterion.to_owned()),
]),
)
}
} }
/// use for when a criterion is specified to on grant/revoke this criterion /// use for when a criterion is specified to on grant/revoke this criterion
@ -537,17 +542,19 @@ impl SuggestionProvider for CriterionSuggestionProvider {
/// executor to grant/revoke every advancement to specified players /// executor to grant/revoke every advancement to specified players
struct EveryAdvancementExecutor { struct EveryAdvancementExecutor {
action: Action, action: Action,
show_advancement: bool,
} }
impl CommandExecutor for EveryAdvancementExecutor { impl CommandExecutor for EveryAdvancementExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let action = self.action; let action = self.action;
let show_advancement = self.show_advancement;
perform( perform(
&context.source, &context.source,
&EntityArgumentType::get_players(context, ARG_TARGETS)?, &EntityArgumentType::get_players(context, ARG_TARGETS)?,
action, action,
&Advancement::get_advancements_list(), &Advancement::get_advancements_list(),
false, show_advancement,
) )
} }
} }
@ -607,10 +614,10 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
}), }),
), ),
) )
.then( .then(literal("everything").executes(EveryAdvancementExecutor {
literal("everything") action: $action,
.executes(EveryAdvancementExecutor { action: $action }), show_advancement: matches!($action, Action::Revoke),
), })),
) )
}; };
} }

View File

@ -80,7 +80,7 @@ struct BanExecutor {
impl CommandExecutor for BanExecutor { impl CommandExecutor for BanExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let targets = GameProfileArgumentType::get(context, "player")?; let targets = GameProfileArgumentType::get(context, "targets")?;
let reason = if self.has_reason { let reason = if self.has_reason {
Some(StringArgumentType::get(context, "reason")?.to_string()) Some(StringArgumentType::get(context, "reason")?.to_string())
} else { } else {
@ -111,7 +111,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
dispatcher.register( dispatcher.register(
command("ban", DESCRIPTION).requires(PERMISSION).then( command("ban", DESCRIPTION).requires(PERMISSION).then(
argument("player", GameProfileArgumentType) argument("targets", GameProfileArgumentType)
.executes(BanExecutor { has_reason: false }) .executes(BanExecutor { has_reason: false })
.then( .then(
argument("reason", StringArgumentType::GreedyPhrase) argument("reason", StringArgumentType::GreedyPhrase)

View File

@ -14,6 +14,7 @@ use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult}; use crate::command::node::{CommandExecutor, CommandExecutorResult};
use crate::data::SaveJSONConfiguration; use crate::data::SaveJSONConfiguration;
use crate::data::banlist_serializer::BannedIpEntry; use crate::data::banlist_serializer::BannedIpEntry;
use crate::entity::EntityBase;
use crate::net::DisconnectReason; use crate::net::DisconnectReason;
use crate::server::Server; use crate::server::Server;
@ -80,6 +81,26 @@ fn ban_ip(context: &CommandContext, target: &str, reason: Option<String>) -> Com
.cloned() .cloned()
.collect(); .collect();
let kick_count = players_to_kick.len();
if !players_to_kick.is_empty() {
let player_names = players_to_kick
.iter()
.map(|p| p.get_display_name())
.reduce(|acc, name| {
TextComponent::text(format!("{}, {}", acc.get_text(), name.get_text()))
})
.unwrap_or_else(TextComponent::empty);
context.source.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_BANIP_INFO,
translation::java::COMMANDS_BANIP_INFO,
[TextComponent::text(kick_count.to_string()), player_names],
),
true,
);
}
for player in players_to_kick { for player in players_to_kick {
let kick_msg = TextComponent::translate_cross( let kick_msg = TextComponent::translate_cross(
translation::java::MULTIPLAYER_DISCONNECT_IP_BANNED, translation::java::MULTIPLAYER_DISCONNECT_IP_BANNED,
@ -89,7 +110,7 @@ fn ban_ip(context: &CommandContext, target: &str, reason: Option<String>) -> Com
player.kick(DisconnectReason::Kicked, &kick_msg); player.kick(DisconnectReason::Kicked, &kick_msg);
} }
Ok(1) Ok(kick_count as i32)
} }
struct BanIpExecutor { struct BanIpExecutor {
@ -98,7 +119,7 @@ struct BanIpExecutor {
impl CommandExecutor for BanIpExecutor { impl CommandExecutor for BanIpExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let target = StringArgumentType::get(context, "ip")?; let target = StringArgumentType::get(context, "target")?;
let reason = if self.has_reason { let reason = if self.has_reason {
Some(StringArgumentType::get(context, "reason")?.to_string()) Some(StringArgumentType::get(context, "reason")?.to_string())
} else { } else {
@ -117,7 +138,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
)); ));
let cmd = command("ban-ip", DESCRIPTION).requires(PERMISSION).then( let cmd = command("ban-ip", DESCRIPTION).requires(PERMISSION).then(
argument("ip", StringArgumentType::SingleWord) argument("target", StringArgumentType::SingleWord)
.executes(BanIpExecutor { has_reason: false }) .executes(BanIpExecutor { has_reason: false })
.then( .then(
argument("reason", StringArgumentType::GreedyPhrase) argument("reason", StringArgumentType::GreedyPhrase)

View File

@ -278,7 +278,7 @@ impl CommandExecutor for GetExecutor {
TextComponent::text(bossbar.max.to_string()), TextComponent::text(bossbar.max.to_string()),
], ],
), ),
false, true,
); );
Ok(bossbar.max) Ok(bossbar.max)
} }
@ -298,7 +298,7 @@ impl CommandExecutor for GetExecutor {
translation::bedrock::COMMANDS_BOSSBAR_GET_PLAYERS_NONE, translation::bedrock::COMMANDS_BOSSBAR_GET_PLAYERS_NONE,
[bossbar_prefix(bossbar.bossbar_data.title, namespace)], [bossbar_prefix(bossbar.bossbar_data.title, namespace)],
), ),
false, true,
); );
} else { } else {
context.source.send_feedback( context.source.send_feedback(
@ -315,7 +315,7 @@ impl CommandExecutor for GetExecutor {
TextComponent::text(online_players.join(", ")), TextComponent::text(online_players.join(", ")),
], ],
), ),
false, true,
); );
} }
Ok(count) Ok(count)
@ -330,7 +330,7 @@ impl CommandExecutor for GetExecutor {
TextComponent::text(bossbar.value.to_string()), TextComponent::text(bossbar.value.to_string()),
], ],
), ),
false, true,
); );
Ok(bossbar.value) Ok(bossbar.value)
} }
@ -355,7 +355,7 @@ impl CommandExecutor for GetExecutor {
namespace, namespace,
)], )],
), ),
false, true,
); );
Ok(bossbar.visible as i32) Ok(bossbar.visible as i32)
} }
@ -509,7 +509,7 @@ impl CommandExecutor for SetExecutor {
true, true,
); );
Ok(0) Ok(max_value)
} }
CommandValueSet::Name => { CommandValueSet::Name => {
let name = ComponentArgumentType::get(context, "name")?; let name = ComponentArgumentType::get(context, "name")?;

View File

@ -85,7 +85,7 @@ impl CommandExecutor for DamageEntityExecutor {
let source = match self.0 { let source = match self.0 {
EntityMode::WithSource | EntityMode::WithSourceAndCause => { EntityMode::WithSource | EntityMode::WithSourceAndCause => {
Some(EntityArgumentType::get_entity(context, "source")?) Some(EntityArgumentType::get_entity(context, "entity")?)
} }
EntityMode::Basic | EntityMode::WithType => None, EntityMode::Basic | EntityMode::WithType => None,
}; };
@ -133,7 +133,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
) )
.then( .then(
literal("by").then( literal("by").then(
argument("source", EntityArgumentType::Entity) argument("entity", EntityArgumentType::Entity)
.executes(DamageEntityExecutor(EntityMode::WithSource)) .executes(DamageEntityExecutor(EntityMode::WithSource))
.then(literal("from").then( .then(literal("from").then(
argument("cause", EntityArgumentType::Entity).executes( argument("cause", EntityArgumentType::Entity).executes(

View File

@ -8,6 +8,8 @@ use crate::command::context::command_context::CommandContext;
use crate::command::errors::error_types::CommandErrorType; use crate::command::errors::error_types::CommandErrorType;
use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult}; use crate::command::node::{CommandExecutor, CommandExecutorResult};
use crate::command::suggestion::provider::{SuggestionProvider, SuggestionProviderResult};
use crate::command::suggestion::suggestions::SuggestionsBuilder;
use crate::data::SaveJSONConfiguration; use crate::data::SaveJSONConfiguration;
const DESCRIPTION: &str = "Revokes operator status from a player."; const DESCRIPTION: &str = "Revokes operator status from a player.";
@ -52,18 +54,35 @@ impl CommandExecutor for DeopExecutor {
} }
} }
if succeeded_deops > 0 {
config.save();
}
if succeeded_deops == 0 { if succeeded_deops == 0 {
Err(ERROR_DEOP_FAILED.create_without_context()) Err(ERROR_DEOP_FAILED.create_without_context())
} else { } else {
config.save();
drop(config);
crate::command::commands::whitelist::kick_non_whitelisted_players(server);
Ok(succeeded_deops) Ok(succeeded_deops)
} }
} }
} }
struct DeopSuggestionProvider;
impl SuggestionProvider for DeopSuggestionProvider {
fn suggest(
&self,
context: &CommandContext,
mut builder: SuggestionsBuilder,
) -> SuggestionProviderResult {
let ops = context.server().data.operator_config.read().unwrap();
for op in &ops.ops {
builder = builder.suggest(op.name.clone());
}
builder.build()
}
}
pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistry) { pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistry) {
registry.register_permission_or_panic(Permission::new( registry.register_permission_or_panic(Permission::new(
PERMISSION, PERMISSION,
@ -72,8 +91,10 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
)); ));
dispatcher.register( dispatcher.register(
command("deop", DESCRIPTION) command("deop", DESCRIPTION).requires(PERMISSION).then(
.requires(PERMISSION) argument("targets", GameProfileArgumentType)
.then(argument("targets", GameProfileArgumentType).executes(DeopExecutor)), .suggests(DeopSuggestionProvider)
.executes(DeopExecutor),
),
); );
} }

View File

@ -134,7 +134,7 @@ impl ExperienceExecutor {
target.add_experience_points(amount); target.add_experience_points(amount);
} else { } else {
let current_lvl = target.experience_level.load(Ordering::Relaxed); let current_lvl = target.experience_level.load(Ordering::Relaxed);
if amount > experience::points_in_level(current_lvl) { if amount >= experience::points_in_level(current_lvl) {
return false; return false;
} }
target.set_experience_points(amount); target.set_experience_points(amount);
@ -147,7 +147,7 @@ impl ExperienceExecutor {
impl CommandExecutor for ExperienceExecutor { impl CommandExecutor for ExperienceExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let targets = EntityArgumentType::get_players(context, "targets")?; let targets = EntityArgumentType::get_players(context, "target")?;
if self.mode == Mode::Query { if self.mode == Mode::Query {
let target = targets[0].clone(); let target = targets[0].clone();
@ -163,7 +163,7 @@ impl CommandExecutor for ExperienceExecutor {
} }
} }
if successes == 0 { if self.mode == Mode::Set && successes == 0 {
return Err(ERROR_SET_POINTS_INVALID.create_without_context()); return Err(ERROR_SET_POINTS_INVALID.create_without_context());
} }
@ -171,7 +171,7 @@ impl CommandExecutor for ExperienceExecutor {
let msg = self.get_success_message(amount, &targets, first_name); let msg = self.get_success_message(amount, &targets, first_name);
context.source.send_feedback(msg, true); context.source.send_feedback(msg, true);
Ok(successes) Ok(targets.len() as i32)
} }
} }
@ -183,50 +183,50 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
)); ));
let add_node = literal("add").then( let add_node = literal("add").then(
argument("targets", EntityArgumentType::Players).then( argument("target", EntityArgumentType::Players).then(
argument("amount", IntegerArgumentType::with_min(0)) argument("amount", IntegerArgumentType::any())
.executes(ExperienceExecutor { .executes(ExperienceExecutor {
mode: Mode::Add, mode: Mode::Add,
exp_type: ExpType::Points, exp_type: ExpType::Points,
}) })
.then(literal("levels").executes(ExperienceExecutor {
mode: Mode::Add,
exp_type: ExpType::Levels,
}))
.then(literal("points").executes(ExperienceExecutor { .then(literal("points").executes(ExperienceExecutor {
mode: Mode::Add, mode: Mode::Add,
exp_type: ExpType::Points, exp_type: ExpType::Points,
}))
.then(literal("levels").executes(ExperienceExecutor {
mode: Mode::Add,
exp_type: ExpType::Levels,
})), })),
), ),
); );
let set_node = literal("set").then( let set_node = literal("set").then(
argument("targets", EntityArgumentType::Players).then( argument("target", EntityArgumentType::Players).then(
argument("amount", IntegerArgumentType::with_min(0)) argument("amount", IntegerArgumentType::with_min(0))
.executes(ExperienceExecutor { .executes(ExperienceExecutor {
mode: Mode::Set, mode: Mode::Set,
exp_type: ExpType::Points, exp_type: ExpType::Points,
}) })
.then(literal("levels").executes(ExperienceExecutor {
mode: Mode::Set,
exp_type: ExpType::Levels,
}))
.then(literal("points").executes(ExperienceExecutor { .then(literal("points").executes(ExperienceExecutor {
mode: Mode::Set, mode: Mode::Set,
exp_type: ExpType::Points, exp_type: ExpType::Points,
}))
.then(literal("levels").executes(ExperienceExecutor {
mode: Mode::Set,
exp_type: ExpType::Levels,
})), })),
), ),
); );
let query_node = literal("query").then( let query_node = literal("query").then(
argument("targets", EntityArgumentType::Player) argument("target", EntityArgumentType::Player)
.then(literal("levels").executes(ExperienceExecutor {
mode: Mode::Query,
exp_type: ExpType::Levels,
}))
.then(literal("points").executes(ExperienceExecutor { .then(literal("points").executes(ExperienceExecutor {
mode: Mode::Query, mode: Mode::Query,
exp_type: ExpType::Points, exp_type: ExpType::Points,
}))
.then(literal("levels").executes(ExperienceExecutor {
mode: Mode::Query,
exp_type: ExpType::Levels,
})), })),
); );

View File

@ -1,4 +1,4 @@
use crate::command::argument_builder::{ArgumentBuilder, argument, command}; use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
use crate::command::argument_types::coordinates::block_pos::{ use crate::command::argument_types::coordinates::block_pos::{
BlockPosArgumentType, OUT_OF_BOUNDS_ERROR_TYPE, BlockPosArgumentType, OUT_OF_BOUNDS_ERROR_TYPE,
}; };
@ -67,7 +67,7 @@ impl CommandExecutor for FillBiomeExecutor {
let target_biome = ResourceKeyArgument::get_biome(context, "biome")?; let target_biome = ResourceKeyArgument::get_biome(context, "biome")?;
let replace_biome = if self.has_replace { let replace_biome = if self.has_replace {
Some(ResourceKeyArgument::get_biome(context, "replace_biome")?) Some(ResourceKeyArgument::get_biome(context, "filter")?)
} else { } else {
None None
}; };
@ -126,11 +126,19 @@ impl CommandExecutor for FillBiomeExecutor {
let msg = TextComponent::translate_cross( let msg = TextComponent::translate_cross(
translation::java::COMMANDS_FILLBIOME_SUCCESS_COUNT, translation::java::COMMANDS_FILLBIOME_SUCCESS_COUNT,
translation::java::COMMANDS_FILLBIOME_SUCCESS_COUNT, translation::java::COMMANDS_FILLBIOME_SUCCESS_COUNT,
[TextComponent::text(changed_count.to_string())], [
TextComponent::text(changed_count.to_string()),
TextComponent::text(min_x.to_string()),
TextComponent::text(min_y.to_string()),
TextComponent::text(min_z.to_string()),
TextComponent::text(max_x.to_string()),
TextComponent::text(max_y.to_string()),
TextComponent::text(max_z.to_string()),
],
); );
context.source.send_feedback(msg, true); context.source.send_feedback(msg, true);
Ok(1) Ok(changed_count)
} }
} }
@ -147,11 +155,13 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
argument("biome", ResourceKeyArgument(BIOME_REGISTRY)) argument("biome", ResourceKeyArgument(BIOME_REGISTRY))
.executes(FillBiomeExecutor { has_replace: false }) .executes(FillBiomeExecutor { has_replace: false })
.then( .then(
argument("replace_biome", ResourceKeyArgument(BIOME_REGISTRY)) literal("replace").then(
argument("filter", ResourceKeyArgument(BIOME_REGISTRY))
.executes(FillBiomeExecutor { has_replace: true }), .executes(FillBiomeExecutor { has_replace: true }),
), ),
), ),
), ),
),
); );
dispatcher.register(builder); dispatcher.register(builder);

View File

@ -10,7 +10,6 @@ use pumpkin_util::PermissionLvl;
use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry}; use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::text::color::{Color, NamedColor};
const DESCRIPTION: &str = "Constantly load chunks in the world."; const DESCRIPTION: &str = "Constantly load chunks in the world.";
const PERMISSION: &str = "minecraft:command.forceload"; const PERMISSION: &str = "minecraft:command.forceload";
@ -86,13 +85,15 @@ impl CommandExecutor for ForceloadAddExecutor {
world.update_active_chunks(); world.update_active_chunks();
let dimension_name = world.dimension.minecraft_name.to_string();
let text = if total_chunks == 1 { let text = if total_chunks == 1 {
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_FORCELOAD_ADDED_SINGLE, translation::java::COMMANDS_FORCELOAD_ADDED_SINGLE,
translation::java::COMMANDS_FORCELOAD_ADDED_SINGLE, translation::java::COMMANDS_FORCELOAD_ADDED_SINGLE,
[ [
TextComponent::text(min_x.to_string()), TextComponent::text(format!("[{min_x}, {min_z}]")),
TextComponent::text(min_z.to_string()), TextComponent::text(dimension_name),
], ],
) )
} else { } else {
@ -101,16 +102,13 @@ impl CommandExecutor for ForceloadAddExecutor {
translation::java::COMMANDS_FORCELOAD_ADDED_MULTIPLE, translation::java::COMMANDS_FORCELOAD_ADDED_MULTIPLE,
[ [
TextComponent::text(total_chunks.to_string()), TextComponent::text(total_chunks.to_string()),
TextComponent::text(min_x.to_string()), TextComponent::text(dimension_name),
TextComponent::text(min_z.to_string()), TextComponent::text(format!("[{min_x}, {min_z}]")),
TextComponent::text(max_x.to_string()), TextComponent::text(format!("[{max_x}, {max_z}]")),
TextComponent::text(max_z.to_string()),
], ],
) )
}; };
context context.source.send_feedback(text, true);
.source
.send_feedback(text.color(Color::Named(NamedColor::Green)), false);
Ok(total_chunks) Ok(total_chunks)
} }
@ -170,13 +168,15 @@ impl CommandExecutor for ForceloadRemoveExecutor {
world.update_active_chunks(); world.update_active_chunks();
let dimension_name = world.dimension.minecraft_name.to_string();
let text = if total_chunks == 1 { let text = if total_chunks == 1 {
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_FORCELOAD_REMOVED_SINGLE, translation::java::COMMANDS_FORCELOAD_REMOVED_SINGLE,
translation::java::COMMANDS_FORCELOAD_REMOVED_SINGLE, translation::java::COMMANDS_FORCELOAD_REMOVED_SINGLE,
[ [
TextComponent::text(min_x.to_string()), TextComponent::text(format!("[{min_x}, {min_z}]")),
TextComponent::text(min_z.to_string()), TextComponent::text(dimension_name),
], ],
) )
} else { } else {
@ -185,16 +185,13 @@ impl CommandExecutor for ForceloadRemoveExecutor {
translation::java::COMMANDS_FORCELOAD_REMOVED_MULTIPLE, translation::java::COMMANDS_FORCELOAD_REMOVED_MULTIPLE,
[ [
TextComponent::text(total_chunks.to_string()), TextComponent::text(total_chunks.to_string()),
TextComponent::text(min_x.to_string()), TextComponent::text(dimension_name),
TextComponent::text(min_z.to_string()), TextComponent::text(format!("[{min_x}, {min_z}]")),
TextComponent::text(max_x.to_string()), TextComponent::text(format!("[{max_x}, {max_z}]")),
TextComponent::text(max_z.to_string()),
], ],
) )
}; };
context context.source.send_feedback(text, true);
.source
.send_feedback(text.color(Color::Named(NamedColor::Green)), false);
Ok(total_chunks) Ok(total_chunks)
} }
@ -222,14 +219,14 @@ impl CommandExecutor for ForceloadRemoveAllExecutor {
world.update_active_chunks(); world.update_active_chunks();
let dimension_name = world.dimension.minecraft_name.to_string();
let text = TextComponent::translate_cross( let text = TextComponent::translate_cross(
translation::java::COMMANDS_FORCELOAD_REMOVED_ALL, translation::java::COMMANDS_FORCELOAD_REMOVED_ALL,
translation::java::COMMANDS_FORCELOAD_REMOVED_ALL, translation::java::COMMANDS_FORCELOAD_REMOVED_ALL,
[], [TextComponent::text(dimension_name)],
); );
context context.source.send_feedback(text, true);
.source
.send_feedback(text.color(Color::Named(NamedColor::Green)), false);
Ok(removed_count as i32) Ok(removed_count as i32)
} }
@ -262,30 +259,28 @@ impl CommandExecutor for ForceloadQueryExecutor {
forced.contains(&chunk_pos) forced.contains(&chunk_pos)
}; };
let dimension_name = world.dimension.minecraft_name.to_string();
if is_forced { if is_forced {
let text = TextComponent::translate_cross( let text = TextComponent::translate_cross(
translation::java::COMMANDS_FORCELOAD_QUERY_SUCCESS, translation::java::COMMANDS_FORCELOAD_QUERY_SUCCESS,
translation::java::COMMANDS_FORCELOAD_QUERY_SUCCESS, translation::java::COMMANDS_FORCELOAD_QUERY_SUCCESS,
[ [
TextComponent::text(chunk_pos.x.to_string()), TextComponent::text(format!("[{}, {}]", chunk_pos.x, chunk_pos.y)),
TextComponent::text(chunk_pos.y.to_string()), TextComponent::text(dimension_name),
], ],
); );
context context.source.send_feedback(text, false);
.source
.send_feedback(text.color(Color::Named(NamedColor::Green)), false);
} else { } else {
let text = TextComponent::translate_cross( let text = TextComponent::translate_cross(
translation::java::COMMANDS_FORCELOAD_QUERY_FAILURE, translation::java::COMMANDS_FORCELOAD_QUERY_FAILURE,
translation::java::COMMANDS_FORCELOAD_QUERY_FAILURE, translation::java::COMMANDS_FORCELOAD_QUERY_FAILURE,
[ [
TextComponent::text(chunk_pos.x.to_string()), TextComponent::text(format!("[{}, {}]", chunk_pos.x, chunk_pos.y)),
TextComponent::text(chunk_pos.y.to_string()), TextComponent::text(dimension_name),
], ],
); );
context context.source.send_error(text);
.source
.send_feedback(text.color(Color::Named(NamedColor::Red)), false);
} }
let all_forced = { let all_forced = {
@ -303,29 +298,34 @@ impl CommandExecutor for ForceloadQueryExecutor {
let text = TextComponent::translate_cross( let text = TextComponent::translate_cross(
translation::java::COMMANDS_FORCELOAD_ADDED_NONE, translation::java::COMMANDS_FORCELOAD_ADDED_NONE,
translation::java::COMMANDS_FORCELOAD_ADDED_NONE, translation::java::COMMANDS_FORCELOAD_ADDED_NONE,
[], [TextComponent::text(
world.dimension.minecraft_name.to_string(),
)],
); );
context context.source.send_error(text);
.source
.send_feedback(text.color(Color::Named(NamedColor::Gray)), false);
} else { } else {
let list_str = all_forced.join(", "); let list_str = all_forced.join(", ");
let text = if all_forced.len() == 1 { let text = if all_forced.len() == 1 {
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_FORCELOAD_LIST_SINGLE, translation::java::COMMANDS_FORCELOAD_LIST_SINGLE,
translation::java::COMMANDS_FORCELOAD_LIST_SINGLE, translation::java::COMMANDS_FORCELOAD_LIST_SINGLE,
[TextComponent::text(list_str)], [
TextComponent::text(world.dimension.minecraft_name.to_string()),
TextComponent::text(list_str),
],
) )
} else { } else {
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_FORCELOAD_LIST_MULTIPLE, translation::java::COMMANDS_FORCELOAD_LIST_MULTIPLE,
translation::java::COMMANDS_FORCELOAD_LIST_MULTIPLE, translation::java::COMMANDS_FORCELOAD_LIST_MULTIPLE,
[TextComponent::text(list_str)], [
TextComponent::text(all_forced.len().to_string()),
TextComponent::text(world.dimension.minecraft_name.to_string()),
TextComponent::text(list_str),
],
) )
}; };
context context.source.send_feedback(text, false);
.source
.send_feedback(text.color(Color::Named(NamedColor::Gray)), false);
} }
Ok(i32::from(is_forced)) Ok(i32::from(is_forced))

View File

@ -290,21 +290,15 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Two),
)); ));
let block_node = literal("block").then( let block_node = literal("block")
argument("pos", BlockPosArgumentType).then( .then(argument("pos", BlockPosArgumentType).then(
argument("slot", SlotArgumentType) argument("slot", SlotArgumentType).then(literal("with").then(block_item_node())),
.then(block_item_node()) ));
.then(literal("with").then(block_item_node())),
),
);
let entity_node = literal("entity").then( let entity_node =
argument("targets", EntityArgumentType::Entities).then( literal("entity").then(argument("targets", EntityArgumentType::Entities).then(
argument("slot", SlotArgumentType) argument("slot", SlotArgumentType).then(literal("with").then(entity_item_node())),
.then(entity_item_node()) ));
.then(literal("with").then(entity_item_node())),
),
);
dispatcher.register( dispatcher.register(
command("item", DESCRIPTION) command("item", DESCRIPTION)

View File

@ -69,7 +69,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
registry.register_permission_or_panic(Permission::new( registry.register_permission_or_panic(Permission::new(
PERMISSION, PERMISSION,
DESCRIPTION, DESCRIPTION,
PermissionDefault::Op(PermissionLvl::Four), PermissionDefault::Op(PermissionLvl::Two),
)); ));
dispatcher.register( dispatcher.register(

View File

@ -31,7 +31,7 @@ struct PardonIpExecutor;
impl CommandExecutor for PardonIpExecutor { impl CommandExecutor for PardonIpExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let target = StringArgumentType::get(context, "ip")?; let target = StringArgumentType::get(context, "target")?;
let ip = IpAddr::from_str(target) let ip = IpAddr::from_str(target)
.map_err(|_| ERROR_PARDONIP_INVALID.create_without_context())?; .map_err(|_| ERROR_PARDONIP_INVALID.create_without_context())?;
@ -77,7 +77,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
let cmd = command("pardon-ip", DESCRIPTION) let cmd = command("pardon-ip", DESCRIPTION)
.requires(PERMISSION) .requires(PERMISSION)
.then(argument("ip", StringArgumentType::SingleWord).executes(PardonIpExecutor)); .then(argument("target", StringArgumentType::SingleWord).executes(PardonIpExecutor));
dispatcher.register_with_aliases(cmd, &["pardonip"]); dispatcher.register_with_aliases(cmd, &["pardonip"]);
} }

View File

@ -357,7 +357,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
registry.register_permission_or_panic(Permission::new( registry.register_permission_or_panic(Permission::new(
PERMISSION, PERMISSION,
DESCRIPTION, DESCRIPTION,
PermissionDefault::Op(PermissionLvl::Two), PermissionDefault::Op(PermissionLvl::Three),
)); ));
dispatcher.register( dispatcher.register(

View File

@ -1,22 +1,31 @@
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::PermissionLvl;
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry}; use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal}; use crate::command::argument_builder::{
ArgumentBuilder, LiteralArgumentBuilder, argument, command, literal,
};
use crate::command::argument_types::core::bool::BoolArgumentType;
use crate::command::argument_types::core::integer::IntegerArgumentType;
use crate::command::argument_types::identifier::IdentifierArgumentType;
use crate::command::argument_types::range::IntRangeArgumentType; use crate::command::argument_types::range::IntRangeArgumentType;
use crate::command::context::command_context::CommandContext; use crate::command::context::command_context::CommandContext;
use crate::command::errors::error_types::CommandErrorType; use crate::command::errors::error_types::CommandErrorType;
use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult}; use crate::command::node::{CommandExecutor, CommandExecutorResult};
use crate::command::suggestion::provider::{SuggestionProvider, SuggestionProviderResult};
use crate::command::suggestion::suggestions::SuggestionsBuilder;
const DESCRIPTION: &str = "Draws a random value."; const DESCRIPTION: &str = "Draws a random value.";
const PERMISSION: &str = "minecraft:command.random"; const PERMISSION: &str = "minecraft:command.random";
const PERMISSION_RESET: &str = "minecraft:command.random.reset";
const ARG_RANGE: &str = "range"; const ARG_RANGE: &str = "range";
const ARG_SEQUENCE: &str = "sequence";
/// The largest allowed range span, matching Vanilla (`i32::MAX - 1`). const ARG_SEED: &str = "seed";
const MAX_RANGE_SPAN: i64 = (i32::MAX - 1) as i64; const ARG_INCLUDE_WORLD_SEED: &str = "includeWorldSeed";
const ARG_INCLUDE_SEQUENCE_ID: &str = "includeSequenceId";
const RANGE_TOO_LARGE_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new( const RANGE_TOO_LARGE_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_RANDOM_ERROR_RANGE_TOO_LARGE, translation::java::COMMANDS_RANDOM_ERROR_RANGE_TOO_LARGE,
@ -28,10 +37,25 @@ const RANGE_TOO_SMALL_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_RANDOM_ERROR_RANGE_TOO_SMALL, translation::java::COMMANDS_RANDOM_ERROR_RANGE_TOO_SMALL,
); );
struct RandomSequenceSuggestionProvider;
impl SuggestionProvider for RandomSequenceSuggestionProvider {
fn suggest(
&self,
context: &CommandContext,
mut builder: SuggestionsBuilder,
) -> SuggestionProviderResult {
let sequences = context.server().random_sequences.lock().unwrap();
for key in sequences.get_sequence_keys() {
builder = builder.suggest(key);
}
builder.build()
}
}
struct RandomExecutor { struct RandomExecutor {
/// Whether the result is announced to every player (`roll`) instead of
/// only the command source (`value`).
roll: bool, roll: bool,
has_sequence: bool,
} }
impl CommandExecutor for RandomExecutor { impl CommandExecutor for RandomExecutor {
@ -44,11 +68,20 @@ impl CommandExecutor for RandomExecutor {
if span == 0 { if span == 0 {
return Err(RANGE_TOO_SMALL_ERROR_TYPE.create_without_context()); return Err(RANGE_TOO_SMALL_ERROR_TYPE.create_without_context());
} }
if span >= MAX_RANGE_SPAN { if span >= 2_147_483_647 {
return Err(RANGE_TOO_LARGE_ERROR_TYPE.create_without_context()); return Err(RANGE_TOO_LARGE_ERROR_TYPE.create_without_context());
} }
let result = rand::random_range(min..=max); let value = if self.has_sequence {
let sequence = IdentifierArgumentType::get(context, ARG_SEQUENCE)?;
let world_seed = context.server().level_info.load().world_gen_settings.seed;
let mut sequences = context.server().random_sequences.lock().unwrap();
sequences
.get_or_create(&sequence, world_seed)
.random_between_inclusive(min, max)
} else {
rand::random_range(min..=max)
};
if self.roll { if self.roll {
let msg = TextComponent::translate_cross( let msg = TextComponent::translate_cross(
@ -56,7 +89,7 @@ impl CommandExecutor for RandomExecutor {
translation::java::COMMANDS_RANDOM_ROLL, translation::java::COMMANDS_RANDOM_ROLL,
[ [
context.source.display_name.clone(), context.source.display_name.clone(),
TextComponent::text(result.to_string()), TextComponent::text(value.to_string()),
TextComponent::text(min.to_string()), TextComponent::text(min.to_string()),
TextComponent::text(max.to_string()), TextComponent::text(max.to_string()),
], ],
@ -64,7 +97,6 @@ impl CommandExecutor for RandomExecutor {
for player in context.server().get_all_players() { for player in context.server().get_all_players() {
player.send_system_message(&msg); player.send_system_message(&msg);
} }
// Also show the roll on the console (or another non-player source).
if context.source.player_or_none().is_none() { if context.source.player_or_none().is_none() {
context.source.send_message(msg); context.source.send_message(msg);
} }
@ -73,32 +105,209 @@ impl CommandExecutor for RandomExecutor {
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_RANDOM_SAMPLE_SUCCESS, translation::java::COMMANDS_RANDOM_SAMPLE_SUCCESS,
translation::java::COMMANDS_RANDOM_SAMPLE_SUCCESS, translation::java::COMMANDS_RANDOM_SAMPLE_SUCCESS,
[TextComponent::text(result.to_string())], [TextComponent::text(value.to_string())],
), ),
false, false,
); );
} }
Ok(result) Ok(value)
} }
} }
struct ResetSequenceExecutor {
has_seed: bool,
has_include_world_seed: bool,
has_include_sequence_id: bool,
}
impl CommandExecutor for ResetSequenceExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let sequence = IdentifierArgumentType::get(context, ARG_SEQUENCE)?;
let world_seed = context.server().level_info.load().world_gen_settings.seed;
let mut sequences = context.server().random_sequences.lock().unwrap();
if self.has_seed {
let seed = IntegerArgumentType::get(context, ARG_SEED)?;
let include_world_seed = if self.has_include_world_seed {
BoolArgumentType::get(context, ARG_INCLUDE_WORLD_SEED)?
} else {
true
};
let include_sequence_id = if self.has_include_sequence_id {
BoolArgumentType::get(context, ARG_INCLUDE_SEQUENCE_ID)?
} else {
true
};
sequences.reset_with_options(
&sequence,
world_seed,
seed,
include_world_seed,
include_sequence_id,
);
} else {
sequences.reset(&sequence, world_seed);
}
context.source.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_RANDOM_RESET_SUCCESS,
translation::java::COMMANDS_RANDOM_RESET_SUCCESS,
[TextComponent::text(sequence.to_string())],
),
false,
);
Ok(1)
}
}
struct ResetAllSequencesExecutor {
has_seed: bool,
has_include_world_seed: bool,
has_include_sequence_id: bool,
}
impl CommandExecutor for ResetAllSequencesExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let mut sequences = context.server().random_sequences.lock().unwrap();
if self.has_seed {
let seed = IntegerArgumentType::get(context, ARG_SEED)?;
let include_world_seed = if self.has_include_world_seed {
BoolArgumentType::get(context, ARG_INCLUDE_WORLD_SEED)?
} else {
true
};
let include_sequence_id = if self.has_include_sequence_id {
BoolArgumentType::get(context, ARG_INCLUDE_SEQUENCE_ID)?
} else {
true
};
sequences.set_seed_defaults(seed, include_world_seed, include_sequence_id);
}
let count = sequences.clear() as i32;
context.source.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_RANDOM_RESET_ALL_SUCCESS,
translation::java::COMMANDS_RANDOM_RESET_ALL_SUCCESS,
[TextComponent::text(count.to_string())],
),
false,
);
Ok(count)
}
}
fn draw_random_value_tree(name: &'static str, announce: bool) -> LiteralArgumentBuilder {
literal(name).then(
argument(ARG_RANGE, IntRangeArgumentType)
.executes(RandomExecutor {
roll: announce,
has_sequence: false,
})
.then(
argument(ARG_SEQUENCE, IdentifierArgumentType)
.suggests(RandomSequenceSuggestionProvider)
.requires(PERMISSION_RESET)
.executes(RandomExecutor {
roll: announce,
has_sequence: true,
}),
),
)
}
pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistry) { pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistry) {
registry.register_permission_or_panic(Permission::new( registry.register_permission_or_panic(Permission::new(
PERMISSION, PERMISSION,
DESCRIPTION, DESCRIPTION,
// Vanilla allows every player to use `/random value` and `/random roll`.
PermissionDefault::Allow, PermissionDefault::Allow,
)); ));
registry.register_permission_or_panic(Permission::new(
PERMISSION_RESET,
DESCRIPTION,
PermissionDefault::Op(PermissionLvl::Two),
));
dispatcher.register( dispatcher.register(
command("random", DESCRIPTION) command("random", DESCRIPTION)
.requires(PERMISSION) .requires(PERMISSION)
.then(literal("value").then( .then(draw_random_value_tree("value", false))
argument(ARG_RANGE, IntRangeArgumentType).executes(RandomExecutor { roll: false }), .then(draw_random_value_tree("roll", true))
)) .then(
.then(literal("roll").then( literal("reset")
argument(ARG_RANGE, IntRangeArgumentType).executes(RandomExecutor { roll: true }), .requires(PERMISSION_RESET)
)), .then(
literal("*")
.executes(ResetAllSequencesExecutor {
has_seed: false,
has_include_world_seed: false,
has_include_sequence_id: false,
})
.then(
argument(ARG_SEED, IntegerArgumentType::any())
.executes(ResetAllSequencesExecutor {
has_seed: true,
has_include_world_seed: false,
has_include_sequence_id: false,
})
.then(
argument(ARG_INCLUDE_WORLD_SEED, BoolArgumentType)
.executes(ResetAllSequencesExecutor {
has_seed: true,
has_include_world_seed: true,
has_include_sequence_id: false,
})
.then(
argument(ARG_INCLUDE_SEQUENCE_ID, BoolArgumentType)
.executes(ResetAllSequencesExecutor {
has_seed: true,
has_include_world_seed: true,
has_include_sequence_id: true,
}),
),
),
),
)
.then(
argument(ARG_SEQUENCE, IdentifierArgumentType)
.suggests(RandomSequenceSuggestionProvider)
.executes(ResetSequenceExecutor {
has_seed: false,
has_include_world_seed: false,
has_include_sequence_id: false,
})
.then(
argument(ARG_SEED, IntegerArgumentType::any())
.executes(ResetSequenceExecutor {
has_seed: true,
has_include_world_seed: false,
has_include_sequence_id: false,
})
.then(
argument(ARG_INCLUDE_WORLD_SEED, BoolArgumentType)
.executes(ResetSequenceExecutor {
has_seed: true,
has_include_world_seed: true,
has_include_sequence_id: false,
})
.then(
argument(ARG_INCLUDE_SEQUENCE_ID, BoolArgumentType)
.executes(ResetSequenceExecutor {
has_seed: true,
has_include_world_seed: true,
has_include_sequence_id: true,
}),
),
),
),
),
),
); );
} }

View File

@ -33,10 +33,6 @@ static ERROR_NOT_RIDING: CommandErrorType<1> = CommandErrorType::new(
translation::java::COMMANDS_RIDE_NOT_RIDING, translation::java::COMMANDS_RIDE_NOT_RIDING,
"Not riding anything", "Not riding anything",
); );
static ERROR_GENERIC: CommandErrorType<2> = CommandErrorType::new(
translation::java::COMMANDS_RIDE_MOUNT_FAILURE_GENERIC,
"Failed to mount",
);
#[allow(clippy::assigning_clones)] #[allow(clippy::assigning_clones)]
fn is_riding_recursive(entity: &dyn EntityBase, possible_vehicle: &dyn EntityBase) -> bool { fn is_riding_recursive(entity: &dyn EntityBase, possible_vehicle: &dyn EntityBase) -> bool {
@ -64,35 +60,9 @@ struct RideMountExecutor;
impl CommandExecutor for RideMountExecutor { impl CommandExecutor for RideMountExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let targets = EntityArgumentType::get_entities(context, "target")?; let target = EntityArgumentType::get_entity(context, "target")?;
let vehicle = EntityArgumentType::get_entity(context, "vehicle")?; let vehicle = EntityArgumentType::get_entity(context, "vehicle")?;
if vehicle.get_player().is_some() {
return Err(ERROR_CANT_RIDE_PLAYERS.create_without_context());
}
let vehicle_world = vehicle.get_entity().world.load();
let mut success_count = 0;
let mut last_error = None;
for target in &targets {
let target_world = target.get_entity().world.load();
if target_world.dimension.minecraft_name != vehicle_world.dimension.minecraft_name {
last_error = Some(ERROR_WRONG_DIMENSION.create_without_context());
continue;
}
if target.get_entity().entity_id == vehicle.get_entity().entity_id {
last_error = Some(ERROR_LOOP.create_without_context());
continue;
}
if is_riding_recursive(target.as_ref(), vehicle.as_ref()) {
last_error = Some(ERROR_LOOP.create_without_context());
continue;
}
let current_vehicle = target let current_vehicle = target
.get_entity() .get_entity()
.vehicle .vehicle
@ -100,23 +70,29 @@ impl CommandExecutor for RideMountExecutor {
.unwrap_or_else(std::sync::PoisonError::into_inner) .unwrap_or_else(std::sync::PoisonError::into_inner)
.clone(); .clone();
if let Some(ref curr_veh) = current_vehicle { if let Some(ref curr_veh) = current_vehicle {
if curr_veh.get_entity().entity_id == vehicle.get_entity().entity_id { return Err(ERROR_ALREADY_RIDING
last_error = Some(ERROR_ALREADY_RIDING.create_without_context( .create_without_context(target.get_display_name(), curr_veh.get_display_name()));
target.get_display_name(),
vehicle.get_display_name(),
));
continue;
} }
// Dismount first
curr_veh if vehicle.get_player().is_some() {
.get_entity() return Err(ERROR_CANT_RIDE_PLAYERS.create_without_context());
.remove_passenger_sync(target.get_entity().entity_id); }
if target.get_entity().entity_id == vehicle.get_entity().entity_id
|| is_riding_recursive(target.as_ref(), vehicle.as_ref())
{
return Err(ERROR_LOOP.create_without_context());
}
let target_world = target.get_entity().world.load();
let vehicle_world = vehicle.get_entity().world.load();
if target_world.dimension.minecraft_name != vehicle_world.dimension.minecraft_name {
return Err(ERROR_WRONG_DIMENSION.create_without_context());
} }
vehicle vehicle
.get_entity() .get_entity()
.add_passenger(vehicle.clone(), target.clone()); .add_passenger(vehicle.clone(), target.clone());
success_count += 1;
let msg = TextComponent::translate_cross( let msg = TextComponent::translate_cross(
translation::java::COMMANDS_RIDE_MOUNT_SUCCESS, translation::java::COMMANDS_RIDE_MOUNT_SUCCESS,
@ -124,19 +100,8 @@ impl CommandExecutor for RideMountExecutor {
[target.get_display_name(), vehicle.get_display_name()], [target.get_display_name(), vehicle.get_display_name()],
); );
context.source.send_feedback(msg, true); context.source.send_feedback(msg, true);
}
if success_count == 0 { Ok(1)
if let Some(err) = last_error {
return Err(err);
}
return Err(ERROR_GENERIC.create_without_context(
targets[0].get_display_name(),
vehicle.get_display_name(),
));
}
Ok(success_count)
} }
} }
@ -144,43 +109,30 @@ struct RideDismountExecutor;
impl CommandExecutor for RideDismountExecutor { impl CommandExecutor for RideDismountExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let targets = EntityArgumentType::get_entities(context, "target")?; let target = EntityArgumentType::get_entity(context, "target")?;
let mut success_count = 0;
let mut last_error = None;
for target in &targets {
let current_vehicle = target let current_vehicle = target
.get_entity() .get_entity()
.vehicle .vehicle
.lock() .lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) .unwrap_or_else(std::sync::PoisonError::into_inner)
.clone(); .clone();
if let Some(vehicle) = current_vehicle { current_vehicle.map_or_else(
|| Err(ERROR_NOT_RIDING.create_without_context(target.get_display_name())),
|vehicle| {
let target_id = target.get_entity().entity_id; let target_id = target.get_entity().entity_id;
vehicle.get_entity().remove_passenger_sync(target_id); vehicle.get_entity().remove_passenger_sync(target_id);
success_count += 1;
let msg = TextComponent::translate_cross( let msg = TextComponent::translate_cross(
translation::java::COMMANDS_RIDE_DISMOUNT_SUCCESS, translation::java::COMMANDS_RIDE_DISMOUNT_SUCCESS,
translation::java::COMMANDS_RIDE_DISMOUNT_SUCCESS, translation::java::COMMANDS_RIDE_DISMOUNT_SUCCESS,
[target.get_display_name()], [target.get_display_name(), vehicle.get_display_name()],
); );
context.source.send_feedback(msg, true); context.source.send_feedback(msg, true);
} else {
last_error =
Some(ERROR_NOT_RIDING.create_without_context(target.get_display_name()));
}
}
if success_count == 0 { Ok(1)
if let Some(err) = last_error { },
return Err(err); )
}
return Err(ERROR_NOT_RIDING.create_without_context(targets[0].get_display_name()));
}
Ok(success_count)
} }
} }
@ -193,7 +145,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
let builder = let builder =
command("ride", DESCRIPTION).requires(PERMISSION).then( command("ride", DESCRIPTION).requires(PERMISSION).then(
argument("target", EntityArgumentType::Entities) argument("target", EntityArgumentType::Entity)
.then(literal("mount").then( .then(literal("mount").then(
argument("vehicle", EntityArgumentType::Entity).executes(RideMountExecutor), argument("vehicle", EntityArgumentType::Entity).executes(RideMountExecutor),
)) ))

View File

@ -78,10 +78,10 @@ impl CommandExecutor for RotateFacingLocationExecutor {
let facing_pos = let facing_pos =
Vec3ArgumentType::get_coordinates(context, "facingLocation")?.resolve(&context.source); Vec3ArgumentType::get_coordinates(context, "facingLocation")?.resolve(&context.source);
let entity = target.get_entity(); let looking_from = context
let eye_height = entity.get_eye_height(); .source
let pos = entity.pos.load(); .entity_anchor
let looking_from = Vector3::new(pos.x, pos.y + eye_height, pos.z); .position_at_entity(target.get_entity());
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos); let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos);
@ -106,10 +106,10 @@ impl CommandExecutor for RotateFacingEntityExecutor {
EntityAnchor::Feet EntityAnchor::Feet
}; };
let target_entity = target.get_entity(); let looking_from = context
let eye_height = target_entity.get_eye_height(); .source
let pos = target_entity.pos.load(); .entity_anchor
let looking_from = Vector3::new(pos.x, pos.y + eye_height, pos.z); .position_at_entity(target.get_entity());
let looking_towards = anchor.position_at_entity(facing_entity.get_entity()); let looking_towards = anchor.position_at_entity(facing_entity.get_entity());

View File

@ -4,7 +4,7 @@ use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use tracing::error; use tracing::error;
use crate::command::argument_builder::{ArgumentBuilder, command}; use crate::command::argument_builder::{ArgumentBuilder, command, literal};
use crate::command::context::command_context::CommandContext; use crate::command::context::command_context::CommandContext;
use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult}; use crate::command::node::{CommandExecutor, CommandExecutorResult};
@ -63,6 +63,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
dispatcher.register( dispatcher.register(
command("save-all", DESCRIPTION) command("save-all", DESCRIPTION)
.requires(PERMISSION) .requires(PERMISSION)
.executes(SaveAllExecutor), .executes(SaveAllExecutor)
.then(literal("flush").executes(SaveAllExecutor)),
); );
} }

View File

@ -1,6 +1,5 @@
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::dimension::Dimension;
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::PermissionLvl; use pumpkin_util::PermissionLvl;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
@ -8,10 +7,9 @@ use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::argument_builder::{ArgumentBuilder, argument, command}; use crate::command::argument_builder::{ArgumentBuilder, argument, command};
use crate::command::argument_types::coordinates::angle::AngleArgumentType;
use crate::command::argument_types::coordinates::block_pos::BlockPosArgumentType; use crate::command::argument_types::coordinates::block_pos::BlockPosArgumentType;
use crate::command::argument_types::coordinates::rotation::RotationArgumentType;
use crate::command::context::command_context::CommandContext; use crate::command::context::command_context::CommandContext;
use crate::command::errors::error_types::CommandErrorType;
use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult}; use crate::command::node::{CommandExecutor, CommandExecutorResult};
use crate::plugin::world::spawn_change::SpawnChangeEvent; use crate::plugin::world::spawn_change::SpawnChangeEvent;
@ -19,43 +17,33 @@ use crate::plugin::world::spawn_change::SpawnChangeEvent;
const DESCRIPTION: &str = "Sets the world spawn point."; const DESCRIPTION: &str = "Sets the world spawn point.";
const PERMISSION: &str = "minecraft:command.setworldspawn"; const PERMISSION: &str = "minecraft:command.setworldspawn";
const ERROR_NOT_OVERWORLD: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_SETWORLDSPAWN_FAILURE_NOT_OVERWORLD,
translation::java::COMMANDS_SETWORLDSPAWN_FAILURE_NOT_OVERWORLD,
);
enum SetWorldSpawnMode { enum SetWorldSpawnMode {
SelfPos, SelfPos,
PosOnly, PosOnly,
PosAndAngle, PosAndRotation,
} }
struct SetWorldSpawnExecutor(SetWorldSpawnMode); struct SetWorldSpawnExecutor(SetWorldSpawnMode);
impl CommandExecutor for SetWorldSpawnExecutor { impl CommandExecutor for SetWorldSpawnExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let (pos, angle) = match self.0 { let (pos, (pitch, yaw)) = match self.0 {
SetWorldSpawnMode::SelfPos => { SetWorldSpawnMode::SelfPos => {
let block_pos = BlockPos::floored_v(context.source.position); let block_pos = BlockPos::floored_v(context.source.position);
(block_pos, 0.0) (block_pos, (0.0, 0.0))
} }
SetWorldSpawnMode::PosOnly => { SetWorldSpawnMode::PosOnly => {
let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?; let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?;
(block_pos, 0.0) (block_pos, (0.0, 0.0))
} }
SetWorldSpawnMode::PosAndAngle => { SetWorldSpawnMode::PosAndRotation => {
let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?; let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?;
let angle = AngleArgumentType::get(context, "angle")?.get_angle(&context.source); let rot = RotationArgumentType::get(context, "rotation")?.rotation(&context.source);
(block_pos, angle) (block_pos, (rot.x, rot.y))
} }
}; };
let world = context.source.world(); let world = context.source.world();
if world.dimension != Dimension::OVERWORLD && world.dimension != Dimension::OVERWORLD_CAVES
{
return Err(ERROR_NOT_OVERWORLD.create_without_context());
}
let server = context.source.server(); let server = context.source.server();
let current_info = server.level_info.load(); let current_info = server.level_info.load();
let previous_position = BlockPos::new( let previous_position = BlockPos::new(
@ -66,8 +54,8 @@ impl CommandExecutor for SetWorldSpawnExecutor {
let new_position = pos; let new_position = pos;
let previous_yaw = current_info.spawn_yaw; let previous_yaw = current_info.spawn_yaw;
let previous_pitch = current_info.spawn_pitch; let previous_pitch = current_info.spawn_pitch;
let new_yaw = angle; let new_yaw = yaw;
let new_pitch = 0.0; let new_pitch = pitch;
let mut event = SpawnChangeEvent::new( let mut event = SpawnChangeEvent::new(
world.clone(), world.clone(),
previous_position, previous_position,
@ -128,8 +116,8 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
argument("pos", BlockPosArgumentType) argument("pos", BlockPosArgumentType)
.executes(SetWorldSpawnExecutor(SetWorldSpawnMode::PosOnly)) .executes(SetWorldSpawnExecutor(SetWorldSpawnMode::PosOnly))
.then( .then(
argument("angle", AngleArgumentType) argument("rotation", RotationArgumentType)
.executes(SetWorldSpawnExecutor(SetWorldSpawnMode::PosAndAngle)), .executes(SetWorldSpawnExecutor(SetWorldSpawnMode::PosAndRotation)),
), ),
), ),
); );

View File

@ -1,11 +1,12 @@
use pumpkin_data::translation; use pumpkin_data::translation;
use pumpkin_util::PermissionLvl; use pumpkin_util::PermissionLvl;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry}; use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use crate::command::argument_builder::{ArgumentBuilder, argument, command}; use crate::command::argument_builder::{ArgumentBuilder, argument, command};
use crate::command::argument_types::coordinates::angle::AngleArgumentType;
use crate::command::argument_types::coordinates::block_pos::BlockPosArgumentType; use crate::command::argument_types::coordinates::block_pos::BlockPosArgumentType;
use crate::command::argument_types::coordinates::rotation::RotationArgumentType;
use crate::command::argument_types::entity::EntityArgumentType; use crate::command::argument_types::entity::EntityArgumentType;
use crate::command::context::command_context::CommandContext; use crate::command::context::command_context::CommandContext;
use crate::command::errors::error_types::CommandErrorType; use crate::command::errors::error_types::CommandErrorType;
@ -24,7 +25,7 @@ enum SpawnpointMode {
SelfDefault, SelfDefault,
TargetsDefault, TargetsDefault,
TargetsPos, TargetsPos,
TargetsPosAngle, TargetsPosRotation,
} }
struct SpawnpointExecutor(SpawnpointMode); struct SpawnpointExecutor(SpawnpointMode);
@ -43,37 +44,60 @@ impl CommandExecutor for SpawnpointExecutor {
_ => EntityArgumentType::get_players(context, "targets")?, _ => EntityArgumentType::get_players(context, "targets")?,
}; };
for target in &targets { let (pos, (pitch, yaw)) = match self.0 {
let (pos, yaw) = match self.0 {
SpawnpointMode::SelfDefault | SpawnpointMode::TargetsDefault => { SpawnpointMode::SelfDefault | SpawnpointMode::TargetsDefault => {
let block_pos = target.position().to_block_pos(); let block_pos = BlockPos::floored_v(context.source.position);
let yaw = target.living_entity.entity.yaw.load(); (block_pos, (0.0, 0.0))
(block_pos, yaw)
} }
SpawnpointMode::TargetsPos => { SpawnpointMode::TargetsPos => {
let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?; let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?;
let yaw = target.living_entity.entity.yaw.load(); (block_pos, (0.0, 0.0))
(block_pos, yaw)
} }
SpawnpointMode::TargetsPosAngle => { SpawnpointMode::TargetsPosRotation => {
let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?; let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?;
let yaw = AngleArgumentType::get(context, "angle")?.get_angle(&context.source); let rot = RotationArgumentType::get(context, "rotation")?.rotation(&context.source);
(block_pos, yaw) (block_pos, (rot.x, rot.y))
} }
}; };
let dimension = target.world().dimension.clone(); let world = context.source.world();
target.set_respawn_point(dimension, pos, yaw, 0.0, true); let dimension = world.dimension.clone();
let dimension_name = dimension.minecraft_name.to_string();
for target in &targets {
target.set_respawn_point(dimension.clone(), pos, yaw, pitch, true);
}
if targets.len() == 1 {
context.source.send_feedback( context.source.send_feedback(
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_SPAWNPOINT_SUCCESS_SINGLE, translation::java::COMMANDS_SPAWNPOINT_SUCCESS_SINGLE,
translation::bedrock::COMMANDS_SPAWNPOINT_SUCCESS_SINGLE, translation::bedrock::COMMANDS_SPAWNPOINT_SUCCESS_SINGLE,
[ [
TextComponent::text(target.gameprofile.name.clone()),
TextComponent::text(pos.0.x.to_string()), TextComponent::text(pos.0.x.to_string()),
TextComponent::text(pos.0.y.to_string()), TextComponent::text(pos.0.y.to_string()),
TextComponent::text(pos.0.z.to_string()), TextComponent::text(pos.0.z.to_string()),
TextComponent::text(yaw.to_string()),
TextComponent::text(pitch.to_string()),
TextComponent::text(dimension_name),
TextComponent::text(targets[0].gameprofile.name.clone()),
],
),
true,
);
} else {
context.source.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_SPAWNPOINT_SUCCESS_MULTIPLE,
translation::java::COMMANDS_SPAWNPOINT_SUCCESS_MULTIPLE,
[
TextComponent::text(pos.0.x.to_string()),
TextComponent::text(pos.0.y.to_string()),
TextComponent::text(pos.0.z.to_string()),
TextComponent::text(yaw.to_string()),
TextComponent::text(pitch.to_string()),
TextComponent::text(dimension_name),
TextComponent::text(targets.len().to_string()),
], ],
), ),
true, true,
@ -102,8 +126,9 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
argument("pos", BlockPosArgumentType) argument("pos", BlockPosArgumentType)
.executes(SpawnpointExecutor(SpawnpointMode::TargetsPos)) .executes(SpawnpointExecutor(SpawnpointMode::TargetsPos))
.then( .then(
argument("angle", AngleArgumentType) argument("rotation", RotationArgumentType).executes(
.executes(SpawnpointExecutor(SpawnpointMode::TargetsPosAngle)), SpawnpointExecutor(SpawnpointMode::TargetsPosRotation),
),
), ),
), ),
), ),

View File

@ -78,18 +78,16 @@ impl CommandExecutor for SpectateTargetExecutor {
let target_entity = target.get_entity(); let target_entity = target.get_entity();
let target_world = target_entity.world.load_full(); let target_world = target_entity.world.load_full();
let players = if self.is_self { let player = if self.is_self {
let player = context context
.source .source
.output .output
.as_player() .as_player()
.ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())?; .ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())?
vec![player]
} else { } else {
EntityArgumentType::get_players(context, "player")? EntityArgumentType::get_player(context, "player")?
}; };
for player in &players {
if player.gamemode.load() != GameMode::Spectator { if player.gamemode.load() != GameMode::Spectator {
let display_name = player.get_display_name(); let display_name = player.get_display_name();
return Err(ERROR_NOT_SPECTATOR.create_without_context(display_name)); return Err(ERROR_NOT_SPECTATOR.create_without_context(display_name));
@ -104,20 +102,14 @@ impl CommandExecutor for SpectateTargetExecutor {
let target_name = target.get_display_name(); let target_name = target.get_display_name();
return Err(ERROR_CANNOT_SPECTATE.create_without_context(target_name)); return Err(ERROR_CANNOT_SPECTATE.create_without_context(target_name));
} }
}
let mut succeeded = 0;
for player in &players {
let target_id = target_entity.entity_id; let target_id = target_entity.entity_id;
player.camera_target_id.store(Some(target_id)); player.camera_target_id.store(Some(target_id));
let pos = target_entity.pos.load(); let pos = target_entity.pos.load();
let yaw = target_entity.yaw.load(); let yaw = target_entity.yaw.load();
let pitch = target_entity.pitch.load(); let pitch = target_entity.pitch.load();
let player_world = player.world();
player.try_send_client_packet(&CSetCamera::new(target_id.into())); player.try_send_client_packet(&CSetCamera::new(target_id.into()));
player.teleport(pos, Some(yaw), Some(pitch), player_world); player.teleport(pos, Some(yaw), Some(pitch), player_world);
succeeded += 1;
}
let target_name = target.get_display_name(); let target_name = target.get_display_name();
context.source.send_feedback( context.source.send_feedback(
@ -129,7 +121,7 @@ impl CommandExecutor for SpectateTargetExecutor {
false, false,
); );
Ok(succeeded) Ok(1)
} }
} }
@ -148,7 +140,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
argument("target", EntityArgumentType::Entity) argument("target", EntityArgumentType::Entity)
.executes(SpectateTargetExecutor { is_self: true }) .executes(SpectateTargetExecutor { is_self: true })
.then( .then(
argument("player", EntityArgumentType::Players) argument("player", EntityArgumentType::Player)
.executes(SpectateTargetExecutor { is_self: false }), .executes(SpectateTargetExecutor { is_self: false }),
), ),
), ),

View File

@ -112,7 +112,7 @@ impl CommandExecutor for QueryExecutor {
), ),
true, true,
); );
Ok((elapsed_seconds * scale).floor() as i32) Ok((elapsed_seconds * scale) as i32)
} }
} }

View File

@ -21,11 +21,14 @@ use pumpkin_util::text::color::NamedColor;
const DESCRIPTION: &str = "Manages teams."; const DESCRIPTION: &str = "Manages teams.";
const PERMISSION: &str = "minecraft:command.team"; const PERMISSION: &str = "minecraft:command.team";
const ARG_TEAM_NAME: &str = "name"; const ARG_TEAM_NAME: &str = "team";
const ARG_DISPLAY_NAME: &str = "displayName"; const ARG_DISPLAY_NAME: &str = "displayName";
const ARG_TEAM: &str = "team"; const ARG_TEAM: &str = "team";
const ARG_MEMBERS: &str = "members"; const ARG_MEMBERS: &str = "members";
const ARG_VALUE: &str = "value"; const ARG_VALUE: &str = "value";
const ARG_PREFIX: &str = "prefix";
const ARG_SUFFIX: &str = "suffix";
const ARG_ALLOWED: &str = "allowed";
const DUPLICATE_TEAM_ERROR: CommandErrorType<0> = CommandErrorType::new( const DUPLICATE_TEAM_ERROR: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_TEAM_ADD_DUPLICATE, translation::java::COMMANDS_TEAM_ADD_DUPLICATE,
@ -543,7 +546,7 @@ struct TeamModifyDisplayNameExecutor;
impl CommandExecutor for TeamModifyDisplayNameExecutor { impl CommandExecutor for TeamModifyDisplayNameExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?; let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let new_name_str = StringArgumentType::get(context, ARG_VALUE)?; let new_name_str = StringArgumentType::get(context, ARG_DISPLAY_NAME)?;
let world = context.world().clone(); let world = context.world().clone();
let team_name_owned = team_name.to_string(); let team_name_owned = team_name.to_string();
@ -588,7 +591,7 @@ struct TeamModifyPrefixExecutor;
impl CommandExecutor for TeamModifyPrefixExecutor { impl CommandExecutor for TeamModifyPrefixExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?; let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let new_prefix_str = StringArgumentType::get(context, ARG_VALUE)?; let new_prefix_str = StringArgumentType::get(context, ARG_PREFIX)?;
let world = context.world().clone(); let world = context.world().clone();
let team_name_owned = team_name.to_string(); let team_name_owned = team_name.to_string();
@ -629,7 +632,7 @@ struct TeamModifySuffixExecutor;
impl CommandExecutor for TeamModifySuffixExecutor { impl CommandExecutor for TeamModifySuffixExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?; let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let new_suffix_str = StringArgumentType::get(context, ARG_VALUE)?; let new_suffix_str = StringArgumentType::get(context, ARG_SUFFIX)?;
let world = context.world().clone(); let world = context.world().clone();
let team_name_owned = team_name.to_string(); let team_name_owned = team_name.to_string();
@ -670,7 +673,7 @@ struct TeamModifyFriendlyFireExecutor;
impl CommandExecutor for TeamModifyFriendlyFireExecutor { impl CommandExecutor for TeamModifyFriendlyFireExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?; let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let value = BoolArgumentType::get(context, ARG_VALUE)?; let value = BoolArgumentType::get(context, ARG_ALLOWED)?;
let world = context.world().clone(); let world = context.world().clone();
let team_name_owned = team_name.to_string(); let team_name_owned = team_name.to_string();
@ -724,7 +727,7 @@ struct TeamModifySeeFriendlyInvisiblesExecutor;
impl CommandExecutor for TeamModifySeeFriendlyInvisiblesExecutor { impl CommandExecutor for TeamModifySeeFriendlyInvisiblesExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?; let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let value = BoolArgumentType::get(context, ARG_VALUE)?; let value = BoolArgumentType::get(context, ARG_ALLOWED)?;
let world = context.world().clone(); let world = context.world().clone();
let team_name_owned = team_name.to_string(); let team_name_owned = team_name.to_string();
@ -976,9 +979,7 @@ fn join_branch() -> LiteralArgumentBuilder {
} }
fn leave_branch() -> LiteralArgumentBuilder { fn leave_branch() -> LiteralArgumentBuilder {
literal("leave") literal("leave").then(
.executes(TeamLeaveExecutor { has_members: false })
.then(
argument(ARG_MEMBERS, EntityArgumentType::Entities) argument(ARG_MEMBERS, EntityArgumentType::Entities)
.executes(TeamLeaveExecutor { has_members: true }), .executes(TeamLeaveExecutor { has_members: true }),
) )
@ -1004,28 +1005,28 @@ fn modify_branch() -> LiteralArgumentBuilder {
) )
.then( .then(
literal("displayName").then( literal("displayName").then(
argument(ARG_VALUE, StringArgumentType::GreedyPhrase) argument(ARG_DISPLAY_NAME, StringArgumentType::GreedyPhrase)
.executes(TeamModifyDisplayNameExecutor), .executes(TeamModifyDisplayNameExecutor),
), ),
) )
.then( .then(
literal("prefix").then( literal("prefix").then(
argument(ARG_VALUE, StringArgumentType::GreedyPhrase) argument(ARG_PREFIX, StringArgumentType::GreedyPhrase)
.executes(TeamModifyPrefixExecutor), .executes(TeamModifyPrefixExecutor),
), ),
) )
.then( .then(
literal("suffix").then( literal("suffix").then(
argument(ARG_VALUE, StringArgumentType::GreedyPhrase) argument(ARG_SUFFIX, StringArgumentType::GreedyPhrase)
.executes(TeamModifySuffixExecutor), .executes(TeamModifySuffixExecutor),
), ),
) )
.then(literal("friendlyFire").then( .then(literal("friendlyFire").then(
argument(ARG_VALUE, BoolArgumentType).executes(TeamModifyFriendlyFireExecutor), argument(ARG_ALLOWED, BoolArgumentType).executes(TeamModifyFriendlyFireExecutor),
)) ))
.then( .then(
literal("seeFriendlyInvisibles").then( literal("seeFriendlyInvisibles").then(
argument(ARG_VALUE, BoolArgumentType) argument(ARG_ALLOWED, BoolArgumentType)
.executes(TeamModifySeeFriendlyInvisiblesExecutor), .executes(TeamModifySeeFriendlyInvisiblesExecutor),
), ),
) )

View File

@ -48,6 +48,7 @@ impl CommandExecutor for TeamMsgCommandExecutor {
let message_component = TextComponent::text(message_text); let message_component = TextComponent::text(message_text);
let online_players = world.players.load(); let online_players = world.players.load();
let mut recipients = 0;
for player in online_players.iter() { for player in online_players.iter() {
if team.players.contains(&player.gameprofile.name) { if team.players.contains(&player.gameprofile.name) {
@ -74,10 +75,11 @@ impl CommandExecutor for TeamMsgCommandExecutor {
}; };
player.send_system_message(&msg); player.send_system_message(&msg);
recipients += 1;
} }
} }
Ok(1) Ok(recipients)
} }
} }

View File

@ -11,6 +11,7 @@ use crate::command::argument_builder::{ArgumentBuilder, argument, command, liter
use crate::command::argument_types::coordinates::rotation::RotationArgumentType; use crate::command::argument_types::coordinates::rotation::RotationArgumentType;
use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType; use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType;
use crate::command::argument_types::entity::EntityArgumentType; use crate::command::argument_types::entity::EntityArgumentType;
use crate::command::argument_types::entity_anchor::{EntityAnchor, EntityAnchorArgumentType};
use crate::command::context::command_context::CommandContext; use crate::command::context::command_context::CommandContext;
use crate::command::errors::error_types::CommandErrorType; use crate::command::errors::error_types::CommandErrorType;
use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::dispatcher::CommandDispatcher;
@ -21,11 +22,6 @@ use crate::world::World;
const DESCRIPTION: &str = "Teleports entities, including players."; const DESCRIPTION: &str = "Teleports entities, including players.";
const PERMISSION: &str = "minecraft:command.teleport"; const PERMISSION: &str = "minecraft:command.teleport";
const ERROR_NOT_PLAYER: CommandErrorType<0> = CommandErrorType::new(
translation::java::PERMISSIONS_REQUIRES_PLAYER,
translation::java::PERMISSIONS_REQUIRES_PLAYER,
);
const ERROR_INVALID_POSITION: CommandErrorType<0> = CommandErrorType::new( const ERROR_INVALID_POSITION: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION, translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION, translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
@ -62,28 +58,24 @@ struct SelfToPosExecutor;
impl CommandExecutor for SelfToPosExecutor { impl CommandExecutor for SelfToPosExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let player = context let entity = context.source.entity_or_err()?;
.source
.output
.as_player()
.ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())?;
let pos = Vec3ArgumentType::get_coordinates(context, "location")?.resolve(&context.source); let pos = Vec3ArgumentType::get_coordinates(context, "location")?.resolve(&context.source);
if !World::is_valid(BlockPos(pos.floor_to_i32())) { if !World::is_valid(BlockPos(pos.floor_to_i32())) {
return Err(ERROR_INVALID_POSITION.create_without_context()); return Err(ERROR_INVALID_POSITION.create_without_context());
} }
let yaw = player.get_entity().yaw.load(); let yaw = entity.get_entity().yaw.load();
let pitch = player.get_entity().pitch.load(); let pitch = entity.get_entity().pitch.load();
let player_world = player.world(); let world = context.source.world();
player.teleport(pos, Some(yaw), Some(pitch), player_world); entity.teleport(pos, Some(yaw), Some(pitch), world.clone());
context.source.send_feedback( context.source.send_feedback(
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_TELEPORT_SUCCESS_LOCATION_SINGLE, translation::java::COMMANDS_TELEPORT_SUCCESS_LOCATION_SINGLE,
translation::bedrock::COMMANDS_TP_SUCCESS_COORDINATES, translation::bedrock::COMMANDS_TP_SUCCESS_COORDINATES,
[ [
player.get_display_name(), entity.get_display_name(),
TextComponent::text(pos.x.to_string()), TextComponent::text(pos.x.to_string()),
TextComponent::text(pos.y.to_string()), TextComponent::text(pos.y.to_string()),
TextComponent::text(pos.z.to_string()), TextComponent::text(pos.z.to_string()),
@ -100,11 +92,7 @@ struct SelfToEntityExecutor;
impl CommandExecutor for SelfToEntityExecutor { impl CommandExecutor for SelfToEntityExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let player = context let entity = context.source.entity_or_err()?;
.source
.output
.as_player()
.ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())?;
let destination = EntityArgumentType::get_entity(context, "destination")?; let destination = EntityArgumentType::get_entity(context, "destination")?;
let destination_entity = destination.get_entity(); let destination_entity = destination.get_entity();
@ -117,14 +105,14 @@ impl CommandExecutor for SelfToEntityExecutor {
return Err(ERROR_INVALID_POSITION.create_without_context()); return Err(ERROR_INVALID_POSITION.create_without_context());
} }
player.teleport(pos, Some(yaw), Some(pitch), world); entity.teleport(pos, Some(yaw), Some(pitch), world);
context.source.send_feedback( context.source.send_feedback(
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE, translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE,
translation::bedrock::COMMANDS_TP_SUCCESSVICTIM, translation::bedrock::COMMANDS_TP_SUCCESSVICTIM,
[ [
player.get_display_name(), entity.get_display_name(),
destination_entity.get_display_name(), destination_entity.get_display_name(),
], ],
), ),
@ -270,10 +258,15 @@ impl CommandExecutor for EntitiesToPosFacingPosExecutor {
let facing_pos = let facing_pos =
Vec3ArgumentType::get_coordinates(context, "facingLocation")?.resolve(&context.source); Vec3ArgumentType::get_coordinates(context, "facingLocation")?.resolve(&context.source);
let (yaw, pitch) = yaw_pitch_facing_position(&pos, &facing_pos);
let world = context.source.world(); let world = context.source.world();
for target in &targets { for target in &targets {
let eye_offset = match context.source.entity_anchor {
EntityAnchor::Feet => 0.0,
EntityAnchor::Eyes => target.get_entity().get_eye_height(),
};
let looking_from = Vector3::new(pos.x, pos.y + eye_offset, pos.z);
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos);
target.teleport(pos, Some(yaw), Some(pitch), world.clone()); target.teleport(pos, Some(yaw), Some(pitch), world.clone());
} }
@ -300,7 +293,9 @@ impl CommandExecutor for EntitiesToPosFacingPosExecutor {
} }
} }
struct EntitiesToPosFacingEntityExecutor; struct EntitiesToPosFacingEntityExecutor {
has_anchor: bool,
}
impl CommandExecutor for EntitiesToPosFacingEntityExecutor { impl CommandExecutor for EntitiesToPosFacingEntityExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
@ -312,10 +307,22 @@ impl CommandExecutor for EntitiesToPosFacingEntityExecutor {
} }
let facing_entity = EntityArgumentType::get_entity(context, "facingEntity")?; let facing_entity = EntityArgumentType::get_entity(context, "facingEntity")?;
let (yaw, pitch) = yaw_pitch_facing_position(&pos, &facing_entity.get_entity().pos.load()); let anchor = if self.has_anchor {
EntityAnchorArgumentType::get(context, "facingAnchor")?
} else {
EntityAnchor::Feet
};
let facing_pos = anchor.position_at_entity(facing_entity.get_entity());
let world = context.source.world(); let world = context.source.world();
for target in &targets { for target in &targets {
let eye_offset = match context.source.entity_anchor {
EntityAnchor::Feet => 0.0,
EntityAnchor::Eyes => target.get_entity().get_eye_height(),
};
let looking_from = Vector3::new(pos.x, pos.y + eye_offset, pos.z);
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos);
target.teleport(pos, Some(yaw), Some(pitch), world.clone()); target.teleport(pos, Some(yaw), Some(pitch), world.clone());
} }
@ -367,7 +374,15 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
.then( .then(
literal("entity").then( literal("entity").then(
argument("facingEntity", EntityArgumentType::Entity) argument("facingEntity", EntityArgumentType::Entity)
.executes(EntitiesToPosFacingEntityExecutor), .executes(EntitiesToPosFacingEntityExecutor {
has_anchor: false,
})
.then(
argument("facingAnchor", EntityAnchorArgumentType)
.executes(EntitiesToPosFacingEntityExecutor {
has_anchor: true,
}),
),
), ),
) )
.then( .then(

View File

@ -148,6 +148,7 @@ impl TickExecutor {
TextComponent::text(nanos_to_millis_string(p50)), TextComponent::text(nanos_to_millis_string(p50)),
TextComponent::text(nanos_to_millis_string(p95)), TextComponent::text(nanos_to_millis_string(p95)),
TextComponent::text(nanos_to_millis_string(p99)), TextComponent::text(nanos_to_millis_string(p99)),
TextComponent::text(slice.len().to_string()),
], ],
), ),
false, false,

View File

@ -87,10 +87,10 @@ impl CommandExecutor for SimpleTriggerExecutor {
translation::java::COMMANDS_TRIGGER_SIMPLE_SUCCESS, translation::java::COMMANDS_TRIGGER_SIMPLE_SUCCESS,
[objective_display_name], [objective_display_name],
), ),
false, true,
); );
Ok(1) Ok(new_value)
} }
} }
@ -158,10 +158,10 @@ impl CommandExecutor for AddTriggerExecutor {
TextComponent::text(value.to_string()), TextComponent::text(value.to_string()),
], ],
), ),
false, true,
); );
Ok(value) Ok(new_value)
} }
} }
@ -221,7 +221,7 @@ impl CommandExecutor for SetTriggerExecutor {
TextComponent::text(value.to_string()), TextComponent::text(value.to_string()),
], ],
), ),
false, true,
); );
Ok(value) Ok(value)

View File

@ -33,7 +33,7 @@ impl CommandExecutor for WeatherExecutor {
}; };
let world = context.source.world(); let world = context.source.world();
let message = { let (message, return_val) = {
let mut weather = world let mut weather = world
.weather .weather
.lock() .lock()
@ -45,10 +45,13 @@ impl CommandExecutor for WeatherExecutor {
duration.unwrap_or_else(|| rand::random_range(12_000..=180_000)); duration.unwrap_or_else(|| rand::random_range(12_000..=180_000));
weather.set_weather_parameters(world, processed_duration, 0, false, false); weather.set_weather_parameters(world, processed_duration, 0, false, false);
(
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_WEATHER_SET_CLEAR, translation::java::COMMANDS_WEATHER_SET_CLEAR,
translation::bedrock::COMMANDS_WEATHER_CLEAR, translation::bedrock::COMMANDS_WEATHER_CLEAR,
[], [],
),
duration.unwrap_or(-1),
) )
} }
WeatherMode::Rain => { WeatherMode::Rain => {
@ -56,10 +59,13 @@ impl CommandExecutor for WeatherExecutor {
duration.unwrap_or_else(|| rand::random_range(12_000..=24_000)); duration.unwrap_or_else(|| rand::random_range(12_000..=24_000));
weather.set_weather_parameters(world, 0, processed_duration, true, false); weather.set_weather_parameters(world, 0, processed_duration, true, false);
(
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_WEATHER_SET_RAIN, translation::java::COMMANDS_WEATHER_SET_RAIN,
translation::bedrock::COMMANDS_WEATHER_RAIN, translation::bedrock::COMMANDS_WEATHER_RAIN,
[], [],
),
duration.unwrap_or(-1),
) )
} }
WeatherMode::Thunder => { WeatherMode::Thunder => {
@ -67,10 +73,13 @@ impl CommandExecutor for WeatherExecutor {
duration.unwrap_or_else(|| rand::random_range(3600..=15_600)); duration.unwrap_or_else(|| rand::random_range(3600..=15_600));
weather.set_weather_parameters(world, 0, processed_duration, true, true); weather.set_weather_parameters(world, 0, processed_duration, true, true);
(
TextComponent::translate_cross( TextComponent::translate_cross(
translation::java::COMMANDS_WEATHER_SET_THUNDER, translation::java::COMMANDS_WEATHER_SET_THUNDER,
translation::bedrock::COMMANDS_WEATHER_THUNDER, translation::bedrock::COMMANDS_WEATHER_THUNDER,
[], [],
),
duration.unwrap_or(-1),
) )
} }
} }
@ -78,7 +87,7 @@ impl CommandExecutor for WeatherExecutor {
context.source.send_feedback(message, true); context.source.send_feedback(message, true);
Ok(1) Ok(return_val)
} }
} }
@ -98,7 +107,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
mode: WeatherMode::Clear, mode: WeatherMode::Clear,
has_duration: false, has_duration: false,
}) })
.then(argument("duration", TimeArgumentType::any()).executes( .then(argument("duration", TimeArgumentType::new(1)).executes(
WeatherExecutor { WeatherExecutor {
mode: WeatherMode::Clear, mode: WeatherMode::Clear,
has_duration: true, has_duration: true,
@ -111,7 +120,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
mode: WeatherMode::Rain, mode: WeatherMode::Rain,
has_duration: false, has_duration: false,
}) })
.then(argument("duration", TimeArgumentType::any()).executes( .then(argument("duration", TimeArgumentType::new(1)).executes(
WeatherExecutor { WeatherExecutor {
mode: WeatherMode::Rain, mode: WeatherMode::Rain,
has_duration: true, has_duration: true,
@ -124,7 +133,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
mode: WeatherMode::Thunder, mode: WeatherMode::Thunder,
has_duration: false, has_duration: false,
}) })
.then(argument("duration", TimeArgumentType::any()).executes( .then(argument("duration", TimeArgumentType::new(1)).executes(
WeatherExecutor { WeatherExecutor {
mode: WeatherMode::Thunder, mode: WeatherMode::Thunder,
has_duration: true, has_duration: true,

View File

@ -12,6 +12,8 @@ use crate::command::context::command_context::CommandContext;
use crate::command::errors::error_types::CommandErrorType; use crate::command::errors::error_types::CommandErrorType;
use crate::command::node::dispatcher::CommandDispatcher; use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::{CommandExecutor, CommandExecutorResult}; use crate::command::node::{CommandExecutor, CommandExecutorResult};
use crate::command::suggestion::provider::{SuggestionProvider, SuggestionProviderResult};
use crate::command::suggestion::suggestions::SuggestionsBuilder;
use crate::data::{LoadJSONConfiguration, SaveJSONConfiguration, whitelist::WhitelistConfig}; use crate::data::{LoadJSONConfiguration, SaveJSONConfiguration, whitelist::WhitelistConfig};
use crate::net::DisconnectReason; use crate::net::DisconnectReason;
use crate::server::Server; use crate::server::Server;
@ -39,11 +41,14 @@ const ERROR_REMOVE_FAILED: CommandErrorType<0> = CommandErrorType::new(
translation::bedrock::COMMANDS_ALLOWLIST_REMOVE_FAILED, translation::bedrock::COMMANDS_ALLOWLIST_REMOVE_FAILED,
); );
fn kick_non_whitelisted_players(server: &Server) { pub fn kick_non_whitelisted_players(server: &Server) {
let whitelist = server.data.whitelist_config.read().unwrap(); let whitelist = server.data.whitelist_config.read().unwrap();
let ops = server.data.operator_config.read().unwrap();
if server.basic_config.enforce_whitelist && server.white_list.load(Ordering::Relaxed) { if server.basic_config.enforce_whitelist && server.white_list.load(Ordering::Relaxed) {
for player in server.get_all_players() { for player in server.get_all_players() {
if whitelist.is_whitelisted(&player.gameprofile) { if ops.get_entry(&player.gameprofile.id).is_some()
|| whitelist.is_whitelisted(&player.gameprofile)
{
continue; continue;
} }
player.kick( player.kick(
@ -66,7 +71,6 @@ impl CommandExecutor for OnExecutor {
if previous { if previous {
Err(ERROR_ALREADY_ON.create_without_context()) Err(ERROR_ALREADY_ON.create_without_context())
} else { } else {
kick_non_whitelisted_players(server);
context.source.send_feedback( context.source.send_feedback(
pumpkin_macros::translate_cross!( pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WHITELIST_ENABLED, translation::java::COMMANDS_WHITELIST_ENABLED,
@ -74,6 +78,7 @@ impl CommandExecutor for OnExecutor {
), ),
true, true,
); );
kick_non_whitelisted_players(server);
Ok(1) Ok(1)
} }
} }
@ -109,9 +114,10 @@ impl CommandExecutor for ListExecutor {
let whitelist = &whitelist_guard.whitelist; let whitelist = &whitelist_guard.whitelist;
if whitelist.is_empty() { if whitelist.is_empty() {
context.source.send_feedback( context.source.send_feedback(
pumpkin_macros::translate_cross!( TextComponent::translate_cross(
translation::java::COMMANDS_WHITELIST_NONE, translation::java::COMMANDS_WHITELIST_NONE,
translation::bedrock::COMMANDS_ALLOWLIST_LIST translation::java::COMMANDS_WHITELIST_NONE,
[],
), ),
false, false,
); );
@ -124,19 +130,21 @@ impl CommandExecutor for ListExecutor {
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
.join(", "); .join(", ");
let names_len = names.len() as i32; let count = whitelist.len() as i32;
context.source.send_feedback( context.source.send_feedback(
pumpkin_macros::translate_cross!( TextComponent::translate_cross(
translation::java::COMMANDS_WHITELIST_LIST, translation::java::COMMANDS_WHITELIST_LIST,
translation::bedrock::COMMANDS_ALLOWLIST_LIST, translation::java::COMMANDS_WHITELIST_LIST,
TextComponent::text(whitelist.len().to_string()), [
TextComponent::text(names) TextComponent::text(count.to_string()),
TextComponent::text(names),
],
), ),
false, false,
); );
Ok(names_len) Ok(count)
} }
} }
@ -146,7 +154,6 @@ impl CommandExecutor for ReloadExecutor {
fn execute(&self, context: &CommandContext) -> CommandExecutorResult { fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
let server = context.source.server(); let server = context.source.server();
*server.data.whitelist_config.write().unwrap() = WhitelistConfig::load(); *server.data.whitelist_config.write().unwrap() = WhitelistConfig::load();
kick_non_whitelisted_players(server);
context.source.send_feedback( context.source.send_feedback(
pumpkin_macros::translate_cross!( pumpkin_macros::translate_cross!(
translation::java::COMMANDS_WHITELIST_RELOADED, translation::java::COMMANDS_WHITELIST_RELOADED,
@ -154,10 +161,29 @@ impl CommandExecutor for ReloadExecutor {
), ),
true, true,
); );
kick_non_whitelisted_players(server);
Ok(1) Ok(1)
} }
} }
struct AddSuggestionProvider;
impl SuggestionProvider for AddSuggestionProvider {
fn suggest(
&self,
context: &CommandContext,
mut builder: SuggestionsBuilder,
) -> SuggestionProviderResult {
let whitelist = context.server().data.whitelist_config.read().unwrap();
for player in context.server().get_all_players() {
if !whitelist.is_whitelisted(&player.gameprofile) {
builder = builder.suggest(player.gameprofile.name.clone());
}
}
builder.build()
}
}
struct AddExecutor; struct AddExecutor;
impl CommandExecutor for AddExecutor { impl CommandExecutor for AddExecutor {
@ -166,6 +192,8 @@ impl CommandExecutor for AddExecutor {
let server = context.source.server(); let server = context.source.server();
let mut whitelist = server.data.whitelist_config.write().unwrap(); let mut whitelist = server.data.whitelist_config.write().unwrap();
let mut successes: i32 = 0; let mut successes: i32 = 0;
let mut modified = false;
for profile in &targets { for profile in &targets {
if let Some(existing_entry) = whitelist if let Some(existing_entry) = whitelist
.whitelist .whitelist
@ -174,6 +202,7 @@ impl CommandExecutor for AddExecutor {
{ {
if existing_entry.name != profile.name { if existing_entry.name != profile.name {
existing_entry.name.clone_from(&profile.name); existing_entry.name.clone_from(&profile.name);
modified = true;
} }
continue; continue;
} }
@ -189,9 +218,12 @@ impl CommandExecutor for AddExecutor {
true, true,
); );
successes += 1; successes += 1;
modified = true;
} }
if modified {
whitelist.save(); whitelist.save();
}
if successes == 0 { if successes == 0 {
Err(ERROR_ADD_FAILED.create_without_context()) Err(ERROR_ADD_FAILED.create_without_context())
@ -201,6 +233,22 @@ impl CommandExecutor for AddExecutor {
} }
} }
struct RemoveSuggestionProvider;
impl SuggestionProvider for RemoveSuggestionProvider {
fn suggest(
&self,
context: &CommandContext,
mut builder: SuggestionsBuilder,
) -> SuggestionProviderResult {
let whitelist = context.server().data.whitelist_config.read().unwrap();
for entry in &whitelist.whitelist {
builder = builder.suggest(entry.name.clone());
}
builder.build()
}
}
struct RemoveExecutor; struct RemoveExecutor;
impl CommandExecutor for RemoveExecutor { impl CommandExecutor for RemoveExecutor {
@ -229,14 +277,14 @@ impl CommandExecutor for RemoveExecutor {
} }
} }
if successes == 0 {
Err(ERROR_REMOVE_FAILED.create_without_context())
} else {
whitelist.save(); whitelist.save();
drop(whitelist); drop(whitelist);
kick_non_whitelisted_players(server); kick_non_whitelisted_players(server);
if successes == 0 {
Err(ERROR_REMOVE_FAILED.create_without_context())
} else {
Ok(successes) Ok(successes)
} }
} }
@ -255,14 +303,20 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
.then(literal("on").executes(OnExecutor)) .then(literal("on").executes(OnExecutor))
.then(literal("off").executes(OffExecutor)) .then(literal("off").executes(OffExecutor))
.then(literal("list").executes(ListExecutor)) .then(literal("list").executes(ListExecutor))
.then(literal("reload").executes(ReloadExecutor))
.then( .then(
literal("add") literal("add").then(
.then(argument("targets", GameProfileArgumentType).executes(AddExecutor)), argument("targets", GameProfileArgumentType)
.suggests(AddSuggestionProvider)
.executes(AddExecutor),
),
) )
.then( .then(
literal("remove") literal("remove").then(
.then(argument("targets", GameProfileArgumentType).executes(RemoveExecutor)), argument("targets", GameProfileArgumentType)
.suggests(RemoveSuggestionProvider)
.executes(RemoveExecutor),
), ),
)
.then(literal("reload").executes(ReloadExecutor)),
); );
} }

View File

@ -121,6 +121,8 @@ pub struct Server {
std::sync::Mutex<std::collections::HashMap<String, pumpkin_nbt::compound::NbtCompound>>, std::sync::Mutex<std::collections::HashMap<String, pumpkin_nbt::compound::NbtCompound>>,
/// Global server stopwatches /// Global server stopwatches
pub stopwatches: std::sync::Mutex<crate::world::stopwatches::Stopwatches>, pub stopwatches: std::sync::Mutex<crate::world::stopwatches::Stopwatches>,
/// Global server random sequences
pub random_sequences: std::sync::Mutex<crate::world::random_sequences::RandomSequences>,
// Manages player advancement // Manages player advancement
pub advancement_manager: Arc<AdvancementManager>, pub advancement_manager: Arc<AdvancementManager>,
// Whether the server whitelist is on or off // Whether the server whitelist is on or off
@ -299,6 +301,9 @@ impl Server {
player_data_storage, player_data_storage,
command_storage: std::sync::Mutex::new(std::collections::HashMap::new()), command_storage: std::sync::Mutex::new(std::collections::HashMap::new()),
stopwatches: std::sync::Mutex::new(crate::world::stopwatches::Stopwatches::new()), stopwatches: std::sync::Mutex::new(crate::world::stopwatches::Stopwatches::new()),
random_sequences: std::sync::Mutex::new(
crate::world::random_sequences::RandomSequences::new(),
),
advancement_manager, advancement_manager,
white_list, white_list,
tick_rate_manager, tick_rate_manager,

View File

@ -23,6 +23,7 @@ pub mod loot;
pub mod map; pub mod map;
pub mod portal; pub mod portal;
pub mod raid; pub mod raid;
pub mod random_sequences;
pub mod stopwatches; pub mod stopwatches;
pub mod time; pub mod time;
pub mod villager_poi; pub mod villager_poi;

View File

@ -0,0 +1,140 @@
use std::collections::HashMap;
use std::hash::{DefaultHasher, Hasher};
use pumpkin_util::identifier::Identifier;
use pumpkin_util::random::RandomImpl;
use pumpkin_util::random::xoroshiro128::Xoroshiro;
/// A single random sequence wrapper.
pub struct RandomSequence {
rng: Xoroshiro,
}
impl RandomSequence {
#[must_use]
pub const fn new(seed: u64) -> Self {
Self {
rng: Xoroshiro::from_seed(seed),
}
}
pub fn random_between_inclusive(&mut self, min: i32, max: i32) -> i32 {
if min >= max {
return min;
}
self.rng.next_inbetween_i32(min, max)
}
}
/// Persistent/runtime manager for server random sequences.
pub struct RandomSequences {
salt: i32,
include_world_seed: bool,
include_sequence_id: bool,
sequences: HashMap<String, RandomSequence>,
}
impl Default for RandomSequences {
fn default() -> Self {
Self::new()
}
}
impl RandomSequences {
#[must_use]
pub fn new() -> Self {
Self {
salt: 0,
include_world_seed: true,
include_sequence_id: true,
sequences: HashMap::new(),
}
}
fn calculate_seed(
sequence: &Identifier,
world_seed: i64,
salt: i32,
include_world_seed: bool,
include_sequence_id: bool,
) -> u64 {
let base_seed = if include_world_seed { world_seed } else { 0 };
let mut seed = (base_seed ^ i64::from(salt)) as u64;
if include_sequence_id {
let mut hasher = DefaultHasher::new();
hasher.write(sequence.to_string().as_bytes());
seed ^= hasher.finish();
}
seed
}
pub fn get_or_create(&mut self, sequence: &Identifier, world_seed: i64) -> &mut RandomSequence {
let key = sequence.to_string();
let salt = self.salt;
let include_world_seed = self.include_world_seed;
let include_sequence_id = self.include_sequence_id;
self.sequences.entry(key).or_insert_with(|| {
let seed = Self::calculate_seed(
sequence,
world_seed,
salt,
include_world_seed,
include_sequence_id,
);
RandomSequence::new(seed)
})
}
pub fn reset(&mut self, sequence: &Identifier, world_seed: i64) {
let key = sequence.to_string();
let seed = Self::calculate_seed(
sequence,
world_seed,
self.salt,
self.include_world_seed,
self.include_sequence_id,
);
self.sequences.insert(key, RandomSequence::new(seed));
}
pub fn reset_with_options(
&mut self,
sequence: &Identifier,
world_seed: i64,
salt: i32,
include_world_seed: bool,
include_sequence_id: bool,
) {
let key = sequence.to_string();
let seed = Self::calculate_seed(
sequence,
world_seed,
salt,
include_world_seed,
include_sequence_id,
);
self.sequences.insert(key, RandomSequence::new(seed));
}
pub fn clear(&mut self) -> usize {
let count = self.sequences.len();
self.sequences.clear();
count
}
pub const fn set_seed_defaults(
&mut self,
salt: i32,
include_world_seed: bool,
include_sequence_id: bool,
) {
self.salt = salt;
self.include_world_seed = include_world_seed;
self.include_sequence_id = include_sequence_id;
}
#[must_use]
pub fn get_sequence_keys(&self) -> Vec<String> {
self.sequences.keys().cloned().collect()
}
}