Compare commits

...

17 Commits

Author SHA1 Message Date
Prestley 1bcca99c2d
Merge 618dfa94fc into 4d98d25215 2024-04-10 14:00:42 +08:00
nossr50 4d98d25215 Fix wiki links being outdated 2024-04-06 15:10:02 -07:00
nossr50 ffc6061f8b Add custom_item_support config file to optionally disable repair/salvage on items with custom models 2024-04-06 14:26:57 -07:00
nossr50 0363ee2e90 2.2.005 2024-04-06 12:59:35 -07:00
nossr50 b6e512b09e fix probability being unbounded 2024-04-06 12:44:56 -07:00
nossr50 aecf17a2a2 dev mode 2024-04-02 13:30:09 -07:00
nossr50 c769813892 2.2.004 2024-04-02 13:25:35 -07:00
nossr50 e509876658 fix xp multiplier not getting applied 2024-04-01 15:13:49 -07:00
nossr50 a047bca94c Fix crossbows not getting added to schema for some users 2024-03-31 14:28:14 -07:00
nossr50 b5a50da09b Merge branch 'master' of https://github.com/mcMMO-Dev/mcMMO 2024-03-31 13:55:08 -07:00
nossr50 2d79b364db Fix lastlogin value being too large for SQLDB 2024-03-31 13:54:57 -07:00
Robert Alan Chapton 657d7cafa7
Update README.md 2024-03-31 10:41:34 -07:00
nossr50 d92c60fc84 2.2.002 2024-03-31 08:50:05 -07:00
nossr50 b56ddebde8 fix trickshot not applying shot from crossbow to bounced arrows 2024-03-31 08:46:04 -07:00
nossr50 cf49fc7599 fix thrown tridents not getting XP or subskill benefits 2024-03-31 08:44:06 -07:00
cj89898 618dfa94fc Cleaned up formatting 2023-06-21 16:43:54 -05:00
cj89898 6fd4dd1403 Cleaned up PAPI Expansion 2023-06-19 20:02:11 -05:00
45 changed files with 744 additions and 988 deletions

View File

@ -1,3 +1,39 @@
Version 2.2.006
Updated outdated wiki URLs in commands to point to the new wiki
Removed the msg about skills being migrated to a new system when using /mmoinfo command
Added new config custom_item_support.yml
Added setting to disable repair on items with custom models, this is not on by default
Added new locale entry 'Anvil.Repair.Reject.CustomModelData'
Added new locale entry 'Anvil.Salvage.Reject.CustomModelData'
NOTES:
Let me know in detail what kind of support you'd like to see in mcMMO regarding custom items, I'm open to suggestions.
This update adds a new config file to allow server owners to disable repair or salvage on items with custom models,
This prevention mechanism is not enabled by default, change the settings in custom_item_support.yml if you want to enable it.
This feature is off by default for now to keep compatibility with existing servers, but it may be enabled by default in the future if feedback suggests it should be.
As a reminder, anyone can update the wiki by clicking on the "edit on github" link on various pages, this will take you to the wiki's source code on GitHub, submit a PR to make changes
Version 2.2.005
Fixed a bug where certain skills such as Dodge/Arrow Deflect had no skill cap and would continue improving forever
Reduced messages on startup for SQL DB
(API) Constructor for ProbabilityImpl now takes a raw value between 0 and 1 instead of an inflated percentage
(API) Added some convenience methods to Probability, and ProbabilityUtil classes
(Codebase) Added more unit tests revolving around Probability/RNG
Version 2.2.004
Fixed bug where values from Experience_Formula.Skill_Multiplier were not functioning
NOTES:
A reminder that these values are multipliers and no longer divisors, if you want 10x lower XP, a value of .1 would do the job.
Version 2.2.003
(SQLDB) Fixed a bug where lastlogin was using a value that was too large
(SQLDB) Fixed bug where crossbows was not getting added to SQL schema for some users
Version 2.2.002
Fixed bug where thrown tridents did not grant XP or benefit from subskills
Fixed bug where trickshot marked bounced arrows as being shot from a bow instead of being shot from a crossbow
Version 2.2.001 Version 2.2.001
Fixed Crossbow's Powered shot showing the text for the wrong skill from the locale when using /crossbows command Fixed Crossbow's Powered shot showing the text for the wrong skill from the locale when using /crossbows command

View File

@ -10,6 +10,22 @@ Spigot Resource: https://spigot.mcmmo.org
I plan to post links to our new wiki (its still under development), downloads, and dev blogs there. I plan to post links to our new wiki (its still under development), downloads, and dev blogs there.
## API
If you are using maven, you can add mcMMO API to your plugin by adding it to pom.xml like so...
```
<repository>
<id>neetgames</id>
<url>https://nexus.neetgames.com/repository/maven-releases/</url>
</repository>
```
```
<dependency>
<groupId>com.gmail.nossr50.mcMMO</groupId>
<artifactId>mcMMO</artifactId>
<version>2.2.004</version>
</dependency>
```
### Builds ### Builds
Currently, you can obtain our builds via the Spigot or Polymart: Currently, you can obtain our builds via the Spigot or Polymart:

View File

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.gmail.nossr50.mcMMO</groupId> <groupId>com.gmail.nossr50.mcMMO</groupId>
<artifactId>mcMMO</artifactId> <artifactId>mcMMO</artifactId>
<version>2.2.001</version> <version>2.2.006-SNAPSHOT</version>
<name>mcMMO</name> <name>mcMMO</name>
<url>https://github.com/mcMMO-Dev/mcMMO</url> <url>https://github.com/mcMMO-Dev/mcMMO</url>
<scm> <scm>

View File

@ -1,7 +1,6 @@
package com.gmail.nossr50.commands.skills; package com.gmail.nossr50.commands.skills;
import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.datatypes.skills.SubSkillType;
import com.gmail.nossr50.listeners.InteractionManager;
import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.Permissions;
@ -29,14 +28,11 @@ public class MmoInfoCommand implements TabExecutor {
*/ */
if(commandSender instanceof Player player) if(commandSender instanceof Player player)
{ {
if(args.length < 1) if(args == null || args.length < 1 || args[0] == null || args[0].isEmpty())
return false; return false;
if(Permissions.mmoinfo(player)) if(Permissions.mmoinfo(player))
{ {
if(args == null || args[0] == null)
return false;
if(args[0].equalsIgnoreCase( "???")) if(args[0].equalsIgnoreCase( "???"))
{ {
player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.Header")); player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.Header"));
@ -44,14 +40,15 @@ public class MmoInfoCommand implements TabExecutor {
player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.DetailsHeader")); player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.DetailsHeader"));
player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.Mystery")); player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.Mystery"));
return true; return true;
} else if(InteractionManager.getAbstractByName(args[0]) != null || mcMMO.p.getSkillTools().EXACT_SUBSKILL_NAMES.contains(args[0]))
{
displayInfo(player, args[0]);
return true;
} }
//Not a real skill final SubSkillType subSkillType = matchSubSkill(args[0]);
player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.NoMatch")); if (subSkillType != null) {
displayInfo(player, subSkillType);
} else {
//Not a real skill
player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.NoMatch"));
}
return true; return true;
} }
} }
@ -59,6 +56,16 @@ public class MmoInfoCommand implements TabExecutor {
return false; return false;
} }
public SubSkillType matchSubSkill(String name) {
for(SubSkillType subSkillType : SubSkillType.values())
{
if(subSkillType.getNiceNameNoSpaces(subSkillType).equalsIgnoreCase(name)
|| subSkillType.name().equalsIgnoreCase(name))
return subSkillType;
}
return null;
}
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
if (args.length == 1) { if (args.length == 1) {
@ -67,20 +74,13 @@ public class MmoInfoCommand implements TabExecutor {
return ImmutableList.of(); return ImmutableList.of();
} }
private void displayInfo(Player player, String subSkillName) private void displayInfo(Player player, SubSkillType subSkillType)
{ {
player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.Header")); player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.Header"));
player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.SubSkillHeader", subSkillName)); player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.SubSkillHeader", subSkillType.getLocaleName()));
player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.DetailsHeader")); player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.DetailsHeader"));
player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.OldSkill"));
for(SubSkillType subSkillType : SubSkillType.values())
{
if(subSkillType.getNiceNameNoSpaces(subSkillType).equalsIgnoreCase(subSkillName))
subSkillName = subSkillType.getWikiName(subSkillType.toString());
}
//Send Player Wiki Link //Send Player Wiki Link
TextComponentFactory.sendPlayerSubSkillWikiLink(player, subSkillName); TextComponentFactory.sendPlayerSubSkillWikiLink(player, subSkillType.getLocaleName(), subSkillType);
} }
} }

View File

@ -0,0 +1,23 @@
package com.gmail.nossr50.config;
import java.io.File;
public class CustomItemSupportConfig extends BukkitConfig {
public CustomItemSupportConfig(File dataFolder) {
super("custom_item_support.yml", dataFolder);
validate();
}
@Override
protected void loadKeys() {
}
public boolean isCustomRepairAllowed() {
return config.getBoolean("Custom_Item_Support.Repair.Allow_Repair_On_Items_With_Custom_Model_Data", true);
}
public boolean isCustomSalvageAllowed() {
return config.getBoolean("Custom_Item_Support.Salvage.Allow_Salvage_On_Items_With_Custom_Model_Data", true);
}
}

View File

