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

This commit is contained in:
nossr50 2021-02-26 16:22:11 -08:00
commit 1a180c4cdf
18 changed files with 691 additions and 394 deletions

51
.github/workflows/maven.yml vendored Normal file
View File

@ -0,0 +1,51 @@
# This workflow automatically tests new commits and pull requests as they come in.
# Note that this does not upload any artifacts, you will need to compile mcMMO manually
# if you wish to create the actual jar.
name: Compile and test
on:
# We run our tests whenever the pom or a source file was touched.
# There is no need to run Maven when only the changelog was touched.
# We may also want to re-run this workflow when the workflow file itself
# was updated too.
push:
paths:
- 'src/**'
- 'pom.xml'
- '.github/workflows/maven.yml'
# Whenever someone submits a new pull request which modified the pom or a source file,
# we want to ensure it compiles successfully and that all tests will pass.
pull_request:
paths:
- 'src/**'
- 'pom.xml'
jobs:
compile:
name: Maven compiler
runs-on: ubuntu-latest
steps:
# 1. Check out the current working tree
- name: Checkout repository
uses: actions/checkout@v2
# 2. Setup Java 1.8 JDK
- name: Java 1.8 setup
uses: actions/setup-java@v1.4.3
with:
java-package: jdk
java-version: 1.8
# 3. Setup local Maven package cache to speed up building
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
# 4. Build via Maven
- name: Build via Maven
run: mvn verify -B --file pom.xml

View File

@ -1,4 +1,7 @@
Version 2.1.175 Version 2.1.175
Fixed a bug where mcMMO would occasionally give a 65 item stack from a double smelt on a furnace
Fixed a bug where arrows could be duped when fired from a crossbow with piercing enchantment
Added setting to enable or disable Green Thumb automatically replanting crops per crop to config.yml under 'Green_Thumb_Replanting_Crops' section
(API) Many skills with RNG elements now send out a SubSkillEvent (which can be used to modify probability or cancel the results), some skills without RNG still send out this event when activated, this event is cancellable so it can be used to make a skill fail (API) Many skills with RNG elements now send out a SubSkillEvent (which can be used to modify probability or cancel the results), some skills without RNG still send out this event when activated, this event is cancellable so it can be used to make a skill fail
Treasure drop rate from Shake, Fishing, Hylian, and Excavation now benefit from the Luck perk Treasure drop rate from Shake, Fishing, Hylian, and Excavation now benefit from the Luck perk
Added a setting to chat.yml to toggle sending party or admin chat messages to console Added a setting to chat.yml to toggle sending party or admin chat messages to console

17
pom.xml
View File

@ -101,6 +101,7 @@
<include>commons-logging:commons-logging</include> <include>commons-logging:commons-logging</include>
<include>org.apache.tomcat:tomcat-jdbc</include> <include>org.apache.tomcat:tomcat-jdbc</include>
<include>org.apache.tomcat:tomcat-juli</include> <include>org.apache.tomcat:tomcat-juli</include>
<include>org.bstats:bstats-base</include>
<include>org.bstats:bstats-bukkit</include> <include>org.bstats:bstats-bukkit</include>
<include>net.kyori:adventure-api</include> <include>net.kyori:adventure-api</include>
<include>net.kyori:adventure-text-serializer-gson</include> <include>net.kyori:adventure-text-serializer-gson</include>
@ -208,27 +209,27 @@
<dependency> <dependency>
<groupId>net.kyori</groupId> <groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson</artifactId> <artifactId>adventure-text-serializer-gson</artifactId>
<version>4.4.0</version> <version>4.5.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.kyori</groupId> <groupId>net.kyori</groupId>
<artifactId>adventure-api</artifactId> <artifactId>adventure-api</artifactId>
<version>4.4.0</version> <version>4.5.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.kyori</groupId> <groupId>net.kyori</groupId>
<artifactId>adventure-nbt</artifactId> <artifactId>adventure-nbt</artifactId>
<version>4.4.0</version> <version>4.5.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.kyori</groupId> <groupId>net.kyori</groupId>
<artifactId>adventure-key</artifactId> <artifactId>adventure-key</artifactId>
<version>4.4.0</version> <version>4.5.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.kyori</groupId> <groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-gson-legacy-impl</artifactId> <artifactId>adventure-text-serializer-gson-legacy-impl</artifactId>
<version>4.4.0</version> <version>4.5.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.kyori</groupId> <groupId>net.kyori</groupId>
@ -253,13 +254,13 @@
<dependency> <dependency>
<groupId>org.bstats</groupId> <groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId> <artifactId>bstats-bukkit</artifactId>
<version>1.8</version> <version>2.2.1</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.16.4-R0.1-SNAPSHOT</version> <version>1.16.5-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -307,7 +308,7 @@
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
<version>3.4.6</version> <version>3.8.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -9,6 +9,7 @@ import com.gmail.nossr50.util.text.StringUtils;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
@ -598,4 +599,7 @@ public class Config extends AutoUpdateConfigLoader {
public int getPowerLevelUpBroadcastInterval() { return config.getInt("General.Level_Up_Chat_Broadcasts.Broadcast_Powerlevels.Milestone_Interval", 100); } public int getPowerLevelUpBroadcastInterval() { return config.getInt("General.Level_Up_Chat_Broadcasts.Broadcast_Powerlevels.Milestone_Interval", 100); }
public boolean isMasterySystemEnabled() { return config.getBoolean( "General.PowerLevel.Skill_Mastery.Enabled"); } public boolean isMasterySystemEnabled() { return config.getBoolean( "General.PowerLevel.Skill_Mastery.Enabled"); }
public boolean isGreenThumbReplantableCrop(@NotNull Material material) {
return config.getBoolean("Green_Thumb_Replanting_Crops." + StringUtils.getCapitalized(material.toString()), true);
}
} }

