Merge branch 'master' of github.com:mcMMO-Dev/mcMMO into tridentsxbows

This commit is contained in:
nossr50 2021-01-02 16:07:17 -08:00
commit 4e8262d818
43 changed files with 1052 additions and 613 deletions

View File

@ -99,15 +99,50 @@ Version 2.2.000
Parties got unnecessarily complex in my absence, I have removed many party features in order to simplify parties and bring them closer to my vision. I have also added new features which should improve parties where it matters. Parties got unnecessarily complex in my absence, I have removed many party features in order to simplify parties and bring them closer to my vision. I have also added new features which should improve parties where it matters.
About the removed party features, all the features I removed I consider poor quality features and I don't think they belong in mcMMO. Feel free to yell at me in discord if you disagree. About the removed party features, all the features I removed I consider poor quality features and I don't think they belong in mcMMO. Feel free to yell at me in discord if you disagree.
I don't know what genius decided to make parties public by default, when I found out that parties had been changed to such a system I could barely contain my disgust. Parties are back to being private, you get invited by a party leader or party officer. That is the only way to join a party. I don't know what genius decided to make parties public by default, when I found out that parties had been changed to such a system I could barely contain my disgust. Parties are back to being private, you get invited by a party leader or party officer. That is the only way to join a party.
Version 2.1.165 Version 2.1.168
The mcMMO system which tracks player placed blocks has had some major rewrites (thanks t00thpick1) Fixed an IndexOutOfBoundsException error when trying to access UserBlockTracker from an invalid range (thanks t00thpick1)
mcMMO will now be compatible with changes to world height (1.17 compatibility) (API) UserBlockTracker is now the interface by which our block-tracker will be known (thanks t00thpick1)
Added missing cooldown locale message 'Commands.Database.Cooldown'
Version 2.1.167
Fixed a serious dupe bug
Add McMMOPlayerPreXpGainEvent event for plugins to modify given exp (thanks electronicboy)
Add SkillActivationPerkEvent (thanks electronicboy)
NOTE:
This bug was introduced in 2.1.165, update immediately if you are on 2.1.165 or 2.1.166
Version 2.1.166
Fixed a small memory leak in the new COTW tracker
Potentially fixed a ConcurrentModificationException involving the TransientEntityTracker (report this error if you encounter it)
Music discs removed from the default fishing_treasures.yml
Optimized how mcMMO saves player data (should improve timings on servers with bad disk speeds and or bad connectivity to their SQL server instance)
NOTES: NOTES:
No one likes fishing up music discs, if you want this change it is recommended you delete fishing_treasures.yml and let it regenerate
(You won't have this file if you haven't updated in a while, if so you don't need to do anything)
If any of you encounter a ConcurrentModificationException error that mentions TransientEntityTracker in its stack trace after this update let me know, I have another fix in mind for this if this update didn't fix it.
Version 2.1.165
Fixed a bug where Enchanted Books dropped by mcMMO (in Fishing) did not function correctly
The mcMMO system which tracks player placed blocks has had some major rewrites (thanks t00thpick1)
Optimized our ChunkUnloadEvent, this should improve timings in this area
How mcMMO tracks COTW entities has been rewritten
When COTW summons are killed players are now informed (from anything other than the time expiring).
mcMMO will now be compatible with changes to world height (1.17 compatibility)
mcMMO will ignore EntityPickupItemEvents from "Fake-Player" NPCs if it recognizes them as such, this will prevent some compatibility issues with some plugins
SuperBreaker will now always activate if the target blocks fastest tool is a Pickaxe (used to require the block giving XP or being considered an ore)
Master Angler mentions that it works better with a boat in its hover tip
Added missing cooldown locale message 'Commands.Database.Cooldown'
Added new locale message 'Taming.Summon.COTW.Removed'
Updated locale entry 'Fishing.SubSkill.MasterAngler.Description'
NOTES:
Books dropped before this fix will not be usable and should just be chucked in lava, the broken books have blue names, the working books have yellow names.
t00thpick1 has taken time to rewrite our block meta tracking system to be more efficient, easier to maintain, and support upcoming features such as world height changes t00thpick1 has taken time to rewrite our block meta tracking system to be more efficient, easier to maintain, and support upcoming features such as world height changes
This new system is compatible with the old one, it will convert old files to the new format as needed. This new system is compatible with the old one, it will convert old files to the new format as needed. You won't even know it is doing anything.
This update shouldn't break anything as the API is the same This update shouldn't break anything as the API is the same.
Alright back to work on T&C unless some major bugs come out of this...
Version 2.1.164 Version 2.1.164
mcMMO will now let players use vanilla blocks that have interactions (such as the vanilla Anvil) which are assigned as either Repair or Salvage blocks if a player is sneaking (see notes) mcMMO will now let players use vanilla blocks that have interactions (such as the vanilla Anvil) which are assigned as either Repair or Salvage blocks if a player is sneaking (see notes)

View File

@ -124,6 +124,10 @@
</artifactSet> </artifactSet>
<!-- <dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>--> <!-- <dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>-->
<relocations> <relocations>
<relocation>
<pattern>net.kyori.examination</pattern>
<shadedPattern>com.gmail.nossr50.kyori.examination</shadedPattern>
</relocation>
<relocation> <relocation>
<pattern>net.kyori.adventure</pattern> <pattern>net.kyori.adventure</pattern>
<shadedPattern>com.gmail.nossr50.mcmmo.kyori.adventure</shadedPattern> <shadedPattern>com.gmail.nossr50.mcmmo.kyori.adventure</shadedPattern>

View File

@ -0,0 +1,50 @@
package com.gmail.nossr50.events.experience;
import com.gmail.nossr50.datatypes.experience.XPGainReason;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* Called when a player gains XP in a skill
*/
public class McMMOPlayerPreXpGainEvent extends McMMOPlayerExperienceEvent {
private float xpGained;
@Deprecated
public McMMOPlayerPreXpGainEvent(Player player, PrimarySkillType skill, float xpGained) {
super(player, skill, XPGainReason.UNKNOWN);
this.xpGained = xpGained;
}
public McMMOPlayerPreXpGainEvent(Player player, PrimarySkillType skill, float xpGained, XPGainReason xpGainReason) {
super(player, skill, xpGainReason);
this.xpGained = xpGained;
}
/**
* @return int amount of experience gained in this event
*/
public int getXpGained() {
return (int) xpGained;
}
/**
* @param xpGained int amount of experience gained in this event
*/
public void setXpGained(int xpGained) {
this.xpGained = xpGained;
}
private static final HandlerList handlers = new HandlerList();
@Override
public @NotNull HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -0,0 +1,49 @@
package com.gmail.nossr50.events.skills;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
public class SkillActivationPerkEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final Player player;
private int ticks;
private final int maxTicks;
public SkillActivationPerkEvent(Player player, int ticks, int maxTicks) {
this.player = player;
this.ticks = ticks;
this.maxTicks = maxTicks;
}
public Player getPlayer() {
return player;
}
public int getTicks() {
return ticks;
}
public void setTicks(int ticks) {
this.ticks = ticks;
}
public int getMaxTicks() {
return maxTicks;
}
@Override
public @NotNull HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@ -252,12 +252,18 @@ public class BlockListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockGrow(BlockGrowEvent event) public void onBlockGrow(BlockGrowEvent event)
{ {
Block block = event.getBlock();
World world = block.getWorld();
/* WORLD BLACKLIST CHECK */ /* WORLD BLACKLIST CHECK */
if(WorldBlacklist.isWorldBlacklisted(event.getBlock().getWorld())) if(WorldBlacklist.isWorldBlacklisted(world))
return; return;
BlockState blockState = event.getBlock().getState(); // Minecraft is dumb, the events still throw when a plant "grows" higher than the max block height. Even though no new block is created
mcMMO.getPlaceStore().setFalse(blockState); if (block.getY() >= world.getMaxHeight())
return;
mcMMO.getPlaceStore().setFalse(block);
} }
/** /**

View File

@ -1,30 +1,20 @@
package com.gmail.nossr50.listeners; package com.gmail.nossr50.listeners;
import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.compat.layers.persistentdata.MobMetaFlagType;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkUnloadEvent; import org.bukkit.event.world.ChunkUnloadEvent;
import java.util.List;
public class ChunkListener implements Listener { public class ChunkListener implements Listener {
@EventHandler(ignoreCancelled = true) @EventHandler(ignoreCancelled = true)
public void onChunkUnload(ChunkUnloadEvent event) { public void onChunkUnload(ChunkUnloadEvent event) {
for(Entity entity : event.getChunk().getEntities()) { List<LivingEntity> matchingEntities = mcMMO.getTransientEntityTracker().getAllTransientEntitiesInChunk(event.getChunk());
if(entity instanceof LivingEntity) { for(LivingEntity livingEntity : matchingEntities) {
LivingEntity livingEntity = (LivingEntity) entity; mcMMO.getTransientEntityTracker().removeSummon(livingEntity, null, false);
if(mcMMO.getCompatibilityManager().getPersistentDataLayer().hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, livingEntity)) {
//Remove from existence
if(livingEntity.isValid()) {
mcMMO.getCompatibilityManager().getPersistentDataLayer().removeMobFlags(livingEntity);
livingEntity.setHealth(0);
livingEntity.remove();
}
}
}
} }
} }
} }

View File

@ -689,12 +689,17 @@ public class EntityListener implements Listener {
*/ */
@EventHandler(ignoreCancelled = true) @EventHandler(ignoreCancelled = true)
public void onEntityDeath(EntityDeathEvent event) { public void onEntityDeath(EntityDeathEvent event) {
/* WORLD BLACKLIST CHECK */
if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld()))
return;
LivingEntity entity = event.getEntity(); LivingEntity entity = event.getEntity();
if(mcMMO.getTransientEntityTracker().isTransientSummon(entity)) {
mcMMO.getTransientEntityTracker().removeSummon(entity, null, false);
}
/* WORLD BLACKLIST CHECK */
if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld())) {
return;
}
if (ExperienceConfig.getInstance().isNPCInteractionPrevented() && Misc.isNPCEntityExcludingVillagers(entity)) { if (ExperienceConfig.getInstance().isNPCInteractionPrevented() && Misc.isNPCEntityExcludingVillagers(entity)) {
return; return;
} }

View File

