Memory leak fixes, optimizations, and persistence

This commit is contained in:
nossr50 2020-10-08 15:25:40 -07:00
parent 0767e62965
commit 20c69b63af
14 changed files with 546 additions and 224 deletions

View File

@ -1,9 +1,30 @@
Version 2.1.148 Version 2.1.148
Fixed a bug where weakness potions could prevent unarmed skills from activating and thus making unarmed useless Fixed a memory leak involving entity metadata
Alchemy progression is now more reasonable (delete skillranks.yml or edit it yourself to receive the change) Alchemy progression is now more reasonable (delete skillranks.yml or edit it yourself to receive the change)
Made some optimizations to combat processing
New experience multiplier labeled 'Eggs' in experience.yml with a default value of 0 (previously mobs from eggs were using the Mobspawner experience multiplier)
New experience multiplier labeled 'Nether_Portal' in experience.yml with a default value of 0
Fixed a bug where mobs from eggs were only tracked if it was dispensed (egg tracking now tracks from egg items as well)
Fixed a bug where egg spawned mobs were sometimes not marked as being from an egg (used in experience multipliers)
Fixed a bug where entities transformed by a single event (such as lightning) weren't tracked properly if there was more than one entity involved
mmodebug now prints out some information about final damage when attacking an entity with certain weapons
(1.14+ required)
Mobs spawned from mob spawners are tracked persistently and are no longer forgotten about after a restart
Tamed mobs are tracked persistently and are no longer forgotten about after a restart
Egg spawned mobs are tracked persistently and are no longer forgotten about after a restart
Nether Portal spawned mobs are tracked persistently and are no longer forgotten about after a restart
Endermen who target endermite are tracked persistently and are no longer forgotten about after a restart
COTW spawned mobs are tracked persistently and are no longer forgotten about after a restart
NOTES: NOTES:
Egg mobs & Nether portal pigs being assigned to the mobspawner xp multiplier didn't make sense to me, so it has been changed. They have their own XP multipliers now.
While working on making data persistent I stumbled upon some alarming memory leak candidates, one of them was 7 years old. Sigh.
Alchemy now progresses much smoother, with rank 2 no longer unlocking right away. Thanks to Momshroom for pointing out this oddity. Alchemy now progresses much smoother, with rank 2 no longer unlocking right away. Thanks to Momshroom for pointing out this oddity.
There's no persistent API for entities in Spigot for 1.13.2, but in the future I'll wire up NMS and write it to NBT myself.
This means the new persistence stuff requires 1.14.0 or higher, if you're still on 1.13.2 for now that stuff will behave like it always did
Version 2.1.147 Version 2.1.147
Fixed a bug where players below the level threshold on a hardcore mode enabled server would gain levels on death in certain circumstances Fixed a bug where players below the level threshold on a hardcore mode enabled server would gain levels on death in certain circumstances

View File

@ -221,7 +221,7 @@
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.16.2-R0.1-SNAPSHOT</version> <version>1.14-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -0,0 +1,7 @@
package com.gmail.nossr50.api.exceptions;
public class IncompleteNamespacedKeyRegister extends RuntimeException {
public IncompleteNamespacedKeyRegister(String message) {
super(message);
}
}

View File

@ -175,6 +175,8 @@ public class ExperienceConfig extends AutoUpdateConfigLoader {
/* Spawned Mob modifier */ /* Spawned Mob modifier */
public double getSpawnedMobXpMultiplier() { return config.getDouble("Experience_Formula.Mobspawners.Multiplier", 0.0); } public double getSpawnedMobXpMultiplier() { return config.getDouble("Experience_Formula.Mobspawners.Multiplier", 0.0); }
public double getEggXpMultiplier() { return config.getDouble("Experience_Formula.Eggs.Multiplier", 0.0); }
public double getNetherPortalXpMultiplier() { return config.getDouble("Experience_Formula.Nether_Portal.Multiplier", 0.0); }
public double getBredMobXpMultiplier() { return config.getDouble("Experience_Formula.Breeding.Multiplier", 1.0); } public double getBredMobXpMultiplier() { return config.getDouble("Experience_Formula.Breeding.Multiplier", 1.0); }
/* Skill modifiers */ /* Skill modifiers */

View File

@ -5,7 +5,6 @@ import com.gmail.nossr50.config.Config;
import com.gmail.nossr50.config.WorldBlacklist; import com.gmail.nossr50.config.WorldBlacklist;
import com.gmail.nossr50.config.experience.ExperienceConfig; import com.gmail.nossr50.config.experience.ExperienceConfig;
import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.player.McMMOPlayer;
import com.gmail.nossr50.datatypes.player.PlayerProfile;
import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.datatypes.skills.SubSkillType;
import com.gmail.nossr50.datatypes.skills.subskills.interfaces.InteractType; import com.gmail.nossr50.datatypes.skills.subskills.interfaces.InteractType;
import com.gmail.nossr50.events.fake.FakeEntityDamageByEntityEvent; import com.gmail.nossr50.events.fake.FakeEntityDamageByEntityEvent;
@ -22,6 +21,8 @@ import com.gmail.nossr50.skills.unarmed.UnarmedManager;
import com.gmail.nossr50.util.BlockUtils; import com.gmail.nossr50.util.BlockUtils;
import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Misc;
import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.Permissions;
import com.gmail.nossr50.util.compat.layers.persistentdata.AbstractPersistentDataLayer;
import com.gmail.nossr50.util.compat.layers.persistentdata.MobMetaFlagType;
import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.player.NotificationManager;
import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.player.UserManager;
import com.gmail.nossr50.util.random.RandomChanceUtil; import com.gmail.nossr50.util.random.RandomChanceUtil;
@ -46,30 +47,36 @@ import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import org.bukkit.projectiles.ProjectileSource; import org.bukkit.projectiles.ProjectileSource;
import org.jetbrains.annotations.NotNull;
public class EntityListener implements Listener { public class EntityListener implements Listener {
private final mcMMO plugin; private final mcMMO pluginRef;
private final @NotNull AbstractPersistentDataLayer persistentDataLayer;
public EntityListener(final mcMMO plugin) { public EntityListener(final mcMMO pluginRef) {
this.plugin = plugin; this.pluginRef = pluginRef;
persistentDataLayer = mcMMO.getCompatibilityManager().getPersistentDataLayer();
} }
@EventHandler(priority = EventPriority.MONITOR) @EventHandler(priority = EventPriority.MONITOR)
public void onEntityTransform(EntityTransformEvent event) public void onEntityTransform(EntityTransformEvent event) {
{ if(event.getEntity() instanceof LivingEntity) {
//Transfer metadata keys from mob-spawned mobs to new mobs LivingEntity livingEntity = (LivingEntity) event.getEntity();
if(event.getEntity().getMetadata(mcMMO.entityMetadataKey) != null || event.getEntity().getMetadata(mcMMO.entityMetadataKey).size() >= 1)
{ //Transfer metadata keys from mob-spawned mobs to new mobs
for(Entity entity : event.getTransformedEntities()) if(persistentDataLayer.hasMobFlags(livingEntity)) {
{ for(Entity entity : event.getTransformedEntities()) {
entity.setMetadata(mcMMO.entityMetadataKey, mcMMO.metadataValue); if(entity instanceof LivingEntity) {
LivingEntity transformedEntity = (LivingEntity) entity;
persistentDataLayer.addMobFlags(livingEntity, transformedEntity);
}
}
} }
} }
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityTargetEntity(EntityTargetLivingEntityEvent event) public void onEntityTargetEntity(EntityTargetLivingEntityEvent event) {
{
if(!ExperienceConfig.getInstance().isEndermanEndermiteFarmingPrevented()) if(!ExperienceConfig.getInstance().isEndermanEndermiteFarmingPrevented())
return; return;
@ -82,8 +89,13 @@ public class EntityListener implements Listener {
//Prevent entities from giving XP if they target endermite //Prevent entities from giving XP if they target endermite
if(event.getTarget() instanceof Endermite) if(event.getTarget() instanceof Endermite)
{ {
if(!event.getEntity().hasMetadata(mcMMO.entityMetadataKey)) if(event.getEntity() instanceof Enderman) {
event.getEntity().setMetadata(mcMMO.entityMetadataKey, mcMMO.metadataValue); Enderman enderman = (Enderman) event.getEntity();
if(!persistentDataLayer.hasMobFlag(MobMetaFlagType.EXPLOITED_ENDERMEN, enderman)) {
persistentDataLayer.flagMetadata(MobMetaFlagType.EXPLOITED_ENDERMEN, enderman);
}
}
} }
} }
@ -119,8 +131,8 @@ public class EntityListener implements Listener {
projectile.setMetadata(mcMMO.infiniteArrowKey, mcMMO.metadataValue); projectile.setMetadata(mcMMO.infiniteArrowKey, mcMMO.metadataValue);
} }
projectile.setMetadata(mcMMO.bowForceKey, new FixedMetadataValue(plugin, Math.min(event.getForce() * AdvancedConfig.getInstance().getForceMultiplier(), 1.0))); projectile.setMetadata(mcMMO.bowForceKey, new FixedMetadataValue(pluginRef, Math.min(event.getForce() * AdvancedConfig.getInstance().getForceMultiplier(), 1.0)));
projectile.setMetadata(mcMMO.arrowDistanceKey, new FixedMetadataValue(plugin, projectile.getLocation())); projectile.setMetadata(mcMMO.arrowDistanceKey, new FixedMetadataValue(pluginRef, projectile.getLocation()));
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@ -145,8 +157,8 @@ public class EntityListener implements Listener {
if(!(projectile instanceof Arrow)) if(!(projectile instanceof Arrow))
return; return;
projectile.setMetadata(mcMMO.bowForceKey, new FixedMetadataValue(plugin, 1.0)); projectile.setMetadata(mcMMO.bowForceKey, new FixedMetadataValue(pluginRef, 1.0));
projectile.setMetadata(mcMMO.arrowDistanceKey, new FixedMetadataValue(plugin, projectile.getLocation())); projectile.setMetadata(mcMMO.arrowDistanceKey, new FixedMetadataValue(pluginRef, projectile.getLocation()));
for(Enchantment enchantment : player.getInventory().getItemInMainHand().getEnchantments().keySet()) { for(Enchantment enchantment : player.getInventory().getItemInMainHand().getEnchantments().keySet()) {
if(enchantment.getName().equalsIgnoreCase("piercing")) if(enchantment.getName().equalsIgnoreCase("piercing"))
@ -185,74 +197,38 @@ public class EntityListener implements Listener {
Entity entity = event.getEntity(); Entity entity = event.getEntity();
if (entity instanceof FallingBlock || entity instanceof Enderman) { if (entity instanceof FallingBlock || entity instanceof Enderman) {
boolean isTracked = entity.hasMetadata(mcMMO.entityMetadataKey); trackMovingBlocks(block, entity); //ignore the IDE warning
//Apparently redstone ore will throw these events
if (mcMMO.getPlaceStore().isTrue(block) && !isTracked) { } else if ((block.getType() != Material.REDSTONE_ORE)) {
mcMMO.getPlaceStore().setFalse(block);
entity.setMetadata(mcMMO.entityMetadataKey, mcMMO.metadataValue);
}
else if (isTracked) {
mcMMO.getPlaceStore().setTrue(block);
}
} else if ((block.getType() == Material.REDSTONE_ORE)) {
}
else {
if (mcMMO.getPlaceStore().isTrue(block)) { if (mcMMO.getPlaceStore().isTrue(block)) {
mcMMO.getPlaceStore().setFalse(block); mcMMO.getPlaceStore().setFalse(block);
} }
} }
} }
/*@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) /**
public void onEntityDamageDebugLowest(EntityDamageEvent event) * This is a complex hack to track blocks for this event
{ * This event is called when a block starts its movement, or ends its movement
if(event instanceof FakeEntityDamageByEntityEvent) * It can start the movement through physics (falling blocks) or through being picked up (endermen)
return; * Since this event can be cancelled, its even weirder to track this stuff
* @param block this will either be the block that was originally picked up, or the block in its final destination
* @param movementSourceEntity this will either be an Endermen or a Falling Block
*/
private void trackMovingBlocks(@NotNull Block block, @NotNull Entity movementSourceEntity) {
if(event instanceof FakeEntityDamageEvent) //A block that has reached its destination, either being placed by endermen or having finished its fall
return; if(movementSourceEntity.hasMetadata(mcMMO.travelingBlock)) {
mcMMO.getPlaceStore().setTrue(block);
Bukkit.broadcastMessage(ChatColor.DARK_AQUA+"DMG Before Events: " movementSourceEntity.removeMetadata(mcMMO.travelingBlock, pluginRef);
+ChatColor.RESET+event.getDamage()); } else {
//A block that is starting movement (from either Endermen or Falling/Physics)
if(mcMMO.getPlaceStore().isTrue(block)) {
mcMMO.getPlaceStore().setFalse(block);
movementSourceEntity.setMetadata(mcMMO.blockMetadataKey, mcMMO.metadataValue);
}
}
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityDamageDebugMonitor(EntityDamageEvent event)
{
if(event instanceof FakeEntityDamageByEntityEvent)
return;
if(event instanceof FakeEntityDamageEvent)
return;
if(!(event.getEntity() instanceof LivingEntity))
return;
LivingEntity entity = (LivingEntity) event.getEntity();
double rawDamage = event.getDamage();
double dmgAfterReduction = event.getFinalDamage();
Bukkit.broadcastMessage(ChatColor.GOLD+"DMG After Events: "
+ event.getEntity().getName()+ChatColor.RESET
+"RawDMG["+rawDamage+"], "
+"FinalDMG=["+dmgAfterReduction+"]");
Bukkit.broadcastMessage(
event.getEntity().getName()
+ChatColor.GREEN
+" HP "
+ChatColor.RESET
+entity.getHealth()
+ChatColor.YELLOW
+" -> "
+ChatColor.RESET
+(entity.getHealth()-event.getFinalDamage()));
Bukkit.broadcastMessage("");
}*/
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onEntityCombustByEntityEvent(EntityCombustByEntityEvent event) { public void onEntityCombustByEntityEvent(EntityCombustByEntityEvent event) {
//Prevent players from setting fire to each other if they are in the same party //Prevent players from setting fire to each other if they are in the same party
@ -284,6 +260,10 @@ public class EntityListener implements Listener {
*/ */
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onEntityDamageByEntity(EntityDamageByEntityEvent event) { public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
if (event instanceof FakeEntityDamageByEntityEvent) {
return;
}
double damage = event.getFinalDamage(); double damage = event.getFinalDamage();
Entity defender = event.getEntity(); Entity defender = event.getEntity();
Entity attacker = event.getDamager(); Entity attacker = event.getDamager();
@ -292,35 +272,31 @@ public class EntityListener implements Listener {
{ {
if(attacker instanceof Player) { if(attacker instanceof Player) {
if(!WorldGuardManager.getInstance().hasMainFlag((Player) attacker)) if(!WorldGuardManager.getInstance().hasMainFlag((Player) attacker)) {
return; return;
}
} else if(attacker instanceof Projectile) { } else if(attacker instanceof Projectile) {
Projectile projectile = (Projectile) attacker; Projectile projectile = (Projectile) attacker;
if(projectile.getShooter() instanceof Player) { if(projectile.getShooter() instanceof Player) {
if(!WorldGuardManager.getInstance().hasMainFlag((Player) projectile.getShooter())) if(!WorldGuardManager.getInstance().hasMainFlag((Player) projectile.getShooter())) {
return; return;
}
} }
} }
} }
/* WORLD BLACKLIST CHECK */ /* WORLD BLACKLIST CHECK */
if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld())) if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld())) {
return;
if (event instanceof FakeEntityDamageByEntityEvent) {
return; return;
} }
// Don't process this event for marked entities, for players this is handled above, // Don't process this event for marked entities, for players this is handled above,
// However, for entities, we do not wanna cancel this event to allow plugins to observe changes // However, for entities, we do not wanna cancel this event to allow plugins to observe changes
// properly // properly
if (defender.getMetadata(mcMMO.CUSTOM_DAMAGE_METAKEY).size() > 0) {
return;
}
if (CombatUtils.isProcessingNoInvulnDamage()) { if (CombatUtils.isProcessingNoInvulnDamage()) {
return; return;
@ -344,6 +320,10 @@ public class EntityListener implements Listener {
return; return;
} }
if (CombatUtils.hasIgnoreDamageMetadata(target)) {
return;
}
if (attacker instanceof Tameable) { if (attacker instanceof Tameable) {
AnimalTamer animalTamer = ((Tameable) attacker).getOwner(); AnimalTamer animalTamer = ((Tameable) attacker).getOwner();
@ -422,7 +402,7 @@ public class EntityListener implements Listener {
} }
CombatUtils.processCombatAttack(event, attacker, target); CombatUtils.processCombatAttack(event, attacker, target);
CombatUtils.handleHealthbars(attacker, target, event.getFinalDamage(), plugin); CombatUtils.handleHealthbars(attacker, target, event.getFinalDamage(), pluginRef);
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false)
@ -490,7 +470,7 @@ public class EntityListener implements Listener {
* Process Registered Interactions * Process Registered Interactions
*/ */
InteractionManager.processEvent(event, plugin, InteractType.ON_ENTITY_DAMAGE); InteractionManager.processEvent(event, pluginRef, InteractType.ON_ENTITY_DAMAGE);
/* /*
* Old code * Old code
@ -646,29 +626,7 @@ public class EntityListener implements Listener {
*/ */
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.LOWEST)
public void onEntityDeathLowest(EntityDeathEvent event) { public void onEntityDeathLowest(EntityDeathEvent event) {
/* WORLD BLACKLIST CHECK */ mcMMO.getTransientMetadataTools().cleanAllMobMetadata(event.getEntity());
if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld()))
return;
LivingEntity entity = event.getEntity();
if (Misc.isNPCEntityExcludingVillagers(entity)) {
return;
}
if (entity.hasMetadata(mcMMO.customNameKey)) {
entity.setCustomName(entity.getMetadata(mcMMO.customNameKey).get(0).asString());
entity.removeMetadata(mcMMO.customNameKey, plugin);
}
if (entity.hasMetadata(mcMMO.customVisibleKey)) {
entity.setCustomNameVisible(entity.getMetadata(mcMMO.customVisibleKey).get(0).asBoolean());
entity.removeMetadata(mcMMO.customVisibleKey, plugin);
}
if (entity.hasMetadata(mcMMO.entityMetadataKey)) {
entity.removeMetadata(mcMMO.entityMetadataKey, plugin);
}
} }
/** /**
@ -704,33 +662,41 @@ public class EntityListener implements Listener {
if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld())) if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld()))
return; return;
LivingEntity entity = event.getEntity(); LivingEntity livingEntity = event.getEntity();
switch (event.getSpawnReason()) { switch (event.getSpawnReason()) {
case NETHER_PORTAL: case NETHER_PORTAL:
trackSpawnedAndPassengers(livingEntity, MobMetaFlagType.NETHER_PORTAL_MOB);
break;
case SPAWNER: case SPAWNER:
case SPAWNER_EGG: case SPAWNER_EGG:
entity.setMetadata(mcMMO.entityMetadataKey, mcMMO.metadataValue); trackSpawnedAndPassengers(livingEntity, MobMetaFlagType.MOB_SPAWNER_MOB);
break;
Entity passenger = entity.getPassenger(); case DISPENSE_EGG:
case EGG:
if (passenger != null) { trackSpawnedAndPassengers(livingEntity, MobMetaFlagType.EGG_MOB);
passenger.setMetadata(mcMMO.entityMetadataKey, mcMMO.metadataValue); break;
}
return;
case BREEDING: case BREEDING:
entity.setMetadata(mcMMO.bredMetadataKey, mcMMO.metadataValue); trackSpawnedAndPassengers(livingEntity, MobMetaFlagType.PLAYER_BRED_MOB);
return; break;
default: default:
} }
} }
private void trackSpawnedAndPassengers(LivingEntity livingEntity, MobMetaFlagType mobMetaFlagType) {
persistentDataLayer.flagMetadata(mobMetaFlagType, livingEntity);
for(Entity passenger : livingEntity.getPassengers()) {
if(passenger != null) {
persistentDataLayer.flagMetadata(mobMetaFlagType, livingEntity);
}
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onEntityBreed(EntityBreedEvent event) { public void onEntityBreed(EntityBreedEvent event) {
if(ExperienceConfig.getInstance().isCOTWBreedingPrevented()) { if(ExperienceConfig.getInstance().isCOTWBreedingPrevented()) {
if(event.getFather().hasMetadata(mcMMO.COTW_TEMPORARY_SUMMON) || event.getMother().hasMetadata(mcMMO.COTW_TEMPORARY_SUMMON)) { if(persistentDataLayer.hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, event.getFather()) || persistentDataLayer.hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, event.getMother())) {
event.setCancelled(true); event.setCancelled(true);
Animals mom = (Animals) event.getMother(); Animals mom = (Animals) event.getMother();
Animals father = (Animals) event.getFather(); Animals father = (Animals) event.getFather();
@ -745,7 +711,6 @@ public class EntityListener implements Listener {
NotificationManager.sendPlayerInformationChatOnly(player, "Taming.Summon.COTW.BreedingDisallowed"); NotificationManager.sendPlayerInformationChatOnly(player, "Taming.Summon.COTW.BreedingDisallowed");
} }
} }
} }
} }
@ -769,7 +734,7 @@ public class EntityListener implements Listener {
// We can make this assumption because we (should) be the only ones // We can make this assumption because we (should) be the only ones
// using this exact metadata // using this exact metadata
Player player = plugin.getServer().getPlayerExact(entity.getMetadata(mcMMO.tntMetadataKey).get(0).asString()); Player player = pluginRef.getServer().getPlayerExact(entity.getMetadata(mcMMO.tntMetadataKey).get(0).asString());
if (!UserManager.hasPlayerDataKey(player)) { if (!UserManager.hasPlayerDataKey(player)) {
return; return;
@ -815,7 +780,7 @@ public class EntityListener implements Listener {
// We can make this assumption because we (should) be the only ones // We can make this assumption because we (should) be the only ones
// using this exact metadata // using this exact metadata
Player player = plugin.getServer().getPlayerExact(entity.getMetadata(mcMMO.tntMetadataKey).get(0).asString()); Player player = pluginRef.getServer().getPlayerExact(entity.getMetadata(mcMMO.tntMetadataKey).get(0).asString());
if (!UserManager.hasPlayerDataKey(player)) { if (!UserManager.hasPlayerDataKey(player)) {
return; return;
@ -983,13 +948,16 @@ public class EntityListener implements Listener {
return; return;
} }
LivingEntity entity = event.getEntity(); LivingEntity livingEntity = event.getEntity();
if (!UserManager.hasPlayerDataKey(player) || Misc.isNPCEntityExcludingVillagers(entity) || entity.hasMetadata(mcMMO.entityMetadataKey)) { if (!UserManager.hasPlayerDataKey(player)
|| Misc.isNPCEntityExcludingVillagers(livingEntity)
|| persistentDataLayer.hasMobFlag(MobMetaFlagType.EGG_MOB, livingEntity)
|| persistentDataLayer.hasMobFlag(MobMetaFlagType.MOB_SPAWNER_MOB, livingEntity)) {
return; return;
} }
entity.setMetadata(mcMMO.entityMetadataKey, mcMMO.metadataValue); persistentDataLayer.flagMetadata(MobMetaFlagType.PLAYER_TAMED_MOB, livingEntity);
//Profile not loaded //Profile not loaded
if(UserManager.getPlayer(player) == null) if(UserManager.getPlayer(player) == null)
@ -997,7 +965,7 @@ public class EntityListener implements Listener {
return; return;
} }
UserManager.getPlayer(player).getTamingManager().awardTamingXP(entity); UserManager.getPlayer(player).getTamingManager().awardTamingXP(livingEntity);
} }
/** /**
@ -1069,15 +1037,4 @@ public class EntityListener implements Listener {
} }
} }
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPigZapEvent(PigZapEvent event) {
/* WORLD BLACKLIST CHECK */
if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld()))
return;
if (event.getEntity().hasMetadata(mcMMO.entityMetadataKey)) {
event.getPigZombie().setMetadata(mcMMO.entityMetadataKey, mcMMO.metadataValue);
}
}
} }

View File

@ -81,6 +81,7 @@ public class mcMMO extends JavaPlugin {
private static MaterialMapStore materialMapStore; private static MaterialMapStore materialMapStore;
private static PlayerLevelUtils playerLevelUtils; private static PlayerLevelUtils playerLevelUtils;
private static SmeltingTracker smeltingTracker; private static SmeltingTracker smeltingTracker;
private static TransientMetadataTools transientMetadataTools;
/* Adventure */ /* Adventure */
private static BukkitAudiences audiences; private static BukkitAudiences audiences;
@ -120,11 +121,9 @@ public class mcMMO extends JavaPlugin {
public static final String FISH_HOOK_REF_METAKEY = "mcMMO: Fish Hook Tracker"; public static final String FISH_HOOK_REF_METAKEY = "mcMMO: Fish Hook Tracker";
public static final String DODGE_TRACKER = "mcMMO: Dodge Tracker"; public static final String DODGE_TRACKER = "mcMMO: Dodge Tracker";
public static final String CUSTOM_DAMAGE_METAKEY = "mcMMO: Custom Damage"; public static final String CUSTOM_DAMAGE_METAKEY = "mcMMO: Custom Damage";
public static final String COTW_TEMPORARY_SUMMON = "mcMMO: COTW Entity"; public final static String travelingBlock = "mcMMO: Traveling Block";
public final static String entityMetadataKey = "mcMMO: Spawned Entity";
public final static String blockMetadataKey = "mcMMO: Piston Tracking"; public final static String blockMetadataKey = "mcMMO: Piston Tracking";
public final static String tntMetadataKey = "mcMMO: Tracked TNT"; public final static String tntMetadataKey = "mcMMO: Tracked TNT";
public final static String funfettiMetadataKey = "mcMMO: Funfetti";
public final static String customNameKey = "mcMMO: Custom Name"; public final static String customNameKey = "mcMMO: Custom Name";
public final static String customVisibleKey = "mcMMO: Name Visibility"; public final static String customVisibleKey = "mcMMO: Name Visibility";
public final static String droppedItemKey = "mcMMO: Tracked Item"; public final static String droppedItemKey = "mcMMO: Tracked Item";
@ -133,12 +132,9 @@ public class mcMMO extends JavaPlugin {
public final static String bowForceKey = "mcMMO: Bow Force"; public final static String bowForceKey = "mcMMO: Bow Force";
public final static String arrowDistanceKey = "mcMMO: Arrow Distance"; public final static String arrowDistanceKey = "mcMMO: Arrow Distance";
public final static String BONUS_DROPS_METAKEY = "mcMMO: Double Drops"; public final static String BONUS_DROPS_METAKEY = "mcMMO: Double Drops";
//public final static String customDamageKey = "mcMMO: Custom Damage";
public final static String disarmedItemKey = "mcMMO: Disarmed Item"; public final static String disarmedItemKey = "mcMMO: Disarmed Item";
public final static String playerDataKey = "mcMMO: Player Data"; public final static String playerDataKey = "mcMMO: Player Data";
public final static String greenThumbDataKey = "mcMMO: Green Thumb";
public final static String databaseCommandKey = "mcMMO: Processing Database Command"; public final static String databaseCommandKey = "mcMMO: Processing Database Command";
public final static String bredMetadataKey = "mcMMO: Bred Animal";
public static FixedMetadataValue metadataValue; public static FixedMetadataValue metadataValue;
@ -282,6 +278,8 @@ public class mcMMO extends JavaPlugin {
smeltingTracker = new SmeltingTracker(); smeltingTracker = new SmeltingTracker();
audiences = BukkitAudiences.create(this); audiences = BukkitAudiences.create(this);
transientMetadataTools = new TransientMetadataTools(this);
} }
public static PlayerLevelUtils getPlayerLevelUtils() { public static PlayerLevelUtils getPlayerLevelUtils() {
@ -699,4 +697,8 @@ public class mcMMO extends JavaPlugin {
public static boolean isProjectKorraEnabled() { public static boolean isProjectKorraEnabled() {
return projectKorraEnabled; return projectKorraEnabled;
} }
public static TransientMetadataTools getTransientMetadataTools() {
return transientMetadataTools;
}
} }

View File

@ -17,6 +17,7 @@ import com.gmail.nossr50.skills.SkillManager;
import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Misc;
import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.Permissions;
import com.gmail.nossr50.util.StringUtils; import com.gmail.nossr50.util.StringUtils;
import com.gmail.nossr50.util.compat.layers.persistentdata.MobMetaFlagType;
import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.player.NotificationManager;
import com.gmail.nossr50.util.random.RandomChanceSkillStatic; import com.gmail.nossr50.util.random.RandomChanceSkillStatic;
import com.gmail.nossr50.util.random.RandomChanceUtil; import com.gmail.nossr50.util.random.RandomChanceUtil;
@ -485,12 +486,9 @@ public class TamingManager extends SkillManager {
callOfWildEntity.setRemoveWhenFarAway(false); callOfWildEntity.setRemoveWhenFarAway(false);
} }
private void applyMetaDataToCOTWEntity(LivingEntity callOfWildEntity) { private void applyMetaDataToCOTWEntity(LivingEntity summonedEntity) {
//This is used to prevent XP gains for damaging this entity
callOfWildEntity.setMetadata(mcMMO.entityMetadataKey, mcMMO.metadataValue);
//This helps identify the entity as being summoned by COTW //This helps identify the entity as being summoned by COTW
callOfWildEntity.setMetadata(mcMMO.COTW_TEMPORARY_SUMMON, mcMMO.metadataValue); mcMMO.getCompatibilityManager().getPersistentDataLayer().flagMetadata(MobMetaFlagType.COTW_SUMMONED_MOB, summonedEntity);
} }
/** /**

View File

@ -0,0 +1,37 @@
package com.gmail.nossr50.util;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.compat.layers.persistentdata.SpigotPersistentDataLayer_1_13;
import org.bukkit.entity.LivingEntity;
public class TransientMetadataTools {
private final mcMMO pluginRef;
public TransientMetadataTools(mcMMO pluginRef) {
this.pluginRef = pluginRef;
}
public void cleanAllMobMetadata(LivingEntity livingEntity) {
//Since its not written anywhere, apparently the GC won't touch objects with metadata still present on them
if (livingEntity.hasMetadata(mcMMO.customNameKey)) {
livingEntity.setCustomName(livingEntity.getMetadata(mcMMO.customNameKey).get(0).asString());
livingEntity.removeMetadata(mcMMO.customNameKey, pluginRef);
}
//Involved in changing mob names to hearts
if (livingEntity.hasMetadata(mcMMO.customVisibleKey)) {
livingEntity.setCustomNameVisible(livingEntity.getMetadata(mcMMO.customVisibleKey).get(0).asBoolean());
livingEntity.removeMetadata(mcMMO.customVisibleKey, pluginRef);
}
//Gets assigned to endermen, potentially doesn't get cleared before this point
if(livingEntity.hasMetadata(mcMMO.travelingBlock)) {
livingEntity.removeMetadata(mcMMO.travelingBlock, pluginRef);
}
//1.13.2 uses transient mob flags and needs to be cleaned up
if(mcMMO.getCompatibilityManager().getPersistentDataLayer() instanceof SpigotPersistentDataLayer_1_13) {
mcMMO.getCompatibilityManager().getPersistentDataLayer().removeMobFlags(livingEntity);
}
}
}

View File

@ -1,32 +1,128 @@
package com.gmail.nossr50.util.compat.layers.persistentdata; package com.gmail.nossr50.util.compat.layers.persistentdata;
import com.gmail.nossr50.api.exceptions.IncompleteNamespacedKeyRegister;
import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.compat.layers.AbstractCompatibilityLayer; import com.gmail.nossr50.util.compat.layers.AbstractCompatibilityLayer;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.block.Furnace; import org.bukkit.block.Furnace;
import org.bukkit.entity.LivingEntity;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.*;
import java.util.UUID;
public abstract class AbstractPersistentDataLayer extends AbstractCompatibilityLayer { public abstract class AbstractPersistentDataLayer extends AbstractCompatibilityLayer {
public static final String LEGACY_ABILITY_TOOL_LORE = "mcMMO Ability Tool"; protected final @NotNull NamespacedKey NSK_SUPER_ABILITY_BOOSTED_ITEM;
public final NamespacedKey superAbilityBoosted; protected final @NotNull NamespacedKey NSK_MOB_SPAWNER_MOB;
public final String SUPER_ABILITY_BOOSTED = "super_ability_boosted"; protected final @NotNull NamespacedKey NSK_EGG_MOB;
protected final @NotNull NamespacedKey NSK_NETHER_GATE_MOB;
protected final @NotNull NamespacedKey NSK_COTW_SUMMONED_MOB;
protected final @NotNull NamespacedKey NSK_PLAYER_BRED_MOB;
protected final @NotNull NamespacedKey NSK_PLAYER_TAMED_MOB;
protected final @NotNull NamespacedKey NSK_VILLAGER_TRADE_ORIGIN_ITEM;
protected final @NotNull NamespacedKey NSK_EXPLOITED_ENDERMEN;
//Never change these constants
public final @NotNull String STR_SUPER_ABILITY_BOOSTED_ITEM = "super_ability_boosted";
public final @NotNull String STR_MOB_SPAWNER_MOB = "mcmmo_mob_spawner_mob";
public final @NotNull String STR_EGG_MOB = "mcmmo_egg_mob";
public final @NotNull String STR_NETHER_PORTAL_MOB = "mcmmo_nethergate_mob";
public final @NotNull String STR_COTW_SUMMONED_MOB = "mcmmo_cotw_summoned_mob";
public final @NotNull String STR_PLAYER_BRED_MOB = "mcmmo_player_bred_mob";
public final @NotNull String STR_PLAYER_TAMED_MOB = "mcmmo_player_tamed_mob";
public final @NotNull String STR_VILLAGER_TRADE_ORIGIN_ITEM = "mcmmo_villager_trade_origin_item";
public final @NotNull String STR_EXPLOITED_ENDERMEN = "mcmmo_exploited_endermen";
/*
* Don't modify these keys
*/
public final @NotNull String STR_FURNACE_UUID_MOST_SIG = "furnace_uuid_most_sig";
public final @NotNull String STR_FURNACE_UUID_LEAST_SIG = "furnace_uuid_least_sig";
protected final @NotNull NamespacedKey NSK_FURNACE_UUID_MOST_SIG;
protected final @NotNull NamespacedKey NSK_FURNACE_UUID_LEAST_SIG;
public final @NotNull String LEGACY_ABILITY_TOOL_LORE = "mcMMO Ability Tool";
protected static final byte SIMPLE_FLAG_VALUE = (byte) 0x1;
public AbstractPersistentDataLayer() { public AbstractPersistentDataLayer() {
superAbilityBoosted = getNamespacedKey(SUPER_ABILITY_BOOSTED); NSK_SUPER_ABILITY_BOOSTED_ITEM = getNamespacedKey(STR_SUPER_ABILITY_BOOSTED_ITEM);
NSK_MOB_SPAWNER_MOB = getNamespacedKey(STR_MOB_SPAWNER_MOB);
NSK_EGG_MOB = getNamespacedKey(STR_EGG_MOB);
NSK_NETHER_GATE_MOB = getNamespacedKey(STR_NETHER_PORTAL_MOB);
NSK_COTW_SUMMONED_MOB = getNamespacedKey(STR_COTW_SUMMONED_MOB);
NSK_PLAYER_BRED_MOB = getNamespacedKey(STR_PLAYER_BRED_MOB);
NSK_PLAYER_TAMED_MOB = getNamespacedKey(STR_PLAYER_TAMED_MOB);
NSK_VILLAGER_TRADE_ORIGIN_ITEM = getNamespacedKey(STR_VILLAGER_TRADE_ORIGIN_ITEM);
NSK_EXPLOITED_ENDERMEN = getNamespacedKey(STR_EXPLOITED_ENDERMEN);
NSK_FURNACE_UUID_MOST_SIG = getNamespacedKey(STR_FURNACE_UUID_MOST_SIG);
NSK_FURNACE_UUID_LEAST_SIG = getNamespacedKey(STR_FURNACE_UUID_LEAST_SIG);
initializeLayer(); initializeLayer();
} }
public @NotNull NamespacedKey getNamespacedKey(@NotNull String key) {
/**
* Helper method to simplify generating namespaced keys
* @param key the {@link String} value of the key
* @return the generated {@link NamespacedKey}
*/
private @NotNull NamespacedKey getNamespacedKey(@NotNull String key) {
return new NamespacedKey(mcMMO.p, key); return new NamespacedKey(mcMMO.p, key);
} }
/**
* Whether or not a target {@link LivingEntity} has a specific mcMMO mob flags
* @param flag the type of mob flag to check for
* @param livingEntity the living entity to check for metadata
* @return true if the mob has metadata values for target {@link MobMetaFlagType}
*/
public abstract boolean hasMobFlag(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity);
/**
* Whether or not a target {@link LivingEntity} has any mcMMO mob flags
* @param livingEntity the living entity to check for metadata
* @return true if the mob has any mcMMO mob related metadata values
*/
public abstract boolean hasMobFlags(@NotNull LivingEntity livingEntity);
/**
* Copies all mcMMO mob flags from one {@link LivingEntity} to another {@link LivingEntity}
* This does not clear existing mcMMO mob flags on the target
* @param sourceEntity entity to copy from
* @param targetEntity entity to copy to
*/
public abstract void addMobFlags(@NotNull LivingEntity sourceEntity, @NotNull LivingEntity targetEntity);
/**
* Adds a mob flag to a {@link LivingEntity} which effectively acts a true/false boolean
* Existence of the flag can be considered a true value, non-existence can be considered false for all intents and purposes
* @param flag the desired flag to assign
* @param livingEntity the target living entity
*/
public abstract void flagMetadata(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity);
/**
* Removes a specific mob flag from target {@link LivingEntity}
* @param flag desired flag to remove
* @param livingEntity the target living entity
*/
public abstract void removeMobFlag(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity);
/**
* Remove all mcMMO related mob flags from the target {@link LivingEntity}
* @param livingEntity target entity
*/
public void removeMobFlags(@NotNull LivingEntity livingEntity) {
for(MobMetaFlagType flag : MobMetaFlagType.values()) {
removeMobFlag(flag, livingEntity);
}
}
public abstract @Nullable UUID getFurnaceOwner(@NotNull Furnace furnace); public abstract @Nullable UUID getFurnaceOwner(@NotNull Furnace furnace);
public abstract void setFurnaceOwner(@NotNull Furnace furnace, @NotNull UUID uuid); public abstract void setFurnaceOwner(@NotNull Furnace furnace, @NotNull UUID uuid);
@ -39,7 +135,7 @@ public abstract class AbstractPersistentDataLayer extends AbstractCompatibilityL
public abstract void removeBonusDigSpeedOnSuperAbilityTool(@NotNull ItemStack itemStack); public abstract void removeBonusDigSpeedOnSuperAbilityTool(@NotNull ItemStack itemStack);
public boolean isLegacyAbilityTool(ItemStack itemStack) { public boolean isLegacyAbilityTool(@NotNull ItemStack itemStack) {
ItemMeta itemMeta = itemStack.getItemMeta(); ItemMeta itemMeta = itemStack.getItemMeta();
if(itemMeta == null) if(itemMeta == null)
@ -53,7 +149,7 @@ public abstract class AbstractPersistentDataLayer extends AbstractCompatibilityL
return lore.contains(LEGACY_ABILITY_TOOL_LORE); return lore.contains(LEGACY_ABILITY_TOOL_LORE);
} }
public static String getLegacyAbilityToolLore() { public @NotNull String getLegacyAbilityToolLore() {
return LEGACY_ABILITY_TOOL_LORE; return LEGACY_ABILITY_TOOL_LORE;
} }
} }

View File

@ -0,0 +1,11 @@
package com.gmail.nossr50.util.compat.layers.persistentdata;
public enum MobMetaFlagType {
MOB_SPAWNER_MOB,
EGG_MOB,
NETHER_PORTAL_MOB,
COTW_SUMMONED_MOB,
PLAYER_BRED_MOB,
PLAYER_TAMED_MOB,
EXPLOITED_ENDERMEN,
}

View File

@ -1,9 +1,11 @@
package com.gmail.nossr50.util.compat.layers.persistentdata; package com.gmail.nossr50.util.compat.layers.persistentdata;
import com.gmail.nossr50.api.exceptions.IncompleteNamespacedKeyRegister;
import com.gmail.nossr50.datatypes.meta.UUIDMeta; import com.gmail.nossr50.datatypes.meta.UUIDMeta;
import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.mcMMO;
import org.bukkit.block.Furnace; import org.bukkit.block.Furnace;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.LivingEntity;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.tags.CustomItemTagContainer; import org.bukkit.inventory.meta.tags.CustomItemTagContainer;
@ -11,6 +13,7 @@ import org.bukkit.inventory.meta.tags.ItemTagType;
import org.bukkit.metadata.Metadatable; import org.bukkit.metadata.Metadatable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.EnumMap;
import java.util.UUID; import java.util.UUID;
/** /**
@ -18,19 +21,94 @@ import java.util.UUID;
*/ */
public class SpigotPersistentDataLayer_1_13 extends AbstractPersistentDataLayer { public class SpigotPersistentDataLayer_1_13 extends AbstractPersistentDataLayer {
private final String FURNACE_OWNER_METADATA_KEY = "mcMMO_furnace_owner"; private final @NotNull String KEY_FURNACE_OWNER = "mcMMO_furnace_owner";
private final @NotNull EnumMap<MobMetaFlagType, String> mobFlagKeyMap;
public SpigotPersistentDataLayer_1_13() {
mobFlagKeyMap = new EnumMap<>(MobMetaFlagType.class);
initMobFlagKeyMap();
}
@Override @Override
public boolean initializeLayer() { public boolean initializeLayer() {
return true; return true;
} }
private void initMobFlagKeyMap() throws IncompleteNamespacedKeyRegister {
for(MobMetaFlagType flagType : MobMetaFlagType.values()) {
switch(flagType) {
case MOB_SPAWNER_MOB:
mobFlagKeyMap.put(flagType, STR_MOB_SPAWNER_MOB);
break;
case EGG_MOB:
mobFlagKeyMap.put(flagType, STR_EGG_MOB);
break;
case NETHER_PORTAL_MOB:
mobFlagKeyMap.put(flagType, STR_NETHER_PORTAL_MOB);
break;
case COTW_SUMMONED_MOB:
mobFlagKeyMap.put(flagType, STR_COTW_SUMMONED_MOB);
break;
case PLAYER_BRED_MOB:
mobFlagKeyMap.put(flagType, STR_PLAYER_BRED_MOB);
break;
case PLAYER_TAMED_MOB:
mobFlagKeyMap.put(flagType, STR_PLAYER_TAMED_MOB);
break;
case EXPLOITED_ENDERMEN:
mobFlagKeyMap.put(flagType, STR_EXPLOITED_ENDERMEN);
break;
default:
throw new IncompleteNamespacedKeyRegister("Missing flag register for: "+flagType.toString());
}
}
}
@Override
public boolean hasMobFlag(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
return livingEntity.hasMetadata(mobFlagKeyMap.get(flag));
}
@Override
public boolean hasMobFlags(@NotNull LivingEntity livingEntity) {
for(String currentKey : mobFlagKeyMap.values()) {
if(livingEntity.hasMetadata(currentKey)) {
return true;
}
}
return false;
}
@Override
public void addMobFlags(@NotNull LivingEntity sourceEntity, @NotNull LivingEntity targetEntity) {
for(MobMetaFlagType flag : MobMetaFlagType.values()) {
if(hasMobFlag(flag, sourceEntity)) {
flagMetadata(flag, targetEntity);
}
}
}
@Override
public void flagMetadata(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
if(!hasMobFlag(flag, livingEntity)) {
livingEntity.setMetadata(mobFlagKeyMap.get(flag), mcMMO.metadataValue);
}
}
@Override
public void removeMobFlag(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
if(hasMobFlag(flag, livingEntity)) {
livingEntity.removeMetadata(mobFlagKeyMap.get(flag), mcMMO.p);
}
}
@Override @Override
public UUID getFurnaceOwner(@NotNull Furnace furnace) { public UUID getFurnaceOwner(@NotNull Furnace furnace) {
Metadatable metadatable = (Metadatable) furnace; Metadatable metadatable = (Metadatable) furnace;
if(metadatable.getMetadata(FURNACE_OWNER_METADATA_KEY).size() > 0) { if(metadatable.getMetadata(KEY_FURNACE_OWNER).size() > 0) {
UUIDMeta uuidMeta = (UUIDMeta) metadatable.getMetadata(FURNACE_OWNER_METADATA_KEY).get(0); UUIDMeta uuidMeta = (UUIDMeta) metadatable.getMetadata(KEY_FURNACE_OWNER).get(0);
return (UUID) uuidMeta.value(); return (UUID) uuidMeta.value();
} else { } else {
return null; return null;
@ -41,11 +119,11 @@ public class SpigotPersistentDataLayer_1_13 extends AbstractPersistentDataLayer
public void setFurnaceOwner(@NotNull Furnace furnace, @NotNull UUID uuid) { public void setFurnaceOwner(@NotNull Furnace furnace, @NotNull UUID uuid) {
Metadatable metadatable = (Metadatable) furnace; Metadatable metadatable = (Metadatable) furnace;
if(metadatable.getMetadata(FURNACE_OWNER_METADATA_KEY).size() > 0) { if(metadatable.getMetadata(KEY_FURNACE_OWNER).size() > 0) {
metadatable.removeMetadata(FURNACE_OWNER_METADATA_KEY, mcMMO.p); metadatable.removeMetadata(KEY_FURNACE_OWNER, mcMMO.p);
} }
metadatable.setMetadata(FURNACE_OWNER_METADATA_KEY, new UUIDMeta(mcMMO.p, uuid)); metadatable.setMetadata(KEY_FURNACE_OWNER, new UUIDMeta(mcMMO.p, uuid));
} }
@Override @Override
@ -57,7 +135,7 @@ public class SpigotPersistentDataLayer_1_13 extends AbstractPersistentDataLayer
return; return;
} }
itemMeta.getCustomTagContainer().setCustomTag(superAbilityBoosted, ItemTagType.INTEGER, originalDigSpeed); itemMeta.getCustomTagContainer().setCustomTag(NSK_SUPER_ABILITY_BOOSTED_ITEM, ItemTagType.INTEGER, originalDigSpeed);
itemStack.setItemMeta(itemMeta); itemStack.setItemMeta(itemMeta);
} }
@ -69,7 +147,7 @@ public class SpigotPersistentDataLayer_1_13 extends AbstractPersistentDataLayer
return false; return false;
CustomItemTagContainer tagContainer = itemMeta.getCustomTagContainer(); CustomItemTagContainer tagContainer = itemMeta.getCustomTagContainer();
return tagContainer.hasCustomTag(superAbilityBoosted, ItemTagType.INTEGER); return tagContainer.hasCustomTag(NSK_SUPER_ABILITY_BOOSTED_ITEM, ItemTagType.INTEGER);
} }
@Override @Override
@ -81,8 +159,8 @@ public class SpigotPersistentDataLayer_1_13 extends AbstractPersistentDataLayer
CustomItemTagContainer tagContainer = itemMeta.getCustomTagContainer(); CustomItemTagContainer tagContainer = itemMeta.getCustomTagContainer();
if(tagContainer.hasCustomTag(superAbilityBoosted , ItemTagType.INTEGER)) { if(tagContainer.hasCustomTag(NSK_SUPER_ABILITY_BOOSTED_ITEM, ItemTagType.INTEGER)) {
return tagContainer.getCustomTag(superAbilityBoosted, ItemTagType.INTEGER); return tagContainer.getCustomTag(NSK_SUPER_ABILITY_BOOSTED_ITEM, ItemTagType.INTEGER);
} else { } else {
return 0; return 0;
} }

View File

@ -1,9 +1,11 @@
package com.gmail.nossr50.util.compat.layers.persistentdata; package com.gmail.nossr50.util.compat.layers.persistentdata;
import com.gmail.nossr50.api.exceptions.IncompleteNamespacedKeyRegister;
import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.mcMMO;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.block.Furnace; import org.bukkit.block.Furnace;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.LivingEntity;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataContainer;
@ -12,28 +14,96 @@ import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.EnumMap;
import java.util.UUID; import java.util.UUID;
public class SpigotPersistentDataLayer_1_14 extends AbstractPersistentDataLayer { public class SpigotPersistentDataLayer_1_14 extends AbstractPersistentDataLayer {
/* private final @NotNull EnumMap<MobMetaFlagType, NamespacedKey> mobFlagKeyMap;
* Don't modify these keys
*/
public static final String FURNACE_UUID_MOST_SIG = "furnace_uuid_most_sig";
public static final String FURNACE_UUID_LEAST_SIG = "furnace_uuid_least_sig";
private NamespacedKey furnaceOwner_MostSig_Key; public SpigotPersistentDataLayer_1_14() {
private NamespacedKey furnaceOwner_LeastSig_Key; mobFlagKeyMap = new EnumMap<>(MobMetaFlagType.class);
initMobFlagKeyMap();
}
@Override @Override
public boolean initializeLayer() { public boolean initializeLayer() {
initNamespacedKeys();
return true; return true;
} }
private void initNamespacedKeys() { /**
furnaceOwner_MostSig_Key = getNamespacedKey(FURNACE_UUID_MOST_SIG); * Registers the namespaced keys required by the API (CB/Spigot)
furnaceOwner_LeastSig_Key = getNamespacedKey(FURNACE_UUID_LEAST_SIG); */
private void initMobFlagKeyMap() throws IncompleteNamespacedKeyRegister {
for(MobMetaFlagType mobMetaFlagType : MobMetaFlagType.values()) {
switch(mobMetaFlagType) {
case MOB_SPAWNER_MOB:
mobFlagKeyMap.put(mobMetaFlagType, NSK_MOB_SPAWNER_MOB);
break;
case EGG_MOB:
mobFlagKeyMap.put(mobMetaFlagType, NSK_EGG_MOB);
break;
case NETHER_PORTAL_MOB:
mobFlagKeyMap.put(mobMetaFlagType, NSK_NETHER_GATE_MOB);
break;
case COTW_SUMMONED_MOB:
mobFlagKeyMap.put(mobMetaFlagType, NSK_COTW_SUMMONED_MOB);
break;
case PLAYER_BRED_MOB:
mobFlagKeyMap.put(mobMetaFlagType, NSK_PLAYER_BRED_MOB);
break;
case EXPLOITED_ENDERMEN:
mobFlagKeyMap.put(mobMetaFlagType, NSK_EXPLOITED_ENDERMEN);
break;
case PLAYER_TAMED_MOB:
mobFlagKeyMap.put(mobMetaFlagType, NSK_PLAYER_TAMED_MOB);
break;
default:
throw new IncompleteNamespacedKeyRegister("missing namespaced key register for type: "+ mobMetaFlagType.toString());
}
}
}
@Override
public boolean hasMobFlag(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
return livingEntity.getPersistentDataContainer().has(mobFlagKeyMap.get(flag), PersistentDataType.SHORT);
}
@Override
public boolean hasMobFlags(@NotNull LivingEntity livingEntity) {
for(NamespacedKey currentKey : mobFlagKeyMap.values()) {
if(livingEntity.getPersistentDataContainer().has(currentKey, PersistentDataType.BYTE)) {
return true;
}
}
return false;
}
@Override
public void addMobFlags(@NotNull LivingEntity sourceEntity, @NotNull LivingEntity targetEntity) {
for(MobMetaFlagType flag : MobMetaFlagType.values()) {
if(hasMobFlag(flag, sourceEntity)) {
flagMetadata(flag, targetEntity);
}
}
}
@Override
public void flagMetadata(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
if(!hasMobFlag(flag, livingEntity)) {
PersistentDataContainer persistentDataContainer = livingEntity.getPersistentDataContainer();
persistentDataContainer.set(mobFlagKeyMap.get(flag), PersistentDataType.BYTE, SIMPLE_FLAG_VALUE);
}
}
@Override
public void removeMobFlag(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
if(hasMobFlag(flag, livingEntity)) {
PersistentDataContainer persistentDataContainer = livingEntity.getPersistentDataContainer();
persistentDataContainer.remove(mobFlagKeyMap.get(flag));
}
} }
@Override @Override
@ -42,8 +112,8 @@ public class SpigotPersistentDataLayer_1_14 extends AbstractPersistentDataLayer
PersistentDataContainer dataContainer = ((PersistentDataHolder) furnace).getPersistentDataContainer(); PersistentDataContainer dataContainer = ((PersistentDataHolder) furnace).getPersistentDataContainer();
//Too lazy to make a custom data type for this stuff //Too lazy to make a custom data type for this stuff
Long mostSigBits = dataContainer.get(furnaceOwner_MostSig_Key, PersistentDataType.LONG); Long mostSigBits = dataContainer.get(NSK_FURNACE_UUID_MOST_SIG, PersistentDataType.LONG);
Long leastSigBits = dataContainer.get(furnaceOwner_LeastSig_Key, PersistentDataType.LONG); Long leastSigBits = dataContainer.get(NSK_FURNACE_UUID_LEAST_SIG, PersistentDataType.LONG);
if(mostSigBits != null && leastSigBits != null) { if(mostSigBits != null && leastSigBits != null) {
return new UUID(mostSigBits, leastSigBits); return new UUID(mostSigBits, leastSigBits);
@ -56,8 +126,8 @@ public class SpigotPersistentDataLayer_1_14 extends AbstractPersistentDataLayer
public void setFurnaceOwner(@NotNull Furnace furnace, @NotNull UUID uuid) { public void setFurnaceOwner(@NotNull Furnace furnace, @NotNull UUID uuid) {
PersistentDataContainer dataContainer = ((PersistentDataHolder) furnace).getPersistentDataContainer(); PersistentDataContainer dataContainer = ((PersistentDataHolder) furnace).getPersistentDataContainer();
dataContainer.set(furnaceOwner_MostSig_Key, PersistentDataType.LONG, uuid.getMostSignificantBits()); dataContainer.set(NSK_FURNACE_UUID_MOST_SIG, PersistentDataType.LONG, uuid.getMostSignificantBits());
dataContainer.set(furnaceOwner_LeastSig_Key, PersistentDataType.LONG, uuid.getLeastSignificantBits()); dataContainer.set(NSK_FURNACE_UUID_LEAST_SIG, PersistentDataType.LONG, uuid.getLeastSignificantBits());
furnace.update(); furnace.update();
} }
@ -72,7 +142,7 @@ public class SpigotPersistentDataLayer_1_14 extends AbstractPersistentDataLayer
ItemMeta itemMeta = itemStack.getItemMeta(); ItemMeta itemMeta = itemStack.getItemMeta();
PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer(); PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer();
dataContainer.set(superAbilityBoosted, PersistentDataType.INTEGER, originalDigSpeed); dataContainer.set(NSK_SUPER_ABILITY_BOOSTED_ITEM, PersistentDataType.INTEGER, originalDigSpeed);
itemStack.setItemMeta(itemMeta); itemStack.setItemMeta(itemMeta);
} }
@ -87,7 +157,7 @@ public class SpigotPersistentDataLayer_1_14 extends AbstractPersistentDataLayer
PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer(); PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer();
//If this value isn't null, then the tool can be considered dig speed boosted //If this value isn't null, then the tool can be considered dig speed boosted
Integer boostValue = dataContainer.get(superAbilityBoosted, PersistentDataType.INTEGER); Integer boostValue = dataContainer.get(NSK_SUPER_ABILITY_BOOSTED_ITEM, PersistentDataType.INTEGER);
return boostValue != null; return boostValue != null;
} }
@ -102,12 +172,12 @@ public class SpigotPersistentDataLayer_1_14 extends AbstractPersistentDataLayer
PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer(); PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer();
if(dataContainer.get(superAbilityBoosted, PersistentDataType.INTEGER) == null) { if(dataContainer.get(NSK_SUPER_ABILITY_BOOSTED_ITEM, PersistentDataType.INTEGER) == null) {
mcMMO.p.getLogger().severe("Value should never be null for a boosted item"); mcMMO.p.getLogger().severe("Value should never be null for a boosted item");
return 0; return 0;
} else { } else {
//Too lazy to make a custom data type for this stuff //Too lazy to make a custom data type for this stuff
Integer boostValue = dataContainer.get(superAbilityBoosted, PersistentDataType.INTEGER); Integer boostValue = dataContainer.get(NSK_SUPER_ABILITY_BOOSTED_ITEM, PersistentDataType.INTEGER);
return Math.max(boostValue, 0); return Math.max(boostValue, 0);
} }
} }
@ -127,7 +197,7 @@ public class SpigotPersistentDataLayer_1_14 extends AbstractPersistentDataLayer
} }
PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer(); PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer();
dataContainer.remove(superAbilityBoosted); //Remove persistent data dataContainer.remove(NSK_SUPER_ABILITY_BOOSTED_ITEM); //Remove persistent data
//TODO: needed? //TODO: needed?
itemStack.setItemMeta(itemMeta); itemStack.setItemMeta(itemMeta);

View File

@ -20,6 +20,8 @@ import com.gmail.nossr50.skills.swords.SwordsManager;
import com.gmail.nossr50.skills.taming.TamingManager; import com.gmail.nossr50.skills.taming.TamingManager;
import com.gmail.nossr50.skills.unarmed.UnarmedManager; import com.gmail.nossr50.skills.unarmed.UnarmedManager;
import com.gmail.nossr50.util.*; import com.gmail.nossr50.util.*;
import com.gmail.nossr50.util.compat.layers.persistentdata.AbstractPersistentDataLayer;
import com.gmail.nossr50.util.compat.layers.persistentdata.MobMetaFlagType;
import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.player.NotificationManager;
import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.player.UserManager;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
@ -41,8 +43,13 @@ import java.util.List;
import java.util.Map; import java.util.Map;
public final class CombatUtils { public final class CombatUtils {
private CombatUtils() {} private CombatUtils() {}
private static AbstractPersistentDataLayer getPersistentData() {
return mcMMO.getCompatibilityManager().getPersistentDataLayer();
}
//Likely.. because who knows what plugins are throwing around //Likely.. because who knows what plugins are throwing around
public static boolean isDamageLikelyFromNormalCombat(DamageCause damageCause) { public static boolean isDamageLikelyFromNormalCombat(DamageCause damageCause) {
switch (damageCause) { switch (damageCause) {
@ -106,6 +113,14 @@ public final class CombatUtils {
applyScaledModifiers(initialDamage, finalDamage, event); applyScaledModifiers(initialDamage, finalDamage, event);
processCombatXP(mcMMOPlayer, target, PrimarySkillType.SWORDS); processCombatXP(mcMMOPlayer, target, PrimarySkillType.SWORDS);
printFinalDamageDebug(player, event, mcMMOPlayer);
}
private static void printFinalDamageDebug(Player player, EntityDamageByEntityEvent event, McMMOPlayer mcMMOPlayer) {
if(mcMMOPlayer.isDebugMode()) {
player.sendMessage("Final Damage value after mcMMO modifiers: "+ event.getFinalDamage());
}
} }
// public static void strengthDebug(Player player) { // public static void strengthDebug(Player player) {
@ -181,6 +196,8 @@ public final class CombatUtils {
applyScaledModifiers(initialDamage, finalDamage, event); applyScaledModifiers(initialDamage, finalDamage, event);
processCombatXP(mcMMOPlayer, target, PrimarySkillType.AXES); processCombatXP(mcMMOPlayer, target, PrimarySkillType.AXES);
printFinalDamageDebug(player, event, mcMMOPlayer);
} }
private static void processUnarmedCombat(LivingEntity target, Player player, EntityDamageByEntityEvent event) { private static void processUnarmedCombat(LivingEntity target, Player player, EntityDamageByEntityEvent event) {
@ -223,6 +240,8 @@ public final class CombatUtils {
applyScaledModifiers(initialDamage, finalDamage, event); applyScaledModifiers(initialDamage, finalDamage, event);
processCombatXP(mcMMOPlayer, target, PrimarySkillType.UNARMED); processCombatXP(mcMMOPlayer, target, PrimarySkillType.UNARMED);
printFinalDamageDebug(player, event, mcMMOPlayer);
} }
private static void processTamingCombat(LivingEntity target, Player master, Wolf wolf, EntityDamageByEntityEvent event) { private static void processTamingCombat(LivingEntity target, Player master, Wolf wolf, EntityDamageByEntityEvent event) {
@ -299,6 +318,8 @@ public final class CombatUtils {
applyScaledModifiers(initialDamage, finalDamage, event); applyScaledModifiers(initialDamage, finalDamage, event);
processCombatXP(mcMMOPlayer, target, PrimarySkillType.ARCHERY, forceMultiplier * distanceMultiplier); processCombatXP(mcMMOPlayer, target, PrimarySkillType.ARCHERY, forceMultiplier * distanceMultiplier);
printFinalDamageDebug(player, event, mcMMOPlayer);
} }
/** /**
@ -372,6 +393,7 @@ public final class CombatUtils {
if (PrimarySkillType.SWORDS.getPermissions(player)) { if (PrimarySkillType.SWORDS.getPermissions(player)) {
processSwordCombat(target, player, event); processSwordCombat(target, player, event);
} }
} }
else if (ItemUtils.isAxe(heldItem)) { else if (ItemUtils.isAxe(heldItem)) {
@ -428,6 +450,7 @@ public final class CombatUtils {
} }
} }
} }
} }
/** /**
@ -547,21 +570,21 @@ public final class CombatUtils {
dealDamage(target, damage, DamageCause.CUSTOM, attacker); dealDamage(target, damage, DamageCause.CUSTOM, attacker);
} }
/** // /**
* Attempt to damage target for value dmg with reason ENTITY_ATTACK with damager attacker // * Attempt to damage target for value dmg with reason ENTITY_ATTACK with damager attacker
* // *
* @param target LivingEntity which to attempt to damage // * @param target LivingEntity which to attempt to damage
* @param damage Amount of damage to attempt to do // * @param damage Amount of damage to attempt to do
* @param attacker Player to pass to event as damager // * @param attacker Player to pass to event as damager
*/ // */
public static void dealDamage(LivingEntity target, double damage, Map<DamageModifier, Double> modifiers, LivingEntity attacker) { // public static void dealDamage(LivingEntity target, double damage, Map<DamageModifier, Double> modifiers, LivingEntity attacker) {
if (target.isDead()) { // if (target.isDead()) {
return; // return;
} // }
//
// Aren't we applying the damage twice???? // // Aren't we applying the damage twice????
target.damage(getFakeDamageFinalResult(attacker, target, damage, modifiers)); // target.damage(getFakeDamageFinalResult(attacker, target, damage, modifiers));
} // }
/** /**
* Attempt to damage target for value dmg with reason ENTITY_ATTACK with damager attacker * Attempt to damage target for value dmg with reason ENTITY_ATTACK with damager attacker
@ -576,8 +599,11 @@ public final class CombatUtils {
return; return;
} }
if(canDamage(attacker, target, cause, damage)) if(canDamage(attacker, target, cause, damage)) {
applyIgnoreDamageMetadata(target);
target.damage(damage); target.damage(damage);
removeIgnoreDamageMetadata(target);
}
} }
private static boolean processingNoInvulnDamage; private static boolean processingNoInvulnDamage;
@ -596,21 +622,33 @@ public final class CombatUtils {
// cause do have issues around plugin observability. This is not a perfect solution, but it appears to be the best one here // cause do have issues around plugin observability. This is not a perfect solution, but it appears to be the best one here
// We also set no damage ticks to 0, to ensure that damage is applied for this case, and reset it back to the original value // We also set no damage ticks to 0, to ensure that damage is applied for this case, and reset it back to the original value
// Snapshot current state so we can pop up properly // Snapshot current state so we can pop up properly
boolean wasMetaSet = target.getMetadata(mcMMO.CUSTOM_DAMAGE_METAKEY).size() != 0; boolean wasMetaSet = hasIgnoreDamageMetadata(target);
boolean wasProcessing = processingNoInvulnDamage; boolean wasProcessing = processingNoInvulnDamage;
// set markers // set markers
processingNoInvulnDamage = true; processingNoInvulnDamage = true;
target.setMetadata(mcMMO.CUSTOM_DAMAGE_METAKEY, mcMMO.metadataValue); applyIgnoreDamageMetadata(target);
int noDamageTicks = target.getNoDamageTicks(); int noDamageTicks = target.getNoDamageTicks();
target.setNoDamageTicks(0); target.setNoDamageTicks(0);
target.damage(damage, attacker); target.damage(damage, attacker);
target.setNoDamageTicks(noDamageTicks); target.setNoDamageTicks(noDamageTicks);
if (!wasMetaSet) if (!wasMetaSet)
target.removeMetadata(mcMMO.CUSTOM_DAMAGE_METAKEY, mcMMO.p); removeIgnoreDamageMetadata(target);
if (!wasProcessing) if (!wasProcessing)
processingNoInvulnDamage = false; processingNoInvulnDamage = false;
} }
public static void removeIgnoreDamageMetadata(LivingEntity target) {
target.removeMetadata(mcMMO.CUSTOM_DAMAGE_METAKEY, mcMMO.p);
}
public static void applyIgnoreDamageMetadata(LivingEntity target) {
target.setMetadata(mcMMO.CUSTOM_DAMAGE_METAKEY, mcMMO.metadataValue);
}
public static boolean hasIgnoreDamageMetadata(LivingEntity target) {
return target.getMetadata(mcMMO.CUSTOM_DAMAGE_METAKEY).size() != 0;
}
public static void dealNoInvulnerabilityTickDamageRupture(LivingEntity target, double damage, Entity attacker, int toolTier) { public static void dealNoInvulnerabilityTickDamageRupture(LivingEntity target, double damage, Entity attacker, int toolTier) {
if (target.isDead()) { if (target.isDead()) {
return; return;
@ -767,19 +805,20 @@ public final class CombatUtils {
} }
} }
if (target.hasMetadata(mcMMO.entityMetadataKey) if(getPersistentData().hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, target)) {
//Epic Spawners compatibility baseXP = 0;
|| target.hasMetadata("ES")) { } else if(getPersistentData().hasMobFlag(MobMetaFlagType.MOB_SPAWNER_MOB, target) || target.hasMetadata("ES")) {
baseXP *= ExperienceConfig.getInstance().getSpawnedMobXpMultiplier(); baseXP *= ExperienceConfig.getInstance().getSpawnedMobXpMultiplier();
} } else if(getPersistentData().hasMobFlag(MobMetaFlagType.NETHER_PORTAL_MOB, target)) {
baseXP *= ExperienceConfig.getInstance().getNetherPortalXpMultiplier();
if (target.hasMetadata(mcMMO.bredMetadataKey)) { } else if(getPersistentData().hasMobFlag(MobMetaFlagType.EGG_MOB, target)) {
baseXP *= ExperienceConfig.getInstance().getEggXpMultiplier();
} else if (getPersistentData().hasMobFlag(MobMetaFlagType.PLAYER_BRED_MOB, target)) {
baseXP *= ExperienceConfig.getInstance().getBredMobXpMultiplier(); baseXP *= ExperienceConfig.getInstance().getBredMobXpMultiplier();
} }
xpGainReason = XPGainReason.PVE;
baseXP *= 10; baseXP *= 10;
xpGainReason = XPGainReason.PVE;
} }
baseXP *= multiplier; baseXP *= multiplier;

View File

@ -139,8 +139,12 @@ Experience_Formula:
PVP: 1.0 PVP: 1.0
# Experience gained from mobs not naturally spawned will get multiplied by this value. 0 by default. # Experience gained from mobs not naturally spawned will get multiplied by this value. 0 by default.
Eggs:
Multiplier: 0
Mobspawners: Mobspawners:
Multiplier: 0 Multiplier: 0
Nether_Portal:
Multiplier: 0
Breeding: Breeding:
Multiplier: 1.0 Multiplier: 1.0