Compare commits

...

30 commits

Author SHA1 Message Date
0dfe0933ca
beta version up 2025-03-20 17:34:01 +01:00
5c17cefd53
add paper order test 2025-03-20 17:34:01 +01:00
85260fcb50
Add single paper tests 2025-03-20 17:34:00 +01:00
2c0623cd3e
Added most of the lore edit unit tests 2025-03-20 17:34:00 +01:00
064f446d68
Fixed Book remove do consume using paper append do consume 2025-03-20 17:34:00 +01:00
8d0d34ae77
fix lore edit price issue 2025-03-20 17:33:59 +01:00
ffd3baa886
update mockbukkit 2025-03-20 17:33:59 +01:00
c1b252520e
Add cursor click test 2025-03-20 17:33:59 +01:00
736b41f77d
fix disabled permission not working 2025-03-20 17:33:59 +01:00
0c0df6b856
update default configs 2025-03-20 17:33:58 +01:00
6958bd0aff
fix "revert color" not using the right function 2025-03-20 17:33:58 +01:00
15f82abf79
fix circular dependency
WHY is there no warning when it happens
2025-03-20 17:33:58 +01:00
c71dfb490e
change color configs & add do consume to lore removal 2025-03-20 17:33:58 +01:00
d61ef6c0d6
2 minor fix
fix work penalty type setting gui not saving on the right path
fix anvil use type not using correct path for paper
2025-03-20 17:33:57 +01:00
87dd099fca
fix do consume being inverted for book edit 2025-03-20 17:33:57 +01:00
5d4ec394dd
fixed item sometimes dupe 2025-03-20 17:33:57 +01:00
079b7bec57
fix stupid init bug 2025-03-20 17:33:57 +01:00
074313fb7c
lore edit color should work 2025-03-20 17:33:56 +01:00
5f3eed08e2
multi line per line xp cost added 2025-03-20 17:33:56 +01:00
53173bcd33
remove per line cost on single line use type 2025-03-20 17:33:56 +01:00
44f49d7ae6
base xp cost + work penalty 2025-03-20 17:33:55 +01:00
050d7cb79f
made edit type consume 2025-03-20 17:33:55 +01:00
32a4f6caf4
Set lore edit defaults if absent
Also fix lore edit being enabled by default
2025-03-20 17:33:55 +01:00
bc531175b2
Moved some and added more config 2025-03-20 17:33:55 +01:00
00efd7e4e4
always display changed left item to user 2025-03-20 17:33:54 +01:00
ea59081ca8
paper lore edit partially works 2025-03-20 17:33:54 +01:00
1b39707500
book edit kind of working 2025-03-20 17:33:54 +01:00
2a1cade9f1
book lore edit start 2025-03-20 17:33:53 +01:00
11cd5d1842
Forgot about lore edit permission 2025-03-20 17:33:53 +01:00
a0fe84a9d4
Lore edit config values 2025-03-20 17:33:47 +01:00
29 changed files with 2506 additions and 236 deletions

View file

@ -38,9 +38,16 @@ ca.bypass.fuse: Allow player to combine every enchantments to every item (no cus
ca.bypass.level: Allow player to bypass every level limit (no custom limit) ca.bypass.level: Allow player to bypass every level limit (no custom limit)
ca.command.reload: Allow administrator to reload the plugin's configs ca.command.reload: Allow administrator to reload the plugin's configs
ca.config.edit: Allow administrator to edit the plugin's config in game ca.config.edit: Allow administrator to edit the plugin's config in game
# Related to use of color (usage of permission for color is toggleable in basic config gui or config.yml) # Bellow permissions also require some config change to allow usage of features
ca.color.code: Allow player to use color code if permission is required (toggleable) # usage of these permission is toggleable in basic config gui or config.yml
ca.color.hex: Allow player to use hexadecimal color if permission is required (toggleable)
# Permissions related to use of color
ca.color.code: Allow player to use color code if enabled (toggleable)
ca.color.hex: Allow player to use hexadecimal color if enabled (toggleable)
# Permissions related to edition of the lore
ca.lore_edit.book: Allow player to edit lore via book and quil if enabled (toggleable)
ca.lore_edit.paper: Allow player to edit lore via paper if enabled (toggleable)
``` ```
### Commands ### Commands

View file

@ -16,7 +16,7 @@ plugins {
} }
group = "xyz.alexcrea" group = "xyz.alexcrea"
version = "1.9.0" version = "1.10.0-beta.1"
repositories { repositories {
// EcoEnchants // EcoEnchants
@ -72,7 +72,7 @@ dependencies {
implementation(kotlin("stdlib")) implementation(kotlin("stdlib"))
// Test dependency // Test dependency
testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.21.0") testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.37.0")
testRuntimeOnly("commons-lang:commons-lang:2.6") testRuntimeOnly("commons-lang:commons-lang:2.6")
} }

View file

@ -270,6 +270,91 @@ disable-merge-over:
# If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied # If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied
#minecraft:unbreaking: 2 #minecraft:unbreaking: 2
# Settings for lore modification
lore_edit:
book_and_quil:
# Permission is ca.lore_edit.book
use_permission: true
append:
# If adding lore using book & quil is enabled
enabled: false
# Cost used every time
fixed_cost: 1
# Cost used for every lore line added
per_line_cost: 0
# Use left item vanilla cost penalty if any
shared_increase: false
# Increase shared left item cost penalty
shared_additive: false
# If adding the lore consume the book & quil
do_consume: false
# Allow using color code and hexadecimal color when editing lore via book & quil
#
# Color code are prefixed by "&" and hexadecimal color by "#"
# Color code will not be applied if it colors nothing. "&&" can be used to write "&"
allow_color_code: true
allow_hexadecimal_color: true
use_cost: 0
remove:
# If removing lore using book & quil is enabled
enabled: false
# Cost used every time
fixed_cost: 1
# Cost used for every lore line removed
per_line_cost: 0
# Use left item vanilla cost penalty if any
shared_increase: false
# Increase shared left item cost penalty
shared_additive: false
# If removing the lore consume the book & quil
do_consume: false
# If the color should get back to color code or hex format
remove_color_on_remove: true
# Cost of replacing colors
remove_color_cost: 0
paper:
# Permission is ca.lore_edit.paper
use_permission: true
# what order should the lines should get added/removed (start/end, if invalid or not present will be end)
order: "end"
append_line:
# If adding lore line using paper is enabled
enabled: false
# Cost used every time
fixed_cost: 1
# Use left item vanilla cost penalty if any
shared_increase: false
# Increase shared left item cost penalty
shared_additive: false
# If adding the lore line consume the paper
do_consume: false
# Allow using color code and hexadecimal color when editing lore via book & quil
#
# Color code are prefixed by "&" and hexadecimal color by "#"
# Color code will not be applied if it colors nothing. "&&" can be used to write "&"
allow_color_code: true
allow_hexadecimal_color: true
color_use_cost: 0
remove_line:
# If removing lore line using paper is enabled
enabled: false
# Cost used every time
fixed_cost: 1
# Use left item vanilla cost penalty if any
shared_increase: false
# Increase shared left item cost penalty
shared_additive: false
# If removing the lore line consume the paper
do_consume: false
# If the color should get back to color code or hex format
remove_color_on_remove: true
# Cost of replacing colors
remove_color_cost: 0
# Whether to show debug logging # Whether to show debug logging
debug_log: false debug_log: false

View file

@ -270,6 +270,91 @@ disable-merge-over:
# If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied # If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied
#minecraft:unbreaking: 2 #minecraft:unbreaking: 2
# Settings for lore modification
lore_edit:
book_and_quil:
# Permission is ca.lore_edit.book
use_permission: true
append:
# If adding lore using book & quil is enabled
enabled: false
# Cost used every time
fixed_cost: 1
# Cost used for every lore line added
per_line_cost: 0
# Use left item vanilla cost penalty if any
shared_increase: false
# Increase shared left item cost penalty
shared_additive: false
# If adding the lore consume the book & quil
do_consume: false
# Allow using color code and hexadecimal color when editing lore via book & quil
#
# Color code are prefixed by "&" and hexadecimal color by "#"
# Color code will not be applied if it colors nothing. "&&" can be used to write "&"
allow_color_code: true
allow_hexadecimal_color: true
use_cost: 0
remove:
# If removing lore using book & quil is enabled
enabled: false
# Cost used every time
fixed_cost: 1
# Cost used for every lore line removed
per_line_cost: 0
# Use left item vanilla cost penalty if any
shared_increase: false
# Increase shared left item cost penalty
shared_additive: false
# If removing the lore consume the book & quil
do_consume: false
# If the color should get back to color code or hex format
remove_color_on_remove: true
# Cost of replacing colors
remove_color_cost: 0
paper:
# Permission is ca.lore_edit.paper
use_permission: true
# what order should the lines should get added/removed (start/end, if invalid or not present will be end)
order: "end"
append_line:
# If adding lore line using paper is enabled
enabled: false
# Cost used every time
fixed_cost: 1
# Use left item vanilla cost penalty if any
shared_increase: false
# Increase shared left item cost penalty
shared_additive: false
# If adding the lore line consume the paper
do_consume: false
# Allow using color code and hexadecimal color when editing lore via book & quil
#
# Color code are prefixed by "&" and hexadecimal color by "#"
# Color code will not be applied if it colors nothing. "&&" can be used to write "&"
allow_color_code: true
allow_hexadecimal_color: true
color_use_cost: 0
remove_line:
# If removing lore line using paper is enabled
enabled: false
# Cost used every time
fixed_cost: 1
# Use left item vanilla cost penalty if any
shared_increase: false
# Increase shared left item cost penalty
shared_additive: false
# If removing the lore line consume the paper
do_consume: false
# If the color should get back to color code or hex format
remove_color_on_remove: true
# Cost of replacing colors
remove_color_cost: 0
# Whether to show debug logging # Whether to show debug logging
debug_log: false debug_log: false

View file