@ -260,7 +260,7 @@ public class ExperienceConfig extends BukkitConfig {
/* Skill modifiers */ /* Skill modifiers */
public double getFormulaSkillModifier(PrimarySkillType skill) { public double getFormulaSkillModifier(PrimarySkillType skill) {
return config.getDouble("Experience_Formula.Modifier." + StringUtils.getCapitalized(skill.toString()), 1); return config.getDouble("Experience_Formula.Skill_Multiplier." + StringUtils.getCapitalized(skill.toString()), 1);
} }
/* Custom XP perk */ /* Custom XP perk */

View File

@ -578,13 +578,9 @@ public final class SQLDatabaseManager implements DatabaseManager {
statement.executeUpdate(); statement.executeUpdate();
statement.close(); statement.close();
long currentTimeMillis = System.currentTimeMillis(); statement = connection.prepareStatement("INSERT INTO " + tablePrefix + "users (user, uuid, lastlogin) VALUES (?, ?, UNIX_TIMESTAMP())", Statement.RETURN_GENERATED_KEYS);
String sql = "INSERT INTO " + tablePrefix + "users (`user`, uuid, lastlogin) VALUES (?, ?, ?)";
statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
statement.setString(1, playerName); statement.setString(1, playerName);
statement.setString(2, uuid != null ? uuid.toString() : null); statement.setString(2, uuid != null ? uuid.toString() : null);
statement.setLong(3, currentTimeMillis);
statement.executeUpdate(); statement.executeUpdate();
resultSet = statement.getGeneratedKeys(); resultSet = statement.getGeneratedKeys();
@ -1038,19 +1034,41 @@ public final class SQLDatabaseManager implements DatabaseManager {
} }
private void updateStructure(String tableName, String columnName, String columnSize) { private void updateStructure(String tableName, String columnName, String columnSize) {
try (Connection connection = getConnection(PoolIdentifier.MISC); try (Connection connection = getConnection(PoolIdentifier.MISC)) {
Statement createStatement = connection.createStatement()) { if (!columnExists(connection, mcMMO.p.getGeneralConfig().getMySQLDatabaseName(), tablePrefix+tableName, columnName)) {
try (Statement createStatement = connection.createStatement()) {
String startingLevel = "'" + mcMMO.p.getAdvancedConfig().getStartingLevel() + "'"; // logger.info("[SQLDB Check] Adding column '" + columnName + "' to table '" + tablePrefix + tableName + "'...");
createStatement.executeUpdate("ALTER TABLE `" + tablePrefix + tableName + "` " String startingLevel = "'" + mcMMO.p.getAdvancedConfig().getStartingLevel() + "'";
+ "ADD COLUMN IF NOT EXISTS `" + columnName + "` int(" + columnSize + ") unsigned NOT NULL DEFAULT " + startingLevel); createStatement.executeUpdate("ALTER TABLE `" + tablePrefix + tableName + "` "
+ "ADD COLUMN `" + columnName + "` int(" + columnSize + ") unsigned NOT NULL DEFAULT " + startingLevel);
}
} else {
// logger.info("[SQLDB Check] Column '" + columnName + "' already exists in table '" + tablePrefix + tableName + "', looks good!");
}
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); // Consider more robust logging e.printStackTrace(); // Consider more robust logging
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
private boolean columnExists(Connection connection, String database, String tableName, String columnName) throws SQLException {
// logger.info("[SQLDB Check] Checking if column '" + columnName + "' exists in table '" + tableName + "'");
try (Statement createStatement = connection.createStatement()) {
String sql = "SELECT `COLUMN_NAME`\n" +
"FROM `INFORMATION_SCHEMA`.`COLUMNS`\n" +
"WHERE `TABLE_SCHEMA`='" + database + "'\n" +
" AND `TABLE_NAME`='" + tableName + "'\n" +
" AND `COLUMN_NAME`='" + columnName + "'";
var resultSet = createStatement.executeQuery(sql);
return resultSet.next();
} catch (SQLException e) {
logger.info("Failed to check if column exists in table " + tableName + " for column " + columnName);
e.printStackTrace();
throw e;
}
}
private void setStatementQuery(PreparedStatement statement, String tableName) throws SQLException { private void setStatementQuery(PreparedStatement statement, String tableName) throws SQLException {
if (!this.h2) { if (!this.h2) {
// Set schema name for MySQL // Set schema name for MySQL

View File

@ -65,6 +65,7 @@ import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.Map; import java.util.Map;
@ -840,14 +841,15 @@ public class McMMOPlayer implements Identified {
* @param xp Experience amount to process * @param xp Experience amount to process
* @return Modified experience * @return Modified experience
*/ */
private float modifyXpGain(PrimarySkillType primarySkillType, float xp) { @VisibleForTesting
float modifyXpGain(PrimarySkillType primarySkillType, float xp) {
//TODO: A rare situation can occur where the default Power Level cap can prevent a player with one skill edited to something silly like Integer.MAX_VALUE from gaining XP in any skill, we may need to represent power level with another data type //TODO: A rare situation can occur where the default Power Level cap can prevent a player with one skill edited to something silly like Integer.MAX_VALUE from gaining XP in any skill, we may need to represent power level with another data type
if ((mcMMO.p.getSkillTools().getLevelCap(primarySkillType) <= getSkillLevel(primarySkillType)) if ((mcMMO.p.getSkillTools().getLevelCap(primarySkillType) <= getSkillLevel(primarySkillType))
|| (mcMMO.p.getGeneralConfig().getPowerLevelCap() <= getPowerLevel())) { || (mcMMO.p.getGeneralConfig().getPowerLevelCap() <= getPowerLevel())) {
return 0; return 0;
} }
xp = (float) (xp * ExperienceConfig.getInstance().getFormulaSkillModifier(primarySkillType) * ExperienceConfig.getInstance().getExperienceGainsGlobalMultiplier()); xp = (float) ((xp * ExperienceConfig.getInstance().getFormulaSkillModifier(primarySkillType)) * ExperienceConfig.getInstance().getExperienceGainsGlobalMultiplier());
if (mcMMO.p.getGeneralConfig().getToolModsEnabled()) { if (mcMMO.p.getGeneralConfig().getToolModsEnabled()) {
CustomTool tool = mcMMO.getModManager().getTool(player.getInventory().getItemInMainHand()); CustomTool tool = mcMMO.getModManager().getTool(player.getInventory().getItemInMainHand());

View File

@ -132,12 +132,12 @@ public enum PrimarySkillType {
/** /**
* WARNING: Being removed in an upcoming update, you should be using mcMMO.getSkillTools() instead * WARNING: Being removed in an upcoming update, you should be using mcMMO.getSkillTools() instead
* @return the max level of this skill * @return the max level of this skill
* @see SkillTools#getXpModifier(com.gmail.nossr50.datatypes.skills.PrimarySkillType) * @see SkillTools#getXpMultiplier(com.gmail.nossr50.datatypes.skills.PrimarySkillType)
* @deprecated this is being removed in an upcoming update, you should be using mcMMO.getSkillTools() instead * @deprecated this is being removed in an upcoming update, you should be using mcMMO.getSkillTools() instead
*/ */
@Deprecated @Deprecated
public double getXpModifier() { public double getXpModifier() {
return mcMMO.p.getSkillTools().getXpModifier(this); return mcMMO.p.getSkillTools().getXpMultiplier(this);
} }
/** /**

View File

@ -213,35 +213,12 @@ public enum SubSkillType {
return endResult.toString(); return endResult.toString();
} }
public String getWikiName(String subSkillName) { public String getWikiUrl() {
/* // remove the text before the first underscore
* Find where to begin our substring (after the prefix) int subStringIndex = getSubStringIndex(name());
*/ String afterPrefix = name().substring(subStringIndex);
StringBuilder endResult = new StringBuilder(); // replace _ or spaces with -
int subStringIndex = getSubStringIndex(subSkillName); return afterPrefix.replace("_", "-").replace(" ", "-").toLowerCase(Locale.ENGLISH);
/*
* Split the string up so we can capitalize each part
*/
String subskillNameWithoutPrefix = subSkillName.substring(subStringIndex);
if(subskillNameWithoutPrefix.contains("_"))
{
String[] splitStrings = subskillNameWithoutPrefix.split("_");
for(int i = 0; i < splitStrings.length; i++)
{
if(i+1 >= splitStrings.length)
endResult.append(StringUtils.getCapitalized(splitStrings[i]));
else {
endResult.append(StringUtils.getCapitalized(splitStrings[i]));
endResult.append("_");
}
}
} else {
endResult.append(StringUtils.getCapitalized(subskillNameWithoutPrefix));
}
return endResult.toString();
} }
/** /**

View File

@ -140,6 +140,7 @@ public class mcMMO extends JavaPlugin {
private GeneralConfig generalConfig; private GeneralConfig generalConfig;
private AdvancedConfig advancedConfig; private AdvancedConfig advancedConfig;
private PartyConfig partyConfig; private PartyConfig partyConfig;
private CustomItemSupportConfig customItemSupportConfig;
private FoliaLib foliaLib; private FoliaLib foliaLib;
private PartyManager partyManager; private PartyManager partyManager;
@ -185,6 +186,7 @@ public class mcMMO extends JavaPlugin {
//Init configs //Init configs
advancedConfig = new AdvancedConfig(getDataFolder()); advancedConfig = new AdvancedConfig(getDataFolder());
partyConfig = new PartyConfig(getDataFolder()); partyConfig = new PartyConfig(getDataFolder());
customItemSupportConfig = new CustomItemSupportConfig(getDataFolder());
//Store this value so other plugins can check it //Store this value so other plugins can check it
isRetroModeEnabled = generalConfig.getIsRetroMode(); isRetroModeEnabled = generalConfig.getIsRetroMode();
@ -806,6 +808,10 @@ public class mcMMO extends JavaPlugin {
return partyManager; return partyManager;
} }
public CustomItemSupportConfig getCustomItemSupportConfig() {
return customItemSupportConfig;
}
public @NotNull FoliaLib getFoliaLib() { public @NotNull FoliaLib getFoliaLib() {
return foliaLib; return foliaLib;
} }

View File

@ -15,14 +15,26 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.TreeMap;
public class PapiExpansion extends PlaceholderExpansion { public class PapiExpansion extends PlaceholderExpansion {
private final Map<String, Placeholder> placeholders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
public static final String SKILL_LEVEL = "level_";
public static final String SKILL_EXP_NEEDED = "xp_needed_";
public static final String SKILL_EXP_REMAINING = "xp_remaining_";
public static final String SKILL_EXP = "xp_";
public static final String SKILL_RANK = "rank_";
public static final String SKILL_EXP_RATE = "xprate_";
public static final String POWER_LEVEL = "power_level";
public static final String POWER_LEVEL_CAP = "power_level_cap";
public static final String IN_PARTY = "in_party";
public static final String PARTY_NAME = "party_name";
public static final String IS_PARTY_LEADER = "is_party_leader";
public static final String PARTY_LEADER = "party_leader";
public static final String PARTY_SIZE = "party_size";
public static final String EXP_RATE = "xprate";
public static final String IS_EXP_EVENT_ACTIVE = "is_xp_event_active";
public PapiExpansion() { public PapiExpansion() {
init();
} }
@Override @Override
@ -37,8 +49,7 @@ public class PapiExpansion extends PlaceholderExpansion {
@Override @Override
public String getVersion() { public String getVersion() {
//grab version from pom.xml return mcMMO.p.getDescription().getVersion();
return "1.0,0";
} }
@Override @Override
@ -54,105 +65,41 @@ public class PapiExpansion extends PlaceholderExpansion {
@Override @Override
@Nullable @Nullable
public String onPlaceholderRequest(final Player player, @NotNull final String params) { public String onPlaceholderRequest(final Player player, @NotNull final String params) {
String token;
String data = null; // Non player-specific placeholders
int dataPosition = params.indexOf(":"); if (params.equalsIgnoreCase(IS_EXP_EVENT_ACTIVE)) {
return mcMMO.p.isXPEventEnabled() ? PlaceholderAPIPlugin.booleanTrue() : PlaceholderAPIPlugin.booleanFalse();
if (dataPosition != -1) { } else if (params.equalsIgnoreCase(EXP_RATE)) {
token = params.substring(0, dataPosition);
data = params.substring(dataPosition + 1);
} else {
token = params;
}
Placeholder placeholder = placeholders.get(token);
if (placeholder != null) {
return placeholder.process(player, data);
} else {
return null;
}
}
public Integer getSkillLevel(PrimarySkillType skill, Player player) {
final McMMOPlayer user = UserManager.getPlayer(player);
if (user == null) return null;
return user.getSkillLevel(skill);
}
public Integer getExpNeeded(PrimarySkillType skill, Player player) {
final McMMOPlayer user = UserManager.getPlayer(player);
if (user == null) return null;
return user.getXpToLevel(skill);
}
public Integer getExp(PrimarySkillType skill, Player player) {
final McMMOPlayer user = UserManager.getPlayer(player);
if (user == null) return null;
return user.getSkillXpLevel(skill);
}
public Integer getExpRemaining(PrimarySkillType skill, Player player) {
final McMMOPlayer user = UserManager.getPlayer(player);
if (user == null) return null;
int current = user.getSkillXpLevel(skill);
int needed = user.getXpToLevel(skill);
return needed - current;
}
public Integer getRank(PrimarySkillType skill, Player player) {
try {
return ExperienceAPI.getPlayerRankSkill(player.getUniqueId(), StringUtils.getCapitalized(skill.toString()));
} catch (Exception ex) {
return null;
}
}
public Integer getPowerLevel(Player player) {
final McMMOPlayer user = UserManager.getPlayer(player);
if (user == null) return null;
return user.getPowerLevel();
}
public Integer getPowerCap(Player player) {
return mcMMO.p.getGeneralConfig().getPowerLevelCap();
}
public String getPartyName(Player player) {
final McMMOPlayer user = UserManager.getPlayer(player);
if (user == null) return null;
final Party party = user.getParty();
return (party == null) ? null : party.getName();
}
public String getPartyLeader(Player player) {
final McMMOPlayer user = UserManager.getPlayer(player);
if (user == null) return null;
final Party party = user.getParty();
return (party == null) ? null : party.getLeader().getPlayerName();
}
public Integer getPartySize(Player player) {
final McMMOPlayer user = UserManager.getPlayer(player);
if (user == null) return null;
final Party party = user.getParty();
return (party == null) ? null : party.getMembers().size();
}
public String getXpRate(Player player) {
return String.valueOf(ExperienceConfig.getInstance().getExperienceGainsGlobalMultiplier()); return String.valueOf(ExperienceConfig.getInstance().getExperienceGainsGlobalMultiplier());
} } else if (params.equalsIgnoreCase(POWER_LEVEL_CAP)) {
return String.valueOf(mcMMO.p.getGeneralConfig().getPowerLevelCap());
public String getSkillXpRate(PrimarySkillType skill, Player player) { }
final McMMOPlayer user = UserManager.getPlayer(player);
if (user == null) return null; final McMMOPlayer user = UserManager.getPlayer(player);
if (user == null) return null;
if (params.startsWith(SKILL_LEVEL)) {
PrimarySkillType skill = PrimarySkillType.valueOf(params.substring(SKILL_LEVEL.length()).toUpperCase());
return skill == null ? null : String.valueOf(user.getSkillLevel(skill));
} else if (params.startsWith(SKILL_EXP_NEEDED)) {
PrimarySkillType skill = PrimarySkillType.valueOf(params.substring(SKILL_EXP_NEEDED.length()).toUpperCase());
return skill == null ? null : String.valueOf(user.getXpToLevel(skill));
} else if (params.startsWith(SKILL_EXP_REMAINING)) {
PrimarySkillType skill = PrimarySkillType.valueOf(params.substring(SKILL_EXP_REMAINING.length()).toUpperCase());
return skill == null ? null : String.valueOf(user.getXpToLevel(skill) - user.getSkillXpLevel(skill));
} else if (params.startsWith(SKILL_EXP)) {
PrimarySkillType skill = PrimarySkillType.valueOf(params.substring(SKILL_EXP.length()).toUpperCase());
return skill == null ? null : String.valueOf(user.getSkillXpLevel(skill));
} else if (params.startsWith(SKILL_RANK)) {
try {
return String.valueOf(ExperienceAPI.getPlayerRankSkill(player.getUniqueId(), StringUtils.getCapitalized(params.substring(SKILL_RANK.length()))));
} catch (Exception ex) {
return null;
}
} else if (params.startsWith(SKILL_EXP_RATE)) {
PrimarySkillType skill = PrimarySkillType.valueOf(params.substring(SKILL_EXP_RATE.length()).toUpperCase());
if (skill == null) return null;
double modifier = 1.0F; double modifier = 1.0F;
if (Permissions.customXpBoost(player, skill)) if (Permissions.customXpBoost(player, skill))
modifier = ExperienceConfig.getInstance().getCustomXpPerkBoost(); modifier = ExperienceConfig.getInstance().getCustomXpPerkBoost();
else if (Permissions.quadrupleXp(player, skill)) else if (Permissions.quadrupleXp(player, skill))
@ -167,69 +114,29 @@ public class PapiExpansion extends PlaceholderExpansion {
modifier = 1.5; modifier = 1.5;
else if (Permissions.oneAndOneTenthXp(player, skill)) else if (Permissions.oneAndOneTenthXp(player, skill))
modifier = 1.1; modifier = 1.1;
return String.valueOf(modifier); return String.valueOf(modifier);
} else if (params.equalsIgnoreCase(POWER_LEVEL)) {
return String.valueOf(user.getPowerLevel());
}
//Party placeholders
final Party party = user.getParty();
if (params.equalsIgnoreCase(IN_PARTY)) {
return (party==null) ? PlaceholderAPIPlugin.booleanFalse() : PlaceholderAPIPlugin.booleanTrue();
} else if (params.equalsIgnoreCase(PARTY_NAME)) {
return (party == null) ? "" : party.getName();
} else if (params.equalsIgnoreCase(IS_PARTY_LEADER)) {
if (party == null) return "";
return party.getLeader().getPlayerName().equals(player.getName()) ? PlaceholderAPIPlugin.booleanTrue() : PlaceholderAPIPlugin.booleanFalse();
} else if (params.equalsIgnoreCase(PARTY_LEADER)) {
return (party == null) ? "" : party.getLeader().getPlayerName();
} else if (params.equalsIgnoreCase(PARTY_SIZE)) {
return (party == null) ? "" : String.valueOf(party.getMembers().size());
}
return null;
} }
public String isExpEventActive(Player player) {
return mcMMO.p.isXPEventEnabled() ? PlaceholderAPIPlugin.booleanTrue() : PlaceholderAPIPlugin.booleanFalse();
}
public void registerPlaceholder(Placeholder placeholder) {
final Placeholder registered = placeholders.get(placeholder.getName());
if (registered != null)
throw new IllegalStateException("Placeholder " + placeholder.getName() + " is already registered!");
placeholders.put(placeholder.getName(), placeholder);
}
protected void init() {
for (PrimarySkillType skill : PrimarySkillType.values()) {
// %mcmmo_level_<skillname>%
registerPlaceholder(new SkillLevelPlaceholder(this, skill));
//%mcmmo_xp_needed_<skillname>%
registerPlaceholder(new SkillExpNeededPlaceholder(this, skill));
//%mcmmo_xp_<skillname>%
registerPlaceholder(new SkillExpPlaceholder(this, skill));
//%mcmmo_xp_remaining_<skillname>%
registerPlaceholder(new SkillExpRemainingPlaceholder(this, skill));
//%mcmmo_rank_<skillname>%
registerPlaceholder(new SkillRankPlaceholder(this, skill));
//%mcmmo_xprate_<skillname>%
registerPlaceholder(new SkillXpRatePlaceholder(this, skill));
}
//%mcmmo_power_level%
registerPlaceholder(new PowerLevelPlaceholder(this));
// %mcmmo_power_level_cap%
registerPlaceholder(new PowerLevelCapPlaceholder(this));
// %mcmmo_in_party%
registerPlaceholder(new PartyIsMemberPlaceholder(this));
/// %mcmmo_party_name%
registerPlaceholder(new PartyNamePlaceholder(this));
// %mcmmo_is_party_leader%
registerPlaceholder(new PartyIsLeaderPlaceholder(this));
// %mcmmo_party_leader%
registerPlaceholder(new PartyLeaderPlaceholder(this));
// %mcmmo_party_size%
registerPlaceholder(new PartySizePlaceholder(this));
// %mcmmo_is_xp_event_active%
registerPlaceholder(new XpEventActivePlaceholder(this));
// %mcmmo_xprate%
registerPlaceholder(new XpRatePlaceholder(this));
};
} }

View File

@ -1,29 +0,0 @@
package com.gmail.nossr50.placeholders;
import org.bukkit.entity.Player;
public class PartyIsLeaderPlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
public PartyIsLeaderPlaceholder(PapiExpansion papiExpansion) {
this.papiExpansion = papiExpansion;
}
/**
* {@inheritDoc}
*/
@Override
public String process(Player player, String params) {
String leader = papiExpansion.getPartyLeader(player);
return (leader.equals(player.getName())) ? "true" : "false";
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return "is_party_leader";
}
}

View File

@ -1,21 +0,0 @@
package com.gmail.nossr50.placeholders;
import org.bukkit.entity.Player;
public class PartyIsMemberPlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
public PartyIsMemberPlaceholder(PapiExpansion papiExpansion) {
this.papiExpansion = papiExpansion;
}
@Override
public String process(Player player, String params) {
return (papiExpansion.getPartyName(player) == null) ? "false" : "true";
}
@Override
public String getName() {
return "in_party";
}
}

View File

@ -1,22 +0,0 @@
package com.gmail.nossr50.placeholders;
import org.apache.commons.lang.StringUtils;
import org.bukkit.entity.Player;
public class PartyLeaderPlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
public PartyLeaderPlaceholder(PapiExpansion papiExpansion) {
this.papiExpansion = papiExpansion;
}
@Override
public String process(Player player, String params) {
return StringUtils.stripToEmpty(papiExpansion.getPartyLeader(player));
}
@Override
public String getName() {
return "party_leader";
}
}

View File

@ -1,22 +0,0 @@
package com.gmail.nossr50.placeholders;
import org.apache.commons.lang.StringUtils;
import org.bukkit.entity.Player;
public class PartyNamePlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
public PartyNamePlaceholder(PapiExpansion papiExpansion) {
this.papiExpansion = papiExpansion;
}
@Override
public String process(Player player, String params) {
return StringUtils.stripToEmpty(papiExpansion.getPartyName(player));
}
@Override
public String getName() {
return "party_name";
}
}

View File

@ -1,22 +0,0 @@
package com.gmail.nossr50.placeholders;
import org.bukkit.entity.Player;
public class PartySizePlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
public PartySizePlaceholder(PapiExpansion papiExpansion) {
this.papiExpansion = papiExpansion;
}
@Override
public String process(Player player, String params) {
Integer partySize = papiExpansion.getPartySize(player);
return (partySize == null) ? "" : partySize.toString();
}
@Override
public String getName() {
return "party_size";
}
}

View File

@ -1,18 +0,0 @@
package com.gmail.nossr50.placeholders;
import org.bukkit.entity.Player;
public interface Placeholder {
/**
* @param player the player to process the placeholder for
* @param params the paramaters to be passed to the placeholder
* @return the value of the placeholder
*/
String process(Player player, String params);
/**
* @return the name of the placeholder
*/
String getName();
}

View File

@ -1,22 +0,0 @@
package com.gmail.nossr50.placeholders;
import org.bukkit.entity.Player;
public class PowerLevelCapPlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
public PowerLevelCapPlaceholder(PapiExpansion papiExpansion) {
this.papiExpansion = papiExpansion;
}
@Override
public String process(Player player, String params) {
Integer cap = papiExpansion.getPowerCap(player);
return (cap == null) ? "" : cap.toString();
}
@Override
public String getName() {
return "power_level_cap";
}
}

View File

@ -1,22 +0,0 @@
package com.gmail.nossr50.placeholders;
import org.bukkit.entity.Player;
public class PowerLevelPlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
public PowerLevelPlaceholder(PapiExpansion papiExpansion) {
this.papiExpansion = papiExpansion;
}
@Override
public String process(Player player, String params) {
Integer powerLevel = papiExpansion.getPowerLevel(player);
return (powerLevel == null) ? "" : powerLevel.toString();
}
@Override
public String getName() {
return "power_level";
}
}

View File

@ -1,26 +0,0 @@
package com.gmail.nossr50.placeholders;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import org.bukkit.entity.Player;
public class SkillExpNeededPlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
private final PrimarySkillType skillType;
public SkillExpNeededPlaceholder(PapiExpansion papiExpansion, PrimarySkillType skillType) {
this.papiExpansion = papiExpansion;
this.skillType = skillType;
}
@Override
public String process(Player player, String params) {
final Integer expNeeded = papiExpansion.getExpNeeded(skillType, player);
return (expNeeded == null) ? "" : expNeeded.toString();
}
@Override
public String getName() {
return "xp_needed_" + skillType.toString().toLowerCase();
}
}

View File

@ -1,26 +0,0 @@
package com.gmail.nossr50.placeholders;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import org.bukkit.entity.Player;
public class SkillExpPlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
private final PrimarySkillType skill;
public SkillExpPlaceholder(PapiExpansion papiExpansion, PrimarySkillType skill) {
this.papiExpansion = papiExpansion;
this.skill = skill;
}
@Override
public String process(Player player, String params) {
Integer exp = papiExpansion.getExp(skill, player);
return (exp == null) ? "" : exp.toString();
}
@Override
public String getName() {
return "xp_" + skill.toString().toLowerCase();
}
}

View File

@ -1,25 +0,0 @@
package com.gmail.nossr50.placeholders;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import org.bukkit.entity.Player;
public class SkillExpRemainingPlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
private final PrimarySkillType skill;
public SkillExpRemainingPlaceholder(PapiExpansion papiExpansion, PrimarySkillType skill) {
this.papiExpansion = papiExpansion;
this.skill = skill;
}
@Override
public String process(Player player, String params) {
final Integer expRemaining = papiExpansion.getExpRemaining(skill, player);
return (expRemaining == null) ? "" : expRemaining.toString();
}
@Override
public String getName() {
return "xp_remaining_" + skill.toString().toLowerCase();
}
}

View File

@ -1,25 +0,0 @@
package com.gmail.nossr50.placeholders;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import org.bukkit.entity.Player;
public class SkillLevelPlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
private final PrimarySkillType skillType;
public SkillLevelPlaceholder(PapiExpansion papiExpansion, PrimarySkillType skillType) {
this.papiExpansion = papiExpansion;
this.skillType = skillType;
}
@Override
public String process(Player p, String params) {
final Integer skillLevel = papiExpansion.getSkillLevel(skillType, p);
return (skillLevel == null) ? "" : skillLevel.toString();
}
@Override
public String getName() {
return "level_" + skillType.toString().toLowerCase();
}
}

View File

@ -1,25 +0,0 @@
package com.gmail.nossr50.placeholders;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import org.bukkit.entity.Player;
public class SkillRankPlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
private final PrimarySkillType skill;
public SkillRankPlaceholder(PapiExpansion papiExpansion, PrimarySkillType skill) {
this.papiExpansion = papiExpansion;
this.skill = skill;
}
@Override
public String process(Player player, String params) {
Integer rank = papiExpansion.getRank(skill, player);
return (rank == null) ? "" : rank.toString();
}
@Override
public String getName() {
return "rank_" + skill.toString().toLowerCase();
}
}