@ -448,6 +448,10 @@ public class PlayerListener implements Listener {
if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld())) if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld()))
return; return;
if(Misc.isNPCEntityExcludingVillagers(event.getEntity())) {
return;
}
if(event.getEntity() instanceof Player) if(event.getEntity() instanceof Player)
{ {
Player player = (Player) event.getEntity(); Player player = (Player) event.getEntity();
@ -463,14 +467,13 @@ public class PlayerListener implements Listener {
return; return;
} }
OnlineMMOPlayer mmoPlayer = mcMMO.getUserManager().queryPlayer(player);
//Profile not loaded //Profile not loaded
if(mcMMO.getUserManager().queryPlayer(player) == null) if(mmoPlayer == null) {
{
return; return;
} }
OnlineMMOPlayer mmoPlayer = mcMMO.getUserManager().queryPlayer(player);
Item drop = event.getItem(); Item drop = event.getItem();
ItemStack dropStack = drop.getItemStack(); ItemStack dropStack = drop.getItemStack();
@ -525,18 +528,32 @@ public class PlayerListener implements Listener {
return; return;
} }
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
//Profile not loaded //Profile not loaded
OnlineMMOPlayer mmoPlayer = mcMMO.getUserManager().queryPlayer(player); McMMOPlayer mmoPlayer = mcMMO.getUserManager().queryPlayer(player);
if(mmoPlayer == null) { if(mmoPlayer == null) {
return; return;
} }
//TODO: There's an issue with using Async saves on player quit mcMMO.getUserManager().saveUserImmediately(mmoPlayer, mcMMO.isServerShutdownExecuted());
//TODO: Basically there are conditions in which an async task does not execute fast enough to save the data if the server shutdown shortly after this task was scheduled //Use a sync save if the server is shutting down to avoid race conditions
mcMMO.getUserManager().saveUserWithDelay(mmoPlayer.getMMOPlayerData(), false, 20); //TODO: Make sure this cleans up
//TODO: Make sure this cleans up
mcMMO.getUserManager().cleanupPlayer(mmoPlayer); //Handles cleaning up the player when their profile is no longer needed //TODO: Make sure this cleans up
//TODO: Make sure this cleans up
//TODO: Make sure this cleans up
//TODO: Make sure this cleans up
//TODO: Make sure this cleans up
//TODO: Make sure this cleans up
//TODO: Make sure this cleans up
//TODO: Make sure this cleans up
//TODO: Make sure this cleans up
//TODO: Make sure this cleans up
//TODO: Make sure this cleans up
//TODO: Make sure this cleans up
mmoPlayer.logout(mcMMO.isServerShutdownExecuted());
} }
/** /**

View File

@ -91,6 +91,8 @@ public class mcMMO extends JavaPlugin {
private static ChatManager chatManager; private static ChatManager chatManager;
private static CommandManager commandManager; //ACF private static CommandManager commandManager; //ACF
private static SkillRegister skillRegister; private static SkillRegister skillRegister;
private static TransientEntityTracker transientEntityTracker;
private static boolean serverShutdownExecuted = false;
/* Adventure */ /* Adventure */
private static BukkitAudiences audiences; private static BukkitAudiences audiences;
@ -299,6 +301,9 @@ public class mcMMO extends JavaPlugin {
chatManager = new ChatManager(this); chatManager = new ChatManager(this);
commandManager = new CommandManager(this); commandManager = new CommandManager(this);
transientEntityTracker = new TransientEntityTracker();
setServerShutdown(false); //Reset flag, used to make decisions about async saves
} }
public static PlayerLevelUtils getPlayerLevelUtils() { public static PlayerLevelUtils getPlayerLevelUtils() {
@ -334,6 +339,10 @@ public class mcMMO extends JavaPlugin {
*/ */
@Override @Override
public void onDisable() { public void onDisable() {
setServerShutdown(true);
//TODO: Write code to catch unfinished async save tasks, for now we just hope they finish in time, which they should in most cases
mcMMO.p.getLogger().info("Server shutdown has been executed, saving and cleaning up data...");
try { try {
userManager.saveAllSync(); // Make sure to save player information if the server shuts down userManager.saveAllSync(); // Make sure to save player information if the server shuts down
userManager.clearAll(); userManager.clearAll();
@ -346,17 +355,11 @@ public class mcMMO extends JavaPlugin {
formulaManager.saveFormula(); formulaManager.saveFormula();
holidayManager.saveAnniversaryFiles(); holidayManager.saveAnniversaryFiles();
placeStore.cleanUp(); // Cleanup empty metadata stores
placeStore.closeAll(); placeStore.closeAll();
} }
catch (Exception e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); }
debug("Canceling all tasks...");
getServer().getScheduler().cancelTasks(this); // This removes our tasks
debug("Unregister all events...");
HandlerList.unregisterAll(this); // Cancel event registrations
if (Config.getInstance().getBackupsEnabled()) { if (Config.getInstance().getBackupsEnabled()) {
// Remove other tasks BEFORE starting the Backup, or we just cancel it straight away. // Remove other tasks BEFORE starting the Backup, or we just cancel it straight away.
try { try {
@ -376,6 +379,11 @@ public class mcMMO extends JavaPlugin {
} }
} }
debug("Canceling all tasks...");
getServer().getScheduler().cancelTasks(this); // This removes our tasks
debug("Unregister all events...");
HandlerList.unregisterAll(this); // Cancel event registrations
databaseManager.onDisable(); databaseManager.onDisable();
debug("Was disabled."); // How informative! debug("Was disabled."); // How informative!
} }
@ -704,5 +712,18 @@ public class mcMMO extends JavaPlugin {
return commandManager; return commandManager;
} }
public static TransientEntityTracker getTransientEntityTracker() {
return transientEntityTracker;
}
public static synchronized boolean isServerShutdownExecuted() {
return serverShutdownExecuted;
}
private static synchronized void setServerShutdown(boolean bool) {
serverShutdownExecuted = bool;
}
public @NotNull SkillRegister getSkillRegister() { return skillRegister; } public @NotNull SkillRegister getSkillRegister() { return skillRegister; }
} }

View File

@ -37,7 +37,6 @@ import org.bukkit.entity.*;
import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta; import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.util.BoundingBox; import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
@ -396,7 +395,7 @@ public class FishingManager extends SkillManager {
if (treasure != null) { if (treasure != null) {
if(treasure instanceof FishingTreasureBook) { if(treasure instanceof FishingTreasureBook) {
treasureDrop = createEnchantBook((FishingTreasureBook) treasure); treasureDrop = ItemUtils.createEnchantBook((FishingTreasureBook) treasure);
} else { } else {
treasureDrop = treasure.getDrop().clone(); // Not cloning is bad, m'kay? treasureDrop = treasure.getDrop().clone(); // Not cloning is bad, m'kay?
@ -450,37 +449,16 @@ public class FishingManager extends SkillManager {
} }
if(fishingSucceeds) { if(fishingSucceeds) {
fishingCatch.setItemStack(treasureDrop);
if (Config.getInstance().getFishingExtraFish()) { if (Config.getInstance().getFishingExtraFish()) {
Misc.spawnItem(player.getEyeLocation(), fishingCatch.getItemStack(), ItemSpawnReason.FISHING_EXTRA_FISH); Misc.spawnItem(player.getEyeLocation(), fishingCatch.getItemStack(), ItemSpawnReason.FISHING_EXTRA_FISH);
} }
fishingCatch.setItemStack(treasureDrop);
} }
applyXpGain(fishXp + treasureXp, XPGainReason.PVE); applyXpGain(fishXp + treasureXp, XPGainReason.PVE);
} }
private @NotNull ItemStack createEnchantBook(@NotNull FishingTreasureBook fishingTreasureBook) {
ItemStack itemStack = fishingTreasureBook.getDrop().clone();
EnchantmentWrapper enchantmentWrapper = getRandomEnchantment(fishingTreasureBook.getLegalEnchantments());
ItemMeta itemMeta = itemStack.getItemMeta();
if(itemMeta == null)
return itemStack;
itemMeta.addEnchant(enchantmentWrapper.getEnchantment(), enchantmentWrapper.getEnchantmentLevel(), ExperienceConfig.getInstance().allowUnsafeEnchantments());
itemStack.setItemMeta(itemMeta);
return itemStack;
}
private @NotNull EnchantmentWrapper getRandomEnchantment(@NotNull List<EnchantmentWrapper> enchantmentWrappers) {
Collections.shuffle(enchantmentWrappers, Misc.getRandom());
int randomIndex = Misc.getRandom().nextInt(enchantmentWrappers.size());
return enchantmentWrappers.get(randomIndex);
}
/** /**
* Handle the vanilla XP boost for Fishing * Handle the vanilla XP boost for Fishing
* *

View File

@ -32,9 +32,7 @@ import org.bukkit.entity.*;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
public class TamingManager extends SkillManager { public class TamingManager extends SkillManager {
//TODO: Temporary static cache, will be changed in 2.2 //TODO: Temporary static cache, will be changed in 2.2
@ -42,8 +40,6 @@ public class TamingManager extends SkillManager {
private static HashMap<CallOfTheWildType, TamingSummon> cotwSummonDataProperties; private static HashMap<CallOfTheWildType, TamingSummon> cotwSummonDataProperties;
private long lastSummonTimeStamp; private long lastSummonTimeStamp;
private HashMap<CallOfTheWildType, List<TrackedTamingEntity>> playerSummonedEntities;
public TamingManager(@NotNull OnlineMMOPlayer mmoPlayer) { public TamingManager(@NotNull OnlineMMOPlayer mmoPlayer) {
super(mmoPlayer, PrimarySkillType.TAMING); super(mmoPlayer, PrimarySkillType.TAMING);
init(); init();
@ -55,20 +51,12 @@ public class TamingManager extends SkillManager {
lastSummonTimeStamp = 0L; lastSummonTimeStamp = 0L;
//Init per-player tracking of summoned entities //Init per-player tracking of summoned entities
initPerPlayerSummonTracking(); mcMMO.getTransientEntityTracker().initPlayer(mmoPlayer.getPlayer());
//Hacky stuff used as a band-aid //Hacky stuff used as a band-aid
initStaticCaches(); initStaticCaches();
} }
private void initPerPlayerSummonTracking() {
playerSummonedEntities = new HashMap<>();
for(CallOfTheWildType callOfTheWildType : CallOfTheWildType.values()) {
playerSummonedEntities.put(callOfTheWildType, new ArrayList<>());
}
}
private void initStaticCaches() { private void initStaticCaches() {
//TODO: Temporary static cache, will be changed in 2.2 //TODO: Temporary static cache, will be changed in 2.2
//This is shared between instances of TamingManager //This is shared between instances of TamingManager
@ -503,58 +491,12 @@ public class TamingManager extends SkillManager {
return summoningItems.containsKey(itemStack.getType()); return summoningItems.containsKey(itemStack.getType());
} }
//TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update private int getAmountCurrentlySummoned(@NotNull CallOfTheWildType callOfTheWildType) {
private int getAmountCurrentlySummoned(CallOfTheWildType callOfTheWildType) { return mcMMO.getTransientEntityTracker().getAmountCurrentlySummoned(getPlayer().getUniqueId(), callOfTheWildType);
//The tracker is unreliable so validate its contents first
recalibrateTracker();
return playerSummonedEntities.get(callOfTheWildType).size();
} }
//TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update
private void addToTracker(@NotNull LivingEntity livingEntity, @NotNull CallOfTheWildType callOfTheWildType) { private void addToTracker(@NotNull LivingEntity livingEntity, @NotNull CallOfTheWildType callOfTheWildType) {
TrackedTamingEntity trackedEntity = new TrackedTamingEntity(livingEntity, callOfTheWildType, this); mcMMO.getTransientEntityTracker().registerEntity(getPlayer().getUniqueId(), new TrackedTamingEntity(livingEntity, callOfTheWildType, getPlayer()));
playerSummonedEntities.get(callOfTheWildType).add(trackedEntity);
}
//TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update
public List<TrackedTamingEntity> getTrackedEntities(CallOfTheWildType callOfTheWildType) {
return playerSummonedEntities.get(callOfTheWildType);
}
//TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update
public void removeFromTracker(@NotNull TrackedTamingEntity trackedEntity) {
playerSummonedEntities.get(trackedEntity.getCallOfTheWildType()).remove(trackedEntity);
NotificationManager.sendPlayerInformationChatOnly(getPlayer(), "Taming.Summon.COTW.TimeExpired", StringUtils.getPrettyEntityTypeString(trackedEntity.getLivingEntity().getType()));
}
/**
* Builds a new tracked list by determining which tracked things are still valid
*/
//TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update
private void recalibrateTracker() {
for(CallOfTheWildType callOfTheWildType : CallOfTheWildType.values()) {
ArrayList<TrackedTamingEntity> validEntities = getValidTrackedEntities(callOfTheWildType);
playerSummonedEntities.put(callOfTheWildType, validEntities); //Replace the old list with the new list
}
}
//TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update
private @NotNull ArrayList<TrackedTamingEntity> getValidTrackedEntities(@NotNull CallOfTheWildType callOfTheWildType) {
ArrayList<TrackedTamingEntity> validTrackedEntities = new ArrayList<>();
for(TrackedTamingEntity trackedTamingEntity : getTrackedEntities(callOfTheWildType)) {
LivingEntity livingEntity = trackedTamingEntity.getLivingEntity();
//Remove from existence
if(livingEntity != null && livingEntity.isValid()) {
validTrackedEntities.add(trackedTamingEntity);
}
}
return validTrackedEntities;
} }
/** /**
@ -563,20 +505,6 @@ public class TamingManager extends SkillManager {
*/ */
//TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update //TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update
public void cleanupAllSummons() { public void cleanupAllSummons() {
for(List<TrackedTamingEntity> trackedTamingEntities : playerSummonedEntities.values()) { mcMMO.getTransientEntityTracker().cleanupPlayer(getPlayer());
for(TrackedTamingEntity trackedTamingEntity : trackedTamingEntities) {
LivingEntity livingEntity = trackedTamingEntity.getLivingEntity();
//Remove from existence
if(livingEntity != null && livingEntity.isValid()) {
mcMMO.getCompatibilityManager().getPersistentDataLayer().removeMobFlags(livingEntity);
livingEntity.setHealth(0);
livingEntity.remove();
}
}
//Clear the list
trackedTamingEntities.clear();
}
} }
} }

View File

@ -4,61 +4,40 @@ import com.gmail.nossr50.config.Config;
import com.gmail.nossr50.datatypes.skills.subskills.taming.CallOfTheWildType; import com.gmail.nossr50.datatypes.skills.subskills.taming.CallOfTheWildType;
import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Misc;
import com.gmail.nossr50.util.skills.ParticleEffectUtils;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class TrackedTamingEntity extends BukkitRunnable { public class TrackedTamingEntity extends BukkitRunnable {
private final LivingEntity livingEntity; private final @NotNull LivingEntity livingEntity;
private final CallOfTheWildType callOfTheWildType; private final @NotNull CallOfTheWildType callOfTheWildType;
private final UUID id; private final @NotNull Player player;
private int length;
private final TamingManager tamingManagerRef;
protected TrackedTamingEntity(LivingEntity livingEntity, CallOfTheWildType callOfTheWildType, TamingManager tamingManagerRef) { protected TrackedTamingEntity(@NotNull LivingEntity livingEntity, @NotNull CallOfTheWildType callOfTheWildType, @NotNull Player player) {
this.tamingManagerRef = tamingManagerRef; this.player = player;
this.callOfTheWildType = callOfTheWildType; this.callOfTheWildType = callOfTheWildType;
this.livingEntity = livingEntity; this.livingEntity = livingEntity;
this.id = livingEntity.getUniqueId();
int tamingCOTWLength = Config.getInstance().getTamingCOTWLength(callOfTheWildType.getConfigEntityTypeEntry()); int tamingCOTWLength = Config.getInstance().getTamingCOTWLength(callOfTheWildType.getConfigEntityTypeEntry());
if (tamingCOTWLength > 0) { if (tamingCOTWLength > 0) {
this.length = tamingCOTWLength * Misc.TICK_CONVERSION_FACTOR; int length = tamingCOTWLength * Misc.TICK_CONVERSION_FACTOR;
this.runTaskLater(mcMMO.p, length); this.runTaskLater(mcMMO.p, length);
} }
} }
@Override @Override
public void run() { public void run() {
if (livingEntity.isValid()) { mcMMO.getTransientEntityTracker().removeSummon(this.getLivingEntity(), player, true);
Location location = livingEntity.getLocation();
location.getWorld().playSound(location, Sound.BLOCK_FIRE_EXTINGUISH, 0.8F, 0.8F);
ParticleEffectUtils.playCallOfTheWildEffect(livingEntity);
if(tamingManagerRef != null)
tamingManagerRef.removeFromTracker(this);
livingEntity.setHealth(0);
livingEntity.remove();
}
this.cancel(); this.cancel();
} }
public CallOfTheWildType getCallOfTheWildType() { public @NotNull CallOfTheWildType getCallOfTheWildType() {
return callOfTheWildType; return callOfTheWildType;
} }
public LivingEntity getLivingEntity() { public @NotNull LivingEntity getLivingEntity() {
return livingEntity; return livingEntity;
} }
public UUID getID() {
return id;
}
} }

View File