@ -39,7 +39,7 @@ public class WorkPenaltyTypeSettingGui extends AbstractSettingGui {
this.currentType = ConfigOptions.INSTANCE.getWorkPenaltyType(); this.currentType = ConfigOptions.INSTANCE.getWorkPenaltyType();
this.items = new EnumMap<>(this.currentType.getPartMap()); this.items = new EnumMap<>(this.currentType.getPartMap());
for (AnvilUseType type : AnvilUseType.getEntries()) { for (AnvilUseType type : useTypes.keySet()) {
updateGuiForType(type); updateGuiForType(type);
} }
} }
@ -80,6 +80,14 @@ public class WorkPenaltyTypeSettingGui extends AbstractSettingGui {
}, CustomAnvil.instance); }, CustomAnvil.instance);
} }
private static final Map<AnvilUseType, String> useTypes =
Map.of(
AnvilUseType.RENAME_ONLY, "a1z9Z",
AnvilUseType.MERGE, "b2y8Y",
AnvilUseType.UNIT_REPAIR, "c3x7X",
AnvilUseType.CUSTOM_CRAFT, "d4w6W"
);
@Override @Override
protected Pattern getGuiPattern() { protected Pattern getGuiPattern() {
return new Pattern( // Yeah that a mess return new Pattern( // Yeah that a mess
@ -92,13 +100,14 @@ public class WorkPenaltyTypeSettingGui extends AbstractSettingGui {
public void updateGuiForType(AnvilUseType type) { public void updateGuiForType(AnvilUseType type) {
PatternPane pane = getPane(); PatternPane pane = getPane();
int ordinal = type.ordinal();
int display = 'z' - ordinal; String typeVals = useTypes.get(type);
int increment = 'a' + ordinal;
int additive = '1' + ordinal; char increment = typeVals.charAt(0);
int exclusiveIncrement = 'Z' - ordinal; char additive = typeVals.charAt(1);
int exclusiveAdditive = '9' - ordinal; char display = typeVals.charAt(2);
char exclusiveIncrement = typeVals.charAt(3);
char exclusiveAdditive = typeVals.charAt(4);
WorkPenaltyType.WorkPenaltyPart part = items.get(type); WorkPenaltyType.WorkPenaltyPart part = items.get(type);
String increasingStr = (part.penaltyIncrease() ? "§a" : "§c") + "Increasing"; String increasingStr = (part.penaltyIncrease() ? "§a" : "§c") + "Increasing";
@ -210,12 +219,11 @@ public class WorkPenaltyTypeSettingGui extends AbstractSettingGui {
} }
public static boolean saveWorkPenalty(Map<AnvilUseType, WorkPenaltyType.WorkPenaltyPart> partEnum) { public static boolean saveWorkPenalty(Map<AnvilUseType, WorkPenaltyType.WorkPenaltyPart> partEnum) {
String path = ConfigOptions.WORK_PENALTY_ROOT;
ConfigHolder configHolder = ConfigHolder.DEFAULT_CONFIG; ConfigHolder configHolder = ConfigHolder.DEFAULT_CONFIG;
FileConfiguration config = configHolder.getConfig(); FileConfiguration config = configHolder.getConfig();
partEnum.forEach((key, value) -> { partEnum.forEach((key, value) -> {
String partPath = path + "." + key.getTypeName(); String partPath = key.getPath();
if (key.getDefaultPenalty().equals(value)) { if (key.getDefaultPenalty().equals(value)) {
config.set(partPath, null); config.set(partPath, null);

View file

@ -5,6 +5,11 @@ import io.delilaheve.util.ConfigOptions;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil;
import xyz.alexcrea.cuanvil.util.config.LoreEditType;
import static io.delilaheve.util.ConfigOptions.*;
import static xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.*;
public class PluginSetDefault { public class PluginSetDefault {
@ -13,19 +18,45 @@ public class PluginSetDefault {
int nbSet = 0; int nbSet = 0;
nbSet+= trySetDefault(config, ConfigOptions.CAP_ANVIL_COST, ConfigOptions.DEFAULT_CAP_ANVIL_COST); nbSet += trySetDefault(config, CAP_ANVIL_COST, DEFAULT_CAP_ANVIL_COST);
nbSet+= trySetDefault(config, ConfigOptions.MAX_ANVIL_COST, ConfigOptions.DEFAULT_MAX_ANVIL_COST); nbSet += trySetDefault(config, MAX_ANVIL_COST, DEFAULT_MAX_ANVIL_COST);
nbSet+= trySetDefault(config, ConfigOptions.REMOVE_ANVIL_COST_LIMIT, ConfigOptions.DEFAULT_REMOVE_ANVIL_COST_LIMIT); nbSet += trySetDefault(config, REMOVE_ANVIL_COST_LIMIT, DEFAULT_REMOVE_ANVIL_COST_LIMIT);
nbSet+= trySetDefault(config, ConfigOptions.REPLACE_TOO_EXPENSIVE, ConfigOptions.DEFAULT_REPLACE_TOO_EXPENSIVE); nbSet += trySetDefault(config, REPLACE_TOO_EXPENSIVE, DEFAULT_REPLACE_TOO_EXPENSIVE);
nbSet+= trySetDefault(config, ConfigOptions.ITEM_REPAIR_COST, ConfigOptions.DEFAULT_ITEM_REPAIR_COST); nbSet += trySetDefault(config, ITEM_REPAIR_COST, DEFAULT_ITEM_REPAIR_COST);
nbSet+= trySetDefault(config, ConfigOptions.UNIT_REPAIR_COST, ConfigOptions.DEFAULT_UNIT_REPAIR_COST); nbSet += trySetDefault(config, UNIT_REPAIR_COST, DEFAULT_UNIT_REPAIR_COST);
nbSet+= trySetDefault(config, ConfigOptions.ITEM_RENAME_COST, ConfigOptions.DEFAULT_ITEM_RENAME_COST); nbSet += trySetDefault(config, ITEM_RENAME_COST, DEFAULT_ITEM_RENAME_COST);
nbSet+= trySetDefault(config, ConfigOptions.SACRIFICE_ILLEGAL_COST, ConfigOptions.DEFAULT_SACRIFICE_ILLEGAL_COST); nbSet += trySetDefault(config, SACRIFICE_ILLEGAL_COST, DEFAULT_SACRIFICE_ILLEGAL_COST);
nbSet += trySetDefault(config, ConfigOptions.ALLOW_COLOR_CODE, ConfigOptions.DEFAULT_ALLOW_COLOR_CODE); nbSet += trySetDefault(config, ConfigOptions.ALLOW_COLOR_CODE, ConfigOptions.DEFAULT_ALLOW_COLOR_CODE);
nbSet+= trySetDefault(config, ConfigOptions.ALLOW_HEXADECIMAL_COLOR, ConfigOptions.DEFAULT_ALLOW_HEXADECIMAL_COLOR); nbSet += trySetDefault(config, ALLOW_HEXADECIMAL_COLOR, DEFAULT_ALLOW_HEXADECIMAL_COLOR);
nbSet+= trySetDefault(config, ConfigOptions.PERMISSION_NEEDED_FOR_COLOR, ConfigOptions.DEFAULT_PERMISSION_NEEDED_FOR_COLOR); nbSet += trySetDefault(config, PERMISSION_NEEDED_FOR_COLOR, DEFAULT_PERMISSION_NEEDED_FOR_COLOR);
nbSet+= trySetDefault(config, ConfigOptions.USE_OF_COLOR_COST, ConfigOptions.DEFAULT_USE_OF_COLOR_COST); nbSet += trySetDefault(config, USE_OF_COLOR_COST, DEFAULT_USE_OF_COLOR_COST);
nbSet+= trySetDefault(config, ConfigOptions.DEFAULT_LIMIT_PATH, ConfigOptions.DEFAULT_ENCHANT_LIMIT); nbSet += trySetDefault(config, DEFAULT_LIMIT_PATH, DEFAULT_ENCHANT_LIMIT);
// Lore Edit defaults
for (@NotNull LoreEditType value : LoreEditType.values()) {
String path = value.getRootPath() + ".";
nbSet += trySetDefault(config, path + IS_ENABLED, DEFAULT_IS_ENABLED);
nbSet += trySetDefault(config, path + FIXED_COST, DEFAULT_FIXED_COST);
nbSet += trySetDefault(config, path + DO_CONSUME, DEFAULT_DO_CONSUME);
if (value.isMultiLine()) {
nbSet += trySetDefault(config, path + PER_LINE_COST, DEFAULT_PER_LINE_COST);
}
if (value.isAppend()) {
nbSet += trySetDefault(config, path + LoreEditConfigUtil.ALLOW_COLOR_CODE, LoreEditConfigUtil.DEFAULT_ALLOW_COLOR_CODE);
nbSet += trySetDefault(config, path + ALLOW_HEX_COLOR, DEFAULT_ALLOW_HEX_COLOR);
nbSet += trySetDefault(config, path + USE_COLOR_COST, DEFAULT_USE_COLOR_COST);
} else {
nbSet += trySetDefault(config, path + REMOVE_COLOR_ON_LORE_REMOVE, DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE);
nbSet += trySetDefault(config, path + REMOVE_COLOR_COST, DEFAULT_REMOVE_COLOR_COST);
}
}
nbSet += trySetDefault(config, BOOK_PERMISSION_NEEDED, DEFAULT_BOOK_PERMISSION_NEEDED);
nbSet += trySetDefault(config, PAPER_PERMISSION_NEEDED, DEFAULT_PAPER_PERMISSION_NEEDED);
nbSet += trySetDefault(config, PAPER_EDIT_ORDER, DEFAULT_PAPER_EDIT_ORDER);
if (nbSet > 0) { if (nbSet > 0) {
CustomAnvil.instance.getLogger().info("Adding " + nbSet + " absent default config values."); CustomAnvil.instance.getLogger().info("Adding " + nbSet + " absent default config values.");

View file

@ -7,7 +7,7 @@ import xyz.alexcrea.cuanvil.config.WorkPenaltyType
import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart
import xyz.alexcrea.cuanvil.enchant.CAEnchantment import xyz.alexcrea.cuanvil.enchant.CAEnchantment
import xyz.alexcrea.cuanvil.util.AnvilUseType import xyz.alexcrea.cuanvil.util.AnvilUseType
import java.util.EnumMap import java.util.*
/** /**
* Config option accessors * Config option accessors
@ -37,6 +37,7 @@ object ConfigOptions {
const val PERMISSION_NEEDED_FOR_COLOR = "permission_needed_for_color" const val PERMISSION_NEEDED_FOR_COLOR = "permission_needed_for_color"
const val USE_OF_COLOR_COST = "use_of_color_cost" const val USE_OF_COLOR_COST = "use_of_color_cost"
// Work penalty config
const val WORK_PENALTY_ROOT = "work_penalty" const val WORK_PENALTY_ROOT = "work_penalty"
const val WORK_PENALTY_INCREASE = "shared_increase" const val WORK_PENALTY_INCREASE = "shared_increase"
const val WORK_PENALTY_ADDITIVE = "shared_additive" const val WORK_PENALTY_ADDITIVE = "shared_additive"
@ -115,6 +116,9 @@ object ConfigOptions {
@JvmField @JvmField
val ENCHANT_LIMIT_RANGE = 1..255 val ENCHANT_LIMIT_RANGE = 1..255
// --------------
// Other defaults
// --------------
// Default value for an enchantment multiplier // Default value for an enchantment multiplier
private const val DEFAULT_ENCHANT_VALUE = 0 private const val DEFAULT_ENCHANT_VALUE = 0
@ -239,7 +243,10 @@ object ConfigOptions {
/** /**
* If one of the color component is enabled * If one of the color component is enabled
*/ */
val renameColorPossible: Boolean get() { return allowColorCode || allowHexadecimalColor } val renameColorPossible: Boolean
get() {
return allowColorCode || allowHexadecimalColor
}
/** /**
* If players need a permission to use color * If players need a permission to use color
@ -282,16 +289,17 @@ object ConfigOptions {
*/ */
fun workPenaltyPart(type: AnvilUseType): WorkPenaltyPart { fun workPenaltyPart(type: AnvilUseType): WorkPenaltyPart {
val config = ConfigHolder.DEFAULT_CONFIG.config val config = ConfigHolder.DEFAULT_CONFIG.config
val path = WORK_PENALTY_ROOT + "." + type.typeName
// Find values // Find values
val defaultPenalty = type.defaultPenalty val defaultPenalty = type.defaultPenalty
val section = config.getConfigurationSection(path) ?: return defaultPenalty val section = config.getConfigurationSection(type.path) ?: return defaultPenalty
val penaltyIncrease = section.getBoolean(WORK_PENALTY_INCREASE, defaultPenalty.penaltyIncrease) val penaltyIncrease = section.getBoolean(WORK_PENALTY_INCREASE, defaultPenalty.penaltyIncrease)
val penaltyAdditive = section.getBoolean(WORK_PENALTY_ADDITIVE, defaultPenalty.penaltyAdditive) val penaltyAdditive = section.getBoolean(WORK_PENALTY_ADDITIVE, defaultPenalty.penaltyAdditive)
val exclusivePenaltyIncrease = section.getBoolean(EXCLUSIVE_WORK_PENALTY_INCREASE, defaultPenalty.exclusivePenaltyIncrease) val exclusivePenaltyIncrease =
val exclusivePenaltyAdditive = section.getBoolean(EXCLUSIVE_WORK_PENALTY_ADDITIVE, defaultPenalty.exclusivePenaltyAdditive) section.getBoolean(EXCLUSIVE_WORK_PENALTY_INCREASE, defaultPenalty.exclusivePenaltyIncrease)
val exclusivePenaltyAdditive =
section.getBoolean(EXCLUSIVE_WORK_PENALTY_ADDITIVE, defaultPenalty.exclusivePenaltyAdditive)
return WorkPenaltyPart(penaltyIncrease, penaltyAdditive, exclusivePenaltyIncrease, exclusivePenaltyAdditive) return WorkPenaltyPart(penaltyIncrease, penaltyAdditive, exclusivePenaltyIncrease, exclusivePenaltyAdditive)
} }
@ -357,7 +365,8 @@ object ConfigOptions {
/** /**
* Get default value if enchantment do not exist on config * Get default value if enchantment do not exist on config
*/ */
private fun getDefaultLevel(enchantmentName: String, // compatibility with 1.20.5. TODO better update system private fun getDefaultLevel(
enchantmentName: String, // compatibility with 1.20.5. TODO better update system
): Int { ): Int {
if (enchantmentName == "sweeping_edge") { if (enchantmentName == "sweeping_edge") {
val limit = enchantLimit("sweeping") val limit = enchantLimit("sweeping")
@ -407,8 +416,10 @@ object ConfigOptions {
/** /**
* Get default value if enchantment do not exist on config * Get default value if enchantment do not exist on config
*/ */
private fun getDefaultValue(enchantment: CAEnchantment, // compatibility with 1.20.5. TODO better update system private fun getDefaultValue(
isFromBook: Boolean) : Int { enchantment: CAEnchantment, // compatibility with 1.20.5. TODO better update system
isFromBook: Boolean
): Int {
val enchantmentName = enchantment.key.toString() val enchantmentName = enchantment.key.toString()
if (enchantmentName == "minecraft:sweeping_edge") { if (enchantmentName == "minecraft:sweeping_edge") {

View file

@ -6,6 +6,7 @@ import io.delilaheve.util.ItemUtil.canMergeWith
import io.delilaheve.util.ItemUtil.unitRepair import io.delilaheve.util.ItemUtil.unitRepair
import org.bukkit.GameMode import org.bukkit.GameMode
import org.bukkit.Material import org.bukkit.Material
import org.bukkit.entity.HumanEntity
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.event.Event import org.bukkit.event.Event
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
@ -15,15 +16,22 @@ import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.InventoryView import org.bukkit.inventory.InventoryView
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.BookMeta
import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_LEFT import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_LEFT
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_RIGHT import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_RIGHT
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT
import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe
import xyz.alexcrea.cuanvil.util.AnvilLoreEditUtil
import xyz.alexcrea.cuanvil.util.AnvilUseType import xyz.alexcrea.cuanvil.util.AnvilUseType
import xyz.alexcrea.cuanvil.util.AnvilXpUtil import xyz.alexcrea.cuanvil.util.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.CustomRecipeUtil import xyz.alexcrea.cuanvil.util.CustomRecipeUtil
import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil
import xyz.alexcrea.cuanvil.util.config.LoreEditType
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.ArrayList
import kotlin.math.min import kotlin.math.min
class AnvilResultListener : Listener { class AnvilResultListener : Listener {
@ -65,40 +73,53 @@ class AnvilResultListener: Listener {
event.result = Event.Result.ALLOW event.result = Event.Result.ALLOW
onCustomCraft( onCustomCraft(
event, recipe, player, event, recipe, player,
leftItem, rightItem, output, inventory) leftItem, rightItem, output, inventory
)
return return
} }
val canMerge = leftItem.canMergeWith(rightItem) // Do not continue if there was no change
val unitRepairResult = leftItem.getRepair(rightItem) if ((output == inventory.getItem(ANVIL_INPUT_LEFT))) {
val allowed = (rightItem == null)
|| (canMerge)
|| (unitRepairResult != null)
// True if there was no change or not allowed
if ((output == inventory.getItem(ANVIL_INPUT_LEFT))
|| !allowed
) {
event.result = Event.Result.DENY event.result = Event.Result.DENY
return return
} }
// Rename
if (rightItem == null) { if (rightItem == null) {
event.result = Event.Result.ALLOW event.result = Event.Result.ALLOW
return return
} }
// Merge
val canMerge = leftItem.canMergeWith(rightItem)
if (canMerge) { if (canMerge) {
event.result = Event.Result.ALLOW event.result = Event.Result.ALLOW
} else if (unitRepairResult != null) { return
}
// Unit repair
val unitRepairResult = leftItem.getRepair(rightItem)
if (unitRepairResult != null) {
onUnitRepairExtract( onUnitRepairExtract(
leftItem, rightItem, output, leftItem, rightItem, output,
unitRepairResult, event, player, inventory unitRepairResult, event, player, inventory
) )
return return
} }
// For lore edit
if (handleBookLoreEdit(event, inventory, player, leftItem, rightItem, output)) {
return
} else if (handlePaperLoreEdit(event, inventory, player, leftItem, rightItem, output)) {
return
} }
private fun onCustomCraft(event: InventoryClickEvent, // Else there was no working situation somehow so we deny
event.result = Event.Result.DENY
}
private fun onCustomCraft(
event: InventoryClickEvent,
recipe: AnvilCustomRecipe, recipe: AnvilCustomRecipe,
player: Player, player: Player,
leftItem: ItemStack, leftItem: ItemStack,
@ -123,7 +144,8 @@ class AnvilResultListener: Listener {
// Handle not creative middle click... // Handle not creative middle click...
if (event.click != ClickType.MIDDLE && if (event.click != ClickType.MIDDLE &&
!handleCustomCraftClick(event, recipe, inventory, player, leftItem, rightItem, amount, xpCost)) return !handleCustomCraftClick(event, recipe, inventory, player, leftItem, rightItem, amount, xpCost)
) return
// Finally, we add the item to the player // Finally, we add the item to the player
if (slotDestination.type == SlotType.CURSOR) { if (slotDestination.type == SlotType.CURSOR) {
@ -133,10 +155,12 @@ class AnvilResultListener: Listener {
} }
} }
private fun handleCustomCraftClick(event: InventoryClickEvent, recipe: AnvilCustomRecipe, private fun handleCustomCraftClick(
event: InventoryClickEvent, recipe: AnvilCustomRecipe,
inventory: AnvilInventory, player: Player, inventory: AnvilInventory, player: Player,
leftItem: ItemStack, rightItem: ItemStack?, leftItem: ItemStack, rightItem: ItemStack?,
amount: Int, xpCost: Int): Boolean { amount: Int, xpCost: Int
): Boolean {
// We remove what should be removed // We remove what should be removed
if (rightItem != null) { if (rightItem != null) {
if (recipe.rightItem == null) return false// in case it changed if (recipe.rightItem == null) return false// in case it changed
@ -174,6 +198,52 @@ class AnvilResultListener: Listener {
return true return true
} }
private fun extractAnvilResult(
event: InventoryClickEvent,
player: Player,
inventory: AnvilInventory,
leftItem: ItemStack?,
leftRemoveCount: Int,
rightItem: ItemStack?,
rightRemoveCount: Int,
output: ItemStack,
repairCost: Int,
): Boolean {
// To avoid vanilla, we cancel the event
event.result = Event.Result.DENY
event.isCancelled = true
// Assumed if player do not have enough xp then it returned MIN_VALUE
if (repairCost == Int.MIN_VALUE) return false
// Where should we get the item
val slotDestination = getActionSlot(event, player)
if (slotDestination.type == SlotType.NO_SLOT) return false
// If not creative middle click...
if (event.click != ClickType.MIDDLE) {
// We remove what should be removed
if (leftItem != null) leftItem.amount -= leftRemoveCount
inventory.setItem(ANVIL_INPUT_LEFT, leftItem)
if (rightItem != null) rightItem.amount -= rightRemoveCount
inventory.setItem(ANVIL_INPUT_RIGHT, rightItem)
inventory.setItem(ANVIL_OUTPUT_SLOT, null)
player.level -= repairCost
}
// Finally, we add the item to the player
if (SlotType.CURSOR == slotDestination.type) {
player.setItemOnCursor(output)
} else {// We assume SlotType == SlotType.INVENTORY
player.inventory.setItem(slotDestination.slot, output)
}
// TODO probably anvil damage & sound here ??
return true
}
private fun onUnitRepairExtract( private fun onUnitRepairExtract(
leftItem: ItemStack, leftItem: ItemStack,
rightItem: ItemStack, rightItem: ItemStack,
@ -188,39 +258,23 @@ class AnvilResultListener: Listener {
rightItem.amount, unitRepairResult rightItem.amount, unitRepairResult
) )
// To avoid vanilla, we cancel the event for unit repair // Get repair cost
event.result = Event.Result.DENY
event.isCancelled = true
// And we give the item manually
// But first we check if we should give the item
val slotDestination = getActionSlot(event, player)
if (slotDestination.type == SlotType.NO_SLOT) return
// Test repair cost
val repairCost = getUnitRepairCost(inventory, player, leftItem, output, resultCopy, resultAmount) val repairCost = getUnitRepairCost(inventory, player, leftItem, output, resultCopy, resultAmount)
if(repairCost == Int.MIN_VALUE) return
// If not creative middle click... // And then we give the item manually
if (event.click != ClickType.MIDDLE) { extractAnvilResult(
// We remove what should be removed event, player, inventory,
inventory.setItem(ANVIL_INPUT_LEFT, null) null, 0,
rightItem.amount -= resultAmount rightItem, resultAmount,
inventory.setItem(ANVIL_INPUT_RIGHT, rightItem) resultCopy, repairCost
inventory.setItem(ANVIL_OUTPUT_SLOT, null) )
player.level -= repairCost
} }
// Finally, we add the item to the player private fun getUnitRepairCost(
if (slotDestination.type == SlotType.CURSOR) { inventory: AnvilInventory, player: Player,
player.setItemOnCursor(output)
} else {// We assume SlotType == SlotType.INVENTORY
player.inventory.setItem(slotDestination.slot, output)
}
}
private fun getUnitRepairCost(inventory: AnvilInventory, player: Player,
leftItem: ItemStack, output: ItemStack, leftItem: ItemStack, output: ItemStack,
resultCopy: ItemStack, resultAmount: Int): Int { resultCopy: ItemStack, resultAmount: Int
): Int {
if (player.gameMode == GameMode.CREATIVE) return 0 if (player.gameMode == GameMode.CREATIVE) return 0
var repairCost = 0 var repairCost = 0
@ -257,6 +311,185 @@ class AnvilResultListener: Listener {
return repairCost return repairCost
} }
private fun getFromLoreEditXpCost(
xpCost: AtomicInteger,
player: Player,
inventory: AnvilInventory,
): Int {
if(GameMode.CREATIVE == player.gameMode) return 0
val repairCost = xpCost.get()
return if ((inventory.maximumRepairCost <= repairCost)
|| (player.level < repairCost)) Int.MIN_VALUE
else repairCost
}
private fun handleBookLoreEdit(
event: InventoryClickEvent,
inventory: AnvilInventory,
player: Player,
leftItem: ItemStack,
rightItem: ItemStack,
output: ItemStack,
): Boolean {
if (Material.WRITABLE_BOOK != rightItem.type) return false
val bookMeta = rightItem.itemMeta as BookMeta? ?: return false
val editType = AnvilLoreEditUtil.bookLoreEditIsAppend(leftItem, rightItem) ?: return false
val xpCost = AtomicInteger()
if (editType) {
if (output != AnvilLoreEditUtil.handleLoreAppendByBook(player, leftItem, bookMeta, xpCost)) return false
// Remove pages to book
val clearedBook: ItemStack?
if (LoreEditType.APPEND_BOOK.doConsume) {
clearedBook = null
} else {
clearedBook = rightItem.clone()
bookMeta.pages = Collections.emptyList()
clearedBook.itemMeta = bookMeta
}
return extractAnvilResult(
event, player, inventory,
null, 0,
clearedBook, 0,
output, getFromLoreEditXpCost(xpCost, player, inventory)
)
} else {
if (output != AnvilLoreEditUtil.handleLoreRemoveByBook(player, leftItem, xpCost)) return false
// fill book meta
val meta = leftItem.itemMeta
if (meta == null || !meta.hasLore()) return false
val lore = ArrayList<String>(meta.lore!!)
if (lore.isEmpty()) return false
val rightCopy : ItemStack?
if (LoreEditType.REMOVE_BOOK.doConsume) {
rightCopy = null
} else {
// Uncolor the page
AnvilLoreEditUtil.uncolorLines(player, lore, LoreEditType.REMOVE_BOOK)
val bookPage = StringBuilder()
lore.forEach {
if (bookPage.isNotEmpty()) bookPage.append('\n')
bookPage.append(it)
}
val resultPage = bookPage.toString()
//TODO maybe check page size ? bc it may be too big ???
rightCopy = rightItem.clone()
bookMeta.setPages(resultPage)
rightCopy.itemMeta = bookMeta
}
return extractAnvilResult(
event, player, inventory,
null, 0,
rightCopy, 0,
output, getFromLoreEditXpCost(xpCost, player, inventory)
)
}
}
private fun handlePaperLoreEdit(
event: InventoryClickEvent,
inventory: AnvilInventory,
player: Player,
leftItem: ItemStack,
rightItem: ItemStack,
output: ItemStack,
): Boolean {
if (Material.PAPER != rightItem.type) return false
val paperMeta = rightItem.itemMeta ?: return false
val editType = AnvilLoreEditUtil.paperLoreEditIsAppend(leftItem, rightItem) ?: return false
val xpCost = AtomicInteger()
if (editType) {
if (output != AnvilLoreEditUtil.handleLoreAppendByPaper(player, leftItem, rightItem, xpCost)) return false
val paperCopy: ItemStack?
if (LoreEditType.APPEND_PAPER.doConsume) {
paperCopy = null
} else {
// Remove custom name to paper
paperCopy = rightItem.clone()
paperCopy.amount = 1
paperMeta.setDisplayName(null)
paperCopy.itemMeta = paperMeta
}
return if (rightItem.amount > 1) {
extractAnvilResult(
event, player, inventory,
paperCopy, 0,
rightItem, 1,
output, getFromLoreEditXpCost(xpCost, player, inventory)
)
} else {
extractAnvilResult(
event, player, inventory,
null, 0,
paperCopy, 0,
output, getFromLoreEditXpCost(xpCost, player, inventory)
)
}
} else {
if (output != AnvilLoreEditUtil.handleLoreRemoveByPaper(player, leftItem, xpCost)) return false
val leftMeta = leftItem.itemMeta
if (leftMeta == null || !leftMeta.hasLore()) return false
val lore = leftMeta.lore!!
if (lore.isEmpty()) return false
// Create result item
val rightClone: ItemStack?
if(LoreEditType.REMOVE_PAPER.doConsume){
rightClone = null
}else{
val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd
var line = if (removeEnd) lore[lore.size - 1]
else lore[0]
// Overkill but uncolor the line
val tempList = ArrayList<String>(1)
tempList.add(line)
AnvilLoreEditUtil.uncolorLines(player, tempList, LoreEditType.REMOVE_PAPER)
line = tempList[0]
rightClone = rightItem.clone()
rightClone.amount = 1
val resultMeta = rightClone.itemMeta ?: return false
resultMeta.setDisplayName(line)
rightClone.itemMeta = resultMeta
}
return if (rightItem.amount > 1) {
extractAnvilResult(
event, player, inventory,
rightClone, 0,
rightItem, 1,
output, getFromLoreEditXpCost(xpCost, player, inventory)
)
} else {
extractAnvilResult(
event, player, inventory,
null, 0,
rightClone, 0,
output, getFromLoreEditXpCost(xpCost, player, inventory)
)
}
}
}
/** /**
* Get the destination slot or "NO_SLOT" slot container if there is no slot available * Get the destination slot or "NO_SLOT" slot container if there is no slot available
*/ */
@ -283,8 +516,7 @@ class AnvilResultListener: Listener {
return NO_SLOT return NO_SLOT
} }
return SlotContainer(SlotType.INVENTORY, slotIndex) return SlotContainer(SlotType.INVENTORY, slotIndex)
} } else if (player.itemOnCursor.type != Material.AIR) return NO_SLOT
else if (player.itemOnCursor.type != Material.AIR) return NO_SLOT
return CURSOR_SLOT return CURSOR_SLOT
} }

View file

@ -11,6 +11,7 @@ import io.delilaheve.util.ItemUtil.repairFrom
import io.delilaheve.util.ItemUtil.setEnchantmentsUnsafe import io.delilaheve.util.ItemUtil.setEnchantmentsUnsafe
import io.delilaheve.util.ItemUtil.unitRepair import io.delilaheve.util.ItemUtil.unitRepair
import org.bukkit.ChatColor import org.bukkit.ChatColor
import org.bukkit.Material
import org.bukkit.entity.HumanEntity import org.bukkit.entity.HumanEntity
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority import org.bukkit.event.EventPriority
@ -19,11 +20,10 @@ import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.util.AnvilColorUtil import xyz.alexcrea.cuanvil.util.*
import xyz.alexcrea.cuanvil.util.AnvilUseType
import xyz.alexcrea.cuanvil.util.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.CustomRecipeUtil
import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair
import java.util.concurrent.atomic.AtomicInteger
/** /**
* Listener for anvil events * Listener for anvil events
*/ */
@ -72,12 +72,15 @@ class PrepareAnvilListener : Listener {
// Test for unit repair // Test for unit repair
if(testUnitRepair(event, inventory, player, first, second)) return if(testUnitRepair(event, inventory, player, first, second)) return
// Test for lore edit
if(testLoreEdit(event, inventory, player, first, second)) return
CustomAnvil.log("no anvil fuse type found") CustomAnvil.log("no anvil fuse type found")
event.result = null event.result = null
} }
// return true if a custom recipe exist with these ingredient // return true if a custom recipe exist with these ingredients
private fun testCustomRecipe(event: PrepareAnvilEvent, inventory: AnvilInventory, private fun testCustomRecipe(event: PrepareAnvilEvent, inventory: AnvilInventory,
player: HumanEntity, player: HumanEntity,
first: ItemStack, second: ItemStack?): Boolean { first: ItemStack, second: ItemStack?): Boolean {
@ -131,7 +134,10 @@ class PrepareAnvilListener : Listener {
if(ConfigOptions.renameColorPossible && inventoryName != null){ if(ConfigOptions.renameColorPossible && inventoryName != null){
val resultString = StringBuilder(inventoryName) val resultString = StringBuilder(inventoryName)
useColor = AnvilColorUtil.handleRenamingColor(resultString, player) useColor = AnvilColorUtil.handleColor(resultString, player,
ConfigOptions.permissionNeededForColor,
ConfigOptions.allowColorCode, ConfigOptions.allowHexadecimalColor,
AnvilColorUtil.ColorUseType.RENAME)
if(useColor) { if(useColor) {
inventoryName = resultString.toString() inventoryName = resultString.toString()
@ -222,4 +228,27 @@ class PrepareAnvilListener : Listener {
return true return true
} }
private fun testLoreEdit(event: PrepareAnvilEvent, inventory: AnvilInventory, player: HumanEntity,
first: ItemStack, second: ItemStack): Boolean {
val type = second.type
var result: ItemStack? = null
val xpCost = AtomicInteger()
if(Material.WRITABLE_BOOK == type) {
result = AnvilLoreEditUtil.tryLoreEditByBook(player, first, second, xpCost)
}
else if(Material.PAPER == type) {
result = AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second, xpCost)
}
if(result == null || first == result) {
CustomAnvil.log("lore edit, But input is same as output")
event.result = null
return false
}
event.result = result
AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, xpCost.get())
return true
}
} }

View file

@ -1,17 +1,35 @@
package xyz.alexcrea.cuanvil.util package xyz.alexcrea.cuanvil.util
import io.delilaheve.util.ConfigOptions import org.bukkit.permissions.Permissible
import org.bukkit.entity.HumanEntity
import java.util.regex.Matcher import java.util.regex.Matcher
import java.util.regex.Pattern import java.util.regex.Pattern
object AnvilColorUtil { object AnvilColorUtil {
private val HEX_PATTERN: Pattern = Pattern.compile("#[A-Fa-f0-9]{6}") // pattern to find hexadecimal string private val HEX_PATTERN: Pattern = Pattern.compile("#[A-Fa-f0-9]{6}") // pattern to find hexadecimal string
private val TRANSFORMED_HEX_PATTERN = Pattern.compile("§x(§[0-9a-fA-F]){6}") // pattern to find minecraft hex string
fun handleRenamingColor(textToColor: StringBuilder, player: HumanEntity): Boolean { /**
val usePermission = ConfigOptions.permissionNeededForColor * Color a stringbuilder object depending on allowed color type and player permissions on color use type
val canUseColorCode = ConfigOptions.allowColorCode && (!usePermission || player.hasPermission("ca.color.code")) * @return if the stringbuilder was changed and color applied
val canUseHexColor = ConfigOptions.allowHexadecimalColor && (!usePermission || player.hasPermission("ca.color.hex")) */
fun handleColor(
textToColor: StringBuilder,
player: Permissible,
usePermission: Boolean,
allowColorCode: Boolean,
allowHexadecimalColor: Boolean,
useType: ColorUseType
): Boolean {
if (!allowColorCode && !allowHexadecimalColor) return false
val canUseColorCode =
allowColorCode && (!usePermission || useType.colorCodePerm == null || player.hasPermission(
useType.colorCodePerm
))
val canUseHexColor =
allowHexadecimalColor && (!usePermission || useType.hexColorPerm == null || player.hasPermission(
useType.hexColorPerm
))
if ((!canUseColorCode) && (!canUseHexColor)) return false if ((!canUseColorCode) && (!canUseHexColor)) return false
@ -33,6 +51,49 @@ object AnvilColorUtil {
return useColor return useColor
} }
/**
* Revert a stringbuilder to a state where applying handleColor with the same options would give the same result
* @return if the stringbuilder was changed and color unapplied
*/
fun revertColor(
colorToText: StringBuilder,
player: Permissible,
usePermission: Boolean,
allowColorCode: Boolean,
allowHexadecimalColor: Boolean,
useType: ColorUseType
): Boolean {
if (!allowColorCode && !allowHexadecimalColor) return false
val canUseColorCode =
allowColorCode && (!usePermission || useType.colorCodePerm == null || player.hasPermission(
useType.colorCodePerm
))
val canUseHexColor =
allowHexadecimalColor && (!usePermission || useType.hexColorPerm == null || player.hasPermission(
useType.hexColorPerm
))
if ((!canUseColorCode) && (!canUseHexColor)) return false
var hasReversed = false
// Reverse hex pattern
if (canUseHexColor) {
val nbReplacement = replaceColorToHex(colorToText, 14)
if (nbReplacement > 0) hasReversed = true
}
if (canUseColorCode) {
replaceAll(colorToText, "&", "&&", 1)
val nbReplacement = replaceAll(colorToText, "§", "&", 2)
if (nbReplacement > 0) hasReversed = true
}
return hasReversed
}
/** /**
* Replace every instance of "from" to "to". * Replace every instance of "from" to "to".
* @param builder The builder to replace the string from. * @param builder The builder to replace the string from.
@ -70,7 +131,7 @@ object AnvilColorUtil {
while (matcher.find(startIndex)) { while (matcher.find(startIndex)) {
startIndex = matcher.start() startIndex = matcher.start()
if(startIndex >= builder.length - endOffset) break if (startIndex >= builder.length - endOffset) break //HOW AND WHERE WOULD THIS HAPPEN ?????
builder.replace(startIndex, startIndex + 1, "§x") builder.replace(startIndex, startIndex + 1, "§x")
startIndex += 2 startIndex += 2
@ -85,4 +146,41 @@ object AnvilColorUtil {
return numberOfChanges return numberOfChanges
} }
/**
* Replace every hex color from the minecraft format to a format like #000000
* @param builder The builder to replace the minecraft hex color from.
* @param endOffset Amount of character that should be ignored at the end.
* @return The number of replacement was that was done.
*/
private fun replaceColorToHex(builder: StringBuilder, endOffset: Int): Int {
val matcher: Matcher = TRANSFORMED_HEX_PATTERN.matcher(builder)
var numberOfChanges = 0
var startIndex = 0
while (matcher.find(startIndex)) {
startIndex = matcher.start()
if (startIndex >= builder.length - endOffset) break //HOW AND WHERE WOULD THIS HAPPEN ?????
builder.replace(startIndex, startIndex + 2, "#")
startIndex += 1
for (i in 0..5) {
builder.deleteCharAt(startIndex)
startIndex += 1
}
numberOfChanges += 1
}
return numberOfChanges
}
enum class ColorUseType(
val colorCodePerm: String?,
val hexColorPerm: String?
) {
RENAME("ca.color.code", "ca.color.hex"),
LORE_EDIT(null, null)
}
} }

View file

@ -0,0 +1,288 @@
package xyz.alexcrea.cuanvil.util
import org.bukkit.entity.HumanEntity
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.BookMeta
import org.bukkit.permissions.Permissible
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil
import xyz.alexcrea.cuanvil.util.config.LoreEditType
import java.util.concurrent.atomic.AtomicInteger
object AnvilLoreEditUtil {
private const val LORE_BY_BOOK: String = "ca.lore_edit.book"
private const val LORE_BY_PAPER: String = "ca.lore_edit.paper"
private fun hasLoreEditByBookPermission(player: Permissible): Boolean {
return !LoreEditConfigUtil.bookLoreEditNeedPermission || player.hasPermission(LORE_BY_BOOK)
}
private fun hasLoreEditByPaperPermission(player: Permissible): Boolean {
return !LoreEditConfigUtil.paperLoreEditNeedPermission || player.hasPermission(LORE_BY_PAPER)
}
fun handleLoreAppendByBook(
player: Permissible,
first: ItemStack,
book: BookMeta,
xpCost: AtomicInteger
): ItemStack? {
if (!hasLoreEditByBookPermission(player)) return null
val result = first.clone()
val meta = result.itemMeta ?: return null
val lore = if (meta.hasLore()) {
ArrayList<String>(meta.lore!!)
} else ArrayList()
val page = book.pages[0]
val lines = ArrayList<String>(page.split("\n"))
val colorCost = colorLines(player, lines, LoreEditType.APPEND_BOOK)
lore.addAll(lines)
meta.lore = lore
result.itemMeta = meta
if (result == first) return null
// Handle xp
xpCost.addAndGet(colorCost) // Cost of using color
xpCost.addAndGet(lines.size * LoreEditType.APPEND_BOOK.perLineCost) // per line cost
xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.APPEND_BOOK)) // Fixed cost and work penalty
return result
}
fun handleLoreRemoveByBook(player: Permissible, first: ItemStack, xpCost: AtomicInteger): ItemStack? {
if (!hasLoreEditByBookPermission(player)) return null
// remove lore
val result = first.clone()
val leftMeta = result.itemMeta ?: return null
val currentLore = ArrayList<String>(leftMeta.lore ?: return null)
val uncolorCost = uncolorLines(player, currentLore, LoreEditType.REMOVE_BOOK)
leftMeta.lore = null
result.itemMeta = leftMeta
if (result == first) return null
// Handle xp
xpCost.addAndGet(uncolorCost)
xpCost.addAndGet(currentLore.size * LoreEditType.REMOVE_BOOK.perLineCost)
xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.REMOVE_BOOK))
return result
}
// Return true if appended, false if removed, null if neither
fun bookLoreEditIsAppend(first: ItemStack, second: ItemStack): Boolean? {
// Test if the book & quil contain content
val meta = second.itemMeta as BookMeta? ?: return false
var hasContent = false
if (meta.hasPages() && meta.pageCount >= 1) {
// Test if the pages is ok
for (page in meta.pages) {
if (page.isNotBlank()) {
hasContent = true
break
}
}
}
// We don't want to "add" the first page is there is content and the first page is empty
if (hasContent) {
if (meta.pages[0].isEmpty()) return null
if (LoreEditType.APPEND_BOOK.enabled)
return true
} else if (LoreEditType.REMOVE_BOOK.enabled) {
if (!first.hasItemMeta()) return null
val leftMeta = first.itemMeta!!
return if (leftMeta.hasLore()) false
else null
}
return null
}
fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack, xpCost: AtomicInteger): ItemStack? {
val bookType = bookLoreEditIsAppend(first, second) ?: return null
val meta = second.itemMeta as BookMeta
return if (bookType) handleLoreAppendByBook(player, first, meta, xpCost)
else handleLoreRemoveByBook(player, first, xpCost)
}
// Return true if appended, false if removed, null if neither
fun paperLoreEditIsAppend(first: ItemStack, second: ItemStack): Boolean? {
// Test if the paper contain a display name
val meta = second.itemMeta ?: return false
val hasContent = meta.hasDisplayName()
if (hasContent) {
if (LoreEditType.APPEND_PAPER.enabled)
return true
} else if (LoreEditType.REMOVE_PAPER.enabled) {
if (!first.hasItemMeta()) return null
val leftMeta = first.itemMeta!!
return if (leftMeta.hasLore() && leftMeta.lore!!.isNotEmpty()) false
else null
}
return null
}
fun handleLoreAppendByPaper(
player: Permissible,
first: ItemStack,
second: ItemStack,
xpCost: AtomicInteger
): ItemStack? {
if (!hasLoreEditByPaperPermission(player)) return null
val result = first.clone()
val meta = result.itemMeta?: return null
val lore = if (meta.hasLore()) {
ArrayList<String>(meta.lore!!)
} else ArrayList()
val appendEnd = LoreEditConfigUtil.paperLoreOrderIsEnd
// A bit overdone to color 1 line but hey
val tempList = ArrayList<String>(1)
tempList.add(second.itemMeta!!.displayName)
val colorCost = colorLines(player, tempList, LoreEditType.APPEND_PAPER)
val line = tempList[0]
if (appendEnd)
lore.add(line)
else
lore.add(0, line)
meta.lore = lore
result.itemMeta = meta
if (result == first) return null
// Handle xp
xpCost.addAndGet(colorCost)
xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.APPEND_PAPER))
return result
}
fun handleLoreRemoveByPaper(player: Permissible, first: ItemStack, xpCost: AtomicInteger): ItemStack? {
if (!hasLoreEditByPaperPermission(player)) return null
// remove lore line
val result = first.clone()
val meta = result.itemMeta!!
val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd
val lore: ArrayList<String> = ArrayList(meta.lore!!)
val line = if (removeEnd) lore.removeAt(lore.size - 1)
else lore.removeAt(0)
meta.lore = if (lore.isEmpty()) null else lore
result.itemMeta = meta
// Get color cost to uncolor this line
val tempList = ArrayList<String>(1)
tempList.add(line)
val uncolorCost = uncolorLines(player, tempList, LoreEditType.REMOVE_PAPER)
if (result == first) return null
// Handle other xp
xpCost.addAndGet(uncolorCost)
xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.REMOVE_PAPER))
return result
}
fun tryLoreEditByPaper(
player: HumanEntity,
first: ItemStack,
second: ItemStack,
xpCost: AtomicInteger
): ItemStack? {
val bookType = paperLoreEditIsAppend(first, second) ?: return null
return if (bookType) handleLoreAppendByPaper(player, first, second, xpCost)
else handleLoreRemoveByPaper(player, first, xpCost)
}
private fun baseEditLoreXpCost(
first: ItemStack,
result: ItemStack,
editType: LoreEditType
): Int {
var xpCost = editType.fixedCost
xpCost += AnvilXpUtil.calculatePenalty(first, null, result, editType.useType)
return xpCost
}
private fun colorLines(player: Permissible, lines: ArrayList<String>, editType: LoreEditType): Int {
val canUseHex = editType.allowHexColor
val canUseColorCode = editType.allowColorCode
val colorCost = editType.useColorCost
// Now handle color of each lines
var hasUsedColor = false
for ((index, line) in lines.withIndex()) {
val coloredLine = StringBuilder(line)
val lineUsedColor = AnvilColorUtil.handleColor(
coloredLine,
player,
false, canUseColorCode, canUseHex,
AnvilColorUtil.ColorUseType.LORE_EDIT
)
if (lineUsedColor) {
hasUsedColor = true
lines[index] = coloredLine.toString()
}
}
return if (hasUsedColor) {
colorCost
} else {
0
}
}
fun uncolorLines(player: Permissible, lines: ArrayList<String>, editType: LoreEditType): Int {
if(!editType.shouldRemoveColorOnLoreRemoval) return 0
// Now handle color of each lines
var hasUndidColor = false
for ((index, line) in lines.withIndex()) {
val uncoloredLine = StringBuilder(line)
val lineUndidColor = AnvilColorUtil.revertColor(
uncoloredLine,
player,
false, true, true,
AnvilColorUtil.ColorUseType.LORE_EDIT
)
if (lineUndidColor) {
hasUndidColor = true
lines[index] = uncoloredLine.toString()
}
}
return if (hasUndidColor) {
editType.removeColorCost
} else {
0
}
}
}

View file

@ -2,28 +2,66 @@ package xyz.alexcrea.cuanvil.util
import org.bukkit.Material import org.bukkit.Material
import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart
import xyz.alexcrea.cuanvil.util.config.LoreEditType
enum class AnvilUseType(val typeName: String, enum class AnvilUseType(
val typeName: String, val path: String,
val defaultPenalty: WorkPenaltyPart, val defaultPenalty: WorkPenaltyPart,
val displayName: String, val displayMat: Material val displayName: String, val displayMat: Material
) { ) {
RENAME_ONLY("rename_only", RENAME_ONLY(
"rename_only",
WorkPenaltyPart(false, true), WorkPenaltyPart(false, true),
"Rename Only", Material.NAME_TAG "Rename Only", Material.NAME_TAG
), ),
MERGE("merge", MERGE(
"merge",
WorkPenaltyPart(true, true), WorkPenaltyPart(true, true),
"Merge", Material.ANVIL "Merge", Material.ANVIL
), ),
UNIT_REPAIR("unit_repair", UNIT_REPAIR(
"unit_repair",
WorkPenaltyPart(true, true), WorkPenaltyPart(true, true),
"Unit Repair", Material.DIAMOND "Unit Repair", Material.DIAMOND
), ),
CUSTOM_CRAFT("custom_craft", CUSTOM_CRAFT(
"custom_craft",
WorkPenaltyPart(false, false), WorkPenaltyPart(false, false),
"Custom Craft", Material.CRAFTING_TABLE "Custom Craft", Material.CRAFTING_TABLE
), ),
LORE_EDIT_BOOK_APPEND(
"lore_edit_book_append", "lore_edit.book_and_quil.append",
WorkPenaltyPart(false, false),
"Book Add", Material.WRITABLE_BOOK
),
LORE_EDIT_BOOK_REMOVE(
"lore_edit_book_remove", "lore_edit.book_and_quil.remove",
WorkPenaltyPart(false, false),
"Book Remove", Material.WRITABLE_BOOK
),
LORE_EDIT_PAPER_APPEND(
"lore_edit_paper_append", "lore_edit.paper.append_line",
WorkPenaltyPart(false, false),
"Paper Add", Material.WRITABLE_BOOK
),
LORE_EDIT_PAPER_REMOVE(
"lore_edit_paper_remove", "lore_edit.paper.remove_line",
WorkPenaltyPart(false, false),
"Paper Remove", Material.WRITABLE_BOOK
),
; ;
constructor(
typeName: String,
defaultPenalty: WorkPenaltyPart,
displayName: String, displayMat: Material
) :
this(
typeName,
AnvilUseTypeUtil.defaultPath(typeName), // stupid util class
defaultPenalty,
displayName, displayMat
)
} }

View file

@ -0,0 +1,26 @@
package xyz.alexcrea.cuanvil.util
import io.delilaheve.util.ConfigOptions
object AnvilUseTypeUtil {
/*
By stupidity of kotlin not allowing static function outside of companion object I need to do another util class only for 1 function:
Companion object on enum class seems to get initialized AFTER the enum itself
that mean. if you want to call a static function on the enum class itself YOU CAN'T. you need to do this stupid thing
or you can make a stupid top level function OR object that even worse because of global scope pollution
(btw was gpt ""solution"". fortunately I do not "vibe code" so I do what I know instead of stupid AI solution & code)
I mean, this is still global scope pollution bc of a USELESS class that SHOULD not exist but as a class is better than a random *ss function
sorry for the rent but this made me frustrated
Note: I still like a lot of part of kotlin compared to java but this part is one that I hate
*/
/**
* Get config path for normal anvil use
*/
fun defaultPath(typeName: String): String {
return "${ConfigOptions.WORK_PENALTY_ROOT}.$typeName"
}
}

View file

@ -11,7 +11,6 @@ import org.bukkit.entity.HumanEntity
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.InventoryView import org.bukkit.inventory.InventoryView
import org.bukkit.inventory.InventoryView.Property.REPAIR_COST
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.Repairable import org.bukkit.inventory.meta.Repairable
import org.bukkit.persistence.PersistentDataType import org.bukkit.persistence.PersistentDataType
@ -129,13 +128,16 @@ object AnvilXpUtil {
return resultSum return resultSum
} }
private fun exclusivePenaltyKey(useType: AnvilUseType): NamespacedKey {
return NamespacedKey(CustomAnvil.instance, "${EXCLUSIVE_PENALTY_PREFIX}_${useType.typeName}")
}
private fun setExclusivePenalty( private fun setExclusivePenalty(
result: ItemStack, result: ItemStack,
resultPenalty: Int, resultPenalty: Int,
useType: AnvilUseType useType: AnvilUseType
) { ) {
val tagPath = EXCLUSIVE_PENALTY_PREFIX + "_" + useType.typeName val key = exclusivePenaltyKey(useType)
val key = NamespacedKey(CustomAnvil.instance, tagPath)
val meta = result.itemMeta!! val meta = result.itemMeta!!
meta.persistentDataContainer.set(key, PersistentDataType.INTEGER, resultPenalty) meta.persistentDataContainer.set(key, PersistentDataType.INTEGER, resultPenalty)
@ -143,14 +145,13 @@ object AnvilXpUtil {
} }
private fun findExclusivePenalty( private fun findExclusivePenalty(
left: ItemStack?, item: ItemStack?,
useType: AnvilUseType useType: AnvilUseType
): Int { ): Int {
if (left == null) return 0 if (item == null || !item.hasItemMeta()) return 0
val tagPath = EXCLUSIVE_PENALTY_PREFIX + "_" + useType.typeName val key = exclusivePenaltyKey(useType)
val key = NamespacedKey(CustomAnvil.instance, tagPath)
val meta = left.itemMeta!! val meta = item.itemMeta!!
return meta.persistentDataContainer.get(key, PersistentDataType.INTEGER) ?: return 0 return meta.persistentDataContainer.get(key, PersistentDataType.INTEGER) ?: return 0
} }

View file

@ -0,0 +1,103 @@
package xyz.alexcrea.cuanvil.util.config
import xyz.alexcrea.cuanvil.config.ConfigHolder.DEFAULT_CONFIG as CONFIG
object LoreEditConfigUtil {
// Per edit type configs Path
const val IS_ENABLED = "enabled"
const val FIXED_COST = "fixed_cost"
const val PER_LINE_COST = "per_line_cost"
const val DO_CONSUME = "do_consume"
// Permission configs path
const val BOOK_PERMISSION_NEEDED = "lore_edit.book_and_quil.use_permission"
const val PAPER_PERMISSION_NEEDED = "lore_edit.paper.use_permission"
// Color configs path
const val ALLOW_COLOR_CODE = "allow_color_code"
const val ALLOW_HEX_COLOR = "allow_hexadecimal_color"
const val USE_COLOR_COST = "use_cost"
const val REMOVE_COLOR_ON_LORE_REMOVE = "remove_color_on_remove"
const val REMOVE_COLOR_COST = "remove_color_cost"
// Lore order config path
const val PAPER_EDIT_ORDER = "lore_edit.paper.order"
// --------------
// Default Values
// --------------
// Per edit type configs defaults
const val DEFAULT_IS_ENABLED = false
const val DEFAULT_FIXED_COST = 1
const val DEFAULT_PER_LINE_COST = 0
const val DEFAULT_DO_CONSUME = false
// Permission configs defaults
const val DEFAULT_BOOK_PERMISSION_NEEDED = true
const val DEFAULT_PAPER_PERMISSION_NEEDED = true
// Color configs defaults
const val DEFAULT_ALLOW_COLOR_CODE = true
const val DEFAULT_ALLOW_HEX_COLOR = true
const val DEFAULT_USE_COLOR_COST = 0
const val DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE = false
const val DEFAULT_REMOVE_COLOR_COST = 0
// Lore order config default
const val DEFAULT_PAPER_EDIT_ORDER = "end"
// -------------
// Config Ranges
// -------------
val FIXED_COST_RANGE = 0..1000
val PER_LINE_COST_RANGE = 0..1000
val USE_COLOR_COST_RANGE = 0..1000
val REMOVE_COLOR_COST_RANGE = 0..1000
// -------------------
// Generic Get methods
// -------------------
/**
* Get if we should append/remove at the end or at the start of the lore list
* This may change to an "OrderType" enum or equivalent later
*/
val paperLoreOrderIsEnd: Boolean
get() {
return CONFIG
.config
.getString(PAPER_EDIT_ORDER, DEFAULT_PAPER_EDIT_ORDER)
.equals(DEFAULT_PAPER_EDIT_ORDER, ignoreCase = true)
}
/**
* If lore edit via book need permission
*/
val bookLoreEditNeedPermission: Boolean
get() {
return CONFIG
.config
.getBoolean(BOOK_PERMISSION_NEEDED, DEFAULT_BOOK_PERMISSION_NEEDED)
}
/**
* If lore edit via paper need permission
*/
val paperLoreEditNeedPermission: Boolean
get() {
return CONFIG
.config
.getBoolean(PAPER_PERMISSION_NEEDED, DEFAULT_PAPER_PERMISSION_NEEDED)
}
// -----------------
// Color Get methods
// -----------------
}

View file

@ -0,0 +1,142 @@
package xyz.alexcrea.cuanvil.util.config
import xyz.alexcrea.cuanvil.util.AnvilUseType
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_COLOR_CODE
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_HEX_COLOR
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_ALLOW_COLOR_CODE
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_ALLOW_HEX_COLOR
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_REMOVE_COLOR_COST
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.DEFAULT_USE_COLOR_COST
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.REMOVE_COLOR_COST
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.REMOVE_COLOR_COST_RANGE
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.REMOVE_COLOR_ON_LORE_REMOVE
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.USE_COLOR_COST
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.USE_COLOR_COST_RANGE
import xyz.alexcrea.cuanvil.config.ConfigHolder.DEFAULT_CONFIG as CONFIG
enum class LoreEditType(
val rootPath: String,
val useType: AnvilUseType,
val isAppend: Boolean,
val isMultiLine: Boolean,
) {
APPEND_BOOK(AnvilUseType.LORE_EDIT_BOOK_APPEND, true, true),
REMOVE_BOOK(AnvilUseType.LORE_EDIT_BOOK_REMOVE, false, true),
APPEND_PAPER(AnvilUseType.LORE_EDIT_PAPER_APPEND, true, false),
REMOVE_PAPER(AnvilUseType.LORE_EDIT_PAPER_REMOVE, false, false),
;
constructor(
useType: AnvilUseType,
isAppend: Boolean,
isMultiLine: Boolean,
) : this(useType.path, useType, isAppend, isMultiLine)
/**
* If this edit type is enabled
*/
val enabled: Boolean
get() {
return CONFIG
.config
.getBoolean("${rootPath}.${LoreEditConfigUtil.IS_ENABLED}", LoreEditConfigUtil.DEFAULT_IS_ENABLED)
}
/**
* Fixed cost added to this edit
*/
val fixedCost: Int
get() {
return CONFIG
.config
.getInt("${rootPath}.${LoreEditConfigUtil.FIXED_COST}", LoreEditConfigUtil.DEFAULT_FIXED_COST)
.takeIf { it in LoreEditConfigUtil.FIXED_COST_RANGE }
?: LoreEditConfigUtil.DEFAULT_FIXED_COST
}
/**
* Cost added per line added
*/
val perLineCost: Int
get() {
if (!isMultiLine) throw IllegalStateException("Per line cost get on single line edit type")
return CONFIG
.config
.getInt("${rootPath}.${LoreEditConfigUtil.PER_LINE_COST}", LoreEditConfigUtil.DEFAULT_PER_LINE_COST)
.takeIf { it in LoreEditConfigUtil.PER_LINE_COST_RANGE }
?: LoreEditConfigUtil.DEFAULT_PER_LINE_COST
}
/**
* If the edit should consume the provided material
*/
val doConsume: Boolean
get() {
return CONFIG
.config
.getBoolean("${rootPath}.${LoreEditConfigUtil.DO_CONSUME}", LoreEditConfigUtil.DEFAULT_DO_CONSUME)
}
/**
* Allow usage of color code on lore add
*/
val allowColorCode: Boolean
get() {
if (!isAppend) throw IllegalStateException("Can only call with an append edit type")
return CONFIG
.config
.getBoolean("$rootPath.$ALLOW_COLOR_CODE", DEFAULT_ALLOW_COLOR_CODE)
}
/**
* Allow usage of hexadecimal color on lore add
*/
val allowHexColor: Boolean
get() {
if (!isAppend) throw IllegalStateException("Can only call with an append edit type")
return CONFIG
.config
.getBoolean("${rootPath}.$ALLOW_HEX_COLOR", DEFAULT_ALLOW_HEX_COLOR)
}
/**
* Cost when using either color code and hex color on lore add
*/
val useColorCost: Int
get() {
if (!isAppend) throw IllegalStateException("Can only call with an append edit type")
return CONFIG
.config
.getInt("${rootPath}.$USE_COLOR_COST", DEFAULT_USE_COLOR_COST)
.takeIf { it in USE_COLOR_COST_RANGE }
?: DEFAULT_USE_COLOR_COST
}
/**
* Should the color code & hex color should get removed on lore remove
*/
val shouldRemoveColorOnLoreRemoval: Boolean
get() {
if (isAppend) throw IllegalStateException("Can only call with a remove edit type")
return CONFIG
.config
.getBoolean("${rootPath}.$REMOVE_COLOR_ON_LORE_REMOVE", DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE)
}
/**
* Cost when using either color code and hex color on lore remove
*/
val removeColorCost: Int
get() {
if (isAppend) throw IllegalStateException("Can only call with a remove edit type")
return CONFIG
.config
.getInt("${rootPath}.$REMOVE_COLOR_COST", DEFAULT_REMOVE_COLOR_COST)
.takeIf { it in REMOVE_COLOR_COST_RANGE }
?: DEFAULT_REMOVE_COLOR_COST
}
}

View file

@ -270,6 +270,91 @@ disable-merge-over:
# If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied # If uncommented. 2 unbreaking II book would not give an unbreaking III book. but unbreaking III book can still be applied
#minecraft:unbreaking: 2 #minecraft:unbreaking: 2
# Settings for lore modification
lore_edit:
book_and_quil:
# Permission is ca.lore_edit.book
use_permission: true
append:
# If adding lore using book & quil is enabled
enabled: false
# Cost used every time
fixed_cost: 1
# Cost used for every lore line added
per_line_cost: 0
# Use left item vanilla cost penalty if any
shared_increase: false
# Increase shared left item cost penalty
shared_additive: false
# If adding the lore consume the book & quil
do_consume: false
# Allow using color code and hexadecimal color when editing lore via book & quil
#
# Color code are prefixed by "&" and hexadecimal color by "#"
# Color code will not be applied if it colors nothing. "&&" can be used to write "&"
allow_color_code: true
allow_hexadecimal_color: true
use_cost: 0
remove:
# If removing lore using book & quil is enabled
enabled: false
# Cost used every time
fixed_cost: 1
# Cost used for every lore line removed
per_line_cost: 0
# Use left item vanilla cost penalty if any
shared_increase: false
# Increase shared left item cost penalty
shared_additive: false
# If removing the lore consume the book & quil
do_consume: false
# If the color should get back to color code or hex format
remove_color_on_remove: true
# Cost of replacing colors
remove_color_cost: 0
paper:
# Permission is ca.lore_edit.paper
use_permission: true
# what order should the lines should get added/removed (start/end, if invalid or not present will be end)
order: "end"
append_line:
# If adding lore line using paper is enabled
enabled: false
# Cost used every time
fixed_cost: 1
# Use left item vanilla cost penalty if any
shared_increase: false
# Increase shared left item cost penalty
shared_additive: false
# If adding the lore line consume the paper
do_consume: false
# Allow using color code and hexadecimal color when editing lore via book & quil
#
# Color code are prefixed by "&" and hexadecimal color by "#"
# Color code will not be applied if it colors nothing. "&&" can be used to write "&"
allow_color_code: true
allow_hexadecimal_color: true
color_use_cost: 0
remove_line:
# If removing lore line using paper is enabled
enabled: false
# Cost used every time
fixed_cost: 1
# Use left item vanilla cost penalty if any
shared_increase: false
# Increase shared left item cost penalty
shared_additive: false
# If removing the lore line consume the paper
do_consume: false
# If the color should get back to color code or hex format
remove_color_on_remove: true
# Cost of replacing colors
remove_color_cost: 0
# Whether to show debug logging # Whether to show debug logging
debug_log: false debug_log: false

View file

@ -42,13 +42,19 @@ permissions:
# color permissions # color permissions
ca.color.code: ca.color.code:
default: op default: op
description: Allow player to use color code if permission is required (toggleable) description: Allow player to use color code if enabled (toggleable)
ca.color.hex: ca.color.hex:
default: op default: op
description: Allow player to use hexadecimal color if permission is required (toggleable) description: Allow player to use hexadecimal color if enabled (toggleable)
# lore edit permissions
ca.lore_edit.book:
default: op
description: Allow player to edit lore via book and quil if enabled (toggleable)
ca.lore_edit.paper:
default: op
description: Allow player to edit lore via paper if enabled (toggleable)
# soft depend on old name of this plugin (UnsafeEnchantsPlus), so I can disable it if it is on the same server
# soft depend on old name (UnsafeEnchantsPlus), so I can disable it if it is on the same server (old name for this plugin)
# Also depend to other plugin for compatibility # Also depend to other plugin for compatibility
softdepend: softdepend:
- UnsafeEnchantsPlus - UnsafeEnchantsPlus

View file

@ -14,7 +14,7 @@ import org.mockbukkit.mockbukkit.entity.PlayerMock;
import org.mockbukkit.mockbukkit.inventory.ItemStackMock; import org.mockbukkit.mockbukkit.inventory.ItemStackMock;
import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.tests.ConfigResetCustomAnvilTest; import xyz.alexcrea.cuanvil.tests.ConfigResetCustomAnvilTest;
import xyz.alexcrea.cuanvil.util.AnvilFuseTestData; import xyz.alexcrea.cuanvil.data.AnvilFuseTestData;
import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil;
import java.util.List; import java.util.List;
@ -88,16 +88,16 @@ public class EnchantmentUtilTests extends ConfigResetCustomAnvilTest {
); );
// Test with no permission // Test with no permission
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData);
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData2); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData2);
// Add permission // Add permission
PermissionAttachment attachment = player.addAttachment(plugin); PermissionAttachment attachment = player.addAttachment(plugin);
attachment.setPermission(permission, true); attachment.setPermission(permission, true);
// Test with new permission // Test with new permission
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData);
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData2); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2);
} }
@Test @Test
@ -161,24 +161,24 @@ public class EnchantmentUtilTests extends ConfigResetCustomAnvilTest {
); );
// Test failing result first // Test failing result first
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData2); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData2);
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData3); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData3);
// Test working sharpness 2 // Test working sharpness 2
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData);
// Set merge limit to 2 & test // Set merge limit to 2 & test
ConfigHolder.DEFAULT_CONFIG.getConfig().set("disable-merge-over.minecraft:sharpness", 1); ConfigHolder.DEFAULT_CONFIG.getConfig().set("disable-merge-over.minecraft:sharpness", 1);
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData);
// Add permission // Add permission
PermissionAttachment attachment = player.addAttachment(plugin); PermissionAttachment attachment = player.addAttachment(plugin);
attachment.setPermission(permission, true); attachment.setPermission(permission, true);
// Test working sharpness 2 // Test working sharpness 2
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData);
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData2); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2);
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData3); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData3);
} }
} }

