Initial commit

This commit is contained in:
DelilahEve 2022-08-20 21:14:21 -04:00
commit e53f9cc88c
14 changed files with 939 additions and 0 deletions

45
build.gradle.kts Normal file
View file

@ -0,0 +1,45 @@
plugins {
kotlin("jvm") version "1.6.21"
java
}
group = "io.delilaheve"
version = "1.0.0"
repositories {
mavenCentral()
maven(url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
}
dependencies {
implementation(kotlin("stdlib"))
compileOnly("org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}
tasks.getByName<Test>("test") {
useJUnitPlatform()
}
// Fat-jar builder
val fatJar = tasks.register<Jar>("fatJar") {
manifest {
attributes.apply { put("Main-Class", "io.delilaheve.UnsafeEnchants") }
}
archiveFileName.set("${rootProject.name}-${version}.jar")
exclude("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA")
duplicatesStrategy = DuplicatesStrategy.WARN
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
with(tasks.jar.get() as CopySpec)
}
// Ensure fatJar and copyJar are run
tasks.getByName("build") {
dependsOn(fatJar)
}

1
gradle.properties Normal file
View file

@ -0,0 +1 @@
kotlin.code.style=official

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View file

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View file

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

2
settings.gradle.kts Normal file
View file

@ -0,0 +1,2 @@
rootProject.name = "UnsafeEnchants"

View file

@ -0,0 +1,88 @@
package io.delilaheve
import io.delilaheve.util.ConfigOptions
import io.delilaheve.util.EnchantmentUtil.calculateValue
import io.delilaheve.util.EnchantmentUtil.combineWith
import io.delilaheve.util.ItemUtil.canMergeWith
import io.delilaheve.util.ItemUtil.findEnchantments
import io.delilaheve.util.ItemUtil.isBook
import io.delilaheve.util.ItemUtil.setEnchantmentsUnsafe
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.Event
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.InventoryView
import org.bukkit.permissions.Permission
import kotlin.math.min
/**
* Listener for anvil events
*/
class AnvilEventListener : Listener {
companion object {
// Vanilla repair cost limit
private const val VANILLA_REPAIR_LIMIT = 40
// Anvil's output slot
private const val ANVIL_OUTPUT_SLOT = 2
}
// Permission node required for the plugin to take over enchantment combination
private val requirePermission: Permission
get() = Permission(UnsafeEnchants.unsafePermission)
/**
* Event handler logic for when an anvil contains items to be combined
*/
@EventHandler
fun anvilCombineCheck(event: PrepareAnvilEvent) {
val inventory = event.inventory
val first = inventory.getItem(0) ?: return
val second = inventory.getItem(1) ?: return
if (first.canMergeWith(second)) {
val firstEnchants = first.findEnchantments().toMutableMap()
val secondEnchants = second.findEnchantments().toMutableMap()
if (ConfigOptions.removeRepairLimit) {
inventory.maximumRepairCost = Int.MAX_VALUE
}
val newEnchants = firstEnchants.combineWith(secondEnchants)
val enchantsString = newEnchants.map { "${it.key.key} ${it.value}" }.joinToString(", ")
UnsafeEnchants.log("New enchants for this item: $enchantsString")
val resultItem = first.clone()
resultItem.setEnchantmentsUnsafe(newEnchants)
val firstValue = firstEnchants.calculateValue(first.isBook())
val secondValue = secondEnchants.calculateValue(second.isBook())
var repairCost = firstValue + secondValue
if (first.isBook() && second.isBook()) {
repairCost = firstEnchants.values.sum() + secondEnchants.values.sum()
}
if (ConfigOptions.limitRepairCost) {
repairCost = min(repairCost, VANILLA_REPAIR_LIMIT)
}
inventory.repairCost = repairCost
event.view.setProperty(InventoryView.Property.REPAIR_COST, repairCost)
event.result = resultItem
}
}
/**
* Event handler logic for when a player is trying to pull an item out of the anvil
*/
@EventHandler(ignoreCancelled = true)
fun anvilExtractionCheck(event: InventoryClickEvent) {
val player = event.whoClicked as? Player ?: return
if (player.hasPermission(requirePermission)) {
val inventory = event.inventory as? AnvilInventory ?: return
if (event.rawSlot != ANVIL_OUTPUT_SLOT) { return }
val output = inventory.getItem(2) ?: return
if (output.type == Material.AIR) { return }
if (player.level < inventory.repairCost) { return }
event.result = Event.Result.ALLOW
}
}
}

View file

@ -0,0 +1,40 @@
package io.delilaheve
import io.delilaheve.util.ConfigOptions
import org.bukkit.plugin.java.JavaPlugin
/**
* Bukkit/Spigot/Paper plugin to alter enchantment max
* levels and allow unsafe enchantment combinations
*/
class UnsafeEnchants : JavaPlugin() {
companion object {
// Permission string required to use the plugin's features
const val unsafePermission = "ue.unsafe"
// Current plugin instance
lateinit var instance: UnsafeEnchants
/**
* Logging handler
*/
fun log(message: String) {
if (ConfigOptions.debugLog) {
instance.logger.info(message)
}
}
}
/**
* Setup plugin for use
*/
override fun onEnable() {
instance = this
saveDefaultConfig()
server.pluginManager.registerEvents(
AnvilEventListener(),
this
)
}
}

View file

@ -0,0 +1,124 @@
package io.delilaheve.util
import io.delilaheve.UnsafeEnchants
import io.delilaheve.util.EnchantmentUtil.enchantmentName
import org.bukkit.enchantments.Enchantment
/**
* Config option accessors
*/
object ConfigOptions {
// Path for default enchantment limits
private const val DEFAULT_LIMIT_PATH = "default_limit"
// Path for allowing unsafe enchants
private const val ALLOW_UNSAFE_PATH = "allow_unsafe"
// Path for limiting repair cost
private const val LIMIT_REPAIR_COST = "limit_repair_cost"
// Path for removing repair cost limits
private const val REMOVE_REPAIR_LIMIT = "remove_repair_limit"
// Root path for enchantment limits
private const val ENCHANT_LIMIT_ROOT = "enchant_limits"
// Root path for enchantment values
private const val ENCHANT_VALUES_ROOT = "enchant_values"
// Keys for specific enchantment values
private const val KEY_BOOK = "book"
private const val KEY_ITEM = "item"
// Debug logging toggle path
private const val DEBUG_LOGGING = "debug_log"
// Default value for enchantment limits
private const val DEFAULT_ENCHANT_LIMIT = 10
// Default value for allowing unsafe enchantments
private const val DEFAULT_ALLOW_UNSAFE = true
// Default value for limiting repair cost
private const val DEFAULT_LIMIT_REPAIR = true
// Default for removing repair cost limits
private const val DEFAULT_REMOVE_LIMIT = false
// Valid range for an enchantment limit
private val ENCHANT_LIMIT_RANGE = 1..255
// Default value for an enchantment multiplier
private const val DEFAULT_ENCHANT_VALUE = 0
// Default value for debug logging
private const val DEFAULT_DEBUG_LOG = false
/**
* Default enchantment limit
*/
private val defaultEnchantLimit: Int
get() {
return UnsafeEnchants.instance
.config
.getInt(DEFAULT_LIMIT_PATH, DEFAULT_ENCHANT_LIMIT)
}
/**
* Whether to allow unsafe enchantments
*/
val allowUnsafe: Boolean
get() {
return UnsafeEnchants.instance
.config
.getBoolean(ALLOW_UNSAFE_PATH, DEFAULT_ALLOW_UNSAFE)
}
/**
* Whether to limit repair costs to the vanilla limit
*/
val limitRepairCost: Boolean
get() {
return UnsafeEnchants.instance
.config
.getBoolean(LIMIT_REPAIR_COST, DEFAULT_LIMIT_REPAIR)
}
/**
* Whether to remove repair cost limit
*/
val removeRepairLimit: Boolean
get() {
return UnsafeEnchants.instance
.config
.getBoolean(REMOVE_REPAIR_LIMIT, DEFAULT_REMOVE_LIMIT)
}
/**
* Whether to show debug logging
*/
val debugLog: Boolean
get() {
return UnsafeEnchants.instance
.config
.getBoolean(DEBUG_LOGGING, DEFAULT_DEBUG_LOG)
}
/**
* Get the given [enchantment]'s limit
*/
fun enchantLimit(enchantment: Enchantment): Int {
val path = "${ENCHANT_LIMIT_ROOT}.${enchantment.enchantmentName}"
return UnsafeEnchants.instance
.config
.getInt(path, defaultEnchantLimit)
.takeIf { it in ENCHANT_LIMIT_RANGE }
?: defaultEnchantLimit
}
/**
* Get the appropriate [enchantment]'s value dependent on whether
* it's source [isFromBook]
*/
fun enchantmentValue(
enchantment: Enchantment,
isFromBook: Boolean
): Int {
val typeKey = if (isFromBook) KEY_BOOK else KEY_ITEM
val path = "${ENCHANT_VALUES_ROOT}.${enchantment.enchantmentName}.$typeKey"
return UnsafeEnchants.instance
.config
.getInt(path, DEFAULT_ENCHANT_VALUE)
.takeIf { it >= DEFAULT_ENCHANT_VALUE }
?: DEFAULT_ENCHANT_VALUE
}
}

View file

@ -0,0 +1,64 @@
package io.delilaheve.util
import io.delilaheve.UnsafeEnchants
import org.bukkit.enchantments.Enchantment
import kotlin.math.max
import kotlin.math.min
/**
* Enchantment manipulation utilities
*/
object EnchantmentUtil {
val Enchantment.enchantmentName: String
get() = key.key
/**
* Combine 2 sets of enchantments according to our configuration
*/
fun MutableMap<Enchantment, Int>.combineWith(
other: MutableMap<Enchantment, Int>
) = mutableMapOf<Enchantment, Int>().apply {
putAll(this@combineWith)
other.forEach { (enchantment, level) ->
when {
// Enchantment not yet in result list
!containsKey(enchantment) -> {
// Add the enchantment if it doesn't have conflicts, or, if we're allowing unsafe enchantments
if (!keys.any { enchantment.conflictsWith(it) } || ConfigOptions.allowUnsafe) {
this[enchantment] = level
}
}
// Enchantment already in result list...
else -> when {
// ... and they're not the same level
this[enchantment] != other[enchantment] -> {
val newLevel = max(this[enchantment] ?: 0, other[enchantment] ?: 0)
// apply the greater of the two if non-zero
if (newLevel > 0) { this[enchantment] = newLevel }
}
// ... and they're the same level
else -> {
// try to increase the enchantment level by 1
var newLevel = this[enchantment]?.plus(1) ?: 0
val maxLevel = ConfigOptions.enchantLimit(enchantment)
newLevel = min(newLevel, maxLevel)
if (newLevel > 0) { this[enchantment] = newLevel }
}
}
}
}
}
/**
* Calculate the value of a set of enchantments
*/
fun Map<Enchantment, Int>.calculateValue(
fromBook: Boolean
) = entries.sumOf { (enchantment, level) ->
val enchantmentMultiplier = ConfigOptions.enchantmentValue(enchantment, fromBook)
val value = level * enchantmentMultiplier
UnsafeEnchants.log("Value for ${enchantment.enchantmentName} is $value")
value
}
}

View file

@ -0,0 +1,83 @@
package io.delilaheve.util
import io.delilaheve.UnsafeEnchants
import org.bukkit.Material.BOOK
import org.bukkit.Material.ENCHANTED_BOOK
import org.bukkit.enchantments.Enchantment
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.EnchantmentStorageMeta
/**
* Item manipulation utilities
*/
object ItemUtil {
/**
* Check if this [ItemStack] is a [BOOK] or [ENCHANTED_BOOK]
*/
fun ItemStack.isBook() = type in listOf(BOOK, ENCHANTED_BOOK)
/**
* Check if this [ItemStack] is an [ENCHANTED_BOOK]
*/
private fun ItemStack.isEnchantedBook() = type == ENCHANTED_BOOK
/**
* Determine if this [ItemStack] can hold enchants, this should be sufficient for
* detecting if an item is a tool/armour/etc... and not a carrot/potato/etc...
*/
private fun ItemStack.canHoldEnchants() = Enchantment.values()
.any { it.canEnchantItem(this) }
/**
* Find the enchantment map for this [ItemStack] and return it as a [MutableMap]
*/
fun ItemStack.findEnchantments() = if (isBook()) {
(itemMeta as? EnchantmentStorageMeta)?.storedEnchants ?: emptyMap()
} else {
itemMeta?.enchants ?: emptyMap()
}
/**
* Apply an [enchantments] map to this [ItemStack]
*/
fun ItemStack.setEnchantmentsUnsafe(enchantments: Map<Enchantment, Int>) {
if (isBook()) {
/* For some god-forsaken reason, item meta is not mutable
* so, we have to get the instance, modify it, then set it
* back to the item... #BecauseMinecraft */
val bookMeta = (itemMeta as? EnchantmentStorageMeta)
bookMeta?.replaceEnchants(enchantments)
itemMeta = bookMeta
} else {
itemMeta?.enchants?.forEach { (enchant, _) ->
removeEnchantment(enchant)
}
addUnsafeEnchantments(enchantments)
}
}
/**
* Apply an [enchantments] map to this book
*/
private fun EnchantmentStorageMeta.replaceEnchants(
enchantments: Map<Enchantment, Int>
) {
storedEnchants.forEach { (enchant, _) ->
removeStoredEnchant(enchant)
}
enchantments.forEach { (enchant, level) ->
val added = addStoredEnchant(enchant, level, true)
UnsafeEnchants.log("${enchant.key} added to item? $added")
}
}
/**
* Check that this [ItemStack] can merge with the [other]
*
* The two items should either be the same type, or, the [other] is a book
*/
fun ItemStack.canMergeWith(
other: ItemStack
) = type == other.type || (canHoldEnchants() && other.isEnchantedBook())
}

View file

@ -0,0 +1,202 @@
# Default limit to apply to any enchants missing from override_limits
#
# Valid range of 1 - 255
default_limit: 10
# Whether enchants should be combined without regard for conflicts by default
#
# This setting will override permissions, if a player has ue.unsafe but this is false
# they will be unable to combine conflicting enchantments
#
# i.e. Protection and Blast Protection can be on the same piece of armour
allow_unsafe: true
# Whether all anvil actions should be capped to the vanilla repair limit (40 levels)
limit_repair_cost: true
# Whether the anvil's repair limit should be removed entirely
#
# The anvil will still visually display "too expensive" however the action will be completable
remove_repair_limit: false
# Override limits for specific enchants
#
# Enchantments not listed here will use the value of default_limit
#
# Overrides provided default to 1 in vanilla and won't change effect with extra levels
# with exceptions to this rule having their own comment
#
# Valid range of 1 - 255 for each enchantment
enchant_limits:
aqua_affinity: 1
binding_curse: 1
channeling: 1
flame: 1
infinity: 1
mending: 1
multishot: 1
silk_touch: 1
vanishing_curse: 1
depth_strider: 3 # anything more than 3 is treated as 3 by the game
# bane_of_arthropods: 1
# blast_protection: 1
# efficiency: 1
# feather_falling: 1
# fire_aspect: 1
# fire_protection: 1
# fortune: 1
# frost_walker: 1
# impaling: 1
# knockback: 1
# looting: 1
# loyalty: 1
# luck_of_the_sea: 1
# lure: 1
# piercing: 1
# power: 1
# projectile_protection: 1
# protection: 1
# punch: 1
# quick_charge: 1
# respiration: 1
# riptide: 1
# sharpness: 1
# smite: 1
# soul_speed: 1
# sweeping: 1
# swift_sneak: 1
# thorns: 1
# unbreaking: 1
# Multipliers used to calculate the enchantment's value in repair/combining
#
# Values here are pulled from the fandom wiki:
# https://minecraft.fandom.com/wiki/Anvil_mechanics#Costs_for_combining_enchantments
#
# If an enchantment is missing values here, or is less than 0, it will default to 0
#
# Calculated as: [Enchantment lvl] * [multiplier]
#
# With default values protection 4 would have a value of 4 when
# coming from either a book (4 * 1) or an item (4 * 1)
enchant_values:
aqua_affinity:
item: 4
book: 2
bane_of_arthropods:
item: 2
book: 1
binding_curse:
item: 8
book: 4
blast_protection:
item: 4
book: 2
channeling:
item: 8
book: 4
depth_strider:
item: 4
book: 2
efficiency:
item: 1
book: 1
flame:
item: 4
book: 2
feather_falling:
item: 2
book: 1
fire_aspect:
item: 4
book: 2
fire_protection:
item: 2
book: 1
fortune:
item: 4
book: 2
frost_walker:
item: 4
book: 2
impaling:
item: 4
book: 2
infinity:
item: 8
book: 4
knockback:
item: 2
book: 1
looting:
item: 4
book: 2
loyalty:
item: 1
book: 1
luck_of_the_sea:
item: 4
book: 2
lure:
item: 4
book: 2
mending:
item: 4
book: 2
multishot:
item: 4
book: 2
piercing:
item: 1
book: 1
power:
item: 1
book: 1
projectile_protection:
item: 2
book: 1
protection:
item: 1
book: 1
punch:
item: 4
book: 2
quick_charge:
item: 2
book: 1
respiration:
item: 4
book: 2
riptide:
item: 4
book: 2
silk_touch:
item: 8
book: 4
sharpness:
item: 1
book: 1
smite:
item: 2
book: 1
soul_speed:
item: 8
book: 4
swift_sneak:
item: 8
book: 4
sweeping:
item: 4
book: 2
thorns:
item: 8
book: 4
unbreaking:
item: 2
book: 1
vanishing_curse:
item: 8
book: 4
# Whether to show debug logging
debug_log: false

View file

@ -0,0 +1,11 @@
main: io.delilaheve.UnsafeEnchants
name: UnsafeEnchants
version: 1.0.0
description: Allow all enchants to be combined
api-version: 1.18
load: POSTWORLD
author: DelilahEve
permissions:
ue.unsafe:
default: true
description: Allow player to combine "unsafe" enchants