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

This commit is contained in:
nossr50 2020-12-30 15:49:16 -08:00
commit 5b898049a3
56 changed files with 2830 additions and 3412 deletions

View File

@ -99,6 +99,52 @@ 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.
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.
Version 2.1.165
The mcMMO system which tracks player placed blocks has had some major rewrites (thanks t00thpick1)
mcMMO will now be compatible with changes to world height (1.17 compatibility)
Added missing cooldown locale message 'Commands.Database.Cooldown'
NOTES:
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 update shouldn't break anything as the API is the same
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)
The Rarity known as Records has been renamed to Mythic
Fishing treasures have been moved from treasures.yml -> fishing_treasures.yml, copy over any custom entries you had from treasures.yml carefully as the config file has changed and you can't just copy paste your old entries without making a few edits
Added Enchanted Books to fishing_treasures.yml as Legendary rarity (can drop with any legal enchantment - see notes)
Added all Netherite gear to the Mythic rarity in fishing_treasures.yml
Added Name Tag to fishing_treasures.yml as Rare rarity
Added Netherite Scrap to fishing_treasures.yml as Legendary rarity
Added Nautilus Shell to fishing_treasures.yml as Legendary rarity
Music Disc rarity in fishing_tresures.yml has been broken up across tiers, they will be more common now.
Mythic rarity (formerly known as Records) now allows for Enchantments to be applied to drops (See Notes)
New exploit fix setting 'PreventPluginNPCInteraction' which defaults to true, when disabled it will allow combat interactions with "NPC" entities from plugins like Citizens
ExploitFix.PreventPluginNPCInteraction Added to experience.yml
Modified locale string 'Fishing.SubSkill.TreasureHunter.Stat.Extra' in existing locale files
You can now define a whitelist of enchants or a blacklist of enchants for an Enchanted_Book entries in fishing_treasures.yml, see notes for an example
NOTES:
Before reading, Fishing/Excavation are getting a complete loot table rewrite in the future, everything changed in this patch is meant as a temporary quality of life fix until the bigger better change in the future.
There's no real reason to allow for vanilla treasures anymore, so if you were using the vanilla treasure override I suggest turning it off.
You can't add Enchanted_Book to any treasures outside of Fishing's treasure drops right now, I'll fix it in an upcoming patch. Well you can add it, but it won't work.
The rarity formerly known as 'Records' was odd to me, if you got the best possible drop it was always going to be a Music Record drop (using the default mcMMO treasure list), and by default the Records tier had only music records. It was treated differently in the code as well, for example Records drops never had enchantments applied to them. So you could add say NETHERITE_ARMOR to them in your user config and it would never put enchantments on it, that seemed very odd to me.
As a response to this, I've renamed Records as Mythic, I've moved the records into varying tiers, you'll start getting them much earlier now. I've also added Netherite and Enchanted Books to the Mythic tier.
Enchanted Books have been added to Fishing loot, this is a basic hacky work around until the config update comes. Enchanted books can have any legal enchant and you can specify which Enchants a book can spawn with.
Also the Enchantment chance to be applied to the book is completely equal across all enchantments, it does not follow the same logic as applying enchantments to fished up gear.
Here is an example of using the whitelist or blacklist for an Enchanted_Book entry in fishing_treasures.yml
https://gist.github.com/nossr50/4e15b8ba6915b5a5f516eccfba2d7169
If you can't load this image, at the address of your treasure for example, at Fishing.Enchanted_Book.Enchantments_Blacklist: you define a list (which must follow yaml spec, google yaml linter) of enchants to disallow, likewise at Fishing.Enchanted_Book.Enchantments_Whitelist you can setup a whitelist, if neither is defined then the book can spawn with all possible enchants, if both are defined the whitelist is used instead of the blacklist
Take care when moving any fishing entries you may have defined in treasures.yml over to fishing_treasures.yml, the config file has had a few things changed (as noted in these notes).
When talking about NPCs in the below notes, I am referring to "Fake" Players used in plugins such as Citizens, not Villagers from Vanilla Minecraft or anything labeled NPC in another plugin which does not constitute a "Fake Player"
Historically mcMMO has checked an entity for being a Fake-Player-NPC and backed out of any interaction, this was originally done because of Fake-Player-NPCs that were meant to be invincible/etc and not give XP
However nowadays what a Fake-Player-NPC is used for is pretty loose, mcMMO only has definitions for some NPCs (such as from Citizens) it doesn't know about most Fake-Player-NPCs in most plugins unless they identify themselves in a similar way to the predefined parameters
Leave this new exploit fix setting on true unless you understand the implications
If you only ran mcMMO on your server you'd have no way to use Enchanted Books if you set the repair anvil to the vanilla anvil, so now you can sneak to open up its menu. By the way, mcMMO has the vanilla anvil as repair's default anvil (instead of iron block which it had been historically).
Version 2.1.163
Fixed the translate URL pointing to the wrong place (thanks chew)
Fixed a bug where FlatFile databases would always attempt a UUID conversion task every save operation (every 10 minutes) causing console spam
@ -106,6 +152,7 @@ Version 2.1.163
COTW Summoned entities are now removed when the chunk they are in is unloaded (prevents some exploits)
NOTES:
Seems I skipped releasing 2.1.162, not a big deal though as you should be using this version instead!
I often test in SQL environments so I missed this bug, reminder to come bother me on discord if you find any annoying bugs!
Also work on T&C is going great lately, I feel great. Perhaps my depression is getting better!

20
pom.xml
View File

@ -306,7 +306,25 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit-dep</artifactId>
<version>4.10</version>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.4.6</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -0,0 +1,57 @@
//package com.gmail.nossr50.commands.admin;
//
//import com.gmail.nossr50.config.treasure.FishingTreasureConfig;
//import com.gmail.nossr50.datatypes.player.McMMOPlayer;
//import com.gmail.nossr50.datatypes.treasure.FishingTreasure;
//import com.gmail.nossr50.datatypes.treasure.Rarity;
//import com.gmail.nossr50.mcMMO;
//import com.gmail.nossr50.skills.fishing.FishingManager;
//import com.gmail.nossr50.util.player.UserManager;
//import org.bukkit.Location;
//import org.bukkit.command.Command;
//import org.bukkit.command.CommandExecutor;
//import org.bukkit.command.CommandSender;
//import org.bukkit.entity.Player;
//import org.jetbrains.annotations.NotNull;
//
//public class DropTreasureCommand implements CommandExecutor {
// @Override
// public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
// if(sender instanceof Player) {
// if(!sender.isOp()) {
// sender.sendMessage("This command is for Operators only");
// return false;
// }
//
// Player player = (Player) sender;
// Location location = player.getLocation();
// McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
//
// if(mmoPlayer == null) {
// //TODO: Localize
// player.sendMessage("Your player data is not loaded yet");
// return false;
// }
//
// if(args.length == 0) {
// mcMMO.p.getLogger().info(player.toString() +" is dropping all mcMMO treasures via admin command at location "+location.toString());
// for(Rarity rarity : FishingTreasureConfig.getInstance().fishingRewards.keySet()) {
// for(FishingTreasure fishingTreasure : FishingTreasureConfig.getInstance().fishingRewards.get(rarity)) {
// FishingManager fishingManager = mmoPlayer.getFishingManager();
// }
// }
// //TODO: impl
// } else {
// String targetTreasure = args[1];
//
// //Drop all treasures matching the name
// //TODO: impl
// }
//
// return true;
// } else {
// sender.sendMessage("No console support for this command");
// return false;
// }
// }
//}

View File

@ -1,6 +1,7 @@
package com.gmail.nossr50.commands.skills;
import com.gmail.nossr50.config.treasure.TreasureConfig;
import com.gmail.nossr50.config.treasure.FishingTreasureConfig;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import com.gmail.nossr50.datatypes.skills.SubSkillType;
import com.gmail.nossr50.datatypes.treasure.Rarity;
import com.gmail.nossr50.locale.LocaleLoader;
@ -28,7 +29,7 @@ public class FishingCommand extends SkillCommand {
private String rareTreasure;
private String epicTreasure;
private String legendaryTreasure;
private String recordTreasure;
private String mythicTreasure;
private String magicChance;
@ -54,19 +55,19 @@ public class FishingCommand extends SkillCommand {
lootTier = fishingManager.getLootTier();
// Item drop rates
commonTreasure = percent.format(TreasureConfig.getInstance().getItemDropRate(lootTier, Rarity.COMMON) / 100.0);
uncommonTreasure = percent.format(TreasureConfig.getInstance().getItemDropRate(lootTier, Rarity.UNCOMMON) / 100.0);
rareTreasure = percent.format(TreasureConfig.getInstance().getItemDropRate(lootTier, Rarity.RARE) / 100.0);
epicTreasure = percent.format(TreasureConfig.getInstance().getItemDropRate(lootTier, Rarity.EPIC) / 100.0);
legendaryTreasure = percent.format(TreasureConfig.getInstance().getItemDropRate(lootTier, Rarity.LEGENDARY) / 100.0);
recordTreasure = percent.format(TreasureConfig.getInstance().getItemDropRate(lootTier, Rarity.RECORD) / 100.0);
commonTreasure = percent.format(FishingTreasureConfig.getInstance().getItemDropRate(lootTier, Rarity.COMMON) / 100.0);
uncommonTreasure = percent.format(FishingTreasureConfig.getInstance().getItemDropRate(lootTier, Rarity.UNCOMMON) / 100.0);
rareTreasure = percent.format(FishingTreasureConfig.getInstance().getItemDropRate(lootTier, Rarity.RARE) / 100.0);
epicTreasure = percent.format(FishingTreasureConfig.getInstance().getItemDropRate(lootTier, Rarity.EPIC) / 100.0);
legendaryTreasure = percent.format(FishingTreasureConfig.getInstance().getItemDropRate(lootTier, Rarity.LEGENDARY) / 100.0);
mythicTreasure = percent.format(FishingTreasureConfig.getInstance().getItemDropRate(lootTier, Rarity.MYTHIC) / 100.0);
// Magic hunter drop rates
double totalEnchantChance = 0;
for (Rarity rarity : Rarity.values()) {
if (rarity != Rarity.RECORD) {
totalEnchantChance += TreasureConfig.getInstance().getEnchantmentDropRate(lootTier, rarity);
if (rarity != Rarity.MYTHIC) {
totalEnchantChance += FishingTreasureConfig.getInstance().getEnchantmentDropRate(lootTier, rarity);
}
}
@ -144,7 +145,7 @@ public class FishingCommand extends SkillCommand {
String.valueOf(rareTreasure),
String.valueOf(epicTreasure),
String.valueOf(legendaryTreasure),
String.valueOf(recordTreasure)));
String.valueOf(mythicTreasure)));
}
return messages;

View File