View file

@ -9,7 +9,6 @@ import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.Repairable; import org.bukkit.inventory.meta.Repairable;
import org.eclipse.aether.util.ConfigUtils;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -17,7 +16,7 @@ import org.junit.jupiter.api.Test;
import org.mockbukkit.mockbukkit.entity.PlayerMock; import org.mockbukkit.mockbukkit.entity.PlayerMock;
import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.tests.SharedCustomAnvilTest; import xyz.alexcrea.cuanvil.tests.SharedCustomAnvilTest;
import xyz.alexcrea.cuanvil.util.AnvilFuseTestData; import xyz.alexcrea.cuanvil.data.AnvilFuseTestData;
import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil;
import xyz.alexcrea.cuanvil.util.CommonItemUtil; import xyz.alexcrea.cuanvil.util.CommonItemUtil;
@ -68,7 +67,7 @@ public class AnvilFuseTests extends SharedCustomAnvilTest {
5 5
); );
AnvilFuseTestUtil.executeAnvilTest(anvil, player, data); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, data);
} }
@Test @Test
@ -88,7 +87,7 @@ public class AnvilFuseTests extends SharedCustomAnvilTest {
5 5
); );
AnvilFuseTestUtil.executeAnvilTest(anvil, player, data); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, data);
} }
@Test @Test
@ -102,7 +101,7 @@ public class AnvilFuseTests extends SharedCustomAnvilTest {
null null
); );
AnvilFuseTestUtil.executeAnvilTest(anvil, player, data); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, data);
} }
// Note: currently anvil can only have null name. maybe handle differently later // Note: currently anvil can only have null name. maybe handle differently later
@ -121,7 +120,7 @@ public class AnvilFuseTests extends SharedCustomAnvilTest {
1, 1, null 1, 1, null
); );
AnvilFuseTestUtil.executeAnvilTest(anvil, player, data); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, data);
} }
} }

