use std::sync::Arc; use pumpkin_data::item_stack::ItemStack; use pumpkin_data::translation; use pumpkin_util::text::TextComponent; use tokio::sync::Mutex; use crate::command::args::bounded_num::BoundedNumArgumentConsumer; use crate::command::args::players::PlayersArgumentConsumer; use crate::command::args::resource::item::{ItemPredicate, ItemPredicateArgumentConsumer}; use crate::command::args::{Arg, ConsumedArgs, FindArg, FindArgDefaultName}; use crate::command::tree::CommandTree; use crate::command::tree::builder::{argument, require}; use crate::command::{CommandError, CommandExecutor, CommandResult, CommandSender}; use crate::entity::EntityBase; use crate::entity::player::Player; use CommandError::InvalidConsumption; const NAMES: [&str; 1] = ["clear"]; const DESCRIPTION: &str = "Clear your inventory or that of target(s)"; const ARG_TARGETS: &str = "targets"; const ARG_ITEM: &str = "item"; const ARG_MAX_COUNT: &str = "max_count"; const MAX_NO_UPPER_LIMIT: i32 = -1; const MAX_NO_CLEAR_BUT_SIMULATE: i32 = 0; /// Returns the number of items actually cleared. /// /// If `max` provided is equal to [`MAX_NO_CLEAR_BUT_SIMULATE`] (`0`), then no items are cleared, /// but instead returns the items that *could* be cleared. /// /// If `max` provided is [`MAX_NO_UPPER_LIMIT`] (`-1`), then there is no limit in clearing. /// /// Otherwise, at most `max` items are cleared. async fn clear_player(target: &Player, item: &ItemPredicate, max: i32) -> i32 { let inventory = target.inventory(); let mut count: i32 = 0; let mut max: i32 = max; let mut is_done: bool = false; iter_test_and_clear( &inventory.main_inventory, &mut count, &mut max, item, &mut is_done, ) .await; let entity_equipment_lock = inventory.entity_equipment.lock().await; iter_test_and_clear( entity_equipment_lock.equipment.values(), &mut count, &mut max, item, &mut is_done, ) .await; drop(entity_equipment_lock); count } async fn iter_test_and_clear<'i, I>( iter: I, count: &mut i32, max: &mut i32, item: &ItemPredicate, is_done: &mut bool, ) where I: IntoIterator>>, { // Don't need to enter the loop if we are already done. if !*is_done { for slot in iter { test_and_clear(count, max, item, slot, is_done).await; if *is_done { break; } } } } async fn test_and_clear( count: &mut i32, max: &mut i32, item: &ItemPredicate, slot: &Arc>, is_done: &mut bool, ) { let mut slot_lock = slot.lock().await; if item.test_item_stack(&slot_lock) { let item_count = slot_lock.item_count as i32; if *max == MAX_NO_CLEAR_BUT_SIMULATE { *count += item_count; } else if *max == MAX_NO_UPPER_LIMIT { *count += item_count; *slot_lock = ItemStack::EMPTY.clone(); } else { // We need more complex logic for this one. let taken = i32::min(*max, item_count); // Take all that we can. *count += taken; if taken == item_count { *slot_lock = ItemStack::EMPTY.clone(); *max -= taken; // Set `is_done` flag if required. *is_done = *max == 0; } else { // As `slot_lock.item_count` is limited to `u8`, this should be fine. slot_lock.decrement(taken as u8); *max -= taken; // Set `is_done` flag if required. *is_done = true; } } } } async fn command_result( sender: &CommandSender, item_count: i32, max_count: i32, targets: &[Arc], ) -> Result { match clear_command_text_output(item_count, max_count, targets).await { Ok(success) => { sender.send_message(success).await; Ok(item_count) } Err(failure) => Err(CommandError::CommandFailed(failure)), } } async fn clear_command_text_output( item_count: i32, max_count: i32, targets: &[Arc], ) -> Result { match (targets, item_count == 0, max_count == 0) { ([target], true, _) => Err(TextComponent::translate_cross( translation::java::CLEAR_FAILED_SINGLE, translation::bedrock::COMMANDS_CLEAR_FAILURE, [target.get_display_name().await], )), (targets, true, _) => Err(TextComponent::translate_cross( translation::java::CLEAR_FAILED_MULTIPLE, translation::bedrock::COMMANDS_CLEAR_FAILURE, [TextComponent::text(targets.len().to_string())], )), ([target], false, false) => Ok(TextComponent::translate_cross( translation::java::COMMANDS_CLEAR_SUCCESS_SINGLE, translation::java::COMMANDS_CLEAR_SUCCESS_SINGLE, [ TextComponent::text(item_count.to_string()), target.get_display_name().await, ], )), (targets, false, false) => Ok(TextComponent::translate_cross( translation::java::COMMANDS_CLEAR_SUCCESS_MULTIPLE, translation::java::COMMANDS_CLEAR_SUCCESS_MULTIPLE, [ TextComponent::text(item_count.to_string()), TextComponent::text(targets.len().to_string()), ], )), ([target], false, true) => Ok(TextComponent::translate_cross( translation::java::COMMANDS_CLEAR_TEST_SINGLE, translation::java::COMMANDS_CLEAR_TEST_SINGLE, [ TextComponent::text(item_count.to_string()), target.get_display_name().await, ], )), (targets, false, true) => Ok(TextComponent::translate_cross( translation::java::COMMANDS_CLEAR_TEST_MULTIPLE, translation::java::COMMANDS_CLEAR_TEST_MULTIPLE, [ TextComponent::text(item_count.to_string()), TextComponent::text(targets.len().to_string()), ], )), } } const fn count_consumer() -> BoundedNumArgumentConsumer { BoundedNumArgumentConsumer::new().min(0).name(ARG_MAX_COUNT) } struct SelfExecutor; impl CommandExecutor for SelfExecutor { fn execute<'a>( &'a self, sender: &'a CommandSender, _server: &'a crate::server::Server, _args: &'a ConsumedArgs<'a>, ) -> CommandResult<'a> { Box::pin(async move { let target = sender.as_player().ok_or(CommandError::InvalidRequirement)?; let items_cleared = clear_player(&target, &ItemPredicate::Any, MAX_NO_UPPER_LIMIT).await; command_result(sender, items_cleared, MAX_NO_UPPER_LIMIT, &[target]).await }) } } struct Executor; impl CommandExecutor for Executor { fn execute<'a>( &'a self, sender: &'a CommandSender, _server: &'a crate::server::Server, args: &'a ConsumedArgs<'a>, ) -> CommandResult<'a> { Box::pin(async move { let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else { return Err(InvalidConsumption(Some(ARG_TARGETS.into()))); }; let mut total_items_cleared = 0; for target in targets { total_items_cleared += clear_player(target, &ItemPredicate::Any, MAX_NO_UPPER_LIMIT).await; } command_result(sender, total_items_cleared, MAX_NO_UPPER_LIMIT, targets).await }) } } struct ItemExecutor; impl CommandExecutor for ItemExecutor { fn execute<'a>( &'a self, sender: &'a CommandSender, _server: &'a crate::server::Server, args: &'a ConsumedArgs<'a>, ) -> CommandResult<'a> { Box::pin(async move { let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else { return Err(InvalidConsumption(Some(ARG_TARGETS.into()))); }; let item = ItemPredicateArgumentConsumer::find_arg(args, ARG_ITEM)?; let mut total_items_cleared = 0; for target in targets { total_items_cleared += clear_player(target, &item, MAX_NO_UPPER_LIMIT).await; } command_result(sender, total_items_cleared, MAX_NO_UPPER_LIMIT, targets).await }) } } struct ItemCountExecutor; impl CommandExecutor for ItemCountExecutor { fn execute<'a>( &'a self, sender: &'a CommandSender, _server: &'a crate::server::Server, args: &'a ConsumedArgs<'a>, ) -> CommandResult<'a> { Box::pin(async move { let Some(Arg::Players(targets)) = args.get(&ARG_TARGETS) else { return Err(InvalidConsumption(Some(ARG_TARGETS.into()))); }; let item = ItemPredicateArgumentConsumer::find_arg(args, ARG_ITEM)?; let Ok(max) = count_consumer().find_arg_default_name(args)? else { return Err(CommandError::CommandFailed(TextComponent::translate_cross( translation::java::PARSING_INT_INVALID, translation::java::PARSING_INT_INVALID, [TextComponent::text(i32::MAX.to_string())], ))); }; let mut total_items_cleared = 0; for target in targets { total_items_cleared += clear_player(target, &item, max).await; } command_result(sender, total_items_cleared, max, targets).await }) } } // #[expect(clippy::redundant_closure_for_method_calls)] // causes lifetime issues pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) .then( argument(ARG_TARGETS, PlayersArgumentConsumer) .execute(Executor) .then( argument(ARG_ITEM, ItemPredicateArgumentConsumer) .execute(ItemExecutor) .then( argument(ARG_MAX_COUNT, count_consumer().name(ARG_MAX_COUNT)) .execute(ItemCountExecutor), ), ), ) .then(require(super::super::CommandSender::is_player).execute(SelfExecutor)) }