blob: d20828c39c758cb5b2df60db0fa56a5da9723190 [file] [log] [blame] [raw]
package net.glowstone.constants;
import io.papermc.paper.enchantments.EnchantmentRarity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.glowstone.inventory.MaterialMatcher;
import net.glowstone.io.nbt.NbtSerialization;
import net.glowstone.util.WeightedRandom.Choice;
import net.kyori.adventure.text.Component;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.enchantments.EnchantmentTarget;
import org.bukkit.entity.EntityCategory;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Definitions of enchantment types.
*/
public final class GlowEnchantment extends Enchantment implements Choice {
private static final List<String> VANILLA_IDS = new ArrayList<>();
private static final HashMap<String, Enchantment> BY_VANILLA_ID = new HashMap<>();
private static final MaterialMatcher MELEE = item ->
EnchantmentTarget.WEAPON.includes(item)
|| item.equals(Material.WOODEN_AXE)
|| item.equals(Material.STONE_AXE)
|| item.equals(Material.IRON_AXE)
|| item.equals(Material.DIAMOND_AXE)
|| item.equals(Material.GOLDEN_AXE);
private static final MaterialMatcher BASE_TOOLS = item -> item.equals(Material.WOODEN_SHOVEL)
|| item.equals(Material.STONE_SHOVEL)
|| item.equals(Material.IRON_SHOVEL)
|| item.equals(Material.DIAMOND_SHOVEL)
|| item.equals(Material.GOLDEN_SHOVEL)
|| item.equals(Material.WOODEN_PICKAXE)
|| item.equals(Material.STONE_PICKAXE)
|| item.equals(Material.IRON_PICKAXE)
|| item.equals(Material.DIAMOND_PICKAXE)
|| item.equals(Material.GOLDEN_PICKAXE)
|| item.equals(Material.WOODEN_AXE)
|| item.equals(Material.STONE_AXE)
|| item.equals(Material.IRON_AXE)
|| item.equals(Material.DIAMOND_AXE)
|| item.equals(Material.GOLDEN_AXE);
private static final MaterialMatcher DIGGING_TOOLS = material -> BASE_TOOLS.matches(material)
|| material == Material.SHEARS;
private static final MaterialMatcher ALL_EQUIPMENT = material ->
EnchantmentTarget.TOOL.includes(material)
|| EnchantmentTarget.WEAPON.includes(material)
|| EnchantmentTarget.ARMOR.includes(material)
|| material == Material.FISHING_ROD
|| material == Material.BOW
|| material == Material.CARROT_ON_A_STICK;
static {
VANILLA_IDS.addAll(
Arrays.stream(GlowEnchantment.Impl.values()).map(GlowEnchantment.Impl::getVanillaId)
.collect(Collectors.toSet()));
}
private final Impl impl;
private GlowEnchantment(Impl impl) {
super(NbtSerialization.namespacedKeyFromString(impl.getVanillaId()));
this.impl = impl;
}
/**
* Register all enchantment types with Enchantment.
*/
public static void register() {
for (Impl impl : Impl.values()) {
GlowEnchantment enchantment = new GlowEnchantment(impl);
BY_VANILLA_ID.put(impl.getVanillaId(), enchantment);
registerEnchantment(enchantment);
}
stopAcceptingRegistrations();
}
public static List<String> getVanillaIds() {
return VANILLA_IDS;
}
/**
* Parses a PotionEffect id or name if possible.
*
* @param enchantmentName The Enchantment name.
* @return The associated Enchantment, or null.
*/
public static Enchantment parseEnchantment(String enchantmentName) {
if (enchantmentName.startsWith("minecraft:")) {
Enchantment enchantment = GlowEnchantment.getByVanillaId(enchantmentName);
if (enchantment == null) {
return null;
} else {
return enchantment;
}
} else {
Enchantment enchantment = Enchantment.getByName(enchantmentName);
if (enchantment == null) {
return null;
} else {
return enchantment;
}
}
}
public static Enchantment getByVanillaId(String vanillaId) {
return BY_VANILLA_ID.get(vanillaId);
}
@Override
public String getName() {
// nb: returns enum name, not text name
return impl.name();
}
public int getId() {
return impl.id;
}
@Override
public int getMaxLevel() {
return impl.maxLevel;
}
@Override
public int getStartLevel() {
return 1;
}
@Override
public EnchantmentTarget getItemTarget() {
return impl.target;
}
@Override
public boolean conflictsWith(Enchantment other) {
return impl.group != Group.NONE && impl.group == ((GlowEnchantment) other).impl.group;
}
// TODO: primary and secondary items.
// primary items can be enchanted using an enchanting table
// secondary items can only be enchanted using an enchanted book and anvil
// once that code is implemented, replace the return below
@Override
public boolean canEnchantItem(ItemStack item) {
// TODO: return canEnchantPrimary(item) || canEnchantSecondary(item);
return impl.matcher.matches(item.getType());
}
@Override
public @NotNull Component displayName(int i) {
throw new UnsupportedOperationException("Adventure API is not yet supported.");
}
@Override
public boolean isTradeable() {
throw new UnsupportedOperationException();
}
@Override
public boolean isDiscoverable() {
throw new UnsupportedOperationException();
}
/**
* If this enchantment can be applied to this item using an enchantment table.
*
* @return If this item is a primary item for this enchantment
*/
public boolean canEnchantPrimary(ItemStack item) {
return false;
}
/**
* If this enchantment cannot be applied using an enchantment table but can be applied using
* an enchanted book and anvil.
*
* @return If this item is a secondary item for this enchantment
*/
public boolean canEnchantSecondary(ItemStack item) {
return false;
}
/**
* The rarity of the enchantment, kept for compatibility with Bukkit.
*
* @see #getRarity()
*/
@Override
public int getWeight() {
return getRarity().getWeight();
}
/**
* Treasure enchantments can only be obtained from chest loot, fishing, or trading for enchanted
* books.
*
* @return true if the enchantment is a treasure, otherwise false
*/
@Override
public boolean isTreasure() {
return impl.treasure;
}
@Override
public boolean isCursed() {
return impl.cursed;
}
/**
* Represents the rarity of obtaining an enchantment.
*
* @return the rarity of the enchantment
*/
public EnchantmentRarity getRarity() {
return impl.rarity;
}
@Override
public float getDamageIncrease(int i, @NotNull EntityCategory entityCategory) {
return 0;
}
@Override
public @NotNull Set<EquipmentSlot> getActiveSlots() {
return null;
}
public boolean isInRange(int level, int modifier) {
return modifier >= impl.getMinRange(level) && modifier <= impl.getMaxRange(level);
}
// TODO: GlowEnchantment builder instead of Impl enum init
@RequiredArgsConstructor
private enum Impl {
PROTECTION_ENVIRONMENTAL(0, "Protection", 4, EnchantmentRarity.COMMON, 1, 11, 20,
EnchantmentTarget.ARMOR, Group.PROTECT, "minecraft:protection"),
PROTECTION_FIRE(1, "Fire Protection", 4, EnchantmentRarity.UNCOMMON, 10, 8, 12,
EnchantmentTarget.ARMOR, Group.PROTECT, "minecraft:fire_protection"),
PROTECTION_FALL(2, "Feather Falling", 4, EnchantmentRarity.UNCOMMON, 5, 6, 10,
EnchantmentTarget.ARMOR_FEET, Group.PROTECT, "minecraft:feather_falling"),
PROTECTION_EXPLOSIONS(3, "Blast Protection", 4, EnchantmentRarity.RARE, 5, 8, 12,
EnchantmentTarget.ARMOR, "minecraft:blast_protection"),
PROTECTION_PROJECTILE(4, "Projectile Protection", 4, EnchantmentRarity.UNCOMMON, 3, 6, 15,
EnchantmentTarget.ARMOR, Group.PROTECT, "minecraft:projectile_protection"),
OXYGEN(5, "Respiration", 3, EnchantmentRarity.RARE, 10, 10, 30,
EnchantmentTarget.ARMOR_HEAD,
"minecraft:respiration"),
WATER_WORKER(6, "Aqua Affinity", 1, EnchantmentRarity.RARE, 1, 0, 40,
EnchantmentTarget.ARMOR_HEAD,
"minecraft:aqua_affinity"),
THORNS(7, "Thorns", 3, EnchantmentRarity.VERY_RARE, 10, 20, 50,
EnchantmentTarget.ARMOR_TORSO,
new MatcherAdapter(EnchantmentTarget.ARMOR), "minecraft:thorns"),
DEPTH_STRIDER(8, "Depth Strider", 3, EnchantmentRarity.RARE, 10, 10, 15,
EnchantmentTarget.ARMOR_FEET,
"minecraft:depth_strider"),
FROST_WALKER(9, "Frost Walker", 2, EnchantmentRarity.RARE, 10, 10, 15,
EnchantmentTarget.ARMOR_FEET,
"minecraft:frost_walker"),
BINDING_CURSE(10, "Curse of Binding", 1, EnchantmentRarity.VERY_RARE, true, 25, 0, 25,
EnchantmentTarget.ARMOR, new MatcherAdapter(EnchantmentTarget.ARMOR), Group.NONE,
"minecraft:binding_curse"),
DAMAGE_ALL(16, "Sharpness", 5, EnchantmentRarity.COMMON, 1, 11, 20,
EnchantmentTarget.WEAPON,
MELEE, Group.ATTACK, "minecraft:sharpness"),
DAMAGE_UNDEAD(17, "Smite", 5, EnchantmentRarity.UNCOMMON, 5, 8, 20,
EnchantmentTarget.WEAPON,
MELEE, Group.ATTACK, "minecraft:smite"),
DAMAGE_ARTHROPODS(18, "Bane of Arthropods", 5, EnchantmentRarity.UNCOMMON, 5, 8, 20,
EnchantmentTarget.WEAPON, MELEE, Group.ATTACK, "minecraft:bane_of_arthropods"),
KNOCKBACK(19, "Knockback", 2, EnchantmentRarity.UNCOMMON, 5, 20, 50,
EnchantmentTarget.WEAPON,
"minecraft:knockback"),
FIRE_ASPECT(20, "Fire Aspect", 2, EnchantmentRarity.RARE, 10, 20, 50,
EnchantmentTarget.WEAPON,
"minecraft:fire_aspect"),
LOOT_BONUS_MOBS(21, "Looting", 3, EnchantmentRarity.RARE, 15, 9, 50,
EnchantmentTarget.WEAPON,
"minecraft:looting"),
SWEEPING_EDGE(22, "Sweeping Edge", 3, EnchantmentRarity.RARE, 5, 9, 15,
EnchantmentTarget.WEAPON,
"minecraft:sweeping"),
DIG_SPEED(32, "Efficiency", 5, EnchantmentRarity.COMMON, 1, 10, 50, EnchantmentTarget.TOOL,
DIGGING_TOOLS, "minecraft:efficiency"),
SILK_TOUCH(33, "Silk Touch", 1, EnchantmentRarity.VERY_RARE, false, 15, 0, 50,
EnchantmentTarget.TOOL, BASE_TOOLS, Group.DIG, "minecraft:silk_touch"),
DURABILITY(34, "Unbreaking", 3, EnchantmentRarity.UNCOMMON, 5, 8, 50,
EnchantmentTarget.TOOL,
ALL_EQUIPMENT, "minecraft:unbreaking"),
LOOT_BONUS_BLOCKS(35, "Fortune", 3, EnchantmentRarity.RARE, 15, 9, 50,
EnchantmentTarget.TOOL,
BASE_TOOLS, Group.DIG, "minecraft:fortune"),
ARROW_DAMAGE(48, "Power", 5, EnchantmentRarity.COMMON, 1, 10, 15, EnchantmentTarget.BOW,
"minecraft:power"),
ARROW_KNOCKBACK(49, "Punch", 2, EnchantmentRarity.RARE, 12, 20, 25, EnchantmentTarget.BOW,
"minecraft:punch"),
ARROW_FIRE(50, "Flame", 1, EnchantmentRarity.RARE, 20, 0, 30, EnchantmentTarget.BOW,
"minecraft:flame"),
ARROW_INFINITE(51, "Infinity", 1, EnchantmentRarity.VERY_RARE, 20, 0, 30,
EnchantmentTarget.BOW, Group.USAGE,
"minecraft:infinity"),
LUCK(61, "Luck of the Sea", 3, EnchantmentRarity.RARE, 15, 9, 50,
EnchantmentTarget.FISHING_ROD,
"minecraft:luck_of_the_sea"),
LURE(62, "Lure", 3, EnchantmentRarity.RARE, 15, 9, 50, EnchantmentTarget.FISHING_ROD,
"minecraft:lure"),
LOYALTY(65, "Loyalty", 3, EnchantmentRarity.UNCOMMON, 17, 5, 23, 50,
EnchantmentTarget.TRIDENT, Group.TRIDENT_THROW, "minecraft:loyalty"),
IMPALING(66, "Impaling", 5, EnchantmentRarity.RARE, 1, 8, 20, EnchantmentTarget.TRIDENT,
"minecraft:impaling"),
RIPTIDE(67, "Riptide", 3, EnchantmentRarity.RARE, 17, 10, 13, 50, EnchantmentTarget.TRIDENT,
Group.TRIDENT_THROW, "minecraft:riptide"),
CHANNELING(68, "Channeling", 1, EnchantmentRarity.VERY_RARE, 25, 0, 25,
EnchantmentTarget.TRIDENT, "minecraft:channeling"),
MENDING(70, "Mending", 1, EnchantmentRarity.RARE, 25, 0, 50, EnchantmentTarget.ALL,
Group.USAGE,
"minecraft:mending"),
VANISHING_CURSE(71, "Curse of Vanishing", 1, EnchantmentRarity.VERY_RARE, true, 25, 0, 25,
EnchantmentTarget.ALL, new MatcherAdapter(EnchantmentTarget.ALL), Group.NONE,
"minecraft:vanishing_curse");
private final int id;
private final String name;
private final int maxLevel;
private final EnchantmentRarity rarity;
private final boolean treasure;
private final boolean cursed;
private final int minValue; // https://minecraft.gamepedia.com/Enchanting/Levels
private final int minIncrement;
private final int maxIncrement;
private final int maxValue;
private final EnchantmentTarget target;
private final MaterialMatcher matcher;
private final Group group;
@Getter
private final String vanillaId;
Impl(int id, String name, int max, EnchantmentRarity rarity, int minValue, int minInc,
int maxInc,
EnchantmentTarget target, String vanillaId) {
this(id, name, max, rarity, false, minValue, minInc, maxInc, target,
new MatcherAdapter(target), Group.NONE, vanillaId);
}
Impl(int id, String name, int max, EnchantmentRarity rarity, int minValue, int minInc,
int maxInc,
EnchantmentTarget target, Group group, String vanillaId) {
this(id, name, max, rarity, false, minValue, minInc, maxInc, target,
new MatcherAdapter(target), group, vanillaId);
}
Impl(int id, String name, int max, EnchantmentRarity rarity, int minValue, int minInc,
int maxInc,
EnchantmentTarget target, MaterialMatcher matcher,
String vanillaId) {
this(id, name, max, rarity, false, minValue, minInc, maxInc, target,
matcher, Group.NONE, vanillaId);
}
Impl(int id, String name, int max, EnchantmentRarity rarity, int minValue, int minInc,
int maxInc, EnchantmentTarget target, MatcherAdapter matcherAdapter,
String vanillaId) {
this(id, name, max, rarity, false, minValue, minInc, maxInc, target, matcherAdapter,
Group.NONE, vanillaId);
}
Impl(int id, String name, int max, EnchantmentRarity rarity, int minValue, int minInc,
int maxInc,
EnchantmentTarget target, MaterialMatcher matcher, Group group, String vanillaId) {
this(id, name, max, rarity, false, minValue, minInc, maxInc, target, matcher,
group, vanillaId);
}
Impl(int id, String name, int max, EnchantmentRarity rarity, boolean treasure, int minValue,
int minInc, int maxInc, EnchantmentTarget target,
MaterialMatcher matcher, Group group, String vanillaId) {
this(id, name, max, rarity, treasure, minValue, minInc, maxInc, Integer.MAX_VALUE,
target,
matcher, group, vanillaId);
}
Impl(int id, String name, int max, EnchantmentRarity rarity, boolean treasure, int minValue,
int minInc, int maxInc, int maxValue, EnchantmentTarget target,
MaterialMatcher matcher, Group group, String vanillaId) {
this(id, name, max, rarity, treasure, false, minValue, minInc, maxInc, maxValue, target,
matcher, group, vanillaId);
}
Impl(int id, String name, int max, EnchantmentRarity rarity, int minValue, int minInc,
int maxInc, int maxValue, EnchantmentTarget target, Group group, String vanillaId) {
this(id, name, max, rarity, false, minValue, minInc, maxInc, maxValue, target,
new MatcherAdapter(target), group, vanillaId);
}
int getMinRange(int modifier) {
modifier = modifier - 1; // Formula depends on input 1 being 0 for "no offset"
return minIncrement * modifier + minValue;
}
int getMaxRange(int modifier) {
return Math.min(getMinRange(modifier) + maxIncrement, maxValue);
}
}
private enum Group {
NONE,
PROTECT,
ATTACK,
DIG,
USAGE,
ARROW,
TRIDENT_THROW // TODO: riptide incompatible with loyalty and channeling, but loyalty and channeling not incompatible, so group system does not work
}
private static class MatcherAdapter implements MaterialMatcher {
private final EnchantmentTarget target;
public MatcherAdapter(EnchantmentTarget target) {
this.target = target;
}
@Override
public boolean matches(Material material) {
return target.includes(material);
}
}
}