View file

@ -0,0 +1,623 @@
package xyz.alexcrea.cuanvil.anvil;
import io.delilaheve.util.ConfigOptions;
import org.bukkit.Material;
import org.bukkit.event.Event;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.AnvilInventory;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockbukkit.mockbukkit.entity.PlayerMock;
import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.data.AnvilClickTestData;
import xyz.alexcrea.cuanvil.data.AnvilFuseTestData;
import xyz.alexcrea.cuanvil.data.TestDataContainer;
import xyz.alexcrea.cuanvil.tests.SharedCustomAnvilTest;
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil;
import xyz.alexcrea.cuanvil.util.config.LoreEditType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class LoreEditTests extends SharedCustomAnvilTest {
private static AnvilInventory anvil;
private static PlayerMock player;
private static final String COLORED_LORE_LINE = "§x§1§2§3§4§5§6TEST §atest";
private static final String UNCOLORED_LORE_LINE = "#123456TEST &atest";
private static final int COLOR_USE_COST = 1;
private static final int COLOR_REMOVE_COST = 2;
private static final int NON_ONE_TEST_FIXED_COST = 10;
private static final int NON_ZERO_TEST_LINE_COST = 2;
private static ItemStack emptyItem;
private static ItemStack oneColoredLoreItem;
private static ItemStack twoColoredLoreItem;
private static ItemStack oneUncoloredLoreItem;
private static ItemStack twoUncoloredLoreItem;
private static ItemStack emptyPaperStack;
private static ItemStack emptyPaper63Item;
private static ItemStack emptyPaperOne;
private static ItemStack coloredPaperStack;
private static ItemStack coloredPaper63Item;
private static ItemStack coloredPaperOne;
private static ItemStack uncoloredPaperStack;
private static ItemStack uncoloredPaper63Item;
private static ItemStack uncoloredPaperOne;
private static ItemStack emptyBook;
private static ItemStack coloredBook1Line;
private static ItemStack coloredBook2Line;
private static ItemStack uncoloredBook1Line;
private static ItemStack uncoloredBook2Line;
private static TestDataContainer defBookAppend;
private static TestDataContainer defBookRemove;
private static TestDataContainer defPaperAppend;
private static TestDataContainer defPaperRemove;
private static TestDataContainer defMultilineBookAppend;
private static TestDataContainer defMultilineBookRemove;
private static TestDataContainer defMultilinePaperAppend;
private static TestDataContainer defMultilinePaperRemove;
private static Map<LoreEditType, TestDataContainer> singleLineTypeToTest;
private static Map<LoreEditType, TestDataContainer> multiLineTypeToTest;
@BeforeAll
public static void setUp() {
// Mock used player & open anvil
player = server.addPlayer();
Inventory anvil = server.createInventory(player, InventoryType.ANVIL);
LoreEditTests.anvil = (AnvilInventory) anvil;
player.openInventory(anvil);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(ConfigOptions.DEBUG_LOGGING, true);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(ConfigOptions.VERBOSE_DEBUG_LOGGING, true);
// Applied item
ItemStack item = new ItemStack(Material.STICK, 33);
ItemMeta meta = item.getItemMeta();
ArrayList<String> lore = new ArrayList<>();
emptyItem = item.clone();
lore.add(COLORED_LORE_LINE);
meta.setLore(lore);
item.setItemMeta(meta);
oneColoredLoreItem = item.clone();
lore.add(COLORED_LORE_LINE);
meta.setLore(lore);
item.setItemMeta(meta);
twoColoredLoreItem = item.clone();
lore.clear();
lore.add(UNCOLORED_LORE_LINE);
meta.setLore(lore);
item.setItemMeta(meta);
oneUncoloredLoreItem = item.clone();
lore.add(UNCOLORED_LORE_LINE);
meta.setLore(lore);
item.setItemMeta(meta);
twoUncoloredLoreItem = item.clone();
lore.clear();
// Paper items
item = new ItemStack(Material.PAPER, 64);
meta = item.getItemMeta();
emptyPaperStack = item.clone();
item.setAmount(63);
emptyPaper63Item = item.clone();
item.setAmount(1);
emptyPaperOne = item.clone();
item.setAmount(64);
meta.setDisplayName(COLORED_LORE_LINE);
item.setItemMeta(meta);
coloredPaperStack = item.clone();
item.setAmount(63);
coloredPaper63Item = item.clone();
item.setAmount(1);
coloredPaperOne = item.clone();
item.setAmount(64);
meta.setDisplayName(UNCOLORED_LORE_LINE);
item.setItemMeta(meta);
uncoloredPaperStack = item.clone();
item.setAmount(63);
uncoloredPaper63Item = item.clone();
item.setAmount(1);
uncoloredPaperOne = item.clone();
// Book items
item = new ItemStack(Material.WRITABLE_BOOK);
BookMeta bookmeta = (BookMeta) item.getItemMeta();
emptyBook = item.clone();
bookmeta.setPages(COLORED_LORE_LINE);
item.setItemMeta(bookmeta);
coloredBook1Line = item.clone();
bookmeta.setPages(COLORED_LORE_LINE + "\n" + COLORED_LORE_LINE);
item.setItemMeta(bookmeta);
coloredBook2Line = item.clone();
bookmeta.setPages(UNCOLORED_LORE_LINE);
item.setItemMeta(bookmeta);
uncoloredBook1Line = item.clone();
bookmeta.setPages(UNCOLORED_LORE_LINE + "\n" + UNCOLORED_LORE_LINE);
item.setItemMeta(bookmeta);
uncoloredBook2Line = item.clone();
// Default working test data
defBookAppend = new TestDataContainer(
new AnvilFuseTestData(
emptyItem, uncoloredBook1Line,
oneColoredLoreItem,
1
), new AnvilClickTestData(
null, emptyBook, null, oneColoredLoreItem,
1, Event.Result.DENY,
true, Event.Result.DENY
));
defBookRemove = new TestDataContainer(
new AnvilFuseTestData(
oneColoredLoreItem, emptyBook,
emptyItem,
1
), new AnvilClickTestData(
null, coloredBook1Line, null, emptyItem,
1, Event.Result.DENY,
true, Event.Result.DENY
));
defPaperAppend = new TestDataContainer(
new AnvilFuseTestData(
emptyItem, uncoloredPaperStack,
oneColoredLoreItem,
1
), new AnvilClickTestData(
emptyPaperOne, uncoloredPaper63Item, null, oneColoredLoreItem,
1, Event.Result.DENY,
true, Event.Result.DENY
));
defPaperRemove = new TestDataContainer(
new AnvilFuseTestData(
oneColoredLoreItem, emptyPaperStack,
emptyItem,
1
), new AnvilClickTestData(
coloredPaperOne, emptyPaper63Item, null, emptyItem,
1, Event.Result.DENY,
true, Event.Result.DENY
));
defMultilineBookAppend = new TestDataContainer(
new AnvilFuseTestData(
emptyItem, uncoloredBook2Line,
twoColoredLoreItem,
1
), new AnvilClickTestData(
null, emptyBook, null, twoColoredLoreItem,
1, Event.Result.DENY,
true, Event.Result.DENY
));
defMultilineBookRemove = new TestDataContainer(
new AnvilFuseTestData(
twoColoredLoreItem, emptyBook,
emptyItem,
1
), new AnvilClickTestData(
null, coloredBook2Line, null, emptyItem,
1, Event.Result.DENY,
true, Event.Result.DENY
));
defMultilinePaperAppend = new TestDataContainer(
new AnvilFuseTestData(
oneColoredLoreItem, uncoloredPaperStack,
twoColoredLoreItem,
1
), new AnvilClickTestData(
emptyPaperOne, uncoloredPaper63Item, null, twoColoredLoreItem,
1, Event.Result.DENY,
true, Event.Result.DENY
));
defMultilinePaperRemove = new TestDataContainer(
new AnvilFuseTestData(
twoColoredLoreItem, emptyPaperStack,
oneColoredLoreItem,
1
), new AnvilClickTestData(
coloredPaperOne, emptyPaper63Item, null, oneColoredLoreItem,
1, Event.Result.DENY,
true, Event.Result.DENY
));
singleLineTypeToTest = Map.of(
LoreEditType.APPEND_BOOK, defBookAppend,
LoreEditType.REMOVE_BOOK, defBookRemove,
LoreEditType.APPEND_PAPER, defPaperAppend,
LoreEditType.REMOVE_PAPER, defPaperRemove
);
multiLineTypeToTest = Map.of(
LoreEditType.APPEND_BOOK, defMultilineBookAppend,
LoreEditType.REMOVE_BOOK, defMultilineBookRemove,
LoreEditType.APPEND_PAPER, defMultilinePaperAppend,
LoreEditType.REMOVE_PAPER, defMultilinePaperRemove
);
}
@BeforeEach
public void prepareAnvil() {
anvil.clear();
// Make sure we reset value in case it got modified
for (@NotNull LoreEditType type : LoreEditType.values()) {
// Make sure it is enabled for the tests (unless its is enabled test)
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.IS_ENABLED, true);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.DO_CONSUME, false);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.FIXED_COST, 1);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.PER_LINE_COST, 0);
// Make sur color is enabled by default
if (type.isAppend()) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.ALLOW_HEX_COLOR, true);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.ALLOW_COLOR_CODE, true);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.USE_COLOR_COST, 0);
} else {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_ON_LORE_REMOVE, false);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_COST, 0);
}
}
// Disable them by default and test them on specific tests
ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.BOOK_PERMISSION_NEEDED, false);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.PAPER_PERMISSION_NEEDED, false);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.PAPER_EDIT_ORDER, LoreEditConfigUtil.DEFAULT_PAPER_EDIT_ORDER);
}
@AfterAll
public static void tearDown() {
player = null;
anvil = null;
}
public @Nullable ItemStack uncoloredEquivalent(@Nullable ItemStack colored) {
// null check
if (null == colored) return null;
if (oneColoredLoreItem == colored) return oneUncoloredLoreItem;
if (twoColoredLoreItem == colored) return twoUncoloredLoreItem;
if (coloredPaperStack == colored) return uncoloredPaperStack;
if (coloredPaper63Item == colored) return uncoloredPaper63Item;
if (coloredPaperOne == colored) return uncoloredPaperOne;
if (coloredBook1Line == colored) return uncoloredBook1Line;
if (coloredBook2Line == colored) return uncoloredBook2Line;
// They already are uncolored
if (oneUncoloredLoreItem == colored) return oneUncoloredLoreItem;
if (twoUncoloredLoreItem == colored) return twoUncoloredLoreItem;
if (uncoloredPaperStack == colored) return uncoloredPaperStack;
if (uncoloredPaper63Item == colored) return uncoloredPaper63Item;
if (uncoloredPaperOne == colored) return uncoloredPaperOne;
if (uncoloredBook1Line == colored) return uncoloredBook1Line;
if (uncoloredBook2Line == colored) return uncoloredBook2Line;
// No lore items return themself
if (emptyItem == colored) return emptyItem;
if (emptyBook == colored) return emptyBook;
if (emptyPaperStack == colored) return emptyPaperStack;
if (emptyPaper63Item == colored) return emptyPaper63Item;
if (emptyPaperOne == colored) return emptyPaperOne;
Assertions.fail("Could not find uncolored version of " + colored);
return null;
}
public static List<LoreEditType> onlyAppendTypes() {
ArrayList<LoreEditType> typeList = new ArrayList<>();
for (@NotNull LoreEditType type : LoreEditType.values()) {
if (type.isAppend()) typeList.add(type);
}
return typeList;
}
public static List<LoreEditType> onlyRemoveTypes() {
ArrayList<LoreEditType> typeList = new ArrayList<>();
for (@NotNull LoreEditType type : LoreEditType.values()) {
if (!type.isAppend()) typeList.add(type);
}
return typeList;
}
@Test
public void simpleTest() {
// Test all defaults to make sure they works
for (LoreEditType type : LoreEditType.values()) {
singleLineTypeToTest.get(type).executeTest(anvil, player);
multiLineTypeToTest.get(type).executeTest(anvil, player);
}
}
@ParameterizedTest
@EnumSource(LoreEditType.class)
public void testPermissionNeeded_DEOP(LoreEditType type) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.BOOK_PERMISSION_NEEDED, true);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.PAPER_PERMISSION_NEEDED, true);
player.setOp(false);
singleLineTypeToTest.get(type).nullifyResult().executeTest(anvil, player);
multiLineTypeToTest.get(type).nullifyResult().executeTest(anvil, player);
}
@ParameterizedTest
@EnumSource(LoreEditType.class)
public void testPermissionNeeded_OP(LoreEditType type) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.BOOK_PERMISSION_NEEDED, true);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.PAPER_PERMISSION_NEEDED, true);
player.setOp(true);
singleLineTypeToTest.get(type).executeTest(anvil, player);
multiLineTypeToTest.get(type).executeTest(anvil, player);
}
@ParameterizedTest
@EnumSource(LoreEditType.class)
public void testLoreTypeDisabled(LoreEditType type) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.IS_ENABLED, false);
singleLineTypeToTest.get(type).nullifyResult().executeTest(anvil, player);
multiLineTypeToTest.get(type).nullifyResult().executeTest(anvil, player);
}
@ParameterizedTest
@EnumSource(LoreEditType.class)
public void testFixedCost_Default(LoreEditType type) {
singleLineTypeToTest.get(type).setCost(1).executeTest(anvil, player);
multiLineTypeToTest.get(type).setCost(1).executeTest(anvil, player);
}
@ParameterizedTest
@EnumSource(LoreEditType.class)
public void testFixedCost_Modified(LoreEditType type) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.FIXED_COST, NON_ONE_TEST_FIXED_COST);
singleLineTypeToTest.get(type).setCost(NON_ONE_TEST_FIXED_COST).executeTest(anvil, player);
multiLineTypeToTest.get(type).setCost(NON_ONE_TEST_FIXED_COST).executeTest(anvil, player);
}
@ParameterizedTest
@EnumSource(LoreEditType.class)
public void testLineCost_Modified(LoreEditType type) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.PER_LINE_COST, NON_ZERO_TEST_LINE_COST);
if (type.isMultiLine()) {
singleLineTypeToTest.get(type).setCost(NON_ZERO_TEST_LINE_COST + LoreEditConfigUtil.DEFAULT_FIXED_COST).executeTest(anvil, player);
multiLineTypeToTest.get(type).setCost(2 * NON_ZERO_TEST_LINE_COST + LoreEditConfigUtil.DEFAULT_FIXED_COST).executeTest(anvil, player);
} else {
singleLineTypeToTest.get(type).executeTest(anvil, player);
multiLineTypeToTest.get(type).executeTest(anvil, player);
}
}
@ParameterizedTest
@EnumSource(LoreEditType.class)
public void testColorCost(LoreEditType type) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.USE_COLOR_COST, COLOR_USE_COST);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_COST, COLOR_REMOVE_COST);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_ON_LORE_REMOVE, true);
TestDataContainer singleLData = singleLineTypeToTest.get(type);
TestDataContainer multiLData = multiLineTypeToTest.get(type);
if (type.isAppend()) {
singleLData.setCost(COLOR_USE_COST + LoreEditConfigUtil.DEFAULT_FIXED_COST).executeTest(anvil, player);
multiLData.setCost(COLOR_USE_COST + LoreEditConfigUtil.DEFAULT_FIXED_COST).executeTest(anvil, player);
} else {
singleLData
.setCost(COLOR_REMOVE_COST + LoreEditConfigUtil.DEFAULT_FIXED_COST)
.setClickRight(uncoloredEquivalent(singleLData.getRightClick()))
.setClickLeft(uncoloredEquivalent(singleLData.getLeftClick()))
.executeTest(anvil, player);
multiLData.setCost(COLOR_REMOVE_COST + LoreEditConfigUtil.DEFAULT_FIXED_COST)
.setClickRight(uncoloredEquivalent(multiLData.getRightClick()))
.setClickLeft(uncoloredEquivalent(multiLData.getLeftClick()))
.executeTest(anvil, player);
}
}
@ParameterizedTest
@MethodSource("onlyAppendTypes")
public void testColorDisabled(LoreEditType type) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.USE_COLOR_COST, COLOR_USE_COST);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.ALLOW_HEX_COLOR, false);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.ALLOW_COLOR_CODE, false);
TestDataContainer singleLData = singleLineTypeToTest.get(type);
TestDataContainer multiLData = multiLineTypeToTest.get(type);
singleLData
.setExpectedResult(uncoloredEquivalent(singleLData.getExpectedFuse()))
.executeTest(anvil, player);
multiLData
.setFuseLeft(uncoloredEquivalent(multiLData.getLeftFuse()))
.setExpectedResult(uncoloredEquivalent(multiLData.getExpectedFuse()))
.executeTest(anvil, player);
}
@ParameterizedTest
@MethodSource("onlyRemoveTypes")
public void testColorRemoveEnabled(LoreEditType type) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.REMOVE_COLOR_ON_LORE_REMOVE, true);
TestDataContainer singleLData = singleLineTypeToTest.get(type);
TestDataContainer multiLData = multiLineTypeToTest.get(type);
singleLData
.setClickRight(uncoloredEquivalent(singleLData.getRightClick()))
.setClickLeft(uncoloredEquivalent(singleLData.getLeftClick()))
.executeTest(anvil, player);
multiLData
.setClickRight(uncoloredEquivalent(multiLData.getRightClick()))
.setClickLeft(uncoloredEquivalent(multiLData.getLeftClick()))
.executeTest(anvil, player);
}
@ParameterizedTest
@MethodSource("onlyRemoveTypes")
public void testDoConsume(LoreEditType type) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.DO_CONSUME, true);
TestDataContainer singleLData = singleLineTypeToTest.get(type);
TestDataContainer multiLData = multiLineTypeToTest.get(type);
// NOTE: we set to null right item only on multi line bc the default data has more than one paper and would go to the left instead
singleLData = singleLData
.setClickRight(type.isMultiLine() ? null : singleLData.getRightClick())
.setClickLeft(null);
singleLData.executeTest(anvil, player);
multiLData = multiLData
.setClickRight(type.isMultiLine() ? null : singleLData.getRightClick())
.setClickLeft(null);
multiLData.executeTest(anvil, player);
// Single paper consumed
if (!type.isMultiLine()) {
singleLData.setFuseRight(emptyPaperOne).setClickRight(null).executeTest(anvil, player);
multiLData.setFuseRight(emptyPaperOne).setClickRight(null).executeTest(anvil, player);
}
}
private void SinglePaperTestPart(LoreEditType type, TestDataContainer data, ItemStack expectedFuse, ItemStack postFuse) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.DO_CONSUME, false);
data = data.setFuseRight(expectedFuse).setClickLeft(null).setClickRight(postFuse);
data.executeTest(anvil, player);
ConfigHolder.DEFAULT_CONFIG.getConfig().set(type.getRootPath() + "." + LoreEditConfigUtil.DO_CONSUME, true);
data.setClickRight(null).executeTest(anvil, player);
}
@Test
public void testSinglePaper_Append() {
SinglePaperTestPart(LoreEditType.APPEND_PAPER,
singleLineTypeToTest.get(LoreEditType.APPEND_PAPER),
uncoloredPaperOne, emptyPaperOne);
SinglePaperTestPart(LoreEditType.APPEND_PAPER,
multiLineTypeToTest.get(LoreEditType.APPEND_PAPER),
uncoloredPaperOne, emptyPaperOne);
}
@Test
public void testSinglePaper_Remove() {
SinglePaperTestPart(LoreEditType.REMOVE_PAPER,
singleLineTypeToTest.get(LoreEditType.REMOVE_PAPER),
emptyPaperOne, coloredPaperOne);
SinglePaperTestPart(LoreEditType.REMOVE_PAPER,
multiLineTypeToTest.get(LoreEditType.REMOVE_PAPER),
emptyPaperOne, coloredPaperOne);
}
@NotNull
private static ItemStack insertToLore(@NotNull ItemStack item, int index, @NotNull String toAppend) {
item = item.clone();
ItemMeta meta = item.getItemMeta();
Assertions.assertNotNull(meta);
Assertions.assertTrue(meta.hasLore());
ArrayList<String> lore = new ArrayList<>(meta.getLore());
lore.add(index, toAppend);
meta.setLore(lore);
item.setItemMeta(meta);
return item;
}
@NotNull
private static ItemStack setDisplayedName(@NotNull ItemStack item, @NotNull String name) {
item = item.clone();
ItemMeta meta = item.getItemMeta();
Assertions.assertNotNull(meta);
meta.setDisplayName(name);
item.setItemMeta(meta);
return item;
}
private static final String TESTED_LORE = "tested_lore";
@ParameterizedTest
@ValueSource(strings = {"sTaRt", "eNd"})
public void testPaperOrder_Append(String order) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.PAPER_EDIT_ORDER, order);
ItemStack result = insertToLore(oneColoredLoreItem, "start".equalsIgnoreCase(order) ? 0 : 1, TESTED_LORE);
ItemStack paper = setDisplayedName(emptyPaperOne, TESTED_LORE);
new TestDataContainer(
new AnvilFuseTestData(
oneColoredLoreItem, paper, result, 1
),
new AnvilClickTestData(
null, emptyPaperOne, null, result,
1,
Event.Result.DENY, true, Event.Result.DENY
)
).executeTest(anvil, player);
}
@ParameterizedTest
@ValueSource(strings = {"sTaRt", "eNd"})
public void testPaperOrder_Remove(String order) {
ConfigHolder.DEFAULT_CONFIG.getConfig().set(LoreEditConfigUtil.PAPER_EDIT_ORDER, order);
ItemStack from = insertToLore(oneColoredLoreItem, "start".equalsIgnoreCase(order) ? 0 : 1, TESTED_LORE);
ItemStack paper = setDisplayedName(emptyPaperOne, TESTED_LORE);
new TestDataContainer(
new AnvilFuseTestData(
from, emptyPaperOne, oneColoredLoreItem, 1
),
new AnvilClickTestData(
null, paper, null, oneColoredLoreItem,
1,
Event.Result.DENY, true, Event.Result.DENY
)
).executeTest(anvil, player);
}
//TODO work penalty test
}

