CompatibilityLayer framework

This commit is contained in:
nossr50 2020-04-24 20:21:21 -07:00
parent f82ad99c82
commit 5984230bf3
37 changed files with 1292 additions and 123 deletions

View File

@ -1,3 +1,12 @@
Version 2.1.126
mcMMO now relies on NMS for some of its features, if NMS cannot properly be wired up when initializing mcMMO behaviours relying on NMS will either be partially supported or disabled
mcMMO now has a compatibility mode, any features that require specific versions of Minecraft for full functionality will be disabled if your server is not running a compatible version, mcMMO will still function in compatibility mode, but either the feature will be modified or disabled depending on the version of the server software
New command /mmocompat - Shows information about whether or not mcMMO is fully functional or if some features are disabled due to the server software not being fully supported. Can be used by players or console.
Fixed an exploit involving fishing rods
Notes:
There are no features that rely on NMS in this version, it took a lot of work to write the NMS framework and I'm going to delay implementation for future versions.
Version 2.1.125 Version 2.1.125
*Fixed a bug where you could not place blocks on top of certain repair/salvage anvils *Fixed a bug where you could not place blocks on top of certain repair/salvage anvils
@ -5,7 +14,7 @@ Version 2.1.125
Version 2.1.124 Version 2.1.124
Repair/Salvage can now be set to use vanilla blocks that already do something and that vanilla functionality will be disabled by mcMMO (you could use vanilla-anvils instead of iron_blocks for repair now) Repair/Salvage can now be set to use vanilla blocks that already do something and that vanilla functionality will be disabled by mcMMO (you could use vanilla-anvils instead of iron_blocks for repair now)
Added Gold_Nugget to Mining's Bonus_Drops in config.yml (edit your config) Added Gold_Nugget to Mining's Bonus_Drops in config.yml (edit your config)g
Added Piglin to experience.yml combat XP tables with a value of 2.0 (edit your config) Added Piglin to experience.yml combat XP tables with a value of 2.0 (edit your config)
Added Hoglin to experience.yml combat XP tables with a value of 4.0 (edit your config) Added Hoglin to experience.yml combat XP tables with a value of 4.0 (edit your config)
Added Zombified_Piglin & Zombie_Pigman to experience.yml combat XP tables with a value of 3.0 (edit your config) Added Zombified_Piglin & Zombie_Pigman to experience.yml combat XP tables with a value of 3.0 (edit your config)

View File

@ -167,7 +167,7 @@
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.15.1-R0.1-SNAPSHOT</version> <version>1.15.2-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -198,6 +198,11 @@
<version>7.0.52</version> <version>7.0.52</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>19.0.0</version>
</dependency>
</dependencies> </dependencies>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

View File

@ -0,0 +1,28 @@
package com.gmail.nossr50.commands.admin;
import com.gmail.nossr50.mcMMO;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class CompatibilityCommand implements CommandExecutor {
/**
* Executes the given command, returning its success.
* <br>
* If false is returned, then the "usage" plugin.yml entry for this command
* (if defined) will be sent to the player.
*
* @param commandSender Source of the command
* @param command Command which was executed
* @param s Alias of the command which was used
* @param strings Passed command arguments
* @return true if a valid command, otherwise false
*/
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) {
mcMMO.getCompatibilityManager().reportCompatibilityStatus(commandSender);
return true;
}
}

View File

