Merge branch 'master' into sao

This commit is contained in:
Jules 2025-11-05 23:36:02 +01:00
commit a803716bec
42 changed files with 471 additions and 393 deletions

View File

@ -94,17 +94,18 @@
<url>https://maven.enginehub.org/repo/</url>
</repository>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
<!-- Party and Friends Repository -->
<repository>
<id>simonsators Repo</id>
<url>https://simonsator.de/repo</url>
</repository>
<!-- Quests (plugin) -->
<repository>
<id>codemc-repo</id>
<url>https://repo.codemc.io/repository/maven-public/</url>
</repository>
<!-- Heroes Repository
<repository>
<id>herocraft</id>
@ -227,9 +228,9 @@
</dependency>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<groupId>net.dmulloy2</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.8.0</version>
<version>5.4.0</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
@ -325,9 +326,9 @@
<!-- Quest Plugins -->
<dependency>
<groupId>me.blackvein</groupId>
<artifactId>Quests</artifactId>
<version>4.4.1-b340</version>
<groupId>me.pikamug.quests</groupId>
<artifactId>quests-core</artifactId>
<version>5.2.7</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>

View File

@ -725,7 +725,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
* @return Experience needed in order to reach next level
*/
public long getLevelUpExperience() {
return getProfess().getExpCurve().getExperience(getLevel() + 1);
return getProfess().getExpCurve().getExperience(this, getLevel());
}
public boolean isOnline() {
@ -752,7 +752,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
public void giveLevels(int value, @NotNull EXPSource source) {
long equivalentExp = 0;
while (value-- > 0) equivalentExp += getProfess().getExpCurve().getExperience(getLevel() + value + 1);
while (value-- > 0) equivalentExp += getProfess().getExpCurve().getExperience(this, getLevel() + value);
giveExperience(equivalentExp, source);
}
@ -1088,7 +1088,7 @@ public class PlayerData extends SynchronizedDataHolder implements OfflinePlayerD
* Loop for exp overload when leveling up, will continue
* looping until exp is 0 or max currentLevel has been reached
*/
while (experience >= (experienceNeeded = getProfess().getExpCurve().getExperience(newLevel + 1))) {
while (experience >= (experienceNeeded = getProfess().getExpCurve().getExperience(this, newLevel))) {
if (maxLevel > 0 && newLevel >= maxLevel) {
experience = 0;

View File

@ -1,38 +0,0 @@
package net.Indyuce.mmocore.api.player.attribute;
import io.lumine.mythic.lib.api.stat.StatInstance;
import io.lumine.mythic.lib.api.stat.handler.StatHandler;
import net.Indyuce.mmocore.api.player.PlayerData;
import org.bukkit.configuration.ConfigurationSection;
/**
* This fixes an issue where registering new stat modifiers in ML
* to add extra attribute points does NOT update the stats granted
* by the attribute.
* <p>
* This stat handler MAY call subsequent stat handlers. There might
* be infinite recursion problems if another attr. grants extra attribute pts.
*/
public class MMOCoreAttributeStatHandler extends StatHandler {
private final PlayerAttribute attr;
public MMOCoreAttributeStatHandler(ConfigurationSection config, PlayerAttribute attr) {
super(config, "ADDITIONAL_" + attr.getId().toUpperCase().replace("-", "_"));
this.attr = attr;
}
/**
* This method is called on login but the MMOCore playerData
* is not loaded yet, hence the try/catch clause
*/
@Override
public void runUpdate(StatInstance instance) {
try {
final PlayerData playerData = PlayerData.get(instance.getMap().getPlayerData());
playerData.getAttributes().getInstance(attr).updateStats();
} catch (NullPointerException exception) {
// Player data is not loaded yet so there's nothing to update.
}
}
}

View File

@ -6,7 +6,7 @@ import io.lumine.mythic.lib.util.lang3.Validate;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.experience.EXPSource;
import net.Indyuce.mmocore.experience.ExpCurve;
import net.Indyuce.mmocore.experience.curve.ExperienceCurve;
import net.Indyuce.mmocore.experience.ExperienceObject;
import net.Indyuce.mmocore.experience.droptable.ExperienceTable;
import org.bukkit.Location;
@ -84,7 +84,7 @@ public class PlayerAttribute implements ExperienceObject {
}
@Override
public String getKey() {
public @NotNull String getKey() {
return "attribute:" + getId().replace("-", "_");
}
@ -99,9 +99,9 @@ public class PlayerAttribute implements ExperienceObject {
return expTable != null;
}
@Nullable
@NotNull
@Override
public ExpCurve getExpCurve() {
public ExperienceCurve getExpCurve() {
throw new RuntimeException("Attributes don't have experience");
}

View File

@ -25,8 +25,8 @@ import net.Indyuce.mmocore.api.player.profess.resource.ResourceRegeneration;
import net.Indyuce.mmocore.api.util.MMOCoreUtils;
import net.Indyuce.mmocore.api.util.math.formula.LinearValue;
import net.Indyuce.mmocore.experience.EXPSource;
import net.Indyuce.mmocore.experience.ExpCurve;
import net.Indyuce.mmocore.experience.ExperienceObject;
import net.Indyuce.mmocore.experience.curve.ExperienceCurve;
import net.Indyuce.mmocore.experience.droptable.ExperienceTable;
import net.Indyuce.mmocore.loot.chest.particle.CastingParticle;
import net.Indyuce.mmocore.player.stats.StatInfo;
@ -59,7 +59,7 @@ public class PlayerClass implements ExperienceObject, PreloadedObject {
private final ManaDisplayOptions manaDisplay;
@NotNull
private final ExpCurve expCurve;
private final ExperienceCurve expCurve;
@Nullable
private final ExperienceTable expTable;
@ -111,10 +111,14 @@ public class PlayerClass implements ExperienceObject, PreloadedObject {
actionBarFormat = config.contains("action-bar") ? config.getString("action-bar") : null;
// Exp curve
expCurve = config.contains("exp-curve")
? MMOCore.plugin.experience.getCurveOrThrow(
config.get("exp-curve").toString().toLowerCase().replace("_", "-").replace(" ", "-"))
: ExpCurve.DEFAULT;
ExperienceCurve expCurve = ExperienceCurve.DEFAULT;
try {
expCurve = ExperienceCurve.fromConfig(config.getString("exp-curve"));
} catch (Throwable exception) {
MMOCore.log(Level.WARNING, "Could not load exp curve from class '" + id + "': " + exception.getMessage());
exception.printStackTrace();
}
this.expCurve = expCurve;
// Main exp table
ExperienceTable expTable = null;
@ -261,7 +265,7 @@ public class PlayerClass implements ExperienceObject, PreloadedObject {
manaDisplay = ManaDisplayOptions.DEFAULT;
maxLevel = 0;
displayOrder = 0;
expCurve = ExpCurve.DEFAULT;
expCurve = ExperienceCurve.DEFAULT;
expTable = null;
comboMap = null;
castParticle = new CastingParticle(VParticle.INSTANT_EFFECT.get());
@ -282,7 +286,7 @@ public class PlayerClass implements ExperienceObject, PreloadedObject {
}
@Override
public String getKey() {
public @NotNull String getKey() {
return "class_" + getId();
}
@ -315,7 +319,7 @@ public class PlayerClass implements ExperienceObject, PreloadedObject {
}
@Override
public @NotNull ExpCurve getExpCurve() {
public @NotNull ExperienceCurve getExpCurve() {
return expCurve;
}

View File

@ -43,7 +43,7 @@ public class InfoCommandTreeNode extends CommandTreeNode {
for (Profession profession : MMOCore.plugin.professionManager.getAll())
sender.sendMessage(
ChatColor.YELLOW + profession.getName() + ": Lvl " + ChatColor.GOLD + playerData.getCollectionSkills().getLevel(profession)
+ ChatColor.YELLOW + " - " + ChatColor.GOLD + playerData.getCollectionSkills().getExperience(profession)
+ ChatColor.YELLOW + " - " + ChatColor.GOLD + MythicLib.plugin.getMMOConfig().decimal.format(playerData.getCollectionSkills().getExperience(profession))
+ ChatColor.YELLOW + " / " + ChatColor.GOLD + playerData.getCollectionSkills().getLevelUpExperience(profession));
sender.sendMessage(ChatColor.YELLOW + "----------------------------------------------------");
return CommandResult.SUCCESS;

View File

@ -200,14 +200,24 @@ public class RPGPlaceholders extends PlaceholderExpansion {
if (playerData.hasSkillBound(slot))
return Double.toString(playerData.getCooldownMap().getCooldown(playerData.getBoundSkill(slot)));
else return MMOCore.plugin.configManager.noSkillBoundPlaceholder;
} else if (identifier.startsWith("profession_experience_"))
}
// Current exp in profession
else if (identifier.startsWith("profession_experience_")) {
return MythicLib.plugin.getMMOConfig().decimal.format(
playerData.getCollectionSkills().getExperience(identifier.substring(22).replace(" ", "-").replace("_", "-").toLowerCase()));
}
else if (identifier.startsWith("profession_next_level_"))
return String.valueOf(PlayerData.get(player).getCollectionSkills()
.getLevelUpExperience(identifier.substring(22).replace(" ", "-").replace("_", "-").toLowerCase()));
// Exp needed to level up profession
else if (identifier.startsWith("profession_next_level_")) {
final var professionId = identifier.substring(22).replace(" ", "-").replace("_", "-").toLowerCase();
final @Nullable var profession = MMOCore.plugin.professionManager.get(professionId);
if (profession == null) return "{profession_not_found}";
final var professionLevel = playerData.getCollectionSkills().getLevel(profession);
return String.valueOf(profession.getExpCurve().getExperience(playerData, professionLevel));
}
// Number of online members in party
else if (identifier.startsWith("party_count")) {
final @Nullable AbstractParty party = playerData.getParty();
return party == null ? "0" : String.valueOf(party.countMembers());

View File

@ -1,81 +0,0 @@
package net.Indyuce.mmocore.experience;
import io.lumine.mythic.lib.util.lang3.Validate;
import net.Indyuce.mmocore.api.player.PlayerData;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
public class ExpCurve {
private final String id;
/**
* Experience needed to level up. Different professions or classes can have
* different exp curves so that it is easier to balance.
*/
private final List<Long> experience = new ArrayList<>();
/**
* Purely arbitrary but MMOCore needs a default exp curve for everything
* otherwise there might be divisions by 0 when trying to update the vanilla
* exp bar which requires a 0.0 -> 1.0 float as parameter.
*
* See {@link PlayerData#refreshVanillaExp()}
*/
public static final ExpCurve DEFAULT = new ExpCurve("default", 100, 200, 300, 400, 500);
/**
* Reads an exp curve from a text file, one line after the other. Each exp
* value has to be the only thing written on every line
*
* @param file Text file to read data from
*/
public ExpCurve(File file) {
this.id = file.getName().replace(".txt", "").toLowerCase().replace("_", "-").replace(" ", "-");
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
String readLine;
while ((readLine = reader.readLine()) != null)
experience.add(Long.valueOf(readLine));
reader.close();
Validate.isTrue(!experience.isEmpty(), "There must be at least one exp value in your exp curve");
} catch(Throwable throwable) {
throw new RuntimeException(throwable);
}
}
/**
* Public constructor for external plugins
*
* @param id Some unique identifier to let other plugin features refer
* to your exp curve.
* @param values The exp values, at to be at least one or the constructor
* will throw an error
*/
public ExpCurve(String id, long... values) {
this.id = id;
for (long value : values)
experience.add(value);
Validate.isTrue(!experience.isEmpty(), "There must be at least one exp value in your exp curve");
}
public String getId() {
return id;
}
/**
* @param level Level being reached by some player
* @return Experience needed to reach provided level. The level serves
* an index for a list checkup. If the level is higher than
* the list size, it just returns the last value of the list
*/
public long getExperience(int level) {
Validate.isTrue(level > 0, "Level must be stricly positive");
return experience.get(Math.min(level, experience.size()) - 1);
}
}

View File

@ -1,10 +1,10 @@
package net.Indyuce.mmocore.experience;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.experience.curve.ExperienceCurve;
import net.Indyuce.mmocore.experience.dispenser.ExperienceDispenser;
import net.Indyuce.mmocore.experience.droptable.ExperienceTable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* General implementation for professions, classes and attributes.
@ -17,6 +17,7 @@ import org.jetbrains.annotations.Nullable;
*/
public interface ExperienceObject extends ExperienceDispenser {
@NotNull
String getKey();
/**
@ -24,14 +25,14 @@ public interface ExperienceObject extends ExperienceDispenser {
*
* @return Exp curve of that object
*/
@Nullable
ExpCurve getExpCurve();
@NotNull
ExperienceCurve getExpCurve();
/**
* Should throw an error if called when
* {@link #hasExperienceTable()} returns false
* Throws an exception no experience table
*
* @return Table read when leveling up
* @see #hasExperienceTable()
*/
@NotNull
ExperienceTable getExperienceTable();

View File

@ -24,6 +24,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
public class PlayerProfessions {
private final Map<String, Double> exp = new HashMap<>();
@ -120,11 +121,14 @@ public class PlayerProfessions {
}
public long getLevelUpExperience(Profession profession) {
return profession.getExpCurve().getExperience(getLevel(profession) + 1);
return profession.getExpCurve().getExperience(playerData, getLevel(profession));
}
@Deprecated
public long getLevelUpExperience(String id) {
return MMOCore.plugin.professionManager.has(id) ? MMOCore.plugin.professionManager.get(id).getExpCurve().getExperience(getLevel(id) + 1) : 0;
final var prof = MMOCore.plugin.professionManager.get(id);
if (prof == null) return 0;
return prof.getExpCurve().getExperience(null, getLevel(id));
}
@Deprecated
@ -132,6 +136,12 @@ public class PlayerProfessions {
setLevel(profession, value, PlayerLevelChangeEvent.Reason.UNKNOWN);
}
@Deprecated
public void takeLevels(@NotNull Profession profession, int value) {
int current = Math.max(1, level.getOrDefault(profession.getId(), 1));
level.put(profession.getId(), Math.max(1, current - value));
}
public void setLevel(@NotNull Profession profession, int newLevel, @NotNull PlayerLevelChangeEvent.Reason reason) {
if (profession.hasMaxLevel()) newLevel = Math.min(profession.getMaxLevel(), newLevel);
final var previousValue = level.put(profession.getId(), Math.max(1, newLevel));
@ -146,12 +156,6 @@ public class PlayerProfessions {
}
}
@Deprecated
public void takeLevels(@NotNull Profession profession, int value) {
int current = Math.max(1, level.getOrDefault(profession.getId(), 1));
level.put(profession.getId(), Math.max(1, current - value));
}
public void setExperience(Profession profession, double value) {
exp.put(profession.getId(), value);
}
@ -159,7 +163,8 @@ public class PlayerProfessions {
public void giveLevels(Profession profession, int value, EXPSource source) {
long equivalentExp = 0;
final var currentLevel = getLevel(profession);
while (value-- > 0) equivalentExp += profession.getExpCurve().getExperience(currentLevel + value + 1);
while (value-- > 0)
equivalentExp += profession.getExpCurve().getExperience(playerData, currentLevel + value);
giveExperience(profession, equivalentExp, source);
}
@ -171,6 +176,8 @@ public class PlayerProfessions {
giveExperience(profession, value, source, null, true);
}
private static final Random RANDOM = new Random();
public void giveExperience(@NotNull Profession profession, double value, @NotNull EXPSource source, @Nullable Location hologramLocation, boolean splitExp) {
Validate.isTrue(playerData.isOnline(), "Cannot give experience to offline player");
if (value <= 0) {
@ -200,8 +207,11 @@ public class PlayerProfessions {
if (event.isCancelled()) return;
// Display hologram
if (hologramLocation != null && profession.getOption(Profession.ProfessionOption.EXP_HOLOGRAMS))
MMOCoreUtils.displayIndicator(hologramLocation.add(.5, 1.5, .5), Language.EXP_HOLOGRAM.getFormat().replace("{exp}", MythicLib.plugin.getMMOConfig().decimal.format(event.getExperience())));
if (hologramLocation != null && profession.getOption(Profession.ProfessionOption.EXP_HOLOGRAMS)) {
// TODO custom location per profession/exp source.
hologramLocation.add(.5 + .7 * RANDOM.nextDouble(), 1.3 + RANDOM.nextDouble() / 3, .5 + .7 * RANDOM.nextDouble());
MMOCoreUtils.displayIndicator(hologramLocation, Language.EXP_HOLOGRAM.getFormat().replace("{exp}", MythicLib.plugin.getMMOConfig().decimal.format(event.getExperience())));
}
final var maxLevel = profession.getMaxLevel();
var currentExp = Math.max(0d, exp.getOrDefault(profession.getId(), 0d) + event.getExperience());
@ -213,7 +223,7 @@ public class PlayerProfessions {
* Loop for exp overload when leveling up, will continue
* looping until exp is 0 or max newLevel has been reached
*/
while (currentExp >= (experienceNeeded = profession.getExpCurve().getExperience(newLevel))) {
while (currentExp >= (experienceNeeded = profession.getExpCurve().getExperience(playerData, newLevel))) {
if (maxLevel > 0 && newLevel >= maxLevel) {
currentExp = 0;

View File

@ -6,6 +6,7 @@ import io.lumine.mythic.lib.util.PreloadedObject;
import io.lumine.mythic.lib.util.lang3.Validate;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.experience.curve.ExperienceCurve;
import net.Indyuce.mmocore.experience.droptable.ExperienceTable;
import net.Indyuce.mmocore.util.formula.ScalingFormula;
import org.bukkit.Location;
@ -22,7 +23,8 @@ public class Profession implements ExperienceObject, PreloadedObject {
private final String id, name;
private final int maxLevel;
private final Map<ProfessionOption, Boolean> options = new HashMap<>();
private final ExpCurve expCurve;
@NotNull
private final ExperienceCurve expCurve;
private final ExperienceTable expTable;
/**
@ -44,9 +46,7 @@ public class Profession implements ExperienceObject, PreloadedObject {
this.name = config.getString("name");
Validate.notNull(name, "Could not load name");
expCurve = config.contains("exp-curve")
? MMOCore.plugin.experience.getCurveOrThrow(config.get("exp-curve").toString().toLowerCase().replace("_", "-").replace(" ", "-"))
: ExpCurve.DEFAULT;
expCurve = ExperienceCurve.fromConfig(config.getString("exp-curve"));
experience = ScalingFormula.fromConfig(config.get("experience"));
ExperienceTable expTable = null;
@ -99,12 +99,12 @@ public class Profession implements ExperienceObject, PreloadedObject {
}
@Override
public String getKey() {
public @NotNull String getKey() {
return "profession_" + getId();
}
@Override
public ExpCurve getExpCurve() {
public @NotNull ExperienceCurve getExpCurve() {
return expCurve;
}

View File

@ -0,0 +1,57 @@
package net.Indyuce.mmocore.experience.curve;
import io.lumine.mythic.lib.util.FileUtils;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface ExperienceCurve {
/**
* Arbitrary values. MMOCore needs a default exp curve for everything otherwise
* it would be swarming be divisions by 0 when trying to update the vanilla
* exp bar which requires a 0.0 -> 1.0 float as parameter.
* <p>
* See {@link PlayerData#refreshVanillaExp()}
*/
public static final ExperienceCurve DEFAULT = new ListExperienceCurve(100, 200, 300, 400, 500);
/**
* If 1 is provided as level, this method returns the experience needed
* to reach level 2. If the exp curve is a formula, it will provide 1 to the formula.
* If the exp curve is a list, it will return the first element of the list.
*
* @param player Player leveling up
* @param level Current level of the player.
* @return Experience needed to reach the next level.
*/
public long getExperience(@NotNull PlayerData player, int level);
@NotNull
public static ExperienceCurve fromConfig(@Nullable String configInput) {
// [BACKWARDS COMPATIBILITY] Load from predefined file
try {
if (configInput == null) throw new RuntimeException();
final var expCurveFile = FileUtils.getFile(MMOCore.plugin, "expcurves/" + configInput + ".txt");
return new ListExperienceCurve(expCurveFile);
} catch (Exception ignored) {
}
if (configInput == null) {
return DEFAULT;
}
// Load from file
else if (configInput.endsWith(".txt")) {
final var expCurveFile = FileUtils.getFile(MMOCore.plugin, configInput);
return new ListExperienceCurve(expCurveFile);
}
// Load as formula
else {
return new FormulaExperienceCurve(configInput);
}
}
}

View File

@ -0,0 +1,30 @@
package net.Indyuce.mmocore.experience.curve;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.util.formula.NumericalExpression;
import io.lumine.mythic.lib.util.lang3.Validate;
import net.Indyuce.mmocore.api.player.PlayerData;
import org.jetbrains.annotations.NotNull;
public class FormulaExperienceCurve implements ExperienceCurve {
private final String formula;
public FormulaExperienceCurve(@NotNull String formula) {
this.formula = formula;
}
@Override
public long getExperience(@NotNull PlayerData player, int level) {
try {
Validate.isTrue(level > 0, "Level must be stricly positive, got " + level);
final var parsed = MythicLib.plugin.getPlaceholderParser().parse(player.getPlayer(), this.formula);
final var value = (int) NumericalExpression.eval(parsed.replace("{level}", String.valueOf(level)));
Validate.isTrue(value > 0, "Exp curve must return a positive value, got " + value);
return value;
} catch (Exception e) {
MythicLib.plugin.getLogger().warning("Error parsing exp formula '" + this.formula + "' for player " + player.getPlayer().getName() + ", using 100: " + e.getMessage());
return 100;
}
}
}

View File

@ -0,0 +1,58 @@
package net.Indyuce.mmocore.experience.curve;
import io.lumine.mythic.lib.util.lang3.Validate;
import net.Indyuce.mmocore.api.player.PlayerData;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;
public class ListExperienceCurve implements ExperienceCurve {
/**
* Experience needed to level up. At index i, experience needed
* to level up from level N to (N + 1).
*/
private final List<Long> experience;
/**
* Reads an exp curve from a text file, one line after the other. Each
* exp value has to be the only thing written on every line
*
* @param file Text file to read data from
*/
public ListExperienceCurve(@NotNull File file) {
this.experience = new ArrayList<>();
try {
BufferedReader reader = new BufferedReader(new FileReader(file));
String readLine;
while ((readLine = reader.readLine()) != null)
experience.add(Long.valueOf(readLine));
reader.close();
Validate.isTrue(!experience.isEmpty(), "There must be at least one exp value in your exp curve");
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
/**
* Exp curve with specific level up exp values
*
* @param values The exp values. There has to be at least one
*/
public ListExperienceCurve(long... values) {
this.experience = new ArrayList<>(values.length);
for (long value : values) experience.add(value);
Validate.isTrue(!experience.isEmpty(), "There must be at least one exp value in your exp curve");
}
@Override
public long getExperience(@NotNull PlayerData player, int level) {
Validate.isTrue(level > 0, "Level must be stricly positive");
return experience.get(Math.min(level, experience.size()));
}
}

View File

@ -15,6 +15,7 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.inventory.CraftItemEvent;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class CraftItemExperienceSource extends SpecificExperienceSource<Material> {
@ -45,7 +46,7 @@ public class CraftItemExperienceSource extends SpecificExperienceSource<Material
PlayerData data = PlayerData.get((Player) event.getWhoClicked());
/**
/*
* This makes sure that the crafting recipe was performed correctly.
*
* In some scenarii, the CraftItemEvent (which is only a click event
@ -62,27 +63,32 @@ public class CraftItemExperienceSource extends SpecificExperienceSource<Material
* - https://git.lumine.io/mythiccraft/mmocore/-/issues/102
* - https://www.spigotmc.org/threads/how-to-get-amount-of-item-crafted.377598/
*/
final int index = getLowerAmountIngredientIndex(event.getInventory().getMatrix());
final int oldAmount = event.getInventory().getMatrix()[index].getAmount();
final int itemsCraftedPerRecipe = event.getInventory().getResult().getAmount();
final Material resultType = event.getInventory().getResult().getType();
final var index = getLowerAmountIngredientIndex(event.getInventory().getMatrix());
final var oldItem = event.getInventory().getMatrix()[index];
final var oldAmount = oldItem.getAmount();
final var itemsCraftedPerRecipe = event.getInventory().getResult().getAmount();
final var resultType = event.getInventory().getResult().getType();
Bukkit.getScheduler().runTask(MMOCore.plugin, () -> {
// First check
int newAmount = getAmount(event.getInventory().getMatrix()[index]);
final var newItem = event.getInventory().getMatrix()[index];
final var newAmount = getEffectiveNewAmount(newItem, oldItem);
if (newAmount >= oldAmount) return;
// Deduce amount crafted
int amountCrafted = (event.getClick().isShiftClick() ? oldAmount - newAmount : 1) * itemsCraftedPerRecipe;
final var amountCrafted = (event.getClick().isShiftClick() ? oldAmount - newAmount : 1) * itemsCraftedPerRecipe;
for (CraftItemExperienceSource source : getSources())
if (source.matches(data, resultType))
source.giveExperience(data, amountCrafted, event.getInventory().getLocation());
});
}
private int getAmount(@Nullable ItemStack item) {
return item == null || item.getType() == Material.AIR ? 0 : item.getAmount();
private int getEffectiveNewAmount(@Nullable ItemStack item, @NotNull ItemStack oldItem) {
// Some items are consumed and leave behind a different type (e.g. buckets)
// If that's the case just say all items were crafted
// Fixes https://gitlab.com/phoenix-dvpmt/mmocore/-/issues/1165
return item == null || item.getType() == Material.AIR || item.getType() != oldItem.getType() ? 0 : item.getAmount();
}
private int getLowerAmountIngredientIndex(ItemStack[] matrix) {

View File

@ -1,10 +1,10 @@
package net.Indyuce.mmocore.manager;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.api.stat.StatInstance;
import io.lumine.mythic.lib.util.FileUtils;
import io.lumine.mythic.lib.util.config.YamlFile;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.attribute.MMOCoreAttributeStatHandler;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.api.player.attribute.PlayerAttribute;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -40,20 +40,23 @@ public class AttributeManager implements MMOCoreManager {
@Override
public void initialize(boolean clearBefore) {
if (clearBefore) {
if (clearBefore)
map.clear();
MythicLib.plugin.getStats().clearRegisteredStats(handler -> handler instanceof MMOCoreAttributeStatHandler);
}
FileUtils.loadObjectsFromFolder(MMOCore.plugin, "attributes", false, (key, config) -> {
final String path = key.toLowerCase().replace("_", "-").replace(" ", "-");
map.put(path, new PlayerAttribute(config));
}, "Could not load attribute '%s' from file '%s': %s");
final var statsConfig = new YamlFile(MythicLib.plugin, "stats").getContent();
// MythicLib stat handlers
for (PlayerAttribute attr : getAll()) {
final MMOCoreAttributeStatHandler handler = new MMOCoreAttributeStatHandler(statsConfig, attr);
MythicLib.plugin.getStats().registerStat(handler, handler.getStat() + "_PERCENT");
final var statId = attr.getId().toUpperCase().replace("-", "_") + "_PERCENT";
MythicLib.plugin.getStats().computeStat(statId).addUpdateListener(ins -> this.updateMMOCoreStatAttributeValue(ins, attr));
}
}
private void updateMMOCoreStatAttributeValue(@NotNull StatInstance instance, PlayerAttribute attribute) {
final var playerData = PlayerData.get(instance.getMap().getPlayerData());
playerData.getAttributes().getInstance(attribute).updateStats();
}
}

View File

@ -69,12 +69,6 @@ public class ConfigManager {
if (!FileUtils.getFile(MMOCore.plugin, "exp-tables").exists())
copyDefaultFile("exp-tables/default_exp_tables.yml");
if (!FileUtils.getFile(MMOCore.plugin, "expcurves").exists()) {
copyDefaultFile("expcurves/levels.txt");
copyDefaultFile("expcurves/mining.txt");
copyDefaultFile("expcurves/skill-tree-node.txt");
}
if (!FileUtils.getFile(MMOCore.plugin, "loot-chests").exists())
copyDefaultFile("loot-chests/default_loot_chests.yml");

View File

@ -1,9 +1,8 @@
package net.Indyuce.mmocore.manager;
import io.lumine.mythic.lib.util.FileUtils;
import io.lumine.mythic.lib.util.lang3.Validate;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.experience.ExpCurve;
import net.Indyuce.mmocore.experience.curve.ExperienceCurve;
import net.Indyuce.mmocore.experience.droptable.ExperienceTable;
import net.Indyuce.mmocore.experience.source.type.ExperienceSource;
import net.Indyuce.mmocore.manager.profession.ExperienceSourceManager;
@ -14,7 +13,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
public class ExperienceManager implements MMOCoreManager {
private final Map<String, ExpCurve> expCurves = new HashMap<>();
private final Map<String, ExperienceTable> expTables = new HashMap<>();
/**
@ -45,14 +43,29 @@ public class ExperienceManager implements MMOCoreManager {
getManager(path).registerSource(source);
}
@Deprecated
public boolean hasCurve(String id) {
return expCurves.containsKey(id);
try {
ExperienceCurve.fromConfig("expcurves/" + id + ".txt");
return true;
} catch (Exception ignored) {
return false;
}
}
@Deprecated
@NotNull
public ExpCurve getCurveOrThrow(String id) {
Validate.isTrue(hasCurve(id), "Could not find exp curve with ID '" + id + "'");
return expCurves.get(id);
public ExperienceCurve getCurveOrThrow(String id) {
try {
return ExperienceCurve.fromConfig(id + ".txt");
} catch (Exception ignored) {
throw new IllegalArgumentException("Could not find exp curve with ID '" + id + "'");
}
}
@Deprecated
public Collection<ExperienceCurve> getCurves() {
return List.of();
}
@Deprecated
@ -82,10 +95,6 @@ public class ExperienceManager implements MMOCoreManager {
throw new IllegalArgumentException("Please provide either a string (exp table name) or a config section (locally define an exp table)");
}
public Collection<ExpCurve> getCurves() {
return expCurves.values();
}
public Collection<ExperienceTable> getTables() {
return expTables.values();
}
@ -100,21 +109,15 @@ public class ExperienceManager implements MMOCoreManager {
@Override
public void initialize(boolean clearBefore) {
if (clearBefore) {
expCurves.clear();
expTables.clear();
managers.forEach((c, manager) -> manager.close());
managers.clear();
}
// Exp curves
FileUtils.loadObjectsFromFolderRaw(MMOCore.plugin, "expcurves", file -> {
final ExpCurve curve = new ExpCurve(file);
expCurves.put(curve.getId(), curve);
}, "Could not load exp curve from file '%s': %s");
// Exp tables
FileUtils.loadObjectsFromFolder(MMOCore.plugin ,"exp-tables", false, (key, config) -> {
FileUtils.loadObjectsFromFolder(MMOCore.plugin, "exp-tables", false, (key, config) -> {
final ExperienceTable table = new ExperienceTable(config);
expTables.put(table.getId(), table);
}, "Could not load exp table '%s' from file '%s': %s");

View File

@ -161,7 +161,11 @@ public class YAMLDatabaseImpl extends YAMLFlatDatabase<PlayerData, OfflinePlayer
// Saves the nodes levels
config.set("skill-tree-level", null);
MMOCore.plugin.skillTreeManager.getAllNodes().forEach(node -> config.set("skill-tree-level." + node.getFullId(), data.getNodeLevel(node)));
config.createSection("skill-tree-level");
for (var node : MMOCore.plugin.skillTreeManager.getAllNodes()) {
final var nodeLevel = data.getNodeLevel(node);
if (nodeLevel > 0) config.set("skill-tree-level." + node.getFullId(), nodeLevel);
}
// Skill levels
config.set("skill", null);

View File

@ -205,12 +205,15 @@ public class CustomBlockManager extends SpecificProfessionManager {
this.protectVanillaBlocks = config.getBoolean("protect-vanilla-blocks");
this.enableToolRestrictions = config.getBoolean("enable-tool-restrictions");
for (String key : config.getStringList("conditions"))
try {
customMineConditions.add(MMOCore.plugin.loadManager.loadCondition(new MMOLineConfig(key)));
} catch (IllegalArgumentException exception) {
MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load custom mining condition '" + key + "': " + exception.getMessage());
}
// Avoid warnings if disabled
if (enabled)
for (String key : config.getStringList("conditions"))
try {
customMineConditions.add(MMOCore.plugin.loadManager.loadCondition(new MMOLineConfig(key)));
} catch (IllegalArgumentException exception) {
MMOCore.plugin.getLogger().log(Level.WARNING, "Could not load custom mining condition '" + key + "': " + exception.getMessage());
}
else customMineConditions.clear();
}
@Deprecated

View File

@ -1,7 +1,7 @@
package net.Indyuce.mmocore.quest;
import net.Indyuce.mmocore.quest.compat.BeautyQuestModule;
import net.Indyuce.mmocore.quest.compat.BlackVeinQuestsModule;
import net.Indyuce.mmocore.quest.compat.QuestsModule;
import net.Indyuce.mmocore.quest.compat.QuestCreatorModule;
import net.Indyuce.mmocore.quest.compat.QuestModule;
import org.bukkit.Bukkit;
@ -10,7 +10,7 @@ import javax.inject.Provider;
public enum QuestModuleType {
MMOCORE("MMOCore", MMOCoreQuestModule::new),
QUESTS("Quests", BlackVeinQuestsModule::new),
QUESTS("Quests", QuestsModule::new),
BEAUTY_QUEST("BeautyQuests", BeautyQuestModule::new),
QUEST_CREATOR("QuestCreator", QuestCreatorModule::new);

View File

@ -1,54 +0,0 @@
package net.Indyuce.mmocore.quest.compat;
import me.blackvein.quests.Quest;
import me.blackvein.quests.Quester;
import me.blackvein.quests.Quests;
import net.Indyuce.mmocore.quest.AbstractQuest;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class BlackVeinQuestsModule implements QuestModule<BlackVeinQuestsModule.BlackVeinQuestQuest> {
private final Quests plugin = (Quests) Bukkit.getPluginManager().getPlugin("Quests");
@Override
public BlackVeinQuestQuest getQuestOrThrow(String id) {
Quests plugin = (Quests) Bukkit.getPluginManager().getPlugin("Quests");
return plugin.getQuestById(id)==null?null:new BlackVeinQuestQuest(plugin.getQuestById(id));
}
@Override
public boolean hasCompletedQuest(String questId, Player player) {
Quester quester = plugin.getQuester(player.getUniqueId());
if(quester==null)
return false;
for(Quest quest:quester.getCompletedQuests()) {
if(quest.getId().equals(questId))
return true;
}
return false;
}
public class BlackVeinQuestQuest implements AbstractQuest {
private final Quest quest;
public BlackVeinQuestQuest(Quest quest) {
this.quest = quest;
}
@Override
public String getName() {
return quest.getName();
}
@Override
public String getId() {
return quest.getId();
}
}
}

View File

@ -0,0 +1,52 @@
package net.Indyuce.mmocore.quest.compat;
import me.pikamug.quests.BukkitQuestsPlugin;
import me.pikamug.quests.player.Quester;
import me.pikamug.quests.quests.Quest;
import net.Indyuce.mmocore.quest.AbstractQuest;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class QuestsModule implements QuestModule<QuestsModule.QuestImpl> {
private final BukkitQuestsPlugin plugin;
public QuestsModule() {
plugin = (BukkitQuestsPlugin) Bukkit.getPluginManager().getPlugin("Quests");
}
@Override
public QuestImpl getQuestOrThrow(String id) {
final var found = plugin.getQuest(id);
return found == null ? null : new QuestImpl(found);
}
@Override
public boolean hasCompletedQuest(String questId, Player player) {
Quester quester = plugin.getQuester(player.getUniqueId());
if (quester == null) return false;
for (var quest : quester.getCompletedQuests())
if (quest.getId().equals(questId)) return true;
return false;
}
public static class QuestImpl implements AbstractQuest {
private final Quest quest;
public QuestImpl(Quest quest) {
this.quest = quest;
}
@Override
public String getName() {
return quest.getName();
}
@Override
public String getId() {
return quest.getId();
}
}
}

View File

@ -9,7 +9,7 @@ import io.lumine.mythic.lib.util.lang3.Validate;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.experience.EXPSource;
import net.Indyuce.mmocore.experience.ExpCurve;
import net.Indyuce.mmocore.experience.curve.ExperienceCurve;
import net.Indyuce.mmocore.experience.ExperienceObject;
import net.Indyuce.mmocore.experience.droptable.ExperienceTable;
import net.Indyuce.mmocore.skilltree.display.DisplayMap;
@ -204,7 +204,7 @@ public class SkillTreeNode implements ExperienceObject {
public static final String KEY_PREFIX = "node";
@Override
public String getKey() {
public @NotNull String getKey() {
return KEY_PREFIX + ":" + getFullId().replace("-", "_");
}
@ -269,9 +269,9 @@ public class SkillTreeNode implements ExperienceObject {
throw new RuntimeException("Skill trees don't have experience");
}
@Nullable
@NotNull
@Override
public ExpCurve getExpCurve() {
public ExperienceCurve getExpCurve() {
throw new RuntimeException("Skill trees don't have experience");
}

View File

@ -20,8 +20,14 @@ options:
off-combat-health-regen: false
off-combat-mana-regen: false
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/class_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current class level.
# | Example: '{level} * 200'
exp-curve: '{level} * 200'
exp-table: class_exp_table

View File

@ -36,8 +36,14 @@ display:
# item-model: 'minecraft:dirt'
# texture: '' # Skull texture (set 'item' to 'PLAYER_HEAD')
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/class_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current class level.
# | Example: '{level} * 200'
exp-curve: '{level} * 200'
skill-slots:
1:

View File

@ -36,8 +36,14 @@ display:
# item-model: 'minecraft:dirt'
# texture: '' # Skull texture (set 'item' to 'PLAYER_HEAD')
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/class_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current class level.
# | Example: '{level} * 200'
exp-curve: '{level} * 200'
# The maximum level players can reach
max-level: 100

View File

@ -35,8 +35,14 @@ display:
# item-model: 'minecraft:dirt'
# texture: '' # Skull texture (set 'item' to 'PLAYER_HEAD')
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/class_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current class level.
# | Example: '{level} * 200'
exp-curve: '{level} * 200'
# The maximum level players can reach
max-level: 100

View File

@ -35,8 +35,14 @@ display:
# item-model: 'minecraft:dirt'
# texture: '' # Skull texture (set 'item' to 'PLAYER_HEAD')
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/class_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current class level.
# | Example: '{level} * 200'
exp-curve: '{level} * 200'
skill-slots:
1:

View File

@ -36,8 +36,14 @@ display:
# item-model: 'minecraft:dirt'
# texture: '' # Skull texture (set 'item' to 'PLAYER_HEAD')
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/class_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current class level.
# | Example: '{level} * 200'
exp-curve: '{level} * 200'
# The maximum level players can reach
max-level: 100

View File

@ -39,8 +39,14 @@ display:
# item-model: 'minecraft:dirt'
# texture: '' # Skull texture (set 'item' to 'PLAYER_HEAD')
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/class_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current class level.
# | Example: '{level} * 200'
exp-curve: '{level} * 200'
# The maximum level players can reach
max-level: 100

View File

@ -1,25 +0,0 @@
200
400
600
800
1000
1200
1400
1600
1800
2000
2200
2400
2600
2800
3000
3200
3400
3600
3800
4000
4200
4400
4600
4800
5000

View File

@ -1,25 +0,0 @@
100
200
300
400
500
600
700
800
900
1000
1100
1200
1300
1400
1500
1600
1700
1800
1900
2000
2100
2200
2300
2400
2500

View File

@ -1,25 +0,0 @@
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

View File

@ -8,8 +8,14 @@ experience:
base: 20
per-level: 3
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/profession_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current profession level.
# | Example: '{level} * 150'
exp-curve: '{level} * 150'
exp-sources:
- 'brewpotion{effect=SPEED}'
@ -20,31 +26,31 @@ exp-sources:
alchemy-experience:
special:
# When brewing a potion into a splash potion,
# only 40% of the base EXP is earned.
splash: 40
# When brewing a potion into a splash potion,
# only 40% of the base EXP is earned.
lingering: 40
# When extending a pot duration,
# only 40% of base EXP is earned.
extend: 40
# When upgrading a potion level,
# only 40% of base EXP is earned.
upgrade: 40
# Base EXP of potions
effects:
# Water bottles
AWKWARD: 5
MUNDANE: 5
THICK: 5
# Potion effects
NIGHT_VISION: 10
INVISIBILITY: 10

View File

@ -8,8 +8,14 @@ experience:
base: 10
per-level: 2
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/profession_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current profession level.
# | Example: '{level} * 150'
exp-curve: '{level} * 150'
# Remove the 'enchant' parameter from the line config
# to make the profession get EXP with ANY enchant.

View File

@ -7,8 +7,14 @@ experience:
base: 10
per-level: 2
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/profession_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current profession level.
# | Example: '{level} * 150'
exp-curve: '{level} * 150'
exp-sources:
- 'mineblock{type=WHEAT;amount=1-3;crop=true;player-placed=true;silk-touch=false}'

View File

@ -8,8 +8,14 @@ experience:
base: 20
per-level: 3
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/profession_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current profession level.
# | Example: '{level} * 150'
exp-curve: '{level} * 150'
exp-sources: {}

View File

@ -8,8 +8,14 @@ experience:
base: 20
per-level: 3
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: mining
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/profession_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current profession level.
# | Example: '{level} * 150'
exp-curve: '{level} * 150'
# This part of the config is ONLY for custom mining.
# Custom Mining must be setup in config.yml and it

View File

@ -7,8 +7,14 @@ experience:
base: 20
per-level: 3
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/profession_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current profession level.
# | Example: '{level} * 150'
exp-curve: '{level} * 150'
exp-sources:
- 'craftitem{type=BLAST_FURNACE;amount=1}'

View File

@ -8,8 +8,14 @@ experience:
base: 20
per-level: 3
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/profession_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current profession level.
# | Example: '{level} * 150'
exp-curve: '{level} * 150'
exp-sources:
- 'repairitem{}'

View File

@ -8,8 +8,14 @@ experience:
base: 13
per-level: 2.5
# Must match an existing exp curve filename from the 'expcurves' folder
exp-curve: levels
# Can be a file path to an existing .txt file inside the MMOCore/ folder.
# | The TXT files contains only the experience required per level, one per line.
# | Example: 'expcurves/profession_exp_curve.txt'
# Or a formula using the {level} and PAPI placeholders.
# | The formula should return the total experience required to reach the next
# | level if {level} is the player's current profession level.
# | Example: '{level} * 150'
exp-curve: '{level} * 150'
exp-sources:
- 'mineblock{type=OAK_LOG;amount=1-3}'