Skip to content

Commit e476d6a

Browse files
committed
Optimized VirtualInventory and SpawnLoot
1 parent f7fc204 commit e476d6a

File tree

5 files changed

+138
-45
lines changed

5 files changed

+138
-45
lines changed

core/src/main/java/github/nighter/smartspawner/language/LanguageManager.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ public enum MessageType {
181181
put("messages.spawner-protected.sound", "block.note_block.pling");
182182

183183
// Selling Items from Spawner
184-
put("messages.sell-all.message", "&#d6e7edYou sold a total of &#3287A9%amount% items&#d6e7ed for&a %price%$ &#d6e7ed!");
184+
put("messages.sell-all.message", "&#d6e7edYou sold a total of &#3287A9%amount% items&#d6e7ed for&a $%price% &#d6e7ed!");
185185
put("messages.sell-all.prefix", true);
186186
put("messages.sell-all.type", "CHAT");
187187
put("messages.sell-all.sound", "block.note_block.bell");
@@ -191,7 +191,7 @@ public enum MessageType {
191191
put("messages.no-items.type", "CHAT");
192192
put("messages.no-items.sound", "block.note_block.pling");
193193

194-
put("messages.sell-all-tax.message", "&#d6e7edYou have sold &#3287A9%amount% items&#d6e7ed. Original price: &a%gross%$&#d6e7ed, After tax (&#ff6b6b-%tax%%&#d6e7ed): &a%price%$");
194+
put("messages.sell-all-tax.message", "&#d6e7edYou have sold &#3287A9%amount% items&#d6e7ed. Original price: &a$%gross%&#d6e7ed, After tax (&#ff6b6b-%tax%%&#d6e7ed): &a$%price%");
195195
put("messages.sell-all-tax.prefix", true);
196196
put("messages.sell-all-tax.type", "CHAT");
197197
put("messages.sell-all-tax.sound", "block.note_block.bell");

core/src/main/java/github/nighter/smartspawner/spawner/lootgen/SpawnerLootGenerator.java

+60-25
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ public void loadConfigurations() {
234234
}
235235
}
236236

237+
// Modify the generateLoot method:
237238
public LootResult generateLoot(EntityType entityType, int minMobs, int maxMobs, SpawnerData spawner) {
238239
String entityName = entityType.name().toLowerCase();
239240
EntityLootConfig config = entityLootConfigs.get(entityName);
@@ -243,38 +244,70 @@ public LootResult generateLoot(EntityType entityType, int minMobs, int maxMobs,
243244
}
244245

245246
int mobCount = random.nextInt(maxMobs - minMobs + 1) + minMobs;
246-
List<ItemStack> totalLoot = new ArrayList<>();
247247
int totalExperience = config.experience * mobCount;
248-
249248
boolean allowEquipment = spawner.isAllowEquipmentItems();
250249

251-
// Pre-filter items based on equipment permission
252-
List<LootItem> validItems = config.possibleItems.stream()
253-
.filter(item -> allowEquipment ||
254-
(item.minDurability == null && item.maxDurability == null))
255-
.collect(Collectors.toList());
250+
// Pre-filter items only once
251+
List<LootItem> validItems = allowEquipment ? config.possibleItems :
252+
config.possibleItems.stream()
253+
.filter(item -> item.minDurability == null && item.maxDurability == null)
254+
.collect(Collectors.toList());
256255

257256
if (validItems.isEmpty()) {
258257
return new LootResult(Collections.emptyList(), totalExperience);
259258
}
260259

261-
// Process each mob individually for accurate drop rates
262-
for (int i = 0; i < mobCount; i++) {
263-
for (LootItem lootItem : validItems) {
260+
// Use a Map to consolidate identical drops instead of List
261+
Map<ItemStack, Integer> consolidatedLoot = new HashMap<>();
262+
263+
// Process mobs in batch rather than individually
264+
for (LootItem lootItem : validItems) {
265+
// Calculate the probability for the entire mob batch at once
266+
int successfulDrops = 0;
267+
268+
// Calculate binomial distribution - how many mobs will drop this item
269+
for (int i = 0; i < mobCount; i++) {
264270
if (random.nextDouble() * 100 <= lootItem.chance) {
265-
int amount = random.nextInt(lootItem.maxAmount - lootItem.minAmount + 1) + lootItem.minAmount;
266-
if (amount > 0) {
267-
ItemStack item = lootItem.createItemStack(random, effectNameCache, romanNumeralCache);
268-
if (item != null) {
269-
item.setAmount(amount);
270-
totalLoot.add(item);
271-
}
271+
successfulDrops++;
272+
}
273+
}
274+
275+
if (successfulDrops > 0) {
276+
// Create item just once per loot type
277+
ItemStack prototype = lootItem.createItemStack(random, effectNameCache, romanNumeralCache);
278+
if (prototype != null) {
279+
// Total amount across all mobs
280+
int totalAmount = 0;
281+
for (int i = 0; i < successfulDrops; i++) {
282+
totalAmount += random.nextInt(lootItem.maxAmount - lootItem.minAmount + 1) + lootItem.minAmount;
283+
}
284+
285+
if (totalAmount > 0) {
286+
// Add to consolidated map
287+
consolidatedLoot.merge(prototype, totalAmount, Integer::sum);
272288
}
273289
}
274290
}
275291
}
276292

277-
return new LootResult(totalLoot, totalExperience);
293+
// Convert consolidated map to item stacks
294+
List<ItemStack> finalLoot = new ArrayList<>(consolidatedLoot.size());
295+
for (Map.Entry<ItemStack, Integer> entry : consolidatedLoot.entrySet()) {
296+
ItemStack item = entry.getKey().clone();
297+
item.setAmount(Math.min(entry.getValue(), item.getMaxStackSize()));
298+
finalLoot.add(item);
299+
300+
// Handle amounts exceeding max stack size
301+
int remaining = entry.getValue() - item.getMaxStackSize();
302+
while (remaining > 0) {
303+
ItemStack extraStack = item.clone();
304+
extraStack.setAmount(Math.min(remaining, item.getMaxStackSize()));
305+
finalLoot.add(extraStack);
306+
remaining -= extraStack.getAmount();
307+
}
308+
}
309+
310+
return new LootResult(finalLoot, totalExperience);
278311
}
279312

280313
public void spawnLootToSpawner(SpawnerData spawner) {
@@ -484,13 +517,15 @@ private List<ItemStack> limitItemsToAvailableSlots(List<ItemStack> items, Spawne
484517
}
485518

486519
private int calculateSlots(Map<VirtualInventory.ItemSignature, Long> items) {
487-
int slots = 0;
488-
for (Map.Entry<VirtualInventory.ItemSignature, Long> entry : items.entrySet()) {
489-
long amount = entry.getValue();
490-
int maxStackSize = entry.getKey().getTemplateRef().getMaxStackSize();
491-
slots += (int) Math.ceil((double) amount / maxStackSize);
492-
}
493-
return slots;
520+
// Use a more efficient calculation approach
521+
return items.entrySet().stream()
522+
.mapToInt(entry -> {
523+
long amount = entry.getValue();
524+
int maxStackSize = entry.getKey().getTemplateRef().getMaxStackSize();
525+
// Use integer division with ceiling function
526+
return (int) ((amount + maxStackSize - 1) / maxStackSize);
527+
})
528+
.sum();
494529
}
495530

496531
private int calculateRequiredSlots(List<ItemStack> items, VirtualInventory inventory) {

core/src/main/java/github/nighter/smartspawner/spawner/properties/VirtualInventory.java

+72-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package github.nighter.smartspawner.spawner.properties;
22

33
import org.bukkit.inventory.ItemStack;
4+
import org.bukkit.inventory.meta.ItemMeta;
5+
46
import java.util.*;
57
import java.util.concurrent.ConcurrentHashMap;
68

@@ -19,6 +21,16 @@ public class VirtualInventory {
1921
private static final Comparator<Map.Entry<ItemSignature, Long>> ITEM_COMPARATOR =
2022
Comparator.comparing(e -> e.getKey().getTemplateRef().getType().name());
2123

24+
// Add an LRU cache for expensive item operations
25+
private static final int ITEM_CACHE_SIZE = 128;
26+
private static final Map<ItemStack, ItemSignature> signatureCache =
27+
Collections.synchronizedMap(new LinkedHashMap<ItemStack, ItemSignature>(ITEM_CACHE_SIZE, 0.75f, true) {
28+
@Override
29+
protected boolean removeEldestEntry(Map.Entry<ItemStack, ItemSignature> eldest) {
30+
return size() > ITEM_CACHE_SIZE;
31+
}
32+
});
33+
2234
public VirtualInventory(int maxSlots) {
2335
this.maxSlots = maxSlots;
2436
this.consolidatedItems = new ConcurrentHashMap<>();
@@ -43,19 +55,49 @@ public ItemSignature(ItemStack item) {
4355
this.hashCode = calculateHashCode();
4456
}
4557

58+
// Replace the current calculateHashCode() method with:
4659
private int calculateHashCode() {
47-
return Objects.hash(
48-
template.getType(),
49-
template.getDurability(),
50-
template.getItemMeta() != null ? template.getItemMeta().toString() : null
51-
);
60+
// Use a faster hash algorithm and cache more item properties
61+
int result = 31 * template.getType().ordinal(); // Using ordinal() instead of name() hashing
62+
result = 31 * result + (int)template.getDurability();
63+
64+
// Only access ItemMeta when needed
65+
if (template.hasItemMeta()) {
66+
ItemMeta meta = template.getItemMeta();
67+
// Extract only the essential meta properties that determine similarity
68+
result = 31 * result + (meta.hasDisplayName() ? meta.getDisplayName().hashCode() : 0);
69+
result = 31 * result + (meta.hasLore() ? meta.getLore().hashCode() : 0);
70+
result = 31 * result + (meta.hasEnchants() ? meta.getEnchants().hashCode() : 0);
71+
}
72+
return result;
5273
}
5374

5475
@Override
5576
public boolean equals(Object o) {
5677
if (this == o) return true;
5778
if (!(o instanceof ItemSignature)) return false;
5879
ItemSignature that = (ItemSignature) o;
80+
81+
// First compare cheap properties
82+
if (template.getType() != that.template.getType() ||
83+
template.getDurability() != that.template.getDurability()) {
84+
return false;
85+
}
86+
87+
// Only check ItemMeta if types match
88+
boolean thisHasMeta = template.hasItemMeta();
89+
boolean thatHasMeta = that.template.hasItemMeta();
90+
91+
if (thisHasMeta != thatHasMeta) {
92+
return false;
93+
}
94+
95+
// If both have no meta, they're similar enough
96+
if (!thisHasMeta) {
97+
return true;
98+
}
99+
100+
// For complex items, fall back to isSimilar but only as a last resort
59101
return template.isSimilar(that.template);
60102
}
61103

@@ -79,27 +121,43 @@ public String getMaterialName() {
79121
}
80122
}
81123

124+
public static ItemSignature getSignature(ItemStack item) {
125+
// First try to get from cache
126+
ItemSignature cachedSig = signatureCache.get(item);
127+
if (cachedSig != null) {
128+
return cachedSig;
129+
}
130+
131+
// Create new signature and cache it
132+
ItemSignature newSig = new ItemSignature(item);
133+
signatureCache.put(item.clone(), newSig);
134+
return newSig;
135+
}
136+
82137
// Add items in bulk with minimal operations
83138
public void addItems(List<ItemStack> items) {
84139
if (items.isEmpty()) return;
85140

86-
// Process items in a single batch
87-
boolean updated = false;
141+
// Pre-allocate space for batch processing
142+
Map<ItemSignature, Long> itemBatch = new HashMap<>(items.size());
143+
144+
// Consolidate all items first
88145
for (ItemStack item : items) {
89146
if (item == null || item.getAmount() <= 0) continue;
90-
91-
ItemSignature sig = new ItemSignature(item);
92-
consolidatedItems.merge(sig, (long) item.getAmount(), Long::sum);
93-
updated = true;
147+
ItemSignature sig = getSignature(item); // Use cached signature
148+
itemBatch.merge(sig, (long) item.getAmount(), Long::sum);
94149
}
95150

96-
if (updated) {
151+
// Apply all changes in one operation
152+
if (!itemBatch.isEmpty()) {
153+
for (Map.Entry<ItemSignature, Long> entry : itemBatch.entrySet()) {
154+
consolidatedItems.merge(entry.getKey(), entry.getValue(), Long::sum);
155+
}
97156
displayCacheDirty = true;
98157
metricsCacheDirty = true;
99-
sortedEntriesCache = null; // Invalidate sorted entries cache
158+
sortedEntriesCache = null;
100159
}
101160
}
102-
103161
// Remove items in bulk with minimal operations
104162
public boolean removeItems(List<ItemStack> items) {
105163
if (items.isEmpty()) return true;

core/src/main/resources/messages/en_US.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ messages:
173173
# Selling Items from Spawner
174174
#---------------------------------------------------
175175
sell-all:
176-
message: "&#d6e7edYou sold a total of &#3287A9%amount% items&#d6e7ed for&a %price%$ &#d6e7ed!"
176+
message: "&#d6e7edYou sold a total of &#3287A9%amount% items&#d6e7ed for&a $%price% &#d6e7ed!"
177177
prefix: true
178178
type: CHAT
179179
sound: block.note_block.bell
@@ -185,7 +185,7 @@ messages:
185185
sound: block.note_block.pling
186186

187187
sell-all-tax:
188-
message: "&#d6e7edYou have sold &#3287A9%amount% items&#d6e7ed. Original price: &a%gross%$&#d6e7ed, After tax (&#ff6b6b%tax%%&#d6e7ed): &a%price%$"
188+
message: "&#d6e7edYou have sold &#3287A9%amount% items&#d6e7ed. Original price: &a$%gross%&#d6e7ed, After tax (&#ff6b6b%tax%%&#d6e7ed): &a$%price%"
189189
prefix: true
190190
type: CHAT
191191
sound: block.note_block.bell

core/src/main/resources/messages/vi_VN.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,13 @@ messages:
173173
# Bán vật phẩm từ Spawner
174174
#---------------------------------------------------
175175
sell-all:
176-
message: "&#d6e7edBạn đã bán tổng cộng &#3287A9%amount% vật phẩm&#d6e7ed với giá &a%price%$&#d6e7ed!"
176+
message: "&#d6e7edBạn đã bán tổng cộng &#3287A9%amount% vật phẩm&#d6e7ed với giá &a$%price%&#d6e7ed!"
177177
prefix: true
178178
type: CHAT
179179
sound: block.note_block.bell
180180

181181
sell-all-tax:
182-
message: "&#d6e7edBạn đã bán &#3287A9%amount% vật phẩm&#d6e7ed. Giá gốc: &a%gross%$&#d6e7ed, Sau thuế (&#ff6b6b-%tax%%&#d6e7ed): &a%price%$"
182+
message: "&#d6e7edBạn đã bán &#3287A9%amount% vật phẩm&#d6e7ed. Giá gốc: &a$%gross%&#d6e7ed, Sau thuế (&#ff6b6b-%tax%%&#d6e7ed): &a$%price%"
183183
prefix: true
184184
type: CHAT
185185
sound: block.note_block.bell

0 commit comments

Comments
 (0)