Made all the slots Unlockable with an associated command & trigger.

This commit is contained in:
Ka0rX 2023-04-03 17:25:50 +01:00
parent cedc6ae6c8
commit 015f4544ce
9 changed files with 239 additions and 79 deletions

View File

@ -23,6 +23,9 @@ public class DefaultMMOLoader extends MMOLoader {
if (config.getKey().equals("stat"))
return new StatTrigger(config);
if(config.getKey().equals("unlock_slot"))
return new UnlockSlotTrigger(config);
if (config.getKey().equals("unlock_skill"))
return new UnlockSkillTrigger(config);

View File

@ -21,6 +21,7 @@ import net.Indyuce.mmocore.api.player.profess.SavedClassInformation;
import net.Indyuce.mmocore.api.player.profess.Subclass;
import net.Indyuce.mmocore.api.player.profess.resource.PlayerResource;
import net.Indyuce.mmocore.api.player.profess.skillbinding.BoundSkillInfo;
import net.Indyuce.mmocore.api.player.profess.skillbinding.SkillSlot;
import net.Indyuce.mmocore.api.player.social.FriendRequest;
import net.Indyuce.mmocore.api.player.stats.PlayerStats;
import net.Indyuce.mmocore.api.quest.PlayerQuests;
@ -122,7 +123,7 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc
* -Skills
* -Skill Books
*/
private final Set<String> unlockedItems= new HashSet<>();
private final Set<String> unlockedItems = new HashSet<>();
/**
* Saves the amount of times the player has claimed some
* exp item in exp tables, for both exp tables used
@ -376,7 +377,6 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc
}
/**
* @return If the item is unlocked by the player
* This is used for skills that can be locked & unlocked.
@ -384,13 +384,16 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc
* Looks at the real value and thus remove the plugin identifier
*/
public boolean hasUnlocked(Unlockable unlockable) {
String unlockableKey = unlockable.getUnlockNamespacedKey().substring(unlockable.getUnlockNamespacedKey().indexOf(":"));
return unlockedItems
.stream()
.filter(key -> key.substring(key.indexOf(":")).equals(unlockableKey))
.collect(Collectors.toList()).size() != 0;
return hasUnlocked(unlockable.getUnlockNamespacedKey());
}
public boolean hasUnlocked(String unlockNamespacedKey) {
return unlockedItems
.stream()
.filter(key -> key.equals(unlockNamespacedKey))
.collect(Collectors.toList()).size() != 0;
}
/**
* Unlocks an item for the player. This is mainly used to unlock skills.
@ -799,9 +802,9 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc
final double r = Math.sin((double) t / warpTime * Math.PI);
for (double j = 0; j < Math.PI * 2; j += Math.PI / 4)
getPlayer().getLocation().getWorld().spawnParticle(Particle.REDSTONE, getPlayer().getLocation().add(
Math.cos((double) 5 * t / warpTime + j) * r,
(double) 2 * t / warpTime,
Math.sin((double) 5 * t / warpTime + j) * r),
Math.cos((double) 5 * t / warpTime + j) * r,
(double) 2 * t / warpTime,
Math.sin((double) 5 * t / warpTime + j) * r),
1, new Particle.DustOptions(Color.PURPLE, 1.25f));
}
}.runTaskTimer(MMOCore.plugin, 0, 1);
@ -830,7 +833,8 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc
* If it's null, no hologram will be displayed
* @param splitExp Should the exp be split among party members
*/
public void giveExperience(double value, @NotNull EXPSource source, @Nullable Location hologramLocation, boolean splitExp) {
public void giveExperience(double value, @NotNull EXPSource source, @Nullable Location hologramLocation,
boolean splitExp) {
if (value <= 0) {
experience = Math.max(0, experience + value);
return;
@ -1172,7 +1176,14 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc
profess.getSkills()
.stream()
.filter(ClassSkill::isUnlockedByDefault)
.forEach(skill ->unlock(skill.getSkill()));
.forEach(skill -> unlock(skill.getSkill()));
//Loads the classUnlockedSlots
profess.getSlots()
.stream()
.filter(SkillSlot::isUnlockedByDefault)
.forEach(this::unlock);
}
public boolean hasSkillBound(int slot) {
@ -1204,7 +1215,7 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc
if (slot >= 0) {
//We apply the skill buffs associated with the slot to the skill.
profess.getSkillSlot(slot).getSkillBuffTriggers().forEach(skillBuffTrigger ->
skillBuffTrigger.apply(this,skill.getSkill().getHandler().getId()));
skillBuffTrigger.apply(this, skill.getSkill().getHandler().getId()));
if (skill.getSkill().getTrigger().isPassive()) {
@ -1220,7 +1231,7 @@ public class PlayerData extends OfflinePlayerData implements Closable, Experienc
public void unbindSkill(int slot) {
//We remove the skill buffs associated with the slot from the skill that is .
profess.getSkillSlot(slot).getSkillBuffTriggers().forEach(skillBuffTrigger ->
skillBuffTrigger.remove(this,boundSkills.get(slot).getClassSkill().getSkill().getHandler().getId()));
skillBuffTrigger.remove(this, boundSkills.get(slot).getClassSkill().getSkill().getHandler().getId()));
BoundSkillInfo boundSkillInfo = boundSkills.remove(slot);
boundSkillInfo.unbind();

View File

@ -15,6 +15,7 @@ import io.lumine.mythic.lib.skill.Skill;
import io.lumine.mythic.lib.skill.handler.MythicLibSkillHandler;
import io.lumine.mythic.lib.skill.trigger.TriggerType;
import io.lumine.mythic.lib.version.VersionMaterial;
import jdk.jshell.execution.Util;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.api.player.profess.event.EventTrigger;
@ -39,6 +40,7 @@ import org.apache.commons.lang.Validate;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Utility;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.inventory.ItemStack;
@ -105,9 +107,11 @@ public class PlayerClass extends PostLoadObject implements ExperienceObject {
throw new IllegalArgumentException("Could not apply playerhead texture: " + exception.getMessage());
}
Validate.isTrue(config.isConfigurationSection("skill-slots"), "You must define the skills-slots for class " + id);
for (String key : config.getConfigurationSection("skill-slots").getKeys(false)) {
SkillSlot skillSlot = new SkillSlot(config.getConfigurationSection("skill-slots." + key));
skillSlots.put(skillSlot.getSlot(), skillSlot);
for (int i = 1; i < MMOCore.plugin.configManager.maxSlots + 1; i++) {
if (config.contains("skill-slots." + i))
skillSlots.put(i, new SkillSlot(config.getConfigurationSection("skill-slots." + i)));
else
skillSlots.put(i, new SkillSlot(i, 0, "true", "&eSkill Slot " + MMOCoreUtils.intToRoman(i), new ArrayList<>(), false, new ArrayList<>()));
}
for (String string : config.getStringList("display.lore"))
description.add(ChatColor.GRAY + MythicLib.plugin.parseColors(string));
@ -185,7 +189,7 @@ public class PlayerClass extends PostLoadObject implements ExperienceObject {
if (config.contains("skills." + key))
skills.put(key, new ClassSkill(registered, config.getConfigurationSection("skills." + key)));
else
skills.put(key, new ClassSkill(registered, 1, 1,false));
skills.put(key, new ClassSkill(registered, 1, 1, false));
}
castParticle = config.contains("cast-particle") ? new CastingParticle(config.getConfigurationSection("cast-particle")) : null;
@ -428,6 +432,10 @@ public class PlayerClass extends PostLoadObject implements ExperienceObject {
return skillSlots.get(slot);
}
public Collection<SkillSlot> getSlots() {
return skillSlots.values();
}
public ClassSkill getSkill(RegisteredSkill skill) {
return getSkill(skill.getHandler().getId());
}

View File

@ -2,31 +2,36 @@ package net.Indyuce.mmocore.api.player.profess.skillbinding;
import io.lumine.mythic.lib.api.MMOLineConfig;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.api.player.Unlockable;
import net.Indyuce.mmocore.api.quest.trigger.SkillBuffTrigger;
import net.Indyuce.mmocore.skill.ClassSkill;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import java.util.ArrayList;
import java.util.List;
public class SkillSlot {
public class SkillSlot implements Unlockable {
private final int slot, modelData;
private final String formula;
private final String name;
private final List<String> lore;
private final boolean isUnlockedByDefault;
private final List<SkillBuffTrigger> skillBuffTriggers;
private Material item;
public SkillSlot(int slot, int modelData, String formula, String name, List<String> lore, List<SkillBuffTrigger> skillBuffTriggers, Material item) {
public SkillSlot(int slot, int modelData, String formula, String name, List<String> lore, boolean isUnlockedByDefault, List<SkillBuffTrigger> skillBuffTriggers) {
this.slot = slot;
this.modelData = modelData;
this.formula = formula;
this.name = name;
this.lore = lore;
this.isUnlockedByDefault = isUnlockedByDefault;
this.skillBuffTriggers = skillBuffTriggers;
this.item = item;
}
public SkillSlot(ConfigurationSection section) {
@ -37,10 +42,11 @@ public class SkillSlot {
if (section.contains("item"))
this.item = Material.valueOf(section.getString("item"));
this.modelData = section.getInt("model-data", 0);
skillBuffTriggers= new ArrayList<>();
if(section.contains("skill-buffs"))
for(String skillBuff:section.getStringList("skill-buffs"))
if(skillBuff.startsWith("skill_buff")){
isUnlockedByDefault = section.getBoolean("unlocked-by-default", true);
skillBuffTriggers = new ArrayList<>();
if (section.contains("skill-buffs"))
for (String skillBuff : section.getStringList("skill-buffs"))
if (skillBuff.startsWith("skill_buff")) {
skillBuffTriggers.add((SkillBuffTrigger) MMOCore.plugin.loadManager.loadTrigger(new MMOLineConfig(skillBuff)));
}
}
@ -69,6 +75,10 @@ public class SkillSlot {
return modelData;
}
public boolean isUnlockedByDefault() {
return isUnlockedByDefault;
}
public List<SkillBuffTrigger> getSkillBuffTriggers() {
return skillBuffTriggers;
}
@ -76,4 +86,24 @@ public class SkillSlot {
public boolean canPlaceSkill(ClassSkill classSkill) {
return classSkill.getSkill().matchesFormula(formula);
}
@Override
public String getUnlockNamespacedKey() {
return "slot:"+slot;
}
/**
* If we lock a slot that had a skill bound
* to it we first unbind the attached skill.
*/
@Override
public void whenLocked(PlayerData playerData) {
if(playerData.hasSkillBound(slot))
playerData.unbindSkill(slot);
}
@Override
public void whenUnlocked(PlayerData playerData) {
}
}

View File

@ -0,0 +1,37 @@
package net.Indyuce.mmocore.api.quest.trigger;
import io.lumine.mythic.lib.api.MMOLineConfig;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.api.player.profess.skillbinding.SkillSlot;
import net.Indyuce.mmocore.api.quest.trigger.api.Removable;
import net.Indyuce.mmocore.skill.RegisteredSkill;
import org.apache.commons.lang.Validate;
import java.util.Objects;
public class UnlockSlotTrigger extends Trigger implements Removable {
private final int slot;
public UnlockSlotTrigger(MMOLineConfig config) {
super(config);
config.validateKeys("slot");
slot = Integer.parseInt("slot");
Validate.isTrue(slot > 0 && slot <= MMOCore.plugin.configManager.maxSlots, "The slot should be between 1 and " + MMOCore.plugin.configManager.maxSlots);
}
@Override
public void apply(PlayerData player) {
SkillSlot skillSlot = player.getProfess().getSkillSlot(slot);
if (!player.hasUnlocked(skillSlot))
player.unlock(skillSlot);
}
@Override
public void remove(PlayerData player) {
SkillSlot skillSlot = player.getProfess().getSkillSlot(slot);
if (player.hasUnlocked(skillSlot))
player.lock(skillSlot);
}
}

View File

@ -0,0 +1,79 @@
package net.Indyuce.mmocore.command.rpg.admin;
import io.lumine.mythic.lib.command.api.CommandTreeNode;
import io.lumine.mythic.lib.command.api.Parameter;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.api.player.profess.skillbinding.SkillSlot;
import net.Indyuce.mmocore.command.api.CommandVerbose;
import net.Indyuce.mmocore.skill.ClassSkill;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class SlotCommandTreeNode extends CommandTreeNode {
public SlotCommandTreeNode(CommandTreeNode parent) {
super(parent, "skill");
addChild(new LockSlotCommandTreeNode(this, "lock", true));
addChild(new LockSlotCommandTreeNode(this, "unlock", false));
}
public class LockSlotCommandTreeNode extends CommandTreeNode {
private final boolean lock;
public LockSlotCommandTreeNode(CommandTreeNode parent, String id, boolean lock) {
super(parent, id);
this.lock = lock;
addParameter(Parameter.PLAYER);
addParameter(Parameter.AMOUNT);
}
@Override
public CommandResult execute(CommandSender sender, String[] args) {
if (args.length < 5)
return CommandResult.THROW_USAGE;
Player player = Bukkit.getPlayer(args[3]);
if (player == null) {
sender.sendMessage(ChatColor.RED + "Could not find the player called " + args[3] + ".");
return CommandResult.FAILURE;
}
PlayerData playerData = PlayerData.get(player);
int slot;
try {
slot = Integer.parseInt(args[4]);
} catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + args[4] + " is not a valid number.");
return CommandResult.FAILURE;
}
if (slot <= 0) {
sender.sendMessage(ChatColor.RED + "The slot can't be negative.");
return CommandResult.FAILURE;
}
SkillSlot skillSlot=playerData.getProfess().getSkillSlot(slot);
if (lock) {
if (!playerData.hasUnlocked(skillSlot)) {
CommandVerbose.verbose(sender, CommandVerbose.CommandType.SKILL, ChatColor.RED + "The skill slot " + skillSlot.getName() + " is already locked" + " for " + player.getName());
return CommandResult.SUCCESS;
}
playerData.lock(skillSlot);
} else {
if (playerData.hasUnlocked(skillSlot)) {
CommandVerbose.verbose(sender, CommandVerbose.CommandType.SKILL, ChatColor.RED + "The skill slot " + skillSlot.getName() + " is already unlocked" + " for " + player.getName());
return CommandResult.SUCCESS;
}
playerData.unlock(skillSlot);
}
CommandVerbose.verbose(sender, CommandVerbose.CommandType.SKILL, ChatColor.GOLD + "The skill slot " + skillSlot.getName() + " is now " + (lock ? "locked" : "unlocked" + " for " + player.getName()));
return CommandResult.SUCCESS;
}
}
@Override
public CommandResult execute(CommandSender sender, String[] args) {
return CommandResult.THROW_USAGE;
}
}

View File

@ -4,6 +4,7 @@ import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.api.item.ItemTag;
import io.lumine.mythic.lib.api.item.NBTItem;
import net.Indyuce.mmocore.MMOCore;
import net.Indyuce.mmocore.api.SoundEvent;
import net.Indyuce.mmocore.api.player.PlayerData;
import net.Indyuce.mmocore.api.player.profess.skillbinding.SkillSlot;
import net.Indyuce.mmocore.api.util.MMOCoreUtils;
@ -15,9 +16,7 @@ import net.Indyuce.mmocore.gui.api.item.Placeholders;
import net.Indyuce.mmocore.gui.api.item.SimplePlaceholderItem;
import net.Indyuce.mmocore.skill.ClassSkill;
import net.Indyuce.mmocore.skill.RegisteredSkill;
import net.Indyuce.mmocore.api.SoundEvent;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Sound;
@ -66,49 +65,7 @@ public class SkillList extends EditableInventory {
}
if (function.equals("slot"))
return new SlotItem(config) {
@Override
public ItemStack display(SkillViewerInventory inv, int n) {
if (!inv.getPlayerData().getProfess().hasSlot(n + 1)) {
return new ItemStack(Material.AIR);
}
SkillSlot skillSlot = inv.getPlayerData().getProfess().getSkillSlot(n + 1);
ItemStack item = super.display(inv, n);
if (!inv.getPlayerData().hasSkillBound(n + 1)) {
//If there is an item filled in the slot config it shows it, else shows the default item.
Material material = skillSlot.hasItem() ? skillSlot.getItem() : super.emptyMaterial;
int customModelData = skillSlot.hasItem() ? skillSlot.getModelData() : super.emptyCMD;
item.setType(material);
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(MMOCore.plugin.placeholderParser.parse(inv.getPlayerData().getPlayer(), skillSlot.getName()));
List<String> lore = skillSlot.getLore()
.stream()
.map(str -> MMOCore.plugin.placeholderParser.parse(inv.getPlayerData().getPlayer(), str))
.collect(Collectors.toList());
meta.setLore(lore);
if (MythicLib.plugin.getVersion().isStrictlyHigher(1, 13)) {
meta.setCustomModelData(customModelData);
}
item.setItemMeta(meta);
}
return item;
}
/**
* This should only be called when there is a skill bound.
*/
@Override
public Placeholders getPlaceholders(SkillViewerInventory inv, int n) {
Placeholders holders = super.getPlaceholders(inv, n);
String none = MythicLib.plugin.parseColors(config.getString("no-skill"));
RegisteredSkill skill = inv.getPlayerData().hasSkillBound(n + 1) ? inv.getPlayerData().getBoundSkill(n + 1).getSkill() : null;
holders.register("skill", skill == null ? none : skill.getName());
return holders;
}
};
return new SlotItem(config);
if (function.equals("previous"))
return new SimplePlaceholderItem<SkillViewerInventory>(config) {
@ -200,6 +157,39 @@ public class SkillList extends EditableInventory {
emptyCMD = config.getInt("empty-custom-model-data", getModelData());
}
@Override
public ItemStack display(SkillViewerInventory inv, int n) {
if (!inv.getPlayerData().hasUnlocked("slot:" + n)) {
return new ItemStack(Material.AIR);
}
SkillSlot skillSlot = inv.getPlayerData().getProfess().getSkillSlot(n + 1);
ItemStack item = super.display(inv, n);
if (!inv.getPlayerData().hasSkillBound(n + 1)) {
//If there is an item filled in the slot config it shows it, else shows the default item.
Material material = skillSlot.hasItem() ? skillSlot.getItem() : emptyMaterial;
int customModelData = skillSlot.hasItem() ? skillSlot.getModelData() : emptyCMD;
item.setType(material);
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(MMOCore.plugin.placeholderParser.parse(inv.getPlayerData().getPlayer(), skillSlot.getName()));
List<String> lore = skillSlot.getLore()
.stream()
.map(str -> MMOCore.plugin.placeholderParser.parse(inv.getPlayerData().getPlayer(), str))
.collect(Collectors.toList());
meta.setLore(lore);
if (MythicLib.plugin.getVersion().isStrictlyHigher(1, 13)) {
meta.setCustomModelData(customModelData);
}
item.setItemMeta(meta);
}
return item;
}
/**
* This should only be called when there is a skill bound.
*/
@Override
public Placeholders getPlaceholders(SkillViewerInventory inv, int n) {
RegisteredSkill selected = inv.selected.getSkill();
@ -209,10 +199,12 @@ public class SkillList extends EditableInventory {
holders.register("index", "" + (n + 1));
holders.register("slot", MMOCoreUtils.intToRoman(n + 1));
holders.register("selected", selected == null ? none : selected.getName());
RegisteredSkill skill = inv.getPlayerData().hasSkillBound(n + 1) ? inv.getPlayerData().getBoundSkill(n + 1).getSkill() : null;
holders.register("skill", skill == null ? none : skill.getName());
return holders;
}
@Override
public boolean hasDifferentDisplay() {
return true;
@ -317,7 +309,9 @@ public class SkillList extends EditableInventory {
private final List<Integer> skillSlots;
private final List<Integer> slotSlots;
//The skill the player Selected
/**
* The skill the player Selected
*/
private ClassSkill selected;
private int page = 0;
@ -329,6 +323,7 @@ public class SkillList extends EditableInventory {
.sorted(Comparator.comparingInt(ClassSkill::getUnlockLevel))
.collect(Collectors.toList());
skillSlots = getEditable().getByFunction("skill").getSlots();
Validate.notNull(getEditable().getByFunction("slot"), "Your skill GUI config file is out-of-date, please regenerate it.");
slotSlots = getEditable().getByFunction("slot").getSlots();
selected = skills.get(page * skillSlots.size());

View File

@ -34,7 +34,7 @@ public class ConfigManager {
public long combatLogTimer, lootChestExpireTime, lootChestPlayerCooldown, globalSkillCooldown;
public double lootChestsChanceWeight, dropItemsChanceWeight, fishingDropsChanceWeight, partyMaxExpSplitRange, pvpModeToggleOnCooldown, pvpModeToggleOffCooldown, pvpModeCombatCooldown,
pvpModeCombatTimeout, pvpModeInvulnerabilityTimeRegionChange, pvpModeInvulnerabilityTimeCommand, pvpModeRegionEnterCooldown, pvpModeRegionLeaveCooldown;
public int maxPartyLevelDifference, maxBoundActiveSkills, maxBoundPassiveSkills, minCombatLevel, maxCombatLevelDifference;
public int maxPartyLevelDifference, maxSlots, minCombatLevel, maxCombatLevelDifference;
public final List<EntityDamageEvent.DamageCause> combatLogDamageCauses = new ArrayList<>();
private final FileConfiguration messages;
@ -157,8 +157,7 @@ public class ConfigManager {
canCreativeCast = MMOCore.plugin.getConfig().getBoolean("can-creative-cast");
cobbleGeneratorXP = MMOCore.plugin.getConfig().getBoolean("should-cobblestone-generators-give-exp");
saveDefaultClassInfo = MMOCore.plugin.getConfig().getBoolean("save-default-class-info");
maxBoundActiveSkills = MMOCore.plugin.getConfig().getInt("max-bound-active-skills", 6);
maxBoundPassiveSkills = MMOCore.plugin.getConfig().getInt("max-bound-passive-skills", 3);
maxSlots = MMOCore.plugin.getConfig().getInt("max-slots");
overrideVanillaExp = MMOCore.plugin.getConfig().getBoolean("override-vanilla-exp");
}

View File

@ -197,16 +197,14 @@ death-exp-loss:
# Percentage of current EXP you lose when dying.
percent: 30
#Default max bound active and passive skills.
#These value can be modified for each class in the class yml.
max-bound-active-skills: 6
#Maximum number of slot. This means that you can't unlock a slot greater than max-slots. (The slot count starts at 1 & end at max-slots).
max-slots: 10
#If you want players to bound their passive skills.
#If false, all the passive skills unlocked will be active
#Also set max-bound-passive-skills to 0 if seting passive-skill-need-bound to false.
passive-skill-need-bound: true
max-bound-passive-skills: 3
# Fun extra RPG feature that switches the player's hotbar with
# the 9 lower row items of his inventory. This allows the player