View file

@ -16,7 +16,7 @@ import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.enchant.CAEnchantment; import xyz.alexcrea.cuanvil.enchant.CAEnchantment;
import xyz.alexcrea.cuanvil.group.EnchantConflictGroup; import xyz.alexcrea.cuanvil.group.EnchantConflictGroup;
import xyz.alexcrea.cuanvil.tests.ConfigResetCustomAnvilTest; import xyz.alexcrea.cuanvil.tests.ConfigResetCustomAnvilTest;
import xyz.alexcrea.cuanvil.util.AnvilFuseTestData; import xyz.alexcrea.cuanvil.data.AnvilFuseTestData;
import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil;
import xyz.alexcrea.cuanvil.util.CommonItemUtil; import xyz.alexcrea.cuanvil.util.CommonItemUtil;
@ -71,7 +71,7 @@ public class ConflictApiTests extends ConfigResetCustomAnvilTest {
Assertions.assertNotNull(sharpness); Assertions.assertNotNull(sharpness);
// Testing default conflict (illegal item should not be produced) // Testing default conflict (illegal item should not be produced)
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData);
// Try to find & remove conflict // Try to find & remove conflict
EnchantConflictGroup conflict = findGroup("sword_enchant_conflict"); EnchantConflictGroup conflict = findGroup("sword_enchant_conflict");
@ -79,7 +79,7 @@ public class ConflictApiTests extends ConfigResetCustomAnvilTest {
// Test what happen when we remove the conflict (illegal item should be allowed) // Test what happen when we remove the conflict (illegal item should be allowed)
ConflictAPI.removeConflict(conflict); ConflictAPI.removeConflict(conflict);
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData);
// We create and add a new conflict // We create and add a new conflict
ConflictBuilder builder = new ConflictBuilder("sword_enchant_conflict"); ConflictBuilder builder = new ConflictBuilder("sword_enchant_conflict");
@ -88,11 +88,11 @@ public class ConflictApiTests extends ConfigResetCustomAnvilTest {
// Nothing should change as it is not new: it was previously deleted // Nothing should change as it is not new: it was previously deleted
Assertions.assertFalse(builder.registerIfNew()); Assertions.assertFalse(builder.registerIfNew());
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData);
// Now the conflict should be registered and conflict should exist // Now the conflict should be registered and conflict should exist
Assertions.assertTrue(builder.registerIfAbsent()); Assertions.assertTrue(builder.registerIfAbsent());
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData);
} }
@Test @Test

View file

@ -13,7 +13,7 @@ import org.mockbukkit.mockbukkit.inventory.ItemStackMock;
import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe; import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe;
import xyz.alexcrea.cuanvil.tests.ConfigResetCustomAnvilTest; import xyz.alexcrea.cuanvil.tests.ConfigResetCustomAnvilTest;
import xyz.alexcrea.cuanvil.util.AnvilFuseTestData; import xyz.alexcrea.cuanvil.data.AnvilFuseTestData;
import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@ -57,14 +57,14 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest {
); );
// Testing default conflict (no recipe exist) // Testing default conflict (no recipe exist)
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData);
// Add and test recipe // Add and test recipe
AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName);
builder.setExactCount(true).setLeftItem(stick).setResultItem(stick).setXpCostPerCraft(2); builder.setExactCount(true).setLeftItem(stick).setResultItem(stick).setXpCostPerCraft(2);
assertTrue(builder.registerIfAbsent()); assertTrue(builder.registerIfAbsent());
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData);
AnvilCustomRecipe recipe = getByName(recipeName); AnvilCustomRecipe recipe = getByName(recipeName);
assertNotNull(recipe); assertNotNull(recipe);
@ -72,21 +72,21 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest {
// Remove recipe // Remove recipe
assertTrue(CustomAnvilRecipeApi.removeRecipe(recipe)); assertTrue(CustomAnvilRecipeApi.removeRecipe(recipe));
assertFalse(CustomAnvilRecipeApi.removeRecipe(recipe)); assertFalse(CustomAnvilRecipeApi.removeRecipe(recipe));
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData);
recipe = getByName(recipeName); recipe = getByName(recipeName);
assertNull(recipe); assertNull(recipe);
// Try to add deleted recipe with no override (should not add) // Try to add deleted recipe with no override (should not add)
assertFalse(CustomAnvilRecipeApi.addRecipe(builder, false)); assertFalse(CustomAnvilRecipeApi.addRecipe(builder, false));
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData);
recipe = getByName(recipeName); recipe = getByName(recipeName);
assertNull(recipe); assertNull(recipe);
// Try to add deleted recipe with override (should add) // Try to add deleted recipe with override (should add)
assertTrue(CustomAnvilRecipeApi.addRecipe(builder, true)); assertTrue(CustomAnvilRecipeApi.addRecipe(builder, true));
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData);
recipe = getByName(recipeName); recipe = getByName(recipeName);
assertNotNull(recipe); assertNotNull(recipe);
@ -119,7 +119,7 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest {
null, null null, null
); );
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData);
AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName); AnvilRecipeBuilder builder = new AnvilRecipeBuilder(recipeName);
builder.setExactCount(false) builder.setExactCount(false)
@ -130,8 +130,8 @@ public class CustomAnvilRecipeApiTests extends ConfigResetCustomAnvilTest {
assertTrue(builder.registerIfAbsent()); assertTrue(builder.registerIfAbsent());
// Now working test // Now working test
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData1); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData1);
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData2); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData2);
} }
@Nullable @Nullable

