Files
Pumpkin/crates/pumpkin/src/command/commands/team.rs
2026-08-07 10:58:46 +02:00

1130 lines
41 KiB
Rust

use crate::command::argument_builder::{
ArgumentBuilder, LiteralArgumentBuilder, argument, command, literal,
};
use crate::command::argument_types::core::bool::BoolArgumentType;
use crate::command::argument_types::core::string::StringArgumentType;
use crate::command::argument_types::entity::EntityArgumentType;
use crate::command::argument_types::team::TeamArgumentType;
use crate::command::argument_types::team_color::TeamColorArgumentType;
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::entity::EntityBase;
use crate::world::scoreboard::{CollisionRule, NameTagVisibility, Team};
use pumpkin_data::translation;
use pumpkin_util::PermissionLvl;
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
use pumpkin_util::text::TextComponent;
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_DISPLAY_NAME: &str = "displayName";
const ARG_TEAM: &str = "team";
const ARG_MEMBERS: &str = "members";
const ARG_VALUE: &str = "value";
const DUPLICATE_TEAM_ERROR: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_TEAM_ADD_DUPLICATE,
translation::java::COMMANDS_TEAM_ADD_DUPLICATE,
);
const TEAM_NOT_FOUND_ERROR: CommandErrorType<1> = CommandErrorType::new(
translation::java::TEAM_NOTFOUND,
translation::java::TEAM_NOTFOUND,
);
const EMPTY_UNCHANGED_ERROR: CommandErrorType<1> = CommandErrorType::new(
translation::java::COMMANDS_TEAM_EMPTY_UNCHANGED,
translation::java::COMMANDS_TEAM_EMPTY_UNCHANGED,
);
const COLOR_UNCHANGED_ERROR: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_TEAM_OPTION_COLOR_UNCHANGED,
translation::java::COMMANDS_TEAM_OPTION_COLOR_UNCHANGED,
);
const NAME_UNCHANGED_ERROR: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_TEAM_OPTION_NAME_UNCHANGED,
translation::java::COMMANDS_TEAM_OPTION_NAME_UNCHANGED,
);
const NAMETAG_VISIBILITY_UNCHANGED_ERROR: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_TEAM_OPTION_NAMETAGVISIBILITY_UNCHANGED,
translation::java::COMMANDS_TEAM_OPTION_NAMETAGVISIBILITY_UNCHANGED,
);
const COLLISION_RULE_UNCHANGED_ERROR: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_TEAM_OPTION_COLLISIONRULE_UNCHANGED,
translation::java::COMMANDS_TEAM_OPTION_COLLISIONRULE_UNCHANGED,
);
const FRIENDLY_FIRE_ALREADY_ENABLED_ERROR: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_TEAM_OPTION_FRIENDLYFIRE_ALREADYENABLED,
translation::java::COMMANDS_TEAM_OPTION_FRIENDLYFIRE_ALREADYENABLED,
);
const FRIENDLY_FIRE_ALREADY_DISABLED_ERROR: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_TEAM_OPTION_FRIENDLYFIRE_ALREADYDISABLED,
translation::java::COMMANDS_TEAM_OPTION_FRIENDLYFIRE_ALREADYDISABLED,
);
const SEE_FRIENDLY_INVISIBLES_ALREADY_ENABLED_ERROR: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_TEAM_OPTION_SEEFRIENDLYINVISIBLES_ALREADYENABLED,
translation::java::COMMANDS_TEAM_OPTION_SEEFRIENDLYINVISIBLES_ALREADYENABLED,
);
const SEE_FRIENDLY_INVISIBLES_ALREADY_DISABLED_ERROR: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_TEAM_OPTION_SEEFRIENDLYINVISIBLES_ALREADYDISABLED,
translation::java::COMMANDS_TEAM_OPTION_SEEFRIENDLYINVISIBLES_ALREADYDISABLED,
);
fn get_entity_scoreboard_name(entity: &dyn EntityBase) -> String {
entity.get_player().map_or_else(
|| entity.get_entity().entity_uuid.to_string(),
|player| player.gameprofile.name.clone(),
)
}
struct TeamAddExecutor {
has_display_name: bool,
}
impl CommandExecutor for TeamAddExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = StringArgumentType::get(context, ARG_TEAM_NAME)?;
let display_name = if self.has_display_name {
TextComponent::text(StringArgumentType::get(context, ARG_DISPLAY_NAME)?.to_string())
} else {
TextComponent::text(team_name.to_string())
};
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
if scoreboard.get_teams().contains_key(team_name) {
return Err(DUPLICATE_TEAM_ERROR.create_without_context());
}
let new_team = Team {
name: team_name.to_string(),
display_name: display_name.clone(),
options: 0,
nametag_visibility: NameTagVisibility::Always,
collision_rule: CollisionRule::Always,
color: NamedColor::White,
player_prefix: TextComponent::empty(),
player_suffix: TextComponent::empty(),
players: vec![],
};
scoreboard.add_team(world, new_team);
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_ADD_SUCCESS,
translation::java::COMMANDS_TEAM_ADD_SUCCESS,
[display_name],
),
true,
)
.await;
Ok(1)
})
}
}
struct TeamRemoveExecutor;
impl CommandExecutor for TeamRemoveExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let team = scoreboard.get_teams().get(team_name).ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?;
let team_display_name = team.display_name.clone();
scoreboard.remove_team(world, team_name);
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_REMOVE_SUCCESS,
translation::java::COMMANDS_TEAM_REMOVE_SUCCESS,
[team_display_name],
),
true,
)
.await;
Ok(1)
})
}
}
struct TeamEmptyExecutor;
impl CommandExecutor for TeamEmptyExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let team = scoreboard.get_teams().get(team_name).ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?;
let team_display_name = team.display_name.clone();
let players_to_remove = team.players.clone();
if players_to_remove.is_empty() {
return Err(EMPTY_UNCHANGED_ERROR.create_without_context(team_display_name));
}
for player in &players_to_remove {
scoreboard.remove_player_from_team(world, team_name, player);
}
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_EMPTY_SUCCESS,
translation::java::COMMANDS_TEAM_EMPTY_SUCCESS,
[
TextComponent::text(players_to_remove.len().to_string()),
team_display_name,
],
),
true,
)
.await;
Ok(players_to_remove.len() as i32)
})
}
}
struct TeamJoinExecutor {
has_members: bool,
}
impl CommandExecutor for TeamJoinExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let team = scoreboard.get_teams().get(team_name).ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?;
let team_display_name = team.display_name.clone();
let entity_names = if self.has_members {
let targets =
EntityArgumentType::get_optional_entities(context, ARG_MEMBERS).await?;
if targets.is_empty() {
return Err(
crate::command::argument_types::entity::NO_ENTITIES_ERROR_TYPE
.create_without_context(),
);
}
targets
.into_iter()
.map(|e| get_entity_scoreboard_name(&*e))
.collect::<Vec<_>>()
} else {
let sender_name = context.source.name.clone();
vec![sender_name]
};
let count = entity_names.len();
for name in &entity_names {
scoreboard.add_player_to_team(world, team_name, name.clone());
}
let msg = if count == 1 {
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_JOIN_SUCCESS_SINGLE,
translation::java::COMMANDS_TEAM_JOIN_SUCCESS_SINGLE,
[
TextComponent::text(entity_names[0].clone()),
team_display_name,
],
)
} else {
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_JOIN_SUCCESS_MULTIPLE,
translation::java::COMMANDS_TEAM_JOIN_SUCCESS_MULTIPLE,
[TextComponent::text(count.to_string()), team_display_name],
)
};
context.source.send_feedback(msg, true).await;
Ok(count as i32)
})
}
}
struct TeamLeaveExecutor {
has_members: bool,
}
impl CommandExecutor for TeamLeaveExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let entity_names = if self.has_members {
let targets =
EntityArgumentType::get_optional_entities(context, ARG_MEMBERS).await?;
if targets.is_empty() {
return Err(
crate::command::argument_types::entity::NO_ENTITIES_ERROR_TYPE
.create_without_context(),
);
}
targets
.into_iter()
.map(|e| get_entity_scoreboard_name(&*e))
.collect::<Vec<_>>()
} else {
let sender_name = context.source.name.clone();
vec![sender_name]
};
let mut removed_count = 0;
for name in &entity_names {
let mut found_team = None;
for team in scoreboard.get_teams().values() {
if team.players.contains(name) {
found_team = Some(team.name.clone());
break;
}
}
if let Some(team_name) = found_team {
scoreboard.remove_player_from_team(world, &team_name, name);
removed_count += 1;
}
}
let msg = if entity_names.len() == 1 {
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_LEAVE_SUCCESS_SINGLE,
translation::java::COMMANDS_TEAM_LEAVE_SUCCESS_SINGLE,
[TextComponent::text(entity_names[0].clone())],
)
} else {
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_LEAVE_SUCCESS_MULTIPLE,
translation::java::COMMANDS_TEAM_LEAVE_SUCCESS_MULTIPLE,
[TextComponent::text(removed_count.to_string())],
)
};
context.source.send_feedback(msg, true).await;
Ok(removed_count)
})
}
}
struct TeamListExecutor {
has_team: bool,
}
impl CommandExecutor for TeamListExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let world = context.world();
let scoreboard = world.scoreboard.lock().await;
if self.has_team {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let team = scoreboard.get_teams().get(team_name).ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?;
if team.players.is_empty() {
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_LIST_MEMBERS_EMPTY,
translation::java::COMMANDS_TEAM_LIST_MEMBERS_EMPTY,
[team.display_name.clone()],
),
false,
)
.await;
} else {
let mut list_comp = TextComponent::empty();
for (i, player) in team.players.iter().enumerate() {
if i > 0 {
list_comp = list_comp.add_child(TextComponent::text(", "));
}
list_comp = list_comp.add_child(TextComponent::text(player.clone()));
}
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_LIST_MEMBERS_SUCCESS,
translation::java::COMMANDS_TEAM_LIST_MEMBERS_SUCCESS,
[
team.display_name.clone(),
TextComponent::text(team.players.len().to_string()),
list_comp,
],
),
false,
)
.await;
}
Ok(team.players.len() as i32)
} else {
let teams = scoreboard.get_teams();
if teams.is_empty() {
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_LIST_TEAMS_EMPTY,
translation::java::COMMANDS_TEAM_LIST_TEAMS_EMPTY,
[],
),
false,
)
.await;
} else {
let mut list_comp = TextComponent::empty();
for (i, team) in teams.values().enumerate() {
if i > 0 {
list_comp = list_comp.add_child(TextComponent::text(", "));
}
list_comp = list_comp.add_child(team.display_name.clone());
}
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_LIST_TEAMS_SUCCESS,
translation::java::COMMANDS_TEAM_LIST_TEAMS_SUCCESS,
[TextComponent::text(teams.len().to_string()), list_comp],
),
false,
)
.await;
}
Ok(teams.len() as i32)
}
})
}
}
struct TeamModifyColorResetExecutor;
impl CommandExecutor for TeamModifyColorResetExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let mut team = scoreboard
.get_teams()
.get(team_name)
.ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?
.clone();
if team.color == NamedColor::White {
return Err(COLOR_UNCHANGED_ERROR.create_without_context());
}
team.color = NamedColor::White;
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_OPTION_COLOR_CLEAR_SUCCESS,
translation::java::COMMANDS_TEAM_OPTION_COLOR_CLEAR_SUCCESS,
[team_display_name],
),
true,
)
.await;
Ok(1)
})
}
}
struct TeamModifyColorExecutor;
impl CommandExecutor for TeamModifyColorExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let new_color = TeamColorArgumentType::get(context, ARG_VALUE)?;
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let mut team = scoreboard
.get_teams()
.get(team_name)
.ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?
.clone();
if team.color == new_color {
return Err(COLOR_UNCHANGED_ERROR.create_without_context());
}
team.color = new_color;
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_OPTION_COLOR_SUCCESS,
translation::java::COMMANDS_TEAM_OPTION_COLOR_SUCCESS,
[team_display_name, TextComponent::text(new_color.name())],
),
true,
)
.await;
Ok(1)
})
}
}
struct TeamModifyDisplayNameExecutor;
impl CommandExecutor for TeamModifyDisplayNameExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let new_name_str = StringArgumentType::get(context, ARG_VALUE)?;
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let mut team = scoreboard
.get_teams()
.get(team_name)
.ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?
.clone();
let new_display_name = TextComponent::text(new_name_str.to_string());
if team.display_name == new_display_name {
return Err(NAME_UNCHANGED_ERROR.create_without_context());
}
team.display_name = new_display_name.clone();
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_OPTION_NAME_SUCCESS,
translation::java::COMMANDS_TEAM_OPTION_NAME_SUCCESS,
[team_display_name, new_display_name],
),
true,
)
.await;
Ok(1)
})
}
}
struct TeamModifyPrefixExecutor;
impl CommandExecutor for TeamModifyPrefixExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let new_prefix_str = StringArgumentType::get(context, ARG_VALUE)?;
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let mut team = scoreboard
.get_teams()
.get(team_name)
.ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?
.clone();
let new_prefix = TextComponent::text(new_prefix_str.to_string());
team.player_prefix = new_prefix.clone();
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_OPTION_PREFIX_SUCCESS,
translation::java::COMMANDS_TEAM_OPTION_PREFIX_SUCCESS,
[team_display_name, new_prefix],
),
true,
)
.await;
Ok(1)
})
}
}
struct TeamModifySuffixExecutor;
impl CommandExecutor for TeamModifySuffixExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let new_suffix_str = StringArgumentType::get(context, ARG_VALUE)?;
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let mut team = scoreboard
.get_teams()
.get(team_name)
.ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?
.clone();
let new_suffix = TextComponent::text(new_suffix_str.to_string());
team.player_suffix = new_suffix.clone();
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_OPTION_SUFFIX_SUCCESS,
translation::java::COMMANDS_TEAM_OPTION_SUFFIX_SUCCESS,
[team_display_name, new_suffix],
),
true,
)
.await;
Ok(1)
})
}
}
struct TeamModifyFriendlyFireExecutor;
impl CommandExecutor for TeamModifyFriendlyFireExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let value = BoolArgumentType::get(context, ARG_VALUE)?;
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let mut team = scoreboard
.get_teams()
.get(team_name)
.ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?
.clone();
let is_enabled = (team.options & 0x01) != 0;
if value == is_enabled {
if value {
return Err(FRIENDLY_FIRE_ALREADY_ENABLED_ERROR.create_without_context());
}
return Err(FRIENDLY_FIRE_ALREADY_DISABLED_ERROR.create_without_context());
}
if value {
team.options |= 0x01;
} else {
team.options &= !0x01;
}
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
let key = if value {
translation::java::COMMANDS_TEAM_OPTION_FRIENDLYFIRE_ENABLED
} else {
translation::java::COMMANDS_TEAM_OPTION_FRIENDLYFIRE_DISABLED
};
context
.source
.send_feedback(
TextComponent::translate_cross(key, key, [team_display_name]),
true,
)
.await;
Ok(1)
})
}
}
struct TeamModifySeeFriendlyInvisiblesExecutor;
impl CommandExecutor for TeamModifySeeFriendlyInvisiblesExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let value = BoolArgumentType::get(context, ARG_VALUE)?;
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let mut team = scoreboard
.get_teams()
.get(team_name)
.ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?
.clone();
let is_enabled = (team.options & 0x02) != 0;
if value == is_enabled {
if value {
return Err(
SEE_FRIENDLY_INVISIBLES_ALREADY_ENABLED_ERROR.create_without_context()
);
}
return Err(SEE_FRIENDLY_INVISIBLES_ALREADY_DISABLED_ERROR.create_without_context());
}
if value {
team.options |= 0x02;
} else {
team.options &= !0x02;
}
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
let key = if value {
translation::java::COMMANDS_TEAM_OPTION_SEEFRIENDLYINVISIBLES_ENABLED
} else {
translation::java::COMMANDS_TEAM_OPTION_SEEFRIENDLYINVISIBLES_DISABLED
};
context
.source
.send_feedback(
TextComponent::translate_cross(key, key, [team_display_name]),
true,
)
.await;
Ok(1)
})
}
}
struct TeamModifyNametagVisibilityExecutor {
value: NameTagVisibility,
}
impl CommandExecutor for TeamModifyNametagVisibilityExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let mut team = scoreboard
.get_teams()
.get(team_name)
.ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?
.clone();
let is_unchanged = matches!(
(&team.nametag_visibility, &self.value),
(NameTagVisibility::Always, NameTagVisibility::Always)
| (NameTagVisibility::Never, NameTagVisibility::Never)
| (
NameTagVisibility::HideForOtherTeams,
NameTagVisibility::HideForOtherTeams
)
| (
NameTagVisibility::HideForOwnTeam,
NameTagVisibility::HideForOwnTeam
)
);
if is_unchanged {
return Err(NAMETAG_VISIBILITY_UNCHANGED_ERROR.create_without_context());
}
team.nametag_visibility = match self.value {
NameTagVisibility::Always => NameTagVisibility::Always,
NameTagVisibility::Never => NameTagVisibility::Never,
NameTagVisibility::HideForOtherTeams => NameTagVisibility::HideForOtherTeams,
NameTagVisibility::HideForOwnTeam => NameTagVisibility::HideForOwnTeam,
};
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_OPTION_NAMETAGVISIBILITY_SUCCESS,
translation::java::COMMANDS_TEAM_OPTION_NAMETAGVISIBILITY_SUCCESS,
[
team_display_name,
TextComponent::text(self.value.to_str().to_string()),
],
),
true,
)
.await;
Ok(1)
})
}
}
struct TeamModifyDeathMessageVisibilityExecutor {
value: &'static str,
}
impl CommandExecutor for TeamModifyDeathMessageVisibilityExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let world = context.world();
let scoreboard = world.scoreboard.lock().await;
let team = scoreboard.get_teams().get(team_name).ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?;
let team_display_name = team.display_name.clone();
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_OPTION_DEATHMESSAGEVISIBILITY_SUCCESS,
translation::java::COMMANDS_TEAM_OPTION_DEATHMESSAGEVISIBILITY_SUCCESS,
[
team_display_name,
TextComponent::text(self.value.to_string()),
],
),
true,
)
.await;
Ok(1)
})
}
}
struct TeamModifyCollisionRuleExecutor {
value: CollisionRule,
}
impl CommandExecutor for TeamModifyCollisionRuleExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
let world = context.world();
let mut scoreboard = world.scoreboard.lock().await;
let mut team = scoreboard
.get_teams()
.get(team_name)
.ok_or_else(|| {
TEAM_NOT_FOUND_ERROR
.create_without_context(TextComponent::text(team_name.to_string()))
})?
.clone();
let is_unchanged = matches!(
(&team.collision_rule, &self.value),
(CollisionRule::Always, CollisionRule::Always)
| (CollisionRule::Never, CollisionRule::Never)
| (CollisionRule::PushOtherTeams, CollisionRule::PushOtherTeams)
| (CollisionRule::PushOwnTeam, CollisionRule::PushOwnTeam)
);
if is_unchanged {
return Err(COLLISION_RULE_UNCHANGED_ERROR.create_without_context());
}
team.collision_rule = match self.value {
CollisionRule::Always => CollisionRule::Always,
CollisionRule::Never => CollisionRule::Never,
CollisionRule::PushOtherTeams => CollisionRule::PushOtherTeams,
CollisionRule::PushOwnTeam => CollisionRule::PushOwnTeam,
};
let team_display_name = team.display_name.clone();
scoreboard.update_team(world, team);
context
.source
.send_feedback(
TextComponent::translate_cross(
translation::java::COMMANDS_TEAM_OPTION_COLLISIONRULE_SUCCESS,
translation::java::COMMANDS_TEAM_OPTION_COLLISIONRULE_SUCCESS,
[
team_display_name,
TextComponent::text(self.value.to_str().to_string()),
],
),
true,
)
.await;
Ok(1)
})
}
}
fn add_branch() -> LiteralArgumentBuilder {
literal("add").then(
argument(ARG_TEAM_NAME, StringArgumentType::SingleWord)
.executes(TeamAddExecutor {
has_display_name: false,
})
.then(
argument(ARG_DISPLAY_NAME, StringArgumentType::GreedyPhrase).executes(
TeamAddExecutor {
has_display_name: true,
},
),
),
)
}
fn remove_branch() -> LiteralArgumentBuilder {
literal("remove").then(argument(ARG_TEAM, TeamArgumentType).executes(TeamRemoveExecutor))
}
fn empty_branch() -> LiteralArgumentBuilder {
literal("empty").then(argument(ARG_TEAM, TeamArgumentType).executes(TeamEmptyExecutor))
}
fn join_branch() -> LiteralArgumentBuilder {
literal("join").then(
argument(ARG_TEAM, TeamArgumentType)
.executes(TeamJoinExecutor { has_members: false })
.then(
argument(ARG_MEMBERS, EntityArgumentType::Entities)
.executes(TeamJoinExecutor { has_members: true }),
),
)
}
fn leave_branch() -> LiteralArgumentBuilder {
literal("leave")
.executes(TeamLeaveExecutor { has_members: false })
.then(
argument(ARG_MEMBERS, EntityArgumentType::Entities)
.executes(TeamLeaveExecutor { has_members: true }),
)
}
fn list_branch() -> LiteralArgumentBuilder {
literal("list")
.executes(TeamListExecutor { has_team: false })
.then(argument(ARG_TEAM, TeamArgumentType).executes(TeamListExecutor { has_team: true }))
}
#[expect(clippy::too_many_lines)]
fn modify_branch() -> LiteralArgumentBuilder {
literal("modify").then(
argument(ARG_TEAM, TeamArgumentType)
.then(
literal("color")
.then(literal("reset").executes(TeamModifyColorResetExecutor))
.then(
argument(ARG_VALUE, TeamColorArgumentType)
.executes(TeamModifyColorExecutor),
),
)
.then(
literal("displayName").then(
argument(ARG_VALUE, StringArgumentType::GreedyPhrase)
.executes(TeamModifyDisplayNameExecutor),
),
)
.then(
literal("prefix").then(
argument(ARG_VALUE, StringArgumentType::GreedyPhrase)
.executes(TeamModifyPrefixExecutor),
),
)
.then(
literal("suffix").then(
argument(ARG_VALUE, StringArgumentType::GreedyPhrase)
.executes(TeamModifySuffixExecutor),
),
)
.then(literal("friendlyFire").then(
argument(ARG_VALUE, BoolArgumentType).executes(TeamModifyFriendlyFireExecutor),
))
.then(
literal("seeFriendlyInvisibles").then(
argument(ARG_VALUE, BoolArgumentType)
.executes(TeamModifySeeFriendlyInvisiblesExecutor),
),
)
.then(
literal("nametagVisibility")
.then(
literal("always").executes(TeamModifyNametagVisibilityExecutor {
value: NameTagVisibility::Always,
}),
)
.then(
literal("never").executes(TeamModifyNametagVisibilityExecutor {
value: NameTagVisibility::Never,
}),
)
.then(literal("hideForOtherTeams").executes(
TeamModifyNametagVisibilityExecutor {
value: NameTagVisibility::HideForOtherTeams,
},
))
.then(literal("hideForOwnTeam").executes(
TeamModifyNametagVisibilityExecutor {
value: NameTagVisibility::HideForOwnTeam,
},
)),
)
.then(
literal("deathMessageVisibility")
.then(
literal("always")
.executes(TeamModifyDeathMessageVisibilityExecutor { value: "always" }),
)
.then(
literal("never")
.executes(TeamModifyDeathMessageVisibilityExecutor { value: "never" }),
)
.then(literal("hideForOtherTeams").executes(
TeamModifyDeathMessageVisibilityExecutor {
value: "hideForOtherTeams",
},
))
.then(literal("hideForOwnTeam").executes(
TeamModifyDeathMessageVisibilityExecutor {
value: "hideForOwnTeam",
},
)),
)
.then(
literal("collisionRule")
.then(literal("always").executes(TeamModifyCollisionRuleExecutor {
value: CollisionRule::Always,
}))
.then(literal("never").executes(TeamModifyCollisionRuleExecutor {
value: CollisionRule::Never,
}))
.then(
literal("pushOtherTeams").executes(TeamModifyCollisionRuleExecutor {
value: CollisionRule::PushOtherTeams,
}),
)
.then(
literal("pushOwnTeam").executes(TeamModifyCollisionRuleExecutor {
value: CollisionRule::PushOwnTeam,
}),
),
),
)
}
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("team", DESCRIPTION)
.requires(PERMISSION)
.then(add_branch())
.then(remove_branch())
.then(empty_branch())
.then(join_branch())
.then(leave_branch())
.then(list_branch())
.then(modify_branch()),
);
}