@ -182,11 +182,14 @@ public final class BlockUtils {
* @return true if the block should affected by Super Breaker, false * @return true if the block should affected by Super Breaker, false
* otherwise * otherwise
*/ */
public static Boolean affectedBySuperBreaker(BlockState blockState) { public static boolean affectedBySuperBreaker(BlockState blockState) {
if(mcMMO.getMaterialMapStore().isIntendedToolPickaxe(blockState.getType()))
return true;
if (ExperienceConfig.getInstance().doesBlockGiveSkillXP(PrimarySkillType.MINING, blockState.getBlockData())) if (ExperienceConfig.getInstance().doesBlockGiveSkillXP(PrimarySkillType.MINING, blockState.getBlockData()))
return true; return true;
return isOre(blockState) || mcMMO.getModManager().isCustomMiningBlock(blockState); return mcMMO.getModManager().isCustomMiningBlock(blockState);
} }
/** /**

View File

@ -2,7 +2,10 @@ package com.gmail.nossr50.util;
import com.gmail.nossr50.config.AdvancedConfig; import com.gmail.nossr50.config.AdvancedConfig;
import com.gmail.nossr50.config.Config; import com.gmail.nossr50.config.Config;
import com.gmail.nossr50.config.experience.ExperienceConfig;
import com.gmail.nossr50.config.party.ItemWeightConfig; import com.gmail.nossr50.config.party.ItemWeightConfig;
import com.gmail.nossr50.datatypes.treasure.EnchantmentWrapper;
import com.gmail.nossr50.datatypes.treasure.FishingTreasureBook;
import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.mcMMO;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
@ -12,9 +15,11 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.FurnaceRecipe; import org.bukkit.inventory.FurnaceRecipe;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Recipe; import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List; import java.util.List;
import static org.bukkit.Material.AIR; import static org.bukkit.Material.AIR;
@ -581,4 +586,26 @@ public final class ItemUtils {
public static boolean canBeSuperAbilityDigBoosted(@NotNull ItemStack itemStack) { public static boolean canBeSuperAbilityDigBoosted(@NotNull ItemStack itemStack) {
return isShovel(itemStack) || isPickaxe(itemStack); return isShovel(itemStack) || isPickaxe(itemStack);
} }
public static @NotNull ItemStack createEnchantBook(@NotNull FishingTreasureBook fishingTreasureBook) {
ItemStack itemStack = fishingTreasureBook.getDrop().clone();
EnchantmentWrapper enchantmentWrapper = getRandomEnchantment(fishingTreasureBook.getLegalEnchantments());
ItemMeta itemMeta = itemStack.getItemMeta();
if(itemMeta == null) {
return itemStack;
}
EnchantmentStorageMeta enchantmentStorageMeta = (EnchantmentStorageMeta) itemMeta;
enchantmentStorageMeta.addStoredEnchant(enchantmentWrapper.getEnchantment(), enchantmentWrapper.getEnchantmentLevel(), ExperienceConfig.getInstance().allowUnsafeEnchantments());
itemStack.setItemMeta(enchantmentStorageMeta);
return itemStack;
}
public static @NotNull EnchantmentWrapper getRandomEnchantment(@NotNull List<EnchantmentWrapper> enchantmentWrappers) {
Collections.shuffle(enchantmentWrappers, Misc.getRandom());
int randomIndex = Misc.getRandom().nextInt(enchantmentWrappers.size());
return enchantmentWrappers.get(randomIndex);
}
} }

View File

@ -55,6 +55,8 @@ public class MaterialMapStore {
private final @NotNull HashSet<String> enchantables; private final @NotNull HashSet<String> enchantables;
private final @NotNull HashSet<String> ores; private final @NotNull HashSet<String> ores;
private final @NotNull HashSet<String> intendedToolPickAxe;
private final @NotNull HashSet<String> intendedToolShovel;
private final @NotNull HashMap<String, Integer> tierValue; private final @NotNull HashMap<String, Integer> tierValue;
@ -100,14 +102,16 @@ public class MaterialMapStore {
enchantables = new HashSet<>(); enchantables = new HashSet<>();
ores = new HashSet<>(); ores = new HashSet<>();
intendedToolPickAxe = new HashSet<>();
intendedToolShovel = new HashSet<>();
tierValue = new HashMap<>(); tierValue = new HashMap<>();
fillVanillaMaterialRegisters(); fillVanillaMaterialRegisters();
} }
private void fillVanillaMaterialRegisters() private void fillVanillaMaterialRegisters() {
{ //The order matters
fillAbilityBlackList(); fillAbilityBlackList();
fillToolBlackList(); fillToolBlackList();
fillMossyWhiteList(); fillMossyWhiteList();
@ -122,6 +126,7 @@ public class MaterialMapStore {
fillTools(); fillTools();
fillEnchantables(); fillEnchantables();
fillOres(); fillOres();
fillIntendedTools();
fillTierMap(); fillTierMap();
} }
@ -206,6 +211,190 @@ public class MaterialMapStore {
ores.add("gilded_blackstone"); ores.add("gilded_blackstone");
} }
private void fillIntendedTools() {
intendedToolPickAxe.addAll(ores);
intendedToolPickAxe.add("ice");
intendedToolPickAxe.add("packed_ice");
intendedToolPickAxe.add("blue_ice");
intendedToolPickAxe.add("frosted_ice");
intendedToolPickAxe.add("anvil");
intendedToolPickAxe.add("bell");
intendedToolPickAxe.add("block_of_redstone");
intendedToolPickAxe.add("brewing_stand");
intendedToolPickAxe.add("cauldron");
intendedToolPickAxe.add("chain");
intendedToolPickAxe.add("hopper");
intendedToolPickAxe.add("iron_bars");
intendedToolPickAxe.add("iron_door");
intendedToolPickAxe.add("iron_trapdoor");
intendedToolPickAxe.add("lantern");
intendedToolPickAxe.add("weighted_pressure_plates");
intendedToolPickAxe.add("block_of_iron");
intendedToolPickAxe.add("copper_blocks");
intendedToolPickAxe.add("cut_copper");
intendedToolPickAxe.add("cut_copper_slab");
intendedToolPickAxe.add("cut_copper_stairs");
intendedToolPickAxe.add("lapis_lazuli_block");
intendedToolPickAxe.add("lightning_rod");
intendedToolPickAxe.add("block_of_diamond");
intendedToolPickAxe.add("block_of_emerald");
intendedToolPickAxe.add("block_of_gold");
intendedToolPickAxe.add("block_of_netherite");
intendedToolPickAxe.add("piston");
intendedToolPickAxe.add("sticky_piston");
intendedToolPickAxe.add("conduit");
intendedToolPickAxe.add("shulker_box");
intendedToolPickAxe.add("element_constructor"); //be & ee
intendedToolPickAxe.add("compound_creator"); //be & ee
intendedToolPickAxe.add("material_reducer"); //be & ee
intendedToolPickAxe.add("activator_rail");
intendedToolPickAxe.add("detector_rail");
intendedToolPickAxe.add("powered_rail");
intendedToolPickAxe.add("rail");
intendedToolPickAxe.add("andesite");
intendedToolPickAxe.add("basalt");
intendedToolPickAxe.add("blackstone");
intendedToolPickAxe.add("blast_furnace");
intendedToolPickAxe.add("block_of_coal");
intendedToolPickAxe.add("block_of_quartz");
intendedToolPickAxe.add("bricks");
intendedToolPickAxe.add("cobblestone");
intendedToolPickAxe.add("cobblestone_wall");
intendedToolPickAxe.add("concrete");
intendedToolPickAxe.add("dark_prismarine");
intendedToolPickAxe.add("diorite");
intendedToolPickAxe.add("dispenser");
intendedToolPickAxe.add("dripstone_block");
intendedToolPickAxe.add("dropper");
intendedToolPickAxe.add("enchantment_table");
intendedToolPickAxe.add("end_stone");
intendedToolPickAxe.add("ender_chest");
intendedToolPickAxe.add("furnace");
intendedToolPickAxe.add("glazed_terracotta");
intendedToolPickAxe.add("granite");
intendedToolPickAxe.add("grindstone");
intendedToolPickAxe.add("heat_block"); //be & ee
intendedToolPickAxe.add("lodestone");
intendedToolPickAxe.add("mossy_cobblestone");
intendedToolPickAxe.add("nether_bricks");
intendedToolPickAxe.add("nether_brick_fence");
intendedToolPickAxe.add("nether_gold_ore");
intendedToolPickAxe.add("nether_quartz_ore");
intendedToolPickAxe.add("netherrack");
intendedToolPickAxe.add("observer");
intendedToolPickAxe.add("prismarine");
intendedToolPickAxe.add("prismarine_bricks");
intendedToolPickAxe.add("pointed_dripstone");
intendedToolPickAxe.add("polished_andesite");
intendedToolPickAxe.add("polished_blackstone");
intendedToolPickAxe.add("polished_blackstone_bricks");
intendedToolPickAxe.add("polished_diorite");
intendedToolPickAxe.add("polished_granite");
intendedToolPickAxe.add("red_sandstone");
intendedToolPickAxe.add("sandstone");
intendedToolPickAxe.add("smoker");
intendedToolPickAxe.add("spawner");
intendedToolPickAxe.add("stonecutter");
// intendedToolPickAxe.add("slabs");
intendedToolPickAxe.add("colored_terracotta");
// intendedToolPickAxe.add("stairs");
intendedToolPickAxe.add("smooth_stone");
intendedToolPickAxe.add("stone");
intendedToolPickAxe.add("stone_bricks");
intendedToolPickAxe.add("stone_button");
intendedToolPickAxe.add("stone_pressure_plate");
intendedToolPickAxe.add("terracotta");
intendedToolPickAxe.add("amethyst_bud");
intendedToolPickAxe.add("amethyst_cluster");
intendedToolPickAxe.add("block_of_amethyst");
intendedToolPickAxe.add("budding_amethyst");
intendedToolPickAxe.add("ancient_debris");
intendedToolPickAxe.add("crying_obsidian");
intendedToolPickAxe.add("glowing_obsidian"); //be
intendedToolPickAxe.add("obsidian");
intendedToolPickAxe.add("respawn_anchor");
//slabs
intendedToolPickAxe.add("petrified_oak_slab");
intendedToolPickAxe.add("stone_slab");
intendedToolPickAxe.add("smooth_stone_slab");
intendedToolPickAxe.add("cobblestone_slab");
intendedToolPickAxe.add("mossy_cobblestone_slab");
intendedToolPickAxe.add("stone_brick_slab");
intendedToolPickAxe.add("mossy_stone_brick_slab");
intendedToolPickAxe.add("andesite_slab");
intendedToolPickAxe.add("polished_andesite_slab");
intendedToolPickAxe.add("diorite_slab");
intendedToolPickAxe.add("polished_diorite_slab");
intendedToolPickAxe.add("granite_slab");
intendedToolPickAxe.add("polished_granite_slab");
intendedToolPickAxe.add("sandstone_slab");
intendedToolPickAxe.add("cut_sandstone_slab");
intendedToolPickAxe.add("smooth_sandstone_slab");
intendedToolPickAxe.add("red_sandstone_slab");
intendedToolPickAxe.add("cut_red_sandstone_slab");
intendedToolPickAxe.add("smooth_red_sandstone_slab");
intendedToolPickAxe.add("brick_slab");
intendedToolPickAxe.add("prismarine_brick_slab");
intendedToolPickAxe.add("dark_prismarine_slab");
intendedToolPickAxe.add("nether_brick_slab");
intendedToolPickAxe.add("red_netherbrick_slab");
intendedToolPickAxe.add("quartz_slab");
intendedToolPickAxe.add("smooth_quartz_slab");
intendedToolPickAxe.add("purpur_slab");
intendedToolPickAxe.add("end_stone_brick_slab");
intendedToolPickAxe.add("blackstone_slab");
intendedToolPickAxe.add("polished_blackstone_slab");
intendedToolPickAxe.add("polished_blackstone_brick_slab");
intendedToolPickAxe.add("lightly_weathered_cut_copper_slab");
intendedToolPickAxe.add("semi_weathered_cut_copper_slab");
intendedToolPickAxe.add("waxed_semi_weathered_cut_copper_slab");
intendedToolPickAxe.add("weathered_cut_copper_slab");
intendedToolPickAxe.add("waxed_cut_copper_slab");
intendedToolPickAxe.add("waxed_lightly_weathered_cut_copper_slab");
//stairs (not all of these exist, just copied the above list and replaced slab with stairs)
intendedToolPickAxe.add("petrified_oak_stairs");
intendedToolPickAxe.add("stone_stairs");
intendedToolPickAxe.add("smooth_stone_stairs");
intendedToolPickAxe.add("cobblestone_stairs");
intendedToolPickAxe.add("mossy_cobblestone_stairs");
intendedToolPickAxe.add("stone_brick_stairs");
intendedToolPickAxe.add("mossy_stone_brick_stairs");
intendedToolPickAxe.add("andesite_stairs");
intendedToolPickAxe.add("polished_andesite_stairs");
intendedToolPickAxe.add("diorite_stairs");
intendedToolPickAxe.add("polished_diorite_stairs");
intendedToolPickAxe.add("granite_stairs");
intendedToolPickAxe.add("polished_granite_stairs");
intendedToolPickAxe.add("sandstone_stairs");
intendedToolPickAxe.add("cut_sandstone_stairs");
intendedToolPickAxe.add("smooth_sandstone_stairs");
intendedToolPickAxe.add("red_sandstone_stairs");
intendedToolPickAxe.add("cut_red_sandstone_stairs");
intendedToolPickAxe.add("smooth_red_sandstone_stairs");
intendedToolPickAxe.add("brick_stairs");
intendedToolPickAxe.add("prismarine_brick_stairs");
intendedToolPickAxe.add("dark_prismarine_stairs");
intendedToolPickAxe.add("nether_brick_stairs");
intendedToolPickAxe.add("red_netherbrick_stairs");
intendedToolPickAxe.add("quartz_stairs");
intendedToolPickAxe.add("smooth_quartz_stairs");
intendedToolPickAxe.add("purpur_stairs");
intendedToolPickAxe.add("end_stone_brick_stairs");
intendedToolPickAxe.add("blackstone_stairs");
intendedToolPickAxe.add("polished_blackstone_stairs");
intendedToolPickAxe.add("polished_blackstone_brick_stairs");
intendedToolPickAxe.add("lightly_weathered_cut_copper_stairs");
intendedToolPickAxe.add("semi_weathered_cut_copper_stairs");
intendedToolPickAxe.add("waxed_semi_weathered_cut_copper_stairs");
intendedToolPickAxe.add("weathered_cut_copper_stairs");
intendedToolPickAxe.add("waxed_cut_copper_stairs");
intendedToolPickAxe.add("waxed_lightly_weathered_cut_copper_stairs");
}
private void fillArmors() { private void fillArmors() {
fillLeatherArmorWhiteList(); fillLeatherArmorWhiteList();
fillIronArmorWhiteList(); fillIronArmorWhiteList();
@ -1078,6 +1267,14 @@ public class MaterialMapStore {
toolBlackList.add("respawn_anchor"); toolBlackList.add("respawn_anchor");
} }
public boolean isIntendedToolPickaxe(Material material) {
return intendedToolPickAxe.contains(material.getKey().getKey());
}
public boolean isIntendedToolPickaxe(String string) {
return intendedToolPickAxe.contains(string);
}
public @NotNull HashSet<String> getNetheriteArmor() { public @NotNull HashSet<String> getNetheriteArmor() {
return netheriteArmor; return netheriteArmor;
} }

View File

@ -0,0 +1,316 @@
package com.gmail.nossr50.util;
import com.gmail.nossr50.datatypes.skills.subskills.taming.CallOfTheWildType;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.skills.taming.TrackedTamingEntity;
import com.gmail.nossr50.util.player.NotificationManager;
import com.gmail.nossr50.util.skills.ParticleEffectUtils;
import com.gmail.nossr50.util.text.StringUtils;
import com.google.common.collect.ImmutableSet;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class TransientEntityTracker {
//These two are updated in step with each other
private final @NotNull HashMap<UUID, HashMap<CallOfTheWildType, HashSet<TrackedTamingEntity>>> perPlayerTransientEntityMap;
private final @NotNull HashSet<LivingEntity> chunkLookupCache;
public TransientEntityTracker() {
perPlayerTransientEntityMap = new HashMap<>();
chunkLookupCache = new HashSet<>();
}
public synchronized @NotNull HashSet<LivingEntity> getChunkLookupCache() {
return chunkLookupCache;
}
public synchronized @NotNull HashMap<UUID, HashMap<CallOfTheWildType, HashSet<TrackedTamingEntity>>> getPerPlayerTransientEntityMap() {
return perPlayerTransientEntityMap;
}
public synchronized void initPlayer(@NotNull Player player) {
if (!isPlayerRegistered(player.getUniqueId())) {
registerPlayer(player.getUniqueId());
}
}
/**
* Removes a player from the tracker
*
* @param playerUUID target player
*/
public synchronized void cleanupPlayer(@NotNull UUID playerUUID) {
cleanPlayer(null, playerUUID);
}
/**
* Removes a player from the tracker
*
* @param player target player
*/
public synchronized void cleanupPlayer(@NotNull Player player) {
cleanPlayer(player, player.getUniqueId());
}
/**
* Removes a player from the tracker
*
* @param player target player
* @param playerUUID target player UUID
*/
private void cleanPlayer(@Nullable Player player, @NotNull UUID playerUUID) {
cleanupAllSummons(player, player.getUniqueId());
removePlayerFromMap(playerUUID);
}
private void removePlayerFromMap(@NotNull UUID playerUUID) {
getPerPlayerTransientEntityMap().remove(playerUUID);
}
/**
* Checks if a player has already been registered
* Being registered constitutes having necessary values initialized in our per-player map
*
* @param playerUUID target player
* @return true if the player is registered
*/
private synchronized boolean isPlayerRegistered(@NotNull UUID playerUUID) {
return getPerPlayerTransientEntityMap().get(playerUUID) != null;
}
/**
* Register a player to our tracker, which initializes the necessary values in our per-player map
*
* @param playerUUID player to register
*/
private synchronized void registerPlayer(@NotNull UUID playerUUID) {
getPerPlayerTransientEntityMap().put(playerUUID, new HashMap<CallOfTheWildType, HashSet<TrackedTamingEntity>>());
for(CallOfTheWildType callOfTheWildType : CallOfTheWildType.values()) {
getPerPlayerTransientEntityMap().get(playerUUID).put(callOfTheWildType, new HashSet<>());
}
}
/**
* Get the tracked transient entities map for a specific player
*
* @param playerUUID the target uuid of the player
* @return the tracked entities map for the player, null if the player isn't registered
*/
public synchronized @Nullable HashMap<CallOfTheWildType, HashSet<TrackedTamingEntity>> getPlayerTrackedEntityMap(@NotNull UUID playerUUID) {
return getPerPlayerTransientEntityMap().get(playerUUID);
}
/**
* Registers an entity to a player
* This includes registration to our per-player map and our chunk lookup cache
*
* @param playerUUID target player's UUID
* @param trackedTamingEntity target entity
*/
public synchronized void registerEntity(@NotNull UUID playerUUID, @NotNull TrackedTamingEntity trackedTamingEntity) {
//Add to map entry
getTrackedEntities(playerUUID, trackedTamingEntity.getCallOfTheWildType()).add(trackedTamingEntity);
//Add to cache for chunk lookups
addToChunkLookupCache(trackedTamingEntity);
}
/**
* Checks if a living entity is a summon
*
* @param livingEntity target livinig entity
* @return true if target living entity is a summon
*/
public synchronized boolean isTransientSummon(@NotNull LivingEntity livingEntity) {
return getChunkLookupCache().contains(livingEntity);
}
/**
* Get the tracked taming entities for a player
* If the player isn't registered this will return null
*
* @param playerUUID the target uuid of the player
* @param callOfTheWildType target type
* @return the set of tracked entities for the player, null if the player isn't registered, the set can be empty
*/
private synchronized @Nullable HashSet<TrackedTamingEntity> getTrackedEntities(@NotNull UUID playerUUID, @NotNull CallOfTheWildType callOfTheWildType) {
HashMap<CallOfTheWildType, HashSet<TrackedTamingEntity>> playerEntityMap = getPlayerTrackedEntityMap(playerUUID);
if(playerEntityMap == null)
return null;
return playerEntityMap.get(callOfTheWildType);
}
/**
* Adds an entity to our chunk lookup cache
*
* @param trackedTamingEntity target tracked taming entity
*/
private synchronized void addToChunkLookupCache(@NotNull TrackedTamingEntity trackedTamingEntity) {
getChunkLookupCache().add(trackedTamingEntity.getLivingEntity());
}
/**
* Removes an entity from our tracker
* This includes removal from our per-player map and our chunk lookup cache
*
* @param livingEntity target entity
*/
private void unregisterEntity(@NotNull LivingEntity livingEntity) {
chunkLookupCacheCleanup(livingEntity);
perPlayerTransientMapCleanup(livingEntity);
}
/**
* Removes an entity from our chunk lookup cache
*
* @param livingEntity target entity
*/
private void chunkLookupCacheCleanup(@NotNull LivingEntity livingEntity) {
getChunkLookupCache().remove(livingEntity);
}
/**
* Clean a living entity from our tracker
* Iterates over all players and their registered entities
* Doesn't do any kind of failure checking, if it doesn't find any player with a registered entity nothing bad happens or is reported
* However it should never happen like that, so maybe we could consider adding some failure to execute checking in the future
*
* @param livingEntity
*/
private void perPlayerTransientMapCleanup(@NotNull LivingEntity livingEntity) {
for(UUID uuid : getPerPlayerTransientEntityMap().keySet()) {
for(CallOfTheWildType callOfTheWildType : CallOfTheWildType.values()) {
HashSet<TrackedTamingEntity> trackedEntities = getTrackedEntities(uuid, callOfTheWildType);
if(trackedEntities == null)
continue;
Iterator<TrackedTamingEntity> iterator = trackedEntities.iterator();
while (iterator.hasNext()) {
if(iterator.next().getLivingEntity().equals(livingEntity)) {
iterator.remove();
return;
}
}
}
}
}
/**
* Get all transient entities that exist in a specific chunk
*
* @param chunk the chunk to match
* @return a list of transient entities that are located in the provided chunk
*/
public synchronized @NotNull List<LivingEntity> getAllTransientEntitiesInChunk(@NotNull Chunk chunk) {
ArrayList<LivingEntity> matchingEntities = new ArrayList<>();
for(LivingEntity livingEntity : getChunkLookupCache()) {
if(livingEntity.getLocation().getChunk().equals(chunk)) {
matchingEntities.add(livingEntity);
}
}
return matchingEntities;
}
/**
* Get the amount of a summon currently active for a player
*
* @param playerUUID target player
* @param callOfTheWildType summon type
* @return the amount of summons currently active for player of target type
*/
public synchronized int getAmountCurrentlySummoned(@NotNull UUID playerUUID, @NotNull CallOfTheWildType callOfTheWildType) {
HashSet<TrackedTamingEntity> trackedEntities = getTrackedEntities(playerUUID, callOfTheWildType);
if(trackedEntities == null)
return 0;
return trackedEntities.size();
}
/**
* Kills a summon and removes its metadata
* Then it removes it from the tracker / chunk lookup cache
*
* @param livingEntity entity to remove
* @param player associated player
*/
public synchronized void removeSummon(@NotNull LivingEntity livingEntity, @Nullable Player player, boolean timeExpired) {
//Kill the summon & remove it
if(livingEntity.isValid()) {
livingEntity.setHealth(0); //Should trigger entity death events
livingEntity.remove();
Location location = livingEntity.getLocation();
if (location.getWorld() != null) {
location.getWorld().playSound(location, Sound.BLOCK_FIRE_EXTINGUISH, 0.8F, 0.8F);
ParticleEffectUtils.playCallOfTheWildEffect(livingEntity);
}
//Inform player of summon death
if(player != null && player.isOnline()) {
if(timeExpired) {
NotificationManager.sendPlayerInformationChatOnly(player, "Taming.Summon.COTW.TimeExpired", StringUtils.getPrettyEntityTypeString(livingEntity.getType()));
} else {
NotificationManager.sendPlayerInformationChatOnly(player, "Taming.Summon.COTW.Removed", StringUtils.getPrettyEntityTypeString(livingEntity.getType()));
}
}
}
//Remove our metadata
mcMMO.getCompatibilityManager().getPersistentDataLayer().removeMobFlags(livingEntity);
//Clean from trackers
unregisterEntity(livingEntity);
}
/**
* Remove all tracked entities from existence if they currently exist
* Clear the tracked entity lists afterwards
*
* @deprecated use {@link #cleanupAllSummons(Player, UUID)} instead
*/
@Deprecated
private void cleanupAllSummons(@NotNull UUID playerUUID) {
cleanupAllSummons(Bukkit.getPlayer(playerUUID), playerUUID);
}
/**
* Kills and cleans up all data related to all summoned entities for a player
*
* @param player used to send messages, can be null
* @param playerUUID used to grab associated data, cannot be null
*/
private void cleanupAllSummons(@Nullable Player player, @NotNull UUID playerUUID) {
for(CallOfTheWildType callOfTheWildType : CallOfTheWildType.values()) {
HashSet<TrackedTamingEntity> trackedEntities = getTrackedEntities(playerUUID, callOfTheWildType);
if(trackedEntities == null) {
continue;
}
ImmutableSet<TrackedTamingEntity> immutableSet = ImmutableSet.copyOf(trackedEntities);
for(TrackedTamingEntity trackedTamingEntity : immutableSet) {
//Remove from existence
removeSummon(trackedTamingEntity.getLivingEntity(), player, false);
}
}
}
}