View file

@ -13,7 +13,7 @@ import org.mockbukkit.mockbukkit.entity.PlayerMock;
import org.mockbukkit.mockbukkit.inventory.ItemStackMock; import org.mockbukkit.mockbukkit.inventory.ItemStackMock;
import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.tests.ConfigResetCustomAnvilTest; import xyz.alexcrea.cuanvil.tests.ConfigResetCustomAnvilTest;
import xyz.alexcrea.cuanvil.util.AnvilFuseTestData; import xyz.alexcrea.cuanvil.data.AnvilFuseTestData;
import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil; import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@ -58,7 +58,7 @@ public class UnitRepairApiTests extends ConfigResetCustomAnvilTest {
2 2
); );
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData);
} }
@Test @Test
@ -76,7 +76,7 @@ public class UnitRepairApiTests extends ConfigResetCustomAnvilTest {
// Remove unit repair // Remove unit repair
assertTrue(UnitRepairApi.removeUnitRepair(Material.DIAMOND, Material.DIAMOND_PICKAXE)); assertTrue(UnitRepairApi.removeUnitRepair(Material.DIAMOND, Material.DIAMOND_PICKAXE));
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData);
// see override // see override
assertFalse(UnitRepairApi.addUnitRepair(Material.DIAMOND, Material.DIAMOND_PICKAXE, 0.25)); assertFalse(UnitRepairApi.addUnitRepair(Material.DIAMOND, Material.DIAMOND_PICKAXE, 0.25));
@ -107,12 +107,12 @@ public class UnitRepairApiTests extends ConfigResetCustomAnvilTest {
2 2
); );
AnvilFuseTestUtil.executeAnvilTest(anvil, player, nullResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, nullResultData);
// Add unit repair // Add unit repair
assertTrue(UnitRepairApi.addUnitRepair(Material.STICK, Material.DIAMOND_PICKAXE)); assertTrue(UnitRepairApi.addUnitRepair(Material.STICK, Material.DIAMOND_PICKAXE));
assertFalse(UnitRepairApi.addUnitRepair(Material.STICK, Material.DIAMOND_PICKAXE)); assertFalse(UnitRepairApi.addUnitRepair(Material.STICK, Material.DIAMOND_PICKAXE));
AnvilFuseTestUtil.executeAnvilTest(anvil, player, legalResultData); AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, legalResultData);
} }
} }

View file

@ -0,0 +1,50 @@
package xyz.alexcrea.cuanvil.data;
import org.bukkit.event.Event;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
public record AnvilClickTestData(
@Nullable ItemStack leftItem,
@Nullable ItemStack rightItem,
@Nullable ItemStack resultSlotItem,
@Nullable ItemStack expectedCursor,
int levelCost,
@Nullable Event.Result expectedResult,
boolean testNoLevelNoChange,
@Nullable Event.Result npChangeResult
) {
public AnvilClickTestData(@Nullable ItemStack leftItem,
@Nullable ItemStack rightItem,
@Nullable ItemStack resultSlotItem,
@Nullable ItemStack expectedCursor,
int levelCost,
@Nullable Event.Result expectedResult) {
this(leftItem, rightItem, resultSlotItem,
expectedCursor, levelCost, expectedResult,
false, null);
}
public AnvilClickTestData(@Nullable ItemStack leftItem,
@Nullable ItemStack rightItem,
@Nullable ItemStack resultSlotItem,
@Nullable ItemStack expectedCursor,
int levelCost) {
this(leftItem, rightItem, resultSlotItem,
expectedCursor, levelCost, null);
}
public AnvilClickTestData(@Nullable ItemStack expectedCursor,
int levelCost,
@Nullable Event.Result expectedResult) {
this(null, null, null,
expectedCursor, levelCost, expectedResult,
false, null);
}
public AnvilClickTestData(@Nullable ItemStack expectedCursor,
int levelCost) {
this(expectedCursor, levelCost, null);
}
}

View file

@ -1,4 +1,4 @@
package xyz.alexcrea.cuanvil.util; package xyz.alexcrea.cuanvil.data;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;

View file