View File

@ -1,25 +0,0 @@
package com.gmail.nossr50.placeholders;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import org.bukkit.entity.Player;
public class SkillXpRatePlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
private final PrimarySkillType skillType;
public SkillXpRatePlaceholder(PapiExpansion papiExpansion, PrimarySkillType skillType) {
this.papiExpansion = papiExpansion;
this.skillType = skillType;
}
@Override
public String process(Player p, String params) {
final String skillLevel = papiExpansion.getSkillXpRate(skillType, p);
return (skillLevel == null) ? "" : skillLevel;
}
@Override
public String getName() {
return "xprate_" + skillType.toString().toLowerCase();
}
}

View File

@ -1,22 +0,0 @@
package com.gmail.nossr50.placeholders;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import org.bukkit.entity.Player;
public class XpEventActivePlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
public <S extends PrimarySkillType> XpEventActivePlaceholder(PapiExpansion papiExpansion) {
this.papiExpansion = papiExpansion;
}
@Override
public String process(Player player, String params) {
return papiExpansion.isExpEventActive(player);
}
@Override
public String getName() {
return "is_xp_event_active";
}
}

View File

@ -1,22 +0,0 @@
package com.gmail.nossr50.placeholders;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import org.bukkit.entity.Player;
public class XpRatePlaceholder implements Placeholder {
private final PapiExpansion papiExpansion;
public <S extends PrimarySkillType> XpRatePlaceholder(PapiExpansion papiExpansion) {
this.papiExpansion = papiExpansion;
}
@Override
public String process(Player player, String params) {
return papiExpansion.getXpRate(player);
}
@Override
public String getName() {
return "xprate";
}
}

View File

@ -72,6 +72,7 @@ public class CrossbowsManager extends SkillManager {
new FixedMetadataValue(pluginRef, bounceCount + 1)); new FixedMetadataValue(pluginRef, bounceCount + 1));
spawnedArrow.setMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW, spawnedArrow.setMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW,
new FixedMetadataValue(pluginRef, originalArrowShooter)); new FixedMetadataValue(pluginRef, originalArrowShooter));
spawnedArrow.setShotFromCrossbow(true);
// Don't allow multi-shot or infinite arrows to be picked up // Don't allow multi-shot or infinite arrows to be picked up
if (spawnedArrow.hasMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW) if (spawnedArrow.hasMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW)

View File

@ -65,10 +65,19 @@ public class RepairManager extends SkillManager {
public void handleRepair(ItemStack item) { public void handleRepair(ItemStack item) {
Player player = getPlayer(); Player player = getPlayer();
Repairable repairable = mcMMO.getRepairableManager().getRepairable(item.getType()); Repairable repairable = mcMMO.getRepairableManager().getRepairable(item.getType());
if (item.getItemMeta() != null) {
if(item.getItemMeta().hasCustomModelData()) {
if(!mcMMO.p.getCustomItemSupportConfig().isCustomRepairAllowed()) {
NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED,
"Anvil.Repair.Reject.CustomModelData");
return;
}
}
if (item.getItemMeta().isUnbreakable()) { if (item.getItemMeta().isUnbreakable()) {
NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Anvil.Unbreakable"); NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Anvil.Unbreakable");
return; return;
}
} }
// Permissions checks on material and item types // Permissions checks on material and item types

View File

@ -62,14 +62,19 @@ public class SalvageManager extends SkillManager {
} }
public void handleSalvage(Location location, ItemStack item) { public void handleSalvage(Location location, ItemStack item) {
Player player = getPlayer(); final Player player = getPlayer();
Salvageable salvageable = mcMMO.getSalvageableManager().getSalvageable(item.getType()); final Salvageable salvageable = mcMMO.getSalvageableManager().getSalvageable(item.getType());
ItemMeta meta = item.getItemMeta(); final ItemMeta meta = item.getItemMeta();
if (meta != null) {
if (meta != null && meta.isUnbreakable()) { if (meta.hasCustomModelData() && !mcMMO.p.getCustomItemSupportConfig().isCustomSalvageAllowed()) {
NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Anvil.Unbreakable"); NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Anvil.Salvage.Reject.CustomModelData");
return; return;
}
if (meta.isUnbreakable()) {
NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Anvil.Unbreakable");
return;
}
} }
// Permissions checks on material and item types // Permissions checks on material and item types
@ -190,30 +195,6 @@ public class SalvageManager extends SkillManager {
return RankUtils.getRank(getPlayer(), SubSkillType.SALVAGE_ARCANE_SALVAGE); return RankUtils.getRank(getPlayer(), SubSkillType.SALVAGE_ARCANE_SALVAGE);
} }
/*public double getExtractFullEnchantChance() {
int skillLevel = getSkillLevel();
for (Tier tier : Tier.values()) {
if (skillLevel >= tier.getLevel()) {
return tier.getExtractFullEnchantChance();
}
}
return 0;
}
public double getExtractPartialEnchantChance() {
int skillLevel = getSkillLevel();
for (Tier tier : Tier.values()) {
if (skillLevel >= tier.getLevel()) {
return tier.getExtractPartialEnchantChance();
}
}
return 0;
}*/
public double getExtractFullEnchantChance() { public double getExtractFullEnchantChance() {
if(Permissions.hasSalvageEnchantBypassPerk(getPlayer())) if(Permissions.hasSalvageEnchantBypassPerk(getPlayer()))
return 100.0D; return 100.0D;

View File

@ -1,10 +1,21 @@
package com.gmail.nossr50.util.random; package com.gmail.nossr50.util.random;
import com.gmail.nossr50.api.exceptions.ValueOutOfBoundsException;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
public interface Probability { public interface Probability {
/**
* A Probability that always fails.
*/
Probability ALWAYS_FAILS = () -> 0;
/**
* A Probability that always succeeds.
*/
Probability ALWAYS_SUCCEEDS = () -> 1;
/** /**
* The value of this Probability * The value of this Probability
* Should return a result between 0 and 1 (inclusive) * Should return a result between 0 and 1 (inclusive)
@ -17,19 +28,40 @@ public interface Probability {
double getValue(); double getValue();
/** /**
* Create a new Probability with the given value * Create a new Probability of a percentage.
* A value of 100 would represent 100% chance of success * This method takes a percentage and creates a Probability of equivalent odds.
* A value of 50 would represent 50% chance of success *
* A value of 0 would represent 0% chance of success * A value of 100 would represent 100% chance of success,
* A value of 1 would represent 1% chance of success * A value of 50 would represent 50% chance of success,
* A value of 0.5 would represent 0.5% chance of success * A value of 0 would represent 0% chance of success,
* A value of 0.01 would represent 0.01% chance of success * A value of 1 would represent 1% chance of success,
* A value of 0.5 would represent 0.5% chance of success,
* A value of 0.01 would represent 0.01% chance of success.
* *
* @param percentage the value of the probability * @param percentage the value of the probability
* @return a new Probability with the given value * @return a new Probability with the given value
*/ */
static @NotNull Probability ofPercent(double percentage) { static @NotNull Probability ofPercent(double percentage) {
return new ProbabilityImpl(percentage); if (percentage < 0) {
throw new ValueOutOfBoundsException("Value should never be negative for Probability! This suggests a coding mistake, contact the devs!");
}
// Convert to a 0-1 floating point representation
double probabilityValue = percentage / 100.0D;
return new ProbabilityImpl(probabilityValue);
}
/**
* Create a new Probability of a value.
* This method takes a value between 0 and 1 and creates a Probability of equivalent odds.
* A value of 1 or greater represents something that will always succeed.
* A value of around 0.5 represents something that succeeds around half the time.
* A value of 0 represents something that will always fail.
* @param value the value of the probability
* @return a new Probability with the given value
*/
static @NotNull Probability ofValue(double value) {
return new ProbabilityImpl(value);
} }
/** /**

View File

@ -8,31 +8,22 @@ public class ProbabilityImpl implements Probability {
private final double probabilityValue; private final double probabilityValue;
/** /**
* Create a probability with a static value * Create a probability from a static value.
* A value of 0 represents a 0% chance of success,
* A value of 1 represents a 100% chance of success.
* A value of 0.5 represents a 50% chance of success.
* A value of 0.01 represents a 1% chance of success.
* And so on.
* *
* @param percentage the percentage value of the probability * @param value the value of the probability between 0 and 100
*/ */
ProbabilityImpl(double percentage) throws ValueOutOfBoundsException { public ProbabilityImpl(double value) throws ValueOutOfBoundsException {
if (percentage < 0) { if (value < 0) {
throw new ValueOutOfBoundsException("Value should never be negative for Probability! This suggests a coding mistake, contact the devs!"); throw new ValueOutOfBoundsException("Value should never be negative for Probability!" +
" This suggests a coding mistake, contact the devs!");
} }
// Convert to a 0-1 floating point representation probabilityValue = value;
probabilityValue = percentage / 100.0D;
}
ProbabilityImpl(double xPos, double xCeiling, double probabilityCeiling) throws ValueOutOfBoundsException {
if(probabilityCeiling > 100) {
throw new ValueOutOfBoundsException("Probability Ceiling should never be above 100!");
} else if (probabilityCeiling < 0) {
throw new ValueOutOfBoundsException("Probability Ceiling should never be below 0!");
}
//Get the percent success, this will be from 0-100
double probabilityPercent = (probabilityCeiling * (xPos / xCeiling));
//Convert to a 0-1 floating point representation
this.probabilityValue = probabilityPercent / 100.0D;
} }
@Override @Override

View File

@ -79,24 +79,24 @@ public class ProbabilityUtil {
switch (getProbabilityType(subSkillType)) { switch (getProbabilityType(subSkillType)) {
case DYNAMIC_CONFIGURABLE: case DYNAMIC_CONFIGURABLE:
double probabilityCeiling; double probabilityCeiling;
double xCeiling; double skillLevel;
double xPos; double maxBonusLevel; // If a skill level is equal to the cap, it has the full probability
if (player != null) { if (player != null) {
McMMOPlayer mmoPlayer = UserManager.getPlayer(player); McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
if (mmoPlayer == null) { if (mmoPlayer == null) {
return Probability.ofPercent(0); return Probability.ofPercent(0);
} }
xPos = mmoPlayer.getSkillLevel(subSkillType.getParentSkill()); skillLevel = mmoPlayer.getSkillLevel(subSkillType.getParentSkill());
} else { } else {
xPos = 0; skillLevel = 0;
} }
//Probability ceiling is configurable in this type //Probability ceiling is configurable in this type
probabilityCeiling = mcMMO.p.getAdvancedConfig().getMaximumProbability(subSkillType); probabilityCeiling = mcMMO.p.getAdvancedConfig().getMaximumProbability(subSkillType);
//The xCeiling is configurable in this type //The xCeiling is configurable in this type
xCeiling = mcMMO.p.getAdvancedConfig().getMaxBonusLevel(subSkillType); maxBonusLevel = mcMMO.p.getAdvancedConfig().getMaxBonusLevel(subSkillType);
return new ProbabilityImpl(xPos, xCeiling, probabilityCeiling); return calculateCurrentSkillProbability(skillLevel, 0, probabilityCeiling, maxBonusLevel);
case STATIC_CONFIGURABLE: case STATIC_CONFIGURABLE:
try { try {
return getStaticRandomChance(subSkillType); return getStaticRandomChance(subSkillType);
@ -127,22 +127,7 @@ public class ProbabilityUtil {
* @return true if the Skill RNG succeeds, false if it fails * @return true if the Skill RNG succeeds, false if it fails
*/ */
public static boolean isSkillRNGSuccessful(@NotNull SubSkillType subSkillType, @NotNull Player player) { public static boolean isSkillRNGSuccessful(@NotNull SubSkillType subSkillType, @NotNull Player player) {
//Process probability final Probability probability = getSkillProbability(subSkillType, player);
Probability probability = getSubSkillProbability(subSkillType, player);
//Send out event
SubSkillEvent subSkillEvent = EventUtils.callSubSkillEvent(player, subSkillType);
if(subSkillEvent.isCancelled()) {
return false; //Event got cancelled so this doesn't succeed
}
//Result modifier
double resultModifier = subSkillEvent.getResultModifier();
//Mutate probability
if(resultModifier != 1.0D)
probability = Probability.ofPercent(probability.getValue() * resultModifier);
//Luck //Luck
boolean isLucky = Permissions.lucky(player, subSkillType.getParentSkill()); boolean isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
@ -154,12 +139,42 @@ public class ProbabilityUtil {
} }
} }
/**
* Returns the {@link Probability} for a specific {@link SubSkillType} for a specific {@link Player}.
* This does not take into account perks such as lucky for the player.
* This is affected by other plugins who can listen to the {@link SubSkillEvent} and cancel it or mutate it.
*
* @param subSkillType the target subskill
* @param player the target player
* @return the probability for this skill
*/
public static Probability getSkillProbability(@NotNull SubSkillType subSkillType, @NotNull Player player) {
//Process probability
Probability probability = getSubSkillProbability(subSkillType, player);
//Send out event
SubSkillEvent subSkillEvent = EventUtils.callSubSkillEvent(player, subSkillType);
if(subSkillEvent.isCancelled()) {
return Probability.ALWAYS_FAILS;
}
//Result modifier
double resultModifier = subSkillEvent.getResultModifier();
//Mutate probability
if(resultModifier != 1.0D)
probability = Probability.ofPercent(probability.getValue() * resultModifier);
return probability;
}
/** /**
* This is one of several Skill RNG check methods * This is one of several Skill RNG check methods
* This helper method is specific to static value RNG, which can be influenced by a player's Luck * This helper method is specific to static value RNG, which can be influenced by a player's Luck
* *
* @param primarySkillType the related primary skill * @param primarySkillType the related primary skill
* @param player the target player, can be null (null players have the worst odds) * @param player the target player can be null (null players have the worst odds)
* @param probabilityPercentage the probability of this player succeeding in "percentage" format (0-100 inclusive) * @param probabilityPercentage the probability of this player succeeding in "percentage" format (0-100 inclusive)
* @return true if the RNG succeeds, false if it fails * @return true if the RNG succeeds, false if it fails
*/ */
@ -223,4 +238,31 @@ public class ProbabilityUtil {
return new String[]{percent.format(firstValue), percent.format(secondValue)}; return new String[]{percent.format(firstValue), percent.format(secondValue)};
} }
/**
* Helper function to calculate what probability a given skill has at a certain level
* @param skillLevel the skill level currently between the floor and the ceiling
* @param floor the minimum odds this skill can have
* @param ceiling the maximum odds this skill can have
* @param maxBonusLevel the maximum level this skill can have to reach the ceiling
*
* @return the probability of success for this skill at this level
*/
public static Probability calculateCurrentSkillProbability(double skillLevel, double floor,
double ceiling, double maxBonusLevel) {
// The odds of success are between the value of the floor and the value of the ceiling.
// If the skill has a maxBonusLevel of 500 on this skill, then at skill level 500 you would have the full odds,
// at skill level 250 it would be half odds.
if (skillLevel >= maxBonusLevel || maxBonusLevel <= 0) {
// Avoid divide by zero bugs
// Max benefit has been reached, should always succeed
return Probability.ofPercent(ceiling);
}
double odds = (skillLevel / maxBonusLevel) * 100D;
// make sure the odds aren't lower or higher than the floor or ceiling
return Probability.ofPercent(Math.min(Math.max(floor, odds), ceiling));
}
} }

View File

@ -111,7 +111,7 @@ public final class CombatUtils {
} }
} }
} }
private static void processTridentCombat(@NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event) { private static void processTridentCombatMelee(@NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event) {
if (event.getCause() == DamageCause.THORNS) { if (event.getCause() == DamageCause.THORNS) {
return; return;
} }
@ -127,10 +127,40 @@ public final class CombatUtils {
TridentsManager tridentsManager = mcMMOPlayer.getTridentsManager(); TridentsManager tridentsManager = mcMMOPlayer.getTridentsManager();
if (tridentsManager.canActivateAbility()) { // if (tridentsManager.canActivateAbility()) {
mcMMOPlayer.checkAbilityActivation(PrimarySkillType.TRIDENTS); // mcMMOPlayer.checkAbilityActivation(PrimarySkillType.TRIDENTS);
// }
if (SkillUtils.canUseSubskill(player, SubSkillType.TRIDENTS_IMPALE)) {
boostedDamage += (tridentsManager.impaleDamageBonus() * mcMMOPlayer.getAttackStrength());
} }
if(canUseLimitBreak(player, target, SubSkillType.TRIDENTS_TRIDENTS_LIMIT_BREAK)) {
boostedDamage += (getLimitBreakDamage(player, target, SubSkillType.TRIDENTS_TRIDENTS_LIMIT_BREAK) * mcMMOPlayer.getAttackStrength());
}
event.setDamage(boostedDamage);
processCombatXP(mcMMOPlayer, target, PrimarySkillType.TRIDENTS);
printFinalDamageDebug(player, event, mcMMOPlayer);
}
private static void processTridentCombatRanged(@NotNull Trident trident, @NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event) {
if (event.getCause() == DamageCause.THORNS) {
return;
}
double boostedDamage = event.getDamage();
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
//Make sure the profiles been loaded
if(mcMMOPlayer == null) {
return;
}
TridentsManager tridentsManager = mcMMOPlayer.getTridentsManager();
if (SkillUtils.canUseSubskill(player, SubSkillType.TRIDENTS_IMPALE)) { if (SkillUtils.canUseSubskill(player, SubSkillType.TRIDENTS_IMPALE)) {
boostedDamage += (tridentsManager.impaleDamageBonus() * mcMMOPlayer.getAttackStrength()); boostedDamage += (tridentsManager.impaleDamageBonus() * mcMMOPlayer.getAttackStrength());
} }
@ -465,7 +495,7 @@ public final class CombatUtils {
} }
if (mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.TRIDENTS)) { if (mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.TRIDENTS)) {
processTridentCombat(target, player, event); processTridentCombatMelee(target, player, event);
} }
} }
} }
@ -481,6 +511,17 @@ public final class CombatUtils {
} }
} }
} }
else if (painSource instanceof Trident trident) {
ProjectileSource projectileSource = trident.getShooter();
if (projectileSource instanceof Player player) {
if (!Misc.isNPCEntityExcludingVillagers(player)) {
if(mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.TRIDENTS, target)) {
processTridentCombatRanged(trident, target, player, event);
}
}
}
}
else if (painSource instanceof Arrow arrow) { else if (painSource instanceof Arrow arrow) {
ProjectileSource projectileSource = arrow.getShooter(); ProjectileSource projectileSource = arrow.getShooter();
boolean isCrossbow = arrow.isShotFromCrossbow(); boolean isCrossbow = arrow.isShotFromCrossbow();

View File

@ -331,7 +331,7 @@ public class SkillTools {
return primarySkillChildrenMap.get(primarySkillType); return primarySkillChildrenMap.get(primarySkillType);
} }
public double getXpModifier(PrimarySkillType primarySkillType) { public double getXpMultiplier(PrimarySkillType primarySkillType) {
return ExperienceConfig.getInstance().getFormulaSkillModifier(primarySkillType); return ExperienceConfig.getInstance().getFormulaSkillModifier(primarySkillType);
} }

View File

@ -34,8 +34,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.Iterator; import java.util.Iterator;
import static java.util.Objects.requireNonNull;
public final class SkillUtils { public final class SkillUtils {
/** /**
* This is a static utility class, therefore we don't want any instances of * This is a static utility class, therefore we don't want any instances of

View File

@ -56,18 +56,23 @@ public class TextComponentFactory {
return Component.text(text); return Component.text(text);
} }
public static void sendPlayerSubSkillWikiLink(Player player, String subskillformatted) { public static String getSubSkillWikiLink(SubSkillType subSkillType) {
return "https://wiki.mcmmo.org/en/skills/"
+ subSkillType.getParentSkill().toString().toLowerCase(Locale.ENGLISH) + "#"
+ subSkillType.getWikiUrl().toLowerCase(Locale.ENGLISH);
}
public static void sendPlayerSubSkillWikiLink(Player player, String subskillformatted, SubSkillType subSkillType) {
if (!mcMMO.p.getGeneralConfig().getUrlLinksEnabled()) if (!mcMMO.p.getGeneralConfig().getUrlLinksEnabled())
return; return;
TextComponent.Builder wikiLinkComponent = Component.text().content(LocaleLoader.getString("Overhaul.mcMMO.MmoInfo.Wiki")); TextComponent.Builder wikiLinkComponent = Component.text().content(LocaleLoader.getString("Overhaul.mcMMO.MmoInfo.Wiki"));
wikiLinkComponent.decoration(TextDecoration.UNDERLINED, true); wikiLinkComponent.decoration(TextDecoration.UNDERLINED, true);
String wikiUrl = "https://wiki.mcmmo.org/" + subskillformatted; final String subSkillWikiLink = getSubSkillWikiLink(subSkillType);
wikiLinkComponent.clickEvent(ClickEvent.openUrl(subSkillWikiLink));
wikiLinkComponent.clickEvent(ClickEvent.openUrl(wikiUrl)); TextComponent.Builder componentBuilder = Component.text().content(subskillformatted).append(Component.newline()).append(Component.text(subSkillWikiLink)).color(NamedTextColor.GRAY).decoration(TextDecoration.ITALIC, true);
TextComponent.Builder componentBuilder = Component.text().content(subskillformatted).append(Component.newline()).append(Component.text(wikiUrl)).color(NamedTextColor.GRAY).decoration(TextDecoration.ITALIC, true);
wikiLinkComponent.hoverEvent(HoverEvent.showText(componentBuilder.build())); wikiLinkComponent.hoverEvent(HoverEvent.showText(componentBuilder.build()));

View File

@ -0,0 +1,11 @@
# This is meant to be a general config for allowing mcMMO to allow interaction with custom items.
# In the future, I would like to add configs to be specific about certain custom items.
# For now, support is generalized to whether the custom item has a custom model.
# This is an easy solution to implement for now, but not the most ideal.
Custom_Item_Support:
Repair:
# Turn this off to disable repair on any items with custom model data
Allow_Repair_On_Items_With_Custom_Model_Data: true
Salvage:
# Turn this off to disable salvage on any items with custom model data
Allow_Salvage_On_Items_With_Custom_Model_Data: true

View File

@ -423,6 +423,8 @@ Salvage.Skills.Lottery.Perfect=&a&lPerfect!&r&6 You salvaged &3{1}&6 effortlessl
Salvage.Skills.Lottery.Untrained=&7You aren't properly trained in salvaging. You were only able to recover &c{0}&7 materials from &a{1}&7. Salvage.Skills.Lottery.Untrained=&7You aren't properly trained in salvaging. You were only able to recover &c{0}&7 materials from &a{1}&7.
#Anvil (Shared between SALVAGE and REPAIR) #Anvil (Shared between SALVAGE and REPAIR)
Anvil.Unbreakable=This item is unbreakable! Anvil.Unbreakable=This item is unbreakable!
Anvil.Repair.Reject.CustomModelData=A mysterious force prevents you from repairing this item...
Anvil.Salvage.Reject.CustomModelData=A mysterious force prevents you from salvaging this item...
#CROSSBOWS #CROSSBOWS
Crossbows.SkillName=CROSSBOWS Crossbows.SkillName=CROSSBOWS
Crossbows.Ability.Lower=&7You lower your crossbow. Crossbows.Ability.Lower=&7You lower your crossbow.

View File

@ -1,245 +1,245 @@
package com.gmail.nossr50.database; //package com.gmail.nossr50.database;
//
import com.gmail.nossr50.config.AdvancedConfig; //import com.gmail.nossr50.config.AdvancedConfig;
import com.gmail.nossr50.config.GeneralConfig; //import com.gmail.nossr50.config.GeneralConfig;
import com.gmail.nossr50.datatypes.MobHealthbarType; //import com.gmail.nossr50.datatypes.MobHealthbarType;
import com.gmail.nossr50.datatypes.player.PlayerProfile; //import com.gmail.nossr50.datatypes.player.PlayerProfile;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType; //import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import com.gmail.nossr50.mcMMO; //import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.compat.CompatibilityManager; //import com.gmail.nossr50.util.compat.CompatibilityManager;
import com.gmail.nossr50.util.platform.MinecraftGameVersion; //import com.gmail.nossr50.util.platform.MinecraftGameVersion;
import com.gmail.nossr50.util.skills.SkillTools; //import com.gmail.nossr50.util.skills.SkillTools;
import com.gmail.nossr50.util.upgrade.UpgradeManager; //import com.gmail.nossr50.util.upgrade.UpgradeManager;
import org.bukkit.entity.Player; //import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; //import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.*; //import org.junit.jupiter.api.*;
import org.mockito.MockedStatic; //import org.mockito.MockedStatic;
import org.mockito.Mockito; //import org.mockito.Mockito;
//
import java.util.logging.Logger; //import java.util.logging.Logger;
//
import static org.junit.jupiter.api.Assertions.*; //import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any; //import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when; //import static org.mockito.Mockito.when;
//
class SQLDatabaseManagerTest { //class SQLDatabaseManagerTest {
private final static @NotNull Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); // private final static @NotNull Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
static MockedStatic<mcMMO> mockedMcMMO; // static MockedStatic<mcMMO> mockedMcMMO;
SQLDatabaseManager sqlDatabaseManager; // SQLDatabaseManager sqlDatabaseManager;
static GeneralConfig generalConfig; // static GeneralConfig generalConfig;
static AdvancedConfig advancedConfig; // static AdvancedConfig advancedConfig;
static UpgradeManager upgradeManager; // static UpgradeManager upgradeManager;
static CompatibilityManager compatibilityManager; // static CompatibilityManager compatibilityManager;
static SkillTools skillTools; // static SkillTools skillTools;
//
@BeforeAll // @BeforeAll
static void setUpAll() { // static void setUpAll() {
// stub mcMMO.p // // stub mcMMO.p
mockedMcMMO = Mockito.mockStatic(mcMMO.class); // mockedMcMMO = Mockito.mockStatic(mcMMO.class);
mcMMO.p = Mockito.mock(mcMMO.class); // mcMMO.p = Mockito.mock(mcMMO.class);
when(mcMMO.p.getLogger()).thenReturn(logger); // when(mcMMO.p.getLogger()).thenReturn(logger);
//
// general config mock // // general config mock
mockGeneralConfig(); // mockGeneralConfig();
//
// advanced config mock // // advanced config mock
advancedConfig = Mockito.mock(AdvancedConfig.class); // advancedConfig = Mockito.mock(AdvancedConfig.class);
when(mcMMO.p.getAdvancedConfig()).thenReturn(advancedConfig); // when(mcMMO.p.getAdvancedConfig()).thenReturn(advancedConfig);
//
// starting level // // starting level
when(mcMMO.p.getAdvancedConfig().getStartingLevel()).thenReturn(0); // when(mcMMO.p.getAdvancedConfig().getStartingLevel()).thenReturn(0);
//
// wire skill tools // // wire skill tools
skillTools = new SkillTools(mcMMO.p); // skillTools = new SkillTools(mcMMO.p);
when(mcMMO.p.getSkillTools()).thenReturn(skillTools); // when(mcMMO.p.getSkillTools()).thenReturn(skillTools);
//
// compatibility manager mock // // compatibility manager mock
compatibilityManager = Mockito.mock(CompatibilityManager.class); // compatibilityManager = Mockito.mock(CompatibilityManager.class);
when(mcMMO.getCompatibilityManager()).thenReturn(compatibilityManager); // when(mcMMO.getCompatibilityManager()).thenReturn(compatibilityManager);
when(compatibilityManager.getMinecraftGameVersion()).thenReturn(new MinecraftGameVersion(1, 20, 4)); // when(compatibilityManager.getMinecraftGameVersion()).thenReturn(new MinecraftGameVersion(1, 20, 4));
//
// upgrade manager mock // // upgrade manager mock
upgradeManager = Mockito.mock(UpgradeManager.class); // upgradeManager = Mockito.mock(UpgradeManager.class);
when(mcMMO.getUpgradeManager()).thenReturn(upgradeManager); // when(mcMMO.getUpgradeManager()).thenReturn(upgradeManager);
//
// don't trigger upgrades // // don't trigger upgrades
when(mcMMO.getUpgradeManager().shouldUpgrade(any())).thenReturn(false); // when(mcMMO.getUpgradeManager().shouldUpgrade(any())).thenReturn(false);
} // }
//
private static void mockGeneralConfig() { // private static void mockGeneralConfig() {
generalConfig = Mockito.mock(GeneralConfig.class); // generalConfig = Mockito.mock(GeneralConfig.class);
when(generalConfig.getLocale()).thenReturn("en_US"); // when(generalConfig.getLocale()).thenReturn("en_US");
when(mcMMO.p.getGeneralConfig()).thenReturn(generalConfig); // when(mcMMO.p.getGeneralConfig()).thenReturn(generalConfig);
//
// max pool size // // max pool size
when(mcMMO.p.getGeneralConfig().getMySQLMaxPoolSize(SQLDatabaseManager.PoolIdentifier.MISC)) // when(mcMMO.p.getGeneralConfig().getMySQLMaxPoolSize(SQLDatabaseManager.PoolIdentifier.MISC))
.thenReturn(10); // .thenReturn(10);
when(mcMMO.p.getGeneralConfig().getMySQLMaxPoolSize(SQLDatabaseManager.PoolIdentifier.LOAD)) // when(mcMMO.p.getGeneralConfig().getMySQLMaxPoolSize(SQLDatabaseManager.PoolIdentifier.LOAD))
.thenReturn(20); // .thenReturn(20);
when(mcMMO.p.getGeneralConfig().getMySQLMaxPoolSize(SQLDatabaseManager.PoolIdentifier.SAVE)) // when(mcMMO.p.getGeneralConfig().getMySQLMaxPoolSize(SQLDatabaseManager.PoolIdentifier.SAVE))
.thenReturn(20); // .thenReturn(20);
//
// max connections // // max connections
when(mcMMO.p.getGeneralConfig().getMySQLMaxConnections(SQLDatabaseManager.PoolIdentifier.MISC)) // when(mcMMO.p.getGeneralConfig().getMySQLMaxConnections(SQLDatabaseManager.PoolIdentifier.MISC))
.thenReturn(30); // .thenReturn(30);
when(mcMMO.p.getGeneralConfig().getMySQLMaxConnections(SQLDatabaseManager.PoolIdentifier.LOAD)) // when(mcMMO.p.getGeneralConfig().getMySQLMaxConnections(SQLDatabaseManager.PoolIdentifier.LOAD))
.thenReturn(30); // .thenReturn(30);
when(mcMMO.p.getGeneralConfig().getMySQLMaxConnections(SQLDatabaseManager.PoolIdentifier.SAVE)) // when(mcMMO.p.getGeneralConfig().getMySQLMaxConnections(SQLDatabaseManager.PoolIdentifier.SAVE))
.thenReturn(30); // .thenReturn(30);
//
// table prefix // // table prefix
when(mcMMO.p.getGeneralConfig().getMySQLTablePrefix()).thenReturn("mcmmo_"); // when(mcMMO.p.getGeneralConfig().getMySQLTablePrefix()).thenReturn("mcmmo_");
//
// public key retrieval // // public key retrieval
when(mcMMO.p.getGeneralConfig().getMySQLPublicKeyRetrieval()).thenReturn(true); // when(mcMMO.p.getGeneralConfig().getMySQLPublicKeyRetrieval()).thenReturn(true);
//
// debug // // debug
when(mcMMO.p.getGeneralConfig().getMySQLDebug()).thenReturn(true); // when(mcMMO.p.getGeneralConfig().getMySQLDebug()).thenReturn(true);
//
// use mysql // // use mysql
when(mcMMO.p.getGeneralConfig().getUseMySQL()).thenReturn(true); // when(mcMMO.p.getGeneralConfig().getUseMySQL()).thenReturn(true);
//
// use ssl // // use ssl
when(mcMMO.p.getGeneralConfig().getMySQLSSL()).thenReturn(true); // when(mcMMO.p.getGeneralConfig().getMySQLSSL()).thenReturn(true);
//
// username // // username
when(mcMMO.p.getGeneralConfig().getMySQLUserName()).thenReturn("sa"); // when(mcMMO.p.getGeneralConfig().getMySQLUserName()).thenReturn("sa");
//
// password // // password
when(mcMMO.p.getGeneralConfig().getMySQLUserPassword()).thenReturn(""); // when(mcMMO.p.getGeneralConfig().getMySQLUserPassword()).thenReturn("");
//
// host // // host
when(mcMMO.p.getGeneralConfig().getMySQLServerName()).thenReturn("localhost"); // when(mcMMO.p.getGeneralConfig().getMySQLServerName()).thenReturn("localhost");
//
// unused mob health bar thingy // // unused mob health bar thingy
when(mcMMO.p.getGeneralConfig().getMobHealthbarDefault()).thenReturn(MobHealthbarType.HEARTS); // when(mcMMO.p.getGeneralConfig().getMobHealthbarDefault()).thenReturn(MobHealthbarType.HEARTS);
} // }
//
@BeforeEach // @BeforeEach
void setUp() { // void setUp() {
assertNull(sqlDatabaseManager); // assertNull(sqlDatabaseManager);
sqlDatabaseManager = new SQLDatabaseManager(logger, "org.h2.Driver", true); // sqlDatabaseManager = new SQLDatabaseManager(logger, "org.h2.Driver", true);
} // }
//
@AfterEach // @AfterEach
void tearDown() { // void tearDown() {
sqlDatabaseManager = null; // sqlDatabaseManager = null;
} // }
//
@AfterAll // @AfterAll
static void tearDownAll() { // static void tearDownAll() {
mockedMcMMO.close(); // mockedMcMMO.close();
} // }
//
@Test // @Test
void testGetConnectionMisc() throws Exception { // void testGetConnectionMisc() throws Exception {
assertNotNull(sqlDatabaseManager.getConnection(SQLDatabaseManager.PoolIdentifier.MISC)); // assertNotNull(sqlDatabaseManager.getConnection(SQLDatabaseManager.PoolIdentifier.MISC));
} // }
//
@Test // @Test
void testGetConnectionLoad() throws Exception { // void testGetConnectionLoad() throws Exception {
assertNotNull(sqlDatabaseManager.getConnection(SQLDatabaseManager.PoolIdentifier.LOAD)); // assertNotNull(sqlDatabaseManager.getConnection(SQLDatabaseManager.PoolIdentifier.LOAD));
} // }
//
@Test // @Test
void testGetConnectionSave() throws Exception { // void testGetConnectionSave() throws Exception {
assertNotNull(sqlDatabaseManager.getConnection(SQLDatabaseManager.PoolIdentifier.SAVE)); // assertNotNull(sqlDatabaseManager.getConnection(SQLDatabaseManager.PoolIdentifier.SAVE));
} // }
//
@Test // @Test
void testNewUser() { // void testNewUser() {
Player player = Mockito.mock(Player.class); // Player player = Mockito.mock(Player.class);
when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID()); // when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
when(player.getName()).thenReturn("nossr50"); // when(player.getName()).thenReturn("nossr50");
sqlDatabaseManager.newUser(player); // sqlDatabaseManager.newUser(player);
} // }
//
@Test // @Test
void testNewUserGetSkillLevel() { // void testNewUserGetSkillLevel() {
Player player = Mockito.mock(Player.class); // Player player = Mockito.mock(Player.class);
when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID()); // when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
when(player.getName()).thenReturn("nossr50"); // when(player.getName()).thenReturn("nossr50");
PlayerProfile playerProfile = sqlDatabaseManager.newUser(player); // PlayerProfile playerProfile = sqlDatabaseManager.newUser(player);
//
for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { // for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
assertEquals(0, playerProfile.getSkillLevel(primarySkillType)); // assertEquals(0, playerProfile.getSkillLevel(primarySkillType));
} // }
} // }
//
@Test // @Test
void testNewUserGetSkillXpLevel() { // void testNewUserGetSkillXpLevel() {
Player player = Mockito.mock(Player.class); // Player player = Mockito.mock(Player.class);
when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID()); // when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
when(player.getName()).thenReturn("nossr50"); // when(player.getName()).thenReturn("nossr50");
PlayerProfile playerProfile = sqlDatabaseManager.newUser(player); // PlayerProfile playerProfile = sqlDatabaseManager.newUser(player);
//
for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { // for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
assertEquals(0, playerProfile.getSkillXpLevel(primarySkillType)); // assertEquals(0, playerProfile.getSkillXpLevel(primarySkillType));
} // }
} // }
//
@Test // @Test
void testSaveSkillLevelValues() { // void testSaveSkillLevelValues() {
Player player = Mockito.mock(Player.class); // Player player = Mockito.mock(Player.class);
when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID()); // when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
when(player.getName()).thenReturn("nossr50"); // when(player.getName()).thenReturn("nossr50");
PlayerProfile playerProfile = sqlDatabaseManager.newUser(player); // PlayerProfile playerProfile = sqlDatabaseManager.newUser(player);
//
// Validate values are starting from zero // // Validate values are starting from zero
for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { // for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
assertEquals(0, playerProfile.getSkillXpLevel(primarySkillType)); // assertEquals(0, playerProfile.getSkillXpLevel(primarySkillType));
} // }
//
// Change values // // Change values
for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { // for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
playerProfile.modifySkill(primarySkillType, 1 + primarySkillType.ordinal()); // playerProfile.modifySkill(primarySkillType, 1 + primarySkillType.ordinal());
} // }
//
boolean saveSuccess = sqlDatabaseManager.saveUser(playerProfile); // boolean saveSuccess = sqlDatabaseManager.saveUser(playerProfile);
assertTrue(saveSuccess); // assertTrue(saveSuccess);
//
PlayerProfile retrievedUser = sqlDatabaseManager.loadPlayerProfile(player.getName()); // PlayerProfile retrievedUser = sqlDatabaseManager.loadPlayerProfile(player.getName());
//
// Check that values got saved // // Check that values got saved
for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { // for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
if (primarySkillType == PrimarySkillType.SALVAGE || primarySkillType == PrimarySkillType.SMELTING) { // if (primarySkillType == PrimarySkillType.SALVAGE || primarySkillType == PrimarySkillType.SMELTING) {
// Child skills are not saved, but calculated // // Child skills are not saved, but calculated
continue; // continue;
} // }
//
assertEquals(1 + primarySkillType.ordinal(), retrievedUser.getSkillLevel(primarySkillType)); // assertEquals(1 + primarySkillType.ordinal(), retrievedUser.getSkillLevel(primarySkillType));
} // }
} // }
//
@Test // @Test
void testSaveSkillXpValues() { // void testSaveSkillXpValues() {
Player player = Mockito.mock(Player.class); // Player player = Mockito.mock(Player.class);
when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID()); // when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
when(player.getName()).thenReturn("nossr50"); // when(player.getName()).thenReturn("nossr50");
PlayerProfile playerProfile = sqlDatabaseManager.newUser(player); // PlayerProfile playerProfile = sqlDatabaseManager.newUser(player);
//
// Validate values are starting from zero // // Validate values are starting from zero
for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { // for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
assertEquals(0, playerProfile.getSkillXpLevel(primarySkillType)); // assertEquals(0, playerProfile.getSkillXpLevel(primarySkillType));
} // }
//
// Change values // // Change values
for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { // for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
playerProfile.setSkillXpLevel(primarySkillType, 1 + primarySkillType.ordinal()); // playerProfile.setSkillXpLevel(primarySkillType, 1 + primarySkillType.ordinal());
} // }
//
sqlDatabaseManager.saveUser(playerProfile); // sqlDatabaseManager.saveUser(playerProfile);
//
PlayerProfile retrievedUser = sqlDatabaseManager.loadPlayerProfile(player.getName()); // PlayerProfile retrievedUser = sqlDatabaseManager.loadPlayerProfile(player.getName());
//
// Check that values got saved // // Check that values got saved
for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { // for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
if (primarySkillType == PrimarySkillType.SALVAGE || primarySkillType == PrimarySkillType.SMELTING) { // if (primarySkillType == PrimarySkillType.SALVAGE || primarySkillType == PrimarySkillType.SMELTING) {
// Child skills are not saved, but calculated // // Child skills are not saved, but calculated
continue; // continue;
} // }
//
assertEquals(1 + primarySkillType.ordinal(), retrievedUser.getSkillXpLevel(primarySkillType)); // assertEquals(1 + primarySkillType.ordinal(), retrievedUser.getSkillXpLevel(primarySkillType));
} // }
} // }
} //}

View File

@ -73,8 +73,9 @@ class WoodcuttingTest extends MMOTestEnvironment {
woodcuttingManager.processBonusDropCheck(blockState); woodcuttingManager.processBonusDropCheck(blockState);
// verify bonus drops were spawned // verify bonus drops were spawned
// TODO: Can fail if triple drops happen, need to update test // TODO: using at least once since triple drops can also happen
Mockito.verify(woodcuttingManager, Mockito.times(1)).spawnHarvestLumberBonusDrops(blockState); // TODO: Change the test env to disallow triple drop in the future
Mockito.verify(woodcuttingManager, Mockito.atLeastOnce()).spawnHarvestLumberBonusDrops(blockState);
} }
@Test @Test

View File

@ -14,19 +14,19 @@ class ProbabilityTest {
private static Stream<Arguments> provideProbabilitiesForWithinExpectations() { private static Stream<Arguments> provideProbabilitiesForWithinExpectations() {
return Stream.of( return Stream.of(
// static probability, % of time for success // static probability, % of time for success
Arguments.of(new ProbabilityImpl(5), 5), Arguments.of(new ProbabilityImpl(.05), 5),
Arguments.of(new ProbabilityImpl(10), 10), Arguments.of(new ProbabilityImpl(.10), 10),
Arguments.of(new ProbabilityImpl(15), 15), Arguments.of(new ProbabilityImpl(.15), 15),
Arguments.of(new ProbabilityImpl(20), 20), Arguments.of(new ProbabilityImpl(.20), 20),
Arguments.of(new ProbabilityImpl(25), 25), Arguments.of(new ProbabilityImpl(.25), 25),
Arguments.of(new ProbabilityImpl(50), 50), Arguments.of(new ProbabilityImpl(.50), 50),
Arguments.of(new ProbabilityImpl(75), 75), Arguments.of(new ProbabilityImpl(.75), 75),
Arguments.of(new ProbabilityImpl(90), 90), Arguments.of(new ProbabilityImpl(.90), 90),
Arguments.of(new ProbabilityImpl(99.9), 99.9), Arguments.of(new ProbabilityImpl(.999), 99.9),
Arguments.of(new ProbabilityImpl(0.05), 0.05), Arguments.of(new ProbabilityImpl(0.0005), 0.05),
Arguments.of(new ProbabilityImpl(0.1), 0.1), Arguments.of(new ProbabilityImpl(0.001), 0.1),
Arguments.of(new ProbabilityImpl(500), 100), Arguments.of(new ProbabilityImpl(50.0), 100),
Arguments.of(new ProbabilityImpl(1000), 100) Arguments.of(new ProbabilityImpl(100.0), 100)
); );
} }

View File

@ -0,0 +1,22 @@
package com.gmail.nossr50.util.random;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ProbabilityTestUtils {
public static void assertProbabilityExpectations(double expectedWinPercent, Probability probability) {
double iterations = 2.0e7; //20 million
double winCount = 0;
for (int i = 0; i < iterations; i++) {
if(probability.evaluate()) {
winCount++;
}
}
double successPercent = (winCount / iterations) * 100;
System.out.println("Wins: " + winCount);
System.out.println("Fails: " + (iterations - winCount));
System.out.println("Percentage succeeded: " + successPercent + ", Expected: " + expectedWinPercent);
assertEquals(expectedWinPercent, successPercent, 0.025D);
System.out.println("Variance is within tolerance levels!");
}
}

View File

@ -1,39 +1,44 @@
package com.gmail.nossr50.util.random; package com.gmail.nossr50.util.random;
import com.gmail.nossr50.config.AdvancedConfig; import com.gmail.nossr50.MMOTestEnvironment;
import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.datatypes.skills.SubSkillType;
import com.gmail.nossr50.mcMMO; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import java.util.logging.Logger;
import java.util.stream.Stream; import java.util.stream.Stream;
import static com.gmail.nossr50.datatypes.skills.SubSkillType.*; import static com.gmail.nossr50.datatypes.skills.SubSkillType.*;
import static com.gmail.nossr50.util.random.ProbabilityTestUtils.assertProbabilityExpectations;
import static com.gmail.nossr50.util.random.ProbabilityUtil.calculateCurrentSkillProbability;
import static java.util.logging.Logger.getLogger;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
class ProbabilityUtilTest { class ProbabilityUtilTest extends MMOTestEnvironment {
mcMMO mmoInstance; private static final Logger logger = getLogger(ProbabilityUtilTest.class.getName());
AdvancedConfig advancedConfig;
final static double impactChance = 11D; final static double impactChance = 11D;
final static double greaterImpactChance = 0.007D; final static double greaterImpactChance = 0.007D;
final static double fastFoodChance = 45.5D; final static double fastFoodChance = 45.5D;
@BeforeEach @BeforeEach
public void setupMocks() throws NoSuchFieldException, IllegalAccessException { public void setupMocks() {
this.mmoInstance = mock(mcMMO.class); mockBaseEnvironment(logger);
mcMMO.class.getField("p").set(null, mmoInstance);
this.advancedConfig = mock(AdvancedConfig.class);
when(mmoInstance.getAdvancedConfig()).thenReturn(advancedConfig);
when(advancedConfig.getImpactChance()).thenReturn(impactChance); when(advancedConfig.getImpactChance()).thenReturn(impactChance);
when(advancedConfig.getGreaterImpactChance()).thenReturn(greaterImpactChance); when(advancedConfig.getGreaterImpactChance()).thenReturn(greaterImpactChance);
when(advancedConfig.getFastFoodChance()).thenReturn(fastFoodChance); when(advancedConfig.getFastFoodChance()).thenReturn(fastFoodChance);
} }
@AfterEach
public void tearDown() {
cleanupBaseEnvironment();
}
private static Stream<Arguments> staticChanceSkills() { private static Stream<Arguments> staticChanceSkills() {
return Stream.of( return Stream.of(
// static probability, % of time for success // static probability, % of time for success
@ -45,22 +50,26 @@ class ProbabilityUtilTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("staticChanceSkills") @MethodSource("staticChanceSkills")
void testStaticChanceSkills(SubSkillType subSkillType, double expectedWinPercent) throws InvalidStaticChance { void staticChanceSkillsShouldSucceedAsExpected(SubSkillType subSkillType, double expectedWinPercent)
throws InvalidStaticChance {
Probability staticRandomChance = ProbabilityUtil.getStaticRandomChance(subSkillType); Probability staticRandomChance = ProbabilityUtil.getStaticRandomChance(subSkillType);
assertProbabilityExpectations(expectedWinPercent, staticRandomChance); assertProbabilityExpectations(expectedWinPercent, staticRandomChance);
} }
private static void assertProbabilityExpectations(double expectedWinPercent, Probability probability) { @Test
double iterations = 2.0e7; public void isSkillRNGSuccessfulShouldBehaveAsExpected() {
double winCount = 0; // Given
for (int i = 0; i < iterations; i++) { when(advancedConfig.getMaximumProbability(UNARMED_ARROW_DEFLECT)).thenReturn(20D);
if(probability.evaluate()) { when(advancedConfig.getMaxBonusLevel(UNARMED_ARROW_DEFLECT)).thenReturn(0);
winCount++;
}
}
double successPercent = (winCount / iterations) * 100; final Probability probability = ProbabilityUtil.getSkillProbability(UNARMED_ARROW_DEFLECT, player);
System.out.println(successPercent + ", " + expectedWinPercent); assertEquals(0.2D, probability.getValue());
assertEquals(expectedWinPercent, successPercent, 0.05D); assertProbabilityExpectations(20, probability);
}
@Test
public void calculateCurrentSkillProbabilityShouldBeTwenty() {
final Probability probability = calculateCurrentSkillProbability(1000, 0, 20, 1000);
assertEquals(0.2D, probability.getValue());
} }
} }