mirror of
https://github.com/mcMMO-Dev/mcMMO.git
synced 2024-11-24 14:16:45 +01:00
Merge branch 'master' of github.com:mcMMO-Dev/mcMMO into tridentsxbows
This commit is contained in:
commit
5b898049a3
@ -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
20
pom.xml
@ -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>
|
||||
|
@ -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;
|
||||
// }
|
||||
// }
|
||||
//}
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
@ -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); }
|
||||
|
383
src/main/java/com/gmail/nossr50/config/treasure/FishingTreasureConfig.java
Executable file
383
src/main/java/com/gmail/nossr50/config/treasure/FishingTreasureConfig.java
Executable 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());
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
*
|
@ -1,4 +1,4 @@
|
||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
||||
package com.gmail.nossr50.util.blockmeta;
|
||||
|
||||
import com.gmail.nossr50.config.HiddenConfig;
|
||||
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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() {}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
@ -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() {
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
@ -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(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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"));
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
852
src/main/resources/fishing_treasures.yml
Normal file
852
src/main/resources/fishing_treasures.yml
Normal 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
|
@ -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.
|
||||
|
@ -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!
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
308
src/test/java/ChunkStoreTest.java
Normal file
308
src/test/java/ChunkStoreTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user