View File

@ -2,32 +2,37 @@ package com.gmail.nossr50.util.blockmeta;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*; import java.io.*;
import java.util.BitSet; import java.util.BitSet;
import java.util.UUID; import java.util.UUID;
public class BitSetChunkStore implements ChunkStore, Serializable { public class BitSetChunkStore implements ChunkStore {
private static final long serialVersionUID = -1L;
transient private boolean dirty = false;
// Bitset store conforms to a "bottom-up" bit ordering consisting of a stack of {worldHeight} Y planes, each Y plane consists of 16 Z rows of 16 X bits.
private BitSet store;
private static final int CURRENT_VERSION = 8; private static final int CURRENT_VERSION = 8;
private static final int MAGIC_NUMBER = 0xEA5EDEBB; private static final int MAGIC_NUMBER = 0xEA5EDEBB;
private int cx;
private int cz;
private int worldHeight;
private UUID worldUid;
public BitSetChunkStore(World world, int cx, int cz) { private final int cx;
this.cx = cx; private final int cz;
this.cz = cz; private final int worldHeight;
this.worldUid = world.getUID(); private final @NotNull UUID worldUid;
this.worldHeight = world.getMaxHeight(); // Bitset store conforms to a "bottom-up" bit ordering consisting of a stack of {worldHeight} Y planes, each Y plane consists of 16 Z rows of 16 X bits.
this.store = new BitSet(16 * 16 * worldHeight); private final @NotNull BitSet store;
private transient boolean dirty = false;
public BitSetChunkStore(@NotNull World world, int cx, int cz) {
this(world.getUID(), world.getMaxHeight(), cx, cz);
} }
private BitSetChunkStore() {} private BitSetChunkStore(@NotNull UUID worldUid, int worldHeight, int cx, int cz) {
this.cx = cx;
this.cz = cz;
this.worldUid = worldUid;
this.worldHeight = worldHeight;
this.store = new BitSet(16 * 16 * worldHeight);
}
@Override @Override
public boolean isDirty() { public boolean isDirty() {
@ -50,7 +55,7 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
} }
@Override @Override
public UUID getWorldId() { public @NotNull UUID getWorldId() {
return worldUid; return worldUid;
} }
@ -81,61 +86,27 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
} }
private int coordToIndex(int x, int y, int z) { private int coordToIndex(int x, int y, int z) {
return coordToIndex(x, y, z, worldHeight);
}
private static int coordToIndex(int x, int y, int z, int worldHeight) {
if (x < 0 || x >= 16 || y < 0 || y >= worldHeight || z < 0 || z >= 16) if (x < 0 || x >= 16 || y < 0 || y >= worldHeight || z < 0 || z >= 16)
throw new IndexOutOfBoundsException(); throw new IndexOutOfBoundsException(String.format("x: %d y: %d z: %d World Height: %d", x, y, z, worldHeight));
return (z * 16 + x) + (256 * y); return (z * 16 + x) + (256 * y);
} }
private void fixWorldHeight() { private static int getWorldHeight(@NotNull UUID worldUid, int storedWorldHeight)
{
World world = Bukkit.getWorld(worldUid); World world = Bukkit.getWorld(worldUid);
// Not sure how this case could come up, but might as well handle it gracefully. Loading a chunkstore for an unloaded world? // Not sure how this case could come up, but might as well handle it gracefully. Loading a chunkstore for an unloaded world?
if (world == null) if (world == null)
return; return storedWorldHeight;
// Lop off any extra data if the world height has shrunk return world.getMaxHeight();
int currentWorldHeight = world.getMaxHeight();
if (currentWorldHeight < worldHeight)
{
store.clear(coordToIndex(16, currentWorldHeight, 16), store.length());
worldHeight = currentWorldHeight;
dirty = true;
}
// If the world height has grown, update the worldHeight variable, but don't bother marking it dirty as unless something else changes we don't need to force a file write;
else if (currentWorldHeight > worldHeight)
worldHeight = currentWorldHeight;
} }
@Deprecated private void serialize(@NotNull DataOutputStream out) throws IOException {
private void writeObject(ObjectOutputStream out) throws IOException {
throw new UnsupportedOperationException("Serializable support should only be used for legacy deserialization");
}
@Deprecated
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.readInt(); // Magic number
in.readInt(); // Format version
long lsb = in.readLong();
long msb = in.readLong();
worldUid = new UUID(msb, lsb);
cx = in.readInt();
cz = in.readInt();
boolean[][][] oldStore = (boolean[][][]) in.readObject();
worldHeight = oldStore[0][0].length;
store = new BitSet(16 * 16 * worldHeight / 8);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < worldHeight; y++) {
store.set(coordToIndex(x, y, z), oldStore[x][z][y]);
}
}
}
dirty = true;
fixWorldHeight();
}
private void serialize(DataOutputStream out) throws IOException {
out.writeInt(MAGIC_NUMBER); out.writeInt(MAGIC_NUMBER);
out.writeInt(CURRENT_VERSION); out.writeInt(CURRENT_VERSION);
@ -153,7 +124,7 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
dirty = false; dirty = false;
} }
private static BitSetChunkStore deserialize(DataInputStream in) throws IOException { private static @NotNull BitSetChunkStore deserialize(@NotNull DataInputStream in) throws IOException {
int magic = in.readInt(); int magic = in.readInt();
// Can be used to determine the format of the file // Can be used to determine the format of the file
int fileVersionNumber = in.readInt(); int fileVersionNumber = in.readInt();
@ -161,28 +132,36 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION) if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION)
throw new IOException(); throw new IOException();
BitSetChunkStore chunkStore = new BitSetChunkStore();
long lsb = in.readLong(); long lsb = in.readLong();
long msb = in.readLong(); long msb = in.readLong();
chunkStore.worldUid = new UUID(msb, lsb); UUID worldUid = new UUID(msb, lsb);
chunkStore.cx = in.readInt(); int cx = in.readInt();
chunkStore.cz = in.readInt(); int cz = in.readInt();
chunkStore.worldHeight = in.readInt(); int worldHeight = in.readInt();
byte[] temp = new byte[in.readInt()]; byte[] temp = new byte[in.readInt()];
in.readFully(temp); in.readFully(temp);
chunkStore.store = BitSet.valueOf(temp); BitSet stored = BitSet.valueOf(temp);
int currentWorldHeight = getWorldHeight(worldUid, worldHeight);
boolean worldHeightShrunk = currentWorldHeight < worldHeight;
// Lop off extra data if world height has shrunk
if (worldHeightShrunk)
stored.clear(coordToIndex(16, currentWorldHeight, 16, worldHeight), stored.length());
BitSetChunkStore chunkStore = new BitSetChunkStore(worldUid, currentWorldHeight, cx, cz);
chunkStore.store.or(stored);
chunkStore.dirty = worldHeightShrunk; // In the expanded case there is no reason to re-write it unless the data changes
chunkStore.fixWorldHeight();
return chunkStore; return chunkStore;
} }
public static class Serialization { public static class Serialization {
public static final short STREAM_MAGIC = (short)0xACDC; public static final short STREAM_MAGIC = (short)0xACDC; // Rock on
public static ChunkStore readChunkStore(DataInputStream inputStream) throws IOException { public static @Nullable ChunkStore readChunkStore(@NotNull DataInputStream inputStream) throws IOException {
if (inputStream.markSupported()) if (inputStream.markSupported())
inputStream.mark(2); inputStream.mark(2);
short magicNumber = inputStream.readShort(); short magicNumber = inputStream.readShort();
@ -196,7 +175,7 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
{ {
// Creates a new stream with the two magic number bytes and then the rest of the original stream... Java is so dumb. I just wanted to look at two bytes. // Creates a new stream with the two magic number bytes and then the rest of the original stream... Java is so dumb. I just wanted to look at two bytes.
PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream, 2); PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream, 2);
pushbackInputStream.unread((magicNumber >>> 0) & 0xFF); pushbackInputStream.unread((magicNumber) & 0xFF);
pushbackInputStream.unread((magicNumber >>> 8) & 0xFF); pushbackInputStream.unread((magicNumber >>> 8) & 0xFF);
inputStream = new DataInputStream(pushbackInputStream); inputStream = new DataInputStream(pushbackInputStream);
} }
@ -209,31 +188,85 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
throw new IOException("Bad Data Format"); throw new IOException("Bad Data Format");
} }
public static void writeChunkStore(DataOutputStream outputStream, ChunkStore chunkStore) throws IOException { public static void writeChunkStore(@NotNull DataOutputStream outputStream, @NotNull ChunkStore chunkStore) throws IOException {
if (!(chunkStore instanceof BitSetChunkStore)) if (!(chunkStore instanceof BitSetChunkStore))
throw new InvalidClassException("ChunkStore must be instance of BitSetChunkStore"); throw new InvalidClassException("ChunkStore must be instance of BitSetChunkStore");
outputStream.writeShort(STREAM_MAGIC); outputStream.writeShort(STREAM_MAGIC);
((BitSetChunkStore)chunkStore).serialize(outputStream); ((BitSetChunkStore)chunkStore).serialize(outputStream);
} }
// Handles loading the old serialized classes even though we have changed name/package // Handles loading the old serialized class
private static class LegacyDeserializationInputStream extends ObjectInputStream { private static class LegacyDeserializationInputStream extends ObjectInputStream {
public LegacyDeserializationInputStream(InputStream in) throws IOException { private static class LegacyChunkStoreDeserializer implements Serializable
{
private static final long serialVersionUID = -1L;
private int cx;
private int cz;
private int worldHeight;
private UUID worldUid;
private boolean[][][] store;
private LegacyChunkStoreDeserializer() {}
@Deprecated
private void writeObject(@NotNull ObjectOutputStream out) throws IOException {
throw new UnsupportedOperationException("You goofed.");
}
@Deprecated
private void readObject(@NotNull ObjectInputStream in) throws IOException, ClassNotFoundException {
in.readInt(); // Magic number
in.readInt(); // Format version
long lsb = in.readLong();
long msb = in.readLong();
worldUid = new UUID(msb, lsb);
cx = in.readInt();
cz = in.readInt();
store = (boolean[][][]) in.readObject();
worldHeight = store[0][0].length;
}
public @NotNull BitSetChunkStore convert()
{
int currentWorldHeight = getWorldHeight(worldUid, worldHeight);
BitSetChunkStore converted = new BitSetChunkStore(worldUid, currentWorldHeight, cx, cz);
// Read old data into new chunkstore
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < worldHeight && y < currentWorldHeight; y++) {
converted.store.set(converted.coordToIndex(x, y, z), store[x][z][y]);
}
}
}
// Mark dirty so it will be re-written in new format on close
converted.dirty = true;
return converted;
}
}
public LegacyDeserializationInputStream(@NotNull InputStream in) throws IOException {
super(in); super(in);
enableResolveObject(true); enableResolveObject(true);
} }
@Override @Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { protected @NotNull ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass read = super.readClassDescriptor(); ObjectStreamClass read = super.readClassDescriptor();
if (read.getName().contentEquals("com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore")) if (read.getName().contentEquals("com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore"))
return ObjectStreamClass.lookup(BitSetChunkStore.class); return ObjectStreamClass.lookup(LegacyChunkStoreDeserializer.class);
return read; return read;
} }
public ChunkStore readLegacyChunkStore(){ public @Nullable ChunkStore readLegacyChunkStore(){
try { try {
return (ChunkStore) readObject(); LegacyChunkStoreDeserializer deserializer = (LegacyChunkStoreDeserializer)readObject();
return deserializer.convert();
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
return null; return null;
} }

View File

@ -1,126 +1,10 @@
package com.gmail.nossr50.util.blockmeta; package com.gmail.nossr50.util.blockmeta;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.jetbrains.annotations.NotNull;
import org.bukkit.block.BlockState;
public interface ChunkManager { public interface ChunkManager extends UserBlockTracker {
void closeAll(); void closeAll();
void chunkUnloaded(int cx, int cz, @NotNull World world);
/** void unloadWorld(@NotNull World world);
* Saves a given Chunk's Chunklet data
*
* @param cx Chunk X coordinate that is to be saved
* @param cz Chunk Z coordinate that is to be saved
* @param world World that the Chunk is in
*/
void saveChunk(int cx, int cz, World world);
/**
* Informs the ChunkletManager a chunk is unloaded
*
* @param cx Chunk X coordinate that is unloaded
* @param cz Chunk Z coordinate that is unloaded
* @param world World that the chunk was unloaded in
*/
void chunkUnloaded(int cx, int cz, World world);
/**
* Save all ChunkletStores related to the given world
*
* @param world World to save
*/
void saveWorld(World world);
/**
* Unload all ChunkletStores from memory related to the given world after saving them
*
* @param world World to unload
*/
void unloadWorld(World world);
/**
* Save all ChunkletStores
*/
void saveAll();
/**
* Check to see if a given location is set to true
*
* @param x X coordinate to check
* @param y Y coordinate to check
* @param z Z coordinate to check
* @param world World to check in
* @return true if the given location is set to true, false if otherwise
*/
boolean isTrue(int x, int y, int z, World world);
/**
* Check to see if a given block location is set to true
*
* @param block Block location to check
* @return true if the given block location is set to true, false if otherwise
*/
boolean isTrue(Block block);
/**
* Check to see if a given BlockState location is set to true
*
* @param blockState BlockState to check
* @return true if the given BlockState location is set to true, false if otherwise
*/
boolean isTrue(BlockState blockState);
/**
* Set a given location to true, should create stores as necessary if the location does not exist
*
* @param x X coordinate to set
* @param y Y coordinate to set
* @param z Z coordinate to set
* @param world World to set in
*/
void setTrue(int x, int y, int z, World world);
/**
* Set a given block location to true, should create stores as necessary if the location does not exist
*
* @param block Block location to set
*/
void setTrue(Block block);
/**
* Set a given BlockState location to true, should create stores as necessary if the location does not exist
*
* @param blockState BlockState location to set
*/
void setTrue(BlockState blockState);
/**
* Set a given location to false, should not create stores if one does not exist for the given location
*
* @param x X coordinate to set
* @param y Y coordinate to set
* @param z Z coordinate to set
* @param world World to set in
*/
void setFalse(int x, int y, int z, World world);
/**
* Set a given block location to false, should not create stores if one does not exist for the given location
*
* @param block Block location to set
*/
void setFalse(Block block);
/**
* Set a given BlockState location to false, should not create stores if one does not exist for the given location
*
* @param blockState BlockState location to set
*/
void setFalse(BlockState blockState);
/**
* Delete any ChunkletStores that are empty
*/
void cleanUp();
} }

View File

@ -1,9 +1,10 @@
package com.gmail.nossr50.util.blockmeta; package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.config.HiddenConfig; import com.gmail.nossr50.config.HiddenConfig;
import org.jetbrains.annotations.NotNull;
public class ChunkManagerFactory { public class ChunkManagerFactory {
public static ChunkManager getChunkManager() { public static @NotNull ChunkManager getChunkManager() {
HiddenConfig hConfig = HiddenConfig.getInstance(); HiddenConfig hConfig = HiddenConfig.getInstance();
if (hConfig.getChunkletsEnabled()) { if (hConfig.getChunkletsEnabled()) {

View File

@ -1,6 +1,6 @@
package com.gmail.nossr50.util.blockmeta; package com.gmail.nossr50.util.blockmeta;
import org.bukkit.World; import org.jetbrains.annotations.NotNull;
import java.util.UUID; import java.util.UUID;
@ -36,7 +36,7 @@ public interface ChunkStore {
*/ */
int getChunkZ(); int getChunkZ();
UUID getWorldId(); @NotNull UUID getWorldId();
/** /**
* Checks the value at the given coordinates * Checks the value at the given coordinates

View File

@ -1,12 +1,16 @@
package com.gmail.nossr50.util.blockmeta; package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.mcMMO;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*; import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.*; import java.util.*;
public class HashChunkManager implements ChunkManager { public class HashChunkManager implements ChunkManager {
@ -21,7 +25,10 @@ public class HashChunkManager implements ChunkManager {
{ {
if (!chunkStore.isDirty()) if (!chunkStore.isDirty())
continue; continue;
writeChunkStore(Bukkit.getWorld(chunkStore.getWorldId()), chunkStore); World world = Bukkit.getWorld(chunkStore.getWorldId());
if (world == null)
continue; // Oh well
writeChunkStore(world, chunkStore);
} }
// Clear in memory chunks // Clear in memory chunks
chunkMap.clear(); chunkMap.clear();
@ -32,7 +39,7 @@ public class HashChunkManager implements ChunkManager {
regionMap.clear(); regionMap.clear();
} }
private synchronized ChunkStore readChunkStore(World world, int cx, int cz) throws IOException { private synchronized @Nullable ChunkStore readChunkStore(@NotNull World world, int cx, int cz) throws IOException {
McMMOSimpleRegionFile rf = getSimpleRegionFile(world, cx, cz, false); McMMOSimpleRegionFile rf = getSimpleRegionFile(world, cx, cz, false);
if (rf == null) if (rf == null)
return null; // If there is no region file, there can't be a chunk return null; // If there is no region file, there can't be a chunk
@ -43,7 +50,7 @@ public class HashChunkManager implements ChunkManager {
} }
} }
private synchronized void writeChunkStore(World world, ChunkStore data) { private synchronized void writeChunkStore(@NotNull World world, @NotNull ChunkStore data) {
if (!data.isDirty()) if (!data.isDirty())
return; // Don't save unchanged data return; // Don't save unchanged data
try { try {
@ -58,7 +65,7 @@ public class HashChunkManager implements ChunkManager {
} }
} }
private synchronized McMMOSimpleRegionFile getSimpleRegionFile(World world, int cx, int cz, boolean createIfAbsent) { private synchronized @Nullable McMMOSimpleRegionFile getSimpleRegionFile(@NotNull World world, int cx, int cz, boolean createIfAbsent) {
CoordinateKey regionKey = toRegionKey(world.getUID(), cx, cz); CoordinateKey regionKey = toRegionKey(world.getUID(), cx, cz);
return regionMap.computeIfAbsent(regionKey, k -> { return regionMap.computeIfAbsent(regionKey, k -> {
@ -73,7 +80,7 @@ public class HashChunkManager implements ChunkManager {
}); });
} }
private ChunkStore loadChunk(int cx, int cz, World world) { private @Nullable ChunkStore loadChunk(int cx, int cz, @NotNull World world) {
try { try {
return readChunkStore(world, cx, cz); return readChunkStore(world, cx, cz);
} }
@ -82,7 +89,7 @@ public class HashChunkManager implements ChunkManager {
return null; return null;
} }
private void unloadChunk(int cx, int cz, World world) { private void unloadChunk(int cx, int cz, @NotNull World world) {
CoordinateKey chunkKey = toChunkKey(world.getUID(), cx, cz); CoordinateKey chunkKey = toChunkKey(world.getUID(), cx, cz);
ChunkStore chunkStore = chunkMap.remove(chunkKey); // Remove from chunk map ChunkStore chunkStore = chunkMap.remove(chunkKey); // Remove from chunk map
if (chunkStore == null) if (chunkStore == null)
@ -102,56 +109,12 @@ public class HashChunkManager implements ChunkManager {
} }
@Override @Override
public synchronized void saveChunk(int cx, int cz, World world) { public synchronized void chunkUnloaded(int cx, int cz, @NotNull World world) {
if (world == null)
return;
CoordinateKey chunkKey = toChunkKey(world.getUID(), cx, cz);
ChunkStore out = chunkMap.get(chunkKey);
if (out == null)
return;
if (!out.isDirty())
return;
writeChunkStore(world, out);
}
@Override
public synchronized void chunkUnloaded(int cx, int cz, World world) {
if (world == null)
return;
unloadChunk(cx, cz, world); unloadChunk(cx, cz, world);
} }
@Override @Override
public synchronized void saveWorld(World world) { public synchronized void unloadWorld(@NotNull World world) {
if (world == null)
return;
UUID wID = world.getUID();
// Save all teh chunks
for (ChunkStore chunkStore : chunkMap.values()) {
if (!chunkStore.isDirty())
continue;
if (!wID.equals(chunkStore.getWorldId()))
continue;
try {
writeChunkStore(world, chunkStore);
}
catch (Exception ignore) { }
}
}
@Override
public synchronized void unloadWorld(World world) {
if (world == null)
return;
UUID wID = world.getUID(); UUID wID = world.getUID();
// Save and remove all the chunks // Save and remove all the chunks
@ -177,18 +140,7 @@ public class HashChunkManager implements ChunkManager {
} }
} }
@Override private synchronized boolean isTrue(int x, int y, int z, @NotNull World world) {
public synchronized void saveAll() {
for (World world : mcMMO.p.getServer().getWorlds()) {
saveWorld(world);
}
}
@Override
public synchronized boolean isTrue(int x, int y, int z, World world) {
if (world == null)
return false;
CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z); CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z);
// Get chunk, load from file if necessary // Get chunk, load from file if necessary
@ -214,67 +166,36 @@ public class HashChunkManager implements ChunkManager {
} }
@Override @Override
public synchronized boolean isTrue(Block block) { public synchronized boolean isTrue(@NotNull Block block) {
if (block == null)
return false;
return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld()); return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
} }
@Override @Override
public synchronized boolean isTrue(BlockState blockState) { public synchronized boolean isTrue(@NotNull BlockState blockState) {
if (blockState == null)
return false;
return isTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld()); return isTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
} }
@Override @Override
public synchronized void setTrue(int x, int y, int z, World world) { public synchronized void setTrue(@NotNull Block block) {
set(x, y, z, world, true); set(block.getX(), block.getY(), block.getZ(), block.getWorld(), true);
} }
@Override @Override
public synchronized void setTrue(Block block) { public synchronized void setTrue(@NotNull BlockState blockState) {
if (block == null) set(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld(), true);
return;
setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
} }
@Override @Override
public synchronized void setTrue(BlockState blockState) { public synchronized void setFalse(@NotNull Block block) {
if (blockState == null) set(block.getX(), block.getY(), block.getZ(), block.getWorld(), false);
return;
setTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
} }
@Override @Override
public synchronized void setFalse(int x, int y, int z, World world) { public synchronized void setFalse(@NotNull BlockState blockState) {
set(x, y, z, world, false); set(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld(), false);
} }
@Override private synchronized void set(int x, int y, int z, @NotNull World world, boolean value){
public synchronized void setFalse(Block block) {
if (block == null)
return;
setFalse(block.getX(), block.getY(), block.getZ(), block.getWorld());
}
@Override
public synchronized void setFalse(BlockState blockState) {
if (blockState == null)
return;
setFalse(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
}
public synchronized void set(int x, int y, int z, World world, boolean value){
if (world == null)
return;
CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z); CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z);
// Get/Load/Create chunkstore // Get/Load/Create chunkstore
@ -307,15 +228,15 @@ public class HashChunkManager implements ChunkManager {
cStore.set(ix, y, iz, value); cStore.set(ix, y, iz, value);
} }
private CoordinateKey blockCoordinateToChunkKey(UUID worldUid, int x, int y, int z) { private CoordinateKey blockCoordinateToChunkKey(@NotNull UUID worldUid, int x, int y, int z) {
return toChunkKey(worldUid, x >> 4, z >> 4); return toChunkKey(worldUid, x >> 4, z >> 4);
} }
private CoordinateKey toChunkKey(UUID worldUid, int cx, int cz){ private CoordinateKey toChunkKey(@NotNull UUID worldUid, int cx, int cz){
return new CoordinateKey(worldUid, cx, cz); return new CoordinateKey(worldUid, cx, cz);
} }
private CoordinateKey toRegionKey(UUID worldUid, int cx, int cz) { private CoordinateKey toRegionKey(@NotNull UUID worldUid, int cx, int cz) {
// Compute region index (32x32 chunk regions) // Compute region index (32x32 chunk regions)
int rx = cx >> 5; int rx = cx >> 5;
int rz = cz >> 5; int rz = cz >> 5;
@ -323,11 +244,11 @@ public class HashChunkManager implements ChunkManager {
} }
private static final class CoordinateKey { private static final class CoordinateKey {
public final UUID worldID; public final @NotNull UUID worldID;
public final int x; public final int x;
public final int z; public final int z;
private CoordinateKey(UUID worldID, int x, int z) { private CoordinateKey(@NotNull UUID worldID, int x, int z) {
this.worldID = worldID; this.worldID = worldID;
this.x = x; this.x = x;
this.z = z; this.z = z;
@ -348,7 +269,4 @@ public class HashChunkManager implements ChunkManager {
return Objects.hash(worldID, x, z); return Objects.hash(worldID, x, z);
} }
} }
@Override
public synchronized void cleanUp() {}
} }

View File

@ -19,6 +19,9 @@
*/ */
package com.gmail.nossr50.util.blockmeta; package com.gmail.nossr50.util.blockmeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*; import java.io.*;
import java.util.BitSet; import java.util.BitSet;
import java.util.zip.DeflaterOutputStream; import java.util.zip.DeflaterOutputStream;
@ -54,7 +57,7 @@ public class McMMOSimpleRegionFile {
private final int segmentMask; private final int segmentMask;
// File location // File location
private final File parent; private final @NotNull File parent;
// File access // File access
private final RandomAccessFile file; private final RandomAccessFile file;
@ -62,7 +65,7 @@ public class McMMOSimpleRegionFile {
private final int rx; private final int rx;
private final int rz; private final int rz;
public McMMOSimpleRegionFile(File f, int rx, int rz) { public McMMOSimpleRegionFile(@NotNull File f, int rx, int rz) {
this.rx = rx; this.rx = rx;
this.rz = rz; this.rz = rz;
this.parent = f; this.parent = f;
@ -104,7 +107,7 @@ public class McMMOSimpleRegionFile {
} }
} }
public synchronized DataOutputStream getOutputStream(int x, int z) { public synchronized @NotNull DataOutputStream getOutputStream(int x, int z) {
int index = getChunkIndex(x, z); // Get chunk index int index = getChunkIndex(x, z); // Get chunk index
return new DataOutputStream(new DeflaterOutputStream(new McMMOSimpleChunkBuffer(this, index))); return new DataOutputStream(new DeflaterOutputStream(new McMMOSimpleChunkBuffer(this, index)));
} }
@ -144,7 +147,7 @@ public class McMMOSimpleRegionFile {
file.writeInt(chunkNumBytes[index]); file.writeInt(chunkNumBytes[index]);
} }
public synchronized DataInputStream getInputStream(int x, int z) throws IOException { public synchronized @Nullable DataInputStream getInputStream(int x, int z) throws IOException {
int index = getChunkIndex(x, z); // Get chunk index int index = getChunkIndex(x, z); // Get chunk index
int byteLength = chunkNumBytes[index]; // Get byte length of data int byteLength = chunkNumBytes[index]; // Get byte length of data

View File

@ -3,6 +3,7 @@ package com.gmail.nossr50.util.blockmeta;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.jetbrains.annotations.NotNull;
public class NullChunkManager implements ChunkManager { public class NullChunkManager implements ChunkManager {
@ -10,53 +11,30 @@ public class NullChunkManager implements ChunkManager {
public void closeAll() {} public void closeAll() {}
@Override @Override
public void saveChunk(int cx, int cz, World world) {} public void chunkUnloaded(int cx, int cz, @NotNull World world) {}
@Override @Override
public void chunkUnloaded(int cx, int cz, World world) {} public void unloadWorld(@NotNull World world) {}
@Override @Override
public void saveWorld(World world) {} public boolean isTrue(@NotNull Block block) {
@Override
public void unloadWorld(World world) {}
@Override
public void saveAll() {}
@Override
public boolean isTrue(int x, int y, int z, World world) {
return false; return false;
} }
@Override @Override
public boolean isTrue(Block block) { public boolean isTrue(@NotNull BlockState blockState) {
return false; return false;
} }
@Override @Override
public boolean isTrue(BlockState blockState) { public void setTrue(@NotNull Block block) {}
return false;
}
@Override @Override
public void setTrue(int x, int y, int z, World world) {} public void setTrue(@NotNull BlockState blockState) {}
@Override @Override
public void setTrue(Block block) {} public void setFalse(@NotNull Block block) {}
@Override @Override
public void setTrue(BlockState blockState) {} public void setFalse(@NotNull BlockState blockState) {}
@Override
public void setFalse(int x, int y, int z, World world) {}
@Override
public void setFalse(Block block) {}
@Override
public void setFalse(BlockState blockState) {}
@Override
public void cleanUp() {}
} }

View File

@ -0,0 +1,56 @@
package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.mcMMO;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.jetbrains.annotations.NotNull;
/**
* Contains blockstore methods that are safe for external plugins to access.
* An instance can be retrieved via {@link mcMMO#getPlaceStore() mcMMO.getPlaceStore()}
*/
public interface UserBlockTracker {
/**
* Check to see if a given block location is set to true
*
* @param block Block location to check
* @return true if the given block location is set to true, false if otherwise
*/
boolean isTrue(@NotNull Block block);
/**
* Check to see if a given BlockState location is set to true
*
* @param blockState BlockState to check
* @return true if the given BlockState location is set to true, false if otherwise
*/
boolean isTrue(@NotNull BlockState blockState);
/**
* Set a given block location to true
*
* @param block Block location to set
*/
void setTrue(@NotNull Block block);
/**
* Set a given BlockState location to true
*
* @param blockState BlockState location to set
*/
void setTrue(@NotNull BlockState blockState);
/**
* Set a given block location to false
*
* @param block Block location to set
*/
void setFalse(@NotNull Block block);
/**
* Set a given BlockState location to false
*
* @param blockState BlockState location to set
*/
void setFalse(@NotNull BlockState blockState);
}

View File

@ -2,8 +2,12 @@ package com.gmail.nossr50.util.skills;
import com.gmail.nossr50.config.experience.ExperienceConfig; import com.gmail.nossr50.config.experience.ExperienceConfig;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import com.gmail.nossr50.events.skills.SkillActivationPerkEvent;
import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.Permissions;
import com.gmail.nossr50.util.player.UserManager;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -43,7 +47,9 @@ public final class PerksUtils {
ticks += 4; ticks += 4;
} }
return ticks; final SkillActivationPerkEvent skillActivationPerkEvent = new SkillActivationPerkEvent(player, ticks, maxTicks);
Bukkit.getPluginManager().callEvent(skillActivationPerkEvent);
return skillActivationPerkEvent.getTicks();
} }
public static float handleXpPerks(Player player, float xp, PrimarySkillType skill) { public static float handleXpPerks(Player player, float xp, PrimarySkillType skill) {

View File

@ -207,50 +207,6 @@ Fishing:
Amount: 1 Amount: 1
XP: 200 XP: 200
Rarity: LEGENDARY Rarity: LEGENDARY
MUSIC_DISC_BLOCKS:
Amount: 1
XP: 200
Rarity: UNCOMMON
MUSIC_DISC_CHIRP:
Amount: 1
XP: 200
Rarity: UNCOMMON
MUSIC_DISC_FAR:
Amount: 1
XP: 200
Rarity: RARE
MUSIC_DISC_MALL:
Amount: 1
XP: 200
Rarity: RARE
MUSIC_DISC_MELLOHI:
Amount: 1
XP: 200
Rarity: RARE
MUSIC_DISC_STAL:
Amount: 1
XP: 200
Rarity: EPIC
MUSIC_DISC_STRAD:
Amount: 1
XP: 200
Rarity: EPIC
MUSIC_DISC_WARD:
Amount: 1
XP: 200
Rarity: EPIC
MUSIC_DISC_11:
Amount: 1
XP: 200
Rarity: LEGENDARY
MUSIC_DISC_WAIT:
Amount: 1
XP: 200
Rarity: LEGENDARY
MUSIC_DISC_13:
Amount: 1
XP: 200
Rarity: MYTHIC
NETHERITE_SWORD: NETHERITE_SWORD:
Amount: 1 Amount: 1
XP: 200 XP: 200

View File

@ -90,7 +90,6 @@ Fishing.SubSkill.Shake.Description=Vyklepni p\u0159edm\u011bty z p\u0159\u00ed\u
Fishing.SubSkill.FishermansDiet.Name=Ryb\u00e1\u0159\u016fv apetit Fishing.SubSkill.FishermansDiet.Name=Ryb\u00e1\u0159\u016fv apetit
Fishing.SubSkill.FishermansDiet.Description=Zlep\u0161uje dopl\u0148ov\u00e1n\u00ed hladu z naryba\u0159en\u00fdch j\u00eddel Fishing.SubSkill.FishermansDiet.Description=Zlep\u0161uje dopl\u0148ov\u00e1n\u00ed hladu z naryba\u0159en\u00fdch j\u00eddel
Fishing.SubSkill.MasterAngler.Name=Mistr Ryb\u00e1\u0159 Fishing.SubSkill.MasterAngler.Name=Mistr Ryb\u00e1\u0159
Fishing.SubSkill.MasterAngler.Description=Zvy\u0161uje \u0161anci zah\u00e1knut\u00ed ryby p\u0159i ryba\u0159en\u00ed
Fishing.SubSkill.IceFishing.Name=Ryba\u0159en\u00ed v ledu Fishing.SubSkill.IceFishing.Name=Ryba\u0159en\u00ed v ledu
Fishing.SubSkill.IceFishing.Description=Umo\u017e\u0148uje v\u00e1m ryba\u0159it v ledov\u00fdch prost\u0159ed\u00edch Fishing.SubSkill.IceFishing.Description=Umo\u017e\u0148uje v\u00e1m ryba\u0159it v ledov\u00fdch prost\u0159ed\u00edch
Fishing.Chance.Raining=&9 De\u0161\u0165ov\u00fd bonus Fishing.Chance.Raining=&9 De\u0161\u0165ov\u00fd bonus

View File

@ -372,7 +372,6 @@ Fishing.SubSkill.IceFishing.Stat = Eisangeln
Fishing.SubSkill.MagicHunter.Description = Finde verzauberte Gegenst\u00E4nde Fishing.SubSkill.MagicHunter.Description = Finde verzauberte Gegenst\u00E4nde
Fishing.SubSkill.MagicHunter.Name = Zauber J\u00E4ger Fishing.SubSkill.MagicHunter.Name = Zauber J\u00E4ger
Fishing.SubSkill.MagicHunter.Stat = Zauber J\u00E4ger Chance Fishing.SubSkill.MagicHunter.Stat = Zauber J\u00E4ger Chance
Fishing.SubSkill.MasterAngler.Description = Erh\u00F6ht die Chance des Anbei\u00DFens beim Angeln
Fishing.SubSkill.MasterAngler.Name = Superangel Fishing.SubSkill.MasterAngler.Name = Superangel
Fishing.SubSkill.Shake.Description = Rei\u00DFe Gegenst\u00E4nde weg von Lebewesen und Spielern mit deiner Angel Fishing.SubSkill.Shake.Description = Rei\u00DFe Gegenst\u00E4nde weg von Lebewesen und Spielern mit deiner Angel
Fishing.SubSkill.Shake.Name = Rei\u00DFen Fishing.SubSkill.Shake.Name = Rei\u00DFen

View File

@ -258,7 +258,7 @@ Fishing.SubSkill.FishermansDiet.Name=Fisherman's Diet
Fishing.SubSkill.FishermansDiet.Description=Improves hunger restored from fished foods Fishing.SubSkill.FishermansDiet.Description=Improves hunger restored from fished foods
Fishing.SubSkill.FishermansDiet.Stat=Fisherman's Diet:&a Rank {0} Fishing.SubSkill.FishermansDiet.Stat=Fisherman's Diet:&a Rank {0}
Fishing.SubSkill.MasterAngler.Name=Master Angler Fishing.SubSkill.MasterAngler.Name=Master Angler
Fishing.SubSkill.MasterAngler.Description=Fish are caught more frequently Fishing.SubSkill.MasterAngler.Description=Fish are caught more frequently, works better when fishing from a boat.
Fishing.SubSkill.MasterAngler.Stat=Fishing min wait time reduction: &a-{0} seconds Fishing.SubSkill.MasterAngler.Stat=Fishing min wait time reduction: &a-{0} seconds
Fishing.SubSkill.MasterAngler.Stat.Extra=Fishing max wait time reduction: &a-{0} seconds Fishing.SubSkill.MasterAngler.Stat.Extra=Fishing max wait time reduction: &a-{0} seconds
Fishing.SubSkill.IceFishing.Name=Ice Fishing Fishing.SubSkill.IceFishing.Name=Ice Fishing
@ -493,6 +493,7 @@ Taming.Summon.COTW.Success.WithoutLifespan=&a(Call Of The Wild) &7You have summo
Taming.Summon.COTW.Success.WithLifespan=&a(Call Of The Wild) &7You have summoned a &6{0}&7 and it has a duration of &6{1}&7 seconds. Taming.Summon.COTW.Success.WithLifespan=&a(Call Of The Wild) &7You have summoned a &6{0}&7 and it has a duration of &6{1}&7 seconds.
Taming.Summon.COTW.Limit=&a(Call Of The Wild) &7You can only have &c{0} &7summoned &7{1} pets at the same time. Taming.Summon.COTW.Limit=&a(Call Of The Wild) &7You can only have &c{0} &7summoned &7{1} pets at the same time.
Taming.Summon.COTW.TimeExpired=&a(Call Of The Wild) &7Time is up, your &6{0}&7 departs. Taming.Summon.COTW.TimeExpired=&a(Call Of The Wild) &7Time is up, your &6{0}&7 departs.
Taming.Summon.COTW.Removed=&a(Call Of The Wild) &7Your summoned &6{0}&7 has vanished from this world.
Taming.Summon.COTW.BreedingDisallowed=&a(Call Of The Wild) &cYou cannot breed a summoned animal. Taming.Summon.COTW.BreedingDisallowed=&a(Call Of The Wild) &cYou cannot breed a summoned animal.
Taming.Summon.COTW.NeedMoreItems=&a(Call Of The Wild) &7You need &e{0}&7 more &3{1}&7(s) Taming.Summon.COTW.NeedMoreItems=&a(Call Of The Wild) &7You need &e{0}&7 more &3{1}&7(s)
Taming.Summon.Name.Format=&6(COTW) &f{0}'s {1} Taming.Summon.Name.Format=&6(COTW) &f{0}'s {1}

View File

@ -91,7 +91,6 @@ Fishing.SubSkill.Shake.Description=Sacudir los items fuera de los monstruos con
Fishing.SubSkill.FishermansDiet.Name=Dieta del pescador Fishing.SubSkill.FishermansDiet.Name=Dieta del pescador
Fishing.SubSkill.FishermansDiet.Description=Mejora el hambre restaurada a partir de alimentos pescados Fishing.SubSkill.FishermansDiet.Description=Mejora el hambre restaurada a partir de alimentos pescados
Fishing.SubSkill.MasterAngler.Name=Maestro pescador Fishing.SubSkill.MasterAngler.Name=Maestro pescador
Fishing.SubSkill.MasterAngler.Description=Aumenta la probabilidad de ser mordido mientras se pesca
Fishing.SubSkill.IceFishing.Name=Pesca de hielo Fishing.SubSkill.IceFishing.Name=Pesca de hielo
Fishing.SubSkill.IceFishing.Description=Te permite pescar en biomas de hielo Fishing.SubSkill.IceFishing.Description=Te permite pescar en biomas de hielo
Fishing.Chance.Raining=&9 Lluvia de Bonus Fishing.Chance.Raining=&9 Lluvia de Bonus

View File

@ -251,7 +251,6 @@ Fishing.SubSkill.FishermansDiet.Name=R\u00e9gime de fermier
Fishing.SubSkill.FishermansDiet.Description=Am\u00e9liore la nutrition des produits de la ferme Fishing.SubSkill.FishermansDiet.Description=Am\u00e9liore la nutrition des produits de la ferme
Fishing.SubSkill.FishermansDiet.Stat=R\u00e9gime de fermier:&a Grade {0} Fishing.SubSkill.FishermansDiet.Stat=R\u00e9gime de fermier:&a Grade {0}
Fishing.SubSkill.MasterAngler.Name=Ma\u00eetre P\u00eacheur Fishing.SubSkill.MasterAngler.Name=Ma\u00eetre P\u00eacheur
Fishing.SubSkill.MasterAngler.Description=Augmente les chances que \u00e7a morde lors de la p\u00eache
Fishing.SubSkill.IceFishing.Name=P\u00eache sur Glace Fishing.SubSkill.IceFishing.Name=P\u00eache sur Glace
Fishing.SubSkill.IceFishing.Description=Vous permet de p\u00eacher dans les biomes glac\u00e9s Fishing.SubSkill.IceFishing.Description=Vous permet de p\u00eacher dans les biomes glac\u00e9s
Fishing.SubSkill.IceFishing.Stat=P\u00eache sur Glace Fishing.SubSkill.IceFishing.Stat=P\u00eache sur Glace

View File

@ -251,7 +251,6 @@ Fishing.SubSkill.FishermansDiet.Name=Horg\u00E1szok Di\u00E9t\u00E1ja
Fishing.SubSkill.FishermansDiet.Description=N\u00F6veli a kihal\u00E1szott \u00E9telek t\u00E1p\u00E9rt\u00E9k\u00E9t Fishing.SubSkill.FishermansDiet.Description=N\u00F6veli a kihal\u00E1szott \u00E9telek t\u00E1p\u00E9rt\u00E9k\u00E9t
Fishing.SubSkill.FishermansDiet.Stat=Horg\u00E1szok Di\u00E9t\u00E1ja:&a Szint {0} Fishing.SubSkill.FishermansDiet.Stat=Horg\u00E1szok Di\u00E9t\u00E1ja:&a Szint {0}
Fishing.SubSkill.MasterAngler.Name=Mester Horg\u00E1sz Fishing.SubSkill.MasterAngler.Name=Mester Horg\u00E1sz
Fishing.SubSkill.MasterAngler.Description=N\u00F6veli a Kap\u00E1s es\u00E9ly\u00E9t horg\u00E1szat k\u00F6zben
Fishing.SubSkill.IceFishing.Name=J\u00E9g Horg\u00E1szat Fishing.SubSkill.IceFishing.Name=J\u00E9g Horg\u00E1szat
Fishing.SubSkill.IceFishing.Description=Lehet\u0151v\u00E9 teszi sz\u00E1modra, hogy fagyos t\u00E1jakon is horg\u00E1szhass Fishing.SubSkill.IceFishing.Description=Lehet\u0151v\u00E9 teszi sz\u00E1modra, hogy fagyos t\u00E1jakon is horg\u00E1szhass
Fishing.SubSkill.IceFishing.Stat=J\u00E9g Horg\u00E1szat Fishing.SubSkill.IceFishing.Stat=J\u00E9g Horg\u00E1szat

View File

@ -258,7 +258,6 @@ Fishing.SubSkill.FishermansDiet.Name=Dieta del Pescatore
Fishing.SubSkill.FishermansDiet.Description=Aumenta la fame recuperata tramite cibi pescati Fishing.SubSkill.FishermansDiet.Description=Aumenta la fame recuperata tramite cibi pescati
Fishing.SubSkill.FishermansDiet.Stat=Dieta del Pescatore:&a Grado {0} Fishing.SubSkill.FishermansDiet.Stat=Dieta del Pescatore:&a Grado {0}
Fishing.SubSkill.MasterAngler.Name=Pescatore Provetto Fishing.SubSkill.MasterAngler.Name=Pescatore Provetto
Fishing.SubSkill.MasterAngler.Description=Migliora la possibilit\u00E0 di ottenere un morso durante la pesca
Fishing.SubSkill.IceFishing.Name=Pesca sul Ghiaccio Fishing.SubSkill.IceFishing.Name=Pesca sul Ghiaccio
Fishing.SubSkill.IceFishing.Description=Ti permette di pescare in biomi ghiacciati Fishing.SubSkill.IceFishing.Description=Ti permette di pescare in biomi ghiacciati
Fishing.SubSkill.IceFishing.Stat=Pesca sul Ghiaccio Fishing.SubSkill.IceFishing.Stat=Pesca sul Ghiaccio

View File

@ -241,7 +241,6 @@ Fishing.SubSkill.FishermansDiet.Name=\u6f01\u5e2b\u306e\u98df\u4e8b
Fishing.SubSkill.FishermansDiet.Description=\u9b5a\u4ecb\u985e\u304b\u3089\u56de\u5fa9\u3059\u308b\u6e80\u8179\u5ea6\u3092\u6539\u5584\u3059\u308b\u3002 Fishing.SubSkill.FishermansDiet.Description=\u9b5a\u4ecb\u985e\u304b\u3089\u56de\u5fa9\u3059\u308b\u6e80\u8179\u5ea6\u3092\u6539\u5584\u3059\u308b\u3002
Fishing.SubSkill.FishermansDiet.Stat=\u6f01\u5e2b\u306e\u98df\u4e8b:&a \u30e9\u30f3\u30af {0} Fishing.SubSkill.FishermansDiet.Stat=\u6f01\u5e2b\u306e\u98df\u4e8b:&a \u30e9\u30f3\u30af {0}
Fishing.SubSkill.MasterAngler.Name=\u30de\u30b9\u30bf\u30fc\u30a2\u30f3\u30b0\u30e9\u30fc Fishing.SubSkill.MasterAngler.Name=\u30de\u30b9\u30bf\u30fc\u30a2\u30f3\u30b0\u30e9\u30fc
Fishing.SubSkill.MasterAngler.Description=\u91e3\u308c\u308b\u78ba\u7387\u304c\u4e0a\u304c\u308a\u307e\u3059\u3002
Fishing.SubSkill.IceFishing.Name=\u7a74\u91e3\u308a Fishing.SubSkill.IceFishing.Name=\u7a74\u91e3\u308a
Fishing.SubSkill.IceFishing.Description=\u5bd2\u3044\u30d0\u30a4\u30aa\u30fc\u30e0\u3067\u306e\u91e3\u308a\u304c\u3067\u304d\u308b\u3088\u3046\u306b\u306a\u308b\u3002 Fishing.SubSkill.IceFishing.Description=\u5bd2\u3044\u30d0\u30a4\u30aa\u30fc\u30e0\u3067\u306e\u91e3\u308a\u304c\u3067\u304d\u308b\u3088\u3046\u306b\u306a\u308b\u3002
Fishing.SubSkill.IceFishing.Stat=\u7a74\u91e3\u308a Fishing.SubSkill.IceFishing.Stat=\u7a74\u91e3\u308a

View File

@ -132,7 +132,6 @@ Fishing.SubSkill.Shake.Description=\uC544\uC774\uD15C\uC744 \uBAB9\uC774\uB098 \
Fishing.SubSkill.FishermansDiet.Name=\uC5B4\uBD80\uC758 \uB2E4\uC774\uC5B4\uD2B8 Fishing.SubSkill.FishermansDiet.Name=\uC5B4\uBD80\uC758 \uB2E4\uC774\uC5B4\uD2B8
Fishing.SubSkill.FishermansDiet.Description=\uC5B4\uB958 \uC74C\uC2DD \uD5C8\uAE30 \uD68C\uBCF5 \uC99D\uAC00 Fishing.SubSkill.FishermansDiet.Description=\uC5B4\uB958 \uC74C\uC2DD \uD5C8\uAE30 \uD68C\uBCF5 \uC99D\uAC00
Fishing.SubSkill.MasterAngler.Name=\uB09A\uC2DC\uAFBC \uC7A5\uC778 Fishing.SubSkill.MasterAngler.Name=\uB09A\uC2DC\uAFBC \uC7A5\uC778
Fishing.SubSkill.MasterAngler.Description=\uB09A\uC2DC\uC911 \uC785\uC9C8 \uD655\uB960 \uC99D\uAC00
Fishing.SubSkill.IceFishing.Name=\uC5BC\uC74C \uB09A\uC2DC Fishing.SubSkill.IceFishing.Name=\uC5BC\uC74C \uB09A\uC2DC
Fishing.SubSkill.IceFishing.Description=\uC5BC\uC74C\uC774 \uB36E\uD600\uC788\uB294 \uD658\uACBD\uC5D0\uC11C \uB09A\uC2DC \uAC00\uB2A5 Fishing.SubSkill.IceFishing.Description=\uC5BC\uC74C\uC774 \uB36E\uD600\uC788\uB294 \uD658\uACBD\uC5D0\uC11C \uB09A\uC2DC \uAC00\uB2A5
Fishing.Chance.Raining=&9 \uBE44 \uD2B9\uD61C Fishing.Chance.Raining=&9 \uBE44 \uD2B9\uD61C

View File

@ -251,7 +251,6 @@ Fishing.SubSkill.FishermansDiet.Name=Fisherman's Diet
Fishing.SubSkill.FishermansDiet.Description=Improves hunger restored from fished foods Fishing.SubSkill.FishermansDiet.Description=Improves hunger restored from fished foods
Fishing.SubSkill.FishermansDiet.Stat=Fisherman's Diet:&a Rank {0} Fishing.SubSkill.FishermansDiet.Stat=Fisherman's Diet:&a Rank {0}
Fishing.SubSkill.MasterAngler.Name=Master Angler Fishing.SubSkill.MasterAngler.Name=Master Angler
Fishing.SubSkill.MasterAngler.Description=Improves chance of getting a bite while fishing
Fishing.SubSkill.IceFishing.Name=Ice Fishing Fishing.SubSkill.IceFishing.Name=Ice Fishing
Fishing.SubSkill.IceFishing.Description=Allows you to fish in icy biomes Fishing.SubSkill.IceFishing.Description=Allows you to fish in icy biomes
Fishing.SubSkill.IceFishing.Stat=Ice Fishing Fishing.SubSkill.IceFishing.Stat=Ice Fishing

View File

@ -71,7 +71,6 @@ Fishing.SubSkill.Shake.Description=Schud items af van mobs w/ hengel
Fishing.SubSkill.FishermansDiet.Name=Visserman\'s dieet Fishing.SubSkill.FishermansDiet.Name=Visserman\'s dieet
Fishing.SubSkill.FishermansDiet.Description=Verbetert de honger hersteld vanaf geviste voedingsmiddelen Fishing.SubSkill.FishermansDiet.Description=Verbetert de honger hersteld vanaf geviste voedingsmiddelen
Fishing.SubSkill.MasterAngler.Name=Meester Hengelaar Fishing.SubSkill.MasterAngler.Name=Meester Hengelaar
Fishing.SubSkill.MasterAngler.Description=Verbetert de kans op het bijten tijdens het vissen
Fishing.SubSkill.IceFishing.Name=Ijs Vissen Fishing.SubSkill.IceFishing.Name=Ijs Vissen
Fishing.SubSkill.IceFishing.Description=Stelt je in staat om te vissen in de ijzige biomen Fishing.SubSkill.IceFishing.Description=Stelt je in staat om te vissen in de ijzige biomen
Fishing.Chance.Raining=&9 Regen Bonus Fishing.Chance.Raining=&9 Regen Bonus

View File

@ -89,7 +89,6 @@ Fishing.SubSkill.Shake.Name=Potrz\u0105\u015bni\u0119cie (przeciwko jednostkom)
Fishing.SubSkill.Shake.Description=Okradaj potwory z przedmiot\u00f3w u\u017cywaj\u0105c w\u0119dki. Fishing.SubSkill.Shake.Description=Okradaj potwory z przedmiot\u00f3w u\u017cywaj\u0105c w\u0119dki.
Fishing.SubSkill.FishermansDiet.Name=Dieta Rybaka Fishing.SubSkill.FishermansDiet.Name=Dieta Rybaka
Fishing.SubSkill.FishermansDiet.Description=Zwi\u0119ksza nasycenie posi\u0142k\u00f3w (ryby) Fishing.SubSkill.FishermansDiet.Description=Zwi\u0119ksza nasycenie posi\u0142k\u00f3w (ryby)
Fishing.SubSkill.MasterAngler.Description=Zwieksza szanse na zlapanie ryby na haczyk
Fishing.SubSkill.IceFishing.Name=Lodowe lowienie ryb Fishing.SubSkill.IceFishing.Name=Lodowe lowienie ryb
Fishing.SubSkill.IceFishing.Description=Pozwala na lowienie ryb w zimowych biomach Fishing.SubSkill.IceFishing.Description=Pozwala na lowienie ryb w zimowych biomach
Fishing.Chance.Raining=&9 Bonus od Deszczu Fishing.Chance.Raining=&9 Bonus od Deszczu

View File

@ -199,7 +199,6 @@ Fishing.SubSkill.FishermansDiet.Name=\u0420\u044b\u0431\u0430\u0446\u043a\u0430\
Fishing.SubSkill.FishermansDiet.Description=\u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u0442 \u0443\u0442\u043e\u043b\u0435\u043d\u0438\u0435 \u0433\u043e\u043b\u043e\u0434\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0440\u044b\u0431\u0430\u0446\u043a\u043e\u0439 \u0435\u0434\u044b Fishing.SubSkill.FishermansDiet.Description=\u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u0442 \u0443\u0442\u043e\u043b\u0435\u043d\u0438\u0435 \u0433\u043e\u043b\u043e\u0434\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0440\u044b\u0431\u0430\u0446\u043a\u043e\u0439 \u0435\u0434\u044b
Fishing.SubSkill.FishermansDiet.Stat=\u0420\u044b\u0431\u0430\u0446\u043a\u0430\u044f \u0414\u0438\u0435\u0442\u0430:&a \u0420\u0430\u043d\u0433 {0} Fishing.SubSkill.FishermansDiet.Stat=\u0420\u044b\u0431\u0430\u0446\u043a\u0430\u044f \u0414\u0438\u0435\u0442\u0430:&a \u0420\u0430\u043d\u0433 {0}
Fishing.SubSkill.MasterAngler.Name=\u041c\u0430\u0441\u0442\u0435\u0440 \u0420\u044b\u0431\u043e\u043b\u043e\u0432 Fishing.SubSkill.MasterAngler.Name=\u041c\u0430\u0441\u0442\u0435\u0440 \u0420\u044b\u0431\u043e\u043b\u043e\u0432
Fishing.SubSkill.MasterAngler.Description=\u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u0442 \u0448\u0430\u043d\u0441 \u043f\u043e\u043a\u043b\u0435\u0432\u043a\u0438 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0440\u044b\u0431\u0430\u043b\u043a\u0438
Fishing.SubSkill.IceFishing.Name=\u041f\u043e\u0434\u043b\u0435\u0434\u043d\u0430\u044f \u0420\u044b\u0431\u0430\u043b\u043a\u0430 Fishing.SubSkill.IceFishing.Name=\u041f\u043e\u0434\u043b\u0435\u0434\u043d\u0430\u044f \u0420\u044b\u0431\u0430\u043b\u043a\u0430
Fishing.SubSkill.IceFishing.Description=\u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u0430\u043c \u0440\u044b\u0431\u0430\u0447\u0438\u0442\u044c \u0432 \u0441\u043d\u0435\u0436\u043d\u044b\u0445 \u0431\u0438\u043e\u043c\u0430\u0445 Fishing.SubSkill.IceFishing.Description=\u041f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u0430\u043c \u0440\u044b\u0431\u0430\u0447\u0438\u0442\u044c \u0432 \u0441\u043d\u0435\u0436\u043d\u044b\u0445 \u0431\u0438\u043e\u043c\u0430\u0445
Fishing.SubSkill.IceFishing.Stat=\u041f\u043e\u0434\u043b\u0435\u0434\u043d\u0430\u044f \u0420\u044b\u0431\u0430\u043b\u043a\u0430 Fishing.SubSkill.IceFishing.Stat=\u041f\u043e\u0434\u043b\u0435\u0434\u043d\u0430\u044f \u0420\u044b\u0431\u0430\u043b\u043a\u0430

View File

@ -90,7 +90,6 @@ Fishing.SubSkill.Shake.Description=\u0e40\u0e02\u0e22\u0e48\u0e32\u0e40\u0e2d\u0
Fishing.SubSkill.FishermansDiet.Name=Fisherman\'s Diet Fishing.SubSkill.FishermansDiet.Name=Fisherman\'s Diet
Fishing.SubSkill.FishermansDiet.Description=\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e04\u0e27\u0e32\u0e21\u0e2d\u0e34\u0e48\u0e21\u0e08\u0e32\u0e01\u0e01\u0e32\u0e23\u0e01\u0e34\u0e19\u0e1b\u0e25\u0e32 Fishing.SubSkill.FishermansDiet.Description=\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e04\u0e27\u0e32\u0e21\u0e2d\u0e34\u0e48\u0e21\u0e08\u0e32\u0e01\u0e01\u0e32\u0e23\u0e01\u0e34\u0e19\u0e1b\u0e25\u0e32
Fishing.SubSkill.MasterAngler.Name=Master Angler Fishing.SubSkill.MasterAngler.Name=Master Angler
Fishing.SubSkill.MasterAngler.Description=\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e42\u0e2d\u0e01\u0e32\u0e2a\u0e44\u0e14\u0e49\u0e23\u0e31\u0e1a\u0e1b\u0e25\u0e32\u0e21\u0e32\u0e01\u0e02\u0e36\u0e49\u0e19\u0e08\u0e32\u0e01\u0e01\u0e32\u0e23\u0e15\u0e01\u0e1b\u0e25\u0e32
Fishing.SubSkill.IceFishing.Name=Ice Fishing Fishing.SubSkill.IceFishing.Name=Ice Fishing
Fishing.SubSkill.IceFishing.Description=\u0e2d\u0e19\u0e38\u0e0d\u0e32\u0e15\u0e34\u0e43\u0e2b\u0e49\u0e15\u0e01\u0e1b\u0e25\u0e32\u0e43\u0e19\u0e19\u0e49\u0e33\u0e41\u0e02\u0e47\u0e07 Fishing.SubSkill.IceFishing.Description=\u0e2d\u0e19\u0e38\u0e0d\u0e32\u0e15\u0e34\u0e43\u0e2b\u0e49\u0e15\u0e01\u0e1b\u0e25\u0e32\u0e43\u0e19\u0e19\u0e49\u0e33\u0e41\u0e02\u0e47\u0e07
Fishing.Chance.Raining=&9 Rain Bonus Fishing.Chance.Raining=&9 Rain Bonus

View File

@ -251,7 +251,6 @@ Fishing.SubSkill.FishermansDiet.Name=\u6e14\u592b\u7684\u98df\u8c31
Fishing.SubSkill.FishermansDiet.Description=\u63d0\u9ad8\u9c7c\u7c7b\u98df\u7269\u6062\u590d\u7684\u9971\u98df\u5ea6 Fishing.SubSkill.FishermansDiet.Description=\u63d0\u9ad8\u9c7c\u7c7b\u98df\u7269\u6062\u590d\u7684\u9971\u98df\u5ea6
Fishing.SubSkill.FishermansDiet.Stat=\u6e14\u592b\u7684\u98df\u8c31:&a \u7b49\u7ea7 {0} Fishing.SubSkill.FishermansDiet.Stat=\u6e14\u592b\u7684\u98df\u8c31:&a \u7b49\u7ea7 {0}
Fishing.SubSkill.MasterAngler.Name=\u9493\u9c7c\u5927\u5e08 Fishing.SubSkill.MasterAngler.Name=\u9493\u9c7c\u5927\u5e08
Fishing.SubSkill.MasterAngler.Description=\u63d0\u9ad8\u9493\u9c7c\u54ac\u94a9\u51e0\u7387
Fishing.SubSkill.IceFishing.Name=\u51b0\u9493 Fishing.SubSkill.IceFishing.Name=\u51b0\u9493
Fishing.SubSkill.IceFishing.Description=\u5141\u8bb8\u4f60\u5728\u51b0\u51b7\u7684\u73af\u5883\u4e0b\u9493\u9c7c Fishing.SubSkill.IceFishing.Description=\u5141\u8bb8\u4f60\u5728\u51b0\u51b7\u7684\u73af\u5883\u4e0b\u9493\u9c7c
Fishing.SubSkill.IceFishing.Stat=\u51b0\u9493 Fishing.SubSkill.IceFishing.Stat=\u51b0\u9493

View File

@ -93,7 +93,6 @@ Fishing.SubSkill.Shake.Description=\u7528\u91e3\u7aff\u628a\u602a\u7269\u7684\u7
Fishing.SubSkill.FishermansDiet.Name=\u6f01\u4eba\u4fbf\u7576 Fishing.SubSkill.FishermansDiet.Name=\u6f01\u4eba\u4fbf\u7576
Fishing.SubSkill.FishermansDiet.Description=\u98df\u7528\u9b5a\u98df\u54c1\u6642\u984d\u5916\u6062\u5fa9\u98fd\u98df\u5ea6 Fishing.SubSkill.FishermansDiet.Description=\u98df\u7528\u9b5a\u98df\u54c1\u6642\u984d\u5916\u6062\u5fa9\u98fd\u98df\u5ea6
Fishing.SubSkill.MasterAngler.Name=\u5782\u91e3\u5927\u5e2b Fishing.SubSkill.MasterAngler.Name=\u5782\u91e3\u5927\u5e2b
Fishing.SubSkill.MasterAngler.Description=\u589e\u52a0\u5728\u91e3\u9b5a\u6642\u4e0a\u9264\u7684\u6a5f\u7387
Fishing.SubSkill.IceFishing.Name=\u51b0\u91e3 Fishing.SubSkill.IceFishing.Name=\u51b0\u91e3
Fishing.SubSkill.IceFishing.Description=\u5141\u8a31\u4f60\u5728\u51b0\u5929\u96ea\u5730\u88e1\u91e3\u9b5a Fishing.SubSkill.IceFishing.Description=\u5141\u8a31\u4f60\u5728\u51b0\u5929\u96ea\u5730\u88e1\u91e3\u9b5a
Fishing.Chance.Raining=&9 \u5927\u91cf\u734e\u52f5 Fishing.Chance.Raining=&9 \u5927\u91cf\u734e\u52f5

View File

@ -2,6 +2,9 @@ import com.gmail.nossr50.util.blockmeta.*;
import com.google.common.io.Files; import com.google.common.io.Files;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.*; import org.junit.*;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito; import org.mockito.Mockito;
@ -140,16 +143,27 @@ public class ChunkStoreTest {
@Test @Test
public void testRegressionChunkMirrorBug() { public void testRegressionChunkMirrorBug() {
ChunkManager chunkManager = new HashChunkManager(); ChunkManager chunkManager = new HashChunkManager();
chunkManager.setTrue(15,0,15, mockWorld); Block mockBlockA = mock(Block.class);
chunkManager.setFalse(-15, 0, -15, mockWorld); Mockito.when(mockBlockA.getX()).thenReturn(15);
Assert.assertTrue(chunkManager.isTrue(15, 0, 15, mockWorld)); Mockito.when(mockBlockA.getZ()).thenReturn(15);
Mockito.when(mockBlockA.getY()).thenReturn(0);
Mockito.when(mockBlockA.getWorld()).thenReturn(mockWorld);
Block mockBlockB = mock(Block.class);
Mockito.when(mockBlockB.getX()).thenReturn(-15);
Mockito.when(mockBlockB.getZ()).thenReturn(-15);
Mockito.when(mockBlockB.getY()).thenReturn(0);
Mockito.when(mockBlockB.getWorld()).thenReturn(mockWorld);
chunkManager.setTrue(mockBlockA);
chunkManager.setFalse(mockBlockB);
Assert.assertTrue(chunkManager.isTrue(mockBlockA));
} }
private interface Delegate { private interface Delegate {
void run(); void run();
} }
private void assertThrows(Delegate delegate, Class<?> clazz) { private void assertThrows(@NotNull Delegate delegate, @NotNull Class<?> clazz) {
try { try {
delegate.run(); delegate.run();
Assert.fail(); // We didn't throw Assert.fail(); // We didn't throw
@ -170,7 +184,7 @@ public class ChunkStoreTest {
Assert.assertTrue(expected.isTrue(x, y, z) == actual.isTrue(x, y, z)); Assert.assertTrue(expected.isTrue(x, y, z) == actual.isTrue(x, y, z));
} }
private static void recursiveDelete(File directoryToBeDeleted) { private static void recursiveDelete(@NotNull File directoryToBeDeleted) {
if (directoryToBeDeleted.isDirectory()) { if (directoryToBeDeleted.isDirectory()) {
for (File file : directoryToBeDeleted.listFiles()) { for (File file : directoryToBeDeleted.listFiles()) {
recursiveDelete(file); recursiveDelete(file);
@ -179,7 +193,7 @@ public class ChunkStoreTest {
directoryToBeDeleted.delete(); directoryToBeDeleted.delete();
} }
private static byte[] serializeChunkstore(ChunkStore chunkStore) throws IOException { private static byte[] serializeChunkstore(@NotNull ChunkStore chunkStore) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
if (chunkStore instanceof BitSetChunkStore) if (chunkStore instanceof BitSetChunkStore)
BitSetChunkStore.Serialization.writeChunkStore(new DataOutputStream(byteArrayOutputStream), chunkStore); BitSetChunkStore.Serialization.writeChunkStore(new DataOutputStream(byteArrayOutputStream), chunkStore);
@ -188,18 +202,17 @@ public class ChunkStoreTest {
return byteArrayOutputStream.toByteArray(); return byteArrayOutputStream.toByteArray();
} }
public static class LegacyChunkStore implements ChunkStore, Serializable { public static class LegacyChunkStore implements ChunkStore, Serializable {
private static final long serialVersionUID = -1L; private static final long serialVersionUID = -1L;
transient private boolean dirty = false; transient private boolean dirty = false;
public boolean[][][] store; public boolean[][][] store;
private static final int CURRENT_VERSION = 7; private static final int CURRENT_VERSION = 7;
private static final int MAGIC_NUMBER = 0xEA5EDEBB; private static final int MAGIC_NUMBER = 0xEA5EDEBB;
private int cx; private final int cx;
private int cz; private final int cz;
private UUID worldUid; private final @NotNull UUID worldUid;
public LegacyChunkStore(World world, int cx, int cz) { public LegacyChunkStore(@NotNull World world, int cx, int cz) {
this.cx = cx; this.cx = cx;
this.cz = cz; this.cz = cz;
this.worldUid = world.getUID(); this.worldUid = world.getUID();
@ -227,7 +240,7 @@ public class ChunkStoreTest {
} }
@Override @Override
public UUID getWorldId() { public @NotNull UUID getWorldId() {
return worldUid; return worldUid;
} }
@ -274,7 +287,7 @@ public class ChunkStoreTest {
return true; return true;
} }
private void writeObject(ObjectOutputStream out) throws IOException { private void writeObject(@NotNull ObjectOutputStream out) throws IOException {
out.writeInt(MAGIC_NUMBER); out.writeInt(MAGIC_NUMBER);
out.writeInt(CURRENT_VERSION); out.writeInt(CURRENT_VERSION);
@ -287,18 +300,18 @@ public class ChunkStoreTest {
dirty = false; dirty = false;
} }
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { private void readObject(@NotNull ObjectInputStream in) throws IOException, ClassNotFoundException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }
private static class UnitTestObjectOutputStream extends ObjectOutputStream { private static class UnitTestObjectOutputStream extends ObjectOutputStream {
public UnitTestObjectOutputStream(OutputStream outputStream) throws IOException { public UnitTestObjectOutputStream(@NotNull OutputStream outputStream) throws IOException {
super(outputStream); super(outputStream);
} }
@Override @Override
public void writeUTF(String str) throws IOException { public void writeUTF(@NotNull String str) throws IOException {
// Pretend to be the old class // Pretend to be the old class
if (str.equals(LegacyChunkStore.class.getName())) if (str.equals(LegacyChunkStore.class.getName()))
str = "com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore"; str = "com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore";