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
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.
## 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
Currently, you can obtain our builds via the Spigot or Polymart:

View File

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

View File

@ -1,7 +1,6 @@
package com.gmail.nossr50.commands.skills;
import com.gmail.nossr50.datatypes.skills.SubSkillType;
import com.gmail.nossr50.listeners.InteractionManager;
import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.Permissions;
@ -29,14 +28,11 @@ public class MmoInfoCommand implements TabExecutor {
*/
if(commandSender instanceof Player player)
{
if(args.length < 1)
if(args == null || args.length < 1 || args[0] == null || args[0].isEmpty())
return false;
if(Permissions.mmoinfo(player))
{
if(args == null || args[0] == null)
return false;
if(args[0].equalsIgnoreCase( "???"))
{
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.Mystery"));
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
player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.NoMatch"));
final SubSkillType subSkillType = matchSubSkill(args[0]);
if (subSkillType != null) {
displayInfo(player, subSkillType);
} else {
//Not a real skill
player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.NoMatch"));
}
return true;
}
}
@ -59,6 +56,16 @@ public class MmoInfoCommand implements TabExecutor {
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
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
if (args.length == 1) {
@ -67,20 +74,13 @@ public class MmoInfoCommand implements TabExecutor {
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.SubSkillHeader", subSkillName));
player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.SubSkillHeader", subSkillType.getLocaleName()));
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
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 */
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 */

View File

@ -578,13 +578,9 @@ public final class SQLDatabaseManager implements DatabaseManager {
statement.executeUpdate();
statement.close();
long currentTimeMillis = System.currentTimeMillis();
String sql = "INSERT INTO " + tablePrefix + "users (`user`, uuid, lastlogin) VALUES (?, ?, ?)";
statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
statement = connection.prepareStatement("INSERT INTO " + tablePrefix + "users (user, uuid, lastlogin) VALUES (?, ?, UNIX_TIMESTAMP())", Statement.RETURN_GENERATED_KEYS);
statement.setString(1, playerName);
statement.setString(2, uuid != null ? uuid.toString() : null);
statement.setLong(3, currentTimeMillis);
statement.executeUpdate();
resultSet = statement.getGeneratedKeys();
@ -1038,19 +1034,41 @@ public final class SQLDatabaseManager implements DatabaseManager {
}
private void updateStructure(String tableName, String columnName, String columnSize) {
try (Connection connection = getConnection(PoolIdentifier.MISC);
Statement createStatement = connection.createStatement()) {
String startingLevel = "'" + mcMMO.p.getAdvancedConfig().getStartingLevel() + "'";
createStatement.executeUpdate("ALTER TABLE `" + tablePrefix + tableName + "` "
+ "ADD COLUMN IF NOT EXISTS `" + columnName + "` int(" + columnSize + ") unsigned NOT NULL DEFAULT " + startingLevel);
try (Connection connection = getConnection(PoolIdentifier.MISC)) {
if (!columnExists(connection, mcMMO.p.getGeneralConfig().getMySQLDatabaseName(), tablePrefix+tableName, columnName)) {
try (Statement createStatement = connection.createStatement()) {
// logger.info("[SQLDB Check] Adding column '" + columnName + "' to table '" + tablePrefix + tableName + "'...");
String startingLevel = "'" + mcMMO.p.getAdvancedConfig().getStartingLevel() + "'";
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) {
e.printStackTrace(); // Consider more robust logging
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 {
if (!this.h2) {
// 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.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;
import java.util.EnumMap;
import java.util.Map;
@ -840,14 +841,15 @@ public class McMMOPlayer implements Identified {
* @param xp Experience amount to process
* @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
if ((mcMMO.p.getSkillTools().getLevelCap(primarySkillType) <= getSkillLevel(primarySkillType))
|| (mcMMO.p.getGeneralConfig().getPowerLevelCap() <= getPowerLevel())) {
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()) {
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
* @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
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();
}
public String getWikiName(String subSkillName) {
/*
* Find where to begin our substring (after the prefix)
*/
StringBuilder endResult = new StringBuilder();
int subStringIndex = getSubStringIndex(subSkillName);
/*
* 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();
public String getWikiUrl() {
// remove the text before the first underscore
int subStringIndex = getSubStringIndex(name());
String afterPrefix = name().substring(subStringIndex);
// replace _ or spaces with -
return afterPrefix.replace("_", "-").replace(" ", "-").toLowerCase(Locale.ENGLISH);
}
/**

View File

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

View File

@ -15,14 +15,26 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.TreeMap;
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() {
init();
}
@Override
@ -37,8 +49,7 @@ public class PapiExpansion extends PlaceholderExpansion {
@Override
public String getVersion() {
//grab version from pom.xml
return "1.0,0";
return mcMMO.p.getDescription().getVersion();
}
@Override
@ -54,105 +65,41 @@ public class PapiExpansion extends PlaceholderExpansion {
@Override
@Nullable
public String onPlaceholderRequest(final Player player, @NotNull final String params) {
String token;
String data = null;
int dataPosition = params.indexOf(":");
if (dataPosition != -1) {
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) {
// Non player-specific placeholders
if (params.equalsIgnoreCase(IS_EXP_EVENT_ACTIVE)) {
return mcMMO.p.isXPEventEnabled() ? PlaceholderAPIPlugin.booleanTrue() : PlaceholderAPIPlugin.booleanFalse();
} else if (params.equalsIgnoreCase(EXP_RATE)) {
return String.valueOf(ExperienceConfig.getInstance().getExperienceGainsGlobalMultiplier());
}
public String getSkillXpRate(PrimarySkillType skill, Player player) {
final McMMOPlayer user = UserManager.getPlayer(player);
if (user == null) return null;
} else if (params.equalsIgnoreCase(POWER_LEVEL_CAP)) {
return String.valueOf(mcMMO.p.getGeneralConfig().getPowerLevelCap());
}
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;
if (Permissions.customXpBoost(player, skill))
modifier = ExperienceConfig.getInstance().getCustomXpPerkBoost();
else if (Permissions.quadrupleXp(player, skill))
@ -167,69 +114,29 @@ public class PapiExpansion extends PlaceholderExpansion {
modifier = 1.5;
else if (Permissions.oneAndOneTenthXp(player, skill))
modifier = 1.1;
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));
spawnedArrow.setMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW,
new FixedMetadataValue(pluginRef, originalArrowShooter));
spawnedArrow.setShotFromCrossbow(true);
// Don't allow multi-shot or infinite arrows to be picked up
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) {
Player player = getPlayer();
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()) {
NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Anvil.Unbreakable");
return;
if (item.getItemMeta().isUnbreakable()) {
NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Anvil.Unbreakable");
return;
}
}
// 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) {
Player player = getPlayer();
final Player player = getPlayer();
Salvageable salvageable = mcMMO.getSalvageableManager().getSalvageable(item.getType());
ItemMeta meta = item.getItemMeta();
if (meta != null && meta.isUnbreakable()) {
NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Anvil.Unbreakable");
return;
final Salvageable salvageable = mcMMO.getSalvageableManager().getSalvageable(item.getType());
final ItemMeta meta = item.getItemMeta();
if (meta != null) {
if (meta.hasCustomModelData() && !mcMMO.p.getCustomItemSupportConfig().isCustomSalvageAllowed()) {
NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Anvil.Salvage.Reject.CustomModelData");
return;
}
if (meta.isUnbreakable()) {
NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Anvil.Unbreakable");
return;
}
}
// Permissions checks on material and item types
@ -190,30 +195,6 @@ public class SalvageManager extends SkillManager {
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() {
if(Permissions.hasSalvageEnchantBypassPerk(getPlayer()))
return 100.0D;

View File

@ -1,10 +1,21 @@
package com.gmail.nossr50.util.random;
import com.gmail.nossr50.api.exceptions.ValueOutOfBoundsException;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.ThreadLocalRandom;
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
* Should return a result between 0 and 1 (inclusive)
@ -17,19 +28,40 @@ public interface Probability {
double getValue();
/**
* Create a new Probability with the given value
* A value of 100 would represent 100% chance of success
* A value of 50 would represent 50% chance of success
* A value of 0 would represent 0% 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
* Create a new Probability of a percentage.
* This method takes a percentage and creates a Probability of equivalent odds.
*
* A value of 100 would represent 100% chance of success,
* A value of 50 would represent 50% chance of success,
* A value of 0 would represent 0% 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
* @return a new Probability with the given value
*/
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;
/**
* 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 {
if (percentage < 0) {
throw new ValueOutOfBoundsException("Value should never be negative for Probability! This suggests a coding mistake, contact the devs!");
public ProbabilityImpl(double value) throws ValueOutOfBoundsException {
if (value < 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
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;
probabilityValue = value;
}
@Override

View File

@ -79,24 +79,24 @@ public class ProbabilityUtil {
switch (getProbabilityType(subSkillType)) {
case DYNAMIC_CONFIGURABLE:
double probabilityCeiling;
double xCeiling;
double xPos;
double skillLevel;
double maxBonusLevel; // If a skill level is equal to the cap, it has the full probability
if (player != null) {
McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
if (mmoPlayer == null) {
return Probability.ofPercent(0);
}
xPos = mmoPlayer.getSkillLevel(subSkillType.getParentSkill());
skillLevel = mmoPlayer.getSkillLevel(subSkillType.getParentSkill());
} else {
xPos = 0;
skillLevel = 0;
}
//Probability ceiling is configurable in this type
probabilityCeiling = mcMMO.p.getAdvancedConfig().getMaximumProbability(subSkillType);
//The xCeiling is configurable in this type
xCeiling = mcMMO.p.getAdvancedConfig().getMaxBonusLevel(subSkillType);
return new ProbabilityImpl(xPos, xCeiling, probabilityCeiling);
maxBonusLevel = mcMMO.p.getAdvancedConfig().getMaxBonusLevel(subSkillType);
return calculateCurrentSkillProbability(skillLevel, 0, probabilityCeiling, maxBonusLevel);
case STATIC_CONFIGURABLE:
try {
return getStaticRandomChance(subSkillType);
@ -127,22 +127,7 @@ public class ProbabilityUtil {
* @return true if the Skill RNG succeeds, false if it fails
*/
public static boolean isSkillRNGSuccessful(@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 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);
final Probability probability = getSkillProbability(subSkillType, player);
//Luck
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 helper method is specific to static value RNG, which can be influenced by a player's Luck
*
* @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)
* @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)};
}
/**
* 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) {
return;
}
@ -127,10 +127,40 @@ public final class CombatUtils {
TridentsManager tridentsManager = mcMMOPlayer.getTridentsManager();
if (tridentsManager.canActivateAbility()) {
mcMMOPlayer.checkAbilityActivation(PrimarySkillType.TRIDENTS);
// if (tridentsManager.canActivateAbility()) {
// 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)) {
boostedDamage += (tridentsManager.impaleDamageBonus() * mcMMOPlayer.getAttackStrength());
}
@ -465,7 +495,7 @@ public final class CombatUtils {
}
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) {
ProjectileSource projectileSource = arrow.getShooter();
boolean isCrossbow = arrow.isShotFromCrossbow();

View File

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

View File

@ -34,8 +34,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.Iterator;
import static java.util.Objects.requireNonNull;
public final class SkillUtils {
/**
* 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);
}
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())
return;
TextComponent.Builder wikiLinkComponent = Component.text().content(LocaleLoader.getString("Overhaul.mcMMO.MmoInfo.Wiki"));
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(wikiUrl)).color(NamedTextColor.GRAY).decoration(TextDecoration.ITALIC, true);
TextComponent.Builder componentBuilder = Component.text().content(subskillformatted).append(Component.newline()).append(Component.text(subSkillWikiLink)).color(NamedTextColor.GRAY).decoration(TextDecoration.ITALIC, true);
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.
#Anvil (Shared between SALVAGE and REPAIR)
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.SkillName=CROSSBOWS
Crossbows.Ability.Lower=&7You lower your crossbow.

View File

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

View File

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

View File

@ -14,19 +14,19 @@ class ProbabilityTest {
private static Stream<Arguments> provideProbabilitiesForWithinExpectations() {
return Stream.of(
// static probability, % of time for success
Arguments.of(new ProbabilityImpl(5), 5),
Arguments.of(new ProbabilityImpl(10), 10),
Arguments.of(new ProbabilityImpl(15), 15),
Arguments.of(new ProbabilityImpl(20), 20),
Arguments.of(new ProbabilityImpl(25), 25),
Arguments.of(new ProbabilityImpl(50), 50),
Arguments.of(new ProbabilityImpl(75), 75),
Arguments.of(new ProbabilityImpl(90), 90),
Arguments.of(new ProbabilityImpl(99.9), 99.9),
Arguments.of(new ProbabilityImpl(0.05), 0.05),
Arguments.of(new ProbabilityImpl(0.1), 0.1),
Arguments.of(new ProbabilityImpl(500), 100),
Arguments.of(new ProbabilityImpl(1000), 100)
Arguments.of(new ProbabilityImpl(.05), 5),
Arguments.of(new ProbabilityImpl(.10), 10),
Arguments.of(new ProbabilityImpl(.15), 15),
Arguments.of(new ProbabilityImpl(.20), 20),
Arguments.of(new ProbabilityImpl(.25), 25),
Arguments.of(new ProbabilityImpl(.50), 50),
Arguments.of(new ProbabilityImpl(.75), 75),
Arguments.of(new ProbabilityImpl(.90), 90),
Arguments.of(new ProbabilityImpl(.999), 99.9),
Arguments.of(new ProbabilityImpl(0.0005), 0.05),
Arguments.of(new ProbabilityImpl(0.001), 0.1),
Arguments.of(new ProbabilityImpl(50.0), 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;
import com.gmail.nossr50.config.AdvancedConfig;
import com.gmail.nossr50.MMOTestEnvironment;
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.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.logging.Logger;
import java.util.stream.Stream;
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.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class ProbabilityUtilTest {
mcMMO mmoInstance;
AdvancedConfig advancedConfig;
class ProbabilityUtilTest extends MMOTestEnvironment {
private static final Logger logger = getLogger(ProbabilityUtilTest.class.getName());
final static double impactChance = 11D;
final static double greaterImpactChance = 0.007D;
final static double fastFoodChance = 45.5D;
@BeforeEach
public void setupMocks() throws NoSuchFieldException, IllegalAccessException {
this.mmoInstance = mock(mcMMO.class);
mcMMO.class.getField("p").set(null, mmoInstance);
this.advancedConfig = mock(AdvancedConfig.class);
when(mmoInstance.getAdvancedConfig()).thenReturn(advancedConfig);
public void setupMocks() {
mockBaseEnvironment(logger);
when(advancedConfig.getImpactChance()).thenReturn(impactChance);
when(advancedConfig.getGreaterImpactChance()).thenReturn(greaterImpactChance);
when(advancedConfig.getFastFoodChance()).thenReturn(fastFoodChance);
}
@AfterEach
public void tearDown() {
cleanupBaseEnvironment();
}
private static Stream<Arguments> staticChanceSkills() {
return Stream.of(
// static probability, % of time for success
@ -45,22 +50,26 @@ class ProbabilityUtilTest {
@ParameterizedTest
@MethodSource("staticChanceSkills")
void testStaticChanceSkills(SubSkillType subSkillType, double expectedWinPercent) throws InvalidStaticChance {
void staticChanceSkillsShouldSucceedAsExpected(SubSkillType subSkillType, double expectedWinPercent)
throws InvalidStaticChance {
Probability staticRandomChance = ProbabilityUtil.getStaticRandomChance(subSkillType);
assertProbabilityExpectations(expectedWinPercent, staticRandomChance);
}
private static void assertProbabilityExpectations(double expectedWinPercent, Probability probability) {
double iterations = 2.0e7;
double winCount = 0;
for (int i = 0; i < iterations; i++) {
if(probability.evaluate()) {
winCount++;
}
}
@Test
public void isSkillRNGSuccessfulShouldBehaveAsExpected() {
// Given
when(advancedConfig.getMaximumProbability(UNARMED_ARROW_DEFLECT)).thenReturn(20D);
when(advancedConfig.getMaxBonusLevel(UNARMED_ARROW_DEFLECT)).thenReturn(0);
double successPercent = (winCount / iterations) * 100;
System.out.println(successPercent + ", " + expectedWinPercent);
assertEquals(expectedWinPercent, successPercent, 0.05D);
final Probability probability = ProbabilityUtil.getSkillProbability(UNARMED_ARROW_DEFLECT, player);
assertEquals(0.2D, probability.getValue());
assertProbabilityExpectations(20, probability);
}
@Test
public void calculateCurrentSkillProbabilityShouldBeTwenty() {
final Probability probability = calculateCurrentSkillProbability(1000, 0, 20, 1000);
assertEquals(0.2D, probability.getValue());
}
}