View File

@ -148,10 +148,13 @@ public class BlockListener implements Listener {
// Get opposite direction so we get correct block // Get opposite direction so we get correct block
BlockFace direction = event.getDirection(); BlockFace direction = event.getDirection();
Block movedBlock = event.getBlock().getRelative(direction); Block movedBlock = event.getBlock().getRelative(direction);
if (movedBlock.getY() >= Misc.getWorldMinCompat(movedBlock.getWorld())) // Very weird that the event is giving us these, they shouldn't exist
mcMMO.getPlaceStore().setTrue(movedBlock); mcMMO.getPlaceStore().setTrue(movedBlock);
for (Block block : event.getBlocks()) { for (Block block : event.getBlocks()) {
movedBlock = block.getRelative(direction); movedBlock = block.getRelative(direction);
if (movedBlock.getY() < Misc.getWorldMinCompat(movedBlock.getWorld())) // Very weird that the event is giving us these, they shouldn't exist
continue;
mcMMO.getPlaceStore().setTrue(movedBlock); mcMMO.getPlaceStore().setTrue(movedBlock);
} }
} }

View File

@ -19,6 +19,7 @@ import com.gmail.nossr50.skills.taming.Taming;
import com.gmail.nossr50.skills.taming.TamingManager; import com.gmail.nossr50.skills.taming.TamingManager;
import com.gmail.nossr50.skills.unarmed.UnarmedManager; import com.gmail.nossr50.skills.unarmed.UnarmedManager;
import com.gmail.nossr50.util.BlockUtils; import com.gmail.nossr50.util.BlockUtils;
import com.gmail.nossr50.util.ItemUtils;
import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Misc;
import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.Permissions;
import com.gmail.nossr50.util.compat.layers.persistentdata.AbstractPersistentDataLayer; import com.gmail.nossr50.util.compat.layers.persistentdata.AbstractPersistentDataLayer;
@ -155,8 +156,7 @@ public class EntityListener implements Listener {
Player player = (Player) event.getEntity().getShooter(); Player player = (Player) event.getEntity().getShooter();
/* WORLD GUARD MAIN FLAG CHECK */ /* WORLD GUARD MAIN FLAG CHECK */
if(WorldGuardUtils.isWorldGuardLoaded()) if(WorldGuardUtils.isWorldGuardLoaded()) {
{
if(!WorldGuardManager.getInstance().hasMainFlag(player)) if(!WorldGuardManager.getInstance().hasMainFlag(player))
return; return;
} }
@ -173,11 +173,10 @@ public class EntityListener implements Listener {
if(!projectile.hasMetadata(mcMMO.arrowDistanceKey)) if(!projectile.hasMetadata(mcMMO.arrowDistanceKey))
projectile.setMetadata(mcMMO.arrowDistanceKey, new FixedMetadataValue(pluginRef, projectile.getLocation())); projectile.setMetadata(mcMMO.arrowDistanceKey, new FixedMetadataValue(pluginRef, projectile.getLocation()));
for (Enchantment enchantment : player.getInventory().getItemInMainHand().getEnchantments().keySet()) { //Check both hands
if (enchantment.getKey().equals(piercingEnchantment)) { if(ItemUtils.doesPlayerHaveEnchantmentInHands(player, "piercing")) {
return; return;
} }
}
if (SkillUtils.isSkillRNGSuccessful(SubSkillType.ARCHERY_ARROW_RETRIEVAL, player)) { if (SkillUtils.isSkillRNGSuccessful(SubSkillType.ARCHERY_ARROW_RETRIEVAL, player)) {
projectile.setMetadata(mcMMO.trackedArrow, mcMMO.metadataValue); projectile.setMetadata(mcMMO.trackedArrow, mcMMO.metadataValue);

View File

@ -116,7 +116,8 @@ public class InventoryListener implements Listener {
//Profile doesn't exist //Profile doesn't exist
if(offlineProfile != null) { if(offlineProfile != null) {
event.setResult(offlineProfile.getSmeltingManager().smeltProcessing(smelting, event.getResult())); //Process smelting
offlineProfile.getSmeltingManager().smeltProcessing(event);
} }
} }
} }

View File

@ -52,21 +52,24 @@ import com.gmail.nossr50.util.skills.RankUtils;
import com.gmail.nossr50.util.skills.SmeltingTracker; import com.gmail.nossr50.util.skills.SmeltingTracker;
import com.gmail.nossr50.util.upgrade.UpgradeManager; import com.gmail.nossr50.util.upgrade.UpgradeManager;
import com.gmail.nossr50.worldguard.WorldGuardManager; import com.gmail.nossr50.worldguard.WorldGuardManager;
import com.google.common.base.Charsets;
import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.shatteredlands.shatt.backup.ZipLibrary; import net.shatteredlands.shatt.backup.ZipLibrary;
import org.bstats.bukkit.Metrics; import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -124,24 +127,24 @@ public class mcMMO extends JavaPlugin {
private static boolean isRetroModeEnabled; private static boolean isRetroModeEnabled;
/* Metadata Values */ /* Metadata Values */
public final static String REPLANT_META_KEY = "mcMMO: Recently Replanted"; public static final String REPLANT_META_KEY = "mcMMO: Recently Replanted";
public static final String FISH_HOOK_REF_METAKEY = "mcMMO: Fish Hook Tracker"; public static final String FISH_HOOK_REF_METAKEY = "mcMMO: Fish Hook Tracker";
public static final String DODGE_TRACKER = "mcMMO: Dodge Tracker"; public static final String DODGE_TRACKER = "mcMMO: Dodge Tracker";
public static final String CUSTOM_DAMAGE_METAKEY = "mcMMO: Custom Damage"; public static final String CUSTOM_DAMAGE_METAKEY = "mcMMO: Custom Damage";
public final static String travelingBlock = "mcMMO: Traveling Block"; public static final String travelingBlock = "mcMMO: Traveling Block";
public final static String blockMetadataKey = "mcMMO: Piston Tracking"; public static final String blockMetadataKey = "mcMMO: Piston Tracking";
public final static String tntMetadataKey = "mcMMO: Tracked TNT"; public static final String tntMetadataKey = "mcMMO: Tracked TNT";
public final static String customNameKey = "mcMMO: Custom Name"; public static final String customNameKey = "mcMMO: Custom Name";
public final static String customVisibleKey = "mcMMO: Name Visibility"; public static final String customVisibleKey = "mcMMO: Name Visibility";
public final static String droppedItemKey = "mcMMO: Tracked Item"; public static final String droppedItemKey = "mcMMO: Tracked Item";
public final static String infiniteArrowKey = "mcMMO: Infinite Arrow"; public static final String infiniteArrowKey = "mcMMO: Infinite Arrow";
public final static String trackedArrow = "mcMMO: Tracked Arrow"; public static final String trackedArrow = "mcMMO: Tracked Arrow";
public final static String bowForceKey = "mcMMO: Bow Force"; public static final String bowForceKey = "mcMMO: Bow Force";
public final static String arrowDistanceKey = "mcMMO: Arrow Distance"; public static final String arrowDistanceKey = "mcMMO: Arrow Distance";
public final static String BONUS_DROPS_METAKEY = "mcMMO: Double Drops"; public static final String BONUS_DROPS_METAKEY = "mcMMO: Double Drops";
public final static String disarmedItemKey = "mcMMO: Disarmed Item"; public static final String disarmedItemKey = "mcMMO: Disarmed Item";
public final static String playerDataKey = "mcMMO: Player Data"; public static final String playerDataKey = "mcMMO: Player Data";
public final static String databaseCommandKey = "mcMMO: Processing Database Command"; public static final String databaseCommandKey = "mcMMO: Processing Database Command";
public static FixedMetadataValue metadataValue; public static FixedMetadataValue metadataValue;
@ -158,7 +161,9 @@ public class mcMMO extends JavaPlugin {
//Platform Manager //Platform Manager
platformManager = new PlatformManager(); platformManager = new PlatformManager();
//Filter out any debug messages (if debug/verbose logging is not enabled)
getLogger().setFilter(new LogFilter(this)); getLogger().setFilter(new LogFilter(this));
metadataValue = new FixedMetadataValue(this, true); metadataValue = new FixedMetadataValue(this, true);
PluginManager pluginManager = getServer().getPluginManager(); PluginManager pluginManager = getServer().getPluginManager();
@ -254,12 +259,12 @@ public class mcMMO extends JavaPlugin {
if(Config.getInstance().getIsMetricsEnabled()) { if(Config.getInstance().getIsMetricsEnabled()) {
metrics = new Metrics(this, 3894); metrics = new Metrics(this, 3894);
metrics.addCustomChart(new Metrics.SimplePie("version", () -> getDescription().getVersion())); metrics.addCustomChart(new SimplePie("version", () -> getDescription().getVersion()));
if(Config.getInstance().getIsRetroMode()) if(Config.getInstance().getIsRetroMode())
metrics.addCustomChart(new Metrics.SimplePie("leveling_system", () -> "Retro")); metrics.addCustomChart(new SimplePie("leveling_system", () -> "Retro"));
else else
metrics.addCustomChart(new Metrics.SimplePie("leveling_system", () -> "Standard")); metrics.addCustomChart(new SimplePie("leveling_system", () -> "Standard"));
} }
} }
catch (Throwable t) { catch (Throwable t) {
@ -273,6 +278,9 @@ public class mcMMO extends JavaPlugin {
} }
getServer().getPluginManager().disablePlugin(this); getServer().getPluginManager().disablePlugin(this);
//Fixes #4438 - Don't initialize things if we are going to disable mcMMO anyway
return;
} }
//Init player level values //Init player level values
@ -284,6 +292,7 @@ public class mcMMO extends JavaPlugin {
//Init smelting tracker //Init smelting tracker
smeltingTracker = new SmeltingTracker(); smeltingTracker = new SmeltingTracker();
//Set up Adventure's audiences
audiences = BukkitAudiences.create(this); audiences = BukkitAudiences.create(this);
transientMetadataTools = new TransientMetadataTools(this); transientMetadataTools = new TransientMetadataTools(this);
@ -347,8 +356,9 @@ public class mcMMO extends JavaPlugin {
holidayManager.saveAnniversaryFiles(); holidayManager.saveAnniversaryFiles();
placeStore.closeAll(); placeStore.closeAll();
} }
catch (Exception e) {
catch (Exception e) { e.printStackTrace(); } e.printStackTrace();
}
if (Config.getInstance().getBackupsEnabled()) { if (Config.getInstance().getBackupsEnabled()) {
// Remove other tasks BEFORE starting the Backup, or we just cancel it straight away. // Remove other tasks BEFORE starting the Backup, or we just cancel it straight away.
@ -358,16 +368,14 @@ public class mcMMO extends JavaPlugin {
catch (IOException e) { catch (IOException e) {
getLogger().severe(e.toString()); getLogger().severe(e.toString());
} }
catch (Throwable e) { catch(NoClassDefFoundError e) {
if (e instanceof NoClassDefFoundError) {
getLogger().severe("Backup class not found!"); getLogger().severe("Backup class not found!");
getLogger().info("Please do not replace the mcMMO jar while the server is running."); getLogger().info("Please do not replace the mcMMO jar while the server is running.");
} }
else { catch (Throwable e) {
getLogger().severe(e.toString()); getLogger().severe(e.toString());
} }
} }
}
debug("Canceling all tasks..."); debug("Canceling all tasks...");
getServer().getScheduler().cancelTasks(this); // This removes our tasks getServer().getScheduler().cancelTasks(this); // This removes our tasks
@ -682,9 +690,9 @@ public class mcMMO extends JavaPlugin {
} }
} }
public InputStreamReader getResourceAsReader(String fileName) { public @Nullable InputStreamReader getResourceAsReader(@NotNull String fileName) {
InputStream in = getResource(fileName); InputStream in = getResource(fileName);
return in == null ? null : new InputStreamReader(in, Charsets.UTF_8); return in == null ? null : new InputStreamReader(in, StandardCharsets.UTF_8);
} }
/** /**

View File

@ -179,10 +179,12 @@ public class HerbalismManager extends SkillManager {
//TODO: The design of Green Terra needs to change, this is a mess //TODO: The design of Green Terra needs to change, this is a mess
if(Permissions.greenThumbPlant(getPlayer(), originalBreak.getType())) { if(Permissions.greenThumbPlant(getPlayer(), originalBreak.getType())) {
if(Config.getInstance().isGreenThumbReplantableCrop(originalBreak.getType())) {
if(!getPlayer().isSneaking()) { if(!getPlayer().isSneaking()) {
greenThumbActivated = processGreenThumbPlants(originalBreak, blockBreakEvent, isGreenTerraActive()); greenThumbActivated = processGreenThumbPlants(originalBreak, blockBreakEvent, isGreenTerraActive());
} }
} }
}
//When replanting a immature crop we cancel the block break event and back out //When replanting a immature crop we cancel the block break event and back out
if(greenThumbActivated) { if(greenThumbActivated) {

View File

@ -11,7 +11,9 @@ import com.gmail.nossr50.util.Permissions;
import com.gmail.nossr50.util.skills.RankUtils; import com.gmail.nossr50.util.skills.RankUtils;
import com.gmail.nossr50.util.skills.SkillUtils; import com.gmail.nossr50.util.skills.SkillUtils;
import org.bukkit.event.inventory.FurnaceBurnEvent; import org.bukkit.event.inventory.FurnaceBurnEvent;
import org.bukkit.event.inventory.FurnaceSmeltEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
public class SmeltingManager extends SkillManager { public class SmeltingManager extends SkillManager {
public SmeltingManager(McMMOPlayer mcMMOPlayer) { public SmeltingManager(McMMOPlayer mcMMOPlayer) {
@ -107,19 +109,29 @@ public class SmeltingManager extends SkillManager {
} }
} }
public ItemStack smeltProcessing(ItemStack smelting, ItemStack result) { public void smeltProcessing(@NotNull FurnaceSmeltEvent furnaceSmeltEvent) {
ItemStack sourceItemStack = furnaceSmeltEvent.getSource();
ItemStack resultItemStack = furnaceSmeltEvent.getResult();
applyXpGain(Smelting.getResourceXp(smelting), XPGainReason.PVE, XPGainSource.PASSIVE); applyXpGain(Smelting.getResourceXp(sourceItemStack), XPGainReason.PVE, XPGainSource.PASSIVE); //Add XP
int itemLimit = resultItemStack.getMaxStackSize();
if (Config.getInstance().getDoubleDropsEnabled(PrimarySkillType.SMELTING, result.getType()) processDoubleSmelt(furnaceSmeltEvent, resultItemStack, itemLimit);
&& isSecondSmeltSuccessful() && result.getAmount() < 64) {
ItemStack newResult = result.clone();
newResult.setAmount(result.getAmount() + 1);
return newResult;
} }
return result; private void processDoubleSmelt(@NotNull FurnaceSmeltEvent furnaceSmeltEvent, @NotNull ItemStack resultItemStack, int itemLimit) {
//TODO: Permission check work around, could store it as NBT on the furnace
//We don't do permission checks because this can be for an offline player and Bukkit has nothing to grab permissions for offline players
//Process double smelt
if (Config.getInstance().getDoubleDropsEnabled(PrimarySkillType.SMELTING, resultItemStack.getType())
&& resultItemStack.getAmount() < itemLimit
&& isSecondSmeltSuccessful()) {
ItemStack newResult = resultItemStack.clone();
newResult.setAmount(Math.min(resultItemStack.getAmount() + 1, itemLimit)); //Don't go over max stack limits
furnaceSmeltEvent.setResult(newResult);
}
} }
public int vanillaXPBoost(int experience) { public int vanillaXPBoost(int experience) {

View File

@ -10,6 +10,7 @@ import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.mcMMO;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.enchantments.Enchantment; import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.FurnaceRecipe; import org.bukkit.inventory.FurnaceRecipe;
@ -18,6 +19,7 @@ import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.meta.EnchantmentStorageMeta; import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -35,14 +37,98 @@ public final class ItemUtils {
* @param item Item to check * @param item Item to check
* @return true if the item is a bow, false otherwise * @return true if the item is a bow, false otherwise
*/ */
public static boolean isBow(ItemStack item) { public static boolean isBow(@NotNull ItemStack item) {
return mcMMO.getMaterialMapStore().isBow(item.getType().getKey().getKey()); return mcMMO.getMaterialMapStore().isBow(item.getType().getKey().getKey());
} }
public static boolean isCrossbow(@NotNull ItemStack item) {
return mcMMO.getMaterialMapStore().isCrossbow(item.getType().getKey().getKey());
}
public static boolean hasItemInEitherHand(@NotNull Player player, Material material) { public static boolean hasItemInEitherHand(@NotNull Player player, Material material) {
return player.getInventory().getItemInMainHand().getType() == material || player.getInventory().getItemInOffHand().getType() == material; return player.getInventory().getItemInMainHand().getType() == material || player.getInventory().getItemInOffHand().getType() == material;
} }
public static boolean doesPlayerHaveEnchantmentOnArmor(@NotNull Player player, @NotNull String enchantmentByName) {
Enchantment enchantment = getEnchantment(enchantmentByName);
if(enchantment == null)
return false;
return doesPlayerHaveEnchantmentOnArmor(player, enchantment);
}
public static boolean doesPlayerHaveEnchantmentOnArmor(@NotNull Player player, @NotNull Enchantment enchantment) {
for(ItemStack itemStack : player.getInventory().getArmorContents()) {
if(itemStack != null) {
if(hasEnchantment(itemStack, enchantment))
return true;
}
}
return false;
}
public static boolean doesPlayerHaveEnchantmentOnArmorOrHands(@NotNull Player player, @NotNull String enchantmentName) {
Enchantment enchantment = getEnchantment(enchantmentName);
if(enchantment == null)
return false;
return doesPlayerHaveEnchantmentOnArmorOrHands(player, enchantment);
}
public static boolean doesPlayerHaveEnchantmentOnArmorOrHands(@NotNull Player player, @NotNull Enchantment enchantment) {
if(doesPlayerHaveEnchantmentOnArmor(player, enchantment))
return true;
if(doesPlayerHaveEnchantmentInHands(player, enchantment))
return true;
return false;
}
public static boolean doesPlayerHaveEnchantmentInHands(@NotNull Player player, @NotNull NamespacedKey enchantmentNameKey) {
Enchantment enchantment = Enchantment.getByKey(enchantmentNameKey);
if(enchantment == null)
return false;
return doesPlayerHaveEnchantmentInHands(player, enchantment);
}
public static boolean doesPlayerHaveEnchantmentInHands(@NotNull Player player, @NotNull String enchantmentName) {
Enchantment enchantment = getEnchantment(enchantmentName);
if(enchantment == null)
return false;
return doesPlayerHaveEnchantmentInHands(player, enchantment);
}
public static boolean doesPlayerHaveEnchantmentInHands(@NotNull Player player, @NotNull Enchantment enchantment) {
return hasEnchantment(player.getInventory().getItemInMainHand(), enchantment) ||
hasEnchantment(player.getInventory().getItemInOffHand(), enchantment);
}
public static boolean hasEnchantment(@NotNull ItemStack itemStack, @NotNull Enchantment enchantment) {
if(itemStack.getItemMeta() != null) {
return itemStack.getItemMeta().hasEnchant(enchantment);
}
return false;
}
public static @Nullable Enchantment getEnchantment(@NotNull String enchantmentName) {
for(Enchantment enchantment : Enchantment.values()) {
if(enchantment.getKey().getKey().equalsIgnoreCase(enchantmentName)) {
return enchantment;
}
}
return null;
}
/** /**
* Checks if the item is a sword. * Checks if the item is a sword.
* *

View File

@ -49,6 +49,7 @@ public class MaterialMapStore {
private final @NotNull HashSet<String> pickAxes; private final @NotNull HashSet<String> pickAxes;
private final @NotNull HashSet<String> tridents; private final @NotNull HashSet<String> tridents;
private final @NotNull HashSet<String> bows; private final @NotNull HashSet<String> bows;
private final @NotNull HashSet<String> crossbows;
private final @NotNull HashSet<String> tools; private final @NotNull HashSet<String> tools;
private final @NotNull HashSet<String> enchantables; private final @NotNull HashSet<String> enchantables;
@ -88,6 +89,7 @@ public class MaterialMapStore {
diamondTools = new HashSet<>(); diamondTools = new HashSet<>();
netheriteTools = new HashSet<>(); netheriteTools = new HashSet<>();
bows = new HashSet<>(); bows = new HashSet<>();
crossbows = new HashSet<>();
stringTools = new HashSet<>(); stringTools = new HashSet<>();
tools = new HashSet<>(); tools = new HashSet<>();
@ -447,6 +449,7 @@ public class MaterialMapStore {
fillTridents(); fillTridents();
fillStringTools(); fillStringTools();
fillBows(); fillBows();
fillCrossbows();
//Tools collection //Tools collection
tools.addAll(woodTools); tools.addAll(woodTools);
@ -464,6 +467,10 @@ public class MaterialMapStore {
bows.add("bow"); bows.add("bow");
} }
private void fillCrossbows() {
crossbows.add("crossbow");
}
private void fillStringTools() { private void fillStringTools() {
stringTools.add("bow"); stringTools.add("bow");
stringTools.add("fishing_rod"); stringTools.add("fishing_rod");
@ -771,6 +778,14 @@ public class MaterialMapStore {
return bows.contains(id); return bows.contains(id);
} }
public boolean isCrossbow(@NotNull Material material) {
return isCrossbow(material.getKey().getKey());
}
public boolean isCrossbow(@NotNull String id) {
return crossbows.contains(id);
}
public boolean isLeatherArmor(@NotNull Material material) { public boolean isLeatherArmor(@NotNull Material material) {
return isLeatherArmor(material.getKey().getKey()); return isLeatherArmor(material.getKey().getKey());
} }

View File

@ -9,6 +9,7 @@ import com.gmail.nossr50.util.player.UserManager;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
import org.bukkit.entity.*; import org.bukkit.entity.*;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
@ -259,6 +260,12 @@ public final class Misc {
} }
} }
public static int getWorldMinCompat(World world)
{
// TODO this method should access the world min variable in a version safe manner so that we don't restrict usage to new versions of spigot only
return 0;
}
public static void printProgress(int convertedUsers, int progressInterval, long startMillis) { public static void printProgress(int convertedUsers, int progressInterval, long startMillis) {
if ((convertedUsers % progressInterval) == 0) { if ((convertedUsers % progressInterval) == 0) {
mcMMO.p.getLogger().info(String.format("Conversion progress: %d users at %.2f users/second", convertedUsers, convertedUsers / (double) ((System.currentTimeMillis() - startMillis) / TIME_CONVERSION_FACTOR))); mcMMO.p.getLogger().info(String.format("Conversion progress: %d users at %.2f users/second", convertedUsers, convertedUsers / (double) ((System.currentTimeMillis() - startMillis) / TIME_CONVERSION_FACTOR)));

View File

@ -1,5 +1,6 @@
package com.gmail.nossr50.util.blockmeta; package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.util.Misc;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -10,12 +11,13 @@ import java.util.BitSet;
import java.util.UUID; import java.util.UUID;
public class BitSetChunkStore implements ChunkStore { public class BitSetChunkStore implements ChunkStore {
private static final int CURRENT_VERSION = 8; private static final int CURRENT_VERSION = 9;
private static final int MAGIC_NUMBER = 0xEA5EDEBB; private static final int MAGIC_NUMBER = 0xEA5EDEBB;
private final int cx; private final int cx;
private final int cz; private final int cz;
private final int worldHeight; private final int worldMin;
private final int worldMax;
private final @NotNull UUID worldUid; private final @NotNull UUID worldUid;
// 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. // 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 final @NotNull BitSet store; private final @NotNull BitSet store;
@ -23,15 +25,16 @@ public class BitSetChunkStore implements ChunkStore {
private transient boolean dirty = false; private transient boolean dirty = false;
public BitSetChunkStore(@NotNull World world, int cx, int cz) { public BitSetChunkStore(@NotNull World world, int cx, int cz) {
this(world.getUID(), world.getMaxHeight(), cx, cz); this(world.getUID(), Misc.getWorldMinCompat(world), world.getMaxHeight(), cx, cz);
} }
private BitSetChunkStore(@NotNull UUID worldUid, int worldHeight, int cx, int cz) { private BitSetChunkStore(@NotNull UUID worldUid, int worldMin, int worldMax, int cx, int cz) {
this.cx = cx; this.cx = cx;
this.cz = cz; this.cz = cz;
this.worldUid = worldUid; this.worldUid = worldUid;
this.worldHeight = worldHeight; this.worldMin = worldMin;
this.store = new BitSet(16 * 16 * worldHeight); this.worldMax = worldMax;
this.store = new BitSet(16 * 16 * (worldMax - worldMin));
} }
@Override @Override
@ -54,6 +57,16 @@ public class BitSetChunkStore implements ChunkStore {
return cz; return cz;
} }
@Override
public int getChunkMin() {
return worldMin;
}
@Override
public int getChunkMax() {
return worldMax;
}
@Override @Override
public @NotNull UUID getWorldId() { public @NotNull UUID getWorldId() {
return worldUid; return worldUid;
@ -86,22 +99,34 @@ public class BitSetChunkStore implements ChunkStore {
} }
private int coordToIndex(int x, int y, int z) { private int coordToIndex(int x, int y, int z) {
return coordToIndex(x, y, z, worldHeight); return coordToIndex(x, y, z, worldMin, worldMax);
} }
private static int coordToIndex(int x, int y, int z, int worldHeight) { private static int coordToIndex(int x, int y, int z, int worldMin, int worldMax) {
if (x < 0 || x >= 16 || y < 0 || y >= worldHeight || z < 0 || z >= 16) if (x < 0 || x >= 16 || y < worldMin || y >= worldMax || z < 0 || z >= 16)
throw new IndexOutOfBoundsException(String.format("x: %d y: %d z: %d World Height: %d", x, y, z, worldHeight)); throw new IndexOutOfBoundsException(String.format("x: %d y: %d z: %d World Min: %d World Max: %d", x, y, z, worldMin, worldMax));
return (z * 16 + x) + (256 * y); int yOffset = -worldMin; // Ensures y multiplier remains positive
return (z * 16 + x) + (256 * (y + yOffset));
} }
private static int getWorldHeight(@NotNull UUID worldUid, int storedWorldHeight) private static int getWorldMin(@NotNull UUID worldUid, int storedWorldMin)
{ {
World world = Bukkit.getWorld(worldUid); World world = Bukkit.getWorld(worldUid);
// Not sure how this case could come up, but might as well handle it gracefully. Loading a chunkstore for an unloaded world? // Not sure how this case could come up, but might as well handle it gracefully. Loading a chunkstore for an unloaded world?
if (world == null) if (world == null)
return storedWorldHeight; return storedWorldMin;
return Misc.getWorldMinCompat(world);
}
private static int getWorldMax(@NotNull UUID worldUid, int storedWorldMax)
{
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 storedWorldMax;
return world.getMaxHeight(); return world.getMaxHeight();
} }
@ -114,7 +139,8 @@ public class BitSetChunkStore implements ChunkStore {
out.writeLong(worldUid.getMostSignificantBits()); out.writeLong(worldUid.getMostSignificantBits());
out.writeInt(cx); out.writeInt(cx);
out.writeInt(cz); out.writeInt(cz);
out.writeInt(worldHeight); out.writeInt(worldMin);
out.writeInt(worldMax);
// Store the byte array directly so we don't have the object type info overhead // Store the byte array directly so we don't have the object type info overhead
byte[] storeData = store.toByteArray(); byte[] storeData = store.toByteArray();
@ -129,7 +155,7 @@ public class BitSetChunkStore implements ChunkStore {
// Can be used to determine the format of the file // Can be used to determine the format of the file
int fileVersionNumber = in.readInt(); int fileVersionNumber = in.readInt();
if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION) if (magic != MAGIC_NUMBER || fileVersionNumber < 8)
throw new IOException(); throw new IOException();
long lsb = in.readLong(); long lsb = in.readLong();
@ -138,21 +164,38 @@ public class BitSetChunkStore implements ChunkStore {
int cx = in.readInt(); int cx = in.readInt();
int cz = in.readInt(); int cz = in.readInt();
int worldHeight = in.readInt(); int worldMin = 0;
if (fileVersionNumber >= 9)
worldMin = in.readInt();
int worldMax = in.readInt();
byte[] temp = new byte[in.readInt()]; byte[] temp = new byte[in.readInt()];
in.readFully(temp); in.readFully(temp);
BitSet stored = BitSet.valueOf(temp); BitSet stored = BitSet.valueOf(temp);
int currentWorldHeight = getWorldHeight(worldUid, worldHeight); int currentWorldMin = getWorldMin(worldUid, worldMin);
int currentWorldMax = getWorldMax(worldUid, worldMax);
boolean worldHeightShrunk = currentWorldHeight < worldHeight; // The order in which the world height update code occurs here is important, the world max truncate math only holds up if done before adjusting for min changes
// Lop off extra data if world height has shrunk // Lop off extra data if world max has shrunk
if (worldHeightShrunk) if (currentWorldMax < worldMax)
stored.clear(coordToIndex(16, currentWorldHeight, 16, worldHeight), stored.length()); stored.clear(coordToIndex(16, currentWorldMax, 16, worldMin, worldMax), stored.length());
// Left shift store if world min has shrunk
if (currentWorldMin > worldMin)
stored = stored.get(currentWorldMin, stored.length()); // Because BitSet's aren't fixed size, a "substring" operation is equivalent to a left shift
// Right shift store if world min has expanded
if (currentWorldMin < worldMin)
{
int offset = (worldMin - currentWorldMin) * 16 * 16; // We are adding this many bits to the front
// This isn't the most efficient way to do this, however, its a rare case to occur, and in the grand scheme of things, the small performance we could gain would cost us significant reduced readability of the code
BitSet shifted = new BitSet();
for (int i = 0; i < stored.length(); i++)
shifted.set(i + offset, stored.get(i));
stored = shifted;
}
BitSetChunkStore chunkStore = new BitSetChunkStore(worldUid, currentWorldHeight, cx, cz); BitSetChunkStore chunkStore = new BitSetChunkStore(worldUid, currentWorldMin, currentWorldMax, cx, cz);
chunkStore.store.or(stored); chunkStore.store.or(stored);
chunkStore.dirty = worldHeightShrunk; // In the expanded case there is no reason to re-write it unless the data changes chunkStore.dirty = currentWorldMin != worldMin || currentWorldMax != worldMax;
return chunkStore; return chunkStore;
} }
@ -203,7 +246,7 @@ public class BitSetChunkStore implements ChunkStore {
private int cx; private int cx;
private int cz; private int cz;
private int worldHeight; private int worldMax;
private UUID worldUid; private UUID worldUid;
private boolean[][][] store; private boolean[][][] store;
@ -226,19 +269,20 @@ public class BitSetChunkStore implements ChunkStore {
cz = in.readInt(); cz = in.readInt();
store = (boolean[][][]) in.readObject(); store = (boolean[][][]) in.readObject();
worldHeight = store[0][0].length; worldMax = store[0][0].length;
} }
public @NotNull BitSetChunkStore convert() public @NotNull BitSetChunkStore convert()
{ {
int currentWorldHeight = getWorldHeight(worldUid, worldHeight); int currentWorldMin = getWorldMin(worldUid, 0);
int currentWorldMax = getWorldMax(worldUid, worldMax);
BitSetChunkStore converted = new BitSetChunkStore(worldUid, currentWorldHeight, cx, cz); BitSetChunkStore converted = new BitSetChunkStore(worldUid, currentWorldMin, currentWorldMax, cx, cz);
// Read old data into new chunkstore // Read old data into new chunkstore
for (int x = 0; x < 16; x++) { for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) { for (int z = 0; z < 16; z++) {
for (int y = 0; y < worldHeight && y < currentWorldHeight; y++) { for (int y = 0; y < worldMax && y < currentWorldMax; y++) {
converted.store.set(converted.coordToIndex(x, y, z), store[x][z][y]); converted.store.set(converted.coordToIndex(x, y, z), store[x][z][y]);
} }
} }

View File

@ -36,6 +36,9 @@ public interface ChunkStore {
*/ */
int getChunkZ(); int getChunkZ();
int getChunkMin();
int getChunkMax();
@NotNull UUID getWorldId(); @NotNull UUID getWorldId();
/** /**

View File

@ -454,6 +454,14 @@ Skills:
Tree_Feller_Sounds: true Tree_Feller_Sounds: true
Level_Cap: 0 Level_Cap: 0
# Disable or Enable the Green Thumb auto replant feature for specific crops, use the name of the block not the crop itemstack
Green_Thumb_Replanting_Crops:
Carrots: true
Wheat: true
Nether_Wart: true
Potatoes: true
Beetroots: true
Cocoa: true
# #
# Settings for Double Drops # Settings for Double Drops
### ###

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
package com.gmail.nossr50.util.blockmeta; package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.TestUtil; import com.gmail.nossr50.TestUtil;
import com.gmail.nossr50.util.Misc;
import com.google.common.io.Files; import com.google.common.io.Files;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
@ -22,7 +23,7 @@ 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. * 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) @RunWith(PowerMockRunner.class)
@PrepareForTest(Bukkit.class) @PrepareForTest({ Bukkit.class, Misc.class })
public class ChunkStoreTest { public class ChunkStoreTest {
private static File tempDir; private static File tempDir;
@BeforeClass @BeforeClass
@ -76,6 +77,34 @@ public class ChunkStoreTest {
assertEqual(original, deserialized); assertEqual(original, deserialized);
} }
@Test
public void testNegativeWorldMin() throws IOException {
PowerMockito.mockStatic(Misc.class);
Mockito.when(Misc.getWorldMinCompat(mockWorld)).thenReturn(-64);
BitSetChunkStore original = new BitSetChunkStore(mockWorld, 1, 2);
original.setTrue(14, -32, 12);
original.setTrue(14, -64, 12);
original.setTrue(13, -63, 12);
byte[] serializedBytes = serializeChunkstore(original);
ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes)));
assertEqual(original, deserialized);
}
@Test
public void testNegativeWorldMinUpgrade() throws IOException {
BitSetChunkStore original = new BitSetChunkStore(mockWorld, 1, 2);
original.setTrue(14, 1, 12);
original.setTrue(14, 2, 12);
original.setTrue(13, 3, 12);
byte[] serializedBytes = serializeChunkstore(original);
PowerMockito.mockStatic(Misc.class);
Mockito.when(Misc.getWorldMinCompat(mockWorld)).thenReturn(-64);
ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes)));
assertEqualIgnoreMinMax(original, deserialized);
}
@Test @Test
public void testChunkCoords() throws IOException { public void testChunkCoords() throws IOException {
for (int x = -96; x < 0; x++) { for (int x = -96; x < 0; x++) {
@ -175,15 +204,26 @@ public class ChunkStoreTest {
} }
private void assertEqual(ChunkStore expected, ChunkStore actual) private void assertEqual(ChunkStore expected, ChunkStore actual)
{
Assert.assertEquals(expected.getChunkMin(), actual.getChunkMin());
Assert.assertEquals(expected.getChunkMax(), actual.getChunkMax());
assertEqualIgnoreMinMax(expected, actual);
}
private void assertEqualIgnoreMinMax(ChunkStore expected, ChunkStore actual)
{ {
Assert.assertEquals(expected.getChunkX(), actual.getChunkX()); Assert.assertEquals(expected.getChunkX(), actual.getChunkX());
Assert.assertEquals(expected.getChunkZ(), actual.getChunkZ()); Assert.assertEquals(expected.getChunkZ(), actual.getChunkZ());
Assert.assertEquals(expected.getWorldId(), actual.getWorldId()); Assert.assertEquals(expected.getWorldId(), actual.getWorldId());
for (int y = 0; y < 256; y++) for (int y = Math.min(actual.getChunkMin(), expected.getChunkMin()); y < Math.max(actual.getChunkMax(), expected.getChunkMax()); y++)
{
if (expected.getChunkMin() > y || actual.getChunkMin() > y || expected.getChunkMax() <= y || actual.getChunkMax() <= y)
continue; // Ignore
for (int x = 0; x < 16; x++) for (int x = 0; x < 16; x++)
for (int z = 0; z < 16; z++) for (int z = 0; z < 16; z++)
Assert.assertTrue(expected.isTrue(x, y, z) == actual.isTrue(x, y, z)); Assert.assertTrue(expected.isTrue(x, y, z) == actual.isTrue(x, y, z));
} }
}
private static byte[] serializeChunkstore(@NotNull ChunkStore chunkStore) throws IOException { private static byte[] serializeChunkstore(@NotNull ChunkStore chunkStore) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
@ -231,6 +271,16 @@ public class ChunkStoreTest {
return cz; return cz;
} }
@Override
public int getChunkMin() {
return 0;
}
@Override
public int getChunkMax() {
return store[0][0].length;
}
@Override @Override
public @NotNull UUID getWorldId() { public @NotNull UUID getWorldId() {
return worldUid; return worldUid;