@ -96,6 +96,7 @@ public class McMMOPlayer {
private int respawnATS; private int respawnATS;
private int teleportATS; private int teleportATS;
private long databaseATS; private long databaseATS;
private double attackStrength; //captured during arm swing events
//private int chimeraWingLastUse; //private int chimeraWingLastUse;
private Location teleportCommence; private Location teleportCommence;
@ -142,12 +143,21 @@ public class McMMOPlayer {
experienceBarManager = new ExperienceBarManager(this); experienceBarManager = new ExperienceBarManager(this);
debugMode = false; //Debug mode helps solve support issues, players can toggle it on or off debugMode = false; //Debug mode helps solve support issues, players can toggle it on or off
attackStrength = 1.0D;
} }
public String getPlayerName() { public String getPlayerName() {
return playerName; return playerName;
} }
public double getAttackStrength() {
return attackStrength;
}
public void setAttackStrength(double attackStrength) {
this.attackStrength = attackStrength;
}
/*public void hideXpBar(PrimarySkillType primarySkillType) /*public void hideXpBar(PrimarySkillType primarySkillType)
{ {
experienceBarManager.hideExperienceBar(primarySkillType); experienceBarManager.hideExperienceBar(primarySkillType);

View File

@ -10,6 +10,7 @@ import org.bukkit.event.Cancellable;
public class SubSkillEvent extends McMMOPlayerSkillEvent implements Cancellable { public class SubSkillEvent extends McMMOPlayerSkillEvent implements Cancellable {
private SubSkillType subSkillType; private SubSkillType subSkillType;
private boolean cancelled = false; private boolean cancelled = false;
private double resultModifier = 1.0D;
/** /**
* Only skills using the old system will fire this event * Only skills using the old system will fire this event
@ -23,11 +24,33 @@ public class SubSkillEvent extends McMMOPlayerSkillEvent implements Cancellable
this.subSkillType = subSkillType; this.subSkillType = subSkillType;
} }
/**
* Only skills using the old system will fire this event
* @param player target player
* @param subSkillType target subskill
* @param resultModifier a value multiplied against the final result of the dice roll, typically between 0-1.0
* @Deprecated Skills will be using a new system stemming from the AbstractSubSkill class so make sure you check for both events, this event will be removed eventually.
*/
@Deprecated
public SubSkillEvent(Player player, SubSkillType subSkillType, double resultModifier) {
super(player, PrimarySkillType.bySecondaryAbility(subSkillType));
this.subSkillType = subSkillType;
this.resultModifier = resultModifier;
}
public SubSkillEvent(Player player, AbstractSubSkill abstractSubSkill) public SubSkillEvent(Player player, AbstractSubSkill abstractSubSkill)
{ {
super(player, abstractSubSkill.getPrimarySkill()); super(player, abstractSubSkill.getPrimarySkill());
} }
public double getResultModifier() {
return resultModifier;
}
public void setResultModifier(double resultModifier) {
this.resultModifier = resultModifier;
}
/** /**
* Gets the SubSkillType involved in the event * Gets the SubSkillType involved in the event
* @return the SubSkillType * @return the SubSkillType

View File

@ -28,6 +28,7 @@ import com.gmail.nossr50.util.skills.CombatUtils;
import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.skills.SkillActivationType;
import com.gmail.nossr50.worldguard.WorldGuardManager; import com.gmail.nossr50.worldguard.WorldGuardManager;
import com.gmail.nossr50.worldguard.WorldGuardUtils; import com.gmail.nossr50.worldguard.WorldGuardUtils;
import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.block.Block; import org.bukkit.block.Block;
@ -289,6 +290,7 @@ public class EntityListener implements Listener {
if(WorldGuardUtils.isWorldGuardLoaded()) if(WorldGuardUtils.isWorldGuardLoaded())
{ {
if(attacker instanceof Player) { if(attacker instanceof Player) {
Bukkit.broadcastMessage("(Player attacked something) EntityDamageEvent fired!");
if(!WorldGuardManager.getInstance().hasMainFlag((Player) attacker)) if(!WorldGuardManager.getInstance().hasMainFlag((Player) attacker))
return; return;

View File

@ -38,7 +38,10 @@ import com.gmail.nossr50.util.*;
import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManager; import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManager;
import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManagerFactory; import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManagerFactory;
import com.gmail.nossr50.util.commands.CommandRegistrationManager; import com.gmail.nossr50.util.commands.CommandRegistrationManager;
import com.gmail.nossr50.util.compat.CompatibilityManager;
import com.gmail.nossr50.util.experience.FormulaManager; import com.gmail.nossr50.util.experience.FormulaManager;
import com.gmail.nossr50.util.platform.PlatformManager;
import com.gmail.nossr50.util.platform.ServerSoftwareType;
import com.gmail.nossr50.util.player.PlayerLevelUtils; import com.gmail.nossr50.util.player.PlayerLevelUtils;
import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.player.UserManager;
import com.gmail.nossr50.util.scoreboards.ScoreboardManager; import com.gmail.nossr50.util.scoreboards.ScoreboardManager;
@ -62,10 +65,10 @@ import java.io.InputStreamReader;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
public class mcMMO extends JavaPlugin { public class mcMMO extends JavaPlugin {
/* Managers */ /* Managers */
private static PlatformManager platformManager;
private static ChunkManager placeStore; private static ChunkManager placeStore;
private static RepairableManager repairableManager; private static RepairableManager repairableManager;
private static SalvageableManager salvageableManager; private static SalvageableManager salvageableManager;
@ -141,6 +144,10 @@ public class mcMMO extends JavaPlugin {
public void onEnable() { public void onEnable() {
try { try {
p = this; p = this;
//Platform Manager
platformManager = new PlatformManager();
getLogger().setFilter(new LogFilter(this)); getLogger().setFilter(new LogFilter(this));
metadataValue = new FixedMetadataValue(this, true); metadataValue = new FixedMetadataValue(this, true);
@ -188,10 +195,10 @@ public class mcMMO extends JavaPlugin {
Bukkit Bukkit
.getScheduler() .getScheduler()
.scheduleSyncRepeatingTask(this, .scheduleSyncRepeatingTask(this,
() -> getLogger().severe("You are running an outdated version of "+getServerSoftware()+", mcMMO will not work unless you update to a newer version!"), () -> getLogger().severe("You are running an outdated version of "+platformManager.getServerSoftware()+", mcMMO will not work unless you update to a newer version!"),
20, 20*60*30); 20, 20*60*30);
if(getServerSoftware() == ServerSoftwareType.CRAFTBUKKIT) if(platformManager.getServerSoftware() == ServerSoftwareType.CRAFT_BUKKIT)
{ {
Bukkit.getScheduler() Bukkit.getScheduler()
.scheduleSyncRepeatingTask(this, .scheduleSyncRepeatingTask(this,
@ -275,40 +282,11 @@ public class mcMMO extends JavaPlugin {
Class<?> checkForClassBaseComponent = Class.forName("net.md_5.bungee.api.chat.BaseComponent"); Class<?> checkForClassBaseComponent = Class.forName("net.md_5.bungee.api.chat.BaseComponent");
} catch (ClassNotFoundException | NoSuchMethodException e) { } catch (ClassNotFoundException | NoSuchMethodException e) {
serverAPIOutdated = true; serverAPIOutdated = true;
String software = getServerSoftwareStr(); String software = platformManager.getServerSoftwareStr();
getLogger().severe("You are running an older version of " + software + " that is not compatible with mcMMO, update your server software!"); getLogger().severe("You are running an older version of " + software + " that is not compatible with mcMMO, update your server software!");
} }
} }
private enum ServerSoftwareType {
PAPER,
SPIGOT,
CRAFTBUKKIT
}
private ServerSoftwareType getServerSoftware()
{
if(Bukkit.getVersion().toLowerCase(Locale.ENGLISH).contains("paper"))
return ServerSoftwareType.PAPER;
else if(Bukkit.getVersion().toLowerCase(Locale.ENGLISH).contains("spigot"))
return ServerSoftwareType.SPIGOT;
else
return ServerSoftwareType.CRAFTBUKKIT;
}
private String getServerSoftwareStr()
{
switch(getServerSoftware())
{
case PAPER:
return "Paper";
case SPIGOT:
return "Spigot";
default:
return "CraftBukkit";
}
}
@Override @Override
public void onLoad() public void onLoad()
{ {
@ -436,6 +414,10 @@ public class mcMMO extends JavaPlugin {
return upgradeManager; return upgradeManager;
} }
public static CompatibilityManager getCompatibilityManager() {
return platformManager.getCompatibilityManager();
}
@Deprecated @Deprecated
public static void setDatabaseManager(DatabaseManager databaseManager) { public static void setDatabaseManager(DatabaseManager databaseManager) {
mcMMO.databaseManager = databaseManager; mcMMO.databaseManager = databaseManager;
@ -684,4 +666,8 @@ public class mcMMO extends JavaPlugin {
public static WorldBlacklist getWorldBlacklist() { public static WorldBlacklist getWorldBlacklist() {
return worldBlacklist; return worldBlacklist;
} }
public static PlatformManager getPlatformManager() {
return platformManager;
}
} }

View File

@ -68,7 +68,7 @@ public class AxesManager extends SkillManager {
* Handle the effects of the Axe Mastery ability * Handle the effects of the Axe Mastery ability
*/ */
public double axeMastery() { public double axeMastery() {
if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.ALWAYS_FIRES, SubSkillType.AXES_AXE_MASTERY, getPlayer())) { if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.ALWAYS_FIRES, SubSkillType.AXES_AXE_MASTERY, getPlayer(), mcMMOPlayer.getAttackStrength())) {
return 0; return 0;
} }
@ -82,7 +82,7 @@ public class AxesManager extends SkillManager {
* @param damage The amount of damage initially dealt by the event * @param damage The amount of damage initially dealt by the event
*/ */
public double criticalHit(LivingEntity target, double damage) { public double criticalHit(LivingEntity target, double damage) {
if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.AXES_CRITICAL_STRIKES, getPlayer())) { if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.AXES_CRITICAL_STRIKES, getPlayer(), mcMMOPlayer.getAttackStrength())) {
return 0; return 0;
} }
@ -118,7 +118,7 @@ public class AxesManager extends SkillManager {
for (ItemStack armor : target.getEquipment().getArmorContents()) { for (ItemStack armor : target.getEquipment().getArmorContents()) {
if (armor != null && ItemUtils.isArmor(armor)) { if (armor != null && ItemUtils.isArmor(armor)) {
if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_STATIC_CHANCE, SubSkillType.AXES_ARMOR_IMPACT, getPlayer())) { if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_STATIC_CHANCE, SubSkillType.AXES_ARMOR_IMPACT, getPlayer(), mcMMOPlayer.getAttackStrength())) {
SkillUtils.handleDurabilityChange(armor, durabilityDamage, 1); SkillUtils.handleDurabilityChange(armor, durabilityDamage, 1);
} }
} }
@ -136,7 +136,7 @@ public class AxesManager extends SkillManager {
*/ */
public double greaterImpact(LivingEntity target) { public double greaterImpact(LivingEntity target) {
//static chance (3rd param) //static chance (3rd param)
if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_STATIC_CHANCE, SubSkillType.AXES_GREATER_IMPACT, getPlayer())) { if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_STATIC_CHANCE, SubSkillType.AXES_GREATER_IMPACT, getPlayer(), mcMMOPlayer.getAttackStrength())) {
return 0; return 0;
} }

View File

@ -60,7 +60,7 @@ public class SwordsManager extends SkillManager {
* @param target The defending entity * @param target The defending entity
*/ */
public void ruptureCheck(LivingEntity target) { public void ruptureCheck(LivingEntity target) {
if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.SWORDS_RUPTURE, getPlayer())) { if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.SWORDS_RUPTURE, getPlayer(), this.mcMMOPlayer.getAttackStrength())) {
if (target instanceof Player) { if (target instanceof Player) {
Player defender = (Player) target; Player defender = (Player) target;
@ -89,8 +89,7 @@ public class SwordsManager extends SkillManager {
if(rank > 0) if(rank > 0)
{ {
double stabDamage = 1.0D + (rank * 1.5); return (1.0D + (rank * 1.5));
return stabDamage;
} }
return 0; return 0;

View File

@ -26,23 +26,11 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
public class UnarmedManager extends SkillManager { public class UnarmedManager extends SkillManager {
private long lastAttacked;
private long attackInterval;
public UnarmedManager(McMMOPlayer mcMMOPlayer) { public UnarmedManager(McMMOPlayer mcMMOPlayer) {
super(mcMMOPlayer, PrimarySkillType.UNARMED); super(mcMMOPlayer, PrimarySkillType.UNARMED);
initUnarmedPerPlayerVars();
} }
/**
* Inits variables used for each player for unarmed
*/
private void initUnarmedPerPlayerVars() {
lastAttacked = 0;
attackInterval = 750;
}
public boolean canActivateAbility() { public boolean canActivateAbility() {
return mcMMOPlayer.getToolPreparationMode(ToolType.FISTS) && Permissions.berserk(getPlayer()); return mcMMOPlayer.getToolPreparationMode(ToolType.FISTS) && Permissions.berserk(getPlayer());
} }
@ -113,7 +101,7 @@ public class UnarmedManager extends SkillManager {
* @param defender The defending player * @param defender The defending player
*/ */
public void disarmCheck(Player defender) { public void disarmCheck(Player defender) {
if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.UNARMED_DISARM, getPlayer()) && !hasIronGrip(defender)) { if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.UNARMED_DISARM, getPlayer(), mcMMOPlayer.getAttackStrength()) && !hasIronGrip(defender)) {
if (EventUtils.callDisarmEvent(defender).isCancelled()) { if (EventUtils.callDisarmEvent(defender).isCancelled()) {
return; return;
} }
@ -150,7 +138,7 @@ public class UnarmedManager extends SkillManager {
* @param damage The amount of damage initially dealt by the event * @param damage The amount of damage initially dealt by the event
*/ */
public double berserkDamage(double damage) { public double berserkDamage(double damage) {
damage = (damage * Unarmed.berserkDamageModifier) - damage; damage = ((damage * Unarmed.berserkDamageModifier) * mcMMOPlayer.getAttackStrength()) - damage;
return damage; return damage;
} }
@ -158,7 +146,7 @@ public class UnarmedManager extends SkillManager {
/** /**
* Handle the effects of the Iron Arm ability * Handle the effects of the Iron Arm ability
*/ */
public double ironArm() { public double calculateIronArmDamage() {
if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.ALWAYS_FIRES, SubSkillType.UNARMED_IRON_ARM_STYLE, getPlayer())) { if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.ALWAYS_FIRES, SubSkillType.UNARMED_IRON_ARM_STYLE, getPlayer())) {
return 0; return 0;
} }
@ -166,10 +154,6 @@ public class UnarmedManager extends SkillManager {
return getIronArmDamage(); return getIronArmDamage();
} }
public boolean isPunchingCooldownOver() {
return (lastAttacked + attackInterval) <= System.currentTimeMillis();
}
public double getIronArmDamage() { public double getIronArmDamage() {
int rank = RankUtils.getRank(getPlayer(), SubSkillType.UNARMED_IRON_ARM_STYLE); int rank = RankUtils.getRank(getPlayer(), SubSkillType.UNARMED_IRON_ARM_STYLE);
@ -199,20 +183,4 @@ public class UnarmedManager extends SkillManager {
return false; return false;
} }
public long getLastAttacked() {
return lastAttacked;
}
public void setLastAttacked(long lastAttacked) {
this.lastAttacked = lastAttacked;
}
public long getAttackInterval() {
return attackInterval;
}
public void setAttackInterval(long attackInterval) {
this.attackInterval = attackInterval;
}
} }

View File

@ -1,6 +1,7 @@
package com.gmail.nossr50.util.commands; package com.gmail.nossr50.util.commands;
import com.gmail.nossr50.commands.*; import com.gmail.nossr50.commands.*;
import com.gmail.nossr50.commands.admin.CompatibilityCommand;
import com.gmail.nossr50.commands.admin.McmmoReloadLocaleCommand; import com.gmail.nossr50.commands.admin.McmmoReloadLocaleCommand;
import com.gmail.nossr50.commands.admin.PlayerDebugCommand; import com.gmail.nossr50.commands.admin.PlayerDebugCommand;
import com.gmail.nossr50.commands.chat.AdminChatCommand; import com.gmail.nossr50.commands.chat.AdminChatCommand;
@ -421,6 +422,13 @@ public final class CommandRegistrationManager {
command.setExecutor(new McmmoReloadLocaleCommand()); command.setExecutor(new McmmoReloadLocaleCommand());
} }
private static void registerCompatibilityCommand() {
PluginCommand command = mcMMO.p.getCommand("mmocompat"); //TODO: Localize
command.setDescription("Information about mcMMO and whether or not its in compatibility mode or fully functional.");
command.setUsage(LocaleLoader.formatString("Commands.Usage.0", "mmocompat"));
command.setExecutor(new CompatibilityCommand());
}
public static void registerCommands() { public static void registerCommands() {
// Generic Commands // Generic Commands
registerMmoInfoCommand(); registerMmoInfoCommand();
@ -472,5 +480,8 @@ public final class CommandRegistrationManager {
// Admin commands // Admin commands
registerReloadLocaleCommand(); registerReloadLocaleCommand();
// Misc
registerCompatibilityCommand();
} }
} }

View File

@ -0,0 +1,12 @@
package com.gmail.nossr50.util.compat;
/**
* Compatibility Layers should be named after the functionality they serve
*/
public interface CompatibilityLayer {
/**
* Whether or not this CompatibilityLayer successfully initialized and in theory should be functional
* @return true if this CompatibilityLayer is functional
*/
boolean noErrorsOnInitialize();
}

View File

@ -0,0 +1,125 @@
package com.gmail.nossr50.util.compat;
import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.StringUtils;
import com.gmail.nossr50.util.compat.layers.PlayerAttackCooldownExploitPreventionLayer;
import com.gmail.nossr50.util.nms.NMSVersion;
import com.gmail.nossr50.util.platform.MinecraftGameVersion;
import org.bukkit.command.CommandSender;
import java.util.HashMap;
/**
*
* These classes are a band-aid solution for adding NMS support into 2.1.XXX
* In 2.2 we are switching to modules and that will clean things up significantly
*
*/
public class CompatibilityManager {
private HashMap<CompatibilityType, Boolean> supportedLayers;
private boolean isFullyCompatibleServerSoftware = true; //true if all compatibility layers load successfully
private final MinecraftGameVersion minecraftGameVersion;
private final NMSVersion nmsVersion;
/* Compatibility Layers */
private PlayerAttackCooldownExploitPreventionLayer playerAttackCooldownExploitPreventionLayer;
public CompatibilityManager(MinecraftGameVersion minecraftGameVersion) {
mcMMO.p.getLogger().info("Loading compatibility layers...");
this.minecraftGameVersion = minecraftGameVersion;
this.nmsVersion = determineNMSVersion();
init();
mcMMO.p.getLogger().info("Finished loading compatibility layers.");
}
private void init() {
initSupportedLayersMap();
initCompatibilityLayers();
}
private void initSupportedLayersMap() {
supportedLayers = new HashMap<>(); //Init map
for(CompatibilityType compatibilityType : CompatibilityType.values()) {
supportedLayers.put(compatibilityType, false); //All layers are set to false when initialized
}
}
/**
* Initialize all necessary compatibility layers
* For any unsupported layers, load a dummy layer
*/
private void initCompatibilityLayers() {
if(nmsVersion == NMSVersion.UNSUPPORTED) {
mcMMO.p.getLogger().info("NMS not supported for this version of Minecraft, possible solutions include updating mcMMO or updating your server software. NMS Support is not available on every version of Minecraft.");
mcMMO.p.getLogger().info("Certain features of mcMMO that require NMS will be disabled, you can check what is disabled by running the /mmocompat command!");
//Load dummy compatibility layers
isFullyCompatibleServerSoftware = false;
loadDummyCompatibilityLayers();
} else {
playerAttackCooldownExploitPreventionLayer = new PlayerAttackCooldownExploitPreventionLayer(nmsVersion);
//Mark as operational
if(playerAttackCooldownExploitPreventionLayer.noErrorsOnInitialize()) {
supportedLayers.put(CompatibilityType.PLAYER_ATTACK_COOLDOWN_EXPLOIT_PREVENTION, true);
}
}
}
private void loadDummyCompatibilityLayers() {
}
public void reportCompatibilityStatus(CommandSender commandSender) {
if(isFullyCompatibleServerSoftware) {
commandSender.sendMessage(LocaleLoader.getString("mcMMO.Template.Prefix",
"mcMMO is fully compatible with the currently running server software."));
} else {
//TODO: Better messages for each incompatible layer
for(CompatibilityType compatibilityType : CompatibilityType.values()) {
if(!supportedLayers.get(compatibilityType)) {
commandSender.sendMessage(LocaleLoader.getString("mcMMO.Template.Prefix",
"Support layer for " + StringUtils.getCapitalized(compatibilityType.toString()) + "is not supported on this version of Minecraft."));
}
}
}
commandSender.sendMessage(LocaleLoader.getString("mcMMO.Template.Prefix", "NMS Status - " + nmsVersion.toString()));
}
public boolean isCompatibilityLayerOperational(CompatibilityType compatibilityType) {
return supportedLayers.get(compatibilityType);
}
public boolean isFullyCompatibleServerSoftware() {
return isFullyCompatibleServerSoftware;
}
public NMSVersion getNmsVersion() {
return nmsVersion;
}
private NMSVersion determineNMSVersion() {
switch(minecraftGameVersion.getMajorVersion().asInt()) {
case 1:
switch(minecraftGameVersion.getMinorVersion().asInt()) {
case 12:
return NMSVersion.NMS_1_12_2;
case 13:
return NMSVersion.NMS_1_13_2;
case 14:
return NMSVersion.NMS_1_14_4;
case 15:
return NMSVersion.NMS_1_15_2;
}
}
return NMSVersion.UNSUPPORTED;
}
public PlayerAttackCooldownExploitPreventionLayer getPlayerAttackCooldownExploitPreventionLayer() {
return playerAttackCooldownExploitPreventionLayer;
}
}

View File

@ -0,0 +1,5 @@
package com.gmail.nossr50.util.compat;
public enum CompatibilityType {
PLAYER_ATTACK_COOLDOWN_EXPLOIT_PREVENTION
}

View File

@ -0,0 +1,32 @@
package com.gmail.nossr50.util.compat.layers;
import com.gmail.nossr50.util.compat.CompatibilityLayer;
import com.gmail.nossr50.util.nms.NMSVersion;
import org.jetbrains.annotations.NotNull;
/**
*
* These classes are a band-aid solution for adding NMS support into 2.1.XXX
* In 2.2 we are switching to modules and that will clean things up significantly
*
*/
public abstract class AbstractCompatibilityLayer implements CompatibilityLayer {
protected boolean noErrorsOnInitialize = true;
protected final @NotNull NMSVersion nmsVersion;
public AbstractCompatibilityLayer(@NotNull NMSVersion nmsVersion) {
this.nmsVersion = nmsVersion;
}
/**
* Initialize the CompatibilityLayer
* @return true if the CompatibilityLayer initialized and should be functional
*/
public abstract boolean initializeLayer();
@Override
public boolean noErrorsOnInitialize() {
return noErrorsOnInitialize;
}
}

View File

@ -0,0 +1,33 @@
package com.gmail.nossr50.util.compat.layers;
import com.gmail.nossr50.util.nms.NMSVersion;
import org.bukkit.entity.Player;
import java.lang.reflect.InvocationTargetException;
public class DummyPlayerAttackCooldownExploitPreventionLayer extends PlayerAttackCooldownExploitPreventionLayer {
public DummyPlayerAttackCooldownExploitPreventionLayer() {
super(NMSVersion.UNSUPPORTED);
}
@Override
public boolean initializeLayer() {
return noErrorsOnInitialize;
}
@Override
public float getAttackStrength(Player player) throws InvocationTargetException, IllegalAccessException {
return 1.0F; //Always full strength
}
@Override
public float getCooldownValue(Player player) throws InvocationTargetException, IllegalAccessException {
return 0F;
}
@Override
public void resetAttackStrength(Player player) throws InvocationTargetException, IllegalAccessException {
//Do nothing
return;
}
}

View File

@ -0,0 +1,198 @@
package com.gmail.nossr50.util.compat.layers;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.nms.NMSConstants;
import com.gmail.nossr50.util.nms.NMSVersion;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
*
* These classes are a band-aid solution for adding NMS support into 2.1.XXX
* In 2.2 we are switching to modules and that will clean things up significantly
*
*/
public class PlayerAttackCooldownExploitPreventionLayer extends AbstractCompatibilityLayer implements PlayerAttackCooldownMethods{
private final String cbNMSVersionPath;
protected Class<?> craftPlayerClass;
protected Class<?> entityHumanClass;
protected Method playerAttackCooldownMethod;
protected Method playerAttackStrengthMethod;
protected Method resetPlayerAttackCooldownMethod;
protected Method getHandleMethod;
public PlayerAttackCooldownExploitPreventionLayer(@NotNull NMSVersion nmsVersion) {
super(nmsVersion);
mcMMO.p.getLogger().info("Loading Compatibility Layer... (Player Attack Cooldown Exploit Prevention)");
if(!isCompatibleWithMinecraftVersion()) {
mcMMO.p.getLogger().severe("this version of mcMMO does not support NMS for this version of Minecraft, try updating mcMMO or updating Minecraft. Not all versions of Minecraft will have NMS support built into mcMMO.");
cbNMSVersionPath = "";
} else {
if(NMSConstants.getCraftBukkitVersionPath(nmsVersion) != null) {
cbNMSVersionPath = NMSConstants.getCraftBukkitVersionPath(nmsVersion);
noErrorsOnInitialize = initializeLayer();
if(noErrorsOnInitialize) {
mcMMO.p.getLogger().info("Successfully Loaded Compatibility Layer! (Player Attack Cooldown Exploit Prevention)");
}
} else {
mcMMO.p.getLogger().info("Failed to load - CL (Player Attack Cooldown Exploit Prevention) Could not find CB NMS path for CL");
flagErrorsDuringStartup();
mcMMO.p.getLogger().warning("Could not wire NMS package path for CraftBukkit!");
cbNMSVersionPath = "";
}
}
}
private boolean isCompatibleWithMinecraftVersion() {
switch(nmsVersion) {
case NMS_1_13_2:
case NMS_1_14_4:
case NMS_1_15_2:
return true;
default:
return false;
}
}
/**
* Cache all reflection methods/types/classes needed for the NMS of this CompatibilityLayer
* @param cooldownMethodName the cooldown method name
* @param attackStrengthMethodName the attack strength method name
* @param resetAttackCooldownMethodName the reset attack cooldown method name
* @param getHandleMethodName the get handle method name
* @return true if NMS was successfully wired
*/
public boolean wireNMS(@NotNull String cooldownMethodName, @NotNull String attackStrengthMethodName, @NotNull String resetAttackCooldownMethodName, @NotNull String getHandleMethodName) {
entityHumanClass = initEntityHumanClass();
craftPlayerClass = initCraftPlayerClass();
try {
this.playerAttackCooldownMethod = entityHumanClass.getMethod(cooldownMethodName);
this.playerAttackStrengthMethod = entityHumanClass.getMethod(attackStrengthMethodName, float.class);
this.resetPlayerAttackCooldownMethod = entityHumanClass.getMethod(resetAttackCooldownMethodName);
if (craftPlayerClass != null) {
this.getHandleMethod = craftPlayerClass.getMethod(getHandleMethodName);
} else {
return false;
}
return true;
} catch (NoSuchMethodException e) {
flagErrorsDuringStartup();
e.printStackTrace();
return false;
}
}
/**
* Get the cached player attack cooldown method
* @return the cached player attack cooldown method
*/
private @Nullable Method getPlayerAttackCooldownMethod() {
return playerAttackCooldownMethod;
}
/**
* Get the cached player attack strength method
* @return the cached player attack strength method
*/
private @Nullable Method getPlayerAttackStrengthMethod() {
return playerAttackStrengthMethod;
}
/**
* Get the cached player attack cooldown reset method
* @return the cached player attack cooldown reset method
*/
private @Nullable Method getResetPlayerAttackCooldownMethod() {
return resetPlayerAttackCooldownMethod;
}
/**
* Grab the CraftPlayer class type from NMS
* @return the CraftPlayer class type from NMS
*/
private @Nullable Class<?> initCraftPlayerClass() {
try {
return Class.forName(NMSConstants.getCraftPlayerClassPath(cbNMSVersionPath));
} catch (ClassNotFoundException e) {
flagErrorsDuringStartup();
e.printStackTrace();
return null;
}
}
/**
* Grab the EntityHuman class type from NMS
* @return the EntityHuman class type from NMS
*/
private @Nullable Class<?> initEntityHumanClass() {
try {
return Class.forName(NMSConstants.getEntityHumanClassPath(cbNMSVersionPath));
} catch (ClassNotFoundException e) {
flagErrorsDuringStartup();
e.printStackTrace();
return null;
}
}
private void flagErrorsDuringStartup() {
noErrorsOnInitialize = false;
}
/**
* Grabs the attack strength for a player
* Should be noted that as of today there is no way to capture a players current attack strength in spigot when they attack an entity outside of network packet listening
* @param player target player
* @return the float value of the player's attack strength
*/
@Override
public float getAttackStrength(Player player) throws InvocationTargetException, IllegalAccessException {
Object craftPlayer = craftPlayerClass.cast(player);
Object entityHuman = entityHumanClass.cast(getHandleMethod.invoke(craftPlayer));
return (float) playerAttackStrengthMethod.invoke(entityHuman, 0F); //Add no adjustment ticks
}
@Override
public float getCooldownValue(Player player) throws InvocationTargetException, IllegalAccessException {
Object craftPlayer = craftPlayerClass.cast(player);
Object entityHuman = entityHumanClass.cast(getHandleMethod.invoke(craftPlayer));
return (float) playerAttackCooldownMethod.invoke(entityHuman); //Add no adjustment ticks
}
@Override
public void resetAttackStrength(Player player) throws InvocationTargetException, IllegalAccessException {
Object craftPlayer = craftPlayerClass.cast(player);
Object entityHuman = entityHumanClass.cast(getHandleMethod.invoke(craftPlayer));
resetPlayerAttackCooldownMethod.invoke(entityHuman);
}
@Override
public boolean initializeLayer() {
switch(nmsVersion) {
case NMS_1_12_2:
return wireNMS("dr", "n", "ds", "getHandle");
case NMS_1_13_2:
return wireNMS("dG", "r", "dH", "getHandle");
case NMS_1_14_4:
return wireNMS("dY", "s", "dZ", "getHandle");
case NMS_1_15_2:
return wireNMS("ex", "s", "ey", "getHandle");
default:
break;
}
return false;
}
}

View File

@ -0,0 +1,19 @@
package com.gmail.nossr50.util.compat.layers;
import org.bukkit.entity.Player;
import java.lang.reflect.InvocationTargetException;
public interface PlayerAttackCooldownMethods {
/**
* Grabs the attack strength for a player
* Should be noted that as of today there is no way to capture a players current attack strength in spigot when they attack an entity outside of network packet listening
* @param player target player
* @return the float value of the player's attack strength
*/
float getAttackStrength(Player player) throws InvocationTargetException, IllegalAccessException;
float getCooldownValue(Player player) throws InvocationTargetException, IllegalAccessException;
void resetAttackStrength(Player player) throws InvocationTargetException, IllegalAccessException;
}

View File

@ -0,0 +1,59 @@
package com.gmail.nossr50.util.nms;
import org.jetbrains.annotations.Nullable;
public class NMSConstants {
public final static String BUKKIT_PACKAGE_PATH = "org.bukkit";
public final static String CRAFT_BUKKIT_PACKAGE_PATH = "org.bukkit.craftbukkit";
public final static String NET_MINECRAFT_SERVER = "net.minecraft.server";
private final static String CRAFT_PLAYER_CLASS_PATH = "entity.CraftPlayer";
private final static String ENTITY_HUMAN_CLASS_PATH = "EntityHuman";
/**
* Grabs the fully qualified path of a class from CB
* @param targetClass source root path
* @return the fully qualified path of a CB class
*/
protected static String getFullyQualifiedCraftBukkitPath(String cbVersionPackage, String targetClass) {
return CRAFT_BUKKIT_PACKAGE_PATH + "." + cbVersionPackage + "." + targetClass;
}
protected static String getFullQualifiedBukkitPath(String fromSourceRoot) {
return BUKKIT_PACKAGE_PATH + "." + fromSourceRoot;
}
protected static String getFullyQualifiedNMSPath(String cbVersionPackage, String fromSourceRoot) {
return NET_MINECRAFT_SERVER + "." +cbVersionPackage + "." + fromSourceRoot;
}
public static String getCraftPlayerClassPath(String cbVersionPackage) {
return getFullyQualifiedCraftBukkitPath(cbVersionPackage, CRAFT_PLAYER_CLASS_PATH);
}
public static String getEntityHumanClassPath(String cbVersionPackage) {
return getFullyQualifiedNMSPath(cbVersionPackage, ENTITY_HUMAN_CLASS_PATH);
}
public static @Nullable String getCraftBukkitVersionPath(NMSVersion nmsVersion) {
switch (nmsVersion) {
case NMS_1_8_8:
break;
case NMS_1_12_2:
return "v1_12_R1";
case NMS_1_13_2:
return "v1_13_R2";
case NMS_1_14_4:
return "v1_14_R1";
case NMS_1_15_2:
return "v1_15_R1";
case NMS_1_16:
break;
case UNSUPPORTED:
break;
}
return null;
}
}

View File

@ -0,0 +1,39 @@
package com.gmail.nossr50.util.nms;
public enum NMSVersion {
//1.8
NMS_1_8_8("1.8.8"),
//1.12
NMS_1_12_2("1.12.2"),
//1.13
NMS_1_13_2("1.13.2"),
//1.14
NMS_1_14_4("1.14.4"),
//1.15
NMS_1_15_2("1.15.2"),
//1.16
NMS_1_16("1.16"),
//Version not known to this build of mcMMO
UNSUPPORTED("unsupported");
private final String sanitizedVersionNumber;
NMSVersion(String sanitizedVersionNumber) {
this.sanitizedVersionNumber = sanitizedVersionNumber;
}
/**
* The standardized major.minor.patch {@link String} for the current NMS mappings
*
* @return the standardized major.minor.patch version string, patch is omitted if it is not a patch version
*/
public String getSanitizedVersionNumber() {
return sanitizedVersionNumber;
}
}

View File

@ -0,0 +1,23 @@
package com.gmail.nossr50.util.platform;
import com.gmail.nossr50.util.compat.CompatibilityManager;
/**
*
* These classes are a band-aid solution for adding NMS support into 2.1.XXX
* In 2.2 we are switching to modules and that will clean things up significantly
*
*/
public abstract class AbstractPlatform implements Platform{
protected final CompatibilityManager compatibilityManager;
protected final MinecraftGameVersion minecraftGameVersion;
protected final ServerSoftwareType serverSoftwareType;
public AbstractPlatform(MinecraftGameVersion minecraftGameVersion, ServerSoftwareType serverSoftwareType, CompatibilityManager compatibilityManager) {
this.minecraftGameVersion = minecraftGameVersion;
this.serverSoftwareType = serverSoftwareType;
this.compatibilityManager = compatibilityManager;
}
}

View File

@ -0,0 +1,32 @@
package com.gmail.nossr50.util.platform;
import com.gmail.nossr50.util.compat.CompatibilityManager;
import org.jetbrains.annotations.NotNull;
/**
*
* These classes are a band-aid solution for adding NMS support into 2.1.XXX
* In 2.2 we are switching to modules and that will clean things up significantly
*
*/
public class BukkitPlatform extends AbstractPlatform {
public BukkitPlatform(MinecraftGameVersion minecraftGameVersion) {
super(minecraftGameVersion, ServerSoftwareType.CRAFT_BUKKIT, new CompatibilityManager(minecraftGameVersion));
}
@Override
public @NotNull ServerSoftwareType getServerSoftwareType() {
return super.serverSoftwareType;
}
@Override
public @NotNull CompatibilityManager getCompatibilityManager() {
return compatibilityManager;
}
@Override
public @NotNull MinecraftGameVersion getGameVersion() {
return super.minecraftGameVersion;
}
}

View File

@ -0,0 +1,113 @@
package com.gmail.nossr50.util.platform;
import com.gmail.nossr50.util.platform.version.SimpleNumericVersion;
import com.gmail.nossr50.util.platform.version.Versioned;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
*
* These classes are a band-aid solution for adding NMS support into 2.1.XXX
* In 2.2 we are switching to modules and that will clean things up significantly
*
*/
public abstract class MajorMinorPatchVersion implements Versioned {
@NotNull
private final SimpleNumericVersion majorVersion;
@NotNull
private final SimpleNumericVersion minorVersion;
@NotNull
private final SimpleNumericVersion patchVersion;
public MajorMinorPatchVersion(@NotNull SimpleNumericVersion majorVersion, @NotNull SimpleNumericVersion minorVersion) {
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
this.patchVersion = new SimpleNumericVersion(0);
}
public MajorMinorPatchVersion(@NotNull SimpleNumericVersion majorVersion, @NotNull SimpleNumericVersion minorVersion, @Nullable SimpleNumericVersion patchVersion) {
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
if(patchVersion == null) {
this.patchVersion = new SimpleNumericVersion(0);
} else {
this.patchVersion = patchVersion;
}
}
public MajorMinorPatchVersion(int majorVerNumber, int minorVerNumber, int patchVerNumber) {
this.majorVersion = new SimpleNumericVersion(majorVerNumber);
this.minorVersion = new SimpleNumericVersion(minorVerNumber);
this.patchVersion = new SimpleNumericVersion(patchVerNumber);
}
public MajorMinorPatchVersion(int majorVerNumber, int minorVerNumber) {
this.majorVersion = new SimpleNumericVersion(majorVerNumber);
this.minorVersion = new SimpleNumericVersion(minorVerNumber);
this.patchVersion = new SimpleNumericVersion(0);
}
/**
* Get the major version string
* @return the major version string
*/
public @NotNull SimpleNumericVersion getMajorVersion() {
return majorVersion;
}
/**
* Get the minor version string
* @return the minor version string
*/
public @NotNull SimpleNumericVersion getMinorVersion() {
return minorVersion;
}
/**
* Get the patch version string
* @return patch version string or null if patch numeric value is less than or equal to 0
*/
public @NotNull SimpleNumericVersion getPatchVersion() {
return patchVersion;
}
@Override
public String getVersionStr() {
if(isPatch()) {
return majorVersion.getVersionString() + "."
+ minorVersion + "."
+ patchVersion;
} else {
return majorVersion.getVersionString() + "."
+ minorVersion.getVersionString();
}
}
/**
* Whether or not this version of Minecraft is a patch
* a patch version value above 0 will indicate that this is a patch
* @return true if this version is a patch
*/
public boolean isPatch() {
return patchVersion.asInt() > 0;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MajorMinorPatchVersion that = (MajorMinorPatchVersion) o;
return majorVersion.equals(that.majorVersion) &&
minorVersion.equals(that.minorVersion) &&
patchVersion.equals(that.patchVersion);
}
@Override
public int hashCode() {
return Objects.hash(majorVersion, minorVersion, patchVersion);
}
}

View File

@ -0,0 +1,30 @@
package com.gmail.nossr50.util.platform;
import com.gmail.nossr50.util.platform.version.SimpleNumericVersion;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
*
* These classes are a band-aid solution for adding NMS support into 2.1.XXX
* In 2.2 we are switching to modules and that will clean things up significantly
*
*/
public class MinecraftGameVersion extends MajorMinorPatchVersion {
public MinecraftGameVersion(@NotNull SimpleNumericVersion majorVersion, @NotNull SimpleNumericVersion minorVersion) {
super(majorVersion, minorVersion);
}
public MinecraftGameVersion(@NotNull SimpleNumericVersion majorVersion, @NotNull SimpleNumericVersion minorVersion, @Nullable SimpleNumericVersion patchVersion) {
super(majorVersion, minorVersion, patchVersion);
}
public MinecraftGameVersion(int majorVerNumber, int minorVerNumber, int patchVerNumber) {
super(majorVerNumber, minorVerNumber, patchVerNumber);
}
public MinecraftGameVersion(int majorVerNumber, int minorVerNumber) {
super(majorVerNumber, minorVerNumber);
}
}

View File

@ -0,0 +1,32 @@
package com.gmail.nossr50.util.platform;
import com.gmail.nossr50.util.compat.CompatibilityManager;
import org.jetbrains.annotations.NotNull;
/**
*
* These classes are a band-aid solution for adding NMS support into 2.1.XXX
* In 2.2 we are switching to modules and that will clean things up significantly
*
*/
public interface Platform {
/**
* Target {@link ServerSoftwareType} for this {@link Platform}
* @return the {@link ServerSoftwareType} for this {@link Platform}
*/
@NotNull ServerSoftwareType getServerSoftwareType();
/**
* Get the {@link CompatibilityManager} for this {@link Platform}
* @return the {@link CompatibilityManager} for this platform
*/
@NotNull CompatibilityManager getCompatibilityManager();
/**
* The target game version of this {@link Platform}
* @return the target {@link MinecraftGameVersion} of this {@link Platform}
*/
@NotNull MinecraftGameVersion getGameVersion();
}

View File

@ -0,0 +1,45 @@
package com.gmail.nossr50.util.platform;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
*
* These classes are a band-aid solution for adding NMS support into 2.1.XXX
* In 2.2 we are switching to modules and that will clean things up significantly
*
*/
public class PlatformBuilder {
private MinecraftGameVersion minecraftGameVersion;
private ServerSoftwareType serverSoftwareType;
public PlatformBuilder() {
}
public PlatformBuilder setMinecraftGameVersion(@NotNull MinecraftGameVersion minecraftGameVersion) {
this.minecraftGameVersion = minecraftGameVersion;
return this;
}
public PlatformBuilder setSoftwareType(@NotNull ServerSoftwareType softwareType) {
this.serverSoftwareType = softwareType;
return this;
}
public @Nullable Platform build() {
switch(serverSoftwareType) {
case PAPER:
case SPIGOT:
case CRAFT_BUKKIT:
return createBukkitPlatform();
default:
return null;
}
}
private BukkitPlatform createBukkitPlatform() {
return new BukkitPlatform(minecraftGameVersion);
}
}

View File

@ -0,0 +1,130 @@
package com.gmail.nossr50.util.platform;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.compat.CompatibilityManager;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Locale;
import java.util.stream.Collectors;
/**
*
* These classes are a band-aid solution for adding NMS support into 2.1.XXX
* In 2.2 we are switching to modules and that will clean things up significantly
*
*/
public class PlatformManager {
protected Platform platform; //current platform
public PlatformManager() {
init();
}
private void init() {
platform = loadPlatform();
}
public Platform getPlatform() {
return platform;
}
private @Nullable Platform loadPlatform() {
ServerSoftwareType serverSoftwareType = determinePlatformType();
PlatformBuilder platformBuilder = new PlatformBuilder();
MinecraftGameVersion gameVersion = determineGameVersion(Bukkit.getBukkitVersion());
return platformBuilder
.setMinecraftGameVersion(gameVersion)
.setSoftwareType(serverSoftwareType)
.build();
}
//TODO: make this work on things other than bukkit
@Deprecated //Only good for determining bukkit game versions
private @NotNull MinecraftGameVersion determineGameVersion(String platformVersionString) {
int major = 0, minor = 0, patch = 0;
String[] splitVersion = platformVersionString.split("\\.", 3);
mcMMO.p.getLogger().info("Platform String: " + platformVersionString);
//TODO: this is very hacky and probably isn't reliable
//Grab all consecutive digits
major = getSubsequentDigits(splitVersion[0].toCharArray(), 0);
minor = getSubsequentDigits(splitVersion[1].toCharArray(), 0);
//Not all versions of Minecraft have a patch digit
//If the first character isn't a digit it's not a patch number and its some crap we don't care about
if(splitVersion.length > 2 && Character.isDigit(splitVersion[2].toCharArray()[0]))
patch = getSubsequentDigits(splitVersion[2].toCharArray(), 0);
mcMMO.p.getLogger().info("Minecraft version determined to be - "
+ major + "."
+ minor + "."
+ patch);
return new MinecraftGameVersion(major, minor, patch);
}
/**
* Get all consecutive digits in a char array from position
* @param charArray target char array
* @param position starting position
* @return all consecutive digits from position
*/
private int getSubsequentDigits(char[] charArray, int position) {
ArrayList<Character> digitArrayList = new ArrayList<>();
do {
if(Character.isDigit(charArray[position])) {
digitArrayList.add(charArray[position]);
position++;
} else {
break;
}
} while (position < charArray.length);
//Convert List<Character> -> String
String digits = digitArrayList
.stream()
.map(String::valueOf)
.collect(Collectors.joining());
//Copy value
return Integer.parseInt(digits);
}
//TODO: Rewrite this properly once we actually support a not-bukkit platform
private @NotNull ServerSoftwareType determinePlatformType() {
if(Bukkit.getVersion().toLowerCase(Locale.ENGLISH).contains("paper"))
return ServerSoftwareType.PAPER;
else if(Bukkit.getVersion().toLowerCase(Locale.ENGLISH).contains("spigot"))
return ServerSoftwareType.SPIGOT;
else
return ServerSoftwareType.CRAFT_BUKKIT;
}
public ServerSoftwareType getServerSoftware()
{
return platform.getServerSoftwareType();
}
public String getServerSoftwareStr()
{
switch(getServerSoftware())
{
case PAPER:
return "Paper";
case SPIGOT:
return "Spigot";
default:
return "CraftBukkit";
}
}
public @Nullable CompatibilityManager getCompatibilityManager() {
return platform.getCompatibilityManager();
}
}

View File

@ -0,0 +1,9 @@
package com.gmail.nossr50.util.platform;
public enum ServerSoftwareType {
PAPER,
SPIGOT,
CRAFT_BUKKIT,
SPONGE,
OTHER
}

View File

@ -0,0 +1,5 @@
package com.gmail.nossr50.util.platform.version;
public interface NumericVersioned extends Versioned {
int asInt();
}

View File

@ -0,0 +1,22 @@
package com.gmail.nossr50.util.platform.version;
import org.jetbrains.annotations.NotNull;
public class SimpleNumericVersion extends SimpleVersion implements NumericVersioned {
private int versionNumber;
public SimpleNumericVersion(int versionNumber) {
super(String.valueOf(versionNumber));
this.versionNumber = versionNumber;
}
@Override
public int asInt() {
return versionNumber;
}
@Override
public @NotNull String getVersionStr() {
return super.getVersionString();
}
}

View File

@ -0,0 +1,17 @@
package com.gmail.nossr50.util.platform.version;
import org.jetbrains.annotations.NotNull;
public class SimpleVersion {
@NotNull
private final String versionString;
public SimpleVersion(@NotNull String versionString) {
this.versionString = versionString;
}
@NotNull
public String getVersionString() {
return versionString;
}
}

View File

@ -0,0 +1,5 @@
package com.gmail.nossr50.util.platform.version;
public interface Versioned {
String getVersionStr();
}

View File

@ -14,7 +14,29 @@ public class RandomChanceSkill implements RandomChanceExecution {
protected final SubSkillType subSkillType; protected final SubSkillType subSkillType;
protected final double probabilityCap; protected final double probabilityCap;
protected final boolean isLucky; protected final boolean isLucky;
private int skillLevel; protected int skillLevel;
protected double resultModifier;
public RandomChanceSkill(Player player, SubSkillType subSkillType, double resultModifier)
{
this.primarySkillType = subSkillType.getParentSkill();
this.subSkillType = subSkillType;
this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR;
final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
if (player != null && mcMMOPlayer != null) {
this.skillLevel = mcMMOPlayer.getSkillLevel(primarySkillType);
} else {
this.skillLevel = 0;
}
if(player != null)
isLucky = Permissions.lucky(player, primarySkillType);
else
isLucky = false;
this.resultModifier = resultModifier;
}
public RandomChanceSkill(Player player, SubSkillType subSkillType) public RandomChanceSkill(Player player, SubSkillType subSkillType)
{ {
@ -33,6 +55,8 @@ public class RandomChanceSkill implements RandomChanceExecution {
isLucky = Permissions.lucky(player, primarySkillType); isLucky = Permissions.lucky(player, primarySkillType);
else else
isLucky = false; isLucky = false;
this.resultModifier = 1.0D;
} }
public RandomChanceSkill(Player player, SubSkillType subSkillType, boolean hasCap) public RandomChanceSkill(Player player, SubSkillType subSkillType, boolean hasCap)
@ -56,6 +80,33 @@ public class RandomChanceSkill implements RandomChanceExecution {
isLucky = Permissions.lucky(player, primarySkillType); isLucky = Permissions.lucky(player, primarySkillType);
else else
isLucky = false; isLucky = false;
this.resultModifier = 1.0D;
}
public RandomChanceSkill(Player player, SubSkillType subSkillType, boolean hasCap, double resultModifier)
{
if(hasCap)
this.probabilityCap = AdvancedConfig.getInstance().getMaximumProbability(subSkillType);
else
this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR;
this.primarySkillType = subSkillType.getParentSkill();
this.subSkillType = subSkillType;
final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
if (player != null && mcMMOPlayer != null) {
this.skillLevel = mcMMOPlayer.getSkillLevel(primarySkillType);
} else {
this.skillLevel = 0;
}
if(player != null)
isLucky = Permissions.lucky(player, primarySkillType);
else
isLucky = false;
this.resultModifier = resultModifier;
} }
/** /**
@ -118,4 +169,12 @@ public class RandomChanceSkill implements RandomChanceExecution {
public boolean isLucky() { public boolean isLucky() {
return isLucky; return isLucky;
} }
public double getResultModifier() {
return resultModifier;
}
public void setResultModifier(double resultModifier) {
this.resultModifier = resultModifier;
}
} }

View File

@ -13,6 +13,13 @@ public class RandomChanceSkillStatic extends RandomChanceSkill {
this.xPos = xPos; this.xPos = xPos;
} }
public RandomChanceSkillStatic(double xPos, Player player, SubSkillType subSkillType, double resultModifier)
{
super(player, subSkillType, resultModifier);
this.xPos = xPos;
}
/** /**
* Gets the XPos used in the formula for success * Gets the XPos used in the formula for success
* *

View File

@ -46,6 +46,32 @@ public class RandomChanceUtil
} }
} }
/**
* This method is the final step in determining if a Sub-Skill / Secondary Skill in mcMMO successfully activates either from chance or otherwise
* Random skills check for success based on numbers and then fire a cancellable event, if that event is not cancelled they succeed
* non-RNG skills just fire the cancellable event and succeed if they go uncancelled
*
* @param skillActivationType this value represents what kind of activation procedures this sub-skill uses
* @param subSkillType The identifier for this specific sub-skill
* @param player The owner of this sub-skill
* @return returns true if all conditions are met and the event is not cancelled
*/
public static boolean isActivationSuccessful(SkillActivationType skillActivationType, SubSkillType subSkillType, Player player, double resultModifier)
{
switch(skillActivationType)
{
case RANDOM_LINEAR_100_SCALE_WITH_CAP:
return checkRandomChanceExecutionSuccess(player, subSkillType, true);
case RANDOM_STATIC_CHANCE:
return checkRandomStaticChanceExecutionSuccess(player, subSkillType, resultModifier);
case ALWAYS_FIRES:
SubSkillEvent event = EventUtils.callSubSkillEvent(player, subSkillType);
return !event.isCancelled();
default:
return false;
}
}
public static double getActivationChance(SkillActivationType skillActivationType, SubSkillType subSkillType, Player player) public static double getActivationChance(SkillActivationType skillActivationType, SubSkillType subSkillType, Player player)
{ {
switch(skillActivationType) switch(skillActivationType)
@ -78,7 +104,24 @@ public class RandomChanceUtil
} }
public static boolean rollDice(double chanceOfSuccess, int bound) { public static boolean rollDice(double chanceOfSuccess, int bound) {
return chanceOfSuccess > ThreadLocalRandom.current().nextInt(bound); return rollDice(chanceOfSuccess, bound, 1.0F);
}
public static boolean rollDice(double chanceOfSuccess, int bound, double resultModifier) {
return chanceOfSuccess > (ThreadLocalRandom.current().nextInt(bound) * resultModifier);
}
/**
* Used for stuff like Excavation, Fishing, etc...
* @param randomChance
* @return
*/
public static boolean checkRandomChanceExecutionSuccess(RandomChanceSkillStatic randomChance, double resultModifier)
{
double chanceOfSuccess = calculateChanceOfSuccess(randomChance);
//Check the odds
return rollDice(chanceOfSuccess, 100, resultModifier);
} }
/** /**
@ -88,10 +131,7 @@ public class RandomChanceUtil
*/ */
public static boolean checkRandomChanceExecutionSuccess(RandomChanceSkillStatic randomChance) public static boolean checkRandomChanceExecutionSuccess(RandomChanceSkillStatic randomChance)
{ {
double chanceOfSuccess = calculateChanceOfSuccess(randomChance); return checkRandomChanceExecutionSuccess(randomChance, 1.0F);
//Check the odds
return rollDice(chanceOfSuccess, 100);
} }
public static boolean checkRandomChanceExecutionSuccess(RandomChanceSkill randomChance) public static boolean checkRandomChanceExecutionSuccess(RandomChanceSkill randomChance)
@ -115,9 +155,7 @@ public class RandomChanceUtil
* @return * @return
*/ */
public static double getRandomChanceExecutionChance(RandomChanceExecution randomChance) { public static double getRandomChanceExecutionChance(RandomChanceExecution randomChance) {
double chanceOfSuccess = getChanceOfSuccess(randomChance.getXPos(), randomChance.getProbabilityCap(), LINEAR_CURVE_VAR); return getChanceOfSuccess(randomChance.getXPos(), randomChance.getProbabilityCap(), LINEAR_CURVE_VAR);
return chanceOfSuccess;
} }
public static double getRandomChanceExecutionChance(RandomChanceStatic randomChance) { public static double getRandomChanceExecutionChance(RandomChanceStatic randomChance) {
@ -210,7 +248,18 @@ public class RandomChanceUtil
return checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType)); return checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType));
} }
public static boolean checkRandomStaticChanceExecutionSuccess(Player player, SubSkillType subSkillType) public static boolean checkRandomChanceExecutionSuccess(Player player, SubSkillType subSkillType, boolean hasCap, double resultModifier)
{
return checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType, hasCap, resultModifier));
}
public static boolean checkRandomChanceExecutionSuccess(Player player, SubSkillType subSkillType, double resultModifier)
{
return checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType, resultModifier));
}
public static boolean checkRandomStaticChanceExecutionSuccess(Player player, SubSkillType subSkillType, double resultModifier)
{ {
try { try {
return checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(getStaticRandomChance(subSkillType), player, subSkillType)); return checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(getStaticRandomChance(subSkillType), player, subSkillType));
@ -222,6 +271,11 @@ public class RandomChanceUtil
return false; return false;
} }
public static boolean checkRandomStaticChanceExecutionSuccess(Player player, SubSkillType subSkillType)
{
return checkRandomStaticChanceExecutionSuccess(player, subSkillType, 1.0F);
}
/** /**
* Grabs static activation rolls for Secondary Abilities * Grabs static activation rolls for Secondary Abilities
* @param subSkillType The secondary ability to grab properties of * @param subSkillType The secondary ability to grab properties of

View File

@ -74,7 +74,7 @@ public final class CombatUtils {
//Add Stab Damage //Add Stab Damage
if(swordsManager.canUseStab()) if(swordsManager.canUseStab())
{ {
finalDamage+=swordsManager.getStabDamage(); finalDamage+=(swordsManager.getStabDamage() * mcMMOPlayer.getAttackStrength());
} }
if (swordsManager.canUseSerratedStrike()) { if (swordsManager.canUseSerratedStrike()) {
@ -83,13 +83,38 @@ public final class CombatUtils {
if(canUseLimitBreak(player, target, SubSkillType.SWORDS_SWORDS_LIMIT_BREAK)) if(canUseLimitBreak(player, target, SubSkillType.SWORDS_SWORDS_LIMIT_BREAK))
{ {
finalDamage+=getLimitBreakDamage(player, target, SubSkillType.SWORDS_SWORDS_LIMIT_BREAK); finalDamage+=(getLimitBreakDamage(player, target, SubSkillType.SWORDS_SWORDS_LIMIT_BREAK) * mcMMOPlayer.getAttackStrength());
} }
applyScaledModifiers(initialDamage, finalDamage, event); applyScaledModifiers(initialDamage, finalDamage, event);
startGainXp(mcMMOPlayer, target, PrimarySkillType.SWORDS); startGainXp(mcMMOPlayer, target, PrimarySkillType.SWORDS);
} }
// public static void strengthDebug(Player player) {
// BukkitPlatform bukkitPlatform = (BukkitPlatform) mcMMO.getPlatformManager().getPlatform();
// Bukkit.broadcastMessage("Strength: "+bukkitPlatform.getPlayerAttackStrength(player));
//
// Bukkit.getScheduler().scheduleSyncDelayedTask(mcMMO.p, () -> {
// Bukkit.broadcastMessage("1 Tick Delay: " + bukkitPlatform.getPlayerAttackStrength(player));
// }, 1);
//
// Bukkit.getScheduler().scheduleSyncDelayedTask(mcMMO.p, () -> {
// Bukkit.broadcastMessage("5 Tick Delay: " + bukkitPlatform.getPlayerAttackStrength(player));
// }, 5);
//
// Bukkit.getScheduler().scheduleSyncDelayedTask(mcMMO.p, () -> {
// Bukkit.broadcastMessage("80 Tick Delay: " + bukkitPlatform.getPlayerAttackStrength(player));
// }, 20 * 4);
//
// Bukkit.broadcastMessage("");
//
//// if(isPlayerFullStrength(player)) {
//// Bukkit.broadcastMessage("Full Strength!");
//// } else {
//// Bukkit.broadcastMessage("Not full strength!");
//// }
// }
private static void processAxeCombat(LivingEntity target, Player player, EntityDamageByEntityEvent event) { private static void processAxeCombat(LivingEntity target, Player player, EntityDamageByEntityEvent event) {
if (event.getCause() == DamageCause.THORNS) { if (event.getCause() == DamageCause.THORNS) {
return; return;
@ -128,12 +153,12 @@ public final class CombatUtils {
} }
if (axesManager.canCriticalHit(target)) { if (axesManager.canCriticalHit(target)) {
finalDamage+=axesManager.criticalHit(target, finalDamage); finalDamage+=(axesManager.criticalHit(target, finalDamage) * mcMMOPlayer.getAttackStrength());
} }
if(canUseLimitBreak(player, target, SubSkillType.AXES_AXES_LIMIT_BREAK)) if(canUseLimitBreak(player, target, SubSkillType.AXES_AXES_LIMIT_BREAK))
{ {
finalDamage+=getLimitBreakDamage(player, target, SubSkillType.AXES_AXES_LIMIT_BREAK); finalDamage+=(getLimitBreakDamage(player, target, SubSkillType.AXES_AXES_LIMIT_BREAK) * mcMMOPlayer.getAttackStrength());
} }
applyScaledModifiers(initialDamage, finalDamage, event); applyScaledModifiers(initialDamage, finalDamage, event);
@ -161,15 +186,13 @@ public final class CombatUtils {
mcMMOPlayer.checkAbilityActivation(PrimarySkillType.UNARMED); mcMMOPlayer.checkAbilityActivation(PrimarySkillType.UNARMED);
} }
if(unarmedManager.isPunchingCooldownOver())
{
//Only execute bonuses if the player is not spamming //Only execute bonuses if the player is not spamming
if (unarmedManager.canUseIronArm()) { if (unarmedManager.canUseIronArm()) {
finalDamage+=unarmedManager.ironArm(); finalDamage+=(unarmedManager.calculateIronArmDamage() * mcMMOPlayer.getAttackStrength());
} }
if (unarmedManager.canUseBerserk()) { if (unarmedManager.canUseBerserk()) {
finalDamage+=unarmedManager.berserkDamage(finalDamage); finalDamage+=(unarmedManager.berserkDamage(finalDamage) * mcMMOPlayer.getAttackStrength());
} }
if (unarmedManager.canDisarm(target)) { if (unarmedManager.canDisarm(target)) {
@ -178,13 +201,11 @@ public final class CombatUtils {
if(canUseLimitBreak(player, target, SubSkillType.UNARMED_UNARMED_LIMIT_BREAK)) if(canUseLimitBreak(player, target, SubSkillType.UNARMED_UNARMED_LIMIT_BREAK))
{ {
finalDamage+=getLimitBreakDamage(player, target, SubSkillType.UNARMED_UNARMED_LIMIT_BREAK); finalDamage+=(getLimitBreakDamage(player, target, SubSkillType.UNARMED_UNARMED_LIMIT_BREAK) * mcMMOPlayer.getAttackStrength());
}
} }
applyScaledModifiers(initialDamage, finalDamage, event); applyScaledModifiers(initialDamage, finalDamage, event);
startGainXp(mcMMOPlayer, target, PrimarySkillType.UNARMED); startGainXp(mcMMOPlayer, target, PrimarySkillType.UNARMED);
unarmedManager.setLastAttacked(System.currentTimeMillis()); //Track how often the player is punching
} }
private static void processTamingCombat(LivingEntity target, Player master, Wolf wolf, EntityDamageByEntityEvent event) { private static void processTamingCombat(LivingEntity target, Player master, Wolf wolf, EntityDamageByEntityEvent event) {
@ -280,9 +301,8 @@ public final class CombatUtils {
* *
* @param event The event to run the combat checks on. * @param event The event to run the combat checks on.
*/ */
public static void processCombatAttack(EntityDamageByEntityEvent event, Entity attacker, LivingEntity target) { public static void processCombatAttack(EntityDamageByEntityEvent event, Entity damageSourceEntity, LivingEntity target) {
Entity damager = event.getDamager(); EntityType entityType = damageSourceEntity.getType();
EntityType entityType = damager.getType();
if (target instanceof Player) { if (target instanceof Player) {
if (Misc.isNPCEntityExcludingVillagers(target)) { if (Misc.isNPCEntityExcludingVillagers(target)) {
@ -298,7 +318,7 @@ public final class CombatUtils {
AcrobaticsManager acrobaticsManager = mcMMOPlayer.getAcrobaticsManager(); AcrobaticsManager acrobaticsManager = mcMMOPlayer.getAcrobaticsManager();
if (acrobaticsManager.canDodge(target)) { if (acrobaticsManager.canDodge(target)) {
event.setDamage(acrobaticsManager.dodgeCheck(attacker, event.getDamage())); event.setDamage(acrobaticsManager.dodgeCheck(damageSourceEntity, event.getDamage()));
} }
if (ItemUtils.isSword(player.getInventory().getItemInMainHand())) { if (ItemUtils.isSword(player.getInventory().getItemInMainHand())) {
@ -308,24 +328,25 @@ public final class CombatUtils {
SwordsManager swordsManager = mcMMOPlayer.getSwordsManager(); SwordsManager swordsManager = mcMMOPlayer.getSwordsManager();
if (swordsManager.canUseCounterAttack(damager)) { if (swordsManager.canUseCounterAttack(damageSourceEntity)) {
swordsManager.counterAttackChecks((LivingEntity) damager, event.getDamage()); swordsManager.counterAttackChecks((LivingEntity) damageSourceEntity, event.getDamage());
} }
} }
} }
if (attacker instanceof Player && entityType == EntityType.PLAYER) { if (damageSourceEntity instanceof Player && entityType == EntityType.PLAYER) {
Player player = (Player) attacker; Player player = (Player) damageSourceEntity;
if (!UserManager.hasPlayerDataKey(player)) { if (UserManager.getPlayer(player) == null) {
return; return;
} }
McMMOPlayer attackingPlayer = UserManager.getPlayer(player);
ItemStack heldItem = player.getInventory().getItemInMainHand(); ItemStack heldItem = player.getInventory().getItemInMainHand();
if (target instanceof Tameable) { if (target instanceof Tameable) {
if (heldItem.getType() == Material.BONE) { if (heldItem.getType() == Material.BONE) {
TamingManager tamingManager = UserManager.getPlayer(player).getTamingManager(); TamingManager tamingManager = attackingPlayer.getTamingManager();
if (tamingManager.canUseBeastLore()) { if (tamingManager.canUseBeastLore()) {
tamingManager.beastLore(target); tamingManager.beastLore(target);
@ -369,7 +390,7 @@ public final class CombatUtils {
} }
else if (entityType == EntityType.WOLF) { else if (entityType == EntityType.WOLF) {
Wolf wolf = (Wolf) damager; Wolf wolf = (Wolf) damageSourceEntity;
AnimalTamer tamer = wolf.getOwner(); AnimalTamer tamer = wolf.getOwner();
if (tamer != null && tamer instanceof Player && PrimarySkillType.TAMING.shouldProcess(target)) { if (tamer != null && tamer instanceof Player && PrimarySkillType.TAMING.shouldProcess(target)) {
@ -381,7 +402,7 @@ public final class CombatUtils {
} }
} }
else if (entityType == EntityType.ARROW || entityType == EntityType.SPECTRAL_ARROW) { else if (entityType == EntityType.ARROW || entityType == EntityType.SPECTRAL_ARROW) {
Projectile arrow = (Projectile) damager; Projectile arrow = (Projectile) damageSourceEntity;
ProjectileSource projectileSource = arrow.getShooter(); ProjectileSource projectileSource = arrow.getShooter();
if (projectileSource != null && projectileSource instanceof Player && PrimarySkillType.ARCHERY.shouldProcess(target)) { if (projectileSource != null && projectileSource instanceof Player && PrimarySkillType.ARCHERY.shouldProcess(target)) {

View File

@ -19,6 +19,8 @@ load: POSTWORLD
api-version: 1.13 api-version: 1.13
commands: commands:
mmocompat:
description: Information about the server and whether or not its considered fully compatible or running in compatibility mode
mmodebug: mmodebug:
aliases: [mcmmodebugmode] aliases: [mcmmodebugmode]
description: Toggles a debug mode which will print useful information to chat description: Toggles a debug mode which will print useful information to chat