Skip to content

Commit 63e330b

Browse files
vLuckyyyRollczi
andauthored
GH-865 Fix Illegal Stack and no space in /give command. (#865)
* 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]>
1 parent d12d636 commit 63e330b

File tree

9 files changed

+232
-177
lines changed

9 files changed

+232
-177
lines changed

eternalcore-core/src/main/java/com/eternalcode/core/bridge/litecommand/LiteCommandsSetup.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.eternalcode.core.injector.annotations.Bean;
44
import com.eternalcode.core.injector.annotations.component.BeanSetup;
55
import com.eternalcode.core.injector.bean.BeanFactory;
6+
import com.eternalcode.core.notice.NoticeService;
67
import com.eternalcode.core.publish.Subscribe;
78
import com.eternalcode.core.publish.Subscriber;
89
import com.eternalcode.core.publish.event.EternalInitializeEvent;
@@ -12,6 +13,7 @@
1213
import dev.rollczi.litecommands.adventure.bukkit.platform.LiteAdventurePlatformExtension;
1314
import dev.rollczi.litecommands.annotations.LiteCommandsAnnotations;
1415
import dev.rollczi.litecommands.bukkit.LiteBukkitFactory;
16+
import dev.rollczi.litecommands.bukkit.LiteBukkitMessages;
1517
import net.kyori.adventure.platform.AudienceProvider;
1618
import net.kyori.adventure.text.minimessage.MiniMessage;
1719
import org.bukkit.Server;
@@ -27,10 +29,21 @@ class LiteCommandsSetup implements Subscriber {
2729
Server server,
2830
AudienceProvider audiencesProvider,
2931
MiniMessage miniMessage,
32+
NoticeService noticeService,
3033
LiteCommandsAnnotations<CommandSender> liteCommandsAnnotations
3134
) {
3235
return LiteBukkitFactory.builder("eternalcore", plugin, server)
3336
.commands(liteCommandsAnnotations)
37+
.message(LiteBukkitMessages.WORLD_NOT_EXIST, (invocation, world) -> noticeService.create()
38+
.sender(invocation.sender())
39+
.notice(translation -> translation.argument().worldDoesntExist())
40+
.placeholder("{WORLD}", world)
41+
)
42+
.message(LiteBukkitMessages.LOCATION_INVALID_FORMAT, (invocation, input) -> noticeService.create()
43+
.sender(invocation.sender())
44+
.notice(translation -> translation.argument().incorrectLocation())
45+
.placeholder("{LOCATION}", input)
46+
)
3447
.extension(new LiteAdventurePlatformExtension<CommandSender>(audiencesProvider), extension -> extension
3548
.serializer(miniMessage)
3649
);

eternalcore-core/src/main/java/com/eternalcode/core/configuration/implementation/PluginConfiguration.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,9 @@ public static class Items {
369369

370370
@Description({ " ", "# The default item give amount, when no amount is specified in the command." })
371371
public int defaultGiveAmount = 1;
372+
373+
@Description({ " ", "# Determines whether items should be dropped on the ground when the player's inventory is full" })
374+
public boolean dropOnFullInventory = true;
372375
}
373376

374377
@Description({ " ", "# Warp Section" })

eternalcore-core/src/main/java/com/eternalcode/core/feature/essentials/item/give/GiveCommand.java

Lines changed: 24 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -2,91 +2,69 @@
22

33
import com.eternalcode.annotations.scan.command.DescriptionDocs;
44
import com.eternalcode.core.configuration.implementation.PluginConfiguration;
5-
import com.eternalcode.core.feature.essentials.item.enchant.EnchantArgument;
65
import com.eternalcode.core.injector.annotations.Inject;
76
import com.eternalcode.core.notice.NoticeService;
87
import com.eternalcode.core.util.MaterialUtil;
9-
import com.eternalcode.core.viewer.Viewer;
108
import dev.rollczi.litecommands.annotations.argument.Arg;
119
import dev.rollczi.litecommands.annotations.context.Context;
1210
import dev.rollczi.litecommands.annotations.execute.Execute;
1311
import dev.rollczi.litecommands.annotations.permission.Permission;
1412
import dev.rollczi.litecommands.annotations.command.Command;
15-
import dev.triumphteam.gui.builder.item.ItemBuilder;
1613
import org.bukkit.Material;
17-
import org.bukkit.enchantments.Enchantment;
14+
import org.bukkit.command.CommandSender;
1815
import org.bukkit.entity.Player;
19-
import org.bukkit.inventory.ItemStack;
2016

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

2521
private final NoticeService noticeService;
26-
private final PluginConfiguration pluginConfig;
22+
private final GiveService giveService;
23+
private final PluginConfiguration config;
2724

2825
@Inject
29-
GiveCommand(NoticeService noticeService, PluginConfiguration pluginConfig) {
26+
GiveCommand(NoticeService noticeService, GiveService giveService, PluginConfiguration config) {
3027
this.noticeService = noticeService;
31-
this.pluginConfig = pluginConfig;
28+
this.giveService = giveService;
29+
this.config = config;
3230
}
3331

3432
@Execute
3533
@DescriptionDocs(description = "Gives you an item", arguments = "<item>")
3634
void execute(@Context Player player, @Arg Material material) {
37-
String formattedMaterial = MaterialUtil.format(material);
38-
39-
this.giveItem(player, material);
40-
41-
this.noticeService.create()
42-
.placeholder("{ITEM}", formattedMaterial)
43-
.notice(translation -> translation.item().giveReceived())
44-
.player(player.getUniqueId())
45-
.send();
35+
this.execute(player, material, this.config.items.defaultGiveAmount);
4636
}
4737

4838
@Execute
4939
@DescriptionDocs(description = "Gives an item to another player", arguments = "<item> <player>")
50-
void execute(@Context Viewer viewer, @Arg Material material, @Arg Player target) {
51-
String formattedMaterial = MaterialUtil.format(material);
52-
53-
this.giveItem(target, material);
54-
55-
this.noticeService.create()
56-
.placeholder("{ITEM}", formattedMaterial)
57-
.notice(translation -> translation.item().giveReceived())
58-
.player(target.getUniqueId())
59-
.send();
60-
61-
this.noticeService.create()
62-
.placeholder("{ITEM}", formattedMaterial)
63-
.placeholder("{PLAYER}", target.getName())
64-
.notice(translation -> translation.item().giveGiven())
65-
.viewer(viewer)
66-
.send();
40+
void execute(@Context CommandSender sender, @Arg Material material, @Arg Player target) {
41+
this.execute(sender, material, this.config.items.defaultGiveAmount, target);
6742
}
6843

6944
@Execute
7045
@DescriptionDocs(description = "Gives you an item with a custom amount", arguments = "<item> <amount>")
7146
void execute(@Context Player player, @Arg Material material, @Arg(GiveArgument.KEY) int amount) {
72-
String formattedMaterial = MaterialUtil.format(material);
73-
74-
this.giveItem(player, material, amount);
47+
boolean isSuccess = this.giveService.giveItem(player, player, material, amount);
7548

76-
this.noticeService.create()
77-
.placeholder("{ITEM}", formattedMaterial)
78-
.notice(translation -> translation.item().giveReceived())
79-
.player(player.getUniqueId())
80-
.send();
49+
if (isSuccess) {
50+
this.noticeService.create()
51+
.placeholder("{ITEM}", MaterialUtil.format(material))
52+
.notice(translation -> translation.item().giveReceived())
53+
.player(player.getUniqueId())
54+
.send();
55+
}
8156
}
8257

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

88-
this.giveItem(target, material, amount);
63+
if (!isSuccess) {
64+
return;
65+
}
8966

67+
String formattedMaterial = MaterialUtil.format(material);
9068
this.noticeService.create()
9169
.placeholder("{ITEM}", formattedMaterial)
9270
.notice(translation -> translation.item().giveReceived())
@@ -97,68 +75,8 @@ void execute(@Context Viewer viewer, @Arg Material material, @Arg(GiveArgument.K
9775
.placeholder("{ITEM}", formattedMaterial)
9876
.placeholder("{PLAYER}", target.getName())
9977
.notice(translation -> translation.item().giveGiven())
100-
.viewer(viewer)
101-
.send();
102-
}
103-
104-
@Execute
105-
@DescriptionDocs(description = "Gives an item with a custom amount to another player", arguments = "<item> <amount> <enchantment> <level> <player>")
106-
void execute(@Context Viewer viewer, @Arg Material material, @Arg(GiveArgument.KEY) int amount, @Arg Enchantment enchantment, @Arg(EnchantArgument.KEY) int level, @Arg Player target) {
107-
String formattedMaterial = MaterialUtil.format(material);
108-
109-
this.giveItem(target, material, amount, enchantment, level);
110-
111-
this.noticeService.create()
112-
.placeholder("{ITEM}", formattedMaterial)
113-
.placeholder("{ENCHANTMENT}", enchantment.getKey().getKey())
114-
.placeholder("{ENCHANTMENT_LEVEL}", String.valueOf(level))
115-
.notice(translation -> translation.item().giveReceivedEnchantment())
116-
.player(target.getUniqueId())
117-
.send();
118-
119-
this.noticeService.create()
120-
.placeholder("{ITEM}", formattedMaterial)
121-
.placeholder("{PLAYER}", target.getName())
122-
.placeholder("{ENCHANTMENT}", enchantment.getKey().getKey())
123-
.placeholder("{ENCHANTMENT_LEVEL}", String.valueOf(level))
124-
.notice(translation -> translation.item().giveGivenEnchantment())
125-
.viewer(viewer)
78+
.sender(sender)
12679
.send();
12780
}
12881

129-
private void giveItem(Player player, Material material) {
130-
int amount = this.pluginConfig.items.defaultGiveAmount;
131-
132-
if (!material.isItem()) {
133-
this.noticeService.create()
134-
.notice(translation -> translation.item().giveNotItem())
135-
.player(player.getUniqueId())
136-
.send();
137-
return;
138-
}
139-
140-
ItemStack item = ItemBuilder.from(material)
141-
.amount(amount)
142-
.build();
143-
144-
player.getInventory().addItem(item);
145-
}
146-
147-
private void giveItem(Player player, Material material, int amount) {
148-
ItemStack item = ItemBuilder.from(material)
149-
.amount(amount)
150-
.build();
151-
152-
player.getInventory().addItem(item);
153-
}
154-
155-
private void giveItem(Player player, Material material, int amount, Enchantment enchantment, int level) {
156-
ItemStack item = ItemBuilder.from(material)
157-
.amount(amount)
158-
.enchant(enchantment, level)
159-
.build();
160-
161-
player.getInventory().addItem(item);
162-
}
163-
16482
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package com.eternalcode.core.feature.essentials.item.give;
2+
3+
import com.eternalcode.core.configuration.implementation.PluginConfiguration;
4+
import com.eternalcode.core.injector.annotations.Inject;
5+
import com.eternalcode.core.injector.annotations.component.Service;
6+
import com.eternalcode.core.notice.NoticeService;
7+
import java.util.Optional;
8+
import org.bukkit.Material;
9+
import org.bukkit.command.CommandSender;
10+
import org.bukkit.entity.Player;
11+
import org.bukkit.inventory.ItemStack;
12+
import org.bukkit.inventory.PlayerInventory;
13+
14+
@Service
15+
class GiveService {
16+
17+
private final PluginConfiguration pluginConfiguration;
18+
private final NoticeService noticeService;
19+
20+
@Inject
21+
public GiveService(PluginConfiguration pluginConfiguration, NoticeService noticeService) {
22+
this.pluginConfiguration = pluginConfiguration;
23+
this.noticeService = noticeService;
24+
}
25+
26+
public boolean giveItem(CommandSender sender, Player player, Material material, int amount) {
27+
if (this.isInvalidMaterial(material)) {
28+
this.noticeService.create()
29+
.notice(translation -> translation.item().giveNotItem())
30+
.sender(sender)
31+
.send();
32+
33+
return false;
34+
}
35+
36+
PlayerInventory inventory = player.getInventory();
37+
GiveResult giveResult = this.processGive(new PlayerContents(inventory.getStorageContents(), inventory.getItemInOffHand()), new ItemStack(material, amount));
38+
Optional<ItemStack> rest = giveResult.rest();
39+
40+
if (rest.isPresent() && !this.pluginConfiguration.items.dropOnFullInventory) {
41+
this.noticeService.create()
42+
.notice(translation -> translation.item().giveNoSpace())
43+
.sender(sender)
44+
.send();
45+
return false;
46+
}
47+
48+
inventory.setStorageContents(giveResult.contents().storage);
49+
inventory.setItemInOffHand(giveResult.contents().extraSlot);
50+
51+
if (rest.isPresent()) {
52+
player.getWorld().dropItemNaturally(player.getLocation(), rest.get());
53+
}
54+
55+
return true;
56+
}
57+
58+
private boolean isInvalidMaterial(Material material) {
59+
return !material.isItem();
60+
}
61+
62+
private GiveResult processGive(PlayerContents contents, ItemStack itemToGive) {
63+
for (int i = 0; i < contents.size(); i++) {
64+
if (itemToGive.getAmount() < 0) {
65+
throw new IllegalArgumentException("Item amount cannot be negative");
66+
}
67+
68+
if (itemToGive.getAmount() == 0) {
69+
return new GiveResult(contents, Optional.empty());
70+
}
71+
72+
ItemStack content = contents.get(i);
73+
74+
if (content == null || content.getType().isAir()) {
75+
contents.set(i, processContentSlot(itemToGive, 0, itemToGive.clone()));
76+
continue;
77+
}
78+
79+
if (!content.isSimilar(itemToGive)) {
80+
continue;
81+
}
82+
83+
contents.set(i, processContentSlot(itemToGive, content.getAmount(), content.clone()));
84+
}
85+
86+
if (itemToGive.getAmount() > 0) {
87+
return new GiveResult(contents, Optional.of(itemToGive));
88+
}
89+
90+
return new GiveResult(contents, Optional.empty());
91+
}
92+
93+
private static ItemStack processContentSlot(ItemStack itemToGive, int amount, ItemStack cloned) {
94+
int amountToConsume = Math.min(itemToGive.getAmount(), itemToGive.getMaxStackSize() - amount);
95+
96+
cloned.setAmount(amount + amountToConsume);
97+
itemToGive.setAmount(itemToGive.getAmount() - amountToConsume);
98+
99+
return cloned;
100+
}
101+
102+
private record GiveResult(PlayerContents contents, Optional<ItemStack> rest) {}
103+
104+
private static class PlayerContents {
105+
private final ItemStack[] storage;
106+
private ItemStack extraSlot;
107+
108+
private PlayerContents(ItemStack[] storage, ItemStack extraSlot) {
109+
this.storage = storage;
110+
this.extraSlot = extraSlot;
111+
}
112+
113+
int size() {
114+
return this.storage.length + 1;
115+
}
116+
117+
ItemStack get(int index) {
118+
if (index == this.size() - 1) {
119+
return this.extraSlot;
120+
}
121+
122+
return this.storage[index];
123+
}
124+
125+
void set(int index, ItemStack item) {
126+
if (index == this.size() - 1) {
127+
this.extraSlot = item;
128+
return;
129+
}
130+
131+
this.storage[index] = item;
132+
}
133+
}
134+
135+
}

0 commit comments

Comments
 (0)