Skip to content

Commit

Permalink
GH-865 Fix Illegal Stack and no space in /give command. (#865)
Browse files Browse the repository at this point in the history
* Fix Illegal Stack and no space in `/give` command.

* Fix style violations reported by Checkstyle.

* Don't use directly field's from EnchantmentLevelPair record.

* Fix `Material#isItem` usage.

* Follow @Rollczi feedback.

* Fix give processing

* Support offhand

* Remove InventoryUtil

* Cleanup messages.

---------

Co-authored-by: Rollczi <[email protected]>
  • Loading branch information
vLuckyyy and Rollczi authored Nov 13, 2024
1 parent d12d636 commit 63e330b
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 177 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.eternalcode.core.injector.annotations.Bean;
import com.eternalcode.core.injector.annotations.component.BeanSetup;
import com.eternalcode.core.injector.bean.BeanFactory;
import com.eternalcode.core.notice.NoticeService;
import com.eternalcode.core.publish.Subscribe;
import com.eternalcode.core.publish.Subscriber;
import com.eternalcode.core.publish.event.EternalInitializeEvent;
Expand All @@ -12,6 +13,7 @@
import dev.rollczi.litecommands.adventure.bukkit.platform.LiteAdventurePlatformExtension;
import dev.rollczi.litecommands.annotations.LiteCommandsAnnotations;
import dev.rollczi.litecommands.bukkit.LiteBukkitFactory;
import dev.rollczi.litecommands.bukkit.LiteBukkitMessages;
import net.kyori.adventure.platform.AudienceProvider;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Server;
Expand All @@ -27,10 +29,21 @@ class LiteCommandsSetup implements Subscriber {
Server server,
AudienceProvider audiencesProvider,
MiniMessage miniMessage,
NoticeService noticeService,
LiteCommandsAnnotations<CommandSender> liteCommandsAnnotations
) {
return LiteBukkitFactory.builder("eternalcore", plugin, server)
.commands(liteCommandsAnnotations)
.message(LiteBukkitMessages.WORLD_NOT_EXIST, (invocation, world) -> noticeService.create()
.sender(invocation.sender())
.notice(translation -> translation.argument().worldDoesntExist())
.placeholder("{WORLD}", world)
)
.message(LiteBukkitMessages.LOCATION_INVALID_FORMAT, (invocation, input) -> noticeService.create()
.sender(invocation.sender())
.notice(translation -> translation.argument().incorrectLocation())
.placeholder("{LOCATION}", input)
)
.extension(new LiteAdventurePlatformExtension<CommandSender>(audiencesProvider), extension -> extension
.serializer(miniMessage)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,9 @@ public static class Items {

@Description({ " ", "# The default item give amount, when no amount is specified in the command." })
public int defaultGiveAmount = 1;

@Description({ " ", "# Determines whether items should be dropped on the ground when the player's inventory is full" })
public boolean dropOnFullInventory = true;
}

@Description({ " ", "# Warp Section" })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,91 +2,69 @@

import com.eternalcode.annotations.scan.command.DescriptionDocs;
import com.eternalcode.core.configuration.implementation.PluginConfiguration;
import com.eternalcode.core.feature.essentials.item.enchant.EnchantArgument;
import com.eternalcode.core.injector.annotations.Inject;
import com.eternalcode.core.notice.NoticeService;
import com.eternalcode.core.util.MaterialUtil;
import com.eternalcode.core.viewer.Viewer;
import dev.rollczi.litecommands.annotations.argument.Arg;
import dev.rollczi.litecommands.annotations.context.Context;
import dev.rollczi.litecommands.annotations.execute.Execute;
import dev.rollczi.litecommands.annotations.permission.Permission;
import dev.rollczi.litecommands.annotations.command.Command;
import dev.triumphteam.gui.builder.item.ItemBuilder;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;

@Command(name = "give", aliases = { "i", "item" })
@Permission("eternalcore.give")
class GiveCommand {

private final NoticeService noticeService;
private final PluginConfiguration pluginConfig;
private final GiveService giveService;
private final PluginConfiguration config;

@Inject
GiveCommand(NoticeService noticeService, PluginConfiguration pluginConfig) {
GiveCommand(NoticeService noticeService, GiveService giveService, PluginConfiguration config) {
this.noticeService = noticeService;
this.pluginConfig = pluginConfig;
this.giveService = giveService;
this.config = config;
}

@Execute
@DescriptionDocs(description = "Gives you an item", arguments = "<item>")
void execute(@Context Player player, @Arg Material material) {
String formattedMaterial = MaterialUtil.format(material);

this.giveItem(player, material);

this.noticeService.create()
.placeholder("{ITEM}", formattedMaterial)
.notice(translation -> translation.item().giveReceived())
.player(player.getUniqueId())
.send();
this.execute(player, material, this.config.items.defaultGiveAmount);
}

@Execute
@DescriptionDocs(description = "Gives an item to another player", arguments = "<item> <player>")
void execute(@Context Viewer viewer, @Arg Material material, @Arg Player target) {
String formattedMaterial = MaterialUtil.format(material);

this.giveItem(target, material);

this.noticeService.create()
.placeholder("{ITEM}", formattedMaterial)
.notice(translation -> translation.item().giveReceived())
.player(target.getUniqueId())
.send();

this.noticeService.create()
.placeholder("{ITEM}", formattedMaterial)
.placeholder("{PLAYER}", target.getName())
.notice(translation -> translation.item().giveGiven())
.viewer(viewer)
.send();
void execute(@Context CommandSender sender, @Arg Material material, @Arg Player target) {
this.execute(sender, material, this.config.items.defaultGiveAmount, target);
}

@Execute
@DescriptionDocs(description = "Gives you an item with a custom amount", arguments = "<item> <amount>")
void execute(@Context Player player, @Arg Material material, @Arg(GiveArgument.KEY) int amount) {
String formattedMaterial = MaterialUtil.format(material);

this.giveItem(player, material, amount);
boolean isSuccess = this.giveService.giveItem(player, player, material, amount);

this.noticeService.create()
.placeholder("{ITEM}", formattedMaterial)
.notice(translation -> translation.item().giveReceived())
.player(player.getUniqueId())
.send();
if (isSuccess) {
this.noticeService.create()
.placeholder("{ITEM}", MaterialUtil.format(material))
.notice(translation -> translation.item().giveReceived())
.player(player.getUniqueId())
.send();
}
}

@Execute
@DescriptionDocs(description = "Gives an item with a custom amount to another player", arguments = "<item> <amount> <player>")
void execute(@Context Viewer viewer, @Arg Material material, @Arg(GiveArgument.KEY) int amount, @Arg Player target) {
String formattedMaterial = MaterialUtil.format(material);
void execute(@Context CommandSender sender, @Arg Material material, @Arg(GiveArgument.KEY) int amount, @Arg Player target) {
boolean isSuccess = this.giveService.giveItem(sender, target, material, amount);

this.giveItem(target, material, amount);
if (!isSuccess) {
return;
}

String formattedMaterial = MaterialUtil.format(material);
this.noticeService.create()
.placeholder("{ITEM}", formattedMaterial)
.notice(translation -> translation.item().giveReceived())
Expand All @@ -97,68 +75,8 @@ void execute(@Context Viewer viewer, @Arg Material material, @Arg(GiveArgument.K
.placeholder("{ITEM}", formattedMaterial)
.placeholder("{PLAYER}", target.getName())
.notice(translation -> translation.item().giveGiven())
.viewer(viewer)
.send();
}

@Execute
@DescriptionDocs(description = "Gives an item with a custom amount to another player", arguments = "<item> <amount> <enchantment> <level> <player>")
void execute(@Context Viewer viewer, @Arg Material material, @Arg(GiveArgument.KEY) int amount, @Arg Enchantment enchantment, @Arg(EnchantArgument.KEY) int level, @Arg Player target) {
String formattedMaterial = MaterialUtil.format(material);

this.giveItem(target, material, amount, enchantment, level);

this.noticeService.create()
.placeholder("{ITEM}", formattedMaterial)
.placeholder("{ENCHANTMENT}", enchantment.getKey().getKey())
.placeholder("{ENCHANTMENT_LEVEL}", String.valueOf(level))
.notice(translation -> translation.item().giveReceivedEnchantment())
.player(target.getUniqueId())
.send();

this.noticeService.create()
.placeholder("{ITEM}", formattedMaterial)
.placeholder("{PLAYER}", target.getName())
.placeholder("{ENCHANTMENT}", enchantment.getKey().getKey())
.placeholder("{ENCHANTMENT_LEVEL}", String.valueOf(level))
.notice(translation -> translation.item().giveGivenEnchantment())
.viewer(viewer)
.sender(sender)
.send();
}

private void giveItem(Player player, Material material) {
int amount = this.pluginConfig.items.defaultGiveAmount;

if (!material.isItem()) {
this.noticeService.create()
.notice(translation -> translation.item().giveNotItem())
.player(player.getUniqueId())
.send();
return;
}

ItemStack item = ItemBuilder.from(material)
.amount(amount)
.build();

player.getInventory().addItem(item);
}

private void giveItem(Player player, Material material, int amount) {
ItemStack item = ItemBuilder.from(material)
.amount(amount)
.build();

player.getInventory().addItem(item);
}

private void giveItem(Player player, Material material, int amount, Enchantment enchantment, int level) {
ItemStack item = ItemBuilder.from(material)
.amount(amount)
.enchant(enchantment, level)
.build();

player.getInventory().addItem(item);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.eternalcode.core.feature.essentials.item.give;

import com.eternalcode.core.configuration.implementation.PluginConfiguration;
import com.eternalcode.core.injector.annotations.Inject;
import com.eternalcode.core.injector.annotations.component.Service;
import com.eternalcode.core.notice.NoticeService;
import java.util.Optional;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;

@Service
class GiveService {

private final PluginConfiguration pluginConfiguration;
private final NoticeService noticeService;

@Inject
public GiveService(PluginConfiguration pluginConfiguration, NoticeService noticeService) {
this.pluginConfiguration = pluginConfiguration;
this.noticeService = noticeService;
}

public boolean giveItem(CommandSender sender, Player player, Material material, int amount) {
if (this.isInvalidMaterial(material)) {
this.noticeService.create()
.notice(translation -> translation.item().giveNotItem())
.sender(sender)
.send();

return false;
}

PlayerInventory inventory = player.getInventory();
GiveResult giveResult = this.processGive(new PlayerContents(inventory.getStorageContents(), inventory.getItemInOffHand()), new ItemStack(material, amount));
Optional<ItemStack> rest = giveResult.rest();

if (rest.isPresent() && !this.pluginConfiguration.items.dropOnFullInventory) {
this.noticeService.create()
.notice(translation -> translation.item().giveNoSpace())
.sender(sender)
.send();
return false;
}

inventory.setStorageContents(giveResult.contents().storage);
inventory.setItemInOffHand(giveResult.contents().extraSlot);

if (rest.isPresent()) {
player.getWorld().dropItemNaturally(player.getLocation(), rest.get());
}

return true;
}

private boolean isInvalidMaterial(Material material) {
return !material.isItem();
}

private GiveResult processGive(PlayerContents contents, ItemStack itemToGive) {
for (int i = 0; i < contents.size(); i++) {
if (itemToGive.getAmount() < 0) {
throw new IllegalArgumentException("Item amount cannot be negative");
}

if (itemToGive.getAmount() == 0) {
return new GiveResult(contents, Optional.empty());
}

ItemStack content = contents.get(i);

if (content == null || content.getType().isAir()) {
contents.set(i, processContentSlot(itemToGive, 0, itemToGive.clone()));
continue;
}

if (!content.isSimilar(itemToGive)) {
continue;
}

contents.set(i, processContentSlot(itemToGive, content.getAmount(), content.clone()));
}

if (itemToGive.getAmount() > 0) {
return new GiveResult(contents, Optional.of(itemToGive));
}

return new GiveResult(contents, Optional.empty());
}

private static ItemStack processContentSlot(ItemStack itemToGive, int amount, ItemStack cloned) {
int amountToConsume = Math.min(itemToGive.getAmount(), itemToGive.getMaxStackSize() - amount);

cloned.setAmount(amount + amountToConsume);
itemToGive.setAmount(itemToGive.getAmount() - amountToConsume);

return cloned;
}

private record GiveResult(PlayerContents contents, Optional<ItemStack> rest) {}

private static class PlayerContents {
private final ItemStack[] storage;
private ItemStack extraSlot;

private PlayerContents(ItemStack[] storage, ItemStack extraSlot) {
this.storage = storage;
this.extraSlot = extraSlot;
}

int size() {
return this.storage.length + 1;
}

ItemStack get(int index) {
if (index == this.size() - 1) {
return this.extraSlot;
}

return this.storage[index];
}

void set(int index, ItemStack item) {
if (index == this.size() - 1) {
this.extraSlot = item;
return;
}

this.storage[index] = item;
}
}

}
Loading

0 comments on commit 63e330b

Please sign in to comment.