@ -12,6 +12,7 @@ import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@ -506,14 +507,17 @@ public class Config extends AutoUpdateConfigLoader {
public boolean getRepairAnvilMessagesEnabled() { return config.getBoolean("Skills.Repair.Anvil_Messages", true); }
public boolean getRepairAnvilPlaceSoundsEnabled() { return config.getBoolean("Skills.Repair.Anvil_Placed_Sounds", true); }
public boolean getRepairAnvilUseSoundsEnabled() { return config.getBoolean("Skills.Repair.Anvil_Use_Sounds", true); }
public Material getRepairAnvilMaterial() { return Material.matchMaterial(config.getString("Skills.Repair.Anvil_Material", "IRON_BLOCK")); }
public @Nullable Material getRepairAnvilMaterial() { return Material.matchMaterial(config.getString("Skills.Repair.Anvil_Material", "IRON_BLOCK")); }
public boolean getRepairConfirmRequired() { return config.getBoolean("Skills.Repair.Confirm_Required", true); }
public boolean getAllowVanillaInventoryRepair() { return config.getBoolean("Skills.Repair.Allow_Vanilla_Anvil_Repair", false); }
public boolean getAllowVanillaAnvilRepair() { return config.getBoolean("Skills.Repair.Allow_Vanilla_Inventory_Repair", false); }
public boolean getAllowVanillaGrindstoneRepair() { return config.getBoolean("Skills.Repair.Allow_Vanilla_Grindstone_Repair", false); }
/* Salvage */
public boolean getSalvageAnvilMessagesEnabled() { return config.getBoolean("Skills.Salvage.Anvil_Messages", true); }
public boolean getSalvageAnvilPlaceSoundsEnabled() { return config.getBoolean("Skills.Salvage.Anvil_Placed_Sounds", true); }
public boolean getSalvageAnvilUseSoundsEnabled() { return config.getBoolean("Skills.Salvage.Anvil_Use_Sounds", true); }
public Material getSalvageAnvilMaterial() { return Material.matchMaterial(config.getString("Skills.Salvage.Anvil_Material", "GOLD_BLOCK")); }
public @Nullable Material getSalvageAnvilMaterial() { return Material.matchMaterial(config.getString("Skills.Salvage.Anvil_Material", "GOLD_BLOCK")); }
public boolean getSalvageConfirmRequired() { return config.getBoolean("Skills.Salvage.Confirm_Required", true); }
/* Unarmed */

View File

@ -154,6 +154,7 @@ public class ExperienceConfig extends AutoUpdateConfigLoader {
public boolean isPistonExploitPrevented() { return config.getBoolean("ExploitFix.Pistons", false); }
public boolean allowUnsafeEnchantments() { return config.getBoolean("ExploitFix.UnsafeEnchantments", false); }
public boolean isCOTWBreedingPrevented() { return config.getBoolean("ExploitFix.COTWBreeding", true); }
public boolean isNPCInteractionPrevented() { return config.getBoolean("ExploitFix.PreventPluginNPCInteraction", true); }
public boolean isFishingExploitingPrevented() { return config.getBoolean("ExploitFix.Fishing", true); }
public boolean isAcrobaticsExploitingPrevented() { return config.getBoolean("ExploitFix.Acrobatics", true); }

View File

@ -0,0 +1,383 @@
package com.gmail.nossr50.config.treasure;
import com.gmail.nossr50.config.ConfigLoader;
import com.gmail.nossr50.datatypes.treasure.*;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.EnchantmentUtils;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.potion.PotionData;
import org.bukkit.potion.PotionType;
import org.jetbrains.annotations.NotNull;
import java.util.*;
public class FishingTreasureConfig extends ConfigLoader {
public static final String FILENAME = "fishing_treasures.yml";
private static FishingTreasureConfig instance;
public @NotNull HashMap<Rarity, List<FishingTreasure>> fishingRewards = new HashMap<>();
public @NotNull HashMap<Rarity, List<EnchantmentTreasure>> fishingEnchantments = new HashMap<>();
public @NotNull HashMap<EntityType, List<ShakeTreasure>> shakeMap = new HashMap<>();
private FishingTreasureConfig() {
super(FILENAME);
loadKeys();
validate();
}
public static FishingTreasureConfig getInstance() {
if (instance == null) {
instance = new FishingTreasureConfig();
}
return instance;
}
@Override
protected boolean validateKeys() {
// Validate all the settings!
List<String> reason = new ArrayList<>();
for (String tier : config.getConfigurationSection("Enchantment_Drop_Rates").getKeys(false)) {
double totalEnchantDropRate = 0;
double totalItemDropRate = 0;
for (Rarity rarity : Rarity.values()) {
double enchantDropRate = config.getDouble("Enchantment_Drop_Rates." + tier + "." + rarity.toString());
double itemDropRate = config.getDouble("Item_Drop_Rates." + tier + "." + rarity.toString());
if ((enchantDropRate < 0.0 || enchantDropRate > 100.0)) {
reason.add("The enchant drop rate for " + tier + " items that are " + rarity.toString() + "should be between 0.0 and 100.0!");
}
if (itemDropRate < 0.0 || itemDropRate > 100.0) {
reason.add("The item drop rate for " + tier + " items that are " + rarity.toString() + "should be between 0.0 and 100.0!");
}
totalEnchantDropRate += enchantDropRate;
totalItemDropRate += itemDropRate;
}
if (totalEnchantDropRate < 0 || totalEnchantDropRate > 100.0) {
reason.add("The total enchant drop rate for " + tier + " should be between 0.0 and 100.0!");
}
if (totalItemDropRate < 0 || totalItemDropRate > 100.0) {
reason.add("The total item drop rate for " + tier + " should be between 0.0 and 100.0!");
}
}
return noErrorsInConfig(reason);
}
@Override
protected void loadKeys() {
if (config.getConfigurationSection("Treasures") != null) {
backup();
return;
}
loadTreasures("Fishing");
loadEnchantments();
for (EntityType entity : EntityType.values()) {
if (entity.isAlive()) {
loadTreasures("Shake." + entity.toString());
}
}
}
private void loadTreasures(@NotNull String type) {
boolean isFishing = type.equals("Fishing");
boolean isShake = type.contains("Shake");
ConfigurationSection treasureSection = config.getConfigurationSection(type);
if (treasureSection == null) {
return;
}
// Initialize fishing HashMap
for (Rarity rarity : Rarity.values()) {
if (!fishingRewards.containsKey(rarity)) {
fishingRewards.put(rarity, (new ArrayList<>()));
}
}
for (String treasureName : treasureSection.getKeys(false)) {
// Validate all the things!
List<String> reason = new ArrayList<>();
String[] treasureInfo = treasureName.split("[|]");
String materialName = treasureInfo[0];
/*
* Material, Amount, and Data
*/
Material material;
if (materialName.contains("INVENTORY")) {
// Use magic material BEDROCK to know that we're grabbing something from the inventory and not a normal treasure
addShakeTreasure(new ShakeTreasure(new ItemStack(Material.BEDROCK, 1, (byte) 0), 1, getInventoryStealDropChance(), getInventoryStealDropLevel()), EntityType.PLAYER);
continue;
} else {
material = Material.matchMaterial(materialName);
}
int amount = config.getInt(type + "." + treasureName + ".Amount");
short data = (treasureInfo.length == 2) ? Short.parseShort(treasureInfo[1]) : (short) config.getInt(type + "." + treasureName + ".Data");
if (material == null) {
reason.add("Cannot find matching item type in this version of MC, skipping - " + materialName);
continue;
}
if (amount <= 0) {
amount = 1;
}
if (material.isBlock() && (data > 127 || data < -128)) {
reason.add("Data of " + treasureName + " is invalid! " + data);
}
/*
* XP, Drop Chance, and Drop Level
*/
int xp = config.getInt(type + "." + treasureName + ".XP");
double dropChance = config.getDouble(type + "." + treasureName + ".Drop_Chance");
int dropLevel = config.getInt(type + "." + treasureName + ".Drop_Level");
if (xp < 0) {
reason.add(treasureName + " has an invalid XP value: " + xp);
}
if (dropChance < 0.0D) {
reason.add(treasureName + " has an invalid Drop_Chance: " + dropChance);
}
if (dropLevel < 0) {
reason.add(treasureName + " has an invalid Drop_Level: " + dropLevel);
}
/*
* Specific Types
*/
Rarity rarity = null;
if (isFishing) {
String rarityStr = config.getString(type + "." + treasureName + ".Rarity");
if(rarityStr != null) {
rarity = Rarity.getRarity(rarityStr);
} else {
mcMMO.p.getLogger().severe("Please edit your config and add a Rarity definition for - " + treasureName);
mcMMO.p.getLogger().severe("Skipping this treasure until rarity is defined - " + treasureName);
continue;
}
}
/*
* Itemstack
*/
ItemStack item = null;
String customName = null;
if(hasCustomName(type, treasureName)) {
customName = config.getString(type + "." + treasureName + ".Custom_Name");
}
if (materialName.contains("POTION")) {
Material mat = Material.matchMaterial(materialName);
if (mat == null) {
reason.add("Potion format for " + FILENAME + " has changed");
} else {
item = new ItemStack(mat, amount, data);
PotionMeta itemMeta = (PotionMeta) item.getItemMeta();
if(itemMeta == null) {
mcMMO.p.getLogger().severe("Item meta when adding potion to fishing treasure was null, contact the mcMMO devs!");
continue;
}
PotionType potionType = null;
try {
potionType = PotionType.valueOf(config.getString(type + "." + treasureName + ".PotionData.PotionType", "WATER"));
} catch (IllegalArgumentException ex) {
reason.add("Invalid Potion_Type: " + config.getString(type + "." + treasureName + ".PotionData.PotionType", "WATER"));
}
boolean extended = config.getBoolean(type + "." + treasureName + ".PotionData.Extended", false);
boolean upgraded = config.getBoolean(type + "." + treasureName + ".PotionData.Upgraded", false);
itemMeta.setBasePotionData(new PotionData(potionType, extended, upgraded));
if (customName != null) {
itemMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', customName));
}
if (config.contains(type + "." + treasureName + ".Lore")) {
List<String> lore = new ArrayList<>();
for (String s : config.getStringList(type + "." + treasureName + ".Lore")) {
lore.add(ChatColor.translateAlternateColorCodes('&', s));
}
itemMeta.setLore(lore);
}
item.setItemMeta(itemMeta);
}
} else if(material == Material.ENCHANTED_BOOK) {
//If any whitelisted enchants exist we use whitelist-based matching
item = new ItemStack(material, 1);
ItemMeta itemMeta = item.getItemMeta();
List<String> allowedEnchantsList = config.getStringList(type + "." + treasureName + ".Enchantments_Whitelist");
List<String> disallowedEnchantsList = config.getStringList(type + "." + treasureName + ".Enchantments_Blacklist");
Set<Enchantment> blackListedEnchants = new HashSet<>();
Set<Enchantment> whiteListedEnchants = new HashSet<>();
matchAndFillSet(disallowedEnchantsList, blackListedEnchants);
matchAndFillSet(allowedEnchantsList, whiteListedEnchants);
if (customName != null && itemMeta != null) {
itemMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', customName));
item.setItemMeta(itemMeta);
}
FishingTreasureBook fishingTreasureBook = new FishingTreasureBook(item, xp, blackListedEnchants, whiteListedEnchants);
addFishingTreasure(rarity, fishingTreasureBook);
//TODO: Add book support for shake
continue; //The code in this whole file is a disaster, ignore this hacky solution :P
} else {
item = new ItemStack(material, amount, data);
if (customName != null) {
ItemMeta itemMeta = item.getItemMeta();
itemMeta.setDisplayName(ChatColor.translateAlternateColorCodes('&', customName));
item.setItemMeta(itemMeta);
}
if (config.contains(type + "." + treasureName + ".Lore")) {
ItemMeta itemMeta = item.getItemMeta();
List<String> lore = new ArrayList<>();
for (String s : config.getStringList(type + "." + treasureName + ".Lore")) {
lore.add(ChatColor.translateAlternateColorCodes('&', s));
}
itemMeta.setLore(lore);
item.setItemMeta(itemMeta);
}
}
if (noErrorsInConfig(reason)) {
if (isFishing) {
addFishingTreasure(rarity, new FishingTreasure(item, xp));
} else if (isShake) {
ShakeTreasure shakeTreasure = new ShakeTreasure(item, xp, dropChance, dropLevel);
EntityType entityType = EntityType.valueOf(type.substring(6));
addShakeTreasure(shakeTreasure, entityType);
}
}
}
}
private void addShakeTreasure(@NotNull ShakeTreasure shakeTreasure, @NotNull EntityType entityType) {
if (!shakeMap.containsKey(entityType))
shakeMap.put(entityType, new ArrayList<>());
shakeMap.get(entityType).add(shakeTreasure);
}
private void addFishingTreasure(@NotNull Rarity rarity, @NotNull FishingTreasure fishingTreasure) {
fishingRewards.get(rarity).add(fishingTreasure);
}
private boolean hasCustomName(@NotNull String type, @NotNull String treasureName) {
return config.contains(type + "." + treasureName + ".Custom_Name");
}
/**
* Matches enchantments on a list (user provided string) to known enchantments in the Spigot API
* Any matches are added to the passed set
* @param enchantListStr the users string list of enchantments
* @param permissiveList the permissive list of enchantments
*/
private void matchAndFillSet(@NotNull List<String> enchantListStr, @NotNull Set<Enchantment> permissiveList) {
if(enchantListStr.isEmpty()) {
return;
}
for(String str : enchantListStr) {
boolean foundMatch = false;
for(Enchantment enchantment : Enchantment.values()) {
if(enchantment.getKey().getKey().equalsIgnoreCase(str)) {
permissiveList.add(enchantment);
foundMatch = true;
break;
}
}
if(!foundMatch) {
mcMMO.p.getLogger().info("[Fishing Treasure Init] Could not find any enchantments which matched the user defined enchantment named: "+str);
}
}
}
private void loadEnchantments() {
for (Rarity rarity : Rarity.values()) {
if (!fishingEnchantments.containsKey(rarity)) {
fishingEnchantments.put(rarity, (new ArrayList<>()));
}
ConfigurationSection enchantmentSection = config.getConfigurationSection("Enchantments_Rarity." + rarity.toString());
if (enchantmentSection == null) {
return;
}
for (String enchantmentName : enchantmentSection.getKeys(false)) {
int level = config.getInt("Enchantments_Rarity." + rarity.toString() + "." + enchantmentName);
Enchantment enchantment = EnchantmentUtils.getByName(enchantmentName);
if (enchantment == null) {
plugin.getLogger().warning("Skipping invalid enchantment in " + FILENAME + ": " + enchantmentName);
continue;
}
fishingEnchantments.get(rarity).add(new EnchantmentTreasure(enchantment, level));
}
}
}
public boolean getInventoryStealEnabled() {
return config.contains("Shake.PLAYER.INVENTORY");
}
public boolean getInventoryStealStacks() {
return config.getBoolean("Shake.PLAYER.INVENTORY.Whole_Stacks");
}
public double getInventoryStealDropChance() {
return config.getDouble("Shake.PLAYER.INVENTORY.Drop_Chance");
}
public int getInventoryStealDropLevel() {
return config.getInt("Shake.PLAYER.INVENTORY.Drop_Level");
}
public double getItemDropRate(int tier, @NotNull Rarity rarity) {
return config.getDouble("Item_Drop_Rates.Tier_" + tier + "." + rarity.toString());
}
public double getEnchantmentDropRate(int tier, @NotNull Rarity rarity) {
return config.getDouble("Enchantment_Drop_Rates.Tier_" + tier + "." + rarity.toString());
}
}

View File

@ -1,14 +1,13 @@
package com.gmail.nossr50.config.treasure;
import com.gmail.nossr50.config.ConfigLoader;
import com.gmail.nossr50.datatypes.treasure.*;
import com.gmail.nossr50.util.EnchantmentUtils;
import com.gmail.nossr50.datatypes.treasure.ExcavationTreasure;
import com.gmail.nossr50.datatypes.treasure.HylianTreasure;
import com.gmail.nossr50.util.text.StringUtils;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
@ -22,18 +21,14 @@ import java.util.List;
public class TreasureConfig extends ConfigLoader {
public static final String FILENAME = "treasures.yml";
private static TreasureConfig instance;
public HashMap<String, List<ExcavationTreasure>> excavationMap = new HashMap<>();
public HashMap<EntityType, List<ShakeTreasure>> shakeMap = new HashMap<>();
public HashMap<String, List<HylianTreasure>> hylianMap = new HashMap<>();
public HashMap<Rarity, List<FishingTreasure>> fishingRewards = new HashMap<>();
public HashMap<Rarity, List<EnchantmentTreasure>> fishingEnchantments = new HashMap<>();
private TreasureConfig() {
super("treasures.yml");
super(FILENAME);
loadKeys();
validate();
}
@ -50,34 +45,6 @@ public class TreasureConfig extends ConfigLoader {
protected boolean validateKeys() {
// Validate all the settings!
List<String> reason = new ArrayList<>();
for (String tier : config.getConfigurationSection("Enchantment_Drop_Rates").getKeys(false)) {
double totalEnchantDropRate = 0;
double totalItemDropRate = 0;
for (Rarity rarity : Rarity.values()) {
double enchantDropRate = config.getDouble("Enchantment_Drop_Rates." + tier + "." + rarity.toString());
double itemDropRate = config.getDouble("Item_Drop_Rates." + tier + "." + rarity.toString());
if ((enchantDropRate < 0.0 || enchantDropRate > 100.0) && rarity != Rarity.RECORD) {
reason.add("The enchant drop rate for " + tier + " items that are " + rarity.toString() + "should be between 0.0 and 100.0!");
}
if (itemDropRate < 0.0 || itemDropRate > 100.0) {
reason.add("The item drop rate for " + tier + " items that are " + rarity.toString() + "should be between 0.0 and 100.0!");
}
totalEnchantDropRate += enchantDropRate;
totalItemDropRate += itemDropRate;
}
if (totalEnchantDropRate < 0 || totalEnchantDropRate > 100.0) {
reason.add("The total enchant drop rate for " + tier + " should be between 0.0 and 100.0!");
}
if (totalItemDropRate < 0 || totalItemDropRate > 100.0) {
reason.add("The total item drop rate for " + tier + " should be between 0.0 and 100.0!");
}
}
return noErrorsInConfig(reason);
}
@ -89,21 +56,11 @@ public class TreasureConfig extends ConfigLoader {
return;
}
loadTreasures("Fishing");
loadTreasures("Excavation");
loadTreasures("Hylian_Luck");
loadEnchantments();
for (EntityType entity : EntityType.values()) {
if (entity.isAlive()) {
loadTreasures("Shake." + entity.toString());
}
}
}
private void loadTreasures(String type) {
boolean isFishing = type.equals("Fishing");
boolean isShake = type.contains("Shake");
boolean isExcavation = type.equals("Excavation");
boolean isHylian = type.equals("Hylian_Luck");
@ -113,13 +70,6 @@ public class TreasureConfig extends ConfigLoader {
return;
}
// Initialize fishing HashMap
for (Rarity rarity : Rarity.values()) {
if (!fishingRewards.containsKey(rarity)) {
fishingRewards.put(rarity, (new ArrayList<>()));
}
}
for (String treasureName : treasureSection.getKeys(false)) {
// Validate all the things!
List<String> reason = new ArrayList<>();
@ -131,16 +81,7 @@ public class TreasureConfig extends ConfigLoader {
* Material, Amount, and Data
*/
Material material;
if (materialName.contains("INVENTORY")) {
// Use magic material BEDROCK to know that we're grabbing something from the inventory and not a normal treasure
if (!shakeMap.containsKey(EntityType.PLAYER))
shakeMap.put(EntityType.PLAYER, new ArrayList<>());
shakeMap.get(EntityType.PLAYER).add(new ShakeTreasure(new ItemStack(Material.BEDROCK, 1, (byte) 0), 1, getInventoryStealDropChance(), getInventoryStealDropLevel()));
continue;
} else {
material = Material.matchMaterial(materialName);
}
int amount = config.getInt(type + "." + treasureName + ".Amount");
short data = (treasureInfo.length == 2) ? Short.parseShort(treasureInfo[1]) : (short) config.getInt(type + "." + treasureName + ".Data");
@ -177,19 +118,6 @@ public class TreasureConfig extends ConfigLoader {
reason.add(treasureName + " has an invalid Drop_Level: " + dropLevel);
}
/*
* Specific Types
*/
Rarity rarity = null;
if (isFishing) {
rarity = Rarity.getRarity(config.getString(type + "." + treasureName + ".Rarity"));
if (rarity == null) {
reason.add("Invalid Rarity for item: " + treasureName);
}
}
/*
* Itemstack
*/
@ -198,7 +126,7 @@ public class TreasureConfig extends ConfigLoader {
if (materialName.contains("POTION")) {
Material mat = Material.matchMaterial(materialName);
if (mat == null) {
reason.add("Potion format for Treasures.yml has changed");
reason.add("Potion format for " + FILENAME + " has changed");
} else {
item = new ItemStack(mat, amount, data);
PotionMeta itemMeta = (PotionMeta) item.getItemMeta();
@ -247,16 +175,7 @@ public class TreasureConfig extends ConfigLoader {
}
if (noErrorsInConfig(reason)) {
if (isFishing) {
fishingRewards.get(rarity).add(new FishingTreasure(item, xp));
} else if (isShake) {
ShakeTreasure shakeTreasure = new ShakeTreasure(item, xp, dropChance, dropLevel);
EntityType entityType = EntityType.valueOf(type.substring(6));
if (!shakeMap.containsKey(entityType))
shakeMap.put(entityType, new ArrayList<>());
shakeMap.get(entityType).add(shakeTreasure);
} else if (isExcavation) {
if (isExcavation) {
ExcavationTreasure excavationTreasure = new ExcavationTreasure(item, xp, dropChance, dropLevel);
List<String> dropList = config.getStringList(type + "." + treasureName + ".Drops_From");
@ -308,58 +227,4 @@ public class TreasureConfig extends ConfigLoader {
hylianMap.put(dropper, new ArrayList<>());
hylianMap.get(dropper).add(treasure);
}
private void loadEnchantments() {
for (Rarity rarity : Rarity.values()) {
if (rarity == Rarity.RECORD) {
continue;
}
if (!fishingEnchantments.containsKey(rarity)) {
fishingEnchantments.put(rarity, (new ArrayList<>()));
}
ConfigurationSection enchantmentSection = config.getConfigurationSection("Enchantments_Rarity." + rarity.toString());
if (enchantmentSection == null) {
return;
}
for (String enchantmentName : enchantmentSection.getKeys(false)) {
int level = config.getInt("Enchantments_Rarity." + rarity.toString() + "." + enchantmentName);
Enchantment enchantment = EnchantmentUtils.getByName(enchantmentName);
if (enchantment == null) {
plugin.getLogger().warning("Skipping invalid enchantment in treasures.yml: " + enchantmentName);
continue;
}
fishingEnchantments.get(rarity).add(new EnchantmentTreasure(enchantment, level));
}
}
}
public boolean getInventoryStealEnabled() {
return config.contains("Shake.PLAYER.INVENTORY");
}
public boolean getInventoryStealStacks() {
return config.getBoolean("Shake.PLAYER.INVENTORY.Whole_Stacks");
}
public double getInventoryStealDropChance() {
return config.getDouble("Shake.PLAYER.INVENTORY.Drop_Chance");
}
public int getInventoryStealDropLevel() {
return config.getInt("Shake.PLAYER.INVENTORY.Drop_Level");
}
public double getItemDropRate(int tier, Rarity rarity) {
return config.getDouble("Item_Drop_Rates.Tier_" + tier + "." + rarity.toString());
}
public double getEnchantmentDropRate(int tier, Rarity rarity) {
return config.getDouble("Enchantment_Drop_Rates.Tier_" + tier + "." + rarity.toString());
}
}

View File

@ -0,0 +1,44 @@
package com.gmail.nossr50.datatypes.treasure;
import com.google.common.base.Objects;
import org.bukkit.enchantments.Enchantment;
import org.jetbrains.annotations.NotNull;
public class EnchantmentWrapper {
private final @NotNull Enchantment enchantment;
private final int enchantmentLevel;
public EnchantmentWrapper(@NotNull Enchantment enchantment, int enchantmentLevel) {
this.enchantment = enchantment;
this.enchantmentLevel = enchantmentLevel;
}
public @NotNull Enchantment getEnchantment() {
return enchantment;
}
public int getEnchantmentLevel() {
return enchantmentLevel;
}
@Override
public String toString() {
return "EnchantmentWrapper{" +
"enchantment=" + enchantment +
", enchantmentLevel=" + enchantmentLevel +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EnchantmentWrapper that = (EnchantmentWrapper) o;
return enchantmentLevel == that.enchantmentLevel && Objects.equal(enchantment, that.enchantment);
}
@Override
public int hashCode() {
return Objects.hashCode(enchantment, enchantmentLevel);
}
}

View File

@ -0,0 +1,76 @@
package com.gmail.nossr50.datatypes.treasure;
import com.gmail.nossr50.mcMMO;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class FishingTreasureBook extends FishingTreasure {
private final @Nullable Set<Enchantment> blackListedEnchantments;
private final @Nullable Set<Enchantment> whiteListedEnchantments;
private final @NotNull List<EnchantmentWrapper> legalEnchantments; //TODO: Make immutable
public FishingTreasureBook(@NotNull ItemStack enchantedBook, int xp, @Nullable Set<Enchantment> blackListedEnchantments, @Nullable Set<Enchantment> whiteListedEnchantments) {
super(enchantedBook, xp);
this.blackListedEnchantments = blackListedEnchantments;
this.whiteListedEnchantments = whiteListedEnchantments;
this.legalEnchantments = new ArrayList<>();
initLegalEnchantments();
}
private void initLegalEnchantments() {
mcMMO.p.getLogger().info("Registering enchantments for Fishing Book...");
for(Enchantment enchantment : Enchantment.values()) {
if(isEnchantAllowed(enchantment)) {
addAllLegalEnchants(enchantment);
}
}
}
/**
* Get all the enchantments which can drop for this book
* This list can be empty, but should in practice never be empty...
*
* @return all the enchantments that can drop for this book
*/
public @NotNull List<EnchantmentWrapper> getLegalEnchantments() {
return legalEnchantments;
}
private @Nullable Set<Enchantment> getBlacklistedEnchantments() {
return blackListedEnchantments;
}
private @Nullable Set<Enchantment> getWhitelistedEnchantments() {
return whiteListedEnchantments;
}
private void addAllLegalEnchants(@NotNull Enchantment enchantment) {
int legalEnchantCap = enchantment.getMaxLevel();
for(int i = 0; i < legalEnchantCap; i++) {
int enchantLevel = i+1;
EnchantmentWrapper enchantmentWrapper = new EnchantmentWrapper(enchantment, enchantLevel);
legalEnchantments.add(enchantmentWrapper);
// mcMMO.p.getLogger().info("Fishing treasure book enchantment added: " + enchantmentWrapper);
}
}
private boolean isEnchantAllowed(@NotNull Enchantment enchantment) {
if(whiteListedEnchantments != null && !whiteListedEnchantments.isEmpty()) {
return whiteListedEnchantments.contains(enchantment);
} else if(blackListedEnchantments != null && !blackListedEnchantments.isEmpty()) {
return !blackListedEnchantments.contains(enchantment);
} else {
return true;
}
}
}

View File

@ -1,14 +1,21 @@
package com.gmail.nossr50.datatypes.treasure;
import com.gmail.nossr50.mcMMO;
import org.jetbrains.annotations.NotNull;
public enum Rarity {
RECORD,
MYTHIC,
LEGENDARY,
EPIC,
RARE,
UNCOMMON,
COMMON;
public static Rarity getRarity(String string) {
public static @NotNull Rarity getRarity(@NotNull String string) {
if(string.equalsIgnoreCase("Records")) {
mcMMO.p.getLogger().severe("Entries in fishing treasures have Records set as rarity, however Records was renamed to Mythic. Please update your treasures to read MYTHIC instead of RECORDS for rarity, or delete the config file to regenerate a new one.");
return Rarity.MYTHIC; //People that copy paste their configs will have Records interpretted as Mythic
}
try {
return valueOf(string);
}

View File

@ -357,7 +357,8 @@ public class EntityListener implements Listener {
return;
}
if (Misc.isNPCEntityExcludingVillagers(defender) || !defender.isValid() || !(defender instanceof LivingEntity)) {
if ((ExperienceConfig.getInstance().isNPCInteractionPrevented() && Misc.isNPCEntityExcludingVillagers(defender)) || !defender.isValid() || !(defender instanceof LivingEntity)) {
return;
}
@ -367,7 +368,7 @@ public class EntityListener implements Listener {
return;
}
if (Misc.isNPCEntityExcludingVillagers(attacker)) {
if (ExperienceConfig.getInstance().isNPCInteractionPrevented() && Misc.isNPCEntityExcludingVillagers(attacker)) {
return;
}
@ -547,7 +548,7 @@ public class EntityListener implements Listener {
}
*/
if (Misc.isNPCEntityExcludingVillagers(entity) || !entity.isValid() || !(entity instanceof LivingEntity)) {
if ((ExperienceConfig.getInstance().isNPCInteractionPrevented() && Misc.isNPCEntityExcludingVillagers(entity)) || !entity.isValid() || !(entity instanceof LivingEntity)) {
return;
}
@ -694,7 +695,7 @@ public class EntityListener implements Listener {
LivingEntity entity = event.getEntity();
if (Misc.isNPCEntityExcludingVillagers(entity)) {
if (ExperienceConfig.getInstance().isNPCInteractionPrevented() && Misc.isNPCEntityExcludingVillagers(entity)) {
return;
}
@ -1002,7 +1003,7 @@ public class EntityListener implements Listener {
LivingEntity livingEntity = event.getEntity();
if (mcMMO.getUserManager().queryPlayer(player) == null
|| Misc.isNPCEntityExcludingVillagers(livingEntity)
|| (ExperienceConfig.getInstance().isNPCInteractionPrevented() && Misc.isNPCEntityExcludingVillagers(livingEntity))
|| persistentDataLayer.hasMobFlag(MobMetaFlagType.EGG_MOB, livingEntity)
|| persistentDataLayer.hasMobFlag(MobMetaFlagType.MOB_SPAWNER_MOB, livingEntity)) {
return;

View File

@ -420,7 +420,7 @@ public class PlayerListener implements Listener {
}
}
fishingManager.handleFishing((Item) caught);
fishingManager.processFishing((Item) caught);
fishingManager.setFishingTarget();
}
return;
@ -619,7 +619,7 @@ public class PlayerListener implements Listener {
if(clickedBlockType == Repair.anvilMaterial || clickedBlockType == Salvage.anvilMaterial) {
event.setUseItemInHand(Event.Result.ALLOW);
if(mcMMO.getMaterialMapStore().isToolActivationBlackListed(clickedBlockType)) {
if(!event.getPlayer().isSneaking() && mcMMO.getMaterialMapStore().isToolActivationBlackListed(clickedBlockType)) {
event.setUseInteractedBlock(Event.Result.DENY);
}
}
@ -781,7 +781,7 @@ public class PlayerListener implements Listener {
public void onPlayerChat(AsyncPlayerChatEvent event) {
Player player = event.getPlayer();
if (Misc.isNPCEntityExcludingVillagers(player) || !mcMMO.getUserManager().hasPlayerDataKey(player)) {
if ((ExperienceConfig.getInstance().isNPCInteractionPrevented() && Misc.isNPCEntityExcludingVillagers(player)) || !UserManager.hasPlayerDataKey(player)) {
return;
}

View File

@ -42,28 +42,6 @@ public class WorldListener implements Listener {
}
}
/**
* Monitor WorldInit events.
*
* @param event The event to watch
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onWorldInit(WorldInitEvent event) {
/* WORLD BLACKLIST CHECK */
if(WorldBlacklist.isWorldBlacklisted(event.getWorld()))
return;
World world = event.getWorld();
if (!new File(world.getWorldFolder(), "mcmmo_data").exists() || plugin == null) {
return;
}
plugin.getLogger().info("Converting block storage for " + world.getName() + " to a new format.");
//new BlockStoreConversionMain(world).run();
}
/**
* Monitor WorldUnload events.
*

View File

@ -7,6 +7,7 @@ import com.gmail.nossr50.config.experience.ExperienceConfig;
import com.gmail.nossr50.config.skills.alchemy.PotionConfig;
import com.gmail.nossr50.config.skills.repair.RepairConfigManager;
import com.gmail.nossr50.config.skills.salvage.SalvageConfigManager;
import com.gmail.nossr50.config.treasure.FishingTreasureConfig;
import com.gmail.nossr50.config.treasure.TreasureConfig;
import com.gmail.nossr50.database.DatabaseManager;
import com.gmail.nossr50.database.DatabaseManagerFactory;
@ -34,8 +35,8 @@ import com.gmail.nossr50.skills.salvage.salvageables.Salvageable;
import com.gmail.nossr50.skills.salvage.salvageables.SalvageableManager;
import com.gmail.nossr50.skills.salvage.salvageables.SimpleSalvageableManager;
import com.gmail.nossr50.util.*;
import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManager;
import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManagerFactory;
import com.gmail.nossr50.util.blockmeta.ChunkManager;
import com.gmail.nossr50.util.blockmeta.ChunkManagerFactory;
import com.gmail.nossr50.util.commands.CommandRegistrationManager;
import com.gmail.nossr50.util.compat.CompatibilityManager;
import com.gmail.nossr50.util.experience.FormulaManager;
@ -345,8 +346,8 @@ public class mcMMO extends JavaPlugin {
formulaManager.saveFormula();
holidayManager.saveAnniversaryFiles();
placeStore.saveAll(); // Save our metadata
placeStore.cleanUp(); // Cleanup empty metadata stores
placeStore.closeAll();
}
catch (Exception e) { e.printStackTrace(); }
@ -527,6 +528,7 @@ public class mcMMO extends JavaPlugin {
private void loadConfigFiles() {
// Force the loading of config files
TreasureConfig.getInstance();
FishingTreasureConfig.getInstance();
HiddenConfig.getInstance();
AdvancedConfig.getInstance();
PotionConfig.getInstance();

View File

@ -1,6 +1,6 @@
package com.gmail.nossr50.skills.fishing;
import com.gmail.nossr50.config.treasure.TreasureConfig;
import com.gmail.nossr50.config.treasure.FishingTreasureConfig;
import com.gmail.nossr50.datatypes.treasure.ShakeTreasure;
import com.gmail.nossr50.util.Misc;
import com.gmail.nossr50.util.adapter.BiomeAdapter;
@ -31,8 +31,8 @@ public final class Fishing {
* @return possibleDrops List of ItemStack that can be dropped
*/
protected static List<ShakeTreasure> findPossibleDrops(LivingEntity target) {
if (TreasureConfig.getInstance().shakeMap.containsKey(target.getType()))
return TreasureConfig.getInstance().shakeMap.get(target.getType());
if (FishingTreasureConfig.getInstance().shakeMap.containsKey(target.getType()))
return FishingTreasureConfig.getInstance().shakeMap.get(target.getType());
return null;
}

View File

@ -4,15 +4,13 @@ import com.gmail.nossr50.api.ItemSpawnReason;
import com.gmail.nossr50.config.AdvancedConfig;
import com.gmail.nossr50.config.Config;
import com.gmail.nossr50.config.experience.ExperienceConfig;
import com.gmail.nossr50.config.treasure.TreasureConfig;
import com.gmail.nossr50.config.treasure.FishingTreasureConfig;
import com.gmail.nossr50.datatypes.experience.XPGainReason;
import com.gmail.nossr50.datatypes.interactions.NotificationType;
import com.neetgames.mcmmo.player.OnlineMMOPlayer;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import com.gmail.nossr50.datatypes.skills.SubSkillType;
import com.gmail.nossr50.datatypes.treasure.EnchantmentTreasure;
import com.gmail.nossr50.datatypes.treasure.FishingTreasure;
import com.gmail.nossr50.datatypes.treasure.Rarity;
import com.gmail.nossr50.datatypes.treasure.ShakeTreasure;
import com.gmail.nossr50.datatypes.treasure.*;
import com.gmail.nossr50.events.skills.fishing.McMMOPlayerFishingTreasureEvent;
import com.gmail.nossr50.events.skills.fishing.McMMOPlayerShakeEvent;
import com.gmail.nossr50.locale.LocaleLoader;
@ -39,10 +37,12 @@ import org.bukkit.entity.*;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@ -369,9 +369,7 @@ public class FishingManager extends SkillManager {
return AdvancedConfig.getInstance().getFishingBoatReductionMaxWaitTicks();
}
public boolean isMagicHunterEnabled()
{
public boolean isMagicHunterEnabled() {
return RankUtils.hasUnlockedSubskill(getPlayer(), SubSkillType.FISHING_MAGIC_HUNTER)
&& RankUtils.hasUnlockedSubskill(getPlayer(), SubSkillType.FISHING_TREASURE_HUNTER)
&& Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.FISHING_TREASURE_HUNTER);
@ -382,12 +380,14 @@ public class FishingManager extends SkillManager {
*
* @param fishingCatch The {@link Item} initially caught
*/
public void handleFishing(Item fishingCatch) {
public void processFishing(@NotNull Item fishingCatch) {
this.fishingCatch = fishingCatch;
int fishXp = ExperienceConfig.getInstance().getXp(PrimarySkillType.FISHING, fishingCatch.getItemStack().getType());
int treasureXp = 0;
ItemStack treasureDrop = null;
Player player = getPlayer();
FishingTreasure treasure = null;
boolean fishingSucceeds = false;
if (Config.getInstance().getFishingDropsEnabled() && Permissions.isSubSkillEnabled(player, SubSkillType.FISHING_TREASURE_HUNTER)) {
treasure = getFishingTreasure();
@ -395,30 +395,45 @@ public class FishingManager extends SkillManager {
}
if (treasure != null) {
ItemStack treasureDrop = treasure.getDrop().clone(); // Not cloning is bad, m'kay?
Map<Enchantment, Integer> enchants = new HashMap<>();
if(treasure instanceof FishingTreasureBook) {
treasureDrop = createEnchantBook((FishingTreasureBook) treasure);
} else {
treasureDrop = treasure.getDrop().clone(); // Not cloning is bad, m'kay?
if (isMagicHunterEnabled()
&& ItemUtils.isEnchantable(treasureDrop)) {
enchants = handleMagicHunter(treasureDrop);
}
Map<Enchantment, Integer> enchants = new HashMap<>();
McMMOPlayerFishingTreasureEvent event;
/*
* Books get some special treatment
*/
if(treasure instanceof FishingTreasureBook) {
//Skip the magic hunter stuff
if(treasureDrop.getItemMeta() != null) {
enchants.putAll(treasureDrop.getItemMeta().getEnchants());
}
McMMOPlayerFishingTreasureEvent event = EventUtils.callFishingTreasureEvent(player, treasureDrop, treasure.getXp(), enchants);
event = EventUtils.callFishingTreasureEvent(player, treasureDrop, treasure.getXp(), enchants);
} else {
if (isMagicHunterEnabled() && ItemUtils.isEnchantable(treasureDrop)) {
enchants = processMagicHunter(treasureDrop);
}
event = EventUtils.callFishingTreasureEvent(player, treasureDrop, treasure.getXp(), enchants);
}
if (!event.isCancelled()) {
treasureDrop = event.getTreasure();
treasureXp = event.getXp();
}
else {
treasureDrop = null;
treasureXp = 0;
}
// Drop the original catch at the feet of the player and set the treasure as the real catch
if (treasureDrop != null) {
fishingSucceeds = true;
boolean enchanted = false;
if (!enchants.isEmpty()) {
if(treasure instanceof FishingTreasureBook) {
enchanted = true;
} else if (!enchants.isEmpty()) {
treasureDrop.addUnsafeEnchantments(enchants);
enchanted = true;
}
@ -427,17 +442,45 @@ public class FishingManager extends SkillManager {
NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Fishing.Ability.TH.MagicFound");
}
if (Config.getInstance().getFishingExtraFish()) {
Misc.spawnItem(player.getEyeLocation(), fishingCatch.getItemStack(), ItemSpawnReason.FISHING_EXTRA_FISH);
}
} else {
treasureDrop = null;
treasureXp = 0;
}
}
if(fishingSucceeds) {
fishingCatch.setItemStack(treasureDrop);
if (Config.getInstance().getFishingExtraFish()) {
Misc.spawnItem(player.getEyeLocation(), fishingCatch.getItemStack(), ItemSpawnReason.FISHING_EXTRA_FISH);
}
}
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
*
@ -487,7 +530,7 @@ public class FishingManager extends SkillManager {
break;
case BEDROCK:
if (TreasureConfig.getInstance().getInventoryStealEnabled()) {
if (FishingTreasureConfig.getInstance().getInventoryStealEnabled()) {
PlayerInventory inventory = targetPlayer.getInventory();
int length = inventory.getContents().length;
int slot = Misc.getRandom().nextInt(length);
@ -497,7 +540,7 @@ public class FishingManager extends SkillManager {
break;
}
if (TreasureConfig.getInstance().getInventoryStealStacks()) {
if (FishingTreasureConfig.getInstance().getInventoryStealStacks()) {
inventory.setItem(slot, null);
}
else {
@ -547,7 +590,7 @@ public class FishingManager extends SkillManager {
*
* @return The {@link FishingTreasure} found, or null if no treasure was found.
*/
private FishingTreasure getFishingTreasure() {
private @Nullable FishingTreasure getFishingTreasure() {
double diceRoll = Misc.getRandom().nextDouble() * 100;
int luck;
@ -565,15 +608,11 @@ public class FishingManager extends SkillManager {
FishingTreasure treasure = null;
for (Rarity rarity : Rarity.values()) {
double dropRate = TreasureConfig.getInstance().getItemDropRate(getLootTier(), rarity);
double dropRate = FishingTreasureConfig.getInstance().getItemDropRate(getLootTier(), rarity);
if (diceRoll <= dropRate) {
/*if (rarity == Rarity.TRAP) {
handleTraps();
break;
}*/
List<FishingTreasure> fishingTreasures = TreasureConfig.getInstance().fishingRewards.get(rarity);
List<FishingTreasure> fishingTreasures = FishingTreasureConfig.getInstance().fishingRewards.get(rarity);
if (fishingTreasures.isEmpty()) {
return null;
@ -611,21 +650,16 @@ public class FishingManager extends SkillManager {
* Process the Magic Hunter ability
*
* @param treasureDrop The {@link ItemStack} to enchant
*
* @return true if the item has been enchanted
*/
private Map<Enchantment, Integer> handleMagicHunter(ItemStack treasureDrop) {
private Map<Enchantment, Integer> processMagicHunter(@NotNull ItemStack treasureDrop) {
Map<Enchantment, Integer> enchants = new HashMap<>();
List<EnchantmentTreasure> fishingEnchantments = null;
double diceRoll = Misc.getRandom().nextDouble() * 100;
for (Rarity rarity : Rarity.values()) {
if (rarity == Rarity.RECORD) {
continue;
}
double dropRate = TreasureConfig.getInstance().getEnchantmentDropRate(getLootTier(), rarity);
double dropRate = FishingTreasureConfig.getInstance().getEnchantmentDropRate(getLootTier(), rarity);
if (diceRoll <= dropRate) {
// Make sure enchanted books always get some kind of enchantment. --hoorigan
@ -633,7 +667,8 @@ public class FishingManager extends SkillManager {
diceRoll = dropRate + 1;
continue;
}
fishingEnchantments = TreasureConfig.getInstance().fishingEnchantments.get(rarity);
fishingEnchantments = FishingTreasureConfig.getInstance().fishingEnchantments.get(rarity);
break;
}

View File

@ -0,0 +1,243 @@
package com.gmail.nossr50.util.blockmeta;
import org.bukkit.Bukkit;
import org.bukkit.World;
import java.io.*;
import java.util.BitSet;
import java.util.UUID;
public class BitSetChunkStore implements ChunkStore, Serializable {
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 MAGIC_NUMBER = 0xEA5EDEBB;
private int cx;
private int cz;
private int worldHeight;
private UUID worldUid;
public BitSetChunkStore(World world, int cx, int cz) {
this.cx = cx;
this.cz = cz;
this.worldUid = world.getUID();
this.worldHeight = world.getMaxHeight();
this.store = new BitSet(16 * 16 * worldHeight);
}
private BitSetChunkStore() {}
@Override
public boolean isDirty() {
return dirty;
}
@Override
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
@Override
public int getChunkX() {
return cx;
}
@Override
public int getChunkZ() {
return cz;
}
@Override
public UUID getWorldId() {
return worldUid;
}
@Override
public boolean isTrue(int x, int y, int z) {
return store.get(coordToIndex(x, y, z));
}
@Override
public void setTrue(int x, int y, int z) {
set(x, y, z, true);
}
@Override
public void setFalse(int x, int y, int z) {
set(x, y, z, false);
}
@Override
public void set(int x, int y, int z, boolean value) {
store.set(coordToIndex(x, y, z), value);
dirty = true;
}
@Override
public boolean isEmpty() {
return store.isEmpty();
}
private int coordToIndex(int x, int y, int z) {
if (x < 0 || x >= 16 || y < 0 || y >= worldHeight || z < 0 || z >= 16)
throw new IndexOutOfBoundsException();
return (z * 16 + x) + (256 * y);
}
private void fixWorldHeight() {
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?
if (world == null)
return;
// Lop off any extra data if the world height has shrunk
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 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(CURRENT_VERSION);
out.writeLong(worldUid.getLeastSignificantBits());
out.writeLong(worldUid.getMostSignificantBits());
out.writeInt(cx);
out.writeInt(cz);
out.writeInt(worldHeight);
// Store the byte array directly so we don't have the object type info overhead
byte[] storeData = store.toByteArray();
out.writeInt(storeData.length);
out.write(storeData);
dirty = false;
}
private static BitSetChunkStore deserialize(DataInputStream in) throws IOException {
int magic = in.readInt();
// Can be used to determine the format of the file
int fileVersionNumber = in.readInt();
if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION)
throw new IOException();
BitSetChunkStore chunkStore = new BitSetChunkStore();
long lsb = in.readLong();
long msb = in.readLong();
chunkStore.worldUid = new UUID(msb, lsb);
chunkStore.cx = in.readInt();
chunkStore.cz = in.readInt();
chunkStore.worldHeight = in.readInt();
byte[] temp = new byte[in.readInt()];
in.readFully(temp);
chunkStore.store = BitSet.valueOf(temp);
chunkStore.fixWorldHeight();
return chunkStore;
}
public static class Serialization {
public static final short STREAM_MAGIC = (short)0xACDC;
public static ChunkStore readChunkStore(DataInputStream inputStream) throws IOException {
if (inputStream.markSupported())
inputStream.mark(2);
short magicNumber = inputStream.readShort();
if (magicNumber == ObjectStreamConstants.STREAM_MAGIC) // Java serializable, use legacy serialization
{
// "Un-read" the magic number for Serializables, they need it to still be in the stream
if (inputStream.markSupported())
inputStream.reset(); // Pretend we never read those bytes
else
{
// 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.unread((magicNumber >>> 0) & 0xFF);
pushbackInputStream.unread((magicNumber >>> 8) & 0xFF);
inputStream = new DataInputStream(pushbackInputStream);
}
return new LegacyDeserializationInputStream(inputStream).readLegacyChunkStore();
}
else if (magicNumber == STREAM_MAGIC) // Pure bytes format
{
return BitSetChunkStore.deserialize(inputStream);
}
throw new IOException("Bad Data Format");
}
public static void writeChunkStore(DataOutputStream outputStream, ChunkStore chunkStore) throws IOException {
if (!(chunkStore instanceof BitSetChunkStore))
throw new InvalidClassException("ChunkStore must be instance of BitSetChunkStore");
outputStream.writeShort(STREAM_MAGIC);
((BitSetChunkStore)chunkStore).serialize(outputStream);
}
// Handles loading the old serialized classes even though we have changed name/package
private static class LegacyDeserializationInputStream extends ObjectInputStream {
public LegacyDeserializationInputStream(InputStream in) throws IOException {
super(in);
enableResolveObject(true);
}
@Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass read = super.readClassDescriptor();
if (read.getName().contentEquals("com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore"))
return ObjectStreamClass.lookup(BitSetChunkStore.class);
return read;
}
public ChunkStore readLegacyChunkStore(){
try {
return (ChunkStore) readObject();
} catch (IOException | ClassNotFoundException e) {
return null;
}
}
}
}
}

View File

@ -1,59 +1,12 @@
package com.gmail.nossr50.util.blockmeta.chunkmeta;
package com.gmail.nossr50.util.blockmeta;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Entity;
import java.io.IOException;
public interface ChunkManager {
void closeAll();
ChunkStore readChunkStore(World world, int x, int z) throws IOException;
void writeChunkStore(World world, int x, int z, ChunkStore data);
void closeChunkStore(World world, int x, int z);
/**
* Loads a specific chunklet
*
* @param cx Chunklet X coordinate that needs to be loaded
* @param cy Chunklet Y coordinate that needs to be loaded
* @param cz Chunklet Z coordinate that needs to be loaded
* @param world World that the chunklet needs to be loaded in
*/
void loadChunklet(int cx, int cy, int cz, World world);
/**
* Unload a specific chunklet
*
* @param cx Chunklet X coordinate that needs to be unloaded
* @param cy Chunklet Y coordinate that needs to be unloaded
* @param cz Chunklet Z coordinate that needs to be unloaded
* @param world World that the chunklet needs to be unloaded from
*/
void unloadChunklet(int cx, int cy, int cz, World world);
/**
* Load a given Chunk's Chunklet data
*
* @param cx Chunk X coordinate that is to be loaded
* @param cz Chunk Z coordinate that is to be loaded
* @param world World that the Chunk is in
*/
void loadChunk(int cx, int cz, World world, Entity[] entities);
/**
* Unload a given Chunk's Chunklet data
*
* @param cx Chunk X coordinate that is to be unloaded
* @param cz Chunk Z coordinate that is to be unloaded
* @param world World that the Chunk is in
*/
void unloadChunk(int cx, int cz, World world);
/**
* Saves a given Chunk's Chunklet data
*
@ -63,17 +16,6 @@ public interface ChunkManager {
*/
void saveChunk(int cx, int cz, World world);
boolean isChunkLoaded(int cx, int cz, World world);
/**
* Informs the ChunkletManager a chunk is loaded
*
* @param cx Chunk X coordinate that is loaded
* @param cz Chunk Z coordinate that is loaded
* @param world World that the chunk was loaded in
*/
void chunkLoaded(int cx, int cz, World world);
/**
* Informs the ChunkletManager a chunk is unloaded
*
@ -97,23 +39,11 @@ public interface ChunkManager {
*/
void unloadWorld(World world);
/**
* Load all ChunkletStores from all loaded chunks from this world into memory
*
* @param world World to load
*/
void loadWorld(World world);
/**
* Save all ChunkletStores
*/
void saveAll();
/**
* Unload all ChunkletStores after saving them
*/
void unloadAll();
/**
* Check to see if a given location is set to true
*

View File

@ -1,4 +1,4 @@
package com.gmail.nossr50.util.blockmeta.chunkmeta;
package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.config.HiddenConfig;

View File

@ -1,13 +1,13 @@
package com.gmail.nossr50.util.blockmeta.chunkmeta;
package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.util.blockmeta.ChunkletStore;
import org.bukkit.World;
import java.io.Serializable;
import java.util.UUID;
/**
* A ChunkStore should be responsible for a 16x16xWorldHeight area of data
*/
public interface ChunkStore extends Serializable {
public interface ChunkStore {
/**
* Checks the chunk's save state
*
@ -36,6 +36,8 @@ public interface ChunkStore extends Serializable {
*/
int getChunkZ();
UUID getWorldId();
/**
* Checks the value at the given coordinates
*
@ -64,15 +66,18 @@ public interface ChunkStore extends Serializable {
*/
void setFalse(int x, int y, int z);
/**
* Set the value at the given coordinates
*
* @param x x coordinate in current chunklet
* @param y y coordinate in current chunklet
* @param z z coordinate in current chunklet
* @param value value to set
*/
void set(int x, int y, int z, boolean value);
/**
* @return true if all values in the chunklet are false, false if otherwise
*/
boolean isEmpty();
/**
* Set all values in this ChunkletStore to the values from another provided ChunkletStore
*
* @param otherStore Another ChunkletStore that this one should copy all data from
*/
void copyFrom(ChunkletStore otherStore);
}

View File

@ -1,151 +0,0 @@
package com.gmail.nossr50.util.blockmeta;
import org.bukkit.World;
import org.bukkit.block.Block;
public interface ChunkletManager {
/**
* Loads a specific chunklet
*
* @param cx Chunklet X coordinate that needs to be loaded
* @param cy Chunklet Y coordinate that needs to be loaded
* @param cz Chunklet Z coordinate that needs to be loaded
* @param world World that the chunklet needs to be loaded in
*/
void loadChunklet(int cx, int cy, int cz, World world);
/**
* Unload a specific chunklet
*
* @param cx Chunklet X coordinate that needs to be unloaded
* @param cy Chunklet Y coordinate that needs to be unloaded
* @param cz Chunklet Z coordinate that needs to be unloaded
* @param world World that the chunklet needs to be unloaded from
*/
void unloadChunklet(int cx, int cy, int cz, World world);
/**
* Load a given Chunk's Chunklet data
*
* @param cx Chunk X coordinate that is to be loaded
* @param cz Chunk Z coordinate that is to be loaded
* @param world World that the Chunk is in
*/
void loadChunk(int cx, int cz, World world);
/**
* Unload a given Chunk's Chunklet data
*
* @param cx Chunk X coordinate that is to be unloaded
* @param cz Chunk Z coordinate that is to be unloaded
* @param world World that the Chunk is in
*/
void unloadChunk(int cx, int cz, World world);
/**
* Informs the ChunkletManager a chunk is loaded
*
* @param cx Chunk X coordinate that is loaded
* @param cz Chunk Z coordinate that is loaded
* @param world World that the chunk was loaded in
*/
void chunkLoaded(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);
/**
* Load all ChunkletStores from all loaded chunks from this world into memory
*
* @param world World to load
*/
void loadWorld(World world);
/**
* Save all ChunkletStores
*/
void saveAll();
/**
* Unload all ChunkletStores after saving them
*/
void unloadAll();
/**
* 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);
/**
* 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 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);
/**
* Delete any ChunkletStores that are empty
*/
void cleanUp();
}

View File

@ -1,15 +0,0 @@
package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.config.HiddenConfig;
public class ChunkletManagerFactory {
public static ChunkletManager getChunkletManager() {
HiddenConfig hConfig = HiddenConfig.getInstance();
if (hConfig.getChunkletsEnabled()) {
return new HashChunkletManager();
}
return new NullChunkletManager();
}
}

View File

@ -1,48 +0,0 @@
package com.gmail.nossr50.util.blockmeta;
import java.io.Serializable;
/**
* A ChunkletStore should be responsible for a 16x16x64 area of data
*/
public interface ChunkletStore extends Serializable {
/**
* Checks the value at the given coordinates
*
* @param x x coordinate in current chunklet
* @param y y coordinate in current chunklet
* @param z z coordinate in current chunklet
* @return true if the value is true at the given coordinates, false if otherwise
*/
boolean isTrue(int x, int y, int z);
/**
* Set the value to true at the given coordinates
*
* @param x x coordinate in current chunklet
* @param y y coordinate in current chunklet
* @param z z coordinate in current chunklet
*/
void setTrue(int x, int y, int z);
/**
* Set the value to false at the given coordinates
*
* @param x x coordinate in current chunklet
* @param y y coordinate in current chunklet
* @param z z coordinate in current chunklet
*/
void setFalse(int x, int y, int z);
/**
* @return true if all values in the chunklet are false, false if otherwise
*/
boolean isEmpty();
/**
* Set all values in this ChunkletStore to the values from another provided ChunkletStore
*
* @param otherStore Another ChunkletStore that this one should copy all data from
*/
void copyFrom(ChunkletStore otherStore);
}

View File

@ -1,8 +0,0 @@
package com.gmail.nossr50.util.blockmeta;
public class ChunkletStoreFactory {
protected static ChunkletStore getChunkletStore() {
// TODO: Add in loading from config what type of store we want.
return new PrimitiveExChunkletStore();
}
}

View File

@ -0,0 +1,354 @@
package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.mcMMO;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import java.io.*;
import java.util.*;
public class HashChunkManager implements ChunkManager {
private final HashMap<CoordinateKey, McMMOSimpleRegionFile> regionMap = new HashMap<>(); // Tracks active regions
private final HashMap<CoordinateKey, HashSet<CoordinateKey>> chunkUsageMap = new HashMap<>(); // Tracks active chunks by region
private final HashMap<CoordinateKey, ChunkStore> chunkMap = new HashMap<>(); // Tracks active chunks
@Override
public synchronized void closeAll() {
// Save all dirty chunkstores
for (ChunkStore chunkStore : chunkMap.values())
{
if (!chunkStore.isDirty())
continue;
writeChunkStore(Bukkit.getWorld(chunkStore.getWorldId()), chunkStore);
}
// Clear in memory chunks
chunkMap.clear();
chunkUsageMap.clear();
// Close all region files
for (McMMOSimpleRegionFile rf : regionMap.values())
rf.close();
regionMap.clear();
}
private synchronized ChunkStore readChunkStore(World world, int cx, int cz) throws IOException {
McMMOSimpleRegionFile rf = getSimpleRegionFile(world, cx, cz, false);
if (rf == null)
return null; // If there is no region file, there can't be a chunk
try (DataInputStream in = rf.getInputStream(cx, cz)) { // Get input stream for chunk
if (in == null)
return null; // No chunk
return BitSetChunkStore.Serialization.readChunkStore(in); // Read in the chunkstore
}
}
private synchronized void writeChunkStore(World world, ChunkStore data) {
if (!data.isDirty())
return; // Don't save unchanged data
try {
McMMOSimpleRegionFile rf = getSimpleRegionFile(world, data.getChunkX(), data.getChunkZ(), true);
try (DataOutputStream out = rf.getOutputStream(data.getChunkX(), data.getChunkZ())) {
BitSetChunkStore.Serialization.writeChunkStore(out, data);
}
data.setDirty(false);
}
catch (IOException e) {
throw new RuntimeException("Unable to write chunk meta data for " + data.getChunkX() + ", " + data.getChunkZ(), e);
}
}
private synchronized McMMOSimpleRegionFile getSimpleRegionFile(World world, int cx, int cz, boolean createIfAbsent) {
CoordinateKey regionKey = toRegionKey(world.getUID(), cx, cz);
return regionMap.computeIfAbsent(regionKey, k -> {
File worldRegionsDirectory = new File(world.getWorldFolder(), "mcmmo_regions");
if (!createIfAbsent && !worldRegionsDirectory.isDirectory())
return null; // Don't create the directory on read-only operations
worldRegionsDirectory.mkdirs(); // Ensure directory exists
File regionFile = new File(worldRegionsDirectory, "mcmmo_" + regionKey.x + "_" + regionKey.z + "_.mcm");
if (!createIfAbsent && !regionFile.exists())
return null; // Don't create the file on read-only operations
return new McMMOSimpleRegionFile(regionFile, regionKey.x, regionKey.z);
});
}
private ChunkStore loadChunk(int cx, int cz, World world) {
try {
return readChunkStore(world, cx, cz);
}
catch (Exception ignored) {}
return null;
}
private void unloadChunk(int cx, int cz, World world) {
CoordinateKey chunkKey = toChunkKey(world.getUID(), cx, cz);
ChunkStore chunkStore = chunkMap.remove(chunkKey); // Remove from chunk map
if (chunkStore == null)
return;
if (chunkStore.isDirty())
writeChunkStore(world, chunkStore);
CoordinateKey regionKey = toRegionKey(world.getUID(), cx, cz);
HashSet<CoordinateKey> chunkKeys = chunkUsageMap.get(regionKey);
chunkKeys.remove(chunkKey); // remove from region file in-use set
if (chunkKeys.isEmpty()) // If it was last chunk in region, close the region file and remove it from memory
{
chunkUsageMap.remove(regionKey);
regionMap.remove(regionKey).close();
}
}
@Override
public synchronized void saveChunk(int cx, int cz, 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);
}
@Override
public synchronized void saveWorld(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();
// Save and remove all the chunks
List<CoordinateKey> chunkKeys = new ArrayList<>(chunkMap.keySet());
for (CoordinateKey chunkKey : chunkKeys) {
if (!wID.equals(chunkKey.worldID))
continue;
ChunkStore chunkStore = chunkMap.remove(chunkKey);
if (!chunkStore.isDirty())
continue;
try {
writeChunkStore(world, chunkStore);
}
catch (Exception ignore) { }
}
// Clear all the region files
List<CoordinateKey> regionKeys = new ArrayList<>(regionMap.keySet());
for (CoordinateKey regionKey : regionKeys) {
if (!wID.equals(regionKey.worldID))
continue;
regionMap.remove(regionKey).close();
chunkUsageMap.remove(regionKey);
}
}
@Override
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);
// Get chunk, load from file if necessary
// Get/Load/Create chunkstore
ChunkStore check = chunkMap.computeIfAbsent(chunkKey, k -> {
// Load from file
ChunkStore loaded = loadChunk(chunkKey.x, chunkKey.z, world);
if (loaded == null)
return null;
// Mark chunk in-use for region tracking
chunkUsageMap.computeIfAbsent(toRegionKey(chunkKey.worldID, chunkKey.x, chunkKey.z), j -> new HashSet<>()).add(chunkKey);
return loaded;
});
// No chunk, return false
if (check == null)
return false;
int ix = Math.abs(x) % 16;
int iz = Math.abs(z) % 16;
return check.isTrue(ix, y, iz);
}
@Override
public synchronized boolean isTrue(Block block) {
if (block == null)
return false;
return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
}
@Override
public synchronized boolean isTrue(BlockState blockState) {
if (blockState == null)
return false;
return isTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
}
@Override
public synchronized void setTrue(int x, int y, int z, World world) {
set(x, y, z, world, true);
}
@Override
public synchronized void setTrue(Block block) {
if (block == null)
return;
setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
}
@Override
public synchronized void setTrue(BlockState blockState) {
if (blockState == null)
return;
setTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
}
@Override
public synchronized void setFalse(int x, int y, int z, World world) {
set(x, y, z, world, false);
}
@Override
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);
// Get/Load/Create chunkstore
ChunkStore cStore = chunkMap.computeIfAbsent(chunkKey, k -> {
// Load from file
ChunkStore loaded = loadChunk(chunkKey.x, chunkKey.z, world);
if (loaded != null)
{
chunkUsageMap.computeIfAbsent(toRegionKey(chunkKey.worldID, chunkKey.x, chunkKey.z), j -> new HashSet<>()).add(chunkKey);
return loaded;
}
// If setting to false, no need to create an empty chunkstore
if (!value)
return null;
// Mark chunk in-use for region tracking
chunkUsageMap.computeIfAbsent(toRegionKey(chunkKey.worldID, chunkKey.x, chunkKey.z), j -> new HashSet<>()).add(chunkKey);
// Create a new chunkstore
return new BitSetChunkStore(world, chunkKey.x, chunkKey.z);
});
// Indicates setting false on empty chunkstore
if (cStore == null)
return;
// Get block offset (offset from chunk corner)
int ix = Math.abs(x) % 16;
int iz = Math.abs(z) % 16;
// Set chunk store value
cStore.set(ix, y, iz, value);
}
private CoordinateKey blockCoordinateToChunkKey(UUID worldUid, int x, int y, int z) {
return toChunkKey(worldUid, x >> 4, z >> 4);
}
private CoordinateKey toChunkKey(UUID worldUid, int cx, int cz){
return new CoordinateKey(worldUid, cx, cz);
}
private CoordinateKey toRegionKey(UUID worldUid, int cx, int cz) {
// Compute region index (32x32 chunk regions)
int rx = cx >> 5;
int rz = cz >> 5;
return new CoordinateKey(worldUid, rx, rz);
}
private static final class CoordinateKey {
public final UUID worldID;
public final int x;
public final int z;
private CoordinateKey(UUID worldID, int x, int z) {
this.worldID = worldID;
this.x = x;
this.z = z;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CoordinateKey coordinateKey = (CoordinateKey) o;
return x == coordinateKey.x &&
z == coordinateKey.z &&
worldID.equals(coordinateKey.worldID);
}
@Override
public int hashCode() {
return Objects.hash(worldID, x, z);
}
}
@Override
public synchronized void cleanUp() {}
}

View File

@ -1,410 +0,0 @@
package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.mcMMO;
import org.bukkit.World;
import org.bukkit.block.Block;
import java.io.*;
import java.util.HashMap;
public class HashChunkletManager implements ChunkletManager {
public HashMap<String, ChunkletStore> store = new HashMap<>();
@Override
public void loadChunklet(int cx, int cy, int cz, World world) {
File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
File cxDir = new File(dataDir, "" + cx);
if (!cxDir.exists()) {
return;
}
File czDir = new File(cxDir, "" + cz);
if (!czDir.exists()) {
return;
}
File yFile = new File(czDir, "" + cy);
if (!yFile.exists()) {
return;
}
ChunkletStore in = deserializeChunkletStore(yFile);
if (in != null) {
store.put(world.getName() + "," + cx + "," + cz + "," + cy, in);
}
}
@Override
public void unloadChunklet(int cx, int cy, int cz, World world) {
File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
if (store.containsKey(world.getName() + "," + cx + "," + cz + "," + cy)) {
File cxDir = new File(dataDir, "" + cx);
if (!cxDir.exists()) {
cxDir.mkdir();
}
File czDir = new File(cxDir, "" + cz);
if (!czDir.exists()) {
czDir.mkdir();
}
File yFile = new File(czDir, "" + cy);
ChunkletStore out = store.get(world.getName() + "," + cx + "," + cz + "," + cy);
serializeChunkletStore(out, yFile);
store.remove(world.getName() + "," + cx + "," + cz + "," + cy);
}
}
@Override
public void loadChunk(int cx, int cz, World world) {
File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
File cxDir = new File(dataDir, "" + cx);
if (!cxDir.exists()) {
return;
}
File czDir = new File(cxDir, "" + cz);
if (!czDir.exists()) {
return;
}
for (int y = 0; y < 4; y++) {
File yFile = new File(czDir, "" + y);
if (!yFile.exists()) {
continue;
}
ChunkletStore in = deserializeChunkletStore(yFile);
if (in != null) {
store.put(world.getName() + "," + cx + "," + cz + "," + y, in);
}
}
}
@Override
public void unloadChunk(int cx, int cz, World world) {
File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
for (int y = 0; y < 4; y++) {
if (store.containsKey(world.getName() + "," + cx + "," + cz + "," + y)) {
File cxDir = new File(dataDir, "" + cx);
if (!cxDir.exists()) {
cxDir.mkdir();
}
File czDir = new File(cxDir, "" + cz);
if (!czDir.exists()) {
czDir.mkdir();
}
File yFile = new File(czDir, "" + y);
ChunkletStore out = store.get(world.getName() + "," + cx + "," + cz + "," + y);
serializeChunkletStore(out, yFile);
store.remove(world.getName() + "," + cx + "," + cz + "," + y);
}
}
}
@Override
public void chunkLoaded(int cx, int cz, World world) {
//loadChunk(cx, cz, world);
}
@Override
public void chunkUnloaded(int cx, int cz, World world) {
unloadChunk(cx, cx, world);
}
@Override
public void saveWorld(World world) {
String worldName = world.getName();
File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
if (!dataDir.exists()) {
dataDir.mkdirs();
}
for (String key : store.keySet()) {
String[] info = key.split(",");
if (worldName.equals(info[0])) {
File cxDir = new File(dataDir, "" + info[1]);
if (!cxDir.exists()) {
cxDir.mkdir();
}
File czDir = new File(cxDir, "" + info[2]);
if (!czDir.exists()) {
czDir.mkdir();
}
File yFile = new File(czDir, "" + info[3]);
serializeChunkletStore(store.get(key), yFile);
}
}
}
@Override
public void unloadWorld(World world) {
saveWorld(world);
String worldName = world.getName();
for (String key : store.keySet()) {
String tempWorldName = key.split(",")[0];
if (tempWorldName.equals(worldName)) {
store.remove(key);
return;
}
}
}
@Override
public void loadWorld(World world) {
//for (Chunk chunk : world.getLoadedChunks()) {
// this.chunkLoaded(chunk.getX(), chunk.getZ(), world);
//}
}
@Override
public void saveAll() {
for (World world : mcMMO.p.getServer().getWorlds()) {
saveWorld(world);
}
}
@Override
public void unloadAll() {
saveAll();
for (World world : mcMMO.p.getServer().getWorlds()) {
unloadWorld(world);
}
}
@Override
public boolean isTrue(int x, int y, int z, World world) {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 6;
String key = world.getName() + "," + cx + "," + cz + "," + cy;
if (!store.containsKey(key)) {
loadChunklet(cx, cy, cz, world);
}
if (!store.containsKey(key)) {
return false;
}
ChunkletStore check = store.get(world.getName() + "," + cx + "," + cz + "," + cy);
int ix = Math.abs(x) % 16;
int iz = Math.abs(z) % 16;
int iy = Math.abs(y) % 64;
return check.isTrue(ix, iy, iz);
}
@Override
public boolean isTrue(Block block) {
return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
}
@Override
public void setTrue(int x, int y, int z, World world) {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 6;
int ix = Math.abs(x) % 16;
int iz = Math.abs(z) % 16;
int iy = Math.abs(y) % 64;
String key = world.getName() + "," + cx + "," + cz + "," + cy;
if (!store.containsKey(key)) {
loadChunklet(cx, cy, cz, world);
}
ChunkletStore cStore = store.get(key);
if (cStore == null) {
cStore = ChunkletStoreFactory.getChunkletStore();
store.put(world.getName() + "," + cx + "," + cz + "," + cy, cStore);
}
cStore.setTrue(ix, iy, iz);
}
@Override
public void setTrue(Block block) {
setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
}
@Override
public void setFalse(int x, int y, int z, World world) {
int cx = x >> 4;
int cz = z >> 4;
int cy = y >> 6;
int ix = Math.abs(x) % 16;
int iz = Math.abs(z) % 16;
int iy = Math.abs(y) % 64;
String key = world.getName() + "," + cx + "," + cz + "," + cy;
if (!store.containsKey(key)) {
loadChunklet(cx, cy, cz, world);
}
ChunkletStore cStore = store.get(key);
if (cStore == null) {
return; // No need to make a store for something we will be setting to false
}
cStore.setFalse(ix, iy, iz);
}
@Override
public void setFalse(Block block) {
setFalse(block.getX(), block.getY(), block.getZ(), block.getWorld());
}
@Override
public void cleanUp() {
for (String key : store.keySet()) {
if (store.get(key).isEmpty()) {
String[] info = key.split(",");
File dataDir = new File(mcMMO.p.getServer().getWorld(info[0]).getWorldFolder(), "mcmmo_data");
File cxDir = new File(dataDir, "" + info[1]);
if (!cxDir.exists()) {
continue;
}
File czDir = new File(cxDir, "" + info[2]);
if (!czDir.exists()) {
continue;
}
File yFile = new File(czDir, "" + info[3]);
yFile.delete();
// Delete empty directories
if (czDir.list().length == 0) {
czDir.delete();
}
if (cxDir.list().length == 0) {
cxDir.delete();
}
}
}
}
/**
* @param cStore ChunkletStore to save
* @param location Where on the disk to put it
*/
private void serializeChunkletStore(ChunkletStore cStore, File location) {
FileOutputStream fileOut = null;
ObjectOutputStream objOut = null;
try {
if (!location.exists()) {
location.createNewFile();
}
fileOut = new FileOutputStream(location);
objOut = new ObjectOutputStream(fileOut);
objOut.writeObject(cStore);
}
catch (IOException ex) {
ex.printStackTrace();
}
finally {
if (objOut != null) {
try {
objOut.flush();
objOut.close();
}
catch (IOException ex) {
ex.printStackTrace();
}
}
if (fileOut != null) {
try {
fileOut.close();
}
catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
/**
* @param location Where on the disk to read from
* @return ChunkletStore from the specified location
*/
private ChunkletStore deserializeChunkletStore(File location) {
ChunkletStore storeIn = null;
FileInputStream fileIn = null;
ObjectInputStream objIn = null;
try {
fileIn = new FileInputStream(location);
objIn = new ObjectInputStream(new BufferedInputStream(fileIn));
storeIn = (ChunkletStore) objIn.readObject();
}
catch (IOException ex) {
if (ex instanceof EOFException) {
// EOF should only happen on Chunklets that somehow have been corrupted.
//mcMMO.p.getLogger().severe("Chunklet data at " + location.toString() + " could not be read due to an EOFException, data in this area will be lost.");
return ChunkletStoreFactory.getChunkletStore();
}
else if (ex instanceof StreamCorruptedException) {
// StreamCorrupted happens when the Chunklet is no good.
//mcMMO.p.getLogger().severe("Chunklet data at " + location.toString() + " is corrupted, data in this area will be lost.");
return ChunkletStoreFactory.getChunkletStore();
}
else if (ex instanceof UTFDataFormatException) {
// UTF happens when the Chunklet cannot be read or is corrupted
//mcMMO.p.getLogger().severe("Chunklet data at " + location.toString() + " could not be read due to an UTFDataFormatException, data in this area will be lost.");
return ChunkletStoreFactory.getChunkletStore();
}
ex.printStackTrace();
}
catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
finally {
if (objIn != null) {
try {
objIn.close();
}
catch (IOException ex) {
ex.printStackTrace();
}
}
if (fileIn != null) {
try {
fileIn.close();
}
catch (IOException ex) {
ex.printStackTrace();
}
}
}
// TODO: Make this less messy, as it is, it's kinda... depressing to do it like this.
// Might also make a mess when we move to stacks, but at that point I think I will write a new Manager...
// IMPORTANT! If ChunkletStoreFactory is going to be returning something other than PrimitiveEx we need to remove this, as it will be breaking time for old maps
/*
if (!(storeIn instanceof PrimitiveExChunkletStore)) {
ChunkletStore tempStore = ChunkletStoreFactory.getChunkletStore();
if (storeIn != null) {
tempStore.copyFrom(storeIn);
}
storeIn = tempStore;
}
*/
return storeIn;
}
}

View File

@ -0,0 +1,257 @@
/*
* This file is part of SpoutPlugin.
*
* Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/>
* SpoutPlugin is licensed under the GNU Lesser General Public License.
*
* SpoutPlugin is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SpoutPlugin is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.gmail.nossr50.util.blockmeta;
import java.io.*;
import java.util.BitSet;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
/**
* File format:
* bytes 0-4096 contain 1024 integer values representing the segment index of each chunk
* bytes 4096-8192 contain 1024 integer values representing the byte length of each chunk
* bytes 8192-8196 is the integer value of the segment exponent
* bytes 8196-12288 are reserved for future use
* bytes 12288+ contain the data segments, by default 1024 byte segments.
* Chunk data is compressed and stored in 1 or more segments as needed.
*/
public class McMMOSimpleRegionFile {
private static final int DEFAULT_SEGMENT_EXPONENT = 10; // TODO, analyze real world usage and determine if a smaller segment(512) is worth it or not. (need to know average chunkstore bytesize)
private static final int DEFAULT_SEGMENT_SIZE = (int)Math.pow(2, DEFAULT_SEGMENT_EXPONENT); // 1024
private static final int RESERVED_HEADER_BYTES = 12288; // This needs to be divisible by segment size
private static final int NUM_CHUNKS = 1024; // 32x32
private static final int SEEK_CHUNK_SEGMENT_INDICES = 0;
private static final int SEEK_CHUNK_BYTE_LENGTHS = 4096;
private static final int SEEK_FILE_INFO = 8192;
// Chunk info
private final int[] chunkSegmentIndex = new int[NUM_CHUNKS];
private final int[] chunkNumBytes = new int[NUM_CHUNKS];
private final int[] chunkNumSegments = new int[NUM_CHUNKS];
// Segments
private final BitSet segments = new BitSet(); // Used to denote which segments are in use or not
// Segment size/mask
private final int segmentExponent;
private final int segmentMask;
// File location
private final File parent;
// File access
private final RandomAccessFile file;
// Region index
private final int rx;
private final int rz;
public McMMOSimpleRegionFile(File f, int rx, int rz) {
this.rx = rx;
this.rz = rz;
this.parent = f;
try {
this.file = new RandomAccessFile(parent, "rw");
// New file, write out header bytes
if (file.length() < RESERVED_HEADER_BYTES) {
file.write(new byte[RESERVED_HEADER_BYTES]);
file.seek(SEEK_FILE_INFO);
file.writeInt(DEFAULT_SEGMENT_EXPONENT);
}
file.seek(SEEK_FILE_INFO);
this.segmentExponent = file.readInt();
this.segmentMask = (1 << segmentExponent) - 1;
// Mark reserved segments reserved
int reservedSegments = this.bytesToSegments(RESERVED_HEADER_BYTES);
segments.set(0, reservedSegments, true);
// Read chunk header data
file.seek(SEEK_CHUNK_SEGMENT_INDICES);
for (int i = 0; i < NUM_CHUNKS; i++)
chunkSegmentIndex[i] = file.readInt();
file.seek(SEEK_CHUNK_BYTE_LENGTHS);
for (int i = 0; i < NUM_CHUNKS; i++) {
chunkNumBytes[i] = file.readInt();
chunkNumSegments[i] = bytesToSegments(chunkNumBytes[i]);
markChunkSegments(i, true);
}
fixFileLength();
}
catch (IOException fnfe) {
throw new RuntimeException(fnfe);
}
}
public synchronized DataOutputStream getOutputStream(int x, int z) {
int index = getChunkIndex(x, z); // Get chunk index
return new DataOutputStream(new DeflaterOutputStream(new McMMOSimpleChunkBuffer(this, index)));
}
private static class McMMOSimpleChunkBuffer extends ByteArrayOutputStream {
final McMMOSimpleRegionFile rf;
final int index;
McMMOSimpleChunkBuffer(McMMOSimpleRegionFile rf, int index) {
super(DEFAULT_SEGMENT_SIZE);
this.rf = rf;
this.index = index;
}
@Override
public void close() throws IOException {
rf.write(index, buf, count);
}
}
private synchronized void write(int index, byte[] buffer, int size) throws IOException {
int oldSegmentIndex = chunkSegmentIndex[index]; // Get current segment index
markChunkSegments(index, false); // Clear our old segments
int newSegmentIndex = findContiguousSegments(oldSegmentIndex, size); // Find contiguous segments to save to
file.seek(newSegmentIndex << segmentExponent); // Seek to file location
file.write(buffer, 0, size); // Write data
// update in memory info
chunkSegmentIndex[index] = newSegmentIndex;
chunkNumBytes[index] = size;
chunkNumSegments[index] = bytesToSegments(size);
// Mark segments in use
markChunkSegments(index, true);
// Update header info
file.seek(SEEK_CHUNK_SEGMENT_INDICES + (4 * index));
file.writeInt(chunkSegmentIndex[index]);
file.seek(SEEK_CHUNK_BYTE_LENGTHS + (4 * index));
file.writeInt(chunkNumBytes[index]);
}
public synchronized DataInputStream getInputStream(int x, int z) throws IOException {
int index = getChunkIndex(x, z); // Get chunk index
int byteLength = chunkNumBytes[index]; // Get byte length of data
// No bytes
if (byteLength == 0)
return null;
byte[] data = new byte[byteLength];
file.seek(chunkSegmentIndex[index] << segmentExponent); // Seek to file location
file.readFully(data); // Read in the data
return new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(data)));
}
public synchronized void close() {
try {
file.close();
segments.clear();
}
catch (IOException ioe) {
throw new RuntimeException("Unable to close file", ioe);
}
}
private synchronized void markChunkSegments(int index, boolean inUse) {
// No bytes used
if (chunkNumBytes[index] == 0)
return;
int start = chunkSegmentIndex[index];
int end = start + chunkNumSegments[index];
// If we are writing, assert we don't write over any in-use segments
if (inUse)
{
int nextSetBit = segments.nextSetBit(start);
if (nextSetBit != -1 && nextSetBit < end)
throw new IllegalStateException("Attempting to overwrite an in-use segment");
}
segments.set(start, end, inUse);
}
private synchronized void fixFileLength() throws IOException {
int fileLength = (int)file.length();
int extend = -fileLength & segmentMask; // how many bytes do we need to be divisible by segment size
// Go to end of file
file.seek(fileLength);
// Append bytes
file.write(new byte[extend], 0, extend);
}
private synchronized int findContiguousSegments(int hint, int size) {
if (size == 0)
return 0; // Zero byte data will not claim any chunks anyways
int segments = bytesToSegments(size); // Number of segments we need
// Check the hinted location (previous location of chunk) most of the time we can fit where we were.
boolean oldFree = true;
for (int i = hint; i < this.segments.size() && i < hint + segments; i++) {
if (this.segments.get(i)) {
oldFree = false;
break;
}
}
// We fit!
if (oldFree)
return hint;
// Find somewhere to put us
int start = 0;
int current = 0;
while (current < this.segments.size()) {
boolean segmentInUse = this.segments.get(current); // check if segment is in use
current++; // Move up a segment
// Move up start if the segment was in use
if (segmentInUse)
start = current;
// If we have enough segments now, return
if (current - start >= segments)
return start;
}
// Return the end of the segments (will expand to fit them)
return start;
}
private synchronized int bytesToSegments(int bytes) {
if (bytes <= 0)
return 1;
return ((bytes - 1) >> segmentExponent) + 1; // ((bytes - 1) / segmentSize) + 1
}
private synchronized int getChunkIndex(int x, int z) {
if (rx != (x >> 5) || rz != (z >> 5))
throw new IndexOutOfBoundsException();
x = x & 0x1F; // 5 bits (mod 32)
z = z & 0x1F; // 5 bits (mod 32)
return (x << 5) + z; // x in the upper 5 bits, z in the lower 5 bits
}
}

View File

@ -1,51 +1,17 @@
package com.gmail.nossr50.util.blockmeta.chunkmeta;
package com.gmail.nossr50.util.blockmeta;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Entity;
import java.io.IOException;
public class NullChunkManager implements ChunkManager {
@Override
public void closeAll() {}
@Override
public ChunkStore readChunkStore(World world, int x, int z) throws IOException {
return null;
}
@Override
public void writeChunkStore(World world, int x, int z, ChunkStore data) {}
@Override
public void closeChunkStore(World world, int x, int z) {}
@Override
public void loadChunklet(int cx, int cy, int cz, World world) {}
@Override
public void unloadChunklet(int cx, int cy, int cz, World world) {}
@Override
public void loadChunk(int cx, int cz, World world, Entity[] entities) {}
@Override
public void unloadChunk(int cx, int cz, World world) {}
@Override
public void saveChunk(int cx, int cz, World world) {}
@Override
public boolean isChunkLoaded(int cx, int cz, World world) {
return true;
}
@Override
public void chunkLoaded(int cx, int cz, World world) {}
@Override
public void chunkUnloaded(int cx, int cz, World world) {}
@ -55,15 +21,9 @@ public class NullChunkManager implements ChunkManager {
@Override
public void unloadWorld(World world) {}
@Override
public void loadWorld(World world) {}
@Override
public void saveAll() {}
@Override
public void unloadAll() {}
@Override
public boolean isTrue(int x, int y, int z, World world) {
return false;

View File

@ -1,85 +0,0 @@
package com.gmail.nossr50.util.blockmeta;
import org.bukkit.World;
import org.bukkit.block.Block;
/**
* A ChunkletManager implementation that does nothing and returns false for all checks.
*
* Useful for turning off Chunklets without actually doing much work
*/
public class NullChunkletManager implements ChunkletManager {
@Override
public void loadChunklet(int cx, int cy, int cz, World world) {
}
@Override
public void unloadChunklet(int cx, int cy, int cz, World world) {
}
@Override
public void loadChunk(int cx, int cz, World world) {
}
@Override
public void unloadChunk(int cx, int cz, World world) {
}
@Override
public void chunkLoaded(int cx, int cz, World world) {
}
@Override
public void chunkUnloaded(int cx, int cz, World world) {
}
@Override
public void saveWorld(World world) {
}
@Override
public void unloadWorld(World world) {
}
@Override
public void loadWorld(World world) {
}
@Override
public void saveAll() {
}
@Override
public void unloadAll() {
}
@Override
public boolean isTrue(int x, int y, int z, World world) {
return false;
}
@Override
public boolean isTrue(Block block) {
return false;
}
@Override
public void setTrue(int x, int y, int z, World world) {
}
@Override
public void setTrue(Block block) {
}
@Override
public void setFalse(int x, int y, int z, World world) {
}
@Override
public void setFalse(Block block) {
}
@Override
public void cleanUp() {
}
}

View File

@ -1,48 +0,0 @@
package com.gmail.nossr50.util.blockmeta;
public class PrimitiveChunkletStore implements ChunkletStore {
private static final long serialVersionUID = -3453078050608607478L;
/** X, Z, Y */
public boolean[][][] store = new boolean[16][16][64];
@Override
public boolean isTrue(int x, int y, int z) {
return store[x][z][y];
}
@Override
public void setTrue(int x, int y, int z) {
store[x][z][y] = true;
}
@Override
public void setFalse(int x, int y, int z) {
store[x][z][y] = false;
}
@Override
public boolean isEmpty() {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 64; y++) {
if (store[x][z][y]) {
return false;
}
}
}
}
return true;
}
@Override
public void copyFrom(ChunkletStore otherStore) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 64; y++) {
store[x][z][y] = otherStore.isTrue(x, y, z);
}
}
}
}
}

View File

@ -1,180 +0,0 @@
package com.gmail.nossr50.util.blockmeta;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
public class PrimitiveExChunkletStore implements ChunkletStore, Externalizable {
private static final long serialVersionUID = 8603603827094383873L;
/** X, Z, Y */
public boolean[][][] store = new boolean[16][16][64];
@Override
public boolean isTrue(int x, int y, int z) {
return store[x][z][y];
}
@Override
public void setTrue(int x, int y, int z) {
store[x][z][y] = true;
}
@Override
public void setFalse(int x, int y, int z) {
store[x][z][y] = false;
}
@Override
public boolean isEmpty() {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 64; y++) {
if (store[x][z][y]) {
return false;
}
}
}
}
return true;
}
@Override
public void copyFrom(ChunkletStore otherStore) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 64; y++) {
store[x][z][y] = otherStore.isTrue(x, y, z);
}
}
}
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
byte[] buffer = new byte[2304]; // 2304 is 16*16*9
int bufferIndex = 0;
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < 64; y++) {
if (store[x][z][y]) {
byte[] temp = constructColumn(x, z);
for (int i = 0; i < 9; i++) {
buffer[bufferIndex] = temp[i];
bufferIndex++;
}
break;
}
}
}
}
out.write(buffer, 0, bufferIndex);
out.flush();
}
// For this we assume that store has been initialized to be all false by now
@Override
public void readExternal(ObjectInput in) throws IOException {
byte[] temp = new byte[9];
// Could probably reorganize this loop to print nasty things if it does not equal 9 or -1
while (in.read(temp, 0, 9) == 9) {
int x = addressByteX(temp[0]);
int z = addressByteZ(temp[0]);
boolean[] yColumn = new boolean[64];
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
yColumn[j + (i * 8)] = (temp[i + 1] & (1 << j)) != 0;
}
}
store[x][z] = yColumn;
}
}
/*
* The column: An array of 9 bytes which represent all y values for a given (x,z) Chunklet-coordinate
*
* The first byte is an address byte, this provides the x and z values.
* The next 8 bytes are all y values from 0 to 63, with each byte containing 8 bits of true/false data
*
* Each of these 8 bytes address to a y value from right to left
*
* Examples:
* 00000001 represents that the lowest y value in this byte is true, all others are off
* 10000000 represents that the highest y value in this byte is true, all others are off
* 10000001 represents that the lowest and highest y values in this byte are true, all others are off
*
* Full columns:
* See comment on Address byte for information on how to use that byte
*
* Example:
* ADDRESS_BYTE 10000000 00000001 00000000 00000000 00000000 00000000 00000000 00000000
* - x, z from ADDRESS_BYTE
* - The next byte contains data from 0 to 7
* - 1 is set in the highest bit position, this is 7 in y coordinate
* - The next byte contains data from 8 to 15
* - 1 is set in the lowest bit position, this is 8 in the y coordinate
* Therefore, for this column: There are true values at (x, 7, z) and (x, 8, z)
*/
private byte[] constructColumn(int x, int z) {
byte[] column = new byte[9];
int index = 1;
column[0] = makeAddressByte(x, z);
for (int i = 0; i < 8; i++) {
byte yCompressed = 0x0;
int subColumnIndex = 8 * i;
int subColumnEnd = subColumnIndex + 8;
for (int y = subColumnIndex; y < subColumnEnd; y++) {
if (store[x][z][y]) {
yCompressed |= 1 << (y % 8);
}
}
column[index] = yCompressed;
index++;
}
return column;
}
/*
* The address byte: A single byte which contains x and z values which correspond to the x and z Chunklet-coordinates
*
* In Chunklet-coordinates, the only valid values are 0-15, so we can fit both into a single byte.
*
* The top 4 bits of the address byte are for the x value
* The bottom 4 bits of the address byte are for the z value
*
* Examples:
* An address byte with a value 00000001 would be split like so:
* - x = 0000 = 0
* - z = 0001 = 1
* => Chunklet coordinates (0, 1)
*
* 01011111
* - x = 0101 = 5
* - z = 1111 = 15
* => Chunklet coordinates (5, 15)
*/
protected static byte makeAddressByte(int x, int z) {
return (byte) ((x << 4) + z);
}
protected static int addressByteX(byte address) {
return (address & 0xF0) >>> 4;
}
protected static int addressByteZ(byte address) {
return address & 0x0F;
}
}

View File

@ -1,10 +0,0 @@
package com.gmail.nossr50.util.blockmeta.chunkmeta;
import org.bukkit.World;
public class ChunkStoreFactory {
protected static ChunkStore getChunkStore(World world, int x, int z) {
// TODO: Add in loading from config what type of store we want.
return new PrimitiveChunkStore(world, x, z);
}
}

View File

@ -1,447 +0,0 @@
package com.gmail.nossr50.util.blockmeta.chunkmeta;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.blockmeta.conversion.BlockStoreConversionZDirectory;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Entity;
import java.io.*;
import java.util.*;
public class HashChunkManager implements ChunkManager {
private final HashMap<UUID, HashMap<Long, McMMOSimpleRegionFile>> regionFiles = new HashMap<>();
public HashMap<String, ChunkStore> store = new HashMap<>();
public ArrayList<BlockStoreConversionZDirectory> converters = new ArrayList<>();
private final HashMap<UUID, Boolean> oldData = new HashMap<>();
@Override
public synchronized void closeAll() {
for (UUID uid : regionFiles.keySet()) {
HashMap<Long, McMMOSimpleRegionFile> worldRegions = regionFiles.get(uid);
for (Iterator<McMMOSimpleRegionFile> worldRegionIterator = worldRegions.values().iterator(); worldRegionIterator.hasNext(); ) {
McMMOSimpleRegionFile rf = worldRegionIterator.next();
if (rf != null) {
rf.close();
worldRegionIterator.remove();
}
}
}
regionFiles.clear();
}
@Override
public synchronized ChunkStore readChunkStore(World world, int x, int z) throws IOException {
McMMOSimpleRegionFile rf = getSimpleRegionFile(world, x, z);
InputStream in = rf.getInputStream(x, z);
if (in == null) {
return null;
}
try (ObjectInputStream objectStream = new ObjectInputStream(in)) {
Object o = objectStream.readObject();
if (o instanceof ChunkStore) {
return (ChunkStore) o;
}
throw new RuntimeException("Wrong class type read for chunk meta data for " + x + ", " + z);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
// Assume the format changed
return null;
//throw new RuntimeException("Unable to process chunk meta data for " + x + ", " + z, e);
}
}
@Override
public synchronized void writeChunkStore(World world, int x, int z, ChunkStore data) {
if (!data.isDirty()) {
return;
}
try {
McMMOSimpleRegionFile rf = getSimpleRegionFile(world, x, z);
ObjectOutputStream objectStream = new ObjectOutputStream(rf.getOutputStream(x, z));
objectStream.writeObject(data);
objectStream.flush();
objectStream.close();
data.setDirty(false);
}
catch (IOException e) {
throw new RuntimeException("Unable to write chunk meta data for " + x + ", " + z, e);
}
}
@Override
public synchronized void closeChunkStore(World world, int x, int z) {
McMMOSimpleRegionFile rf = getSimpleRegionFile(world, x, z);
if (rf != null) {
rf.close();
}
}
private synchronized McMMOSimpleRegionFile getSimpleRegionFile(World world, int x, int z) {
File directory = new File(world.getWorldFolder(), "mcmmo_regions");
directory.mkdirs();
UUID key = world.getUID();
HashMap<Long, McMMOSimpleRegionFile> worldRegions = regionFiles.computeIfAbsent(key, k -> new HashMap<>());
int rx = x >> 5;
int rz = z >> 5;
long key2 = (((long) rx) << 32) | ((rz) & 0xFFFFFFFFL);
McMMOSimpleRegionFile regionFile = worldRegions.get(key2);
if (regionFile == null) {
File file = new File(directory, "mcmmo_" + rx + "_" + rz + "_.mcm");
regionFile = new McMMOSimpleRegionFile(file, rx, rz);
worldRegions.put(key2, regionFile);
}
return regionFile;
}
@Override
public synchronized void loadChunklet(int cx, int cy, int cz, World world) {
loadChunk(cx, cz, world, null);
}
@Override
public synchronized void unloadChunklet(int cx, int cy, int cz, World world) {
unloadChunk(cx, cz, world);
}
@Override
public synchronized void loadChunk(int cx, int cz, World world, Entity[] entities) {
if (world == null || store.containsKey(world.getName() + "," + cx + "," + cz)) {
return;
}
UUID key = world.getUID();
if (!oldData.containsKey(key)) {
oldData.put(key, (new File(world.getWorldFolder(), "mcmmo_data")).exists());
}
else if (oldData.get(key)) {
if (convertChunk(new File(world.getWorldFolder(), "mcmmo_data"), cx, cz, world, true)) {
return;
}
}
ChunkStore chunkStore = null;
try {
chunkStore = readChunkStore(world, cx, cz);
}
catch (Exception e) { e.printStackTrace(); }
if (chunkStore == null) {
return;
}
store.put(world.getName() + "," + cx + "," + cz, chunkStore);
}
@Override
public synchronized void unloadChunk(int cx, int cz, World world) {
saveChunk(cx, cz, world);
if (store.containsKey(world.getName() + "," + cx + "," + cz)) {
store.remove(world.getName() + "," + cx + "," + cz);
//closeChunkStore(world, cx, cz);
}
}
@Override
public synchronized void saveChunk(int cx, int cz, World world) {
if (world == null) {
return;
}
String key = world.getName() + "," + cx + "," + cz;
if (store.containsKey(key)) {
ChunkStore out = store.get(world.getName() + "," + cx + "," + cz);
if (!out.isDirty()) {
return;
}
writeChunkStore(world, cx, cz, out);
}
}
@Override
public synchronized boolean isChunkLoaded(int cx, int cz, World world) {
if (world == null) {
return false;
}
return store.containsKey(world.getName() + "," + cx + "," + cz);
}
@Override
public synchronized void chunkLoaded(int cx, int cz, World world) {}
@Override
public synchronized void chunkUnloaded(int cx, int cz, World world) {
if (world == null) {
return;
}
unloadChunk(cx, cz, world);
}
@Override
public synchronized void saveWorld(World world) {
if (world == null) {
return;
}
closeAll();
String worldName = world.getName();
List<String> keys = new ArrayList<>(store.keySet());
for (String key : keys) {
String[] info = key.split(",");
if (worldName.equals(info[0])) {
try {
saveChunk(Integer.parseInt(info[1]), Integer.parseInt(info[2]), world);
}
catch (Exception e) {
// Ignore
}
}
}
}
@Override
public synchronized void unloadWorld(World world) {
if (world == null) {
return;
}
String worldName = world.getName();
List<String> keys = new ArrayList<>(store.keySet());
for (String key : keys) {
String[] info = key.split(",");
if (worldName.equals(info[0])) {
try {
unloadChunk(Integer.parseInt(info[1]), Integer.parseInt(info[2]), world);
}
catch (Exception e) {
// Ignore
}
}
}
closeAll();
}
@Override
public synchronized void loadWorld(World world) {}
@Override
public synchronized void saveAll() {
closeAll();
for (World world : mcMMO.p.getServer().getWorlds()) {
saveWorld(world);
}
}
@Override
public synchronized void unloadAll() {
closeAll();
for (World world : mcMMO.p.getServer().getWorlds()) {
unloadWorld(world);
}
}
@Override
public synchronized boolean isTrue(int x, int y, int z, World world) {
if (world == null) {
return false;
}
int cx = x >> 4;
int cz = z >> 4;
String key = world.getName() + "," + cx + "," + cz;
if (!store.containsKey(key)) {
loadChunk(cx, cz, world, null);
}
if (!store.containsKey(key)) {
return false;
}
ChunkStore check = store.get(key);
int ix = Math.abs(x) % 16;
int iz = Math.abs(z) % 16;
return check.isTrue(ix, y, iz);
}
@Override
public synchronized boolean isTrue(Block block) {
if (block == null) {
return false;
}
return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
}
@Override
public synchronized boolean isTrue(BlockState blockState) {
if (blockState == null) {
return false;
}
return isTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
}
@Override
public synchronized void setTrue(int x, int y, int z, World world) {
if (world == null) {
return;
}
int cx = x >> 4;
int cz = z >> 4;
int ix = Math.abs(x) % 16;
int iz = Math.abs(z) % 16;
String key = world.getName() + "," + cx + "," + cz;
if (!store.containsKey(key)) {
loadChunk(cx, cz, world, null);
}
ChunkStore cStore = store.get(key);
if (cStore == null) {
cStore = ChunkStoreFactory.getChunkStore(world, cx, cz);
store.put(key, cStore);
}
cStore.setTrue(ix, y, iz);
}
@Override
public synchronized void setTrue(Block block) {
if (block == null) {
return;
}
setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
}
@Override
public void setTrue(BlockState blockState) {
if (blockState == null) {
return;
}
setTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
}
@Override
public synchronized void setFalse(int x, int y, int z, World world) {
if (world == null) {
return;
}
int cx = x >> 4;
int cz = z >> 4;
int ix = Math.abs(x) % 16;
int iz = Math.abs(z) % 16;
String key = world.getName() + "," + cx + "," + cz;
if (!store.containsKey(key)) {
loadChunk(cx, cz, world, null);
}
ChunkStore cStore = store.get(key);
if (cStore == null) {
return; // No need to make a store for something we will be setting to false
}
cStore.setFalse(ix, y, iz);
}
@Override
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());
}
@Override
public synchronized void cleanUp() {}
public synchronized void convertChunk(File dataDir, int cx, int cz, World world) {
convertChunk(dataDir, cx, cz, world, false);
}
public synchronized boolean convertChunk(File dataDir, int cx, int cz, World world, boolean actually) {
if (!actually || !dataDir.exists()) {
return false;
}
File cxDir = new File(dataDir, "" + cx);
if (!cxDir.exists()) {
return false;
}
File czDir = new File(cxDir, "" + cz);
if (!czDir.exists()) {
return false;
}
boolean conversionSet = false;
for (BlockStoreConversionZDirectory converter : this.converters) {
if (converter == null) {
continue;
}
if (converter.taskID >= 0) {
continue;
}
converter.start(world, cxDir, czDir);
conversionSet = true;
break;
}
if (!conversionSet) {
BlockStoreConversionZDirectory converter = new BlockStoreConversionZDirectory();
converter.start(world, cxDir, czDir);
converters.add(converter);
}
return true;
}
}

View File

@ -1,39 +0,0 @@
/*
* This file is part of SpoutPlugin.
*
* Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/>
* SpoutPlugin is licensed under the GNU Lesser General Public License.
*
* SpoutPlugin is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SpoutPlugin is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.gmail.nossr50.util.blockmeta.chunkmeta;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class McMMOSimpleChunkBuffer extends ByteArrayOutputStream {
final McMMOSimpleRegionFile rf;
final int index;
McMMOSimpleChunkBuffer(McMMOSimpleRegionFile rf, int index) {
super(1024);
this.rf = rf;
this.index = index;
}
@Override
public void close() throws IOException {
rf.write(index, buf, count);
}
}

View File

@ -1,306 +0,0 @@
/*
* This file is part of SpoutPlugin.
*
* Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/>
* SpoutPlugin is licensed under the GNU Lesser General Public License.
*
* SpoutPlugin is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SpoutPlugin is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.gmail.nossr50.util.blockmeta.chunkmeta;
import java.io.*;
import java.util.ArrayList;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
public class McMMOSimpleRegionFile {
private RandomAccessFile file;
private final int[] dataStart = new int[1024];
private final int[] dataActualLength = new int[1024];
private final int[] dataLength = new int[1024];
private final ArrayList<Boolean> inuse = new ArrayList<>();
private int segmentSize;
private int segmentMask;
private final int rx;
private final int rz;
private final int defaultSegmentSize;
private final File parent;
@SuppressWarnings("unused")
private long lastAccessTime = System.currentTimeMillis();
@SuppressWarnings("unused")
private static final long TIMEOUT_TIME = 300000; // 5 min
public McMMOSimpleRegionFile(File f, int rx, int rz) {
this(f, rx, rz, 10);
}
public McMMOSimpleRegionFile(File f, int rx, int rz, int defaultSegmentSize) {
this.rx = rx;
this.rz = rz;
this.defaultSegmentSize = defaultSegmentSize;
this.parent = f;
lastAccessTime = System.currentTimeMillis();
if (file == null) {
try {
this.file = new RandomAccessFile(parent, "rw");
if (file.length() < 4096 * 3) {
for (int i = 0; i < 1024 * 3; i++) {
file.writeInt(0);
}
file.seek(4096 * 2);
file.writeInt(defaultSegmentSize);
}
file.seek(4096 * 2);
this.segmentSize = file.readInt();
this.segmentMask = (1 << segmentSize) - 1;
int reservedSegments = this.sizeToSegments(4096 * 3);
for (int i = 0; i < reservedSegments; i++) {
while (inuse.size() <= i) {
inuse.add(false);
}
inuse.set(i, true);
}
file.seek(0);
for (int i = 0; i < 1024; i++) {
dataStart[i] = file.readInt();
}
for (int i = 0; i < 1024; i++) {
dataActualLength[i] = file.readInt();
dataLength[i] = sizeToSegments(dataActualLength[i]);
setInUse(i, true);
}
extendFile();
}
catch (IOException fnfe) {
throw new RuntimeException(fnfe);
}
}
}
public synchronized final RandomAccessFile getFile() {
lastAccessTime = System.currentTimeMillis();
if (file == null) {
try {
this.file = new RandomAccessFile(parent, "rw");
if (file.length() < 4096 * 3) {
for (int i = 0; i < 1024 * 3; i++) {
file.writeInt(0);
}
file.seek(4096 * 2);
file.writeInt(defaultSegmentSize);
}
file.seek(4096 * 2);
this.segmentSize = file.readInt();
this.segmentMask = (1 << segmentSize) - 1;
int reservedSegments = this.sizeToSegments(4096 * 3);
for (int i = 0; i < reservedSegments; i++) {
while (inuse.size() <= i) {
inuse.add(false);
}
inuse.set(i, true);
}
file.seek(0);
for (int i = 0; i < 1024; i++) {
dataStart[i] = file.readInt();
}
for (int i = 0; i < 1024; i++) {
dataActualLength[i] = file.readInt();
dataLength[i] = sizeToSegments(dataActualLength[i]);
setInUse(i, true);
}
extendFile();
}
catch (IOException fnfe) {
throw new RuntimeException(fnfe);
}
}
return file;
}
public synchronized boolean testCloseTimeout() {
/*
if (System.currentTimeMillis() - TIMEOUT_TIME > lastAccessTime) {
close();
return true;
}
*/
return false;
}
public synchronized DataOutputStream getOutputStream(int x, int z) {
int index = getChunkIndex(x, z);
return new DataOutputStream(new DeflaterOutputStream(new McMMOSimpleChunkBuffer(this, index)));
}
public synchronized DataInputStream getInputStream(int x, int z) throws IOException {
int index = getChunkIndex(x, z);
int actualLength = dataActualLength[index];
if (actualLength == 0) {
return null;
}
byte[] data = new byte[actualLength];
getFile().seek(dataStart[index] << segmentSize);
getFile().readFully(data);
return new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(data)));
}
synchronized void write(int index, byte[] buffer, int size) throws IOException {
int oldStart = setInUse(index, false);
int start = findSpace(oldStart, size);
getFile().seek(start << segmentSize);
getFile().write(buffer, 0, size);
dataStart[index] = start;
dataActualLength[index] = size;
dataLength[index] = sizeToSegments(size);
setInUse(index, true);
saveFAT();
}
public synchronized void close() {
try {
if (file != null) {
file.seek(4096 * 2);
file.close();
}
file = null;
}
catch (IOException ioe) {
throw new RuntimeException("Unable to close file", ioe);
}
}
private synchronized int setInUse(int index, boolean used) {
if (dataActualLength[index] == 0) {
return dataStart[index];
}
int start = dataStart[index];
int end = start + dataLength[index];
for (int i = start; i < end; i++) {
while (i > inuse.size() - 1) {
inuse.add(false);
}
Boolean old = inuse.set(i, used);
if (old != null && old == used) {
if (old) {
throw new IllegalStateException("Attempting to overwrite an in-use segment");
}
throw new IllegalStateException("Attempting to delete empty segment");
}
}
return dataStart[index];
}
private synchronized void extendFile() throws IOException {
long extend = (-getFile().length()) & segmentMask;
getFile().seek(getFile().length());
while ((extend--) > 0) {
getFile().write(0);
}
}
private synchronized int findSpace(int oldStart, int size) {
int segments = sizeToSegments(size);
boolean oldFree = true;
for (int i = oldStart; i < inuse.size() && i < oldStart + segments; i++) {
if (inuse.get(i)) {
oldFree = false;
break;
}
}
if (oldFree) {
return oldStart;
}
int start = 0;
int end = 0;
while (end < inuse.size()) {
if (inuse.get(end)) {
end++;
start = end;
}
else {
end++;
}
if (end - start >= segments) {
return start;
}
}
return start;
}
private synchronized int sizeToSegments(int size) {
if (size <= 0) {
return 1;
}
return ((size - 1) >> segmentSize) + 1;
}
private synchronized Integer getChunkIndex(int x, int z) {
if (rx != (x >> 5) || rz != (z >> 5)) {
throw new RuntimeException(x + ", " + z + " not in region " + rx + ", " + rz);
}
x = x & 0x1F;
z = z & 0x1F;
return (x << 5) + z;
}
private synchronized void saveFAT() throws IOException {
getFile().seek(0);
for (int i = 0; i < 1024; i++) {
getFile().writeInt(dataStart[i]);
}
for (int i = 0; i < 1024; i++) {
getFile().writeInt(dataActualLength[i]);
}
}
}

View File

@ -1,147 +0,0 @@
package com.gmail.nossr50.util.blockmeta.chunkmeta;
import com.gmail.nossr50.util.blockmeta.ChunkletStore;
import org.bukkit.Bukkit;
import org.bukkit.World;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.UUID;
public class PrimitiveChunkStore implements ChunkStore {
private static final long serialVersionUID = -1L;
transient private boolean dirty = false;
/** X, Z, Y */
public boolean[][][] store;
private static final int CURRENT_VERSION = 7;
private static final int MAGIC_NUMBER = 0xEA5EDEBB;
private int cx;
private int cz;
private UUID worldUid;
public PrimitiveChunkStore(World world, int cx, int cz) {
this.cx = cx;
this.cz = cz;
this.worldUid = world.getUID();
this.store = new boolean[16][16][world.getMaxHeight()];
}
@Override
public boolean isDirty() {
return dirty;
}
@Override
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
@Override
public int getChunkX() {
return cx;
}
@Override
public int getChunkZ() {
return cz;
}
@Override
public boolean isTrue(int x, int y, int z) {
return store[x][z][y];
}
@Override
public void setTrue(int x, int y, int z) {
if (y >= store[0][0].length || y < 0)
return;
store[x][z][y] = true;
dirty = true;
}
@Override
public void setFalse(int x, int y, int z) {
if (y >= store[0][0].length || y < 0)
return;
store[x][z][y] = false;
dirty = true;
}
@Override
public boolean isEmpty() {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < store[0][0].length; y++) {
if (store[x][z][y]) {
return false;
}
}
}
}
return true;
}
@Override
public void copyFrom(ChunkletStore otherStore) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < store[0][0].length; y++) {
store[x][z][y] = otherStore.isTrue(x, y, z);
}
}
}
dirty = true;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(MAGIC_NUMBER);
out.writeInt(CURRENT_VERSION);
out.writeLong(worldUid.getLeastSignificantBits());
out.writeLong(worldUid.getMostSignificantBits());
out.writeInt(cx);
out.writeInt(cz);
out.writeObject(store);
dirty = false;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
int magic = in.readInt();
// Can be used to determine the format of the file
int fileVersionNumber = in.readInt();
if (magic != MAGIC_NUMBER) {
fileVersionNumber = 0;
}
long lsb = in.readLong();
long msb = in.readLong();
worldUid = new UUID(msb, lsb);
cx = in.readInt();
cz = in.readInt();
store = (boolean[][][]) in.readObject();
if (fileVersionNumber < 5) {
fixArray();
dirty = true;
}
}
private void fixArray() {
boolean[][][] temp = this.store;
this.store = new boolean[16][16][Bukkit.getWorld(worldUid).getMaxHeight()];
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < store[0][0].length; y++) {
try {
store[x][z][y] = temp[x][y][z];
}
catch (Exception e) { e.printStackTrace(); }
}
}
}
}
}

View File

@ -1,90 +0,0 @@
package com.gmail.nossr50.util.blockmeta.conversion;
import com.gmail.nossr50.config.HiddenConfig;
import com.gmail.nossr50.mcMMO;
import org.bukkit.scheduler.BukkitScheduler;
import java.io.File;
public class BlockStoreConversionMain implements Runnable {
private int taskID, i;
private org.bukkit.World world;
BukkitScheduler scheduler;
File dataDir;
File[] xDirs;
BlockStoreConversionXDirectory[] converters;
public BlockStoreConversionMain(org.bukkit.World world) {
this.taskID = -1;
this.world = world;
this.scheduler = mcMMO.p.getServer().getScheduler();
this.dataDir = new File(this.world.getWorldFolder(), "mcmmo_data");
this.converters = new BlockStoreConversionXDirectory[HiddenConfig.getInstance().getConversionRate()];
}
public void start() {
if (this.taskID >= 0) {
return;
}
this.taskID = this.scheduler.runTaskLater(mcMMO.p, this, 1).getTaskId();
}
@Override
public void run() {
if (!this.dataDir.exists()) {
softStop();
return;
}
if (!this.dataDir.isDirectory()) {
this.dataDir.delete();
softStop();
return;
}
if (this.dataDir.listFiles().length <= 0) {
this.dataDir.delete();
softStop();
return;
}
this.xDirs = this.dataDir.listFiles();
for (this.i = 0; (this.i < HiddenConfig.getInstance().getConversionRate()) && (this.i < this.xDirs.length); this.i++) {
if (this.converters[this.i] == null) {
this.converters[this.i] = new BlockStoreConversionXDirectory();
}
this.converters[this.i].start(this.world, this.xDirs[this.i]);
}
softStop();
}
public void stop() {
if (this.taskID < 0) {
return;
}
this.scheduler.cancelTask(this.taskID);
this.taskID = -1;
}
public void softStop() {
stop();
if (this.dataDir.exists() && this.dataDir.isDirectory()) {
start();
return;
}
mcMMO.p.getLogger().info("Finished converting the storage for " + world.getName() + ".");
this.dataDir = null;
this.xDirs = null;
this.world = null;
this.scheduler = null;
this.converters = null;
}
}

View File

@ -1,80 +0,0 @@
package com.gmail.nossr50.util.blockmeta.conversion;
import com.gmail.nossr50.config.HiddenConfig;
import com.gmail.nossr50.mcMMO;
import org.bukkit.scheduler.BukkitScheduler;
import java.io.File;
public class BlockStoreConversionXDirectory implements Runnable {
private int taskID, i;
private org.bukkit.World world;
BukkitScheduler scheduler;
File dataDir;
File[] zDirs;
BlockStoreConversionZDirectory[] converters;
public BlockStoreConversionXDirectory() {
this.taskID = -1;
}
public void start(org.bukkit.World world, File dataDir) {
this.world = world;
this.scheduler = mcMMO.p.getServer().getScheduler();
this.converters = new BlockStoreConversionZDirectory[HiddenConfig.getInstance().getConversionRate()];
this.dataDir = dataDir;
if (this.taskID >= 0) {
return;
}
this.taskID = this.scheduler.runTaskLater(mcMMO.p, this, 1).getTaskId();
}
@Override
public void run() {
if (!this.dataDir.exists()) {
stop();
return;
}
if (!this.dataDir.isDirectory()) {
this.dataDir.delete();
stop();
return;
}
if (this.dataDir.listFiles().length <= 0) {
this.dataDir.delete();
stop();
return;
}
this.zDirs = this.dataDir.listFiles();
for (this.i = 0; (this.i < HiddenConfig.getInstance().getConversionRate()) && (this.i < this.zDirs.length); this.i++) {
if (this.converters[this.i] == null) {
this.converters[this.i] = new BlockStoreConversionZDirectory();
}
this.converters[this.i].start(this.world, this.dataDir, this.zDirs[this.i]);
}
stop();
}
public void stop() {
if (this.taskID < 0) {
return;
}
this.scheduler.cancelTask(this.taskID);
this.taskID = -1;
this.dataDir = null;
this.zDirs = null;
this.world = null;
this.scheduler = null;
this.converters = null;
}
}

View File

@ -1,191 +0,0 @@
package com.gmail.nossr50.util.blockmeta.conversion;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.blockmeta.ChunkletStore;
import com.gmail.nossr50.util.blockmeta.HashChunkletManager;
import com.gmail.nossr50.util.blockmeta.PrimitiveChunkletStore;
import com.gmail.nossr50.util.blockmeta.PrimitiveExChunkletStore;
import com.gmail.nossr50.util.blockmeta.chunkmeta.HashChunkManager;
import com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore;
import org.bukkit.scheduler.BukkitScheduler;
import java.io.File;
public class BlockStoreConversionZDirectory implements Runnable {
public int taskID, cx, cz, x, y, z, y2, xPos, zPos, cxPos, czPos;
private String cxs, czs, chunkletName, chunkName;
private org.bukkit.World world;
private BukkitScheduler scheduler;
private File xDir, dataDir;
private HashChunkletManager manager;
private HashChunkManager newManager;
private ChunkletStore tempChunklet;
private PrimitiveChunkletStore primitiveChunklet = null;
private PrimitiveExChunkletStore primitiveExChunklet = null;
private PrimitiveChunkStore currentChunk;
private boolean[] oldArray, newArray;
public BlockStoreConversionZDirectory() {
this.taskID = -1;
}
public void start(org.bukkit.World world, File xDir, File dataDir) {
this.world = world;
this.scheduler = mcMMO.p.getServer().getScheduler();
this.manager = new HashChunkletManager();
this.newManager = (HashChunkManager) mcMMO.getPlaceStore();
this.dataDir = dataDir;
this.xDir = xDir;
if (this.taskID >= 0) {
return;
}
this.taskID = this.scheduler.runTaskLater(mcMMO.p, this, 1).getTaskId();
}
@Override
public void run() {
if (!this.dataDir.exists()) {
stop();
return;
}
if (!this.dataDir.isDirectory()) {
this.dataDir.delete();
stop();
return;
}
if (this.dataDir.listFiles().length <= 0) {
this.dataDir.delete();
stop();
return;
}
this.cxs = this.xDir.getName();
this.czs = this.dataDir.getName();
this.cx = 0;
this.cz = 0;
try {
this.cx = Integer.parseInt(this.cxs);
this.cz = Integer.parseInt(this.czs);
}
catch (Exception e) {
this.dataDir.delete();
stop();
return;
}
this.manager.loadChunk(this.cx, this.cz, this.world);
for (this.y = 0; this.y < (this.world.getMaxHeight() / 64); this.y++) {
this.chunkletName = this.world.getName() + "," + this.cx + "," + this.cz + "," + this.y;
this.tempChunklet = this.manager.store.get(this.chunkletName);
if (this.tempChunklet instanceof PrimitiveChunkletStore) {
this.primitiveChunklet = (PrimitiveChunkletStore) this.tempChunklet;
}
else if (this.tempChunklet instanceof PrimitiveExChunkletStore) {
this.primitiveExChunklet = (PrimitiveExChunkletStore) this.tempChunklet;
}
if (this.tempChunklet == null) {
continue;
}
this.chunkName = this.world.getName() + "," + this.cx + "," + this.cz;
this.currentChunk = (PrimitiveChunkStore) this.newManager.store.get(this.chunkName);
if (this.currentChunk != null) {
this.xPos = this.cx * 16;
this.zPos = this.cz * 16;
for (this.x = 0; this.x < 16; this.x++) {
for (this.z = 0; this.z < 16; this.z++) {
this.cxPos = this.xPos + this.x;
this.czPos = this.zPos + this.z;
for (this.y2 = (64 * this.y); this.y2 < (64 * this.y + 64); this.y2++) {
try {
if (!this.manager.isTrue(this.cxPos, this.y2, this.czPos, this.world)) {
continue;
}
this.newManager.setTrue(this.cxPos, this.y2, this.czPos, this.world);
}
catch (Exception e) { e.printStackTrace(); }
}
}
}
continue;
}
this.newManager.setTrue(this.cx * 16, 0, this.cz * 16, this.world);
this.newManager.setFalse(this.cx * 16, 0, this.cz * 16, this.world);
this.currentChunk = (PrimitiveChunkStore) this.newManager.store.get(this.chunkName);
for (this.x = 0; this.x < 16; this.x++) {
for (this.z = 0; this.z < 16; this.z++) {
if (this.primitiveChunklet != null) {
this.oldArray = this.primitiveChunklet.store[x][z];
}
if (this.primitiveExChunklet != null) {
this.oldArray = this.primitiveExChunklet.store[x][z];
}
else {
return;
}
this.newArray = this.currentChunk.store[x][z];
if (this.oldArray.length < 64) {
return;
}
else if (this.newArray.length < ((this.y * 64) + 64)) {
return;
}
System.arraycopy(this.oldArray, 0, this.newArray, (this.y * 64), 64);
}
}
}
this.manager.unloadChunk(this.cx, this.cz, this.world);
this.newManager.unloadChunk(this.cx, this.cz, this.world);
for (File yFile : dataDir.listFiles()) {
if (!yFile.exists()) {
continue;
}
yFile.delete();
}
stop();
}
public void stop() {
if (this.taskID < 0) {
return;
}
this.scheduler.cancelTask(taskID);
this.taskID = -1;
this.cxs = null;
this.czs = null;
this.chunkletName = null;
this.chunkName = null;
this.manager = null;
this.xDir = null;
this.dataDir = null;
this.tempChunklet = null;
this.primitiveChunklet = null;
this.primitiveExChunklet = null;
this.currentChunk = null;
}
}

View File

@ -149,6 +149,15 @@ public final class CommandRegistrationManager {
command.setExecutor(new McgodCommand());
}
// private static void registerDropTreasureCommand() {
// PluginCommand command = mcMMO.p.getCommand("mmodroptreasures");
// command.setDescription(LocaleLoader.getString("Commands.Description.droptreasures"));
// command.setPermission("mcmmo.commands.droptreasures");
// command.setPermissionMessage(permissionsMessage);
// command.setUsage(LocaleLoader.getString("Commands.Usage.0", "mcgod"));
// command.setExecutor(new DropTreasureCommand());
// }
private static void registerMmoInfoCommand() {
PluginCommand command = mcMMO.p.getCommand("mmoinfo");
command.setDescription(LocaleLoader.getString("Commands.Description.mmoinfo"));

View File

@ -383,9 +383,11 @@ public final class CombatUtils {
EntityType entityType = painSource.getType();
if (target instanceof Player) {
if(ExperienceConfig.getInstance().isNPCInteractionPrevented()) {
if (Misc.isNPCEntityExcludingVillagers(target)) {
return;
}
}
Player player = (Player) target;
if (!mcMMO.getUserManager().hasPlayerDataKey(player)) {
@ -755,7 +757,7 @@ public final class CombatUtils {
break;
}
if (Misc.isNPCEntityExcludingVillagers(entity) || !(entity instanceof LivingEntity) || !shouldBeAffected(attacker, entity)) {
if ((ExperienceConfig.getInstance().isNPCInteractionPrevented() && Misc.isNPCEntityExcludingVillagers(entity)) || !(entity instanceof LivingEntity) || !shouldBeAffected(attacker, entity)) {
continue;
}

View File

@ -35,6 +35,10 @@ ExploitFix:
TreeFellerReducedXP: true
PistonCheating: true
SnowGolemExcavation: true
# This include NPCs from stuff like Citizens, this is not a setting for Vanilla Minecraft Villagers (Which can be considered NPCs)
# mcMMO normally doesn't process attacks against an Entity if it is an NPC from another plugin
# Of course, mcMMO doesn't know for sure whether or not something is an NPC, it checks a few known things, see our source code to see how
PreventPluginNPCInteraction: true
Experience_Bars:
# Turn this to false if you wanna disable XP bars
Enable: true

View File

@ -0,0 +1,852 @@
#
# Settings for Fishing Treasures / Shake Treasures
# Last updated on 12/28/2020
###
Fishing:
LEATHER_BOOTS:
Amount: 1
XP: 200
Rarity: COMMON
LEATHER_HELMET:
Amount: 1
XP: 200
Rarity: COMMON
LEATHER_LEGGINGS:
Amount: 1
XP: 200
Rarity: COMMON
LEATHER_CHESTPLATE:
Amount: 1
XP: 200
Rarity: COMMON
WOODEN_SWORD:
Amount: 1
XP: 200
Rarity: COMMON
WOODEN_SHOVEL:
Amount: 1
XP: 200
Rarity: COMMON
WOODEN_PICKAXE:
Amount: 1
XP: 200
Rarity: COMMON
WOODEN_AXE:
Amount: 1
XP: 200
Rarity: COMMON
WOODEN_HOE:
Amount: 1
XP: 200
Rarity: COMMON
LAPIS_LAZULI:
Amount: 20
XP: 200
Rarity: COMMON
STONE_SWORD:
Amount: 1
XP: 200
Rarity: UNCOMMON
STONE_SHOVEL:
Amount: 1
XP: 200
Rarity: UNCOMMON
STONE_PICKAXE:
Amount: 1
XP: 200
Rarity: UNCOMMON
STONE_AXE:
Amount: 1
XP: 200
Rarity: UNCOMMON
STONE_HOE:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_SWORD:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_SHOVEL:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_PICKAXE:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_AXE:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_HOE:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_BOOTS:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_HELMET:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_LEGGINGS:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_CHESTPLATE:
Amount: 1
XP: 200
Rarity: UNCOMMON
IRON_INGOT:
Amount: 5
XP: 200
Rarity: UNCOMMON
GOLD_INGOT:
Amount: 5
XP: 200
Rarity: UNCOMMON
IRON_SWORD:
Amount: 1
XP: 200
Rarity: RARE
IRON_SHOVEL:
Amount: 1
XP: 200
Rarity: RARE
IRON_PICKAXE:
Amount: 1
XP: 200
Rarity: RARE
IRON_AXE:
Amount: 1
XP: 200
Rarity: RARE
IRON_HOE:
Amount: 1
XP: 200
Rarity: RARE
BOW:
Amount: 1
XP: 200
Rarity: RARE
ENDER_PEARL:
Amount: 1
XP: 200
Rarity: RARE
BLAZE_ROD:
Amount: 1
XP: 200
Rarity: RARE
NAME_TAG:
Amount: 1
XP: 200
Rarity: RARE
IRON_BOOTS:
Amount: 1
XP: 200
Rarity: EPIC
IRON_HELMET:
Amount: 1
XP: 200
Rarity: EPIC
IRON_LEGGINGS:
Amount: 1
XP: 200
Rarity: EPIC
IRON_CHESTPLATE:
Amount: 1
XP: 200
Rarity: EPIC
GHAST_TEAR:
Amount: 1
XP: 200
Rarity: EPIC
DIAMOND:
Amount: 5
XP: 200
Rarity: EPIC
NAUTILUS_SHELL:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_SWORD:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_SHOVEL:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_PICKAXE:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_AXE:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_HOE:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_BOOTS:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_HELMET:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_LEGGINGS:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_CHESTPLATE:
Amount: 1
XP: 200
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:
Amount: 1
XP: 200
Rarity: MYTHIC
NETHERITE_SHOVEL:
Amount: 1
XP: 200
Rarity: MYTHIC
NETHERITE_PICKAXE:
Amount: 1
XP: 200
Rarity: MYTHIC
NETHERITE_AXE:
Amount: 1
XP: 200
Rarity: MYTHIC
NETHERITE_HOE:
Amount: 1
XP: 200
Rarity: MYTHIC
NETHERITE_BOOTS:
Amount: 1
XP: 200
Rarity: MYTHIC
NETHERITE_HELMET:
Amount: 1
XP: 200
Rarity: MYTHIC
NETHERITE_LEGGINGS:
Amount: 1
XP: 200
Rarity: MYTHIC
NETHERITE_CHESTPLATE:
Amount: 1
XP: 200
Rarity: MYTHIC
ENCHANTED_BOOK:
Amount: 1
XP: 400
Rarity: LEGENDARY
# Uncomment the below 3 lines to use the Whitelist, Alternatively rename it Enchantments_Blacklist to use the blacklist
# NOTE: Enchantments_Whitelist and Enchantments_Blacklist only do anything for Enchanted_Books at the moment, you can't use it with any other treasure definition
# Enchantments_Whitelist:
# - Fortune
# - Protection
NETHERITE_SCRAP:
Amount: 1
XP: 400
Rarity: LEGENDARY
#
# Fishing drop rates
###
Item_Drop_Rates:
Tier_1:
COMMON: 7.50
UNCOMMON: 1.25
RARE: 0.25
EPIC: 0.10
LEGENDARY: 0.01
MYTHIC: 0.01
Tier_2:
COMMON: 6.50
UNCOMMON: 1.75
RARE: 0.75
EPIC: 0.50
LEGENDARY: 0.05
MYTHIC: 0.01
Tier_3:
COMMON: 3.50
UNCOMMON: 2.75
RARE: 1.25
EPIC: 1.00
LEGENDARY: 0.10
MYTHIC: 0.01
Tier_4:
COMMON: 2.00
UNCOMMON: 3.50
RARE: 2.25
EPIC: 1.50
LEGENDARY: 1.00
MYTHIC: 0.01
Tier_5:
COMMON: 1.50
UNCOMMON: 3.75
RARE: 2.50
EPIC: 2.00
LEGENDARY: 1.00
MYTHIC: 0.01
Tier_6:
COMMON: 1.00
UNCOMMON: 3.25
RARE: 3.75
EPIC: 2.50
LEGENDARY: 1.50
MYTHIC: 0.05
Tier_7:
COMMON: 0.25
UNCOMMON: 2.75
RARE: 4.00
EPIC: 5.00
LEGENDARY: 2.50
MYTHIC: 0.10
Tier_8:
COMMON: 0.10
UNCOMMON: 1.50
RARE: 6.00
EPIC: 7.50
LEGENDARY: 5.00
MYTHIC: 0.25
#
# Fishing enchantment drop rates
###
Enchantments_Rarity:
COMMON:
EFFICIENCY: 1
UNBREAKING: 1
FORTUNE: 1
PROTECTION: 1
FIRE_PROTECTION: 1
FEATHER_FALLING: 1
BLAST_PROTECTION: 1
PROJECTILE_PROTECTION: 1
RESPIRATION: 1
THORNS: 1
SHARPNESS: 1
SMITE: 1
BANE_OF_ARTHROPODS: 1
POWER: 1
UNCOMMON:
EFFICIENCY: 2
PROTECTION: 2
FIRE_PROTECTION: 2
FEATHER_FALLING: 2
BLAST_PROTECTION: 2
PROJECTILE_PROTECTION: 2
SHARPNESS: 2
SMITE: 2
BANE_OF_ARTHROPODS: 2
KNOCKBACK: 1
LOOTING: 1
POWER: 2
PUNCH: 1
RARE:
EFFICIENCY: 3
UNBREAKING: 2
PROTECTION: 3
FIRE_PROTECTION: 3
FEATHER_FALLING: 3
BLAST_PROTECTION: 3
PROJECTILE_PROTECTION: 3
RESPIRATION: 2
SHARPNESS: 3
SMITE: 3
BANE_OF_ARTHROPODS: 3
FIRE_ASPECT: 1
LOOTING: 2
POWER: 3
EPIC:
EFFICIENCY: 4
FORTUNE: 2
AQUA_AFFINITY: 1
THORNS: 2
SHARPNESS: 4
SMITE: 4
BANE_OF_ARTHROPODS: 4
POWER: 4
FLAME: 1
LEGENDARY:
EFFICIENCY: 5
UNBREAKING: 3
FORTUNE: 3
PROTECTION: 4
FIRE_PROTECTION: 4
FEATHER_FALLING: 4
BLAST_PROTECTION: 4
PROJECTILE_PROTECTION: 4
RESPIRATION: 3
AQUA_AFFINITY: 1
THORNS: 3
SHARPNESS: 5
SMITE: 5
BANE_OF_ARTHROPODS: 5
KNOCKBACK: 2
FIRE_ASPECT: 2
LOOTING: 3
SILK_TOUCH: 1
POWER: 5
PUNCH: 2
INFINITY: 1
MYTHIC:
EFFICIENCY: 5
UNBREAKING: 3
FORTUNE: 3
PROTECTION: 4
FIRE_PROTECTION: 4
FEATHER_FALLING: 4
BLAST_PROTECTION: 4
PROJECTILE_PROTECTION: 4
RESPIRATION: 3
AQUA_AFFINITY: 1
THORNS: 3
SHARPNESS: 5
SMITE: 5
BANE_OF_ARTHROPODS: 5
KNOCKBACK: 2
FIRE_ASPECT: 2
LOOTING: 3
SILK_TOUCH: 1
POWER: 5
PUNCH: 2
INFINITY: 1
Enchantment_Drop_Rates:
Tier_1:
COMMON: 5.00
UNCOMMON: 1.00
RARE: 0.10
EPIC: 0.01
LEGENDARY: 0.01
MYTHIC: 0.01
Tier_2:
COMMON: 7.50
UNCOMMON: 1.00
RARE: 0.10
EPIC: 0.01
LEGENDARY: 0.01
MYTHIC: 0.01
Tier_3:
COMMON: 7.50
UNCOMMON: 2.50
RARE: 0.25
EPIC: 0.10
LEGENDARY: 0.01
MYTHIC: 0.01
Tier_4:
COMMON: 10.0
UNCOMMON: 2.75
RARE: 0.50
EPIC: 0.10
LEGENDARY: 0.05
MYTHIC: 0.05
Tier_5:
COMMON: 10.0
UNCOMMON: 4.00
RARE: 0.75
EPIC: 0.25
LEGENDARY: 0.10
MYTHIC: 0.10
Tier_6:
COMMON: 9.50
UNCOMMON: 5.50
RARE: 1.75
EPIC: 0.50
LEGENDARY: 0.25
MYTHIC: 0.25
Tier_7:
COMMON: 8.50
UNCOMMON: 7.50
RARE: 2.75
EPIC: 0.75
LEGENDARY: 0.50
MYTHIC: 0.50
Tier_8:
COMMON: 7.50
UNCOMMON: 10.0
RARE: 5.25
EPIC: 1.50
LEGENDARY: 0.75
MYTHIC: 0.75
#
# Settings for Shake
# If you are in retro mode, Drop_Level is multiplied by 10.
###
Shake:
BLAZE:
BLAZE_ROD:
Amount: 1
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
CAVE_SPIDER:
SPIDER_EYE:
Amount: 1
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
STRING:
Amount: 1
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
COBWEB:
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
POTION|0|POISON:
PotionData:
PotionType: POISON
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
CHICKEN:
FEATHER:
Amount: 1
XP: 0
Drop_Chance: 33.3
Drop_Level: 0
CHICKEN:
Amount: 1
XP: 0
Drop_Chance: 33.3
Drop_Level: 0
EGG:
Amount: 1
XP: 0
Drop_Chance: 33.3
Drop_Level: 0
COW:
MILK_BUCKET:
Amount: 1
XP: 0
Drop_Chance: 2.0
Drop_Level: 0
LEATHER:
Amount: 1
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
BEEF:
Amount: 1
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
CREEPER:
CREEPER_HEAD:
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
GUNPOWDER:
Amount: 1
XP: 0
Drop_Chance: 99.0
Drop_Level: 0
ENDERMAN:
ENDER_PEARL:
Amount: 1
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
GHAST:
GUNPOWDER:
Amount: 1
XP: 0
Drop_Chance: 50.0
Drop_Level: 0
GHAST_TEAR:
Amount: 1
XP: 0
Drop_Chance: 50.0
Drop_Level: 0
HORSE:
LEATHER:
Amount: 1
XP: 0
Drop_Chance: 99.0
Drop_Level: 0
SADDLE:
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
IRON_GOLEM:
PUMPKIN:
Amount: 1
XP: 0
Drop_Chance: 3.0
Drop_Level: 0
IRON_INGOT:
Amount: 1
XP: 0
Drop_Chance: 12.0
Drop_Level: 0
POPPY:
Amount: 1
XP: 0
Drop_Chance: 85.0
Drop_Level: 0
MAGMA_CUBE:
MAGMA_CREAM:
Amount: 1
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
MUSHROOM_COW:
MILK_BUCKET:
Amount: 1
XP: 0
Drop_Chance: 5.0
Drop_Level: 0
MUSHROOM_STEW:
Amount: 1
XP: 0
Drop_Chance: 5.0
Drop_Level: 0
LEATHER:
Amount: 1
XP: 0
Drop_Chance: 30.0
Drop_Level: 0
BEEF:
Amount: 1
XP: 0
Drop_Chance: 30.0
Drop_Level: 0
RED_MUSHROOM:
Amount: 2
XP: 0
Drop_Chance: 30.0
Drop_Level: 0
PIG:
PORKCHOP:
Amount: 1
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
PIG_ZOMBIE:
ROTTEN_FLESH:
Amount: 1
XP: 0
Drop_Chance: 50.0
Drop_Level: 0
GOLD_NUGGET:
Amount: 1
XP: 0
Drop_Chance: 50.0
Drop_Level: 0
PLAYER:
SKELETON_SKULL:
Amount: 1
XP: 0
Drop_Chance: 0.0
Drop_Level: 0
INVENTORY:
Whole_Stacks: false
Drop_Chance: 0.0
Drop_Level: 0
SHEEP:
WHITE_WOOL:
Amount: 3
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
SHULKER:
SHULKER_SHELL:
Amount: 1
XP: 0
Drop_Chance: 25.0
Drop_Level: 0
PURPUR_BLOCK:
Amount: 1
XP: 0
Drop_Chance: 75.0
Drop_Level: 0
SKELETON:
SKELETON_SKULL:
Amount: 1
XP: 0
Drop_Chance: 2.0
Drop_Level: 0
BONE:
Amount: 1
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
ARROW:
Amount: 2
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
SLIME:
SLIME_BALL:
Amount: 1
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
SPIDER:
SPIDER_EYE:
Amount: 1
XP: 0
Drop_Chance: 50.0
Drop_Level: 0
STRING:
Amount: 1
XP: 0
Drop_Chance: 50.0
Drop_Level: 0
SNOWMAN:
PUMPKIN:
Amount: 1
XP: 0
Drop_Chance: 3.0
Drop_Level: 0
SNOWBALL:
Amount: 2
XP: 0
Drop_Chance: 97.0
Drop_Level: 0
SQUID:
INK_SAC:
Amount: 1
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
WITCH:
SPLASH_POTION|0|INSTANT_HEAL:
PotionData:
PotionType: INSTANT_HEAL
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
SPLASH_POTION|0|FIRE_RESISTANCE:
PotionData:
PotionType: FIRE_RESISTANCE
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
SPLASH_POTION|0|SPEED:
PotionData:
PotionType: SPEED
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
GLASS_BOTTLE:
Amount: 1
XP: 0
Drop_Chance: 7.0
Drop_Level: 0
GLOWSTONE_DUST:
Amount: 1
XP: 0
Drop_Chance: 15.0
Drop_Level: 0
GUNPOWDER:
Amount: 1
XP: 0
Drop_Chance: 15.0
Drop_Level: 0
REDSTONE:
Amount: 1
XP: 0
Drop_Chance: 15.0
Drop_Level: 0
SPIDER_EYE:
Amount: 1
XP: 0
Drop_Chance: 15.0
Drop_Level: 0
STICK:
Amount: 1
XP: 0
Drop_Chance: 15.0
Drop_Level: 0
SUGAR:
Amount: 1
XP: 0
Drop_Chance: 15.0
Drop_Level: 0
WITHER_SKELETON:
WITHER_SKELETON_SKULL:
Amount: 1
XP: 0
Drop_Chance: 2.0
Drop_Level: 0
BONE:
Amount: 1
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
COAL:
Amount: 2
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
ZOMBIE:
ZOMBIE_HEAD:
Amount: 1
XP: 0
Drop_Chance: 2.0
Drop_Level: 0
ROTTEN_FLESH:
Amount: 1
XP: 0
Drop_Chance: 98.0
Drop_Level: 0

View File

@ -380,7 +380,7 @@ Fishing.SubSkill.Shake.Stat = Rei\u00DFen Chance
Fishing.SubSkill.TreasureHunter.Description = Angle verschiedene Objekte
Fishing.SubSkill.TreasureHunter.Name = Schatz J\u00E4ger
Fishing.SubSkill.TreasureHunter.Stat = Schatz J\u00E4ger Rang: &a{0}&3/&a{1}
Fishing.SubSkill.TreasureHunter.Stat.Extra = Drop Rate: &7\u00DCblich: &e{0} &aUn\u00FCblich: &e{1}\r\n&9Selten: &e{2} &dEpisch: &e{3} &6Legend\u00E4r: &e{4} &bSchallplatte: &e{5}
Fishing.SubSkill.TreasureHunter.Stat.Extra = Drop Rate: &7\u00DCblich: &e{0} &aUn\u00FCblich: &e{1}\r\n&9Selten: &e{2} &dEpisch: &e{3} &6Legend\u00E4r: &e{4} &bMythic: &e{5}
Guides.Acrobatics.Section.0 = &3\u00DCber Akrobatik:\n&eAkrobatik ist die Kunst sich anmutig fortzubewegen.\n&eFall- und Kampfschaden werden reduziert\n\n&3XP GAIN:\n&eErfahrung sammelst du indem du in K\u00E4mpfen\n&eausweichst oder St\u00FCrze aus gro\u00DFen H\u00F6hen \u00FCberlebst.
Guides.Acrobatics.Section.1 = &3Wie funktioniert Abrollen?\n&eAb und zu rollst du beim Fallen ab und der Fallschaden wird\n&ereduziert. Wenn du den Schleichen Knopf w\u00E4hrend dem Fallen\n&eh\u00E4ltst, verdoppelt sich die Chance abzurollen.\n&eIn dem Fall rollst du anmutig ab.\n&eAnmutige Rollen sind wie normale Rollen, nur dass\n&esie \u00F6fter passieren und damit mehr Schutz vor St\u00FCrzen\n&eliefern.

View File

@ -247,7 +247,7 @@ Fishing.Ability.Locked.2=LOCKED UNTIL {0}+ SKILL (MASTER ANGLER)
Fishing.SubSkill.TreasureHunter.Name=Treasure Hunter
Fishing.SubSkill.TreasureHunter.Description=Fish up misc. objects
Fishing.SubSkill.TreasureHunter.Stat=Treasure Hunter Rank: &a{0}&3/&a{1}
Fishing.SubSkill.TreasureHunter.Stat.Extra=Drop Rate: &7Common: &e{0} &aUncommon: &e{1}\n&9Rare: &e{2} &dEpic: &e{3} &6Legendary: &e{4} &bRecord: &e{5}
Fishing.SubSkill.TreasureHunter.Stat.Extra=Drop Rate: &7Common: &e{0} &aUncommon: &e{1}\n&9Rare: &e{2} &dEpic: &e{3} &6Legendary: &e{4} &bMythic: &e{5}
Fishing.SubSkill.MagicHunter.Name=Magic Hunter
Fishing.SubSkill.MagicHunter.Description=Find Enchanted Items
Fishing.SubSkill.MagicHunter.Stat=Magic Hunter Chance
@ -615,6 +615,7 @@ Commands.Cooldowns.Header=&6--= &amcMMO Ability Cooldowns&6 =--
Commands.Cooldowns.Row.N=\ &c{0}&f - &6{1} seconds left
Commands.Cooldowns.Row.Y=\ &b{0}&f - &2Ready!
Commands.Database.CooldownMS=You must wait {0} milliseconds before using this command again.
Commands.Database.Cooldown=You must wait {0} seconds before using this command again.
Commands.Database.Processing=Your previous command is still being processed. Please wait.
Commands.Disabled=This command is disabled.
Commands.DoesNotExist= &cPlayer does not exist in the database!

View File

@ -240,7 +240,7 @@ Fishing.Ability.Locked.2=Bloqu\u00e9 jusqu\'\u00e0 {0}+ niveau(x) (Ma\u00eetre P
Fishing.SubSkill.TreasureHunter.Name=Chasseur de tr\u00e9sors
Fishing.SubSkill.TreasureHunter.Description=Remonte des objets inhabituels
Fishing.SubSkill.TreasureHunter.Stat=Grade de chasseur de tr\u00e9sor: &a{0}&3/&a{1}
Fishing.SubSkill.TreasureHunter.Stat.Extra=Ratio de drop: &7Commun: &e{0} &aNon-commun: &e{1}\n&9Rare: &e{2} &dEpique: &e{3} &6Legendaire: &e{4} &bRecord: &e{5}
Fishing.SubSkill.TreasureHunter.Stat.Extra=Ratio de drop: &7Commun: &e{0} &aNon-commun: &e{1}\n&9Rare: &e{2} &dEpique: &e{3} &6Legendaire: &e{4} &bMythic: &e{5}
Fishing.SubSkill.MagicHunter.Name=P\u00eache magique
Fishing.SubSkill.MagicHunter.Description=Remonte des objets magiques
Fishing.SubSkill.MagicHunter.Stat=Chance du chasseur de tr\u00e9sor

View File

@ -240,7 +240,7 @@ Fishing.Ability.Locked.2=LEZ\u00C1RVA {0}+ K\u00C9PESS\u00C9G SZINTIG (MESTER HO
Fishing.SubSkill.TreasureHunter.Name=Kincsvad\u00E1sz
Fishing.SubSkill.TreasureHunter.Description=Furcsa t\u00E1rgyak kihal\u00E1sz\u00E1sa
Fishing.SubSkill.TreasureHunter.Stat=Kincsvad\u00E1sz Szint: &a{0}&3/&a{1}
Fishing.SubSkill.TreasureHunter.Stat.Extra=T\u00E1rgy Es\u00E9si Es\u00E9ly: &7\u00C1tlagos: &e{0} &aRendk\u00EDv\u00FCli: &e{1}\n&9Ritka: &e{2} &dEpikus: &e{3} &6Legend\u00E1s: &e{4} &bRekord: &e{5}
Fishing.SubSkill.TreasureHunter.Stat.Extra=T\u00E1rgy Es\u00E9si Es\u00E9ly: &7\u00C1tlagos: &e{0} &aRendk\u00EDv\u00FCli: &e{1}\n&9Ritka: &e{2} &dEpikus: &e{3} &6Legend\u00E1s: &e{4} &bMythic: &e{5}
Fishing.SubSkill.MagicHunter.Name=M\u00E1gikus Vad\u00E1sz
Fishing.SubSkill.MagicHunter.Description=Elvar\u00E1zsolt T\u00E1rgyak Megtal\u00E1l\u00E1sa
Fishing.SubSkill.MagicHunter.Stat=Es\u00E9ly M\u00E1gikus Vad\u00E1szra

View File

@ -247,7 +247,7 @@ Fishing.Ability.Locked.2=BLOCCATO FINO AD ABILIT\u00E0 {0}+ (PESCATORE PROVETTO)
Fishing.SubSkill.TreasureHunter.Name=Cacciatore di Tesori
Fishing.SubSkill.TreasureHunter.Description=Pesca oggetti vari
Fishing.SubSkill.TreasureHunter.Stat=Grado Cacciatore di Tesori: &a{0}&3/&a{1}
Fishing.SubSkill.TreasureHunter.Stat.Extra=Tasso di Drop: &7Comune: &e{0} &aNon comune: &e{1}\n&9Raro: &e{2} &dEpico: &e{3} &6Leggendario: &e{4} &bRecord: &e{5}
Fishing.SubSkill.TreasureHunter.Stat.Extra=Tasso di Drop: &7Comune: &e{0} &aNon comune: &e{1}\n&9Raro: &e{2} &dEpico: &e{3} &6Leggendario: &e{4} &bMythic: &e{5}
Fishing.SubSkill.MagicHunter.Name=Cacciatore di Magia
Fishing.SubSkill.MagicHunter.Description=Trova Oggetti Incantati
Fishing.SubSkill.MagicHunter.Stat=Possibilit\u00E0 Cacciatore di Magia

View File

@ -230,7 +230,7 @@ Fishing.Ability.Locked.2=\u30ed\u30c3\u30af\u3055\u308c\u308b\u307e\u3067 {0}+ \
Fishing.SubSkill.TreasureHunter.Name=\u30c8\u30ec\u30b8\u30e3\u30fc\u30cf\u30f3\u30bf\u30fc
Fishing.SubSkill.TreasureHunter.Description=\u9b5a\u3084\u7269\u3092\u91e3\u308a\u4e0a\u3052\u308b\u3002
Fishing.SubSkill.TreasureHunter.Stat=\u30c8\u30ec\u30b8\u30e3\u30fc\u30cf\u30f3\u30bf\u30fc \u30e9\u30f3\u30af: &a{0}&3/&a{1}
Fishing.SubSkill.TreasureHunter.Stat.Extra=\u30c9\u30ed\u30c3\u30d7\u7387: &7\u30b3\u30e2\u30f3: &e{0} &a\u30a2\u30f3\u30b3\u30e2\u30f3: &e{1}\n&9\u30ec\u30a2: &e{2} &d\u30a8\u30d4\u30c3\u30af: &e{3} &6\u30ec\u30b8\u30a7\u30f3\u30c0\u30ea\u30fc: &e{4} &b\u30ec\u30b3\u30fc\u30c9: &e{5}
Fishing.SubSkill.TreasureHunter.Stat.Extra=\u30c9\u30ed\u30c3\u30d7\u7387: &7\u30b3\u30e2\u30f3: &e{0} &a\u30a2\u30f3\u30b3\u30e2\u30f3: &e{1}\n&9\u30ec\u30a2: &e{2} &d\u30a8\u30d4\u30c3\u30af: &e{3} &6\u30ec\u30b8\u30a7\u30f3\u30c0\u30ea\u30fc: &e{4} &bMythic: &e{5}
Fishing.SubSkill.MagicHunter.Name=\u30de\u30b8\u30c3\u30af\u30cf\u30f3\u30bf\u30fc
Fishing.SubSkill.MagicHunter.Description=\u30a8\u30f3\u30c1\u30e3\u30f3\u30c8\u3055\u308c\u305f\u30a2\u30a4\u30c6\u30e0\u3092\u898b\u3064\u3051\u308b\u3002
Fishing.SubSkill.MagicHunter.Stat=\u30de\u30b8\u30c3\u30af\u30cf\u30f3\u30bf\u30fc \u78ba\u7387

View File

@ -240,7 +240,7 @@ Fishing.Ability.Locked.2=LOCKED UNTIL {0}+ SKILL (MASTER ANGLER)
Fishing.SubSkill.TreasureHunter.Name=Lobių ieškotojas
Fishing.SubSkill.TreasureHunter.Description=Fish up misc. objects
Fishing.SubSkill.TreasureHunter.Stat=Lobių ieškotojo rankas: &a{0}&3/&a{1}
Fishing.SubSkill.TreasureHunter.Stat.Extra=Drop Rate: &7Common: &e{0} &aUncommon: &e{1}\n&9Rare: &e{2} &dEpic: &e{3} &6Legendary: &e{4} &bRecord: &e{5}
Fishing.SubSkill.TreasureHunter.Stat.Extra=Drop Rate: &7Common: &e{0} &aUncommon: &e{1}\n&9Rare: &e{2} &dEpic: &e{3} &6Legendary: &e{4} &bMythic: &e{5}
Fishing.SubSkill.MagicHunter.Name=Magiškas žvejys
Fishing.SubSkill.MagicHunter.Description=Find Enchanted Items
Fishing.SubSkill.MagicHunter.Stat=Magic Hunter Chance

View File

@ -188,7 +188,7 @@ Fishing.Ability.FD=\u0420\u044b\u0431\u0430\u0446\u043a\u0430\u044f \u0414\u0438
Fishing.SubSkill.TreasureHunter.Name=\u041e\u0445\u043e\u0442\u043d\u0438\u043a \u0437\u0430 \u0421\u043e\u043a\u0440\u043e\u0432\u0438\u0449\u0430\u043c\u0438 (\u041f\u0430\u0441\u0441\u0438\u0432\u043d\u043e\u0435)
Fishing.SubSkill.TreasureHunter.Description=\u041b\u043e\u0432\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u043f\u0440\u0435\u0434\u043c\u0435\u0442\u043e\u0432
Fishing.SubSkill.TreasureHunter.Stat=\u0420\u0430\u043d\u0433 \u041e\u0445\u043e\u0442\u043d\u0438\u043a\u0430 \u0437\u0430 \u0421\u043e\u043a\u0440\u043e\u0432\u0438\u0449\u0430\u043c\u0438: &a{0}&3/&a{1}
Fishing.SubSkill.TreasureHunter.Stat.Extra=\u0428\u0430\u043d\u0441 \u0434\u0440\u043e\u043f\u0430: &7\u041e\u0431\u044b\u0447\u043d\u044b\u0439: &e{0} &a\u041d\u0435\u043e\u0431\u044b\u0447\u043d\u044b\u0439: &e{1}\n&9\u0420\u0435\u0434\u043a\u0438\u0439: &e{2} &d\u042d\u043f\u0438\u0447\u0435\u0441\u043a\u0438\u0439: &e{3} &6\u041b\u0435\u0433\u0435\u043d\u0434\u0430\u0440\u043d\u044b\u0439: &e{4} &b\u041f\u043b\u0430\u0441\u0442\u0438\u043d\u043a\u0430: &e{5}
Fishing.SubSkill.TreasureHunter.Stat.Extra=\u0428\u0430\u043d\u0441 \u0434\u0440\u043e\u043f\u0430: &7\u041e\u0431\u044b\u0447\u043d\u044b\u0439: &e{0} &a\u041d\u0435\u043e\u0431\u044b\u0447\u043d\u044b\u0439: &e{1}\n&9\u0420\u0435\u0434\u043a\u0438\u0439: &e{2} &d\u042d\u043f\u0438\u0447\u0435\u0441\u043a\u0438\u0439: &e{3} &6\u041b\u0435\u0433\u0435\u043d\u0434\u0430\u0440\u043d\u044b\u0439: &e{4} &bMythic: &e{5}
Fishing.SubSkill.MagicHunter.Name=\u041e\u0445\u043e\u0442\u043d\u0438\u043a \u0417\u0430 \u041c\u0430\u0433\u0438\u0435\u0439
Fishing.SubSkill.MagicHunter.Description=\u041d\u0430\u0445\u043e\u0434\u043a\u0430 \u0417\u0430\u0447\u0430\u0440\u043e\u0432\u0430\u043d\u044b\u0445 \u041f\u0440\u0435\u0434\u043c\u0435\u0442\u043e\u0432
Fishing.SubSkill.MagicHunter.Stat=\u041e\u0445\u043e\u0442\u043d\u0438\u043a \u0417\u0430 \u041c\u0430\u0433\u0438\u0435\u0439 \u0428\u0430\u043d\u0441

View File

@ -240,7 +240,7 @@ Fishing.Ability.Locked.2=\u9501\u5b9a\u72b6\u6001,\u76f4\u5230 {0}+ \u6280\u80fd
Fishing.SubSkill.TreasureHunter.Name=\u6dd8\u91d1\u8005
Fishing.SubSkill.TreasureHunter.Description=\u9493\u51fa\u5404\u79cd\u5404\u6837\u7684\u7269\u54c1
Fishing.SubSkill.TreasureHunter.Stat=\u6dd8\u91d1\u8005\u7b49\u7ea7: &a{0}&3/&a{1}
Fishing.SubSkill.TreasureHunter.Stat.Extra=\u6389\u843d\u7387: &7\u4e00\u822c: &e{0} &a\u666e\u901a: &e{1}\n&9\u7a00\u6709: &e{2} &d\u7f55\u89c1: &e{3} &6\u53f2\u8bd7: &e{4} &b\u4f20\u8bf4: &e{5}
Fishing.SubSkill.TreasureHunter.Stat.Extra=\u6389\u843d\u7387: &7\u4e00\u822c: &e{0} &a\u666e\u901a: &e{1}\n&9\u7a00\u6709: &e{2} &d\u7f55\u89c1: &e{3} &6\u53f2\u8bd7: &e{4} &bMythic: &e{5}
Fishing.SubSkill.MagicHunter.Name=\u9b54\u6cd5\u730e\u4eba
Fishing.SubSkill.MagicHunter.Description=\u627e\u5230\u9644\u9b54\u7269\u54c1
Fishing.SubSkill.MagicHunter.Stat=\u9b54\u6cd5\u730e\u4eba\u51e0\u7387

View File

@ -19,6 +19,9 @@ load: POSTWORLD
api-version: 1.13
commands:
# mmodroptreasures:
# description: An admin command used to spawn treasure drops
# permission: mcmmo.commands.droptreasures
mmoxpbar:
aliases: xpbarsettings
description: Change XP bar settings

View File

@ -1,447 +1,4 @@
#
# Settings for Fishing
# Last updated on ${project.version}-b${BUILD_NUMBER}
###
Fishing:
LEATHER_BOOTS:
Amount: 1
XP: 200
Rarity: COMMON
LEATHER_HELMET:
Amount: 1
XP: 200
Rarity: COMMON
LEATHER_LEGGINGS:
Amount: 1
XP: 200
Rarity: COMMON
LEATHER_CHESTPLATE:
Amount: 1
XP: 200
Rarity: COMMON
WOODEN_SWORD:
Amount: 1
XP: 200
Rarity: COMMON
WOODEN_SHOVEL:
Amount: 1
XP: 200
Rarity: COMMON
WOODEN_PICKAXE:
Amount: 1
XP: 200
Rarity: COMMON
WOODEN_AXE:
Amount: 1
XP: 200
Rarity: COMMON
WOODEN_HOE:
Amount: 1
XP: 200
Rarity: COMMON
LAPIS_LAZULI:
Amount: 20
XP: 200
Rarity: COMMON
STONE_SWORD:
Amount: 1
XP: 200
Rarity: UNCOMMON
STONE_SHOVEL:
Amount: 1
XP: 200
Rarity: UNCOMMON
STONE_PICKAXE:
Amount: 1
XP: 200
Rarity: UNCOMMON
STONE_AXE:
Amount: 1
XP: 200
Rarity: UNCOMMON
STONE_HOE:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_SWORD:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_SHOVEL:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_PICKAXE:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_AXE:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_HOE:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_BOOTS:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_HELMET:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_LEGGINGS:
Amount: 1
XP: 200
Rarity: UNCOMMON
GOLDEN_CHESTPLATE:
Amount: 1
XP: 200
Rarity: UNCOMMON
IRON_INGOT:
Amount: 5
XP: 200
Rarity: UNCOMMON
GOLD_INGOT:
Amount: 5
XP: 200
Rarity: UNCOMMON
IRON_SWORD:
Amount: 1
XP: 200
Rarity: RARE
IRON_SHOVEL:
Amount: 1
XP: 200
Rarity: RARE
IRON_PICKAXE:
Amount: 1
XP: 200
Rarity: RARE
IRON_AXE:
Amount: 1
XP: 200
Rarity: RARE
IRON_HOE:
Amount: 1
XP: 200
Rarity: RARE
BOW:
Amount: 1
XP: 200
Rarity: RARE
ENDER_PEARL:
Amount: 1
XP: 200
Rarity: RARE
BLAZE_ROD:
Amount: 1
XP: 200
Rarity: RARE
IRON_BOOTS:
Amount: 1
XP: 200
Rarity: EPIC
IRON_HELMET:
Amount: 1
XP: 200
Rarity: EPIC
IRON_LEGGINGS:
Amount: 1
XP: 200
Rarity: EPIC
IRON_CHESTPLATE:
Amount: 1
XP: 200
Rarity: EPIC
GHAST_TEAR:
Amount: 1
XP: 200
Rarity: EPIC
DIAMOND:
Amount: 5
XP: 200
Rarity: EPIC
DIAMOND_SWORD:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_SHOVEL:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_PICKAXE:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_AXE:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_HOE:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_BOOTS:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_HELMET:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_LEGGINGS:
Amount: 1
XP: 200
Rarity: LEGENDARY
DIAMOND_CHESTPLATE:
Amount: 1
XP: 200
Rarity: LEGENDARY
MUSIC_DISC_BLOCKS:
Amount: 1
XP: 200
Rarity: RECORD
MUSIC_DISC_CHIRP:
Amount: 1
XP: 200
Rarity: RECORD
MUSIC_DISC_FAR:
Amount: 1
XP: 200
Rarity: RECORD
MUSIC_DISC_MALL:
Amount: 1
XP: 200
Rarity: RECORD
MUSIC_DISC_MELLOHI:
Amount: 1
XP: 200
Rarity: RECORD
MUSIC_DISC_STAL:
Amount: 1
XP: 200
Rarity: RECORD
MUSIC_DISC_STRAD:
Amount: 1
XP: 200
Rarity: RECORD
MUSIC_DISC_WARD:
Amount: 1
XP: 200
Rarity: RECORD
MUSIC_DISC_11:
Amount: 1
XP: 200
Rarity: RECORD
MUSIC_DISC_WAIT:
Amount: 1
XP: 200
Rarity: RECORD
MUSIC_DISC_13:
Amount: 1
XP: 200
Rarity: RECORD
#
# Fishing drop rates
###
Item_Drop_Rates:
Tier_1:
TRAP: 7.68
COMMON: 7.50
UNCOMMON: 1.25
RARE: 0.25
EPIC: 0.10
LEGENDARY: 0.01
RECORD: 0.01
Tier_2:
TRAP: 2.50
COMMON: 6.50
UNCOMMON: 1.75
RARE: 0.75
EPIC: 0.50
LEGENDARY: 0.05
RECORD: 0.01
Tier_3:
TRAP: 1.50
COMMON: 3.50
UNCOMMON: 2.75
RARE: 1.25
EPIC: 1.00
LEGENDARY: 0.10
RECORD: 0.01
Tier_4:
TRAP: 1.00
COMMON: 2.00
UNCOMMON: 3.50
RARE: 2.25
EPIC: 1.50
LEGENDARY: 1.00
RECORD: 0.01
Tier_5:
TRAP: 0.25
COMMON: 1.50
UNCOMMON: 3.75
RARE: 2.50
EPIC: 2.00
LEGENDARY: 1.00
RECORD: 0.01
Tier_6:
TRAP: 0.10
COMMON: 1.00
UNCOMMON: 3.25
RARE: 3.75
EPIC: 2.50
LEGENDARY: 1.50
RECORD: 0.05
Tier_7:
TRAP: 0.05
COMMON: 0.25
UNCOMMON: 2.75
RARE: 4.00
EPIC: 5.00
LEGENDARY: 2.50
RECORD: 0.10
Tier_8:
TRAP: 0.01
COMMON: 0.10
UNCOMMON: 1.50
RARE: 6.00
EPIC: 7.50
LEGENDARY: 5.00
RECORD: 0.25
#
# Fishing enchantment drop rates
###
Enchantments_Rarity:
COMMON:
EFFICIENCY: 1
UNBREAKING: 1
FORTUNE: 1
PROTECTION: 1
FIRE_PROTECTION: 1
FEATHER_FALLING: 1
BLAST_PROTECTION: 1
PROJECTILE_PROTECTION: 1
RESPIRATION: 1
THORNS: 1
SHARPNESS: 1
SMITE: 1
BANE_OF_ARTHROPODS: 1
POWER: 1
UNCOMMON:
EFFICIENCY: 2
PROTECTION: 2
FIRE_PROTECTION: 2
FEATHER_FALLING: 2
BLAST_PROTECTION: 2
PROJECTILE_PROTECTION: 2
SHARPNESS: 2
SMITE: 2
BANE_OF_ARTHROPODS: 2
KNOCKBACK: 1
LOOTING: 1
POWER: 2
PUNCH: 1
RARE:
EFFICIENCY: 3
UNBREAKING: 2
PROTECTION: 3
FIRE_PROTECTION: 3
FEATHER_FALLING: 3
BLAST_PROTECTION: 3
PROJECTILE_PROTECTION: 3
RESPIRATION: 2
SHARPNESS: 3
SMITE: 3
BANE_OF_ARTHROPODS: 3
FIRE_ASPECT: 1
LOOTING: 2
POWER: 3
EPIC:
EFFICIENCY: 4
FORTUNE: 2
AQUA_AFFINITY: 1
THORNS: 2
SHARPNESS: 4
SMITE: 4
BANE_OF_ARTHROPODS: 4
POWER: 4
FLAME: 1
LEGENDARY:
EFFICIENCY: 5
UNBREAKING: 3
FORTUNE: 3
PROTECTION: 4
FIRE_PROTECTION: 4
FEATHER_FALLING: 4
BLAST_PROTECTION: 4
PROJECTILE_PROTECTION: 4
RESPIRATION: 3
AQUA_AFFINITY: 1
THORNS: 3
SHARPNESS: 5
SMITE: 5
BANE_OF_ARTHROPODS: 5
KNOCKBACK: 2
FIRE_ASPECT: 2
LOOTING: 3
SILK_TOUCH: 1
POWER: 5
PUNCH: 2
INFINITY: 1
Enchantment_Drop_Rates:
Tier_1:
COMMON: 5.00
UNCOMMON: 1.00
RARE: 0.10
EPIC: 0.01
LEGENDARY: 0.01
Tier_2:
COMMON: 7.50
UNCOMMON: 1.00
RARE: 0.10
EPIC: 0.01
LEGENDARY: 0.01
Tier_3:
COMMON: 7.50
UNCOMMON: 2.50
RARE: 0.25
EPIC: 0.10
LEGENDARY: 0.01
Tier_4:
COMMON: 10.0
UNCOMMON: 2.75
RARE: 0.50
EPIC: 0.10
LEGENDARY: 0.05
Tier_5:
COMMON: 10.0
UNCOMMON: 4.00
RARE: 0.75
EPIC: 0.25
LEGENDARY: 0.10
Tier_6:
COMMON: 9.50
UNCOMMON: 5.50
RARE: 1.75
EPIC: 0.50
LEGENDARY: 0.25
Tier_7:
COMMON: 8.50
UNCOMMON: 7.50
RARE: 2.75
EPIC: 0.75
LEGENDARY: 0.50
Tier_8:
COMMON: 7.50
UNCOMMON: 10.0
RARE: 5.25
EPIC: 1.50
LEGENDARY: 0.75
#
# Settings for Excavation's Archaeology
# If you are in retro mode, Drop_Level is multiplied by 10.
###
@ -635,334 +192,3 @@ Hylian_Luck:
Drop_Chance: 100.0
Drop_Level: 0
Drops_From: [Pots]
#
# Settings for Shake
# If you are in retro mode, Drop_Level is multiplied by 10.
###
Shake:
BLAZE:
BLAZE_ROD:
Amount: 1
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
CAVE_SPIDER:
SPIDER_EYE:
Amount: 1
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
STRING:
Amount: 1
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
COBWEB:
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
POTION|0|POISON:
PotionData:
PotionType: POISON
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
CHICKEN:
FEATHER:
Amount: 1
XP: 0
Drop_Chance: 33.3
Drop_Level: 0
CHICKEN:
Amount: 1
XP: 0
Drop_Chance: 33.3
Drop_Level: 0
EGG:
Amount: 1
XP: 0
Drop_Chance: 33.3
Drop_Level: 0
COW:
MILK_BUCKET:
Amount: 1
XP: 0
Drop_Chance: 2.0
Drop_Level: 0
LEATHER:
Amount: 1
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
BEEF:
Amount: 1
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
CREEPER:
CREEPER_HEAD:
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
GUNPOWDER:
Amount: 1
XP: 0
Drop_Chance: 99.0
Drop_Level: 0
ENDERMAN:
ENDER_PEARL:
Amount: 1
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
GHAST:
GUNPOWDER:
Amount: 1
XP: 0
Drop_Chance: 50.0
Drop_Level: 0
GHAST_TEAR:
Amount: 1
XP: 0
Drop_Chance: 50.0
Drop_Level: 0
HORSE:
LEATHER:
Amount: 1
XP: 0
Drop_Chance: 99.0
Drop_Level: 0
SADDLE:
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
IRON_GOLEM:
PUMPKIN:
Amount: 1
XP: 0
Drop_Chance: 3.0
Drop_Level: 0
IRON_INGOT:
Amount: 1
XP: 0
Drop_Chance: 12.0
Drop_Level: 0
POPPY:
Amount: 1
XP: 0
Drop_Chance: 85.0
Drop_Level: 0
MAGMA_CUBE:
MAGMA_CREAM:
Amount: 1
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
MUSHROOM_COW:
MILK_BUCKET:
Amount: 1
XP: 0
Drop_Chance: 5.0
Drop_Level: 0
MUSHROOM_STEW:
Amount: 1
XP: 0
Drop_Chance: 5.0
Drop_Level: 0
LEATHER:
Amount: 1
XP: 0
Drop_Chance: 30.0
Drop_Level: 0
BEEF:
Amount: 1
XP: 0
Drop_Chance: 30.0
Drop_Level: 0
RED_MUSHROOM:
Amount: 2
XP: 0
Drop_Chance: 30.0
Drop_Level: 0
PIG:
PORKCHOP:
Amount: 1
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
PIG_ZOMBIE:
ROTTEN_FLESH:
Amount: 1
XP: 0
Drop_Chance: 50.0
Drop_Level: 0
GOLD_NUGGET:
Amount: 1
XP: 0
Drop_Chance: 50.0
Drop_Level: 0
PLAYER:
SKELETON_SKULL:
Amount: 1
XP: 0
Drop_Chance: 0.0
Drop_Level: 0
INVENTORY:
Whole_Stacks: false
Drop_Chance: 0.0
Drop_Level: 0
SHEEP:
WHITE_WOOL:
Amount: 3
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
SHULKER:
SHULKER_SHELL:
Amount: 1
XP: 0
Drop_Chance: 25.0
Drop_Level: 0
PURPUR_BLOCK:
Amount: 1
XP: 0
Drop_Chance: 75.0
Drop_Level: 0
SKELETON:
SKELETON_SKULL:
Amount: 1
XP: 0
Drop_Chance: 2.0
Drop_Level: 0
BONE:
Amount: 1
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
ARROW:
Amount: 2
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
SLIME:
SLIME_BALL:
Amount: 1
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
SPIDER:
SPIDER_EYE:
Amount: 1
XP: 0
Drop_Chance: 50.0
Drop_Level: 0
STRING:
Amount: 1
XP: 0
Drop_Chance: 50.0
Drop_Level: 0
SNOWMAN:
PUMPKIN:
Amount: 1
XP: 0
Drop_Chance: 3.0
Drop_Level: 0
SNOWBALL:
Amount: 2
XP: 0
Drop_Chance: 97.0
Drop_Level: 0
SQUID:
INK_SAC:
Amount: 1
XP: 0
Drop_Chance: 100.0
Drop_Level: 0
WITCH:
SPLASH_POTION|0|INSTANT_HEAL:
PotionData:
PotionType: INSTANT_HEAL
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
SPLASH_POTION|0|FIRE_RESISTANCE:
PotionData:
PotionType: FIRE_RESISTANCE
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
SPLASH_POTION|0|SPEED:
PotionData:
PotionType: SPEED
Amount: 1
XP: 0
Drop_Chance: 1.0
Drop_Level: 0
GLASS_BOTTLE:
Amount: 1
XP: 0
Drop_Chance: 7.0
Drop_Level: 0
GLOWSTONE_DUST:
Amount: 1
XP: 0
Drop_Chance: 15.0
Drop_Level: 0
GUNPOWDER:
Amount: 1
XP: 0
Drop_Chance: 15.0
Drop_Level: 0
REDSTONE:
Amount: 1
XP: 0
Drop_Chance: 15.0
Drop_Level: 0
SPIDER_EYE:
Amount: 1
XP: 0
Drop_Chance: 15.0
Drop_Level: 0
STICK:
Amount: 1
XP: 0
Drop_Chance: 15.0
Drop_Level: 0
SUGAR:
Amount: 1
XP: 0
Drop_Chance: 15.0
Drop_Level: 0
WITHER_SKELETON:
WITHER_SKELETON_SKULL:
Amount: 1
XP: 0
Drop_Chance: 2.0
Drop_Level: 0
BONE:
Amount: 1
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
COAL:
Amount: 2
XP: 0
Drop_Chance: 49.0
Drop_Level: 0
ZOMBIE:
ZOMBIE_HEAD:
Amount: 1
XP: 0
Drop_Chance: 2.0
Drop_Level: 0
ROTTEN_FLESH:
Amount: 1
XP: 0
Drop_Chance: 98.0
Drop_Level: 0

View File

@ -0,0 +1,308 @@
import com.gmail.nossr50.util.blockmeta.*;
import com.google.common.io.Files;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.junit.*;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.io.*;
import java.util.UUID;
import static org.mockito.Mockito.mock;
/**
* Could be a lot better. But some tests are better than none! Tests the major things, still kinda unit-testy. Verifies that the serialization isn't completely broken.
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest(Bukkit.class)
public class ChunkStoreTest {
private static File tempDir;
@BeforeClass
public static void setUpClass() {
tempDir = Files.createTempDir();
}
@AfterClass
public static void tearDownClass() {
recursiveDelete(tempDir);
}
private World mockWorld;
@Before
public void setUpMock(){
UUID worldUUID = UUID.randomUUID();
mockWorld = mock(World.class);
Mockito.when(mockWorld.getUID()).thenReturn(worldUUID);
Mockito.when(mockWorld.getMaxHeight()).thenReturn(256);
Mockito.when(mockWorld.getWorldFolder()).thenReturn(tempDir);
PowerMockito.mockStatic(Bukkit.class);
Mockito.when(Bukkit.getWorld(worldUUID)).thenReturn(mockWorld);
}
@Test
public void testSetValue() {
BitSetChunkStore original = new BitSetChunkStore(mockWorld, 0, 0);
original.setTrue(0, 0, 0);
Assert.assertTrue(original.isTrue(0, 0, 0));
original.setFalse(0, 0, 0);
Assert.assertFalse(original.isTrue(0, 0, 0));
}
@Test
public void testIsEmpty() {
BitSetChunkStore original = new BitSetChunkStore(mockWorld, 0, 0);
Assert.assertTrue(original.isEmpty());
original.setTrue(0, 0, 0);
original.setFalse(0, 0, 0);
Assert.assertTrue(original.isEmpty());
}
@Test
public void testRoundTrip() throws IOException {
BitSetChunkStore original = new BitSetChunkStore(mockWorld, 1, 2);
original.setTrue(14, 89, 12);
original.setTrue(14, 90, 12);
original.setTrue(13, 89, 12);
byte[] serializedBytes = serializeChunkstore(original);
ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes)));
assertEqual(original, deserialized);
}
@Test
public void testChunkCoords() throws IOException {
for (int x = -96; x < 0; x++) {
int cx = x >> 4;
int ix = Math.abs(x) % 16;
System.out.print(cx + ":" + ix + " ");
}
}
@Test
public void testUpgrade() throws IOException {
LegacyChunkStore original = new LegacyChunkStore(mockWorld, 12, 32);
original.setTrue(14, 89, 12);
original.setTrue(14, 90, 12);
original.setTrue(13, 89, 12);
byte[] serializedBytes = serializeChunkstore(original);
ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes)));
assertEqual(original, deserialized);
}
@Test
public void testSimpleRegionRoundtrip() throws IOException {
LegacyChunkStore original = new LegacyChunkStore(mockWorld, 12, 12);
original.setTrue(14, 89, 12);
original.setTrue(14, 90, 12);
original.setTrue(13, 89, 12);
File file = new File(tempDir, "SimpleRegionRoundTrip.region");
McMMOSimpleRegionFile region = new McMMOSimpleRegionFile(file, 0, 0);
try (DataOutputStream outputStream = region.getOutputStream(12, 12)){
outputStream.write(serializeChunkstore(original));
}
region.close();
region = new McMMOSimpleRegionFile(file, 0, 0);
try (DataInputStream is = region.getInputStream(original.getChunkX(), original.getChunkZ()))
{
Assert.assertNotNull(is);
ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(is);
assertEqual(original, deserialized);
}
region.close();
file.delete();
}
@Test
public void testSimpleRegionRejectsOutOfBounds() {
File file = new File(tempDir, "SimpleRegionRoundTrip.region");
McMMOSimpleRegionFile region = new McMMOSimpleRegionFile(file, 0, 0);
assertThrows(() -> region.getOutputStream(-1, 0), IndexOutOfBoundsException.class);
assertThrows(() -> region.getOutputStream(0, -1), IndexOutOfBoundsException.class);
assertThrows(() -> region.getOutputStream(32, 0), IndexOutOfBoundsException.class);
assertThrows(() -> region.getOutputStream(0, 32), IndexOutOfBoundsException.class);
region.close();
}
@Test
public void testChunkStoreRejectsOutOfBounds() {
ChunkStore chunkStore = new BitSetChunkStore(mockWorld, 0, 0);
assertThrows(() -> chunkStore.setTrue(-1, 0, 0), IndexOutOfBoundsException.class);
assertThrows(() -> chunkStore.setTrue(0, -1, 0), IndexOutOfBoundsException.class);
assertThrows(() -> chunkStore.setTrue(0, 0, -1), IndexOutOfBoundsException.class);
assertThrows(() -> chunkStore.setTrue(16, 0, 0), IndexOutOfBoundsException.class);
assertThrows(() -> chunkStore.setTrue(0, mockWorld.getMaxHeight(), 0), IndexOutOfBoundsException.class);
assertThrows(() -> chunkStore.setTrue(0, 0, 16), IndexOutOfBoundsException.class);
}
@Test
public void testRegressionChunkMirrorBug() {
ChunkManager chunkManager = new HashChunkManager();
chunkManager.setTrue(15,0,15, mockWorld);
chunkManager.setFalse(-15, 0, -15, mockWorld);
Assert.assertTrue(chunkManager.isTrue(15, 0, 15, mockWorld));
}
private interface Delegate {
void run();
}
private void assertThrows(Delegate delegate, Class<?> clazz) {
try {
delegate.run();
Assert.fail(); // We didn't throw
}
catch (Throwable t) {
Assert.assertTrue(t.getClass().equals(clazz));
}
}
private void assertEqual(ChunkStore expected, ChunkStore actual)
{
Assert.assertEquals(expected.getChunkX(), actual.getChunkX());
Assert.assertEquals(expected.getChunkZ(), actual.getChunkZ());
Assert.assertEquals(expected.getWorldId(), actual.getWorldId());
for (int y = 0; y < 256; y++)
for (int x = 0; x < 16; x++)
for (int z = 0; z < 16; z++)
Assert.assertTrue(expected.isTrue(x, y, z) == actual.isTrue(x, y, z));
}
private static void recursiveDelete(File directoryToBeDeleted) {
if (directoryToBeDeleted.isDirectory()) {
for (File file : directoryToBeDeleted.listFiles()) {
recursiveDelete(file);
}
}
directoryToBeDeleted.delete();
}
private static byte[] serializeChunkstore(ChunkStore chunkStore) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
if (chunkStore instanceof BitSetChunkStore)
BitSetChunkStore.Serialization.writeChunkStore(new DataOutputStream(byteArrayOutputStream), chunkStore);
else
new UnitTestObjectOutputStream(byteArrayOutputStream).writeObject(chunkStore); // Serializes the class as if it were the old PrimitiveChunkStore
return byteArrayOutputStream.toByteArray();
}
public static class LegacyChunkStore implements ChunkStore, Serializable {
private static final long serialVersionUID = -1L;
transient private boolean dirty = false;
public boolean[][][] store;
private static final int CURRENT_VERSION = 7;
private static final int MAGIC_NUMBER = 0xEA5EDEBB;
private int cx;
private int cz;
private UUID worldUid;
public LegacyChunkStore(World world, int cx, int cz) {
this.cx = cx;
this.cz = cz;
this.worldUid = world.getUID();
this.store = new boolean[16][16][world.getMaxHeight()];
}
@Override
public boolean isDirty() {
return dirty;
}
@Override
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
@Override
public int getChunkX() {
return cx;
}
@Override
public int getChunkZ() {
return cz;
}
@Override
public UUID getWorldId() {
return worldUid;
}
@Override
public boolean isTrue(int x, int y, int z) {
return store[x][z][y];
}
@Override
public void setTrue(int x, int y, int z) {
if (y >= store[0][0].length || y < 0)
return;
store[x][z][y] = true;
dirty = true;
}
@Override
public void setFalse(int x, int y, int z) {
if (y >= store[0][0].length || y < 0)
return;
store[x][z][y] = false;
dirty = true;
}
@Override
public void set(int x, int y, int z, boolean value) {
if (y >= store[0][0].length || y < 0)
return;
store[x][z][y] = value;
dirty = true;
}
@Override
public boolean isEmpty() {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = 0; y < store[0][0].length; y++) {
if (store[x][z][y]) {
return false;
}
}
}
}
return true;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(MAGIC_NUMBER);
out.writeInt(CURRENT_VERSION);
out.writeLong(worldUid.getLeastSignificantBits());
out.writeLong(worldUid.getMostSignificantBits());
out.writeInt(cx);
out.writeInt(cz);
out.writeObject(store);
dirty = false;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
throw new UnsupportedOperationException();
}
}
private static class UnitTestObjectOutputStream extends ObjectOutputStream {
public UnitTestObjectOutputStream(OutputStream outputStream) throws IOException {
super(outputStream);
}
@Override
public void writeUTF(String str) throws IOException {
// Pretend to be the old class
if (str.equals(LegacyChunkStore.class.getName()))
str = "com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore";
super.writeUTF(str);
}
}
}