Lot of internal change and monetary cost (#116)

Internal changes this big was not intentional but had to do it for
monetary cost

excluding that: 
- add monetary cost, with dependency on rename
dialogue
- also change some a bit rename dialog
This commit is contained in:
alexcrea 2026-06-10 15:35:41 +02:00 committed by GitHub
commit 380b0de92f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 1702 additions and 717 deletions

View file

@ -40,11 +40,14 @@ repositories {
// ItemsAdder // ItemsAdder
maven(url = "https://maven.devs.beer/") maven(url = "https://maven.devs.beer/")
// for fast stats // For fast stats
maven { maven {
name = "thenextlvlReleases" name = "thenextlvlReleases"
url = uri("https://repo.thenextlvl.net/releases") url = uri("https://repo.thenextlvl.net/releases")
} }
// For vault unlocked
maven { url = uri("https://repo.codemc.io/repository/creatorfromhell/") }
} }
val reobfNMS = providers.gradleProperty("subprojects.reobfnms") val reobfNMS = providers.gradleProperty("subprojects.reobfnms")
@ -103,6 +106,9 @@ dependencies {
// ItemsAdder API // ItemsAdder API
compileOnly("dev.lone:api-itemsadder:4.0.10") compileOnly("dev.lone:api-itemsadder:4.0.10")
// Vault api
compileOnly("net.milkbowl.vault:VaultUnlockedAPI:2.16")
// Include nms // Include nms
implementation(project(":nms:nms-common")) implementation(project(":nms:nms-common"))
implementation(project(":nms:nms-paper")) implementation(project(":nms:nms-paper"))

View file

@ -430,6 +430,33 @@ lore_edit:
allow_hexadecimal_color: false allow_hexadecimal_color: false
allow_minimessage: true allow_minimessage: true
# Allow to replace the xp cost by a monetary cost
# If enabled it will not be bound to the experience level limits
#
# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher)
# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost
#
# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird"
# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect
#
# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later
monetary_cost:
enabled: false
# If using vault unlocked this allow to specify what currency should be used for anvil usage
# default being the default currency
currency: default
# multiply the anvil cost by a value to allow to have price a big bigger than like 40
multipliers:
# global multipliers. all usage type will be multiplied by this value
global: 1.0
# usage specific type. it will only apply for specific xp "reason"
enchantment: 1.0 # related to enchantments level
repair: 1.0 # for repairing via unit repair (per unit)
rename: 1.0 # for renaming the item
lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled)
work_penalty: 1.0 # for work penalty (aka use penalty)
recipe: 1.0 # for custom anvil recipe cost
# Whether to show debug logging # Whether to show debug logging
debug_log: false debug_log: false

View file

@ -450,6 +450,33 @@ lore_edit:
allow_hexadecimal_color: false allow_hexadecimal_color: false
allow_minimessage: true allow_minimessage: true
# Allow to replace the xp cost by a monetary cost
# If enabled it will not be bound to the experience level limits
#
# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher)
# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost
#
# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird"
# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect
#
# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later
monetary_cost:
enabled: false
# If using vault unlocked this allow to specify what currency should be used for anvil usage
# default being the default currency
currency: default
# multiply the anvil cost by a value to allow to have price a big bigger than like 40
multipliers:
# global multipliers. all usage type will be multiplied by this value
global: 1.0
# usage specific type. it will only apply for specific xp "reason"
enchantment: 1.0 # related to enchantments level
repair: 1.0 # for repairing via unit repair (per unit)
rename: 1.0 # for renaming the item
lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled)
work_penalty: 1.0 # for work penalty (aka use penalty)
recipe: 1.0 # for custom anvil recipe cost
# Whether to show debug logging # Whether to show debug logging
debug_log: false debug_log: false

View file

@ -442,6 +442,33 @@ lore_edit:
allow_hexadecimal_color: false allow_hexadecimal_color: false
allow_minimessage: true allow_minimessage: true
# Allow to replace the xp cost by a monetary cost
# If enabled it will not be bound to the experience level limits
#
# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher)
# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost
#
# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird"
# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect
#
# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later
monetary_cost:
enabled: false
# If using vault unlocked this allow to specify what currency should be used for anvil usage
# default being the default currency
currency: default
# multiply the anvil cost by a value to allow to have price a big bigger than like 40
multipliers:
# global multipliers. all usage type will be multiplied by this value
global: 1.0
# usage specific type. it will only apply for specific xp "reason"
enchantment: 1.0 # related to enchantments level
repair: 1.0 # for repairing via unit repair (per unit)
rename: 1.0 # for renaming the item
lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled)
work_penalty: 1.0 # for work penalty (aka use penalty)
recipe: 1.0 # for custom anvil recipe cost
# Whether to show debug logging # Whether to show debug logging
debug_log: false debug_log: false

View file

@ -430,6 +430,33 @@ lore_edit:
allow_hexadecimal_color: false allow_hexadecimal_color: false
allow_minimessage: true allow_minimessage: true
# Allow to replace the xp cost by a monetary cost
# If enabled it will not be bound to the experience level limits
#
# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher)
# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost
#
# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird"
# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect
#
# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later
monetary_cost:
enabled: false
# If using vault unlocked this allow to specify what currency should be used for anvil usage
# default being the default currency
currency: default
# multiply the anvil cost by a value to allow to have price a big bigger than like 40
multipliers:
# global multipliers. all usage type will be multiplied by this value
global: 1.0
# usage specific type. it will only apply for specific xp "reason"
enchantment: 1.0 # related to enchantments level
repair: 1.0 # for repairing via unit repair (per unit)
rename: 1.0 # for renaming the item
lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled)
work_penalty: 1.0 # for work penalty (aka use penalty)
recipe: 1.0 # for custom anvil recipe cost
# Whether to show debug logging # Whether to show debug logging
debug_log: false debug_log: false

View file

@ -18,4 +18,6 @@ interface AnvilRenameDialog {
fun currentText(player: HumanEntity): String? fun currentText(player: HumanEntity): String?
fun isOpenFor(player: HumanEntity): Boolean
} }

View file

@ -18,6 +18,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventoryView
import org.bukkit.craftbukkit.inventory.view.CraftAnvilView import org.bukkit.craftbukkit.inventory.view.CraftAnvilView
import org.bukkit.entity.HumanEntity import org.bukkit.entity.HumanEntity
import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.InventoryView
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.persistence.PersistentDataType import org.bukkit.persistence.PersistentDataType
import org.bukkit.plugin.Plugin import org.bukkit.plugin.Plugin
@ -32,7 +33,7 @@ class AnvilRenameDialogImpl(
val keepUserPreviousDialog: Supplier<Boolean>, val keepUserPreviousDialog: Supplier<Boolean>,
val maxLength: Supplier<Int>, val maxLength: Supplier<Int>,
val plugin: Plugin, val plugin: Plugin,
): AnvilRenameDialog { ) : AnvilRenameDialog {
companion object { companion object {
private const val RENAME_TEXT_KEY = "rename" private const val RENAME_TEXT_KEY = "rename"
@ -43,16 +44,28 @@ class AnvilRenameDialogImpl(
// Need to be able to translate it later ! // Need to be able to translate it later !
private val USER_FACING_RENAME_TITLE = Component.text("Rename Your Item") private val USER_FACING_RENAME_TITLE = Component.text("Rename Your Item")
private val USER_FACING_WARNING = Component.text("Note that the repair text will appear blank after Confirm\n" + private val USER_FACING_WARNING = Component.text(
"But the name will be correctly applied") "Note that the repair text will appear blank after Confirm\n" +
"But the name will be correctly applied"
)
private val USER_FACING_CONFIRM = Component.text("Confirm").color(TextColor.fromHexString("#40FF40")) private val USER_FACING_CONFIRM = Component.text("Confirm").color(TextColor.fromHexString("#40FF40"))
private val USER_FACING_CANCEL = Component.text("Cancel").color(TextColor.fromHexString("#FF4040")) private val USER_FACING_CANCEL = Component.text("Cancel").color(TextColor.fromHexString("#FF4040"))
fun itemDefaultName(item: ItemStack?): String? {
return PLAIN_TEXT_SERIALIZER.serializeOrNull(item?.effectiveName())
}
} }
private val lastNames = HashMap<UUID, String>() private val lastNames = HashMap<UUID, String>()
private val lastRenames = HashMap<UUID, String>()
private val lastLeftItem = HashMap<UUID, String>() private val lastLeftItem = HashMap<UUID, String>()
private val runTaskMap = HashMap<UUID, ScheduledTask>() private val runTaskMap = HashMap<UUID, ScheduledTask>()
// For monetary cost
val hasUiOpen = HashSet<UUID>()
private val containerField = CraftInventoryView::class.java.getDeclaredField("container") private val containerField = CraftInventoryView::class.java.getDeclaredField("container")
init { init {
@ -63,72 +76,90 @@ class AnvilRenameDialogImpl(
return true return true
} }
fun makeDialog(initial: String?, callback: Consumer<String?>): Dialog { fun makeDialog(playerID: UUID, initial: String?, callback: Consumer<String?>): Dialog {
val maxLength = this.maxLength.get() val maxLength = this.maxLength.get()
val initialFinal = initial?.take(maxLength) val initialFinal = initial?.take(maxLength)
val baseBuilder = DialogBase.builder(USER_FACING_RENAME_TITLE) val baseBuilder = DialogBase.builder(USER_FACING_RENAME_TITLE)
.canCloseWithEscape(true) .canCloseWithEscape(true)
.afterAction(DialogBase.DialogAfterAction.CLOSE) .afterAction(DialogBase.DialogAfterAction.CLOSE)
.inputs(listOf( .inputs(
DialogInput.text(RENAME_TEXT_KEY, Component.text("Rename text")) listOf(
.maxLength(maxLength) DialogInput.text(RENAME_TEXT_KEY, Component.text("Rename text"))
.initial(initialFinal ?: "") .maxLength(maxLength)
.labelVisible(false) .initial(initialFinal ?: "")
.width(MAX_WIDTH) .labelVisible(false)
.build(), .width(MAX_WIDTH)
.build(),
), ),
) )
baseBuilder.body(listOf( baseBuilder.body(
listOf(
DialogBody.plainMessage(USER_FACING_WARNING, MAX_WIDTH) DialogBody.plainMessage(USER_FACING_WARNING, MAX_WIDTH)
)) )
)
return Dialog.create { builder -> builder.empty() return Dialog.create { builder ->
.base(baseBuilder.build()) builder.empty()
.type(DialogType.confirmation( .base(baseBuilder.build())
ActionButton.builder(USER_FACING_CONFIRM) .type(
.action(DialogAction.customClick({ response, _ -> DialogType.confirmation(
val text = response.getText(RENAME_TEXT_KEY)!! ActionButton.builder(USER_FACING_CONFIRM)
callback.accept(text) .action(DialogAction.customClick({ response, _ ->
}, ClickCallback.Options.builder().build())) hasUiOpen.remove(playerID)
.build(), val text = response.getText(RENAME_TEXT_KEY)!!
ActionButton.builder(USER_FACING_CANCEL) callback.accept(text)
.build(), }, ClickCallback.Options.builder().build()))
)) .build(),
ActionButton.builder(USER_FACING_CANCEL)
.action(DialogAction.customClick({ response, _ ->
hasUiOpen.remove(playerID)
}, ClickCallback.Options.builder().build()))
.build(),
)
)
} }
} }
private fun setResult(player: HumanEntity, view: CraftAnvilView, result: String?) { private fun setResult(player: HumanEntity, view: InventoryView, result: String?) {
val defaultName = PLAIN_TEXT_SERIALIZER.serializeOrNull(view.getItem(0)?.effectiveName()) val defaultName = itemDefaultName(view.getItem(0))
if(defaultName == result) { if (defaultName == result) {
setName(player, view, "") setName(player, view, "", null)
if(defaultName != null) lastNames[player.uniqueId] = defaultName if (defaultName != null) lastNames[player.uniqueId] = defaultName
} } else setName(player, view, result, result)
else setName(player, view, result)
} }
private fun setName(player: HumanEntity, view: CraftAnvilView, name: String?) { private fun setName(player: HumanEntity, view: InventoryView, name: String?, rename: String?) {
val menu = (containerField.get(view) as AnvilMenu) val menu = (containerField.get(view) as AnvilMenu)
menu.itemName = name val isSameName = menu.itemName == name
menu.itemName = rename
if(name == null) if (name == null)
lastNames.remove(player.uniqueId) lastNames.remove(player.uniqueId)
else else
lastNames[player.uniqueId] = name lastNames[player.uniqueId] = name
CraftEventFactory.callPrepareResultEvent(menu, 2);
if (rename == null)
lastRenames.remove(player.uniqueId)
else
lastRenames[player.uniqueId] = rename
if (!isSameName)
CraftEventFactory.callPrepareResultEvent(menu, 2);
} }
private fun nameFromItem(player: HumanEntity, item: ItemStack?): String? { private fun nameFromItem(player: HumanEntity, item: ItemStack?): String? {
// Already has text // Already has text
if(item?.hasItemMeta() != true || !item.itemMeta.hasCustomName()) if (item?.hasItemMeta() != true || !item.itemMeta.hasCustomName())
return PLAIN_TEXT_SERIALIZER.serializeOrNull(item?.effectiveName()) return PLAIN_TEXT_SERIALIZER.serializeOrNull(item?.effectiveName())
if(keepUserPreviousDialog.get() && item.hasItemMeta()) { if (keepUserPreviousDialog.get() && item.hasItemMeta()) {
val lastName = item.itemMeta.persistentDataContainer.get( val lastName = item.itemMeta.persistentDataContainer.get(
AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY, AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY,
PersistentDataType.STRING) PersistentDataType.STRING
)
if(lastName != null) return lastName if (lastName != null) return lastName
} }
return fromFormated.apply(player, item.effectiveName()) return fromFormated.apply(player, item.effectiveName())
@ -136,33 +167,37 @@ class AnvilRenameDialogImpl(
private fun tryShowDialogScheduled(player: HumanEntity, event: PrepareAnvilEvent) { private fun tryShowDialogScheduled(player: HumanEntity, event: PrepareAnvilEvent) {
val view = event.view val view = event.view
if(view !is CraftAnvilView) return if (view !is CraftAnvilView) return
val renameText = view.renameText val renameText = view.renameText
val leftItem = view.getItem(0) val leftItem = view.getItem(0)
val leftItemStr = leftItem?.toString() val leftItemStr = leftItem?.toString()
val lastName = lastNames.getOrDefault(player.uniqueId, null)
if(lastLeftItem.getOrDefault(player.uniqueId, null) != leftItemStr) { val lastName = lastNames.getOrDefault(player.uniqueId, null)
if(leftItemStr == null) val lastRename = lastRenames.getOrDefault(player.uniqueId, null)
if (lastLeftItem.getOrDefault(player.uniqueId, null) != leftItemStr) {
if (leftItemStr == null)
lastLeftItem.remove(player.uniqueId) lastLeftItem.remove(player.uniqueId)
else lastLeftItem[player.uniqueId] = leftItemStr else lastLeftItem[player.uniqueId] = leftItemStr
setName(player, view, nameFromItem(player, leftItem)) setName(player, view, renameText, nameFromItem(player, leftItem))
return return
} }
if(lastName == renameText) if (lastName == renameText || lastRename == renameText)
return return
if(renameText?.isBlank() == true) { if (renameText?.isBlank() == true || renameText == itemDefaultName(leftItem)) {
setName(player, view, lastNames[player.uniqueId]) setName(player, view, lastName, lastRename)
return return
} }
val dialog = makeDialog(lastName) val dialog = makeDialog(player.uniqueId, lastRename)
{ result -> setResult(player, view, result) } { result -> setResult(player, view, result) }
player.showDialog(dialog) player.showDialog(dialog)
hasUiOpen.add(player.uniqueId)
} }
// We need to wait for a short time as changing item will change the name BEFORE the item change // We need to wait for a short time as changing item will change the name BEFORE the item change
@ -178,13 +213,14 @@ class AnvilRenameDialogImpl(
{}, {},
2 2
) )
if(task == null) return if (task == null) return
runTaskMap[player.uniqueId] = task runTaskMap[player.uniqueId] = task
} }
override fun closeInventory(player: HumanEntity) { override fun closeInventory(player: HumanEntity) {
lastNames.remove(player.uniqueId) lastNames.remove(player.uniqueId)
lastRenames.remove(player.uniqueId)
lastLeftItem.remove(player.uniqueId) lastLeftItem.remove(player.uniqueId)
runTaskMap.remove(player.uniqueId)?.cancel() runTaskMap.remove(player.uniqueId)?.cancel()
} }
@ -193,4 +229,8 @@ class AnvilRenameDialogImpl(
return lastNames[player.uniqueId] return lastNames[player.uniqueId]
} }
override fun isOpenFor(player: HumanEntity): Boolean {
return hasUiOpen.contains(player.uniqueId)
}
} }

View file

@ -0,0 +1,45 @@
package xyz.alexcrea.cuanvil.util
import io.papermc.paper.threadedregions.scheduler.ScheduledTask
import org.bukkit.entity.HumanEntity
import org.bukkit.inventory.InventoryView
import org.bukkit.plugin.Plugin
import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog
import java.util.HashMap
import java.util.UUID
object AnvilTitleUtil {
private val runTaskMap = HashMap<UUID, ScheduledTask>()
private fun actualRename(view: InventoryView, name: String, player: HumanEntity, anvilDialog: AnvilRenameDialog) {
runTaskMap.remove(player.uniqueId)
if (view.title == name) return
// We assume rename impl is used
if (anvilDialog.isOpenFor(player)) return
view.title = name
}
// We don't want to rename instantly it is causing issue with rename text
// especially as it can "override" current ui when it is rename ui time but rename ui also need some delay
fun rename(view: InventoryView, name: String, player: HumanEntity, anvilDialog: AnvilRenameDialog, plugin: Plugin) {
runTaskMap.remove(player.uniqueId)?.cancel()
val task = player.scheduler.runDelayed(
plugin,
{ _ ->
run { actualRename(view, name, player, anvilDialog) }
},
{
runTaskMap.remove(player.uniqueId)
},
2
)
if (task == null) return
runTaskMap[player.uniqueId] = task
}
}

View file

@ -0,0 +1,12 @@
package xyz.alexcrea.cuanvil.util
import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.InventoryView
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.view.AnvilView
object ModernPrepareAnvilCreator {
fun createPrepareAnvil(view: InventoryView, item: ItemStack?): PrepareAnvilEvent {
return PrepareAnvilEvent(view as AnvilView, item)
}
}

View file

@ -17,7 +17,7 @@ import org.jetbrains.annotations.NotNull;
* Most of the time you would likely need {@link CAPreAnvilBypassEvent} or {@link CAEarlyPreAnvilBypassEvent} * Most of the time you would likely need {@link CAPreAnvilBypassEvent} or {@link CAEarlyPreAnvilBypassEvent}
* for this event to be useful. * for this event to be useful.
* <p> * <p>
* There is also {@link CATreatAnvilResultEvent} that may be better for some use case. * There is also {@link CATreatAnvilResult2Event} that may be better for some use case.
*/ */
public class CAClickResultBypassEvent extends Event implements Cancellable { public class CAClickResultBypassEvent extends Event implements Cancellable {

View file

@ -15,7 +15,7 @@ import org.jetbrains.annotations.NotNull;
* <p> * <p>
* You should also use {@link CAClickResultBypassEvent} if you want to use this event for something useful. * You should also use {@link CAClickResultBypassEvent} if you want to use this event for something useful.
* <p> * <p>
* It is also recommended that you read about {@link CAPreAnvilBypassEvent} and {@link CATreatAnvilResultEvent} * It is also recommended that you read about {@link CAPreAnvilBypassEvent} and {@link CATreatAnvilResult2Event}
* as your use case may be more prone to use theses. * as your use case may be more prone to use theses.
*/ */
public class CAEarlyPreAnvilBypassEvent extends Event implements Cancellable { public class CAEarlyPreAnvilBypassEvent extends Event implements Cancellable {

View file

@ -18,7 +18,7 @@ import org.jetbrains.annotations.NotNull;
* <p> * <p>
* You should also use {@link CAClickResultBypassEvent} if you want to use this event for something useful. * You should also use {@link CAClickResultBypassEvent} if you want to use this event for something useful.
* <p> * <p>
* It is also recommended that you read about {@link CAEarlyPreAnvilBypassEvent} and {@link CATreatAnvilResultEvent} * It is also recommended that you read about {@link CAEarlyPreAnvilBypassEvent} and {@link CATreatAnvilResult2Event}
* as your use case may be more prone to use theses. * as your use case may be more prone to use theses.
*/ */
public class CAPreAnvilBypassEvent extends Event implements Cancellable { public class CAPreAnvilBypassEvent extends Event implements Cancellable {

View file

@ -0,0 +1,196 @@
package xyz.alexcrea.cuanvil.api.event.listener;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.alexcrea.cuanvil.anvil.AnvilCost;
import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
/**
* Called after custom anvil processed the click on the result on the anvil inventory.
* This event should be used to modify the result of an anvil use.
* <p>
* You may also want to check {@link CAClickResultBypassEvent},
* {@link CAPreAnvilBypassEvent}
* and {@link CAEarlyPreAnvilBypassEvent} for your use case
* <p>
* A null result will cancel this event
*/
@SuppressWarnings("unused")
public class CATreatAnvilResult2Event extends Event {
private static final HandlerList HANDLERS = new HandlerList();
public static HandlerList getHandlerList() {
return HANDLERS;
}
@Override
public @NotNull HandlerList getHandlers() {
return HANDLERS;
}
@NotNull
private final InventoryView view;
private final AnvilUseType useType;
@Nullable
private final ItemStack left;
@Nullable
private final ItemStack right;
@Nullable
private ItemStack result;
private final AnvilCost cost;
@ApiStatus.Internal
public CATreatAnvilResult2Event(
@NotNull InventoryView view,
Inventory inv,
AnvilUseType useType,
@Nullable ItemStack result,
AnvilCost cost) {
this.view = view;
this.useType = useType;
this.left = inv.getItem(0); // TODO use view here
this.right = inv.getItem(1);
this.result = result;
this.cost = cost;
}
/**
* Get the bukkit inventory view.
* <p>
* Temporarily marked as internal as it will get changed to anvil view on legacy removal
* so signature will change
*
* @return The inventory view of this event.
*/
@ApiStatus.Internal
public @NotNull InventoryView getView() {
return view;
}
/**
* Get the type of use source of the result.
*
* @return The craft use type.
*/
public AnvilUseType getUseType() {
return useType;
}
/**
* Get the left item of the anvil use
*
* @return the left item
*/
public @Nullable ItemStack getLeftItem() {
return left;
}
/**
* Get the right item of the anvil use
*
* @return the right item
*/
public @Nullable ItemStack getRightItem() {
return right;
}
/**
* Get the current result
* <p>
* note that it will not be null unless another listener previously set it to null.
*
* @return The current result.
*/
public @Nullable ItemStack getResult() {
return result;
}
/**
* Set the current result
* <p>
* note that a null result will cancel this anvil use.
*
* @param result The new result
*/
public void setResult(@Nullable ItemStack result) {
this.result = result;
}
/**
* Get the level cost displayed on the anvil.
* <h3>Important note:</h3>
* the final price are re calculated on click for the following use case:
* <ul>
* <li>Custom craft</li>
* <li>Unit repair</li>
* <li>Lore edit</li>
* </ul>
* This value will be used as final price for:
* <li>Item merge</li>
* <li>Item rename</li>
* </ul>
*
* @return The current cost.
* @deprecated use #{@link #getCost()} instead
*/
@Deprecated(forRemoval = true, since = "1.17.0")
public int getLevelCost() {
return cost.asXpCost();
}
/**
* Set the level cost displayed on the anvil.
* <h3>Important note:</h3>
* the final price are re calculated on click for the following use case:
* <ul>
* <li>Custom craft</li>
* <li>Unit repair</li>
* <li>Lore edit</li>
* </ul>
* This value will be used as final price for:
* <li>Item merge</li>
* <li>Item rename</li>
* </ul>
*
* @param levelCost The new cost.
* @deprecated use #{@link #getCost()} and set value on this instead
*/
@Deprecated(forRemoval = true, since = "1.17.0")
public void setLevelCost(int levelCost) {
cost.setGeneric(levelCost - cost.getGeneric() - cost.asXpCost());
}
/**
* Allow access to the current cost of the event
* Note that modifying this object will change the event resulting cost
*
* <h3>Important note:</h3>
* the final price are re calculated on click for the following use case:
* <ul>
* <li>Custom craft</li>
* <li>Unit repair</li>
* <li>Lore edit</li>
* </ul>
* This value will be used as final price for:
* <li>Item merge</li>
* <li>Item rename</li>
*
* @return the current anvil cost
*/
public AnvilCost getCost() {
return cost;
}
}

View file

@ -6,7 +6,8 @@ import org.bukkit.event.inventory.PrepareAnvilEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import xyz.alexcrea.cuanvil.util.AnvilUseType; import xyz.alexcrea.cuanvil.anvil.AnvilCost;
import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
/** /**
* Called after custom anvil processed the click on the result on the anvil inventory. * Called after custom anvil processed the click on the result on the anvil inventory.
@ -17,8 +18,12 @@ import xyz.alexcrea.cuanvil.util.AnvilUseType;
* and {@link CAEarlyPreAnvilBypassEvent} for your use case * and {@link CAEarlyPreAnvilBypassEvent} for your use case
* <p> * <p>
* A null result will cancel this pre anvil event * A null result will cancel this pre anvil event
*
* @deprecated Prepare anvil Event cannot be provided as it can be called on result and therefore not have prepared anvil event
* use {@link CATreatAnvilResult2Event} instead
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Deprecated(forRemoval = true, since = "1.17.0")
public class CATreatAnvilResultEvent extends Event { public class CATreatAnvilResultEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList(); private static final HandlerList HANDLERS = new HandlerList();
@ -40,13 +45,13 @@ public class CATreatAnvilResultEvent extends Event {
@Nullable @Nullable
private ItemStack result; private ItemStack result;
private int levelCost; private final AnvilCost cost;
public CATreatAnvilResultEvent(@NotNull PrepareAnvilEvent event, AnvilUseType useType, @Nullable ItemStack result, int levelCost) { public CATreatAnvilResultEvent(@NotNull PrepareAnvilEvent event, AnvilUseType useType, @Nullable ItemStack result, AnvilCost cost) {
this.event = event; this.event = event;
this.useType = useType; this.useType = useType;
this.result = result; this.result = result;
this.levelCost = levelCost; this.cost = cost;
} }
/** /**
@ -104,9 +109,11 @@ public class CATreatAnvilResultEvent extends Event {
* </ul> * </ul>
* *
* @return The current cost. * @return The current cost.
* @deprecated use #{@link #getCost()} instead
*/ */
@Deprecated(forRemoval = true, since = "1.17.0")
public int getLevelCost() { public int getLevelCost() {
return levelCost; return cost.asXpCost();
} }
/** /**
@ -124,8 +131,32 @@ public class CATreatAnvilResultEvent extends Event {
* </ul> * </ul>
* *
* @param levelCost The new cost. * @param levelCost The new cost.
* @deprecated use #{@link #getCost()} and set value on this instead
*/ */
@Deprecated(forRemoval = true, since = "1.17.0")
public void setLevelCost(int levelCost) { public void setLevelCost(int levelCost) {
this.levelCost = levelCost; cost.setGeneric(levelCost - cost.getGeneric() - cost.asXpCost());
} }
/**
* Allow access to the current cost of the event
* Note that modifying this object will change the event resulting cost
*
* <h3>Important note:</h3>
* the final price are re calculated on click for the following use case:
* <ul>
* <li>Custom craft</li>
* <li>Unit repair</li>
* <li>Lore edit</li>
* </ul>
* This value will be used as final price for:
* <li>Item merge</li>
* <li>Item rename</li>
*
* @return the current anvil cost
*/
public AnvilCost getCost() {
return cost;
}
} }

View file

@ -2,7 +2,7 @@ package xyz.alexcrea.cuanvil.config;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import xyz.alexcrea.cuanvil.util.AnvilUseType; import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
import java.util.EnumMap; import java.util.EnumMap;

View file

@ -11,11 +11,11 @@ import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.config.WorkPenaltyType; import xyz.alexcrea.cuanvil.config.WorkPenaltyType;
import xyz.alexcrea.cuanvil.gui.config.global.BasicConfigGui; import xyz.alexcrea.cuanvil.gui.config.global.BasicConfigGui;
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions; import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions;
import xyz.alexcrea.cuanvil.util.AnvilUseType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumMap; import java.util.EnumMap;

View file

@ -2,10 +2,10 @@ package xyz.alexcrea.cuanvil.update.plugin;
import io.delilaheve.util.ConfigOptions; import io.delilaheve.util.ConfigOptions;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
import xyz.alexcrea.cuanvil.config.ConfigHolder; import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.config.WorkPenaltyType; import xyz.alexcrea.cuanvil.config.WorkPenaltyType;
import xyz.alexcrea.cuanvil.gui.config.settings.WorkPenaltyTypeSettingGui; import xyz.alexcrea.cuanvil.gui.config.settings.WorkPenaltyTypeSettingGui;
import xyz.alexcrea.cuanvil.util.AnvilUseType;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.EnumMap; import java.util.EnumMap;

View file

@ -12,6 +12,7 @@ import xyz.alexcrea.cuanvil.command.ReloadExecutor
import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil
import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager
import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil
import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry
import xyz.alexcrea.cuanvil.gui.config.MainConfigGui import xyz.alexcrea.cuanvil.gui.config.MainConfigGui
@ -259,9 +260,11 @@ open class CustomAnvil : JavaPlugin() {
MainConfigGui.getInstance().init(DependencyManager.packetManager) MainConfigGui.getInstance().init(DependencyManager.packetManager)
GuiSharedConstant.loadConstants() GuiSharedConstant.loadConstants()
// Prepare economy if possible
EconomyManager.setupEconomy(this)
// Finally, re add default we may be missing // Finally, re add default we may be missing
PluginSetDefault.reAddMissingDefault() PluginSetDefault.reAddMissingDefault()
} }
fun reloadResource( fun reloadResource(

View file

@ -3,12 +3,16 @@ package io.delilaheve.util
import io.delilaheve.CustomAnvil import io.delilaheve.CustomAnvil
import io.delilaheve.util.EnchantmentUtil.enchantmentName import io.delilaheve.util.EnchantmentUtil.enchantmentName
import org.bukkit.NamespacedKey import org.bukkit.NamespacedKey
import org.bukkit.entity.HumanEntity
import xyz.alexcrea.cuanvil.anvil.AnvilUseType
import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.config.WorkPenaltyType import xyz.alexcrea.cuanvil.config.WorkPenaltyType
import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart
import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager
import xyz.alexcrea.cuanvil.enchant.CAEnchantment import xyz.alexcrea.cuanvil.enchant.CAEnchantment
import xyz.alexcrea.cuanvil.util.AnvilUseType import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil
import java.math.BigDecimal
import java.util.* import java.util.*
/** /**
@ -72,6 +76,11 @@ object ConfigOptions {
const val IMMUTABLE_ENCHANTMENT_LIST = "immutable_enchantments" const val IMMUTABLE_ENCHANTMENT_LIST = "immutable_enchantments"
// Monetary configs
const val MONETARY_USAGE_ROOT = "monetary_cost"
const val SHOULD_USE_MONEY = "$MONETARY_USAGE_ROOT.enabled"
const val MONEY_CURRENCY = "$MONETARY_USAGE_ROOT.currency"
const val MONETARY_MULTIPLIER_ROOT = "$MONETARY_USAGE_ROOT.multipliers"
// Keys for specific enchantment values // Keys for specific enchantment values
private const val KEY_BOOK = "book" private const val KEY_BOOK = "book"
@ -110,6 +119,11 @@ object ConfigOptions {
const val DEFAULT_PER_COLOR_CODE_PERMISSION = false const val DEFAULT_PER_COLOR_CODE_PERMISSION = false
// Monetary configs
const val DEFAULT_SHOULD_USE_MONEY = false
const val DEFAULT_MONEY_CURRENCY = "default"
const val DEFAULT_MONEY_MULTIPLIER = 1.0
// Debug flag // Debug flag
private const val DEFAULT_DEBUG_LOG = false private const val DEFAULT_DEBUG_LOG = false
private const val DEFAULT_VERBOSE_DEBUG_LOG = false private const val DEFAULT_VERBOSE_DEBUG_LOG = false
@ -164,6 +178,11 @@ object ConfigOptions {
// Default max before merge disabled (negative mean enabled) // Default max before merge disabled (negative mean enabled)
const val DEFAULT_MAX_BEFORE_MERGE_DISABLED = -1 const val DEFAULT_MAX_BEFORE_MERGE_DISABLED = -1
// -----------
// Permissions
// -----------
private const val RENAME_DIALOG_PERMISSION = "ca.rename.dialog"
// ------------- // -------------
// Get methods // Get methods
// ------------- // -------------
@ -452,6 +471,13 @@ object ConfigOptions {
.getBoolean(DIALOG_RENAME_USE_PERMISSION, DEFAULT_DIALOG_RENAME_USE_PERMISSION) .getBoolean(DIALOG_RENAME_USE_PERMISSION, DEFAULT_DIALOG_RENAME_USE_PERMISSION)
} }
fun canUseDialogRename(player: HumanEntity): Boolean {
if(!doRenameDialog || !AnvilRenameDialogUtil.anvilRenameDialog.canSendDialog()) return false
if(doRenameDialogUsePermission && !player.hasPermission(RENAME_DIALOG_PERMISSION)) return false
return true
}
/** /**
* Do the dialog menu require permission * Do the dialog menu require permission
*/ */
@ -625,4 +651,29 @@ object ConfigOptions {
return false return false
} }
/*
* Monetary configs (only for 1.21.6+)
* Also require dialog rename
*/
fun shouldUseMoney(player: HumanEntity): Boolean {
return EconomyManager.economy?.initialized() == true &&
canUseDialogRename(player) &&
ConfigHolder.DEFAULT_CONFIG
.config
.getBoolean(SHOULD_USE_MONEY, DEFAULT_SHOULD_USE_MONEY)
}
val usedCurrency: String
get() {
return ConfigHolder.DEFAULT_CONFIG
.config
.getString(MONEY_CURRENCY, DEFAULT_MONEY_CURRENCY)!!
}
fun getMonetaryMultiplier(type: String): BigDecimal {
return BigDecimal(ConfigHolder.DEFAULT_CONFIG
.config
.getDouble("$MONETARY_MULTIPLIER_ROOT.$type", DEFAULT_MONEY_MULTIPLIER))
}
} }

View file

@ -0,0 +1,55 @@
package xyz.alexcrea.cuanvil.anvil
import java.math.BigDecimal
import io.delilaheve.util.ConfigOptions.getMonetaryMultiplier as moneyMultiplier
open class AnvilCost {
private val isAlone: Boolean
var valid = true // Get set as invalid if cost can be satisfied
var isMonetary = false
var generic = 0
var enchantment = 0
var repair = 0
var rename = 0
var lore = 0
var illegalPenalty = 0
var workPenalty = 0
var recipe = 0
constructor(generic: Int) {
this.generic = generic
isAlone = true
}
constructor() {
isAlone = false
}
fun asXpCost(): Int {
return generic + enchantment + repair + rename + lore + illegalPenalty + workPenalty + recipe
}
open fun asMonetaryCost(): BigDecimal {
// multiply by per use type multipliers
return BigDecimal(generic)
.add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment")))
.add(BigDecimal(repair).multiply(moneyMultiplier("repair")))
.add(BigDecimal(rename).multiply(moneyMultiplier("rename")))
.add(BigDecimal(lore).multiply(moneyMultiplier("lore_edit")))
.add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment")))
.add(BigDecimal(illegalPenalty).multiply(moneyMultiplier("work_penalty")))
.add(BigDecimal(workPenalty).multiply(moneyMultiplier("work_penalty")))
.add(BigDecimal(recipe).multiply(moneyMultiplier("recipe")))
.multiply(moneyMultiplier("global"))
}
}
class CustomCraftCost(val rawCost: Int): AnvilCost() {
override fun asMonetaryCost(): BigDecimal {
return BigDecimal(rawCost)
.multiply(moneyMultiplier("global"))
}
}

View file

@ -0,0 +1,331 @@
package xyz.alexcrea.cuanvil.anvil
import io.delilaheve.CustomAnvil
import io.delilaheve.util.ConfigOptions
import io.delilaheve.util.EnchantmentUtil.combineWith
import io.delilaheve.util.ItemUtil.findEnchantments
import io.delilaheve.util.ItemUtil.isEnchantedBook
import io.delilaheve.util.ItemUtil.repairFrom
import io.delilaheve.util.ItemUtil.setEnchantmentsUnsafe
import io.delilaheve.util.ItemUtil.unitRepair
import org.bukkit.ChatColor
import org.bukkit.Material
import org.bukkit.entity.HumanEntity
import org.bukkit.entity.Player
import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.InventoryView
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.ItemMeta
import org.bukkit.persistence.PersistentDataType
import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe
import xyz.alexcrea.cuanvil.util.CasedStringUtil
import xyz.alexcrea.cuanvil.util.CustomRecipeUtil
import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir
import xyz.alexcrea.cuanvil.util.MiniMessageUtil
import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair
import xyz.alexcrea.cuanvil.util.anvil.AnvilColorUtil
import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil
import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.config.LoreEditType
import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil
object AnvilMergeLogic {
open class AnvilResult {
companion object {
val EMPTY = AnvilResult(null, AnvilCost())
}
val item: ItemStack?
val cost: AnvilCost
val ignoreXpRules: Boolean
constructor(item: ItemStack?, cost: AnvilCost, ignoreXpRules: Boolean = false) {
this.item = item
this.cost = cost
this.ignoreXpRules = ignoreXpRules
}
fun isEmpty(): Boolean {
return item == null
}
}
class UnitRepairResult : AnvilResult {
companion object {
val EMPTY = UnitRepairResult(null, AnvilCost(), 0)
}
val repairAmount: Int
constructor(item: ItemStack?, cost: AnvilCost, repairAmount: Int) : super(item, cost) {
this.repairAmount = repairAmount
}
}
class CustomCraftResult : AnvilResult {
companion object {
val EMPTY = CustomCraftResult(null, CustomCraftCost(0), 0, null)
}
val customCraftCost: CustomCraftCost
val amount: Int
val recipe: AnvilCustomRecipe?
constructor(
item: ItemStack?, cost: CustomCraftCost,
amount: Int, recipe: AnvilCustomRecipe?
) : super(item, cost, true) {
this.customCraftCost = cost
this.amount = amount
this.recipe = recipe
}
}
class LoreEditResult : AnvilResult {
companion object {
val EMPTY = LoreEditResult(null, AnvilCost(), LoreEditType.APPEND_PAPER)
}
val type: LoreEditType
constructor(item: ItemStack?, cost: AnvilCost, type: LoreEditType) : super(item, cost) {
this.type = type
}
}
fun doRenaming(
view: InventoryView, //TODO use anvil view
inventory: AnvilInventory,
player: Player, first: ItemStack
): AnvilResult {
val resultItem = DependencyManager.cloneItem(player, first)
val cost = AnvilCost()
cost.rename = handleRename(resultItem, inventory, player)
// Test/stop if nothing changed.
if (first == resultItem) {
CustomAnvil.log("no right item, But input is same as output")
return AnvilResult.EMPTY
}
cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY)
val result =
DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.RENAME_ONLY, cost)
return AnvilResult(result, cost)
}
private fun processDialogPCD(meta: ItemMeta, player: HumanEntity) {
val text = AnvilRenameDialogUtil.anvilRenameDialog.currentText(player)
return processPCD(meta, player, text)
}
fun processPCD(meta: ItemMeta, player: HumanEntity, text: String?) {
val keepDialog = ConfigOptions.canUseDialogRename(player) && ConfigOptions.shouldKeepRenameText
val pdc = meta.persistentDataContainer
if (!keepDialog)
pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY)
else {
if (text == null || text.isBlank())
pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY)
else pdc.set(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY, PersistentDataType.STRING, text)
}
}
private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int {
// Can be null
var renameText = ChatColor.stripColor(inventory.renameText)
var sumCost = 0
var useColor = false
if (ConfigOptions.renameColorPossible && renameText != null) {
val component = AnvilColorUtil.handleColor(
renameText,
AnvilColorUtil.renamePermission(player)
)
if (component != null) {
renameText = MiniMessageUtil.legacy_mm.serialize(component)
sumCost += ConfigOptions.useOfColorCost
useColor = true
}
}
// Rename item and add renaming cost
resultItem.itemMeta?.let {
val hasDisplayName = it.hasDisplayName()
val displayName = if (!hasDisplayName) null
else if (useColor) it.displayName
else ChatColor.stripColor(it.displayName)
if (!displayName.contentEquals(renameText) && !(displayName == null &&
renameText == "" ||
//TODO on recent paper check effective name instead
renameText == CasedStringUtil.snakeToUpperSpacedCase(resultItem.type.name.lowercase())
)
) {
it.setDisplayName(renameText)
processDialogPCD(it, player)
resultItem.itemMeta = it
sumCost += ConfigOptions.itemRenameCost
}
return sumCost
}
return 0
}
fun doMerge(
view: InventoryView, //TODO use anvil view instead
inventory: AnvilInventory,
player: Player,
first: ItemStack, second: ItemStack
): AnvilResult {
val newEnchants = first.findEnchantments()
.combineWith(second.findEnchantments(), first, player)
var hasChanged = !isIdentical(first.findEnchantments(), newEnchants)
val resultItem = DependencyManager.cloneItem(player, first)
val cost = AnvilCost()
if (hasChanged) {
resultItem.setEnchantmentsUnsafe(newEnchants)
// Calculate enchantment cost
AnvilXpUtil.getRightValues(second, resultItem, cost)
}
// Calculate repair cost
if (!first.isEnchantedBook() && !second.isEnchantedBook()) {
// we only need to be concerned with repair when neither item is a book
val repaired = resultItem.repairFrom(first, second)
cost.repair = if (repaired) ConfigOptions.itemRepairCost else 0
hasChanged = hasChanged || repaired
}
// Test/stop if nothing changed.
if (!hasChanged) {
CustomAnvil.log("Mergeable with second, But input is same as output")
return AnvilResult.EMPTY
}
// As calculatePenalty edit result, we need to calculate penalty after checking equality
cost.workPenalty = AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE)
// Calculate rename cost
cost.rename = handleRename(resultItem, inventory, player)
val result =
DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.MERGE, cost)
return AnvilResult(result, cost)
}
private fun isIdentical(
firstEnchants: MutableMap<CAEnchantment, Int>,
resultEnchants: MutableMap<CAEnchantment, Int>
): Boolean {
if (firstEnchants.size != resultEnchants.size) return false
for (entry in resultEnchants) {
if (firstEnchants.getOrDefault(entry.key, entry.value - 1) != entry.value) return false
}
return true
}
// return true if a custom recipe exist with these ingredients
fun testCustomRecipe(
view: InventoryView, //TODO use anvil view instead
inventory: AnvilInventory,
player: Player,
first: ItemStack, second: ItemStack?
): CustomCraftResult {
val recipe = CustomRecipeUtil.getCustomRecipe(first, second)
CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}")
if (recipe == null) return CustomCraftResult.EMPTY
val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, first, second)
val resultItem: ItemStack = DependencyManager.cloneItem(player, recipe.resultItem!!)
resultItem.amount *= amount
// Maybe add an option on custom craft to ignore/not ignore penalty ??
val xpCost = recipe.determineCost(amount, first, resultItem)
val cost = CustomCraftCost(xpCost)
// This is for displayed cost
cost.recipe = if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost)
else AnvilXpUtil.calculateLevelForXp(xpCost)
val result =
DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.CUSTOM_CRAFT, cost)
return CustomCraftResult(result, cost, amount, recipe)
}
fun testUnitRepair(
view: InventoryView, //TODO use anvil view
inventory: AnvilInventory,
player: Player,
first: ItemStack, second: ItemStack
): UnitRepairResult {
val unitRepairAmount = first.getRepair(second) ?: return UnitRepairResult.EMPTY
return testUnitRepair(view, inventory, player, first, second, unitRepairAmount)
}
fun testUnitRepair(
view: InventoryView, //TODO use anvil view instead
inventory: AnvilInventory,
player: Player,
first: ItemStack, second: ItemStack,
unitRepairAmount: Double
): UnitRepairResult {
val resultItem = DependencyManager.cloneItem(player, first)
val cost = AnvilCost()
cost.rename = handleRename(resultItem, inventory, player)
val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount)
if (repairAmount > 0)
cost.repair = repairAmount * ConfigOptions.unitRepairCost
// We do not care about right item penalty for unit repair
cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR)
// Test/stop if nothing changed.
if (first == resultItem) {
CustomAnvil.log("unit repair, But input is same as output")
return UnitRepairResult.EMPTY
}
val result =
DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.UNIT_REPAIR, cost)
return UnitRepairResult(result, cost, repairAmount)
}
fun testLoreEdit(
player: Player,
first: ItemStack, second: ItemStack
): LoreEditResult {
val type = second.type
val result = if (Material.WRITABLE_BOOK == type)
AnvilLoreEditUtil.tryLoreEditByBook(player, first, second)
else if (Material.PAPER == type)
AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second)
else LoreEditResult.EMPTY
if (result.isEmpty()) return result
if (result.item!!.isAir || first == result.item) {
CustomAnvil.log("lore edit, But input is same as output")
return LoreEditResult.EMPTY
}
return result
}
}

View file

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

View file

@ -7,15 +7,19 @@ import org.bukkit.Bukkit
import org.bukkit.ChatColor import org.bukkit.ChatColor
import org.bukkit.command.CommandSender import org.bukkit.command.CommandSender
import org.bukkit.entity.HumanEntity import org.bukkit.entity.HumanEntity
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.Inventory import org.bukkit.inventory.Inventory
import org.bukkit.inventory.InventoryView
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.anvil.AnvilCost
import xyz.alexcrea.cuanvil.anvil.AnvilUseType
import xyz.alexcrea.cuanvil.api.event.listener.CAClickResultBypassEvent import xyz.alexcrea.cuanvil.api.event.listener.CAClickResultBypassEvent
import xyz.alexcrea.cuanvil.api.event.listener.CAEarlyPreAnvilBypassEvent import xyz.alexcrea.cuanvil.api.event.listener.CAEarlyPreAnvilBypassEvent
import xyz.alexcrea.cuanvil.api.event.listener.CAPreAnvilBypassEvent import xyz.alexcrea.cuanvil.api.event.listener.CAPreAnvilBypassEvent
import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResultEvent import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResult2Event
import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.dependency.datapack.DataPackDependency import xyz.alexcrea.cuanvil.dependency.datapack.DataPackDependency
import xyz.alexcrea.cuanvil.dependency.gui.GenericExternGuiTester import xyz.alexcrea.cuanvil.dependency.gui.GenericExternGuiTester
@ -29,7 +33,6 @@ import xyz.alexcrea.cuanvil.dependency.scheduler.TaskScheduler
import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil
import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT
import xyz.alexcrea.cuanvil.util.AnvilUseType
import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError
import java.util.logging.Level import java.util.logging.Level
@ -198,7 +201,7 @@ object DependencyManager {
} }
// Return true if should bypass (either by a dependency or error) // Return true if should bypass (either by a dependency or error)
fun tryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean { fun tryEventPreAnvilBypass(event: PrepareAnvilEvent, player: Player): Boolean {
try { try {
return unsafeTryEventPreAnvilBypass(event, player) return unsafeTryEventPreAnvilBypass(event, player)
} catch (e: Exception) { } catch (e: Exception) {
@ -207,7 +210,7 @@ object DependencyManager {
} }
} }
private fun unsafeTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean { private fun unsafeTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: Player): Boolean {
// Run the event // Run the event
val bypassEvent = CAPreAnvilBypassEvent(event) val bypassEvent = CAPreAnvilBypassEvent(event)
Bukkit.getPluginManager().callEvent(bypassEvent) Bukkit.getPluginManager().callEvent(bypassEvent)
@ -232,22 +235,24 @@ object DependencyManager {
// Return null if there was an issue // Return null if there was an issue
fun tryTreatAnvilResult( fun tryTreatAnvilResult(
event: PrepareAnvilEvent, view: InventoryView,
inventory: Inventory, // TODO REMOVE, use view instead on legacy removal
player: HumanEntity,
result: ItemStack, result: ItemStack,
useType: AnvilUseType, useType: AnvilUseType,
cost: Int cost: AnvilCost
): CATreatAnvilResultEvent? { ): ItemStack? {
val treatEvent = CATreatAnvilResultEvent(event, useType, result, cost) val treatEvent = CATreatAnvilResult2Event(view, inventory, useType, result, cost)
try { try {
unsafeTryTreatAnvilResult(treatEvent) unsafeTryTreatAnvilResult(treatEvent)
return treatEvent; return treatEvent.result
} catch (e: Exception) { } catch (e: Exception) {
logExceptionAndClear(event.view.player, event.inventory, e) logExceptionAndClear(player, inventory, e)
return null return null
} }
} }
private fun unsafeTryTreatAnvilResult(event: CATreatAnvilResultEvent) { private fun unsafeTryTreatAnvilResult(event: CATreatAnvilResult2Event) {
Bukkit.getPluginManager().callEvent(event) Bukkit.getPluginManager().callEvent(event)
excellentEnchantsCompatibility?.treatAnvilResult(event) excellentEnchantsCompatibility?.treatAnvilResult(event)
@ -293,11 +298,11 @@ object DependencyManager {
} }
// Clone item and use plugin specific clone if needed // Clone item and use plugin specific clone if needed
fun cloneItem(event: PrepareAnvilEvent, item: ItemStack): ItemStack { fun cloneItem(player: HumanEntity, item: ItemStack): ItemStack {
try { try {
return unsafeCloneItem(item) return unsafeCloneItem(item)
} catch (e: Exception) { } catch (e: Exception) {
logException(event.view.player, e) logException(player, e)
return item.clone() return item.clone()
} }
} }

View file

@ -0,0 +1,32 @@
package xyz.alexcrea.cuanvil.dependency.economy
import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin
import java.math.BigDecimal
interface EconomyManager {
companion object {
var economy: EconomyManager? = null
fun setupEconomy(plugin: Plugin) {
if (plugin.server.pluginManager.getPlugin("Vault") == null)
return
if (UnlockedEconomyManager.unlockedAvailable())
economy = UnlockedEconomyManager(plugin)
if (economy == null || !economy!!.initialized())
economy = VaultEconomyManager(plugin)
}
}
fun initialized(): Boolean
// We assume "initialized" got checked before these function get called
fun has(player: Player, money: BigDecimal): Boolean
fun remove(player: Player, money: BigDecimal): Boolean
fun format(money: BigDecimal): String;
}

View file

@ -0,0 +1,74 @@
package xyz.alexcrea.cuanvil.dependency.economy
import io.delilaheve.util.ConfigOptions
import net.milkbowl.vault2.economy.Economy
import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin
import java.math.BigDecimal
class UnlockedEconomyManager : EconomyManager {
val plugin: String
val economy: Economy?
companion object {
fun unlockedAvailable(): Boolean {
try {
Class.forName("net.milkbowl.vault2.economy.Economy")
return true
} catch (_: ClassNotFoundException) {
return false
}
}
}
constructor(plugin: Plugin) {
this.plugin = plugin.name
val rsp = plugin.server.servicesManager.getRegistration(Economy::class.java)
economy = rsp?.getProvider()
}
override fun initialized(): Boolean {
return economy != null
}
private fun currency(): String {
val configured = ConfigOptions.usedCurrency
return if ("default".equals(configured, true))
economy!!.getDefaultCurrency(plugin)
else configured
}
override fun has(player: Player, money: BigDecimal): Boolean {
if (money.signum() <= 0) return true
return economy!!.has(
plugin,
player.uniqueId,
player.world.name,
currency(),
money
)
}
override fun remove(player: Player, money: BigDecimal): Boolean {
if (money.signum() <= 0) return true
return economy!!.withdraw(
plugin,
player.uniqueId,
player.world.name,
currency(),
money
)
.transactionSuccess()
}
override fun format(money: BigDecimal): String {
return economy!!.format(plugin, money, currency())
}
}

View file

@ -0,0 +1,37 @@
package xyz.alexcrea.cuanvil.dependency.economy
import net.milkbowl.vault.economy.Economy
import org.bukkit.entity.Player
import org.bukkit.plugin.Plugin
import java.math.BigDecimal
class VaultEconomyManager : EconomyManager {
val economy: Economy?
constructor(plugin: Plugin) {
val rsp = plugin.server.servicesManager.getRegistration(Economy::class.java)
economy = rsp?.getProvider()
}
override fun initialized(): Boolean {
return economy != null
}
override fun has(player: Player, money: BigDecimal): Boolean {
if (money.signum() <= 0) return true
return economy!!.has(player, money.toDouble())
}
override fun remove(player: Player, money: BigDecimal): Boolean {
if (money.signum() <= 0) return true
return economy!!.withdrawPlayer(player, money.toDouble()).transactionSuccess()
}
override fun format(money: BigDecimal): String {
return economy!!.format(money.toDouble())
}
}

View file

@ -8,15 +8,16 @@ import com.jankominek.disenchantment.events.ShatterEvent
import com.jankominek.disenchantment.listeners.DisenchantClickListener import com.jankominek.disenchantment.listeners.DisenchantClickListener
import com.jankominek.disenchantment.listeners.ShatterClickListener import com.jankominek.disenchantment.listeners.ShatterClickListener
import io.delilaheve.CustomAnvil import io.delilaheve.CustomAnvil
import org.bukkit.entity.HumanEntity import org.bukkit.entity.Player
import org.bukkit.event.Listener import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.PrepareAnvilEvent 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.anvil.AnvilCost
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener
import xyz.alexcrea.cuanvil.util.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError
import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
import java.util.logging.Level import java.util.logging.Level
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -50,7 +51,7 @@ class DisenchantmentDependency {
InventoryClickEvent.getHandlerList().unregister(listener) InventoryClickEvent.getHandlerList().unregister(listener)
} }
fun testPrepareAnvil(event: PrepareAnvilEvent, player: HumanEntity): Boolean { fun testPrepareAnvil(event: PrepareAnvilEvent, player: Player): Boolean {
val previousResult = event.result val previousResult = event.result
event.result = null event.result = null
@ -58,14 +59,14 @@ class DisenchantmentDependency {
DisenchantEvent.onEvent(event) DisenchantEvent.onEvent(event)
if (event.result != null) { if (event.result != null) {
CustomAnvil.log("Detected pre anvil item extract bypass.") CustomAnvil.log("Detected pre anvil item extract bypass.")
AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost) AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost))
return true return true
} }
ShatterEvent.onEvent(event) ShatterEvent.onEvent(event)
if (event.result != null) { if (event.result != null) {
CustomAnvil.log("Detected pre anvil split enchant bypass.") CustomAnvil.log("Detected pre anvil split enchant bypass.")
AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost) AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost))
return true return true
} }

View file

@ -8,11 +8,12 @@ import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.plugin.RegisteredListener import org.bukkit.plugin.RegisteredListener
import xyz.alexcrea.cuanvil.api.EnchantmentApi import xyz.alexcrea.cuanvil.api.EnchantmentApi
import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResultEvent import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResult2Event
import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEPreV5Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEPreV5Enchantment
import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5Enchantment
import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5_4Enchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5_4Enchantment
import xyz.alexcrea.cuanvil.enchant.wrapped.CALegacyEEEnchantment import xyz.alexcrea.cuanvil.enchant.wrapped.CALegacyEEEnchantment
import xyz.alexcrea.cuanvil.util.ModernPrepareAnvilCreator
import java.lang.reflect.Method import java.lang.reflect.Method
import su.nightexpress.excellentenchants.api.EnchantRegistry as V5EnchantRegistry import su.nightexpress.excellentenchants.api.EnchantRegistry as V5EnchantRegistry
import su.nightexpress.excellentenchants.enchantment.impl.universal.CurseOfFragilityEnchant as LegacyCurseOfFragilityEnchant import su.nightexpress.excellentenchants.enchantment.impl.universal.CurseOfFragilityEnchant as LegacyCurseOfFragilityEnchant
@ -218,14 +219,20 @@ class ExcellentEnchantsDependency {
return handleRechargeMethod.invoke(this.usedAnvilListener, event, first, second) as Boolean return handleRechargeMethod.invoke(this.usedAnvilListener, event, first, second) as Boolean
} }
fun treatAnvilResult(event: CATreatAnvilResultEvent) { fun treatAnvilResult(event: CATreatAnvilResult2Event) {
val result = event.result val result = event.result ?: return
if (result == null) return
val first: ItemStack = treatInput(event.event.inventory.getItem(0)) val first: ItemStack = treatInput(event.leftItem)
val second: ItemStack = treatInput(event.event.inventory.getItem(1)) val second: ItemStack = treatInput(event.rightItem)
val fakeEvent: PrepareAnvilEvent = try {
//TODO remove this on legacy removal
PrepareAnvilEvent(event.view, result)
} catch (_: NoSuchMethodError) {
ModernPrepareAnvilCreator.createPrepareAnvil(event.view, result)
}
handleCombineMethod.invoke(this.usedAnvilListener, fakeEvent, first, second, result)
handleCombineMethod.invoke(this.usedAnvilListener, event.event, first, second, result) event.result = fakeEvent.result
} }
fun testAnvilResult(event: InventoryClickEvent): Any { fun testAnvilResult(event: InventoryClickEvent): Any {

View file

@ -1,7 +1,7 @@
package xyz.alexcrea.cuanvil.dependency.plugins package xyz.alexcrea.cuanvil.dependency.plugins
import io.delilaheve.CustomAnvil import io.delilaheve.CustomAnvil
import org.bukkit.entity.HumanEntity import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.AnvilInventory
@ -9,8 +9,9 @@ import org.bukkit.plugin.RegisteredListener
import valorless.havenbags.HavenBags import valorless.havenbags.HavenBags
import valorless.havenbags.features.BagSkin import valorless.havenbags.features.BagSkin
import valorless.havenbags.features.BagUpgrade import valorless.havenbags.features.BagUpgrade
import xyz.alexcrea.cuanvil.anvil.AnvilCost
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener
import xyz.alexcrea.cuanvil.util.AnvilXpUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
class HavenBagsDependency { class HavenBagsDependency {
@ -45,7 +46,7 @@ class HavenBagsDependency {
} }
fun testPrepareAnvil(event: PrepareAnvilEvent, player: HumanEntity): Boolean { fun testPrepareAnvil(event: PrepareAnvilEvent, player: Player): Boolean {
val previousResult = event.result val previousResult = event.result
event.result = null event.result = null
@ -53,14 +54,14 @@ class HavenBagsDependency {
bagSkin.onPrepareAnvil(event) bagSkin.onPrepareAnvil(event)
if (event.result != null) { if (event.result != null) {
CustomAnvil.log("Detected pre anvil heaven bag anvil skin.") CustomAnvil.log("Detected pre anvil heaven bag anvil skin.")
AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost) AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost))
return true return true
} }
bagUpgrade.onPrepareAnvil(event) bagUpgrade.onPrepareAnvil(event)
if (event.result != null) { if (event.result != null) {
CustomAnvil.log("Detected pre anvil heaven bag anvil upgrade.") CustomAnvil.log("Detected pre anvil heaven bag anvil upgrade.")
AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost) AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost))
return true return true
} }

View file

@ -3,7 +3,6 @@ package xyz.alexcrea.cuanvil.listener
import io.delilaheve.CustomAnvil import io.delilaheve.CustomAnvil
import io.delilaheve.util.ConfigOptions import io.delilaheve.util.ConfigOptions
import io.delilaheve.util.ItemUtil.canMergeWith import io.delilaheve.util.ItemUtil.canMergeWith
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.Player import org.bukkit.entity.Player
@ -16,22 +15,25 @@ 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 org.bukkit.inventory.meta.BookMeta
import xyz.alexcrea.cuanvil.anvil.AnvilCost
import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic
import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.AnvilResult
import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.CustomCraftResult
import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.LoreEditResult
import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.UnitRepairResult
import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager
import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentDisplayName import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentDisplayName
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.util.AnvilLoreEditUtil
import xyz.alexcrea.cuanvil.util.AnvilUseType
import xyz.alexcrea.cuanvil.util.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.CustomRecipeUtil import xyz.alexcrea.cuanvil.util.CustomRecipeUtil
import xyz.alexcrea.cuanvil.util.MiniMessageUtil import xyz.alexcrea.cuanvil.util.MiniMessageUtil
import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil
import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil
import xyz.alexcrea.cuanvil.util.config.LoreEditType import xyz.alexcrea.cuanvil.util.config.LoreEditType
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
import kotlin.math.min import kotlin.math.min
@ -50,6 +52,7 @@ class AnvilResultListener : Listener {
fun anvilExtractionCheck(event: InventoryClickEvent) { fun anvilExtractionCheck(event: InventoryClickEvent) {
val player = event.whoClicked as? Player ?: return val player = event.whoClicked as? Player ?: return
val inventory = event.inventory as? AnvilInventory ?: return val inventory = event.inventory as? AnvilInventory ?: return
val view = event.view
if (event.rawSlot != ANVIL_OUTPUT_SLOT) { if (event.rawSlot != ANVIL_OUTPUT_SLOT) {
return return
@ -64,84 +67,104 @@ class AnvilResultListener : Listener {
val leftItem = inventory.getItem(ANVIL_INPUT_LEFT) ?: return val leftItem = inventory.getItem(ANVIL_INPUT_LEFT) ?: return
val rightItem = inventory.getItem(ANVIL_INPUT_RIGHT) val rightItem = inventory.getItem(ANVIL_INPUT_RIGHT)
// Deny by default. allow if working
event.result = Event.Result.DENY
if (GameMode.CREATIVE != player.gameMode && inventory.repairCost >= inventory.maximumRepairCost) { if (GameMode.CREATIVE != player.gameMode && inventory.repairCost >= inventory.maximumRepairCost) {
event.result = Event.Result.DENY
return return
} }
// Test custom recipe // Test custom recipe
val recipe = CustomRecipeUtil.getCustomRecipe(leftItem, rightItem) val customRecipeResult = AnvilMergeLogic.testCustomRecipe(view, inventory, player, leftItem, rightItem)
if (recipe != null) { if (!customRecipeResult.isEmpty()) {
event.result = Event.Result.ALLOW
onCustomCraft( onCustomCraft(
event, recipe, player, event, player, inventory,
leftItem, rightItem, output, inventory leftItem, rightItem, customRecipeResult
) )
return return
} }
// Do not continue if there was no change // Do not continue if there was no change
if ((output == inventory.getItem(ANVIL_INPUT_LEFT))) { if ((output == inventory.getItem(ANVIL_INPUT_LEFT))) {
event.result = Event.Result.DENY
return return
} }
// Rename // Rename
if (rightItem == null) { if (rightItem == null) {
event.result = Event.Result.ALLOW val result = AnvilMergeLogic.doRenaming(view, inventory, player, leftItem)
if (result.isEmpty()) return
extractAnvilResult(
event, player, inventory,
null, 0,
null, 0,
result
)
return return
} }
// Merge // Merge
val canMerge = leftItem.canMergeWith(rightItem) val canMerge = leftItem.canMergeWith(rightItem)
if (canMerge) { if (canMerge) {
event.result = Event.Result.ALLOW val result = AnvilMergeLogic.doMerge(view, inventory, player, leftItem, rightItem)
extractAnvilResult(
event, player, inventory,
null, 0,
null, 0,
result
)
return return
} }
// Unit repair // Unit repair
val unitRepairResult = leftItem.getRepair(rightItem) val unitRepairResult = AnvilMergeLogic.testUnitRepair(
if (unitRepairResult != null) { view, inventory, player,
leftItem, rightItem
)
if (unitRepairResult.isEmpty()) {
onUnitRepairExtract( onUnitRepairExtract(
leftItem, rightItem, output, rightItem, event, player, inventory,
unitRepairResult, event, player, inventory unitRepairResult
) )
return return
} }
// For lore edit // For lore edit
if (handleBookLoreEdit(event, inventory, player, leftItem, rightItem, output)) { val loreResult = AnvilMergeLogic.testLoreEdit(player, leftItem, rightItem)
return if (!loreResult.isEmpty()) {
} else if (handlePaperLoreEdit(event, inventory, player, leftItem, rightItem, output)) { if (loreResult.type.isBook)
handleBookLoreEdit(event, inventory, player, leftItem, rightItem, loreResult)
else
handlePaperLoreEdit(event, inventory, player, leftItem, rightItem, loreResult)
return return
} }
// Else there was no working situation somehow so we deny
event.result = Event.Result.DENY
} }
private fun onCustomCraft( private fun onCustomCraft(
event: InventoryClickEvent, event: InventoryClickEvent,
recipe: AnvilCustomRecipe,
player: Player, player: Player,
inventory: AnvilInventory,
leftItem: ItemStack, leftItem: ItemStack,
rightItem: ItemStack?, rightItem: ItemStack?,
output: ItemStack, result: CustomCraftResult,
inventory: AnvilInventory
) { ) {
event.result = Event.Result.DENY val recipe = result.recipe!!
val rawCost = result.customCraftCost.rawCost
if (recipe.leftItem == null) return // in case it changed
val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem)
val xpCost = recipe.determineCost(amount, leftItem, output)
val finalCost = val finalCost =
if (recipe.removeExactLinearXp) xpCost if (recipe.removeExactLinearXp) rawCost
else AnvilXpUtil.calculateLevelForXp(xpCost) else AnvilXpUtil.calculateLevelForXp(rawCost)
CustomAnvil.log(
"gamemode: ${player.gameMode != GameMode.CREATIVE}, " +
"cost: $finalCost, level: ${player.level}, " +
"result: ${player.totalExperience < finalCost} ${player.level < finalCost}"
)
CustomAnvil.log("gamemode: ${player.gameMode != GameMode.CREATIVE}, cost: $finalCost, level: ${player.level}, result: ${player.totalExperience < finalCost} ${player.level < finalCost}")
if (player.gameMode != GameMode.CREATIVE) { if (player.gameMode != GameMode.CREATIVE) {
if (recipe.removeExactLinearXp) { if (ConfigOptions.shouldUseMoney(player)) {
result.cost.isMonetary = true
if (!EconomyManager.economy!!.has(player, result.cost.asMonetaryCost())) return
} else if (recipe.removeExactLinearXp) {
val levelXp = AnvilXpUtil.calculateXpForLevel(player.level) val levelXp = AnvilXpUtil.calculateXpForLevel(player.level)
val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp
val totalXp = levelXp + player.exp * delta val totalXp = levelXp + player.exp * delta
@ -158,31 +181,31 @@ class AnvilResultListener : Listener {
if (event.click != ClickType.MIDDLE && if (event.click != ClickType.MIDDLE &&
!handleCustomCraftClick( !handleCustomCraftClick(
event, event,
recipe,
inventory, inventory,
player, player,
leftItem, leftItem,
rightItem, rightItem,
amount, result
finalCost,
recipe.removeExactLinearXp
) )
) return ) 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) {
player.setItemOnCursor(output) player.setItemOnCursor(result.item)
} else {// We assume SlotType == SlotType.INVENTORY } else {// We assume SlotType == SlotType.INVENTORY
player.inventory.setItem(slotDestination.slot, output) player.inventory.setItem(slotDestination.slot, result.item)
} }
} }
private fun handleCustomCraftClick( private fun handleCustomCraftClick(
event: InventoryClickEvent, recipe: AnvilCustomRecipe, event: InventoryClickEvent,
inventory: AnvilInventory, player: Player, inventory: AnvilInventory, player: Player,
leftItem: ItemStack, rightItem: ItemStack?, leftItem: ItemStack, rightItem: ItemStack?,
amount: Int, xpCost: Int, linearCost: Boolean = false result: CustomCraftResult
): Boolean { ): Boolean {
val amount = result.amount
val recipe = result.recipe!!
// 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
@ -194,25 +217,7 @@ class AnvilResultListener : Listener {
leftItem.amount -= amount * recipe.leftItem!!.amount leftItem.amount -= amount * recipe.leftItem!!.amount
inventory.setItem(ANVIL_INPUT_LEFT, leftItem) inventory.setItem(ANVIL_INPUT_LEFT, leftItem)
if (player.gameMode != GameMode.CREATIVE) { removeCustomCraftCost(player, result)
if (linearCost) {
val levelXp = AnvilXpUtil.calculateXpForLevel(player.level)
val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp
var totalXp = levelXp + player.exp * delta
totalXp -= xpCost
val newLevel = AnvilXpUtil.calculateLevelForXp(totalXp.toInt())
val newLevelXp = AnvilXpUtil.calculateXpForLevel(newLevel)
val newDelta = AnvilXpUtil.calculateXpForLevel(newLevel + 1) - newLevelXp
val xp = (totalXp - newLevelXp) / newDelta
player.level = newLevel
player.exp = xp / newDelta
} else {
player.level -= xpCost
}
}
// Then we try to find the new values for the anvil // Then we try to find the new values for the anvil
val newAmount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem) val newAmount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem)
@ -236,6 +241,47 @@ class AnvilResultListener : Listener {
return true return true
} }
private fun removeCustomCraftCost(player: Player, result: CustomCraftResult) {
if (player.gameMode == GameMode.CREATIVE) return
val rawCost = result.customCraftCost.rawCost
if (result.cost.isMonetary) {
EconomyManager.economy!!.remove(player, result.cost.asMonetaryCost())
return
}
if (result.recipe!!.removeExactLinearXp) {
val levelXp = AnvilXpUtil.calculateXpForLevel(player.level)
val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp
var totalXp = levelXp + player.exp * delta
totalXp -= rawCost
val newLevel = AnvilXpUtil.calculateLevelForXp(totalXp.toInt())
val newLevelXp = AnvilXpUtil.calculateXpForLevel(newLevel)
val newDelta = AnvilXpUtil.calculateXpForLevel(newLevel + 1) - newLevelXp
val xp = (totalXp - newLevelXp) / newDelta
player.level = newLevel
player.exp = xp / newDelta
} else {
player.level -= AnvilXpUtil.calculateLevelForXp(rawCost)
}
}
private fun tryRemoveCost(player: Player, cost: AnvilCost): Boolean {
if (player.gameMode == GameMode.CREATIVE) return true
if (cost.isMonetary) {
val result = EconomyManager.economy!!.remove(player, cost.asMonetaryCost())
if (!result) return false
} else {
player.level -= cost.asXpCost()
}
return true
}
private fun extractAnvilResult( private fun extractAnvilResult(
event: InventoryClickEvent, event: InventoryClickEvent,
player: Player, player: Player,
@ -244,15 +290,17 @@ class AnvilResultListener : Listener {
leftRemoveCount: Int, leftRemoveCount: Int,
rightItem: ItemStack?, rightItem: ItemStack?,
rightRemoveCount: Int, rightRemoveCount: Int,
output: ItemStack, result: AnvilResult
repairCost: Int,
): Boolean { ): Boolean {
if (result.isEmpty()) return false
// To avoid vanilla, we cancel the event // To avoid vanilla, we cancel the event
event.result = Event.Result.DENY event.result = Event.Result.DENY
event.isCancelled = true event.isCancelled = true
val cost = result.cost
// Assumed if player do not have enough xp then it returned MIN_VALUE processCost(inventory, player, cost)
if (repairCost == Int.MIN_VALUE) return false if (!cost.valid && player.gameMode != GameMode.CREATIVE) return false
// Where should we get the item // Where should we get the item
val slotDestination = getActionSlot(event, player) val slotDestination = getActionSlot(event, player)
@ -260,6 +308,8 @@ class AnvilResultListener : Listener {
// If not creative middle click... // If not creative middle click...
if (event.click != ClickType.MIDDLE) { if (event.click != ClickType.MIDDLE) {
if (!tryRemoveCost(player, cost)) return false
// We remove what should be removed // We remove what should be removed
if (leftItem != null) leftItem.amount -= leftRemoveCount if (leftItem != null) leftItem.amount -= leftRemoveCount
inventory.setItem(ANVIL_INPUT_LEFT, leftItem) inventory.setItem(ANVIL_INPUT_LEFT, leftItem)
@ -268,99 +318,58 @@ class AnvilResultListener : Listener {
inventory.setItem(ANVIL_INPUT_RIGHT, rightItem) inventory.setItem(ANVIL_INPUT_RIGHT, rightItem)
inventory.setItem(ANVIL_OUTPUT_SLOT, null) inventory.setItem(ANVIL_OUTPUT_SLOT, null)
player.level -= repairCost
} }
// Finally, we add the item to the player // Finally, we add the item to the player
if (SlotType.CURSOR == slotDestination.type) { if (SlotType.CURSOR == slotDestination.type) {
player.setItemOnCursor(output) player.setItemOnCursor(result.item)
} else {// We assume SlotType == SlotType.INVENTORY } else {// We assume SlotType == SlotType.INVENTORY
player.inventory.setItem(slotDestination.slot, output) player.inventory.setItem(slotDestination.slot, result.item)
} }
// TODO probably anvil damage & sound here ?? // TODO probably anvil damage & sound here ??
return true return true
} }
private fun onUnitRepairExtract( private fun processCost(inventory: AnvilInventory, player: Player, cost: AnvilCost) {
leftItem: ItemStack, var sum = cost.repair
rightItem: ItemStack,
output: ItemStack,
unitRepairResult: Double,
event: InventoryClickEvent,
player: Player,
inventory: AnvilInventory
) {
val resultCopy = leftItem.clone()
val resultAmount = resultCopy.unitRepair(
rightItem.amount, unitRepairResult
)
// Get repair cost
val repairCost = getUnitRepairCost(inventory, player, leftItem, output, resultCopy, resultAmount)
// And then we give the item manually
extractAnvilResult(
event, player, inventory,
null, 0,
rightItem, resultAmount,
resultCopy, repairCost
)
}
private fun getUnitRepairCost(
inventory: AnvilInventory, player: Player,
leftItem: ItemStack, output: ItemStack,
resultCopy: ItemStack, resultAmount: Int
): Int {
if (player.gameMode == GameMode.CREATIVE) return 0
var repairCost = 0
// Get repairCost
leftItem.itemMeta?.let { leftMeta ->
val leftName = leftMeta.displayName
output.itemMeta?.let {
// Rename cost
if (!leftName.contentEquals(it.displayName)) {
repairCost += ConfigOptions.itemRenameCost
// Color cost
if (it.displayName.contains('§')) {
repairCost += ConfigOptions.useOfColorCost
}
}
}
}
repairCost += AnvilXpUtil.calculatePenalty(leftItem, null, resultCopy, AnvilUseType.UNIT_REPAIR)
repairCost += resultAmount * ConfigOptions.unitRepairCost
if ( if (
!ConfigOptions.doRemoveCostLimit && !ConfigOptions.doRemoveCostLimit &&
ConfigOptions.doCapCost ConfigOptions.doCapCost
) { ) {
repairCost = min(repairCost, ConfigOptions.maxAnvilCost) val final = min(sum, ConfigOptions.maxAnvilCost)
cost.generic += (final - sum)
sum = final
} }
if ((inventory.maximumRepairCost <= repairCost) if (ConfigOptions.shouldUseMoney(player)) {
|| (player.level < repairCost) cost.isMonetary = true
) return Int.MIN_VALUE if (!EconomyManager.economy!!.has(player, cost.asMonetaryCost()))
cost.valid = false
return repairCost } else {
if ((inventory.maximumRepairCost <= sum)
|| (player.level < sum)
) cost.valid = false
}
} }
private fun getFromLoreEditXpCost( private fun onUnitRepairExtract(
xpCost: AtomicInteger, rightItem: ItemStack,
event: InventoryClickEvent,
player: Player, player: Player,
inventory: AnvilInventory, inventory: AnvilInventory,
): Int { result: UnitRepairResult,
if (GameMode.CREATIVE == player.gameMode) return 0 ) {
// We give the item manually
val repairCost = xpCost.get() extractAnvilResult(
return if ((inventory.maximumRepairCost <= repairCost) event, player, inventory,
|| (player.level < repairCost) null, 0,
) Int.MIN_VALUE rightItem, result.repairAmount,
else repairCost result
)
} }
private fun handleBookLoreEdit( private fun handleBookLoreEdit(
@ -369,70 +378,84 @@ class AnvilResultListener : Listener {
player: Player, player: Player,
leftItem: ItemStack, leftItem: ItemStack,
rightItem: ItemStack, rightItem: ItemStack,
output: ItemStack, result: LoreEditResult
): Boolean { ) {
if (Material.WRITABLE_BOOK != rightItem.type) return false if (result.type.isAppend)
val bookMeta = rightItem.itemMeta as BookMeta? ?: return false handleBookLoreAppend(event, inventory, player, rightItem, result)
else
handleBookLoreRemove(event, inventory, player, leftItem, rightItem, result)
}
val editType = AnvilLoreEditUtil.bookLoreEditIsAppend(leftItem, rightItem) ?: return false private fun handleBookLoreAppend(
event: InventoryClickEvent,
inventory: AnvilInventory,
player: Player,
rightItem: ItemStack,
result: LoreEditResult
) {
val bookMeta = rightItem.itemMeta as BookMeta? ?: return
val xpCost = AtomicInteger() // Remove pages to book
if (editType) { val clearedBook: ItemStack?
if (output != AnvilLoreEditUtil.handleLoreAppendByBook(player, leftItem, bookMeta, xpCost)) return false if (LoreEditType.APPEND_BOOK.doConsume) {
clearedBook = null
// 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 { } else {
if (output != AnvilLoreEditUtil.handleLoreRemoveByBook(player, leftItem, xpCost)) return false clearedBook = rightItem.clone()
bookMeta.pages = Collections.emptyList()
clearedBook.itemMeta = bookMeta
}
// fill book meta extractAnvilResult(
val lore = DependencyManager.stripLore(leftItem) event, player, inventory,
if (lore.isEmpty()) return false null, 0,
clearedBook, 0,
result
)
}
val rightCopy: ItemStack? private fun handleBookLoreRemove(
if (LoreEditType.REMOVE_BOOK.doConsume) { event: InventoryClickEvent,
rightCopy = null inventory: AnvilInventory,
} else { player: Player,
// Uncolor the page leftItem: ItemStack,
AnvilLoreEditUtil.uncolorLines(player, lore, LoreEditType.REMOVE_BOOK) rightItem: ItemStack,
result: LoreEditResult
) {
val bookMeta = rightItem.itemMeta as BookMeta? ?: return
val bookPage = StringBuilder() // fill book meta
lore.forEach { val lore = DependencyManager.stripLore(leftItem)
if (bookPage.isNotEmpty()) bookPage.append('\n') if (lore.isEmpty()) return
if(it == null) return@forEach
bookPage.append(MiniMessageUtil.plain_text_mm.serialize(it)) val rightCopy: ItemStack?
} if (LoreEditType.REMOVE_BOOK.doConsume) {
rightCopy = null
} else {
// Uncolor the page
AnvilLoreEditUtil.uncolorLines(player, lore, LoreEditType.REMOVE_BOOK)
val resultPage = bookPage.toString() val bookPage = StringBuilder()
//TODO maybe check page size ? bc it may be too big ??? lore.forEach {
if (bookPage.isNotEmpty()) bookPage.append('\n')
if (it == null) return@forEach
rightCopy = rightItem.clone() bookPage.append(MiniMessageUtil.plain_text_mm.serialize(it))
bookMeta.setPages(resultPage)
rightCopy.itemMeta = bookMeta
} }
return extractAnvilResult( val resultPage = bookPage.toString()
event, player, inventory, //TODO maybe check page size ? bc it may be too big ???
null, 0,
rightCopy, 0, rightCopy = rightItem.clone()
output, getFromLoreEditXpCost(xpCost, player, inventory) bookMeta.setPages(resultPage)
) rightCopy.itemMeta = bookMeta
} }
extractAnvilResult(
event, player, inventory,
null, 0,
rightCopy, 0,
result
)
} }
private fun handlePaperLoreEdit( private fun handlePaperLoreEdit(
@ -441,89 +464,106 @@ class AnvilResultListener : Listener {
player: Player, player: Player,
leftItem: ItemStack, leftItem: ItemStack,
rightItem: ItemStack, rightItem: ItemStack,
output: ItemStack, result: LoreEditResult
): Boolean { ) {
if (Material.PAPER != rightItem.type) return false if (result.type.isAppend)
val paperMeta = rightItem.itemMeta ?: return false handlePaperLoreAppend(event, inventory, player, rightItem, result)
else
handlePaperLoreRemove(event, inventory, player, leftItem, rightItem, result)
}
val editTypeIsAppend = AnvilLoreEditUtil.paperLoreEditIsAppend(leftItem, rightItem) ?: return false private fun handlePaperLoreAppend(
event: InventoryClickEvent,
inventory: AnvilInventory,
player: Player,
rightItem: ItemStack,
result: LoreEditResult
) {
val paperMeta = rightItem.itemMeta ?: return
val xpCost = AtomicInteger()
if (editTypeIsAppend) {
if (output != AnvilLoreEditUtil.handleLoreAppendByPaper(player, leftItem, rightItem, xpCost)) return false
val paperCopy: ItemStack? val paperCopy: ItemStack?
if (LoreEditType.APPEND_PAPER.doConsume) { if (LoreEditType.APPEND_PAPER.doConsume) {
paperCopy = null paperCopy = null
} else {
// Remove custom name to paper
paperCopy = rightItem.clone()
paperCopy.amount = 1
paperMeta.setComponentDisplayName(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 { } else {
if (output != AnvilLoreEditUtil.handleLoreRemoveByPaper(player, leftItem, xpCost)) return false // Remove custom name to paper
paperCopy = rightItem.clone()
paperCopy.amount = 1
paperMeta.setComponentDisplayName(null)
val leftMeta = leftItem.itemMeta // Remove pcd name
if (leftMeta == null || !leftMeta.hasLore()) return false AnvilMergeLogic.processPCD(paperMeta, player, null)
val lore = DependencyManager.stripLore(leftItem)
if (lore.isEmpty()) return false
// Create result item paperCopy.itemMeta = paperMeta
val rightClone: ItemStack?
if (LoreEditType.REMOVE_PAPER.doConsume) {
rightClone = null
} else {
val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd
val line = if (removeEnd) lore[lore.size - 1]
else lore[0]
// uncolor the line
val ref = AtomicReference(line)
AnvilLoreEditUtil.uncolorLine(player, ref, LoreEditType.REMOVE_PAPER)
rightClone = rightItem.clone()
rightClone.amount = 1
val resultMeta = rightClone.itemMeta ?: return false
resultMeta.setComponentDisplayName(ref.get())
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)
)
}
} }
if (rightItem.amount > 1) {
extractAnvilResult(
event, player, inventory,
paperCopy, 0,
rightItem, 1,
result
)
} else {
extractAnvilResult(
event, player, inventory,
null, 0,
paperCopy, 0,
result
)
}
}
private fun handlePaperLoreRemove(
event: InventoryClickEvent,
inventory: AnvilInventory,
player: Player,
leftItem: ItemStack,
rightItem: ItemStack,
result: LoreEditResult
) {
val leftMeta = leftItem.itemMeta
if (leftMeta == null || !leftMeta.hasLore()) return
val lore = DependencyManager.stripLore(leftItem)
if (lore.isEmpty()) return
// Create result item
val rightClone: ItemStack?
if (LoreEditType.REMOVE_PAPER.doConsume) {
rightClone = null
} else {
val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd
val line = if (removeEnd) lore[lore.size - 1]
else lore[0]
// uncolor the line
val ref = AtomicReference(line)
AnvilLoreEditUtil.uncolorLine(player, ref, LoreEditType.REMOVE_PAPER)
rightClone = rightItem.clone()
rightClone.amount = 1
val resultMeta = rightClone.itemMeta ?: return
resultMeta.setComponentDisplayName(ref.get())
rightClone.itemMeta = resultMeta
}
if (rightItem.amount > 1) {
extractAnvilResult(
event, player, inventory,
rightClone, 0,
rightItem, 1,
result
)
} else {
extractAnvilResult(
event, player, inventory,
null, 0,
rightClone, 0,
result
)
}
} }
/** /**

View file

@ -3,33 +3,29 @@ package xyz.alexcrea.cuanvil.listener
import com.github.stefvanschie.inventoryframework.util.InventoryViewUtil import com.github.stefvanschie.inventoryframework.util.InventoryViewUtil
import io.delilaheve.CustomAnvil import io.delilaheve.CustomAnvil
import io.delilaheve.util.ConfigOptions import io.delilaheve.util.ConfigOptions
import io.delilaheve.util.EnchantmentUtil.combineWith
import io.delilaheve.util.ItemUtil.canMergeWith import io.delilaheve.util.ItemUtil.canMergeWith
import io.delilaheve.util.ItemUtil.findEnchantments
import io.delilaheve.util.ItemUtil.isEnchantedBook
import io.delilaheve.util.ItemUtil.repairFrom
import io.delilaheve.util.ItemUtil.setEnchantmentsUnsafe
import io.delilaheve.util.ItemUtil.unitRepair
import org.bukkit.ChatColor
import org.bukkit.Material
import org.bukkit.entity.HumanEntity import org.bukkit.entity.HumanEntity
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority import org.bukkit.event.EventPriority
import org.bukkit.event.Listener import org.bukkit.event.Listener
import org.bukkit.event.inventory.PrepareAnvilEvent import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.InventoryView
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.EnchantmentStorageMeta import org.bukkit.inventory.meta.EnchantmentStorageMeta
import org.bukkit.inventory.meta.ItemMeta import org.bukkit.inventory.meta.ItemMeta
import org.bukkit.persistence.PersistentDataType import xyz.alexcrea.cuanvil.anvil.AnvilCost
import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.AnvilResult
import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.doMerge
import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.doRenaming
import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testCustomRecipe
import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testLoreEdit
import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testUnitRepair
import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
import xyz.alexcrea.cuanvil.util.*
import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir
import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil
import java.util.concurrent.atomic.AtomicInteger
/** /**
* Listener for anvil events * Listener for anvil events
@ -44,8 +40,6 @@ class PrepareAnvilListener : Listener {
const val ANVIL_OUTPUT_SLOT = 2 const val ANVIL_OUTPUT_SLOT = 2
var IS_EMPTY_TEST = false var IS_EMPTY_TEST = false
private const val RENAME_DIALOG_PERMISSION = "ca.rename.dialog"
} }
/** /**
@ -53,23 +47,27 @@ class PrepareAnvilListener : Listener {
*/ */
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
fun anvilCombineCheck(event: PrepareAnvilEvent) { fun anvilCombineCheck(event: PrepareAnvilEvent) {
// Should find player val view = event.view
val player: HumanEntity = InventoryViewUtil.getInstance().getPlayer(event.view)
val inventory = event.inventory val inventory = event.inventory
val player = InventoryViewUtil.getInstance().getPlayer(view)
if(player !is Player) return
tryRenameDialog(player, event)
// Test if custom anvil is bypassed before immutability test // Test if custom anvil is bypassed before immutability test
if (DependencyManager.earlyTryEventPreAnvilBypass(event, player)) { if (DependencyManager.earlyTryEventPreAnvilBypass(event, player)) {
// even if we got bypassed we still want to set price // even if we got bypassed we still want to set price
AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, event.inventory.repairCost) AnvilXpUtil.setAnvilInvCost(inventory, view, player, AnvilCost(event.inventory.repairCost))
return return
} }
val first = inventory.getItem(ANVIL_INPUT_LEFT) ?: return val first = inventory.getItem(ANVIL_INPUT_LEFT)
val second = inventory.getItem(ANVIL_INPUT_RIGHT) val second = inventory.getItem(ANVIL_INPUT_RIGHT)
if(IS_EMPTY_TEST) { if(IS_EMPTY_TEST) {
event.result = null
IS_EMPTY_TEST = false IS_EMPTY_TEST = false
applyResult(event, player, AnvilResult.EMPTY)
return return
} }
@ -81,79 +79,69 @@ class PrepareAnvilListener : Listener {
if (isImmutable(first) || isImmutable(second)) { if (isImmutable(first) || isImmutable(second)) {
CustomAnvil.verboseLog("Skipping anvil process as one of the two item is immutable") CustomAnvil.verboseLog("Skipping anvil process as one of the two item is immutable")
event.result = null applyResult(event, player, AnvilResult.EMPTY)
return return
} }
tryRenameDialog(player, event)
// Test if the event should bypass custom anvil. // Test if the event should bypass custom anvil.
if (DependencyManager.tryEventPreAnvilBypass(event, player)) { if (DependencyManager.tryEventPreAnvilBypass(event, player)) {
// even if we got bypassed we still want to set price // even if we got bypassed we still want to set price
AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, event.inventory.repairCost) AnvilXpUtil.setAnvilInvCost(inventory, view, player, AnvilCost(event.inventory.repairCost))
return return
} }
if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return
val result = getResult(view, inventory, player, first, second)
applyResult(event, player, result)
}
fun getResult(
view: InventoryView, //TODO use anvil view
inventory: AnvilInventory,
player: Player,
first: ItemStack?, second: ItemStack?) : AnvilResult
{
if(first == null)
return AnvilResult.EMPTY
// Test custom recipe // Test custom recipe
if (testCustomRecipe(event, inventory, player, first, second)) return var result: AnvilResult = testCustomRecipe(view, inventory, player, first, second)
if (!result.isEmpty())
return result
// Test rename lonely item // Test rename lonely item
val isAir = second.isAir val shouldTryRename = second.isAir
CustomAnvil.verboseLog("checking air in main logic: $isAir") CustomAnvil.verboseLog("checking air in main logic: $shouldTryRename")
if (isAir) { if (shouldTryRename)
doRenaming(event, inventory, player, first) return doRenaming(view, inventory, player, first)
return
}
// Test for merge // Test for merge
if (first.canMergeWith(second!!)) { if (first.canMergeWith(second!!))
doMerge(event, inventory, player, first, second) return doMerge(view, inventory, player, first, second)
return
}
// Test for unit repair // Test for unit repair
if (testUnitRepair(event, inventory, player, first, second)) return result = testUnitRepair(view, inventory, player, first, second)
if (!result.isEmpty())
return result
// Test for lore edit // Test for lore edit
if (testLoreEdit(event, inventory, player, first, second)) return result = testLoreEdit(player, first, second)
if (!result.isEmpty())
CustomAnvil.log("no anvil fuse type found") return result
event.result = null
return AnvilResult.EMPTY
} }
private fun tryRenameDialog( private fun tryRenameDialog(
player: HumanEntity, player: HumanEntity,
event: PrepareAnvilEvent event: PrepareAnvilEvent
) { ) {
if(!canUseRenameDialog(player)) return if(!ConfigOptions.canUseDialogRename(player)) return
AnvilRenameDialogUtil.anvilRenameDialog.tryShowDialog(player, event) AnvilRenameDialogUtil.anvilRenameDialog.tryShowDialog(player, event)
} }
private fun canUseRenameDialog(player: HumanEntity): Boolean {
if(!ConfigOptions.doRenameDialog || !AnvilRenameDialogUtil.anvilRenameDialog.canSendDialog()) return false
if(ConfigOptions.doRenameDialogUsePermission && !player.hasPermission(RENAME_DIALOG_PERMISSION)) return false
return true
}
private fun processDialogPCD(it: ItemMeta, player: HumanEntity) {
val keepDialog = canUseRenameDialog(player) && ConfigOptions.shouldKeepRenameText
val pdc = it.persistentDataContainer
if(!keepDialog)
pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY)
else {
val text = AnvilRenameDialogUtil.anvilRenameDialog.currentText(player)
if(text == null || text.isBlank())
pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY)
else pdc.set(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY, PersistentDataType.STRING, text)
}
}
private fun isImmutable(item: ItemStack?): Boolean { private fun isImmutable(item: ItemStack?): Boolean {
if (item.isAir) return false if (item.isAir) return false
@ -180,221 +168,14 @@ class PrepareAnvilListener : Listener {
return false return false
} }
// return true if a custom recipe exist with these ingredients private fun applyResult(event: PrepareAnvilEvent, player: Player, result: AnvilResult) {
private fun testCustomRecipe( event.result = result.item
event: PrepareAnvilEvent, inventory: AnvilInventory,
player: HumanEntity,
first: ItemStack, second: ItemStack?
): Boolean {
val recipe = CustomRecipeUtil.getCustomRecipe(first, second)
CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}")
if (recipe == null) return false
val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, first, second) if(result.item == null) {
AnvilXpUtil.onNoResult(player, event.view)
val resultItem: ItemStack = DependencyManager.cloneItem(event, recipe.resultItem!!)
resultItem.amount *= amount
// Maybe add an option on custom craft to ignore/not ignore penalty ??
val xpCost = recipe.determineCost(amount, first, resultItem)
val levelCost =
if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost)
else AnvilXpUtil.calculateLevelForXp(xpCost)
val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.CUSTOM_CRAFT, levelCost)
if (finalResult == null) return false
event.result = finalResult.result
if (finalResult.result.isAir) return false
AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost, true)
return true
}
private fun doRenaming(
event: PrepareAnvilEvent, inventory: AnvilInventory,
player: HumanEntity, first: ItemStack
) {
val resultItem = DependencyManager.cloneItem(event, first)
var anvilCost = handleRename(resultItem, inventory, player)
// Test/stop if nothing changed.
if (first == resultItem) {
CustomAnvil.log("no right item, But input is same as output")
event.result = null
return return
} }
AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, result.cost, result.ignoreXpRules)
anvilCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY)
val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.RENAME_ONLY, anvilCost)
if (finalResult == null) return
event.result = finalResult.result
if (finalResult.result.isAir) return
AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost)
} }
private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int {
// Can be null
var renameText = ChatColor.stripColor(inventory.renameText)
var sumCost = 0
var useColor = false
if (ConfigOptions.renameColorPossible && renameText != null) {
val component = AnvilColorUtil.handleColor(
renameText,
AnvilColorUtil.renamePermission(player))
if (component != null) {
renameText = MiniMessageUtil.legacy_mm.serialize(component)
sumCost += ConfigOptions.useOfColorCost
useColor = true
}
}
// Rename item and add renaming cost
resultItem.itemMeta?.let {
val hasDisplayName = it.hasDisplayName()
val displayName = if (!hasDisplayName) null
else if (useColor) it.displayName
else ChatColor.stripColor(it.displayName)
if (!displayName.contentEquals(renameText) && !(displayName == null &&
renameText == "" ||
//TODO on recent paper check effective name instead
renameText == CasedStringUtil.snakeToUpperSpacedCase(resultItem.type.name.lowercase())
)) {
it.setDisplayName(renameText)
processDialogPCD(it, player)
resultItem.itemMeta = it
sumCost += ConfigOptions.itemRenameCost
}
return sumCost
}
return 0
}
private fun doMerge(
event: PrepareAnvilEvent, inventory: AnvilInventory,
player: HumanEntity,
first: ItemStack, second: ItemStack
) {
val newEnchants = first.findEnchantments()
.combineWith(second.findEnchantments(), first, player)
var hasChanged = !isIdentical(first.findEnchantments(), newEnchants)
val resultItem = DependencyManager.cloneItem(event, first)
var anvilCost = 0
if(hasChanged){
resultItem.setEnchantmentsUnsafe(newEnchants)
// Calculate enchantment cost
anvilCost+= AnvilXpUtil.getRightValues(second, resultItem)
}
// Calculate repair cost
if (!first.isEnchantedBook() && !second.isEnchantedBook()) {
// we only need to be concerned with repair when neither item is a book
val repaired = resultItem.repairFrom(first, second)
anvilCost += if (repaired) ConfigOptions.itemRepairCost else 0
hasChanged = hasChanged || repaired
}
// Test/stop if nothing changed.
if (!hasChanged) {
CustomAnvil.log("Mergable with second, But input is same as output")
event.result = null
return
}
// As calculatePenalty edit result, we need to calculate penalty after checking equality
anvilCost += AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE)
// Calculate rename cost
anvilCost += handleRename(resultItem, inventory, player)
// Finally, we set result
val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.MERGE, anvilCost)
if (finalResult == null) return
event.result = finalResult.result
if (finalResult.result.isAir) return
AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost)
}
private fun isIdentical(
firstEnchants: MutableMap<CAEnchantment, Int>,
resultEnchants: MutableMap<CAEnchantment, Int>
): Boolean {
if(firstEnchants.size != resultEnchants.size) return false
for (entry in resultEnchants) {
if(firstEnchants.getOrDefault(entry.key, entry.value-1) != entry.value) return false
}
return true
}
// return true if there is a valid unit repair with these ingredients
private fun testUnitRepair(
event: PrepareAnvilEvent, inventory: AnvilInventory, player: HumanEntity,
first: ItemStack, second: ItemStack
): Boolean {
val unitRepairAmount = first.getRepair(second) ?: return false
val resultItem = DependencyManager.cloneItem(event, first)
var anvilCost = handleRename(resultItem, inventory, player)
val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount)
if (repairAmount > 0) {
anvilCost += repairAmount * ConfigOptions.unitRepairCost
}
// We do not care about right item penalty for unit repair
anvilCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR)
// Test/stop if nothing changed.
if (first == resultItem) {
CustomAnvil.log("unit repair, But input is same as output")
event.result = null
return true
}
val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.UNIT_REPAIR, anvilCost)
if (finalResult == null) return false
event.result = finalResult.result
if (finalResult.result.isAir) return false
AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost)
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.isAir || 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

