From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> Date: Sat, 1 Feb 2025 16:47:09 +0300 Subject: [PATCH] Clump experience orbs diff --git a/net/minecraft/world/entity/ExperienceOrb.java b/net/minecraft/world/entity/ExperienceOrb.java index ff4648b3be103446ab401d36c14cc80b48840cab..18ccbb1344875dd7a2f2f8084102d341b2a9597e 100644 --- a/net/minecraft/world/entity/ExperienceOrb.java +++ b/net/minecraft/world/entity/ExperienceOrb.java @@ -47,6 +47,10 @@ public class ExperienceOrb extends Entity { @Nullable public java.util.UUID triggerEntityId; public org.bukkit.entity.ExperienceOrb.SpawnReason spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN; + // DivineMC start - Clump experience orbs + public java.util.Map clumps$clumpedMap; + public Optional clumps$currentEntry; + // DivineMC end - Clump experience orbs private void loadPaperNBT(CompoundTag tag) { CompoundTag expData = tag.getCompoundOrEmpty("Paper.ExpData"); @@ -245,6 +249,28 @@ public class ExperienceOrb extends Entity { } private static boolean tryMergeToExisting(ServerLevel level, Vec3 pos, int amount) { + // DivineMC start - Clump experience orbs + if (org.bxteam.divinemc.DivineConfig.clumpOrbs) { + AABB aABB = AABB.ofSize(pos, 1.0D, 1.0D, 1.0D); + int id = level.getRandom().nextInt(40); + List list = level.getEntities(EntityTypeTest.forClass(ExperienceOrb.class), aABB, (experienceOrbx) -> canMerge(experienceOrbx, id, amount)); + if (!list.isEmpty()) { + ExperienceOrb experienceOrb = list.getFirst(); + java.util.Map clumpedMap = (experienceOrb).clumps$getClumpedMap(); + (experienceOrb).clumps$setClumpedMap(java.util.stream.Stream.of(clumpedMap, java.util.Collections.singletonMap(amount, 1)) + .flatMap(map -> map.entrySet().stream()) + .collect(java.util.stream.Collectors.toMap(java.util.Map.Entry::getKey, java.util.Map.Entry::getValue, Integer::sum))); + (experienceOrb).count = (clumpedMap.values() + .stream() + .reduce(Integer::sum) + .orElse(1)); + (experienceOrb).age = (0); + return true; + } else { + return false; + } + } + // DivineMC end - Clump experience orbs // Paper - TODO some other event for this kind of merge AABB aabb = AABB.ofSize(pos, 1.0, 1.0, 1.0); int randomInt = level.getRandom().nextInt(io.papermc.paper.configuration.GlobalConfiguration.get().misc.xpOrbGroupsPerArea.or(ORB_GROUPS_PER_AREA)); // Paper - Configure how many orb groups per area @@ -260,11 +286,11 @@ public class ExperienceOrb extends Entity { } private boolean canMerge(ExperienceOrb orb) { - return orb != this && canMerge(orb, this.getId(), this.getValue()); + return org.bxteam.divinemc.DivineConfig.clumpOrbs ? orb.isAlive() && !this.is(orb) : orb != this && ExperienceOrb.canMerge(orb, this.getId(), this.getValue()); // DivineMC - Clump experience orbs } private static boolean canMerge(ExperienceOrb orb, int amount, int other) { - return !orb.isRemoved() && (orb.getId() - amount) % io.papermc.paper.configuration.GlobalConfiguration.get().misc.xpOrbGroupsPerArea.or(ORB_GROUPS_PER_AREA) == 0 && orb.getValue() == other; // Paper - Configure how many orbs will merge together + return org.bxteam.divinemc.DivineConfig.clumpOrbs ? orb.isAlive() : !orb.isRemoved() && (orb.getId() - amount) % io.papermc.paper.configuration.GlobalConfiguration.get().misc.xpOrbGroupsPerArea.or(ORB_GROUPS_PER_AREA) == 0 && orb.getValue() == other; // Paper - Configure how many orbs will merge together // Canvas - optimize orbs } private void merge(ExperienceOrb orb) { @@ -273,6 +299,18 @@ public class ExperienceOrb extends Entity { return; } // Paper end - call orb merge event + // DivineMC start - Clump experience orbs + if (org.bxteam.divinemc.DivineConfig.clumpOrbs) { + java.util.Map otherMap = (orb).clumps$getClumpedMap(); + this.count = clumps$getClumpedMap().values().stream().reduce(Integer::sum).orElse(1); + this.age = Math.min(this.age, (orb).age); + clumps$setClumpedMap(java.util.stream.Stream.of(clumps$getClumpedMap(), otherMap) + .flatMap(map -> map.entrySet().stream()) + .collect(java.util.stream.Collectors.toMap(java.util.Map.Entry::getKey, java.util.Map.Entry::getValue, Integer::sum))); + orb.discard(); + return; + } + // DivineMC end - Clump experience orbs this.count = this.count + orb.count; this.age = Math.min(this.age, orb.age); orb.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.MERGE); // CraftBukkit - add Bukkit remove cause @@ -314,6 +352,13 @@ public class ExperienceOrb extends Entity { compound.putInt("Value", this.getValue()); // Paper - save as Integer compound.putInt("Count", this.count); this.savePaperNBT(compound); // Paper + // DivineMC start - Clump experience orbs + if (clumps$clumpedMap != null) { + CompoundTag map = new CompoundTag(); + clumps$getClumpedMap().forEach((value, count) -> map.putInt(String.valueOf(value), count)); + compound.put("clumpedMap", map); + } + // DivineMC end - Clump experience orbs } @Override @@ -323,10 +368,53 @@ public class ExperienceOrb extends Entity { this.setValue(compound.getIntOr("Value", 0)); // Paper - load as Integer this.count = compound.read("Count", ExtraCodecs.POSITIVE_INT).orElse(1); this.loadPaperNBT(compound); // Paper + // DivineMC start - Clump experience orbs + java.util.Map map = new java.util.HashMap<>(); + if (compound.contains("clumpedMap")) { + CompoundTag clumpedMap = compound.getCompound("clumpedMap").orElseThrow(); + for (String s : clumpedMap.keySet()) { + map.put(Integer.parseInt(s), clumpedMap.getInt(s).orElseThrow()); + } + } else { + map.put(this.getValue(), count); + } + + clumps$setClumpedMap(map); + // DivineMC end - Clump experience orbs } @Override public void playerTouch(Player entity) { + // DivineMC start - Clump experience orbs + if (entity instanceof ServerPlayer serverPlayer && org.bxteam.divinemc.DivineConfig.clumpOrbs && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { + entity.takeXpDelay = 0; + entity.take(this, 1); + + if (this.getValue() != 0 || clumps$resolve()) { + java.util.concurrent.atomic.AtomicInteger toGive = new java.util.concurrent.atomic.AtomicInteger(); + clumps$getClumpedMap().forEach((value, amount) -> { + int actualValue = value; + for (int i = 0; i < amount; i++) { + int leftOver = actualValue; + if (leftOver == actualValue) { + leftOver = this.repairPlayerItems((ServerPlayer) entity, actualValue); + } + if (leftOver > 0) { + toGive.addAndGet(leftOver); + } + } + }); + if (toGive.get() > 0) { + entity.giveExperiencePoints(toGive.get()); + } + } + + this.count = 0; + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.PICKUP); + + return; + } + // Canvas end if (entity instanceof ServerPlayer serverPlayer) { if (entity.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent entity.takeXpDelay = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerXpCooldownEvent(entity, this.level().purpurConfig.playerExpPickupDelay, org.bukkit.event.player.PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; // Purpur - Configurable player pickup exp delay @@ -344,10 +432,60 @@ public class ExperienceOrb extends Entity { } } + // DivineMC start - Clump experience orbs + public Optional clumps$captureCurrentEntry(Optional entry) { + clumps$currentEntry = entry; + return entry; + } + + public java.util.Map clumps$getClumpedMap() { + if (clumps$clumpedMap == null) { + clumps$clumpedMap = new java.util.HashMap<>(); + clumps$clumpedMap.put(this.getValue(), 1); + } + + return clumps$clumpedMap; + } + + public void clumps$setClumpedMap(java.util.Map map) { + clumps$clumpedMap = map; + clumps$resolve(); + } + + public boolean clumps$resolve() { + this.setValue(clumps$getClumpedMap().entrySet() + .stream() + .map(entry -> entry.getKey() * entry.getValue()) + .reduce(Integer::sum) + .orElse(1)); + + return this.getValue() > 0; + } + // DivineMC end - Clump experience orbs + private int repairPlayerItems(ServerPlayer player, int value) { - Optional randomItemWith = level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player) : EnchantmentHelper.getRandomItemWith( // Purpur - Add option to mend the most damaged equipment first - EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged - ); + Optional randomItemWith = clumps$captureCurrentEntry(level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player) : EnchantmentHelper.getRandomItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged)); // Purpur - Add option to mend the most damaged equipment first // DivineMC - Clump experience orbs + + // DivineMC start - Clump experience orbs + if (org.bxteam.divinemc.DivineConfig.clumpOrbs) { + return clumps$currentEntry + .map(foundItem -> { + ItemStack itemstack = foundItem.itemStack(); + int xpToRepair = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.serverLevel(), itemstack, (int) (value * 1)); + int toRepair = Math.min(xpToRepair, itemstack.getDamageValue()); + itemstack.setDamageValue(itemstack.getDamageValue() - toRepair); + if (toRepair > 0) { + int used = value - toRepair * value / xpToRepair; + if (used > 0) { + return this.repairPlayerItems(player, used); + } + } + return 0; + }) + .orElse(value); + } + // DivineMC end - Clump experience orbs + if (randomItemWith.isPresent()) { ItemStack itemStack = randomItemWith.get().itemStack(); int i = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.serverLevel(), itemStack, value);