@ -0,0 +1,250 @@
package xyz.alexcrea.cuanvil.data;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.AnvilInventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Assertions;
import xyz.alexcrea.cuanvil.util.AnvilFuseTestUtil;
@SuppressWarnings("unused")
public record TestDataContainer(
@NotNull AnvilFuseTestData fuseData,
@Nullable AnvilClickTestData clickData
) {
public void executeTest(AnvilInventory anvil, Player player) {
executeFuseTest(anvil, player);
if (clickData != null) executeClickTest(anvil, player);
}
public void executeFuseTest(AnvilInventory anvil, HumanEntity player) {
AnvilFuseTestUtil.executeAnvilFuseTest(anvil, player, fuseData);
}
public void executeClickTest(AnvilInventory anvil, Player player) {
Assertions.assertNotNull(clickData);
AnvilFuseTestUtil.executeAnvilClickTest(anvil, player, clickData);
}
public @NotNull TestDataContainer nullifyResult() {
return new TestDataContainer(
new AnvilFuseTestData(
fuseData.leftItem(), fuseData.rightItem(),
null
), null);
}
public @NotNull TestDataContainer setCost(
@Nullable Integer priceAfterLeft,
@Nullable Integer priceAfterRight,
int priceAfterBoth
) {
AnvilFuseTestData data = new AnvilFuseTestData(
fuseData.leftItem(), fuseData.rightItem(), fuseData.expectedResult(),
fuseData.expectedAfterLeftPlaced(),
fuseData.expectedAfterRightPlaced(),
priceAfterLeft,
priceAfterRight,
priceAfterBoth
);
AnvilClickTestData CData;
if (clickData == null) {
CData = null;
} else {
CData = new AnvilClickTestData(
clickData.leftItem(), clickData.rightItem(), clickData.resultSlotItem(),
clickData.expectedCursor(), priceAfterBoth,
clickData.expectedResult(),
clickData.testNoLevelNoChange(), clickData.npChangeResult()
);
}
return new TestDataContainer(data, CData);
}
public @NotNull TestDataContainer setCost(
int priceAfterBoth
) {
return setCost(null, null, priceAfterBoth);
}
// Set fuse items
public @NotNull TestDataContainer setFuseItems(@Nullable ItemStack left, @Nullable ItemStack right, @Nullable ItemStack expected) {
AnvilFuseTestData data = new AnvilFuseTestData(
left, right, expected,
fuseData.expectedAfterLeftPlaced(),
fuseData.expectedAfterRightPlaced(),
fuseData.expectedPriceAfterLeftPlaced(),
fuseData.expectedPriceAfterRightPlaced(),
fuseData.expectedPriceAfterBothPlaced()
);
return new TestDataContainer(data, clickData);
}
public @NotNull TestDataContainer setFuseItems(
@Nullable ItemStack left, @Nullable ItemStack right, @Nullable ItemStack expected,
@Nullable ItemStack leftExpected, @Nullable ItemStack rightExpected) {
AnvilFuseTestData data = new AnvilFuseTestData(
left, right, expected,
leftExpected,
rightExpected,
fuseData.expectedPriceAfterLeftPlaced(),
fuseData.expectedPriceAfterRightPlaced(),
fuseData.expectedPriceAfterBothPlaced()
);
return new TestDataContainer(data, clickData);
}
public @NotNull TestDataContainer setFuseLeft(@Nullable ItemStack left) {
AnvilFuseTestData data = new AnvilFuseTestData(
left, fuseData.rightItem(), fuseData.expectedResult(),
fuseData.expectedAfterLeftPlaced(),
fuseData.expectedAfterRightPlaced(),
fuseData.expectedPriceAfterLeftPlaced(),
fuseData.expectedPriceAfterRightPlaced(),
fuseData.expectedPriceAfterBothPlaced()
);
return new TestDataContainer(data, clickData);
}
public @NotNull TestDataContainer setFuseRight(@Nullable ItemStack right) {
AnvilFuseTestData data = new AnvilFuseTestData(
fuseData.leftItem(), right, fuseData.expectedResult(),
fuseData.expectedAfterLeftPlaced(),
fuseData.expectedAfterRightPlaced(),
fuseData.expectedPriceAfterLeftPlaced(),
fuseData.expectedPriceAfterRightPlaced(),
fuseData.expectedPriceAfterBothPlaced()
);
return new TestDataContainer(data, clickData);
}
public @NotNull TestDataContainer setFuseExpected(@Nullable ItemStack expected) {
AnvilFuseTestData data = new AnvilFuseTestData(
fuseData.leftItem(), fuseData.rightItem(), expected,
fuseData.expectedAfterLeftPlaced(),
fuseData.expectedAfterRightPlaced(),
fuseData.expectedPriceAfterLeftPlaced(),
fuseData.expectedPriceAfterRightPlaced(),
fuseData.expectedPriceAfterBothPlaced()
);
return new TestDataContainer(data, clickData);
}
public @NotNull TestDataContainer setFuseExpectedLeft(@Nullable ItemStack expected) {
AnvilFuseTestData data = new AnvilFuseTestData(
fuseData.leftItem(), fuseData.rightItem(), fuseData.expectedResult(),
expected,
fuseData.expectedAfterRightPlaced(),
fuseData.expectedPriceAfterLeftPlaced(),
fuseData.expectedPriceAfterRightPlaced(),
fuseData.expectedPriceAfterBothPlaced()
);
return new TestDataContainer(data, clickData);
}
public @NotNull TestDataContainer setFuseExpectedRight(@Nullable ItemStack expected) {
AnvilFuseTestData data = new AnvilFuseTestData(
fuseData.leftItem(), fuseData.rightItem(), fuseData.expectedResult(),
fuseData.expectedAfterLeftPlaced(),
expected,
fuseData.expectedPriceAfterLeftPlaced(),
fuseData.expectedPriceAfterRightPlaced(),
fuseData.expectedPriceAfterBothPlaced()
);
return new TestDataContainer(data, clickData);
}
// Set click items
public @NotNull TestDataContainer setClickLeft(@Nullable ItemStack left) {
if (clickData == null) return this;
AnvilClickTestData data = new AnvilClickTestData(
left, clickData.rightItem(), clickData.resultSlotItem(), clickData.expectedCursor(),
clickData.levelCost(), clickData.expectedResult(),
clickData.testNoLevelNoChange(), clickData.npChangeResult()
);
return new TestDataContainer(fuseData, data);
}
public @NotNull TestDataContainer setClickRight(@Nullable ItemStack right) {
if (clickData == null) return this;
AnvilClickTestData data = new AnvilClickTestData(
clickData.leftItem(), right, clickData.resultSlotItem(), clickData.expectedCursor(),
clickData.levelCost(), clickData.expectedResult(),
clickData.testNoLevelNoChange(), clickData.npChangeResult()
);
return new TestDataContainer(fuseData, data);
}
public @NotNull TestDataContainer setClickOutput(@Nullable ItemStack output) {
if (clickData == null) return this;
AnvilClickTestData data = new AnvilClickTestData(
clickData.leftItem(), clickData.rightItem(), output, clickData.expectedCursor(),
clickData.levelCost(), clickData.expectedResult(),
clickData.testNoLevelNoChange(), clickData.npChangeResult()
);
return new TestDataContainer(fuseData, data);
}
public @NotNull TestDataContainer setClickCursor(@Nullable ItemStack cursor) {
if (clickData == null) return this;
AnvilClickTestData data = new AnvilClickTestData(
clickData.leftItem(), clickData.rightItem(), clickData.resultSlotItem(), cursor,
clickData.levelCost(), clickData.expectedResult(),
clickData.testNoLevelNoChange(), clickData.npChangeResult()
);
return new TestDataContainer(fuseData, data);
}
// Both item
public @NotNull TestDataContainer setExpectedResult(@Nullable ItemStack result) {
return setFuseExpected(result).setClickCursor(result);
}
// Get fuse item
public @Nullable ItemStack getLeftFuse() {
return fuseData.leftItem();
}
public @Nullable ItemStack getRightFuse() {
return fuseData.rightItem();
}
public @Nullable ItemStack getExpectedFuse() {
return fuseData.expectedResult();
}
public @Nullable ItemStack getLeftExpectedFuse() {
return fuseData.expectedAfterLeftPlaced();
}
public @Nullable ItemStack getRightExpectedFuse() {
return fuseData.expectedAfterRightPlaced();
}
// Get click item
public @Nullable ItemStack getLeftClick() {
return clickData == null ? null : clickData.leftItem();
}
public @Nullable ItemStack getRightClick() {
return clickData == null ? null : clickData.rightItem();
}
public @Nullable ItemStack getOutputClick() {
return clickData == null ? null : clickData.resultSlotItem();
}
public @Nullable ItemStack getCursorClick() {
return clickData == null ? null : clickData.expectedCursor();
}
}

View file

@ -3,7 +3,9 @@ package xyz.alexcrea.cuanvil.util;
import io.delilaheve.util.ItemUtil; import io.delilaheve.util.ItemUtil;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.HumanEntity; import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.PrepareAnvilEvent; import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.inventory.*;
import org.bukkit.inventory.AnvilInventory; import org.bukkit.inventory.AnvilInventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
@ -11,7 +13,10 @@ import org.bukkit.inventory.meta.Repairable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import xyz.alexcrea.cuanvil.data.AnvilClickTestData;
import xyz.alexcrea.cuanvil.data.AnvilFuseTestData;
import xyz.alexcrea.cuanvil.enchant.CAEnchantment; import xyz.alexcrea.cuanvil.enchant.CAEnchantment;
import xyz.alexcrea.cuanvil.listener.AnvilResultListener;
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener; import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener;
import xyz.alexcrea.cuanvil.mock.AnvilViewMock; import xyz.alexcrea.cuanvil.mock.AnvilViewMock;
@ -21,6 +26,9 @@ import java.util.List;
public class AnvilFuseTestUtil { public class AnvilFuseTestUtil {
private static PrepareAnvilListener PREPARE_LISTENER = new PrepareAnvilListener();
private static AnvilResultListener RESULT_LISTENER = new AnvilResultListener();
public static ItemStack prepareItem(@NotNull Material material, public static ItemStack prepareItem(@NotNull Material material,
@NotNull List<CAEnchantment> enchantments, @NotNull List<CAEnchantment> enchantments,
@NotNull List<Integer> level) { @NotNull List<Integer> level) {
@ -54,6 +62,7 @@ public class AnvilFuseTestUtil {
Integer... levels) { Integer... levels) {
return prepareItem(material, 0, enchantmentNames, levels); return prepareItem(material, 0, enchantmentNames, levels);
} }
public static ItemStack prepareItem(@NotNull Material material, public static ItemStack prepareItem(@NotNull Material material,
int repairCost, int repairCost,
@NotNull List<String> enchantmentNames, @NotNull List<String> enchantmentNames,
@ -86,14 +95,14 @@ public class AnvilFuseTestUtil {
PrepareAnvilEvent event = new PrepareAnvilEvent(view, anvil.getItem(2)); PrepareAnvilEvent event = new PrepareAnvilEvent(view, anvil.getItem(2));
// Not ideal but possible and the easiest so why not // Not ideal but possible and the easiest so why not
new PrepareAnvilListener().anvilCombineCheck(event); PREPARE_LISTENER.anvilCombineCheck(event);
anvil.setResult(event.getResult()); anvil.setResult(event.getResult());
} catch (Exception e) { } catch (Exception e) {
Assertions.fail(e); Assertions.fail(e);
} }
} }
public static void executeAnvilTest( public static void executeAnvilFuseTest(
@NotNull AnvilInventory anvil, @NotNull AnvilInventory anvil,
@NotNull HumanEntity player, @NotNull HumanEntity player,
@NotNull AnvilFuseTestData data @NotNull AnvilFuseTestData data
@ -117,9 +126,65 @@ public class AnvilFuseTestUtil {
testPlacingItem(anvil, player, testPlacingItem(anvil, player,
0, data.expectedPriceAfterBothPlaced(), 0, data.expectedPriceAfterBothPlaced(),
data.leftItem(), data.expectedResult()); data.leftItem(), data.expectedResult());
}
// Sadly, can't currently test player click public static void executeAnvilClickTest(
@NotNull AnvilInventory anvil,
@NotNull Player player,
@NotNull AnvilClickTestData data
) {
if (data.testNoLevelNoChange()) {
ItemStack left = anvil.getFirstItem();
ItemStack right = anvil.getSecondItem();
ItemStack result = anvil.getResult();
player.setLevel(0);
player.setItemOnCursor(null);
// Do a test with not enough level
simulateClick(anvil, player, data.npChangeResult());
// Nothing should have changed
assertEqual(left, anvil.getFirstItem());
assertEqual(right, anvil.getSecondItem());
assertEqual(result, anvil.getResult());
assertEqual(null, player.getItemOnCursor());
}
player.setLevel(data.levelCost());
player.setItemOnCursor(null);
simulateClick(anvil, player, data.expectedResult());
// Should have similated the click
assertEqual(data.leftItem(), anvil.getFirstItem());
assertEqual(data.rightItem(), anvil.getSecondItem());
assertEqual(data.resultSlotItem(), anvil.getResult());
assertEqual(data.expectedCursor(), data.expectedCursor());
// Test if the player has no more xp
Assertions.assertEquals(0, player.getLevel(), "Player has more level than expected");
}
private static void simulateClick(
@NotNull AnvilInventory anvil,
@NotNull Player player,
@Nullable Event.Result expectedResult
) {
AnvilViewMock view = new AnvilViewMock(player, anvil);
try {
InventoryClickEvent event = new InventoryClickEvent(view,
InventoryType.SlotType.RESULT,
PrepareAnvilListener.ANVIL_OUTPUT_SLOT,
ClickType.LEFT,
InventoryAction.PICKUP_ALL);
RESULT_LISTENER.anvilExtractionCheck(event);
if (expectedResult != null) {
Assertions.assertEquals(expectedResult, event.getResult());
}
} catch (Exception e) {
Assertions.fail(e);
}
} }
@SuppressWarnings({"removal"}) @SuppressWarnings({"removal"})
@ -140,15 +205,16 @@ public class AnvilFuseTestUtil {
assertPriceEqual(expectedPrice, anvil.getRepairCost()); assertPriceEqual(expectedPrice, anvil.getRepairCost());
} }
public static void assertEqual(@Nullable ItemStack item1, @Nullable ItemStack item2) { public static void assertEqual(@Nullable ItemStack expected, @Nullable ItemStack other) {
boolean secondIsAir = isAir(item2); boolean secondIsAir = isAir(other);
if(isAir(item1)) Assertions.assertTrue(secondIsAir,"Item "+item2+" was not AIR but was expected to be air"); if (isAir(expected))
Assertions.assertTrue(secondIsAir, "Item " + other + " was not air but was expected to be");
else { else {
Assertions.assertFalse(secondIsAir,"Item "+item2+" was expected not to be air"); Assertions.assertFalse(secondIsAir, "Item " + other + " is air but was expected to be " + expected);
item1.setDurability(item1.getDurability()); expected.setDurability(expected.getDurability());
item2.setDurability(item2.getDurability()); other.setDurability(other.getDurability());
Assertions.assertEquals(item1, item2); Assertions.assertEquals(expected, other);
} }
} }

View file

@ -43,10 +43,17 @@ permissions:
# color permissions # color permissions
ca.color.code: ca.color.code:
default: op default: op
description: Allow player to use color code if permission is required (toggleable) description: Allow player to use color code if enabled (toggleable)
ca.color.hex: ca.color.hex:
default: op default: op
description: Allow player to use hexadecimal color if permission is required (toggleable) description: Allow player to use hexadecimal color if enabled (toggleable)
# lore edit permissions
ca.lore_edit.book:
default: op
description: Allow player to edit lore via book and quil if enabled (toggleable)
ca.lore_edit.paper:
default: op
description: Allow player to edit lore via paper if enabled (toggleable)
# soft depend on old name (UnsafeEnchantsPlus), so I can disable it if it is on the same server (old name for this plugin) # soft depend on old name (UnsafeEnchantsPlus), so I can disable it if it is on the same server (old name for this plugin)