feat(command): add scoreboard/trigger command
This commit is contained in:
parent
6219d36c64
commit
dab8474e71
@ -226,6 +226,7 @@ pub mod game_profile;
|
|||||||
pub mod hex_color;
|
pub mod hex_color;
|
||||||
pub mod identifier;
|
pub mod identifier;
|
||||||
pub mod nbt;
|
pub mod nbt;
|
||||||
|
pub mod objective;
|
||||||
pub mod range;
|
pub mod range;
|
||||||
pub mod resource_key;
|
pub mod resource_key;
|
||||||
pub mod slot;
|
pub mod slot;
|
||||||
|
|||||||
50
pumpkin/src/command/argument_types/objective.rs
Normal file
50
pumpkin/src/command/argument_types/objective.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
use crate::command::{
|
||||||
|
argument_types::argument_type::{ArgumentType, JavaClientArgumentType},
|
||||||
|
context::command_context::CommandContext,
|
||||||
|
errors::command_syntax_error::CommandSyntaxError,
|
||||||
|
string_reader::StringReader,
|
||||||
|
suggestion::suggestions::{Suggestions, SuggestionsBuilder},
|
||||||
|
};
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
/// Represents an argument type parsing a scoreboard objective name.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ObjectiveArgumentType;
|
||||||
|
|
||||||
|
impl ArgumentType for ObjectiveArgumentType {
|
||||||
|
type Item = String;
|
||||||
|
|
||||||
|
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||||
|
let name = reader.read_unquoted_string();
|
||||||
|
Ok(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
|
||||||
|
JavaClientArgumentType::Objective
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_suggestions<'a>(
|
||||||
|
&'a self,
|
||||||
|
context: &'a CommandContext,
|
||||||
|
mut builder: SuggestionsBuilder,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Suggestions> + Send + 'a>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let scoreboard = context.world().scoreboard.lock().await;
|
||||||
|
for objective_name in scoreboard.get_objectives().keys() {
|
||||||
|
builder = builder.filter_and_suggest_one(objective_name.as_str());
|
||||||
|
}
|
||||||
|
builder.build()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<String> {
|
||||||
|
vec!["objective".to_string(), "trigger_obj".to_string()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectiveArgumentType {
|
||||||
|
/// Returns a [`CommandContext`]'s parsed `String` argument as a string slice.
|
||||||
|
pub fn get<'a>(context: &'a CommandContext, name: &str) -> Result<&'a str, CommandSyntaxError> {
|
||||||
|
Ok(context.get_argument::<String>(name)?.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,6 +44,7 @@ mod saveall;
|
|||||||
mod saveoff;
|
mod saveoff;
|
||||||
mod saveon;
|
mod saveon;
|
||||||
mod say;
|
mod say;
|
||||||
|
mod scoreboard;
|
||||||
mod seed;
|
mod seed;
|
||||||
mod setblock;
|
mod setblock;
|
||||||
mod setidletimeout;
|
mod setidletimeout;
|
||||||
@ -61,6 +62,7 @@ mod time;
|
|||||||
mod title;
|
mod title;
|
||||||
mod tps;
|
mod tps;
|
||||||
mod transfer;
|
mod transfer;
|
||||||
|
mod trigger;
|
||||||
mod weather;
|
mod weather;
|
||||||
mod whitelist;
|
mod whitelist;
|
||||||
mod worldborder;
|
mod worldborder;
|
||||||
@ -169,6 +171,8 @@ pub async fn default_dispatcher(
|
|||||||
tag::register(&mut dispatcher, registry);
|
tag::register(&mut dispatcher, registry);
|
||||||
tick::register(&mut dispatcher, registry);
|
tick::register(&mut dispatcher, registry);
|
||||||
advancement::register(&mut dispatcher, registry);
|
advancement::register(&mut dispatcher, registry);
|
||||||
|
trigger::register(&mut dispatcher, registry);
|
||||||
|
scoreboard::register(&mut dispatcher, registry);
|
||||||
dispatcher
|
dispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
209
pumpkin/src/command/commands/scoreboard.rs
Normal file
209
pumpkin/src/command/commands/scoreboard.rs
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
|
||||||
|
use crate::command::argument_types::core::string::StringArgumentType;
|
||||||
|
use crate::command::argument_types::entity::EntityArgumentType;
|
||||||
|
use crate::command::argument_types::objective::ObjectiveArgumentType;
|
||||||
|
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::world::scoreboard::{ScoreboardObjective, ScoreboardScore};
|
||||||
|
use pumpkin_data::translation;
|
||||||
|
use pumpkin_protocol::codec::var_int::VarInt;
|
||||||
|
use pumpkin_protocol::java::client::play::RenderType;
|
||||||
|
use pumpkin_util::PermissionLvl;
|
||||||
|
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||||
|
use pumpkin_util::text::TextComponent;
|
||||||
|
|
||||||
|
const DESCRIPTION: &str = "Manages scoreboard objectives and players.";
|
||||||
|
const PERMISSION: &str = "minecraft:command.scoreboard";
|
||||||
|
|
||||||
|
const ARG_OBJECTIVE: &str = "objective";
|
||||||
|
const ARG_CRITERION: &str = "criterion";
|
||||||
|
const ARG_DISPLAY_NAME: &str = "display_name";
|
||||||
|
const ARG_TARGETS: &str = "targets";
|
||||||
|
|
||||||
|
const DUPLICATE_OBJECTIVE_ERROR: CommandErrorType<0> = CommandErrorType::new(
|
||||||
|
translation::java::COMMANDS_SCOREBOARD_OBJECTIVES_ADD_DUPLICATE,
|
||||||
|
translation::java::COMMANDS_SCOREBOARD_OBJECTIVES_ADD_DUPLICATE,
|
||||||
|
);
|
||||||
|
|
||||||
|
const INVALID_ENABLE_ERROR: CommandErrorType<0> = CommandErrorType::new(
|
||||||
|
translation::java::COMMANDS_SCOREBOARD_PLAYERS_ENABLE_INVALID,
|
||||||
|
translation::java::COMMANDS_SCOREBOARD_PLAYERS_ENABLE_INVALID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const FAILED_ENABLE_ERROR: CommandErrorType<0> = CommandErrorType::new(
|
||||||
|
translation::java::COMMANDS_SCOREBOARD_PLAYERS_ENABLE_FAILED,
|
||||||
|
translation::java::COMMANDS_SCOREBOARD_PLAYERS_ENABLE_FAILED,
|
||||||
|
);
|
||||||
|
|
||||||
|
struct ObjectivesAddExecutor {
|
||||||
|
has_display_name: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandExecutor for ObjectivesAddExecutor {
|
||||||
|
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let objective_name = StringArgumentType::get(context, ARG_OBJECTIVE)?;
|
||||||
|
let criterion = StringArgumentType::get(context, ARG_CRITERION)?;
|
||||||
|
|
||||||
|
let display_name = if self.has_display_name {
|
||||||
|
TextComponent::text(StringArgumentType::get(context, ARG_DISPLAY_NAME)?.to_string())
|
||||||
|
} else {
|
||||||
|
TextComponent::text(objective_name.to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
let world = context.world();
|
||||||
|
let mut scoreboard = world.scoreboard.lock().await;
|
||||||
|
|
||||||
|
if scoreboard.get_objectives().contains_key(objective_name) {
|
||||||
|
return Err(DUPLICATE_OBJECTIVE_ERROR.create_without_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
let obj_name_static = Box::leak(objective_name.to_string().into_boxed_str());
|
||||||
|
let criterion_static = Box::leak(criterion.to_string().into_boxed_str());
|
||||||
|
|
||||||
|
let new_objective = ScoreboardObjective::new(
|
||||||
|
obj_name_static,
|
||||||
|
display_name.clone(),
|
||||||
|
RenderType::Integer,
|
||||||
|
None,
|
||||||
|
criterion_static,
|
||||||
|
);
|
||||||
|
|
||||||
|
scoreboard.add_objective(world, new_objective).await;
|
||||||
|
|
||||||
|
context
|
||||||
|
.source
|
||||||
|
.send_feedback(
|
||||||
|
TextComponent::translate_cross(
|
||||||
|
translation::java::COMMANDS_SCOREBOARD_OBJECTIVES_ADD_SUCCESS,
|
||||||
|
translation::java::COMMANDS_SCOREBOARD_OBJECTIVES_ADD_SUCCESS,
|
||||||
|
[display_name],
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PlayersEnableExecutor;
|
||||||
|
|
||||||
|
impl CommandExecutor for PlayersEnableExecutor {
|
||||||
|
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let targets = EntityArgumentType::get_players(context, ARG_TARGETS).await?;
|
||||||
|
let objective_name = ObjectiveArgumentType::get(context, ARG_OBJECTIVE)?;
|
||||||
|
|
||||||
|
let world = context.world();
|
||||||
|
let mut scoreboard = world.scoreboard.lock().await;
|
||||||
|
|
||||||
|
let objective = scoreboard
|
||||||
|
.get_objectives()
|
||||||
|
.get(objective_name)
|
||||||
|
.ok_or_else(|| INVALID_ENABLE_ERROR.create_without_context())?;
|
||||||
|
|
||||||
|
if objective.criterion != "trigger" {
|
||||||
|
return Err(INVALID_ENABLE_ERROR.create_without_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
let objective_display_name = objective.display_name.clone();
|
||||||
|
|
||||||
|
let mut enabled_count = 0;
|
||||||
|
for player in &targets {
|
||||||
|
let player_name = &player.gameprofile.name;
|
||||||
|
let current_score = scoreboard
|
||||||
|
.get_scores()
|
||||||
|
.get(objective_name)
|
||||||
|
.and_then(|m| m.get(player_name));
|
||||||
|
|
||||||
|
let is_already_enabled = current_score.is_some_and(|s| !s.locked);
|
||||||
|
|
||||||
|
if !is_already_enabled {
|
||||||
|
let value = current_score.map_or(0, |s| s.value.0);
|
||||||
|
let display_name = current_score.and_then(|s| s.display_name.clone());
|
||||||
|
let number_format = current_score.and_then(|s| s.number_format.clone());
|
||||||
|
|
||||||
|
let updated_score = ScoreboardScore {
|
||||||
|
entity_name: Box::leak(player_name.clone().into_boxed_str()),
|
||||||
|
objective_name: Box::leak(objective_name.to_string().into_boxed_str()),
|
||||||
|
value: VarInt(value),
|
||||||
|
display_name,
|
||||||
|
number_format,
|
||||||
|
locked: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
scoreboard.update_score(world, updated_score).await;
|
||||||
|
enabled_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if enabled_count == 0 {
|
||||||
|
return Err(FAILED_ENABLE_ERROR.create_without_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = if targets.len() == 1 {
|
||||||
|
TextComponent::translate_cross(
|
||||||
|
translation::java::COMMANDS_SCOREBOARD_PLAYERS_ENABLE_SUCCESS_SINGLE,
|
||||||
|
translation::java::COMMANDS_SCOREBOARD_PLAYERS_ENABLE_SUCCESS_SINGLE,
|
||||||
|
[
|
||||||
|
objective_display_name,
|
||||||
|
TextComponent::text(targets[0].gameprofile.name.clone()),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
TextComponent::translate_cross(
|
||||||
|
translation::java::COMMANDS_SCOREBOARD_PLAYERS_ENABLE_SUCCESS_MULTIPLE,
|
||||||
|
translation::java::COMMANDS_SCOREBOARD_PLAYERS_ENABLE_SUCCESS_MULTIPLE,
|
||||||
|
[
|
||||||
|
objective_display_name,
|
||||||
|
TextComponent::text(targets.len().to_string()),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
context.source.send_feedback(msg, true).await;
|
||||||
|
|
||||||
|
Ok(enabled_count)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(dispatcher: &mut CommandDispatcher, registry: &mut PermissionRegistry) {
|
||||||
|
registry.register_permission_or_panic(Permission::new(
|
||||||
|
PERMISSION,
|
||||||
|
DESCRIPTION,
|
||||||
|
PermissionDefault::Op(PermissionLvl::Two),
|
||||||
|
));
|
||||||
|
|
||||||
|
dispatcher.register(
|
||||||
|
command("scoreboard", DESCRIPTION)
|
||||||
|
.requires(PERMISSION)
|
||||||
|
.then(
|
||||||
|
literal("objectives").then(
|
||||||
|
literal("add").then(
|
||||||
|
argument(ARG_OBJECTIVE, StringArgumentType::SingleWord).then(
|
||||||
|
argument(ARG_CRITERION, StringArgumentType::SingleWord)
|
||||||
|
.executes(ObjectivesAddExecutor {
|
||||||
|
has_display_name: false,
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
argument(ARG_DISPLAY_NAME, StringArgumentType::GreedyPhrase)
|
||||||
|
.executes(ObjectivesAddExecutor {
|
||||||
|
has_display_name: true,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.then(literal("players").then(literal("enable").then(
|
||||||
|
argument(ARG_TARGETS, EntityArgumentType::Players).then(
|
||||||
|
argument(ARG_OBJECTIVE, ObjectiveArgumentType).executes(PlayersEnableExecutor),
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
);
|
||||||
|
}
|
||||||
256
pumpkin/src/command/commands/trigger.rs
Normal file
256
pumpkin/src/command/commands/trigger.rs
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
|
||||||
|
use crate::command::argument_types::core::integer::IntegerArgumentType;
|
||||||
|
use crate::command::argument_types::objective::ObjectiveArgumentType;
|
||||||
|
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::world::scoreboard::ScoreboardScore;
|
||||||
|
use pumpkin_data::translation;
|
||||||
|
use pumpkin_protocol::codec::var_int::VarInt;
|
||||||
|
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||||
|
use pumpkin_util::text::TextComponent;
|
||||||
|
|
||||||
|
const DESCRIPTION: &str = "Modifies a trigger scoreboard objective.";
|
||||||
|
const PERMISSION: &str = "minecraft:command.trigger";
|
||||||
|
|
||||||
|
const ARG_OBJECTIVE: &str = "objective";
|
||||||
|
const ARG_VALUE: &str = "value";
|
||||||
|
|
||||||
|
const INVALID_TRIGGER_ERROR: CommandErrorType<0> = CommandErrorType::new(
|
||||||
|
translation::java::COMMANDS_TRIGGER_FAILED_INVALID,
|
||||||
|
translation::java::COMMANDS_TRIGGER_FAILED_INVALID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const UNPRIMED_TRIGGER_ERROR: CommandErrorType<0> = CommandErrorType::new(
|
||||||
|
translation::java::COMMANDS_TRIGGER_FAILED_UNPRIMED,
|
||||||
|
translation::java::COMMANDS_TRIGGER_FAILED_UNPRIMED,
|
||||||
|
);
|
||||||
|
|
||||||
|
struct SimpleTriggerExecutor;
|
||||||
|
|
||||||
|
impl CommandExecutor for SimpleTriggerExecutor {
|
||||||
|
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let player = context.source.player_or_err()?;
|
||||||
|
let player_name = &player.gameprofile.name;
|
||||||
|
let objective_name = ObjectiveArgumentType::get(context, ARG_OBJECTIVE)?;
|
||||||
|
|
||||||
|
let world = context.world();
|
||||||
|
let mut scoreboard = world.scoreboard.lock().await;
|
||||||
|
|
||||||
|
let objective = scoreboard
|
||||||
|
.get_objectives()
|
||||||
|
.get(objective_name)
|
||||||
|
.ok_or_else(|| INVALID_TRIGGER_ERROR.create_without_context())?;
|
||||||
|
|
||||||
|
if objective.criterion != "trigger" {
|
||||||
|
return Err(INVALID_TRIGGER_ERROR.create_without_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
let objective_display_name = objective.display_name.clone();
|
||||||
|
|
||||||
|
let is_locked = scoreboard
|
||||||
|
.get_scores()
|
||||||
|
.get(objective_name)
|
||||||
|
.and_then(|m| m.get(player_name))
|
||||||
|
.is_none_or(|s| s.locked);
|
||||||
|
|
||||||
|
if is_locked {
|
||||||
|
return Err(UNPRIMED_TRIGGER_ERROR.create_without_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_value = scoreboard
|
||||||
|
.get_scores()
|
||||||
|
.get(objective_name)
|
||||||
|
.and_then(|m| m.get(player_name))
|
||||||
|
.map_or(0, |s| s.value.0);
|
||||||
|
|
||||||
|
let new_value = current_value + 1;
|
||||||
|
|
||||||
|
let updated_score = ScoreboardScore {
|
||||||
|
entity_name: Box::leak(player_name.clone().into_boxed_str()),
|
||||||
|
objective_name: Box::leak(objective_name.to_string().into_boxed_str()),
|
||||||
|
value: VarInt(new_value),
|
||||||
|
display_name: None,
|
||||||
|
number_format: None,
|
||||||
|
locked: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
scoreboard.update_score(world, updated_score).await;
|
||||||
|
|
||||||
|
context
|
||||||
|
.source
|
||||||
|
.send_feedback(
|
||||||
|
TextComponent::translate_cross(
|
||||||
|
translation::java::COMMANDS_TRIGGER_SIMPLE_SUCCESS,
|
||||||
|
translation::java::COMMANDS_TRIGGER_SIMPLE_SUCCESS,
|
||||||
|
[objective_display_name],
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(new_value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AddTriggerExecutor;
|
||||||
|
|
||||||
|
impl CommandExecutor for AddTriggerExecutor {
|
||||||
|
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let player = context.source.player_or_err()?;
|
||||||
|
let player_name = &player.gameprofile.name;
|
||||||
|
let objective_name = ObjectiveArgumentType::get(context, ARG_OBJECTIVE)?;
|
||||||
|
let value = IntegerArgumentType::get(context, ARG_VALUE)?;
|
||||||
|
|
||||||
|
let world = context.world();
|
||||||
|
let mut scoreboard = world.scoreboard.lock().await;
|
||||||
|
|
||||||
|
let objective = scoreboard
|
||||||
|
.get_objectives()
|
||||||
|
.get(objective_name)
|
||||||
|
.ok_or_else(|| INVALID_TRIGGER_ERROR.create_without_context())?;
|
||||||
|
|
||||||
|
if objective.criterion != "trigger" {
|
||||||
|
return Err(INVALID_TRIGGER_ERROR.create_without_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
let objective_display_name = objective.display_name.clone();
|
||||||
|
|
||||||
|
let is_locked = scoreboard
|
||||||
|
.get_scores()
|
||||||
|
.get(objective_name)
|
||||||
|
.and_then(|m| m.get(player_name))
|
||||||
|
.is_none_or(|s| s.locked);
|
||||||
|
|
||||||
|
if is_locked {
|
||||||
|
return Err(UNPRIMED_TRIGGER_ERROR.create_without_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_value = scoreboard
|
||||||
|
.get_scores()
|
||||||
|
.get(objective_name)
|
||||||
|
.and_then(|m| m.get(player_name))
|
||||||
|
.map_or(0, |s| s.value.0);
|
||||||
|
|
||||||
|
let new_value = current_value + value;
|
||||||
|
|
||||||
|
let updated_score = ScoreboardScore {
|
||||||
|
entity_name: Box::leak(player_name.clone().into_boxed_str()),
|
||||||
|
objective_name: Box::leak(objective_name.to_string().into_boxed_str()),
|
||||||
|
value: VarInt(new_value),
|
||||||
|
display_name: None,
|
||||||
|
number_format: None,
|
||||||
|
locked: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
scoreboard.update_score(world, updated_score).await;
|
||||||
|
|
||||||
|
context
|
||||||
|
.source
|
||||||
|
.send_feedback(
|
||||||
|
TextComponent::translate_cross(
|
||||||
|
translation::java::COMMANDS_TRIGGER_ADD_SUCCESS,
|
||||||
|
translation::java::COMMANDS_TRIGGER_ADD_SUCCESS,
|
||||||
|
[
|
||||||
|
objective_display_name,
|
||||||
|
TextComponent::text(value.to_string()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(new_value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SetTriggerExecutor;
|
||||||
|
|
||||||
|
impl CommandExecutor for SetTriggerExecutor {
|
||||||
|
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let player = context.source.player_or_err()?;
|
||||||
|
let player_name = &player.gameprofile.name;
|
||||||
|
let objective_name = ObjectiveArgumentType::get(context, ARG_OBJECTIVE)?;
|
||||||
|
let value = IntegerArgumentType::get(context, ARG_VALUE)?;
|
||||||
|
|
||||||
|
let world = context.world();
|
||||||
|
let mut scoreboard = world.scoreboard.lock().await;
|
||||||
|
|
||||||
|
let objective = scoreboard
|
||||||
|
.get_objectives()
|
||||||
|
.get(objective_name)
|
||||||
|
.ok_or_else(|| INVALID_TRIGGER_ERROR.create_without_context())?;
|
||||||
|
|
||||||
|
if objective.criterion != "trigger" {
|
||||||
|
return Err(INVALID_TRIGGER_ERROR.create_without_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
let objective_display_name = objective.display_name.clone();
|
||||||
|
|
||||||
|
let is_locked = scoreboard
|
||||||
|
.get_scores()
|
||||||
|
.get(objective_name)
|
||||||
|
.and_then(|m| m.get(player_name))
|
||||||
|
.is_none_or(|s| s.locked);
|
||||||
|
|
||||||
|
if is_locked {
|
||||||
|
return Err(UNPRIMED_TRIGGER_ERROR.create_without_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
let updated_score = ScoreboardScore {
|
||||||
|
entity_name: Box::leak(player_name.clone().into_boxed_str()),
|
||||||
|
objective_name: Box::leak(objective_name.to_string().into_boxed_str()),
|
||||||
|
value: VarInt(value),
|
||||||
|
display_name: None,
|
||||||
|
number_format: None,
|
||||||
|
locked: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
scoreboard.update_score(world, updated_score).await;
|
||||||
|
|
||||||
|
context
|
||||||
|
.source
|
||||||
|
.send_feedback(
|
||||||
|
TextComponent::translate_cross(
|
||||||
|
translation::java::COMMANDS_TRIGGER_SET_SUCCESS,
|
||||||
|
translation::java::COMMANDS_TRIGGER_SET_SUCCESS,
|
||||||
|
[
|
||||||
|
objective_display_name,
|
||||||
|
TextComponent::text(value.to_string()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(dispatcher: &mut CommandDispatcher, registry: &mut PermissionRegistry) {
|
||||||
|
registry.register_permission_or_panic(Permission::new(
|
||||||
|
PERMISSION,
|
||||||
|
DESCRIPTION,
|
||||||
|
PermissionDefault::Allow,
|
||||||
|
));
|
||||||
|
|
||||||
|
dispatcher.register(
|
||||||
|
command("trigger", DESCRIPTION).requires(PERMISSION).then(
|
||||||
|
argument(ARG_OBJECTIVE, ObjectiveArgumentType)
|
||||||
|
.executes(SimpleTriggerExecutor)
|
||||||
|
.then(literal("add").then(
|
||||||
|
argument(ARG_VALUE, IntegerArgumentType::any()).executes(AddTriggerExecutor),
|
||||||
|
))
|
||||||
|
.then(literal("set").then(
|
||||||
|
argument(ARG_VALUE, IntegerArgumentType::any()).executes(SetTriggerExecutor),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -41,8 +41,13 @@ impl scoreboard::HostScoreboard for PluginHostState {
|
|||||||
RenderType::Hearts => pumpkin_protocol::java::client::play::RenderType::Hearts,
|
RenderType::Hearts => pumpkin_protocol::java::client::play::RenderType::Hearts,
|
||||||
};
|
};
|
||||||
|
|
||||||
let objective =
|
let objective = ScoreboardObjective::new(
|
||||||
ScoreboardObjective::new(Box::leak(name.into_boxed_str()), display_name, rt, None);
|
Box::leak(name.into_boxed_str()),
|
||||||
|
display_name,
|
||||||
|
rt,
|
||||||
|
None,
|
||||||
|
"dummy",
|
||||||
|
);
|
||||||
world
|
world
|
||||||
.scoreboard
|
.scoreboard
|
||||||
.lock()
|
.lock()
|
||||||
|
|||||||
@ -26,6 +26,16 @@ pub struct Scoreboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Scoreboard {
|
impl Scoreboard {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn get_objectives(&self) -> &HashMap<String, ScoreboardObjective<'static>> {
|
||||||
|
&self.objectives
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn get_scores(&self) -> &HashMap<String, HashMap<String, ScoreboardScore<'static>>> {
|
||||||
|
&self.scores
|
||||||
|
}
|
||||||
|
|
||||||
async fn broadcast_editioned<J: ClientPacket, B: BClientPacket>(
|
async fn broadcast_editioned<J: ClientPacket, B: BClientPacket>(
|
||||||
world: &World,
|
world: &World,
|
||||||
je_packet: &J,
|
je_packet: &J,
|
||||||
@ -281,6 +291,7 @@ pub struct ScoreboardObjective<'a> {
|
|||||||
pub display_name: TextComponent,
|
pub display_name: TextComponent,
|
||||||
pub render_type: RenderType,
|
pub render_type: RenderType,
|
||||||
pub number_format: Option<NumberFormat>,
|
pub number_format: Option<NumberFormat>,
|
||||||
|
pub criterion: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ScoreboardObjective<'a> {
|
impl<'a> ScoreboardObjective<'a> {
|
||||||
@ -290,12 +301,14 @@ impl<'a> ScoreboardObjective<'a> {
|
|||||||
display_name: TextComponent,
|
display_name: TextComponent,
|
||||||
render_type: RenderType,
|
render_type: RenderType,
|
||||||
number_format: Option<NumberFormat>,
|
number_format: Option<NumberFormat>,
|
||||||
|
criterion: &'a str,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
display_name,
|
display_name,
|
||||||
render_type,
|
render_type,
|
||||||
number_format,
|
number_format,
|
||||||
|
criterion,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,6 +319,7 @@ pub struct ScoreboardScore<'a> {
|
|||||||
pub value: VarInt,
|
pub value: VarInt,
|
||||||
pub display_name: Option<TextComponent>,
|
pub display_name: Option<TextComponent>,
|
||||||
pub number_format: Option<NumberFormat>,
|
pub number_format: Option<NumberFormat>,
|
||||||
|
pub locked: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ScoreboardScore<'a> {
|
impl<'a> ScoreboardScore<'a> {
|
||||||
@ -323,6 +337,7 @@ impl<'a> ScoreboardScore<'a> {
|
|||||||
value,
|
value,
|
||||||
display_name,
|
display_name,
|
||||||
number_format,
|
number_format,
|
||||||
|
locked: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user