@ -3,11 +3,11 @@ package xyz.alexcrea.cuanvil.recipe
import io.delilaheve.CustomAnvil import io.delilaheve.CustomAnvil
import org.bukkit.configuration.ConfigurationSection import org.bukkit.configuration.ConfigurationSection
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.anvil.AnvilUseType
import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant
import xyz.alexcrea.cuanvil.util.AnvilUseType
import xyz.alexcrea.cuanvil.util.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir
import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
class AnvilCustomRecipe( class AnvilCustomRecipe(
val name: String, val name: String,

View file

@ -1,11 +1,11 @@
package xyz.alexcrea.cuanvil.util package xyz.alexcrea.cuanvil.util.anvil
import io.delilaheve.util.ConfigOptions import io.delilaheve.util.ConfigOptions
import net.kyori.adventure.text.Component import net.kyori.adventure.text.Component
import org.bukkit.permissions.Permissible import org.bukkit.permissions.Permissible
import xyz.alexcrea.cuanvil.util.MiniMessageUtil
import java.util.regex.Matcher import java.util.regex.Matcher
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.text.indexOf
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

View file

@ -1,17 +1,19 @@
package xyz.alexcrea.cuanvil.util package xyz.alexcrea.cuanvil.util.anvil
import net.kyori.adventure.text.Component import net.kyori.adventure.text.Component
import org.bukkit.entity.HumanEntity import org.bukkit.entity.HumanEntity
import org.bukkit.inventory.ItemStack import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.BookMeta import org.bukkit.inventory.meta.BookMeta
import org.bukkit.permissions.Permissible import org.bukkit.permissions.Permissible
import xyz.alexcrea.cuanvil.anvil.AnvilCost
import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.LoreEditResult
import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore
import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentLore import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentLore
import xyz.alexcrea.cuanvil.util.MiniMessageUtil
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil
import xyz.alexcrea.cuanvil.util.config.LoreEditType import xyz.alexcrea.cuanvil.util.config.LoreEditType
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
object AnvilLoreEditUtil { object AnvilLoreEditUtil {
@ -31,7 +33,7 @@ object AnvilLoreEditUtil {
player: Permissible, player: Permissible,
first: ItemStack, first: ItemStack,
book: BookMeta, book: BookMeta,
xpCost: AtomicInteger cost: AnvilCost
): ItemStack? { ): ItemStack? {
if (!hasLoreEditByBookPermission(player)) return null if (!hasLoreEditByBookPermission(player)) return null
@ -42,8 +44,10 @@ object AnvilLoreEditUtil {
val page = book.pages[0] val page = book.pages[0]
val lines = ArrayList<String>(page.split("\n")) val lines = ArrayList<String>(page.split("\n"))
val outLines = ArrayList<Component>(lines.size) val outLines = ArrayList<Component>(lines.size)
val colorCost = colorLines(player, LoreEditType.APPEND_BOOK, val colorCost = colorLines(
lines, outLines) player, LoreEditType.APPEND_BOOK,
lines, outLines
)
lore.addAll(outLines) lore.addAll(outLines)
@ -53,14 +57,14 @@ object AnvilLoreEditUtil {
if (result == first) return null if (result == first) return null
// Handle xp // Handle xp
xpCost.addAndGet(colorCost) // Cost of using color cost.lore = colorCost // Cost of using color
xpCost.addAndGet(outLines.size * LoreEditType.APPEND_BOOK.perLineCost) // per line cost cost.lore += outLines.size * LoreEditType.APPEND_BOOK.perLineCost // per line cost
xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.APPEND_BOOK)) // Fixed cost and work penalty baseEditLoreXpCost(cost, first, result, LoreEditType.APPEND_BOOK) // Fixed cost and work penalty
return result return result
} }
fun handleLoreRemoveByBook(player: Permissible, first: ItemStack, xpCost: AtomicInteger): ItemStack? { fun handleLoreRemoveByBook(player: Permissible, first: ItemStack, cost: AnvilCost): ItemStack? {
if (!hasLoreEditByBookPermission(player)) return null if (!hasLoreEditByBookPermission(player)) return null
// remove lore // remove lore
@ -78,9 +82,9 @@ object AnvilLoreEditUtil {
if (result == first) return null if (result == first) return null
// Handle xp // Handle xp
xpCost.addAndGet(uncolorCost) cost.lore = uncolorCost
xpCost.addAndGet(currentLore.size * LoreEditType.REMOVE_BOOK.perLineCost) cost.lore += currentLore.size * LoreEditType.REMOVE_BOOK.perLineCost
xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.REMOVE_BOOK)) baseEditLoreXpCost(cost, first, result, LoreEditType.REMOVE_BOOK)
return result return result
} }
@ -116,12 +120,17 @@ object AnvilLoreEditUtil {
return null return null
} }
fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack, xpCost: AtomicInteger): ItemStack? { fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack): LoreEditResult {
val isAppend = bookLoreEditIsAppend(first, second) ?: return null val isAppend = bookLoreEditIsAppend(first, second) ?: return LoreEditResult.EMPTY
val type = if (isAppend) LoreEditType.APPEND_BOOK else LoreEditType.REMOVE_BOOK
val meta = second.itemMeta as BookMeta val meta = second.itemMeta as BookMeta
return if (isAppend) handleLoreAppendByBook(player, first, meta, xpCost) val cost = AnvilCost()
else handleLoreRemoveByBook(player, first, xpCost) val item = if (isAppend)
handleLoreAppendByBook(player, first, meta, cost)
else handleLoreRemoveByBook(player, first, cost)
return LoreEditResult(item, cost, type)
} }
// Return true if appended, false if removed, null if neither // Return true if appended, false if removed, null if neither
@ -147,7 +156,7 @@ object AnvilLoreEditUtil {
player: Permissible, player: Permissible,
first: ItemStack, first: ItemStack,
second: ItemStack, second: ItemStack,
xpCost: AtomicInteger cost: AnvilCost
): ItemStack? { ): ItemStack? {
if (!hasLoreEditByPaperPermission(player)) return null if (!hasLoreEditByPaperPermission(player)) return null
@ -159,9 +168,11 @@ object AnvilLoreEditUtil {
// A bit overdone to color 1 line but hey // A bit overdone to color 1 line but hey
val outList = ArrayList<Component>(1) val outList = ArrayList<Component>(1)
val colorCost = colorLines(player, LoreEditType.APPEND_PAPER, val colorCost = colorLines(
player, LoreEditType.APPEND_PAPER,
Collections.singletonList(second.itemMeta!!.displayName), Collections.singletonList(second.itemMeta!!.displayName),
outList) outList
)
val line = outList[0] val line = outList[0]
if (appendEnd) if (appendEnd)
@ -175,13 +186,13 @@ object AnvilLoreEditUtil {
if (result == first) return null if (result == first) return null
// Handle xp // Handle xp
xpCost.addAndGet(colorCost) cost.lore = colorCost
xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.APPEND_PAPER)) baseEditLoreXpCost(cost, first, result, LoreEditType.APPEND_PAPER)
return result return result
} }
fun handleLoreRemoveByPaper(player: Permissible, first: ItemStack, xpCost: AtomicInteger): ItemStack? { fun handleLoreRemoveByPaper(player: Permissible, first: ItemStack, cost: AnvilCost): ItemStack? {
if (!hasLoreEditByPaperPermission(player)) return null if (!hasLoreEditByPaperPermission(player)) return null
// remove lore line // remove lore line
@ -213,8 +224,8 @@ object AnvilLoreEditUtil {
val uncolorCost = uncolorLine(player, line, LoreEditType.REMOVE_PAPER) val uncolorCost = uncolorLine(player, line, LoreEditType.REMOVE_PAPER)
// Handle other xp // Handle other xp
xpCost.addAndGet(uncolorCost) cost.lore = uncolorCost
xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.REMOVE_PAPER)) baseEditLoreXpCost(cost, first, result, LoreEditType.REMOVE_PAPER)
return result return result
} }
@ -222,33 +233,39 @@ object AnvilLoreEditUtil {
fun tryLoreEditByPaper( fun tryLoreEditByPaper(
player: HumanEntity, player: HumanEntity,
first: ItemStack, first: ItemStack,
second: ItemStack, second: ItemStack
xpCost: AtomicInteger ): LoreEditResult {
): ItemStack? { val isAppend = paperLoreEditIsAppend(first, second) ?: return LoreEditResult.EMPTY
val isAppend = paperLoreEditIsAppend(first, second) ?: return null val type = if (isAppend) LoreEditType.APPEND_BOOK else LoreEditType.REMOVE_BOOK
return if (isAppend) handleLoreAppendByPaper(player, first, second, xpCost) val cost = AnvilCost()
else handleLoreRemoveByPaper(player, first, xpCost) val item = if (isAppend)
handleLoreAppendByPaper(player, first, second, cost)
else handleLoreRemoveByPaper(player, first, cost)
return LoreEditResult(item, cost, type)
} }
private fun baseEditLoreXpCost( private fun baseEditLoreXpCost(
cost: AnvilCost,
first: ItemStack, first: ItemStack,
result: ItemStack, result: ItemStack,
editType: LoreEditType editType: LoreEditType
): Int { ) {
var xpCost = editType.fixedCost cost.lore += editType.fixedCost
xpCost += AnvilXpUtil.calculatePenalty(first, null, result, editType.useType) cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, result, editType.useType)
return xpCost
} }
fun colorPermission(player: Permissible, editType: LoreEditType): AnvilColorUtil.ColorPermissions { fun colorPermission(player: Permissible, editType: LoreEditType): AnvilColorUtil.ColorPermissions {
return AnvilColorUtil.calculatePermissions(player, return AnvilColorUtil.calculatePermissions(
player,
false, false,
editType.allowColorCode, editType.allowColorCode,
editType.allowHexColor, editType.allowHexColor,
editType.allowMinimessage, editType.allowMinimessage,
AnvilColorUtil.ColorUseType.LORE_EDIT) AnvilColorUtil.ColorUseType.LORE_EDIT
)
} }
private fun colorLine(line: String, permission: AnvilColorUtil.ColorPermissions): Component? { private fun colorLine(line: String, permission: AnvilColorUtil.ColorPermissions): Component? {
@ -258,8 +275,10 @@ object AnvilLoreEditUtil {
) )
} }
private fun colorLines(player: Permissible, editType: LoreEditType, private fun colorLines(
lines: List<String>, outLines: MutableList<Component>): Int { player: Permissible, editType: LoreEditType,
lines: List<String>, outLines: MutableList<Component>
): Int {
val permission = colorPermission(player, editType) val permission = colorPermission(player, editType)
val colorCost = editType.useColorCost val colorCost = editType.useColorCost
@ -286,7 +305,7 @@ object AnvilLoreEditUtil {
// Now handle color of each lines // Now handle color of each lines
var hasUndidColor = false var hasUndidColor = false
for ((index, line) in lines.withIndex()) { for ((index, line) in lines.withIndex()) {
if(line == null){ if (line == null) {
lines[index] = null lines[index] = null
continue continue
} }
@ -301,7 +320,7 @@ object AnvilLoreEditUtil {
hasUndidColor = true hasUndidColor = true
result = clearedLine result = clearedLine
} else { } else {
result = MiniMessageUtil.plain_text_mm.serialize(line) result = MiniMessageUtil.plain_text_mm.serialize(line)
} }
lines[index] = MiniMessageUtil.plain_text_mm.deserialize(result) lines[index] = MiniMessageUtil.plain_text_mm.deserialize(result)
@ -330,7 +349,7 @@ object AnvilLoreEditUtil {
var hasUndidColor = false var hasUndidColor = false
val result: String val result: String
if(clearedLine != null){ if (clearedLine != null) {
hasUndidColor = true hasUndidColor = true
result = clearedLine result = clearedLine
} else { } else {

View file

@ -1,4 +1,4 @@
package xyz.alexcrea.cuanvil.util package xyz.alexcrea.cuanvil.util.anvil
import io.delilaheve.util.ConfigOptions import io.delilaheve.util.ConfigOptions

View file

@ -1,4 +1,4 @@
package xyz.alexcrea.cuanvil.util package xyz.alexcrea.cuanvil.util.anvil
import io.delilaheve.CustomAnvil import io.delilaheve.CustomAnvil
import io.delilaheve.util.ConfigOptions import io.delilaheve.util.ConfigOptions
@ -14,25 +14,48 @@ import org.bukkit.inventory.InventoryView
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
import xyz.alexcrea.cuanvil.anvil.AnvilCost
import xyz.alexcrea.cuanvil.anvil.AnvilUseType
import xyz.alexcrea.cuanvil.config.ConfigHolder import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.dependency.DependencyManager import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager
import xyz.alexcrea.cuanvil.group.ConflictType import xyz.alexcrea.cuanvil.group.ConflictType
import xyz.alexcrea.cuanvil.util.AnvilTitleUtil
import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil
import kotlin.math.min import kotlin.math.min
object AnvilXpUtil { object AnvilXpUtil {
const val EXCLUSIVE_PENALTY_PREFIX = "repair_cost" const val EXCLUSIVE_PENALTY_PREFIX = "repair_cost"
/**
* Display the required cost (either as xp or as )
*/
fun setAnvilInvCost(
inventory: AnvilInventory,
view: InventoryView,
player: Player,
cost: AnvilCost,
ignoreRules: Boolean = false
) {
if (ConfigOptions.shouldUseMoney(player)) {
cost.isMonetary = true
setAnvilPrice(inventory, view, player, cost)
} else
setAnvilInvXp(inventory, view, player, cost.asXpCost(), ignoreRules)
}
/** /**
* Display xp needed for the work on the anvil inventory * Display xp needed for the work on the anvil inventory
*/ */
fun setAnvilInvXp( private fun setAnvilInvXp(
inventory: AnvilInventory, inventory: AnvilInventory,
view: InventoryView, view: InventoryView,
player: HumanEntity, player: HumanEntity,
anvilCost: Int, anvilCost: Int,
ignoreRules: Boolean = false ignoreRules: Boolean = false
) { ) {
// Test repair cost limit // Test repair cost limit
val finalAnvilCost = if ( val finalAnvilCost = if (
!ignoreRules && !ignoreRules &&
@ -78,7 +101,51 @@ object AnvilXpUtil {
} }
player.updateInventory() player.updateInventory()
}
}
/**
* Display monetary cost needed for the work on the anvil inventory
*/
private fun setAnvilPrice(
inventory: AnvilInventory,
view: InventoryView,
player: Player,
cost: AnvilCost,
) {
val finalCost = cost.asMonetaryCost()
val has = player.gameMode == GameMode.CREATIVE ||
EconomyManager.economy!!.has(player, finalCost)
val text = "Cost: " + (if (has) "§2" else "§4") +
EconomyManager.economy!!.format(finalCost)
AnvilTitleUtil.rename(
view, text,
player,
AnvilRenameDialogUtil.anvilRenameDialog,
CustomAnvil.instance
)
clearAnvilXpCost(inventory, view, player)
}
private fun clearAnvilXpCost(
inventory: AnvilInventory,
view: InventoryView,
player: HumanEntity,
) {
// TODO for 2.x.x use anvil view & set directly there
inventory.repairCost = 0
// retry after a tick
DependencyManager.scheduler.scheduleOnEntity(
CustomAnvil.instance, player
) {
inventory.repairCost = 0
if (player !is Player) return@scheduleOnEntity
player.updateInventory()
} }
} }
@ -128,6 +195,16 @@ object AnvilXpUtil {
return resultSum return resultSum
} }
fun onNoResult(player: HumanEntity, view: InventoryView) {
if (ConfigOptions.shouldUseMoney(player))
AnvilTitleUtil.rename(
view, "Repair & Name",
player,
AnvilRenameDialogUtil.anvilRenameDialog,
CustomAnvil.instance
)
}
private fun exclusivePenaltyKey(useType: AnvilUseType): NamespacedKey { private fun exclusivePenaltyKey(useType: AnvilUseType): NamespacedKey {
return NamespacedKey(CustomAnvil.instance, "${EXCLUSIVE_PENALTY_PREFIX}_${useType.typeName}") return NamespacedKey(CustomAnvil.instance, "${EXCLUSIVE_PENALTY_PREFIX}_${useType.typeName}")
} }
@ -159,10 +236,8 @@ object AnvilXpUtil {
* Function to calculate right enchantment values * Function to calculate right enchantment values
* it include enchantment placed on final item and conflicting enchantment * it include enchantment placed on final item and conflicting enchantment
*/ */
fun getRightValues(right: ItemStack, result: ItemStack): Int { fun getRightValues(right: ItemStack, result: ItemStack, cost: AnvilCost) {
// Calculate right value and illegal enchant penalty // Calculate right value and illegal enchant penalty
var illegalPenalty = 0
var rightValue = 0
val rightIsFormBook = right.isEnchantedBook() val rightIsFormBook = right.isEnchantedBook()
val resultEnchs = result.findEnchantments() val resultEnchs = result.findEnchantments()
@ -180,7 +255,7 @@ object AnvilXpUtil {
resultEnchsKeys.remove(enchantment.key) resultEnchsKeys.remove(enchantment.key)
if (ConflictType.ENCHANTMENT_CONFLICT == conflictType) { if (ConflictType.ENCHANTMENT_CONFLICT == conflictType) {
illegalPenalty += ConfigOptions.sacrificeIllegalCost cost.illegalPenalty += ConfigOptions.sacrificeIllegalCost
CustomAnvil.verboseLog("Big conflict. Adding illegal price penalty") CustomAnvil.verboseLog("Big conflict. Adding illegal price penalty")
} }
continue continue
@ -191,16 +266,14 @@ object AnvilXpUtil {
val enchantmentMultiplier = ConfigOptions.enchantmentValue(enchantment.key, rightIsFormBook) val enchantmentMultiplier = ConfigOptions.enchantmentValue(enchantment.key, rightIsFormBook)
val value = resultLevel * enchantmentMultiplier val value = resultLevel * enchantmentMultiplier
CustomAnvil.log("Value for ${enchantment.key.enchantmentName} level ${enchantment.value} is $value ($resultLevel * $enchantmentMultiplier)") CustomAnvil.log("Value for ${enchantment.key.enchantmentName} level ${enchantment.value} is $value ($resultLevel * $enchantmentMultiplier)")
rightValue += value cost.enchantment += value
} }
CustomAnvil.log( CustomAnvil.log(
"Calculated right values: " + "Calculated right values: " +
"rightValue: $rightValue, " + "rightValue: ${cost.enchantment}, " +
"illegalPenalty: $illegalPenalty" "illegalPenalty: ${cost.illegalPenalty}"
) )
return rightValue + illegalPenalty
} }
/** /**

View file

@ -1,6 +1,6 @@
package xyz.alexcrea.cuanvil.util.config package xyz.alexcrea.cuanvil.util.config
import xyz.alexcrea.cuanvil.util.AnvilUseType import xyz.alexcrea.cuanvil.anvil.AnvilUseType
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_COLOR_CODE 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.ALLOW_HEX_COLOR
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_MINIMESSAGE import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil.ALLOW_MINIMESSAGE
@ -18,20 +18,23 @@ import xyz.alexcrea.cuanvil.config.ConfigHolder.DEFAULT_CONFIG as CONFIG
enum class LoreEditType( enum class LoreEditType(
val rootPath: String, val rootPath: String,
val useType: AnvilUseType, val useType: AnvilUseType,
val isBook: Boolean,
val isAppend: Boolean, val isAppend: Boolean,
val isMultiLine: Boolean, val isMultiLine: Boolean,
) { ) {
APPEND_BOOK(AnvilUseType.LORE_EDIT_BOOK_APPEND, true, true), APPEND_BOOK(AnvilUseType.LORE_EDIT_BOOK_APPEND, true, true, true),
REMOVE_BOOK(AnvilUseType.LORE_EDIT_BOOK_REMOVE, false, true), REMOVE_BOOK(AnvilUseType.LORE_EDIT_BOOK_REMOVE, true, false, true),
APPEND_PAPER(AnvilUseType.LORE_EDIT_PAPER_APPEND, true, false), APPEND_PAPER(AnvilUseType.LORE_EDIT_PAPER_APPEND, false, true, false),
REMOVE_PAPER(AnvilUseType.LORE_EDIT_PAPER_REMOVE, false, false), REMOVE_PAPER(AnvilUseType.LORE_EDIT_PAPER_REMOVE, false, false, false),
; ;
constructor( constructor(
useType: AnvilUseType, useType: AnvilUseType,
isPaper: Boolean,
isAppend: Boolean, isAppend: Boolean,
isMultiLine: Boolean, isMultiLine: Boolean,
) : this(useType.path, useType, isAppend, isMultiLine) ) : this(useType.path, useType,
isPaper, isAppend, isMultiLine)
/** /**
* If this edit type is enabled * If this edit type is enabled

View file

@ -8,7 +8,7 @@ import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil
import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog
import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialogImpl import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialogImpl
import xyz.alexcrea.cuanvil.update.UpdateUtils import xyz.alexcrea.cuanvil.update.UpdateUtils
import xyz.alexcrea.cuanvil.util.AnvilColorUtil import xyz.alexcrea.cuanvil.util.anvil.AnvilColorUtil
object AnvilRenameDialogUtil { object AnvilRenameDialogUtil {
@ -47,5 +47,9 @@ object AnvilRenameDialogUtil {
return null return null
} }
override fun isOpenFor(player: HumanEntity): Boolean {
return false
}
} }
} }

View file

@ -432,6 +432,34 @@ lore_edit:
allow_hexadecimal_color: false allow_hexadecimal_color: false
allow_minimessage: true allow_minimessage: true
# Allow to replace the xp cost by a monetary cost
# If enabled it will not be bound to the experience level limits
#
# It also requires to enable dialog rename (set "enable_dialog_rename: false" a bit higher)
# If dialog rename permission is enabled and player do not have the permission merge will fall back to vanilla xp cost
#
# If you are using custom craft I recommend using Linear Xp Cost with Exact Linear Xp as normal Xp Cost will act "weird"
# But Linear Xp will act as 1$ time global multiplier. In other word: like you expect
#
# As this feature require dialog rename, it can only be enabled starting with paper 1.21.6 and later
monetary_cost:
enabled: false
# If using vault unlocked this allow to specify what currency should be used for anvil usage
# default being the default currency
currency: default
# multiply the anvil cost by a value to allow to have price a big bigger than like 40
multipliers:
# global multipliers. all usage type will be multiplied by this value
global: 1.0
# usage specific type. it will only apply for specific xp "reason"
enchantment: 1.0 # related to enchantments level
repair: 1.0 # for repairing via unit repair (per unit)
rename: 1.0 # for renaming the item
lore_edit: 1.0 # for changing the lore of the item (only if lore edit is enabled)
illegal_penalty: 1.0 # for trying to combine illegal enchantment
work_penalty: 1.0 # for work penalty (aka use penalty)
recipe: 1.0 # for custom anvil recipe cost
# Whether to show debug logging # Whether to show debug logging
debug_log: false debug_log: false

View file

@ -195,7 +195,7 @@ public class AnvilFuseTestUtil {
simulateClick(anvil, player, data.expectedResult()); simulateClick(anvil, player, data.expectedResult());
// Should have similated the click // Should have simulated the click
assertEqual(data.leftItem(), anvil.getFirstItem()); assertEqual(data.leftItem(), anvil.getFirstItem());
assertEqual(data.rightItem(), anvil.getSecondItem()); assertEqual(data.rightItem(), anvil.getSecondItem());
assertEqual(data.resultSlotItem(), anvil.getResult()); assertEqual(data.resultSlotItem(), anvil.getResult());
@ -260,7 +260,7 @@ public class AnvilFuseTestUtil {
} }
public static boolean isAir(@Nullable ItemStack item) { public static boolean isAir(@Nullable ItemStack item) {
return item == null || item.isEmpty(); return item == null || item.isEmpty() || item.getAmount() == 0;
} }
public static void assertPriceEqual(Integer expectedPrice, int price) { public static void assertPriceEqual(Integer expectedPrice, int price) {