This commit is contained in:
parent
2be3e8be88
commit
d1cfc0d181
1
.gitignore
vendored
1
.gitignore
vendored
@ -157,3 +157,4 @@ version_history.json
|
||||
world_nether/
|
||||
world_the_end/
|
||||
patchbukkit/
|
||||
minecraft-sources*/
|
||||
|
||||
@ -352,18 +352,33 @@ impl DataComponentImpl for CustomModelDataImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct TooltipDisplayImpl;
|
||||
impl TooltipDisplayImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for TooltipDisplayImpl {
|
||||
default_impl!(TooltipDisplay);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct CreativeSlotLockImpl;
|
||||
impl CreativeSlotLockImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for CreativeSlotLockImpl {
|
||||
default_impl!(CreativeSlotLock);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct EnchantmentGlintOverrideImpl;
|
||||
impl EnchantmentGlintOverrideImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for EnchantmentGlintOverrideImpl {
|
||||
default_impl!(EnchantmentGlintOverride);
|
||||
}
|
||||
@ -431,18 +446,33 @@ impl DataComponentImpl for BaseColorImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct InstrumentImpl;
|
||||
impl InstrumentImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for InstrumentImpl {
|
||||
default_impl!(Instrument);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ProvidesTrimMaterialImpl;
|
||||
impl ProvidesTrimMaterialImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for ProvidesTrimMaterialImpl {
|
||||
default_impl!(ProvidesTrimMaterial);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ProvidesBannerPatternsImpl;
|
||||
impl ProvidesBannerPatternsImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for ProvidesBannerPatternsImpl {
|
||||
default_impl!(ProvidesBannerPatterns);
|
||||
}
|
||||
@ -495,6 +525,11 @@ impl DataComponentImpl for BannerPatternsImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct PotDecorationsImpl;
|
||||
impl PotDecorationsImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for PotDecorationsImpl {
|
||||
default_impl!(PotDecorations);
|
||||
}
|
||||
@ -522,6 +557,11 @@ impl DataComponentImpl for LockImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct BreakSoundImpl;
|
||||
impl BreakSoundImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for BreakSoundImpl {
|
||||
default_impl!(BreakSound);
|
||||
}
|
||||
|
||||
@ -26,12 +26,22 @@ impl DataComponentImpl for BlockEntityDataImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct EntityDataImpl;
|
||||
impl EntityDataImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for EntityDataImpl {
|
||||
default_impl!(EntityData);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct BucketEntityDataImpl;
|
||||
impl BucketEntityDataImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for BucketEntityDataImpl {
|
||||
default_impl!(BucketEntityData);
|
||||
}
|
||||
@ -148,6 +158,11 @@ impl DataComponentImpl for BlockStateImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct BeesImpl;
|
||||
impl BeesImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for BeesImpl {
|
||||
default_impl!(Bees);
|
||||
}
|
||||
@ -177,6 +192,11 @@ impl DataComponentImpl for ContainerLootImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct SulfurCubeContentImpl;
|
||||
impl SulfurCubeContentImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for SulfurCubeContentImpl {
|
||||
default_impl!(SulfurCubeContent);
|
||||
}
|
||||
|
||||
@ -90,6 +90,11 @@ impl DataComponentImpl for WrittenBookContentImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct DebugStickStateImpl;
|
||||
impl DebugStickStateImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for DebugStickStateImpl {
|
||||
default_impl!(DebugStickState);
|
||||
}
|
||||
|
||||
@ -48,6 +48,13 @@ impl Hash for Modifier {
|
||||
pub struct AttributeModifiersImpl {
|
||||
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 {
|
||||
default_impl!(AttributeModifiers);
|
||||
}
|
||||
@ -742,12 +749,22 @@ impl DataComponentImpl for GliderImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct DeathProtectionImpl;
|
||||
impl DeathProtectionImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for DeathProtectionImpl {
|
||||
default_impl!(DeathProtection);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct BlocksAttacksImpl;
|
||||
impl BlocksAttacksImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for BlocksAttacksImpl {
|
||||
default_impl!(BlocksAttacks);
|
||||
}
|
||||
@ -1037,6 +1054,11 @@ impl DataComponentImpl for SwingAnimationImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct AdditionalTradeCostImpl;
|
||||
impl AdditionalTradeCostImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for AdditionalTradeCostImpl {
|
||||
default_impl!(AdditionalTradeCost);
|
||||
}
|
||||
|
||||
@ -384,12 +384,22 @@ impl Hash for ConsumableImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct UseEffectsImpl;
|
||||
impl UseEffectsImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for UseEffectsImpl {
|
||||
default_impl!(UseEffects);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct UseRemainderImpl;
|
||||
impl UseRemainderImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for UseRemainderImpl {
|
||||
default_impl!(UseRemainder);
|
||||
}
|
||||
|
||||
@ -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::Rarity => Some(RarityImpl::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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,11 @@ use pumpkin_nbt::tag::NbtTag;
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct DyeImpl;
|
||||
impl DyeImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for DyeImpl {
|
||||
default_impl!(Dye);
|
||||
}
|
||||
@ -31,6 +36,11 @@ impl DataComponentImpl for DyedColorImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct MapColorImpl;
|
||||
impl MapColorImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for MapColorImpl {
|
||||
default_impl!(MapColor);
|
||||
}
|
||||
@ -56,6 +66,11 @@ impl DataComponentImpl for MapIdImpl {
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct MapDecorationsImpl;
|
||||
impl MapDecorationsImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for MapDecorationsImpl {
|
||||
default_impl!(MapDecorations);
|
||||
}
|
||||
@ -590,12 +605,28 @@ impl DataComponentImpl for ProfileImpl {
|
||||
pub struct JukeboxPlayableImpl {
|
||||
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 {
|
||||
default_impl!(JukeboxPlayable);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct RecipesImpl;
|
||||
impl RecipesImpl {
|
||||
pub const fn read_data(_data: &NbtTag) -> Option<Self> {
|
||||
Some(Self)
|
||||
}
|
||||
}
|
||||
impl DataComponentImpl for RecipesImpl {
|
||||
default_impl!(Recipes);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -49,8 +49,17 @@ impl GameProfileResult {
|
||||
///
|
||||
/// Instead, call this method *before* using `write()`/`read()` on a lock.
|
||||
pub fn resolve(&self, source: &CommandSource) -> Result<Vec<GameProfile>, CommandSyntaxError> {
|
||||
let players = match self {
|
||||
Self::Selector(selector) => selector.find_players(source),
|
||||
match self {
|
||||
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) => {
|
||||
let server = source.server();
|
||||
if let Some(player) = server.get_player_by_name(name) {
|
||||
@ -72,7 +81,7 @@ impl GameProfileResult {
|
||||
}
|
||||
|
||||
if server.advanced_config.networking.java.online_mode {
|
||||
return match lookup_profile_by_name_blocking(
|
||||
match lookup_profile_by_name_blocking(
|
||||
name,
|
||||
&server.advanced_config.networking.java.authentication,
|
||||
) {
|
||||
@ -86,7 +95,7 @@ impl GameProfileResult {
|
||||
Ok(vec![Self::profile_from_uuid_name(uuid, resolved_name)])
|
||||
}
|
||||
_ => Err(Self::unknown_player_syntax_error()),
|
||||
};
|
||||
}
|
||||
} else if let Ok(uuid) = offline_uuid(name) {
|
||||
let profile = Self::profile_from_uuid_name(uuid, name.clone());
|
||||
server
|
||||
@ -95,10 +104,10 @@ impl GameProfileResult {
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.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) => {
|
||||
let server = source.server();
|
||||
@ -121,11 +130,9 @@ impl GameProfileResult {
|
||||
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> {
|
||||
|
||||
@ -28,7 +28,6 @@ const ARG_TARGETS: &str = "targets";
|
||||
const ARG_ADVANCEMENT: &str = "advancement";
|
||||
const ARG_CRITERION: &str = "criterion";
|
||||
|
||||
#[allow(unused)]
|
||||
const ERROR_CRITERION_NOT_FOUND: CommandErrorType<2> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_ADVANCEMENT_CRITERIONNOTFOUND,
|
||||
translation::java::COMMANDS_ADVANCEMENT_CRITERIONNOTFOUND,
|
||||
@ -112,7 +111,7 @@ impl Action {
|
||||
true
|
||||
}
|
||||
Self::Revoke => {
|
||||
if !progress.is_done() {
|
||||
if !progress.has_progress() {
|
||||
return false;
|
||||
}
|
||||
let criteria: Vec<Arc<str>> = progress.get_completed_criteria().collect();
|
||||
@ -306,11 +305,16 @@ fn perform(
|
||||
advancements: &[&'static Advancement],
|
||||
show_advancement: bool,
|
||||
) -> Result<i32, CommandSyntaxError> {
|
||||
let mut i = 0;
|
||||
let mut advancement_count = 0;
|
||||
let mut player_count = 0;
|
||||
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[..] {
|
||||
if let [first_player] = targets {
|
||||
Err(match action {
|
||||
@ -362,7 +366,7 @@ fn perform(
|
||||
format!("{}.one.to.many.success", action.get_key()),
|
||||
[
|
||||
first_advancement.name(),
|
||||
TextComponent::text(targets.len().to_string()),
|
||||
TextComponent::text(player_count.to_string()),
|
||||
],
|
||||
)
|
||||
}
|
||||
@ -370,7 +374,7 @@ fn perform(
|
||||
TextComponent::translate(
|
||||
format!("{}.many.to.one.success", action.get_key()),
|
||||
[
|
||||
TextComponent::text(advancements.len().to_string()),
|
||||
TextComponent::text(advancement_count.to_string()),
|
||||
first.get_display_name(),
|
||||
],
|
||||
)
|
||||
@ -378,13 +382,13 @@ fn perform(
|
||||
TextComponent::translate(
|
||||
format!("{}.many.to.many.success", action.get_key()),
|
||||
[
|
||||
TextComponent::text(advancements.len().to_string()),
|
||||
TextComponent::text(targets.len().to_string()),
|
||||
TextComponent::text(advancement_count.to_string()),
|
||||
TextComponent::text(player_count.to_string()),
|
||||
],
|
||||
)
|
||||
};
|
||||
context.send_feedback(translate, true);
|
||||
Ok(i)
|
||||
Ok(advancement_count)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
criterion: &str,
|
||||
) -> 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
|
||||
.iter()
|
||||
.map(|player| action.perform_criterion(player, advancement, criterion))
|
||||
.filter(|&success| success)
|
||||
.count() as i32;
|
||||
|
||||
if count == 0 {
|
||||
if let [first_player] = targets {
|
||||
Err(match action {
|
||||
@ -458,21 +471,13 @@ pub fn perform_criterion(
|
||||
[
|
||||
TextComponent::text(criterion.to_owned()),
|
||||
advancement.name(),
|
||||
TextComponent::text(targets.len().to_string()),
|
||||
TextComponent::text(count.to_string()),
|
||||
],
|
||||
)
|
||||
};
|
||||
context.send_feedback(translate, true);
|
||||
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
|
||||
@ -537,17 +542,19 @@ impl SuggestionProvider for CriterionSuggestionProvider {
|
||||
/// executor to grant/revoke every advancement to specified players
|
||||
struct EveryAdvancementExecutor {
|
||||
action: Action,
|
||||
show_advancement: bool,
|
||||
}
|
||||
|
||||
impl CommandExecutor for EveryAdvancementExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
let action = self.action;
|
||||
let show_advancement = self.show_advancement;
|
||||
perform(
|
||||
&context.source,
|
||||
&EntityArgumentType::get_players(context, ARG_TARGETS)?,
|
||||
action,
|
||||
&Advancement::get_advancements_list(),
|
||||
false,
|
||||
show_advancement,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -607,10 +614,10 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
}),
|
||||
),
|
||||
)
|
||||
.then(
|
||||
literal("everything")
|
||||
.executes(EveryAdvancementExecutor { action: $action }),
|
||||
),
|
||||
.then(literal("everything").executes(EveryAdvancementExecutor {
|
||||
action: $action,
|
||||
show_advancement: matches!($action, Action::Revoke),
|
||||
})),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ struct BanExecutor {
|
||||
|
||||
impl CommandExecutor for BanExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
let targets = GameProfileArgumentType::get(context, "player")?;
|
||||
let targets = GameProfileArgumentType::get(context, "targets")?;
|
||||
let reason = if self.has_reason {
|
||||
Some(StringArgumentType::get(context, "reason")?.to_string())
|
||||
} else {
|
||||
@ -111,7 +111,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
|
||||
dispatcher.register(
|
||||
command("ban", DESCRIPTION).requires(PERMISSION).then(
|
||||
argument("player", GameProfileArgumentType)
|
||||
argument("targets", GameProfileArgumentType)
|
||||
.executes(BanExecutor { has_reason: false })
|
||||
.then(
|
||||
argument("reason", StringArgumentType::GreedyPhrase)
|
||||
|
||||
@ -14,6 +14,7 @@ use crate::command::node::dispatcher::CommandDispatcher;
|
||||
use crate::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
use crate::data::SaveJSONConfiguration;
|
||||
use crate::data::banlist_serializer::BannedIpEntry;
|
||||
use crate::entity::EntityBase;
|
||||
use crate::net::DisconnectReason;
|
||||
use crate::server::Server;
|
||||
|
||||
@ -80,6 +81,26 @@ fn ban_ip(context: &CommandContext, target: &str, reason: Option<String>) -> Com
|
||||
.cloned()
|
||||
.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 {
|
||||
let kick_msg = TextComponent::translate_cross(
|
||||
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);
|
||||
}
|
||||
|
||||
Ok(1)
|
||||
Ok(kick_count as i32)
|
||||
}
|
||||
|
||||
struct BanIpExecutor {
|
||||
@ -98,7 +119,7 @@ struct BanIpExecutor {
|
||||
|
||||
impl CommandExecutor for BanIpExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
let target = StringArgumentType::get(context, "ip")?;
|
||||
let target = StringArgumentType::get(context, "target")?;
|
||||
let reason = if self.has_reason {
|
||||
Some(StringArgumentType::get(context, "reason")?.to_string())
|
||||
} else {
|
||||
@ -117,7 +138,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
));
|
||||
|
||||
let cmd = command("ban-ip", DESCRIPTION).requires(PERMISSION).then(
|
||||
argument("ip", StringArgumentType::SingleWord)
|
||||
argument("target", StringArgumentType::SingleWord)
|
||||
.executes(BanIpExecutor { has_reason: false })
|
||||
.then(
|
||||
argument("reason", StringArgumentType::GreedyPhrase)
|
||||
|
||||
@ -278,7 +278,7 @@ impl CommandExecutor for GetExecutor {
|
||||
TextComponent::text(bossbar.max.to_string()),
|
||||
],
|
||||
),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
Ok(bossbar.max)
|
||||
}
|
||||
@ -298,7 +298,7 @@ impl CommandExecutor for GetExecutor {
|
||||
translation::bedrock::COMMANDS_BOSSBAR_GET_PLAYERS_NONE,
|
||||
[bossbar_prefix(bossbar.bossbar_data.title, namespace)],
|
||||
),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
context.source.send_feedback(
|
||||
@ -315,7 +315,7 @@ impl CommandExecutor for GetExecutor {
|
||||
TextComponent::text(online_players.join(", ")),
|
||||
],
|
||||
),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
}
|
||||
Ok(count)
|
||||
@ -330,7 +330,7 @@ impl CommandExecutor for GetExecutor {
|
||||
TextComponent::text(bossbar.value.to_string()),
|
||||
],
|
||||
),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
Ok(bossbar.value)
|
||||
}
|
||||
@ -355,7 +355,7 @@ impl CommandExecutor for GetExecutor {
|
||||
namespace,
|
||||
)],
|
||||
),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
Ok(bossbar.visible as i32)
|
||||
}
|
||||
@ -509,7 +509,7 @@ impl CommandExecutor for SetExecutor {
|
||||
true,
|
||||
);
|
||||
|
||||
Ok(0)
|
||||
Ok(max_value)
|
||||
}
|
||||
CommandValueSet::Name => {
|
||||
let name = ComponentArgumentType::get(context, "name")?;
|
||||
|
||||
@ -85,7 +85,7 @@ impl CommandExecutor for DamageEntityExecutor {
|
||||
|
||||
let source = match self.0 {
|
||||
EntityMode::WithSource | EntityMode::WithSourceAndCause => {
|
||||
Some(EntityArgumentType::get_entity(context, "source")?)
|
||||
Some(EntityArgumentType::get_entity(context, "entity")?)
|
||||
}
|
||||
EntityMode::Basic | EntityMode::WithType => None,
|
||||
};
|
||||
@ -133,7 +133,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
)
|
||||
.then(
|
||||
literal("by").then(
|
||||
argument("source", EntityArgumentType::Entity)
|
||||
argument("entity", EntityArgumentType::Entity)
|
||||
.executes(DamageEntityExecutor(EntityMode::WithSource))
|
||||
.then(literal("from").then(
|
||||
argument("cause", EntityArgumentType::Entity).executes(
|
||||
|
||||
@ -8,6 +8,8 @@ use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
use crate::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
use crate::command::suggestion::provider::{SuggestionProvider, SuggestionProviderResult};
|
||||
use crate::command::suggestion::suggestions::SuggestionsBuilder;
|
||||
use crate::data::SaveJSONConfiguration;
|
||||
|
||||
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 {
|
||||
Err(ERROR_DEOP_FAILED.create_without_context())
|
||||
} else {
|
||||
config.save();
|
||||
drop(config);
|
||||
|
||||
crate::command::commands::whitelist::kick_non_whitelisted_players(server);
|
||||
|
||||
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) {
|
||||
registry.register_permission_or_panic(Permission::new(
|
||||
PERMISSION,
|
||||
@ -72,8 +91,10 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
));
|
||||
|
||||
dispatcher.register(
|
||||
command("deop", DESCRIPTION)
|
||||
.requires(PERMISSION)
|
||||
.then(argument("targets", GameProfileArgumentType).executes(DeopExecutor)),
|
||||
command("deop", DESCRIPTION).requires(PERMISSION).then(
|
||||
argument("targets", GameProfileArgumentType)
|
||||
.suggests(DeopSuggestionProvider)
|
||||
.executes(DeopExecutor),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -134,7 +134,7 @@ impl ExperienceExecutor {
|
||||
target.add_experience_points(amount);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
target.set_experience_points(amount);
|
||||
@ -147,7 +147,7 @@ impl ExperienceExecutor {
|
||||
|
||||
impl CommandExecutor for ExperienceExecutor {
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
|
||||
@ -171,7 +171,7 @@ impl CommandExecutor for ExperienceExecutor {
|
||||
let msg = self.get_success_message(amount, &targets, first_name);
|
||||
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(
|
||||
argument("targets", EntityArgumentType::Players).then(
|
||||
argument("amount", IntegerArgumentType::with_min(0))
|
||||
argument("target", EntityArgumentType::Players).then(
|
||||
argument("amount", IntegerArgumentType::any())
|
||||
.executes(ExperienceExecutor {
|
||||
mode: Mode::Add,
|
||||
exp_type: ExpType::Points,
|
||||
})
|
||||
.then(literal("levels").executes(ExperienceExecutor {
|
||||
mode: Mode::Add,
|
||||
exp_type: ExpType::Levels,
|
||||
}))
|
||||
.then(literal("points").executes(ExperienceExecutor {
|
||||
mode: Mode::Add,
|
||||
exp_type: ExpType::Points,
|
||||
}))
|
||||
.then(literal("levels").executes(ExperienceExecutor {
|
||||
mode: Mode::Add,
|
||||
exp_type: ExpType::Levels,
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
let set_node = literal("set").then(
|
||||
argument("targets", EntityArgumentType::Players).then(
|
||||
argument("target", EntityArgumentType::Players).then(
|
||||
argument("amount", IntegerArgumentType::with_min(0))
|
||||
.executes(ExperienceExecutor {
|
||||
mode: Mode::Set,
|
||||
exp_type: ExpType::Points,
|
||||
})
|
||||
.then(literal("levels").executes(ExperienceExecutor {
|
||||
mode: Mode::Set,
|
||||
exp_type: ExpType::Levels,
|
||||
}))
|
||||
.then(literal("points").executes(ExperienceExecutor {
|
||||
mode: Mode::Set,
|
||||
exp_type: ExpType::Points,
|
||||
}))
|
||||
.then(literal("levels").executes(ExperienceExecutor {
|
||||
mode: Mode::Set,
|
||||
exp_type: ExpType::Levels,
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
let query_node = literal("query").then(
|
||||
argument("targets", EntityArgumentType::Player)
|
||||
.then(literal("levels").executes(ExperienceExecutor {
|
||||
mode: Mode::Query,
|
||||
exp_type: ExpType::Levels,
|
||||
}))
|
||||
argument("target", EntityArgumentType::Player)
|
||||
.then(literal("points").executes(ExperienceExecutor {
|
||||
mode: Mode::Query,
|
||||
exp_type: ExpType::Points,
|
||||
}))
|
||||
.then(literal("levels").executes(ExperienceExecutor {
|
||||
mode: Mode::Query,
|
||||
exp_type: ExpType::Levels,
|
||||
})),
|
||||
);
|
||||
|
||||
|
||||
@ -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::{
|
||||
BlockPosArgumentType, OUT_OF_BOUNDS_ERROR_TYPE,
|
||||
};
|
||||
@ -67,7 +67,7 @@ impl CommandExecutor for FillBiomeExecutor {
|
||||
let target_biome = ResourceKeyArgument::get_biome(context, "biome")?;
|
||||
|
||||
let replace_biome = if self.has_replace {
|
||||
Some(ResourceKeyArgument::get_biome(context, "replace_biome")?)
|
||||
Some(ResourceKeyArgument::get_biome(context, "filter")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@ -126,11 +126,19 @@ impl CommandExecutor for FillBiomeExecutor {
|
||||
let msg = TextComponent::translate_cross(
|
||||
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);
|
||||
|
||||
Ok(1)
|
||||
Ok(changed_count)
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,11 +155,13 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
argument("biome", ResourceKeyArgument(BIOME_REGISTRY))
|
||||
.executes(FillBiomeExecutor { has_replace: false })
|
||||
.then(
|
||||
argument("replace_biome", ResourceKeyArgument(BIOME_REGISTRY))
|
||||
literal("replace").then(
|
||||
argument("filter", ResourceKeyArgument(BIOME_REGISTRY))
|
||||
.executes(FillBiomeExecutor { has_replace: true }),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
dispatcher.register(builder);
|
||||
|
||||
@ -10,7 +10,6 @@ use pumpkin_util::PermissionLvl;
|
||||
use pumpkin_util::math::vector2::Vector2;
|
||||
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_util::text::color::{Color, NamedColor};
|
||||
|
||||
const DESCRIPTION: &str = "Constantly load chunks in the world.";
|
||||
const PERMISSION: &str = "minecraft:command.forceload";
|
||||
@ -86,13 +85,15 @@ impl CommandExecutor for ForceloadAddExecutor {
|
||||
|
||||
world.update_active_chunks();
|
||||
|
||||
let dimension_name = world.dimension.minecraft_name.to_string();
|
||||
|
||||
let text = if total_chunks == 1 {
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_FORCELOAD_ADDED_SINGLE,
|
||||
translation::java::COMMANDS_FORCELOAD_ADDED_SINGLE,
|
||||
[
|
||||
TextComponent::text(min_x.to_string()),
|
||||
TextComponent::text(min_z.to_string()),
|
||||
TextComponent::text(format!("[{min_x}, {min_z}]")),
|
||||
TextComponent::text(dimension_name),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
@ -101,16 +102,13 @@ impl CommandExecutor for ForceloadAddExecutor {
|
||||
translation::java::COMMANDS_FORCELOAD_ADDED_MULTIPLE,
|
||||
[
|
||||
TextComponent::text(total_chunks.to_string()),
|
||||
TextComponent::text(min_x.to_string()),
|
||||
TextComponent::text(min_z.to_string()),
|
||||
TextComponent::text(max_x.to_string()),
|
||||
TextComponent::text(max_z.to_string()),
|
||||
TextComponent::text(dimension_name),
|
||||
TextComponent::text(format!("[{min_x}, {min_z}]")),
|
||||
TextComponent::text(format!("[{max_x}, {max_z}]")),
|
||||
],
|
||||
)
|
||||
};
|
||||
context
|
||||
.source
|
||||
.send_feedback(text.color(Color::Named(NamedColor::Green)), false);
|
||||
context.source.send_feedback(text, true);
|
||||
|
||||
Ok(total_chunks)
|
||||
}
|
||||
@ -170,13 +168,15 @@ impl CommandExecutor for ForceloadRemoveExecutor {
|
||||
|
||||
world.update_active_chunks();
|
||||
|
||||
let dimension_name = world.dimension.minecraft_name.to_string();
|
||||
|
||||
let text = if total_chunks == 1 {
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_FORCELOAD_REMOVED_SINGLE,
|
||||
translation::java::COMMANDS_FORCELOAD_REMOVED_SINGLE,
|
||||
[
|
||||
TextComponent::text(min_x.to_string()),
|
||||
TextComponent::text(min_z.to_string()),
|
||||
TextComponent::text(format!("[{min_x}, {min_z}]")),
|
||||
TextComponent::text(dimension_name),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
@ -185,16 +185,13 @@ impl CommandExecutor for ForceloadRemoveExecutor {
|
||||
translation::java::COMMANDS_FORCELOAD_REMOVED_MULTIPLE,
|
||||
[
|
||||
TextComponent::text(total_chunks.to_string()),
|
||||
TextComponent::text(min_x.to_string()),
|
||||
TextComponent::text(min_z.to_string()),
|
||||
TextComponent::text(max_x.to_string()),
|
||||
TextComponent::text(max_z.to_string()),
|
||||
TextComponent::text(dimension_name),
|
||||
TextComponent::text(format!("[{min_x}, {min_z}]")),
|
||||
TextComponent::text(format!("[{max_x}, {max_z}]")),
|
||||
],
|
||||
)
|
||||
};
|
||||
context
|
||||
.source
|
||||
.send_feedback(text.color(Color::Named(NamedColor::Green)), false);
|
||||
context.source.send_feedback(text, true);
|
||||
|
||||
Ok(total_chunks)
|
||||
}
|
||||
@ -222,14 +219,14 @@ impl CommandExecutor for ForceloadRemoveAllExecutor {
|
||||
|
||||
world.update_active_chunks();
|
||||
|
||||
let dimension_name = world.dimension.minecraft_name.to_string();
|
||||
|
||||
let text = TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_FORCELOAD_REMOVED_ALL,
|
||||
translation::java::COMMANDS_FORCELOAD_REMOVED_ALL,
|
||||
[],
|
||||
[TextComponent::text(dimension_name)],
|
||||
);
|
||||
context
|
||||
.source
|
||||
.send_feedback(text.color(Color::Named(NamedColor::Green)), false);
|
||||
context.source.send_feedback(text, true);
|
||||
|
||||
Ok(removed_count as i32)
|
||||
}
|
||||
@ -262,30 +259,28 @@ impl CommandExecutor for ForceloadQueryExecutor {
|
||||
forced.contains(&chunk_pos)
|
||||
};
|
||||
|
||||
let dimension_name = world.dimension.minecraft_name.to_string();
|
||||
|
||||
if is_forced {
|
||||
let text = TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_FORCELOAD_QUERY_SUCCESS,
|
||||
translation::java::COMMANDS_FORCELOAD_QUERY_SUCCESS,
|
||||
[
|
||||
TextComponent::text(chunk_pos.x.to_string()),
|
||||
TextComponent::text(chunk_pos.y.to_string()),
|
||||
TextComponent::text(format!("[{}, {}]", chunk_pos.x, chunk_pos.y)),
|
||||
TextComponent::text(dimension_name),
|
||||
],
|
||||
);
|
||||
context
|
||||
.source
|
||||
.send_feedback(text.color(Color::Named(NamedColor::Green)), false);
|
||||
context.source.send_feedback(text, false);
|
||||
} else {
|
||||
let text = TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_FORCELOAD_QUERY_FAILURE,
|
||||
translation::java::COMMANDS_FORCELOAD_QUERY_FAILURE,
|
||||
[
|
||||
TextComponent::text(chunk_pos.x.to_string()),
|
||||
TextComponent::text(chunk_pos.y.to_string()),
|
||||
TextComponent::text(format!("[{}, {}]", chunk_pos.x, chunk_pos.y)),
|
||||
TextComponent::text(dimension_name),
|
||||
],
|
||||
);
|
||||
context
|
||||
.source
|
||||
.send_feedback(text.color(Color::Named(NamedColor::Red)), false);
|
||||
context.source.send_error(text);
|
||||
}
|
||||
|
||||
let all_forced = {
|
||||
@ -303,29 +298,34 @@ impl CommandExecutor for ForceloadQueryExecutor {
|
||||
let text = TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_FORCELOAD_ADDED_NONE,
|
||||
translation::java::COMMANDS_FORCELOAD_ADDED_NONE,
|
||||
[],
|
||||
[TextComponent::text(
|
||||
world.dimension.minecraft_name.to_string(),
|
||||
)],
|
||||
);
|
||||
context
|
||||
.source
|
||||
.send_feedback(text.color(Color::Named(NamedColor::Gray)), false);
|
||||
context.source.send_error(text);
|
||||
} else {
|
||||
let list_str = all_forced.join(", ");
|
||||
let text = if all_forced.len() == 1 {
|
||||
TextComponent::translate_cross(
|
||||
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 {
|
||||
TextComponent::translate_cross(
|
||||
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
|
||||
.source
|
||||
.send_feedback(text.color(Color::Named(NamedColor::Gray)), false);
|
||||
context.source.send_feedback(text, false);
|
||||
}
|
||||
|
||||
Ok(i32::from(is_forced))
|
||||
|
||||
@ -290,21 +290,15 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
PermissionDefault::Op(PermissionLvl::Two),
|
||||
));
|
||||
|
||||
let block_node = literal("block").then(
|
||||
argument("pos", BlockPosArgumentType).then(
|
||||
argument("slot", SlotArgumentType)
|
||||
.then(block_item_node())
|
||||
.then(literal("with").then(block_item_node())),
|
||||
),
|
||||
);
|
||||
let block_node = literal("block")
|
||||
.then(argument("pos", BlockPosArgumentType).then(
|
||||
argument("slot", SlotArgumentType).then(literal("with").then(block_item_node())),
|
||||
));
|
||||
|
||||
let entity_node = literal("entity").then(
|
||||
argument("targets", EntityArgumentType::Entities).then(
|
||||
argument("slot", SlotArgumentType)
|
||||
.then(entity_item_node())
|
||||
.then(literal("with").then(entity_item_node())),
|
||||
),
|
||||
);
|
||||
let entity_node =
|
||||
literal("entity").then(argument("targets", EntityArgumentType::Entities).then(
|
||||
argument("slot", SlotArgumentType).then(literal("with").then(entity_item_node())),
|
||||
));
|
||||
|
||||
dispatcher.register(
|
||||
command("item", DESCRIPTION)
|
||||
|
||||
@ -69,7 +69,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
registry.register_permission_or_panic(Permission::new(
|
||||
PERMISSION,
|
||||
DESCRIPTION,
|
||||
PermissionDefault::Op(PermissionLvl::Four),
|
||||
PermissionDefault::Op(PermissionLvl::Two),
|
||||
));
|
||||
|
||||
dispatcher.register(
|
||||
|
||||
@ -31,7 +31,7 @@ struct PardonIpExecutor;
|
||||
|
||||
impl CommandExecutor for PardonIpExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
let target = StringArgumentType::get(context, "ip")?;
|
||||
let target = StringArgumentType::get(context, "target")?;
|
||||
let ip = IpAddr::from_str(target)
|
||||
.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)
|
||||
.requires(PERMISSION)
|
||||
.then(argument("ip", StringArgumentType::SingleWord).executes(PardonIpExecutor));
|
||||
.then(argument("target", StringArgumentType::SingleWord).executes(PardonIpExecutor));
|
||||
|
||||
dispatcher.register_with_aliases(cmd, &["pardonip"]);
|
||||
}
|
||||
|
||||
@ -357,7 +357,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
registry.register_permission_or_panic(Permission::new(
|
||||
PERMISSION,
|
||||
DESCRIPTION,
|
||||
PermissionDefault::Op(PermissionLvl::Two),
|
||||
PermissionDefault::Op(PermissionLvl::Three),
|
||||
));
|
||||
|
||||
dispatcher.register(
|
||||
|
||||
@ -1,22 +1,31 @@
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::PermissionLvl;
|
||||
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
use crate::command::argument_builder::{ArgumentBuilder, 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::context::command_context::CommandContext;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
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 PERMISSION: &str = "minecraft:command.random";
|
||||
const PERMISSION_RESET: &str = "minecraft:command.random.reset";
|
||||
|
||||
const ARG_RANGE: &str = "range";
|
||||
|
||||
/// The largest allowed range span, matching Vanilla (`i32::MAX - 1`).
|
||||
const MAX_RANGE_SPAN: i64 = (i32::MAX - 1) as i64;
|
||||
const ARG_SEQUENCE: &str = "sequence";
|
||||
const ARG_SEED: &str = "seed";
|
||||
const ARG_INCLUDE_WORLD_SEED: &str = "includeWorldSeed";
|
||||
const ARG_INCLUDE_SEQUENCE_ID: &str = "includeSequenceId";
|
||||
|
||||
const RANGE_TOO_LARGE_ERROR_TYPE: CommandErrorType<0> = CommandErrorType::new(
|
||||
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,
|
||||
);
|
||||
|
||||
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 {
|
||||
/// Whether the result is announced to every player (`roll`) instead of
|
||||
/// only the command source (`value`).
|
||||
roll: bool,
|
||||
has_sequence: bool,
|
||||
}
|
||||
|
||||
impl CommandExecutor for RandomExecutor {
|
||||
@ -44,11 +68,20 @@ impl CommandExecutor for RandomExecutor {
|
||||
if span == 0 {
|
||||
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());
|
||||
}
|
||||
|
||||
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 {
|
||||
let msg = TextComponent::translate_cross(
|
||||
@ -56,7 +89,7 @@ impl CommandExecutor for RandomExecutor {
|
||||
translation::java::COMMANDS_RANDOM_ROLL,
|
||||
[
|
||||
context.source.display_name.clone(),
|
||||
TextComponent::text(result.to_string()),
|
||||
TextComponent::text(value.to_string()),
|
||||
TextComponent::text(min.to_string()),
|
||||
TextComponent::text(max.to_string()),
|
||||
],
|
||||
@ -64,7 +97,6 @@ impl CommandExecutor for RandomExecutor {
|
||||
for player in context.server().get_all_players() {
|
||||
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() {
|
||||
context.source.send_message(msg);
|
||||
}
|
||||
@ -73,32 +105,209 @@ impl CommandExecutor for RandomExecutor {
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_RANDOM_SAMPLE_SUCCESS,
|
||||
translation::java::COMMANDS_RANDOM_SAMPLE_SUCCESS,
|
||||
[TextComponent::text(result.to_string())],
|
||||
[TextComponent::text(value.to_string())],
|
||||
),
|
||||
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) {
|
||||
registry.register_permission_or_panic(Permission::new(
|
||||
PERMISSION,
|
||||
DESCRIPTION,
|
||||
// Vanilla allows every player to use `/random value` and `/random roll`.
|
||||
PermissionDefault::Allow,
|
||||
));
|
||||
|
||||
registry.register_permission_or_panic(Permission::new(
|
||||
PERMISSION_RESET,
|
||||
DESCRIPTION,
|
||||
PermissionDefault::Op(PermissionLvl::Two),
|
||||
));
|
||||
|
||||
dispatcher.register(
|
||||
command("random", DESCRIPTION)
|
||||
.requires(PERMISSION)
|
||||
.then(literal("value").then(
|
||||
argument(ARG_RANGE, IntRangeArgumentType).executes(RandomExecutor { roll: false }),
|
||||
))
|
||||
.then(literal("roll").then(
|
||||
argument(ARG_RANGE, IntRangeArgumentType).executes(RandomExecutor { roll: true }),
|
||||
)),
|
||||
.then(draw_random_value_tree("value", false))
|
||||
.then(draw_random_value_tree("roll", true))
|
||||
.then(
|
||||
literal("reset")
|
||||
.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,
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -33,10 +33,6 @@ static ERROR_NOT_RIDING: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_RIDE_NOT_RIDING,
|
||||
"Not riding anything",
|
||||
);
|
||||
static ERROR_GENERIC: CommandErrorType<2> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_RIDE_MOUNT_FAILURE_GENERIC,
|
||||
"Failed to mount",
|
||||
);
|
||||
|
||||
#[allow(clippy::assigning_clones)]
|
||||
fn is_riding_recursive(entity: &dyn EntityBase, possible_vehicle: &dyn EntityBase) -> bool {
|
||||
@ -64,35 +60,9 @@ struct RideMountExecutor;
|
||||
|
||||
impl CommandExecutor for RideMountExecutor {
|
||||
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")?;
|
||||
|
||||
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
|
||||
.get_entity()
|
||||
.vehicle
|
||||
@ -100,23 +70,29 @@ impl CommandExecutor for RideMountExecutor {
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone();
|
||||
if let Some(ref curr_veh) = current_vehicle {
|
||||
if curr_veh.get_entity().entity_id == vehicle.get_entity().entity_id {
|
||||
last_error = Some(ERROR_ALREADY_RIDING.create_without_context(
|
||||
target.get_display_name(),
|
||||
vehicle.get_display_name(),
|
||||
));
|
||||
continue;
|
||||
return Err(ERROR_ALREADY_RIDING
|
||||
.create_without_context(target.get_display_name(), curr_veh.get_display_name()));
|
||||
}
|
||||
// Dismount first
|
||||
curr_veh
|
||||
.get_entity()
|
||||
.remove_passenger_sync(target.get_entity().entity_id);
|
||||
|
||||
if vehicle.get_player().is_some() {
|
||||
return Err(ERROR_CANT_RIDE_PLAYERS.create_without_context());
|
||||
}
|
||||
|
||||
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
|
||||
.get_entity()
|
||||
.add_passenger(vehicle.clone(), target.clone());
|
||||
success_count += 1;
|
||||
|
||||
let msg = TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_RIDE_MOUNT_SUCCESS,
|
||||
@ -124,19 +100,8 @@ impl CommandExecutor for RideMountExecutor {
|
||||
[target.get_display_name(), vehicle.get_display_name()],
|
||||
);
|
||||
context.source.send_feedback(msg, true);
|
||||
}
|
||||
|
||||
if success_count == 0 {
|
||||
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)
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,43 +109,30 @@ struct RideDismountExecutor;
|
||||
|
||||
impl CommandExecutor for RideDismountExecutor {
|
||||
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
|
||||
.get_entity()
|
||||
.vehicle
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.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;
|
||||
vehicle.get_entity().remove_passenger_sync(target_id);
|
||||
success_count += 1;
|
||||
|
||||
let msg = TextComponent::translate_cross(
|
||||
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);
|
||||
} else {
|
||||
last_error =
|
||||
Some(ERROR_NOT_RIDING.create_without_context(target.get_display_name()));
|
||||
}
|
||||
}
|
||||
|
||||
if success_count == 0 {
|
||||
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)
|
||||
Ok(1)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +145,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
|
||||
let builder =
|
||||
command("ride", DESCRIPTION).requires(PERMISSION).then(
|
||||
argument("target", EntityArgumentType::Entities)
|
||||
argument("target", EntityArgumentType::Entity)
|
||||
.then(literal("mount").then(
|
||||
argument("vehicle", EntityArgumentType::Entity).executes(RideMountExecutor),
|
||||
))
|
||||
|
||||
@ -78,10 +78,10 @@ impl CommandExecutor for RotateFacingLocationExecutor {
|
||||
let facing_pos =
|
||||
Vec3ArgumentType::get_coordinates(context, "facingLocation")?.resolve(&context.source);
|
||||
|
||||
let entity = target.get_entity();
|
||||
let eye_height = entity.get_eye_height();
|
||||
let pos = entity.pos.load();
|
||||
let looking_from = Vector3::new(pos.x, pos.y + eye_height, pos.z);
|
||||
let looking_from = context
|
||||
.source
|
||||
.entity_anchor
|
||||
.position_at_entity(target.get_entity());
|
||||
|
||||
let (yaw, pitch) = yaw_pitch_facing_position(&looking_from, &facing_pos);
|
||||
|
||||
@ -106,10 +106,10 @@ impl CommandExecutor for RotateFacingEntityExecutor {
|
||||
EntityAnchor::Feet
|
||||
};
|
||||
|
||||
let target_entity = target.get_entity();
|
||||
let eye_height = target_entity.get_eye_height();
|
||||
let pos = target_entity.pos.load();
|
||||
let looking_from = Vector3::new(pos.x, pos.y + eye_height, pos.z);
|
||||
let looking_from = context
|
||||
.source
|
||||
.entity_anchor
|
||||
.position_at_entity(target.get_entity());
|
||||
|
||||
let looking_towards = anchor.position_at_entity(facing_entity.get_entity());
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use tracing::error;
|
||||
|
||||
use crate::command::argument_builder::{ArgumentBuilder, command};
|
||||
use crate::command::argument_builder::{ArgumentBuilder, command, literal};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
use crate::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
@ -63,6 +63,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
dispatcher.register(
|
||||
command("save-all", DESCRIPTION)
|
||||
.requires(PERMISSION)
|
||||
.executes(SaveAllExecutor),
|
||||
.executes(SaveAllExecutor)
|
||||
.then(literal("flush").executes(SaveAllExecutor)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::PermissionLvl;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
@ -8,10 +7,9 @@ use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
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::rotation::RotationArgumentType;
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
use crate::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
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 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 {
|
||||
SelfPos,
|
||||
PosOnly,
|
||||
PosAndAngle,
|
||||
PosAndRotation,
|
||||
}
|
||||
|
||||
struct SetWorldSpawnExecutor(SetWorldSpawnMode);
|
||||
|
||||
impl CommandExecutor for SetWorldSpawnExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
let (pos, angle) = match self.0 {
|
||||
let (pos, (pitch, yaw)) = match self.0 {
|
||||
SetWorldSpawnMode::SelfPos => {
|
||||
let block_pos = BlockPos::floored_v(context.source.position);
|
||||
(block_pos, 0.0)
|
||||
(block_pos, (0.0, 0.0))
|
||||
}
|
||||
SetWorldSpawnMode::PosOnly => {
|
||||
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 angle = AngleArgumentType::get(context, "angle")?.get_angle(&context.source);
|
||||
(block_pos, angle)
|
||||
let rot = RotationArgumentType::get(context, "rotation")?.rotation(&context.source);
|
||||
(block_pos, (rot.x, rot.y))
|
||||
}
|
||||
};
|
||||
|
||||
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 current_info = server.level_info.load();
|
||||
let previous_position = BlockPos::new(
|
||||
@ -66,8 +54,8 @@ impl CommandExecutor for SetWorldSpawnExecutor {
|
||||
let new_position = pos;
|
||||
let previous_yaw = current_info.spawn_yaw;
|
||||
let previous_pitch = current_info.spawn_pitch;
|
||||
let new_yaw = angle;
|
||||
let new_pitch = 0.0;
|
||||
let new_yaw = yaw;
|
||||
let new_pitch = pitch;
|
||||
let mut event = SpawnChangeEvent::new(
|
||||
world.clone(),
|
||||
previous_position,
|
||||
@ -128,8 +116,8 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
argument("pos", BlockPosArgumentType)
|
||||
.executes(SetWorldSpawnExecutor(SetWorldSpawnMode::PosOnly))
|
||||
.then(
|
||||
argument("angle", AngleArgumentType)
|
||||
.executes(SetWorldSpawnExecutor(SetWorldSpawnMode::PosAndAngle)),
|
||||
argument("rotation", RotationArgumentType)
|
||||
.executes(SetWorldSpawnExecutor(SetWorldSpawnMode::PosAndRotation)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::PermissionLvl;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
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::rotation::RotationArgumentType;
|
||||
use crate::command::argument_types::entity::EntityArgumentType;
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
@ -24,7 +25,7 @@ enum SpawnpointMode {
|
||||
SelfDefault,
|
||||
TargetsDefault,
|
||||
TargetsPos,
|
||||
TargetsPosAngle,
|
||||
TargetsPosRotation,
|
||||
}
|
||||
|
||||
struct SpawnpointExecutor(SpawnpointMode);
|
||||
@ -43,37 +44,60 @@ impl CommandExecutor for SpawnpointExecutor {
|
||||
_ => EntityArgumentType::get_players(context, "targets")?,
|
||||
};
|
||||
|
||||
for target in &targets {
|
||||
let (pos, yaw) = match self.0 {
|
||||
let (pos, (pitch, yaw)) = match self.0 {
|
||||
SpawnpointMode::SelfDefault | SpawnpointMode::TargetsDefault => {
|
||||
let block_pos = target.position().to_block_pos();
|
||||
let yaw = target.living_entity.entity.yaw.load();
|
||||
(block_pos, yaw)
|
||||
let block_pos = BlockPos::floored_v(context.source.position);
|
||||
(block_pos, (0.0, 0.0))
|
||||
}
|
||||
SpawnpointMode::TargetsPos => {
|
||||
let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?;
|
||||
let yaw = target.living_entity.entity.yaw.load();
|
||||
(block_pos, yaw)
|
||||
(block_pos, (0.0, 0.0))
|
||||
}
|
||||
SpawnpointMode::TargetsPosAngle => {
|
||||
SpawnpointMode::TargetsPosRotation => {
|
||||
let block_pos = BlockPosArgumentType::get_block_pos(context, "pos")?;
|
||||
let yaw = AngleArgumentType::get(context, "angle")?.get_angle(&context.source);
|
||||
(block_pos, yaw)
|
||||
let rot = RotationArgumentType::get(context, "rotation")?.rotation(&context.source);
|
||||
(block_pos, (rot.x, rot.y))
|
||||
}
|
||||
};
|
||||
|
||||
let dimension = target.world().dimension.clone();
|
||||
target.set_respawn_point(dimension, pos, yaw, 0.0, true);
|
||||
let world = context.source.world();
|
||||
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(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::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.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[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,
|
||||
@ -102,8 +126,9 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
argument("pos", BlockPosArgumentType)
|
||||
.executes(SpawnpointExecutor(SpawnpointMode::TargetsPos))
|
||||
.then(
|
||||
argument("angle", AngleArgumentType)
|
||||
.executes(SpawnpointExecutor(SpawnpointMode::TargetsPosAngle)),
|
||||
argument("rotation", RotationArgumentType).executes(
|
||||
SpawnpointExecutor(SpawnpointMode::TargetsPosRotation),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -78,18 +78,16 @@ impl CommandExecutor for SpectateTargetExecutor {
|
||||
let target_entity = target.get_entity();
|
||||
let target_world = target_entity.world.load_full();
|
||||
|
||||
let players = if self.is_self {
|
||||
let player = context
|
||||
let player = if self.is_self {
|
||||
context
|
||||
.source
|
||||
.output
|
||||
.as_player()
|
||||
.ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())?;
|
||||
vec![player]
|
||||
.ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())?
|
||||
} else {
|
||||
EntityArgumentType::get_players(context, "player")?
|
||||
EntityArgumentType::get_player(context, "player")?
|
||||
};
|
||||
|
||||
for player in &players {
|
||||
if player.gamemode.load() != GameMode::Spectator {
|
||||
let display_name = player.get_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();
|
||||
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;
|
||||
player.camera_target_id.store(Some(target_id));
|
||||
let pos = target_entity.pos.load();
|
||||
let yaw = target_entity.yaw.load();
|
||||
let pitch = target_entity.pitch.load();
|
||||
let player_world = player.world();
|
||||
player.try_send_client_packet(&CSetCamera::new(target_id.into()));
|
||||
player.teleport(pos, Some(yaw), Some(pitch), player_world);
|
||||
succeeded += 1;
|
||||
}
|
||||
|
||||
let target_name = target.get_display_name();
|
||||
context.source.send_feedback(
|
||||
@ -129,7 +121,7 @@ impl CommandExecutor for SpectateTargetExecutor {
|
||||
false,
|
||||
);
|
||||
|
||||
Ok(succeeded)
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +140,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
argument("target", EntityArgumentType::Entity)
|
||||
.executes(SpectateTargetExecutor { is_self: true })
|
||||
.then(
|
||||
argument("player", EntityArgumentType::Players)
|
||||
argument("player", EntityArgumentType::Player)
|
||||
.executes(SpectateTargetExecutor { is_self: false }),
|
||||
),
|
||||
),
|
||||
|
||||
@ -112,7 +112,7 @@ impl CommandExecutor for QueryExecutor {
|
||||
),
|
||||
true,
|
||||
);
|
||||
Ok((elapsed_seconds * scale).floor() as i32)
|
||||
Ok((elapsed_seconds * scale) as i32)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,11 +21,14 @@ use pumpkin_util::text::color::NamedColor;
|
||||
const DESCRIPTION: &str = "Manages teams.";
|
||||
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_TEAM: &str = "team";
|
||||
const ARG_MEMBERS: &str = "members";
|
||||
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(
|
||||
translation::java::COMMANDS_TEAM_ADD_DUPLICATE,
|
||||
@ -543,7 +546,7 @@ struct TeamModifyDisplayNameExecutor;
|
||||
impl CommandExecutor for TeamModifyDisplayNameExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
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 team_name_owned = team_name.to_string();
|
||||
@ -588,7 +591,7 @@ struct TeamModifyPrefixExecutor;
|
||||
impl CommandExecutor for TeamModifyPrefixExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
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 team_name_owned = team_name.to_string();
|
||||
@ -629,7 +632,7 @@ struct TeamModifySuffixExecutor;
|
||||
impl CommandExecutor for TeamModifySuffixExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
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 team_name_owned = team_name.to_string();
|
||||
@ -670,7 +673,7 @@ struct TeamModifyFriendlyFireExecutor;
|
||||
impl CommandExecutor for TeamModifyFriendlyFireExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
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 team_name_owned = team_name.to_string();
|
||||
@ -724,7 +727,7 @@ struct TeamModifySeeFriendlyInvisiblesExecutor;
|
||||
impl CommandExecutor for TeamModifySeeFriendlyInvisiblesExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
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 team_name_owned = team_name.to_string();
|
||||
@ -976,9 +979,7 @@ fn join_branch() -> LiteralArgumentBuilder {
|
||||
}
|
||||
|
||||
fn leave_branch() -> LiteralArgumentBuilder {
|
||||
literal("leave")
|
||||
.executes(TeamLeaveExecutor { has_members: false })
|
||||
.then(
|
||||
literal("leave").then(
|
||||
argument(ARG_MEMBERS, EntityArgumentType::Entities)
|
||||
.executes(TeamLeaveExecutor { has_members: true }),
|
||||
)
|
||||
@ -1004,28 +1005,28 @@ fn modify_branch() -> LiteralArgumentBuilder {
|
||||
)
|
||||
.then(
|
||||
literal("displayName").then(
|
||||
argument(ARG_VALUE, StringArgumentType::GreedyPhrase)
|
||||
argument(ARG_DISPLAY_NAME, StringArgumentType::GreedyPhrase)
|
||||
.executes(TeamModifyDisplayNameExecutor),
|
||||
),
|
||||
)
|
||||
.then(
|
||||
literal("prefix").then(
|
||||
argument(ARG_VALUE, StringArgumentType::GreedyPhrase)
|
||||
argument(ARG_PREFIX, StringArgumentType::GreedyPhrase)
|
||||
.executes(TeamModifyPrefixExecutor),
|
||||
),
|
||||
)
|
||||
.then(
|
||||
literal("suffix").then(
|
||||
argument(ARG_VALUE, StringArgumentType::GreedyPhrase)
|
||||
argument(ARG_SUFFIX, StringArgumentType::GreedyPhrase)
|
||||
.executes(TeamModifySuffixExecutor),
|
||||
),
|
||||
)
|
||||
.then(literal("friendlyFire").then(
|
||||
argument(ARG_VALUE, BoolArgumentType).executes(TeamModifyFriendlyFireExecutor),
|
||||
argument(ARG_ALLOWED, BoolArgumentType).executes(TeamModifyFriendlyFireExecutor),
|
||||
))
|
||||
.then(
|
||||
literal("seeFriendlyInvisibles").then(
|
||||
argument(ARG_VALUE, BoolArgumentType)
|
||||
argument(ARG_ALLOWED, BoolArgumentType)
|
||||
.executes(TeamModifySeeFriendlyInvisiblesExecutor),
|
||||
),
|
||||
)
|
||||
|
||||
@ -48,6 +48,7 @@ impl CommandExecutor for TeamMsgCommandExecutor {
|
||||
let message_component = TextComponent::text(message_text);
|
||||
|
||||
let online_players = world.players.load();
|
||||
let mut recipients = 0;
|
||||
|
||||
for player in online_players.iter() {
|
||||
if team.players.contains(&player.gameprofile.name) {
|
||||
@ -74,10 +75,11 @@ impl CommandExecutor for TeamMsgCommandExecutor {
|
||||
};
|
||||
|
||||
player.send_system_message(&msg);
|
||||
recipients += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(1)
|
||||
Ok(recipients)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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::vec3::Vec3ArgumentType;
|
||||
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::errors::error_types::CommandErrorType;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
@ -21,11 +22,6 @@ use crate::world::World;
|
||||
const DESCRIPTION: &str = "Teleports entities, including players.";
|
||||
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(
|
||||
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
|
||||
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
|
||||
@ -62,28 +58,24 @@ struct SelfToPosExecutor;
|
||||
|
||||
impl CommandExecutor for SelfToPosExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
let player = context
|
||||
.source
|
||||
.output
|
||||
.as_player()
|
||||
.ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())?;
|
||||
let entity = context.source.entity_or_err()?;
|
||||
|
||||
let pos = Vec3ArgumentType::get_coordinates(context, "location")?.resolve(&context.source);
|
||||
if !World::is_valid(BlockPos(pos.floor_to_i32())) {
|
||||
return Err(ERROR_INVALID_POSITION.create_without_context());
|
||||
}
|
||||
|
||||
let yaw = player.get_entity().yaw.load();
|
||||
let pitch = player.get_entity().pitch.load();
|
||||
let player_world = player.world();
|
||||
player.teleport(pos, Some(yaw), Some(pitch), player_world);
|
||||
let yaw = entity.get_entity().yaw.load();
|
||||
let pitch = entity.get_entity().pitch.load();
|
||||
let world = context.source.world();
|
||||
entity.teleport(pos, Some(yaw), Some(pitch), world.clone());
|
||||
|
||||
context.source.send_feedback(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_TELEPORT_SUCCESS_LOCATION_SINGLE,
|
||||
translation::bedrock::COMMANDS_TP_SUCCESS_COORDINATES,
|
||||
[
|
||||
player.get_display_name(),
|
||||
entity.get_display_name(),
|
||||
TextComponent::text(pos.x.to_string()),
|
||||
TextComponent::text(pos.y.to_string()),
|
||||
TextComponent::text(pos.z.to_string()),
|
||||
@ -100,11 +92,7 @@ struct SelfToEntityExecutor;
|
||||
|
||||
impl CommandExecutor for SelfToEntityExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
let player = context
|
||||
.source
|
||||
.output
|
||||
.as_player()
|
||||
.ok_or_else(|| ERROR_NOT_PLAYER.create_without_context())?;
|
||||
let entity = context.source.entity_or_err()?;
|
||||
|
||||
let destination = EntityArgumentType::get_entity(context, "destination")?;
|
||||
let destination_entity = destination.get_entity();
|
||||
@ -117,14 +105,14 @@ impl CommandExecutor for SelfToEntityExecutor {
|
||||
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(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_TELEPORT_SUCCESS_ENTITY_SINGLE,
|
||||
translation::bedrock::COMMANDS_TP_SUCCESSVICTIM,
|
||||
[
|
||||
player.get_display_name(),
|
||||
entity.get_display_name(),
|
||||
destination_entity.get_display_name(),
|
||||
],
|
||||
),
|
||||
@ -270,10 +258,15 @@ impl CommandExecutor for EntitiesToPosFacingPosExecutor {
|
||||
|
||||
let facing_pos =
|
||||
Vec3ArgumentType::get_coordinates(context, "facingLocation")?.resolve(&context.source);
|
||||
let (yaw, pitch) = yaw_pitch_facing_position(&pos, &facing_pos);
|
||||
let world = context.source.world();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@ -300,7 +293,9 @@ impl CommandExecutor for EntitiesToPosFacingPosExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
struct EntitiesToPosFacingEntityExecutor;
|
||||
struct EntitiesToPosFacingEntityExecutor {
|
||||
has_anchor: bool,
|
||||
}
|
||||
|
||||
impl CommandExecutor for EntitiesToPosFacingEntityExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
@ -312,10 +307,22 @@ impl CommandExecutor for EntitiesToPosFacingEntityExecutor {
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@ -367,7 +374,15 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
.then(
|
||||
literal("entity").then(
|
||||
argument("facingEntity", EntityArgumentType::Entity)
|
||||
.executes(EntitiesToPosFacingEntityExecutor),
|
||||
.executes(EntitiesToPosFacingEntityExecutor {
|
||||
has_anchor: false,
|
||||
})
|
||||
.then(
|
||||
argument("facingAnchor", EntityAnchorArgumentType)
|
||||
.executes(EntitiesToPosFacingEntityExecutor {
|
||||
has_anchor: true,
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.then(
|
||||
|
||||
@ -148,6 +148,7 @@ impl TickExecutor {
|
||||
TextComponent::text(nanos_to_millis_string(p50)),
|
||||
TextComponent::text(nanos_to_millis_string(p95)),
|
||||
TextComponent::text(nanos_to_millis_string(p99)),
|
||||
TextComponent::text(slice.len().to_string()),
|
||||
],
|
||||
),
|
||||
false,
|
||||
|
||||
@ -87,10 +87,10 @@ impl CommandExecutor for SimpleTriggerExecutor {
|
||||
translation::java::COMMANDS_TRIGGER_SIMPLE_SUCCESS,
|
||||
[objective_display_name],
|
||||
),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
|
||||
Ok(1)
|
||||
Ok(new_value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,10 +158,10 @@ impl CommandExecutor for AddTriggerExecutor {
|
||||
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()),
|
||||
],
|
||||
),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
|
||||
Ok(value)
|
||||
|
||||
@ -33,7 +33,7 @@ impl CommandExecutor for WeatherExecutor {
|
||||
};
|
||||
|
||||
let world = context.source.world();
|
||||
let message = {
|
||||
let (message, return_val) = {
|
||||
let mut weather = world
|
||||
.weather
|
||||
.lock()
|
||||
@ -45,10 +45,13 @@ impl CommandExecutor for WeatherExecutor {
|
||||
duration.unwrap_or_else(|| rand::random_range(12_000..=180_000));
|
||||
|
||||
weather.set_weather_parameters(world, processed_duration, 0, false, false);
|
||||
(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_WEATHER_SET_CLEAR,
|
||||
translation::bedrock::COMMANDS_WEATHER_CLEAR,
|
||||
[],
|
||||
),
|
||||
duration.unwrap_or(-1),
|
||||
)
|
||||
}
|
||||
WeatherMode::Rain => {
|
||||
@ -56,10 +59,13 @@ impl CommandExecutor for WeatherExecutor {
|
||||
duration.unwrap_or_else(|| rand::random_range(12_000..=24_000));
|
||||
|
||||
weather.set_weather_parameters(world, 0, processed_duration, true, false);
|
||||
(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_WEATHER_SET_RAIN,
|
||||
translation::bedrock::COMMANDS_WEATHER_RAIN,
|
||||
[],
|
||||
),
|
||||
duration.unwrap_or(-1),
|
||||
)
|
||||
}
|
||||
WeatherMode::Thunder => {
|
||||
@ -67,10 +73,13 @@ impl CommandExecutor for WeatherExecutor {
|
||||
duration.unwrap_or_else(|| rand::random_range(3600..=15_600));
|
||||
|
||||
weather.set_weather_parameters(world, 0, processed_duration, true, true);
|
||||
(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_WEATHER_SET_THUNDER,
|
||||
translation::bedrock::COMMANDS_WEATHER_THUNDER,
|
||||
[],
|
||||
),
|
||||
duration.unwrap_or(-1),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -78,7 +87,7 @@ impl CommandExecutor for WeatherExecutor {
|
||||
|
||||
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,
|
||||
has_duration: false,
|
||||
})
|
||||
.then(argument("duration", TimeArgumentType::any()).executes(
|
||||
.then(argument("duration", TimeArgumentType::new(1)).executes(
|
||||
WeatherExecutor {
|
||||
mode: WeatherMode::Clear,
|
||||
has_duration: true,
|
||||
@ -111,7 +120,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
mode: WeatherMode::Rain,
|
||||
has_duration: false,
|
||||
})
|
||||
.then(argument("duration", TimeArgumentType::any()).executes(
|
||||
.then(argument("duration", TimeArgumentType::new(1)).executes(
|
||||
WeatherExecutor {
|
||||
mode: WeatherMode::Rain,
|
||||
has_duration: true,
|
||||
@ -124,7 +133,7 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
mode: WeatherMode::Thunder,
|
||||
has_duration: false,
|
||||
})
|
||||
.then(argument("duration", TimeArgumentType::any()).executes(
|
||||
.then(argument("duration", TimeArgumentType::new(1)).executes(
|
||||
WeatherExecutor {
|
||||
mode: WeatherMode::Thunder,
|
||||
has_duration: true,
|
||||
|
||||
@ -12,6 +12,8 @@ use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
use crate::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
use crate::command::suggestion::provider::{SuggestionProvider, SuggestionProviderResult};
|
||||
use crate::command::suggestion::suggestions::SuggestionsBuilder;
|
||||
use crate::data::{LoadJSONConfiguration, SaveJSONConfiguration, whitelist::WhitelistConfig};
|
||||
use crate::net::DisconnectReason;
|
||||
use crate::server::Server;
|
||||
@ -39,11 +41,14 @@ const ERROR_REMOVE_FAILED: CommandErrorType<0> = CommandErrorType::new(
|
||||
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 ops = server.data.operator_config.read().unwrap();
|
||||
if server.basic_config.enforce_whitelist && server.white_list.load(Ordering::Relaxed) {
|
||||
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;
|
||||
}
|
||||
player.kick(
|
||||
@ -66,7 +71,6 @@ impl CommandExecutor for OnExecutor {
|
||||
if previous {
|
||||
Err(ERROR_ALREADY_ON.create_without_context())
|
||||
} else {
|
||||
kick_non_whitelisted_players(server);
|
||||
context.source.send_feedback(
|
||||
pumpkin_macros::translate_cross!(
|
||||
translation::java::COMMANDS_WHITELIST_ENABLED,
|
||||
@ -74,6 +78,7 @@ impl CommandExecutor for OnExecutor {
|
||||
),
|
||||
true,
|
||||
);
|
||||
kick_non_whitelisted_players(server);
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
@ -109,9 +114,10 @@ impl CommandExecutor for ListExecutor {
|
||||
let whitelist = &whitelist_guard.whitelist;
|
||||
if whitelist.is_empty() {
|
||||
context.source.send_feedback(
|
||||
pumpkin_macros::translate_cross!(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_WHITELIST_NONE,
|
||||
translation::bedrock::COMMANDS_ALLOWLIST_LIST
|
||||
translation::java::COMMANDS_WHITELIST_NONE,
|
||||
[],
|
||||
),
|
||||
false,
|
||||
);
|
||||
@ -124,19 +130,21 @@ impl CommandExecutor for ListExecutor {
|
||||
.collect::<Vec<&str>>()
|
||||
.join(", ");
|
||||
|
||||
let names_len = names.len() as i32;
|
||||
let count = whitelist.len() as i32;
|
||||
|
||||
context.source.send_feedback(
|
||||
pumpkin_macros::translate_cross!(
|
||||
TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_WHITELIST_LIST,
|
||||
translation::bedrock::COMMANDS_ALLOWLIST_LIST,
|
||||
TextComponent::text(whitelist.len().to_string()),
|
||||
TextComponent::text(names)
|
||||
translation::java::COMMANDS_WHITELIST_LIST,
|
||||
[
|
||||
TextComponent::text(count.to_string()),
|
||||
TextComponent::text(names),
|
||||
],
|
||||
),
|
||||
false,
|
||||
);
|
||||
|
||||
Ok(names_len)
|
||||
Ok(count)
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,7 +154,6 @@ impl CommandExecutor for ReloadExecutor {
|
||||
fn execute(&self, context: &CommandContext) -> CommandExecutorResult {
|
||||
let server = context.source.server();
|
||||
*server.data.whitelist_config.write().unwrap() = WhitelistConfig::load();
|
||||
kick_non_whitelisted_players(server);
|
||||
context.source.send_feedback(
|
||||
pumpkin_macros::translate_cross!(
|
||||
translation::java::COMMANDS_WHITELIST_RELOADED,
|
||||
@ -154,10 +161,29 @@ impl CommandExecutor for ReloadExecutor {
|
||||
),
|
||||
true,
|
||||
);
|
||||
kick_non_whitelisted_players(server);
|
||||
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;
|
||||
|
||||
impl CommandExecutor for AddExecutor {
|
||||
@ -166,6 +192,8 @@ impl CommandExecutor for AddExecutor {
|
||||
let server = context.source.server();
|
||||
let mut whitelist = server.data.whitelist_config.write().unwrap();
|
||||
let mut successes: i32 = 0;
|
||||
let mut modified = false;
|
||||
|
||||
for profile in &targets {
|
||||
if let Some(existing_entry) = whitelist
|
||||
.whitelist
|
||||
@ -174,6 +202,7 @@ impl CommandExecutor for AddExecutor {
|
||||
{
|
||||
if existing_entry.name != profile.name {
|
||||
existing_entry.name.clone_from(&profile.name);
|
||||
modified = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -189,9 +218,12 @@ impl CommandExecutor for AddExecutor {
|
||||
true,
|
||||
);
|
||||
successes += 1;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if modified {
|
||||
whitelist.save();
|
||||
}
|
||||
|
||||
if successes == 0 {
|
||||
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;
|
||||
|
||||
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();
|
||||
drop(whitelist);
|
||||
|
||||
kick_non_whitelisted_players(server);
|
||||
|
||||
if successes == 0 {
|
||||
Err(ERROR_REMOVE_FAILED.create_without_context())
|
||||
} else {
|
||||
Ok(successes)
|
||||
}
|
||||
}
|
||||
@ -255,14 +303,20 @@ pub fn register(dispatcher: &mut CommandDispatcher, registry: &PermissionRegistr
|
||||
.then(literal("on").executes(OnExecutor))
|
||||
.then(literal("off").executes(OffExecutor))
|
||||
.then(literal("list").executes(ListExecutor))
|
||||
.then(literal("reload").executes(ReloadExecutor))
|
||||
.then(
|
||||
literal("add")
|
||||
.then(argument("targets", GameProfileArgumentType).executes(AddExecutor)),
|
||||
literal("add").then(
|
||||
argument("targets", GameProfileArgumentType)
|
||||
.suggests(AddSuggestionProvider)
|
||||
.executes(AddExecutor),
|
||||
),
|
||||
)
|
||||
.then(
|
||||
literal("remove")
|
||||
.then(argument("targets", GameProfileArgumentType).executes(RemoveExecutor)),
|
||||
literal("remove").then(
|
||||
argument("targets", GameProfileArgumentType)
|
||||
.suggests(RemoveSuggestionProvider)
|
||||
.executes(RemoveExecutor),
|
||||
),
|
||||
)
|
||||
.then(literal("reload").executes(ReloadExecutor)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -121,6 +121,8 @@ pub struct Server {
|
||||
std::sync::Mutex<std::collections::HashMap<String, pumpkin_nbt::compound::NbtCompound>>,
|
||||
/// Global server 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
|
||||
pub advancement_manager: Arc<AdvancementManager>,
|
||||
// Whether the server whitelist is on or off
|
||||
@ -299,6 +301,9 @@ impl Server {
|
||||
player_data_storage,
|
||||
command_storage: std::sync::Mutex::new(std::collections::HashMap::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,
|
||||
white_list,
|
||||
tick_rate_manager,
|
||||
|
||||
@ -23,6 +23,7 @@ pub mod loot;
|
||||
pub mod map;
|
||||
pub mod portal;
|
||||
pub mod raid;
|
||||
pub mod random_sequences;
|
||||
pub mod stopwatches;
|
||||
pub mod time;
|
||||
pub mod villager_poi;
|
||||
|
||||
140
crates/pumpkin/src/world/random_sequences.rs
Normal file
140
crates/pumpkin/src/world/random_sequences.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user