diff --git a/.gitignore b/.gitignore index 81fb6b4..fa79b2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ # Ignore Gradle project-specific cache directory -.gradle +.gradle/ # Ignore Gradle build output directory build .vscode -bin \ No newline at end of file +bin + +.idea/ +run/ \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 2967995..cef5c6f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,13 +1,8 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * This is a general purpose Gradle build. - * To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.8/samples - */ - plugins { `java-library` - id("io.papermc.paperweight.userdev") version "1.7.1" + alias { libs.plugins.paperweight } + alias { libs.plugins.runPaper } + alias { libs.plugins.shadow } } repositories { @@ -20,12 +15,23 @@ repositories { } dependencies { - compileOnly("com.mojang:brigadier:1.0.18") - implementation("commons-io:commons-io:2.6") - paperweight.paperDevBundle("1.21-R0.1-SNAPSHOT") - implementation("net.kyori:adventure-text-minimessage:4.17.0") + paperweight.paperDevBundle(libs.versions.paper) + compileOnly(libs.brigadier) + implementation(libs.commonsIo) + implementation(libs.miniMessage) + compileOnly(libs.lombok) + annotationProcessor(libs.lombok) + implementation(libs.jackson) } java { toolchain.languageVersion.set(JavaLanguageVersion.of(21)) +} + +tasks.shadowJar { + dependencies { + include(dependency("tools.jackson.core:jackson-databind")) + include(dependency("tools.jackson.core:jackson-core")) + include(dependency("com.fasterxml.jackson.core:jackson-annotations")) + } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ac3234..afa616d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,2 +1,24 @@ # This file was generated by the Gradle 'init' task. # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format +[versions] +paper = "1.21.11-R0.1-SNAPSHOT" +paperweight = "2.0.0-beta.18" +brigadier = "1.0.500" +runPaper = "3.0.0-beta.1" +lombok = "1.18.42" +adventure = "5.1.1" +commonsIo = "2.22.0" +jackson = "3.1.3" +shadow = "9.3.0" + +[libraries] +brigadier = {group = "com.mojang", name = "brigadier", version.ref = "brigadier"} +lombok = {group = "org.projectlombok", name = "lombok", version.ref = "lombok"} +miniMessage = {group = "net.kyori", name = "adventure-text-minimessage", version.ref = "adventure"} +commonsIo = {group = "commons-io", name = "commons-io", version.ref = "commonsIo"} +jackson = {group = "tools.jackson.core", name = "jackson-databind", version.ref = "jackson"} + +[plugins] +paperweight = { id = "io.papermc.paperweight.userdev", version.ref = "paperweight"} +runPaper = { id = "xyz.jpenilla.run-paper", version.ref="runPaper"} +shadow = { id = "com.gradleup.shadow", version.ref="shadow" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e644113..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a441313..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b740cf1..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 25da30d..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -68,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/java/re/sylfa/itemcreator/ItemCreator.java b/src/main/java/re/sylfa/itemcreator/ItemCreator.java index f621fb8..ac9450a 100644 --- a/src/main/java/re/sylfa/itemcreator/ItemCreator.java +++ b/src/main/java/re/sylfa/itemcreator/ItemCreator.java @@ -1,81 +1,139 @@ package re.sylfa.itemcreator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - +import com.destroystokyo.paper.profile.ProfileProperty; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.datacomponent.item.Consumable; +import io.papermc.paper.datacomponent.item.CustomModelData; +import io.papermc.paper.datacomponent.item.DeathProtection; +import io.papermc.paper.datacomponent.item.Enchantable; +import io.papermc.paper.datacomponent.item.ItemAttributeModifiers; +import io.papermc.paper.datacomponent.item.ResolvableProfile; +import io.papermc.paper.datacomponent.item.attribute.AttributeModifierDisplay; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import lombok.Getter; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.MusicInstrument; import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.bukkit.inventory.meta.components.FoodComponent; +import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; +import org.bukkit.inventory.meta.components.ToolComponent; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; import org.bukkit.util.permissions.CommandPermissions; - -import io.papermc.paper.command.brigadier.Commands; -import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager; -import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import org.checkerframework.checker.nullness.qual.NonNull; import re.sylfa.itemcreator.commands.ItemCreatorCommand; +import re.sylfa.itemcreator.deserializers.components.AttributeModifiersDeserializer; +import re.sylfa.itemcreator.deserializers.components.ConsumableDeserializer; +import re.sylfa.itemcreator.deserializers.components.ConsumeEffectDeserializer; +import re.sylfa.itemcreator.deserializers.components.CustomModelDataDeserializer; +import re.sylfa.itemcreator.deserializers.components.DeathProtectionDeserializer; +import re.sylfa.itemcreator.deserializers.components.EnchantableDeserializer; +import re.sylfa.itemcreator.deserializers.components.EquippableDeserializer; +import re.sylfa.itemcreator.deserializers.components.FoodDeserializer; +import re.sylfa.itemcreator.deserializers.components.JukeboxPlayableDeserializer; +import re.sylfa.itemcreator.deserializers.components.MusicInstrumentDeserializer; +import re.sylfa.itemcreator.deserializers.components.PotionEffectDeserializer; +import re.sylfa.itemcreator.deserializers.components.PotionEffectTypeDeserializer; +import re.sylfa.itemcreator.deserializers.components.ProfileDeserializer; +import re.sylfa.itemcreator.deserializers.components.ToolDeserializer; +import re.sylfa.itemcreator.deserializers.components.ToolRuleDeserializer; +import re.sylfa.itemcreator.deserializers.types.ColorDeserializer; +import re.sylfa.itemcreator.deserializers.types.CustomItemDeserializer; +import re.sylfa.itemcreator.deserializers.types.ItemDeserializer; +import re.sylfa.itemcreator.deserializers.types.ItemKeyDeserializer; +import re.sylfa.itemcreator.deserializers.types.KeyDeserializer; +import re.sylfa.itemcreator.deserializers.types.MaterialDeserializer; +import re.sylfa.itemcreator.deserializers.types.NamespacedKeyDeserializer; +import re.sylfa.itemcreator.deserializers.types.TextComponentDeserializer; import re.sylfa.itemcreator.items.CustomItem; +import re.sylfa.itemcreator.items.ItemKey; import re.sylfa.itemcreator.items.ItemReader; import re.sylfa.itemcreator.items.ItemRegistry; import re.sylfa.itemcreator.recipes.CustomRecipe; import re.sylfa.itemcreator.recipes.RecipeReader; import re.sylfa.itemcreator.recipes.RecipeRegistry; import re.sylfa.itemcreator.util.Log; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public class ItemCreator extends JavaPlugin { - + + @Getter private static ItemCreator instance; - private static ItemRegistry itemRegistry = new ItemRegistry(); - private static RecipeRegistry recipeRegistry = new RecipeRegistry(); + @Getter + private static final ItemRegistry itemRegistry = new ItemRegistry(); + @Getter + private static final RecipeRegistry recipeRegistry = new RecipeRegistry(); + + @Getter + private static ObjectMapper mapper; + @Override public void onEnable() { instance = this; Log.log("Enabled!"); + registerMappers(); + init(); registerPermissions(); registerCommands(); } - public static ItemCreator getInstance() { - return instance; - } - - public static ItemRegistry getItemRegistry() { - return itemRegistry; - } - - public static RecipeRegistry getRecipeRegistry() { - return recipeRegistry; - } public void init(){ itemRegistry.removeAll(); itemRegistry.add(ItemReader.readAllItems().toArray(CustomItem[]::new)); + List readRecipes = RecipeReader.readAllRecipes(); - Map readRecipesMap = readRecipes.stream().collect(Collectors.toMap(r -> r.key(), r -> r.asRecipe())); - recipeRegistry.getAll().keySet().forEach(key -> Log.log("Removing %s: %b", key.asString(), Bukkit.removeRecipe(key))); + Map readRecipesMap = readRecipes.stream() + .collect(Collectors.toMap( + CustomRecipe::key, + CustomRecipe::asRecipe + )); + recipeRegistry.getAll().keySet() + .forEach(key -> { + boolean recipeRemoved = Bukkit.removeRecipe(key); + // TODO toggleable in configuration +// Log.log("Removing %s: %b", key.asString(), recipeRemoved); + } + ); recipeRegistry.removeAll(); readRecipes.forEach(recipeRegistry::add); - readRecipesMap.values().forEach(r -> Bukkit.addRecipe(r, true)); + // FIXME resendRecipes = false does not work for now + readRecipesMap.values().forEach(r -> Bukkit.addRecipe(r, false)); } public void reload() { init(); } - public void registerPermissions() { + private void registerPermissions() { CommandPermissions.registerPermissions(new Permission("itemcreator.give", PermissionDefault.OP)); } - public void registerCommands() { - LifecycleEventManager manager = this.getLifecycleManager(); + private void registerCommands() { + LifecycleEventManager<@NonNull Plugin> manager = this.getLifecycleManager(); manager.registerEventHandler(LifecycleEvents.COMMANDS, event -> { final Commands commands = event.registrar(); List.of( @@ -83,4 +141,41 @@ public void registerCommands() { ).forEach(command -> commands.register(command.command(), command.aliases())); }); } + + private void registerMappers() { + + SimpleModule module = new SimpleModule(); + module.addDeserializer(CustomItem.class, new CustomItemDeserializer()) + .addDeserializer(ItemStack.class, new ItemDeserializer()) + .addDeserializer(Material.class, new MaterialDeserializer()) + .addDeserializer(Component.class, new TextComponentDeserializer()) + .addDeserializer(NamespacedKey.class, new NamespacedKeyDeserializer()) + .addDeserializer(EquippableComponent.class, new EquippableDeserializer()) + .addDeserializer(Key.class, new KeyDeserializer()) + .addDeserializer(ToolComponent.class, new ToolDeserializer()) + .addDeserializer(ToolRuleDeserializer.RuleValues.class, new ToolRuleDeserializer()) + .addDeserializer(JukeboxPlayableComponent.class, new JukeboxPlayableDeserializer()) + .addDeserializer(Consumable.class, new ConsumableDeserializer()) + .addDeserializer(FoodComponent.class, new FoodDeserializer()) + .addDeserializer(ItemKey.class, new ItemKeyDeserializer()) + .addDeserializer(PotionEffect.class, new PotionEffectDeserializer()) + .addDeserializer(DeathProtection.class, new DeathProtectionDeserializer()) + .addDeserializer(ConsumeEffect.class, new ConsumeEffectDeserializer()) + .addDeserializer(PotionEffectType.class, new PotionEffectTypeDeserializer()) + .addDeserializer(Color.class, new ColorDeserializer()) + .addDeserializer(MusicInstrument.class, new MusicInstrumentDeserializer()) + .addDeserializer(Enchantable.class, new EnchantableDeserializer()) + .addDeserializer(ResolvableProfile.class, new ProfileDeserializer()) + .addDeserializer(ProfileProperty.class, new ProfileDeserializer.ProfilePropertyDeserializer()) + .addDeserializer(ItemAttributeModifiers.class, new AttributeModifiersDeserializer()) + .addDeserializer(AttributeModifiersDeserializer.AttributeModification.class, new AttributeModifiersDeserializer.AttributeModifierDeserializer()) + .addDeserializer(AttributeModifierDisplay.class, new AttributeModifiersDeserializer.AttributeModifierDisplayDeserializer()) + .addDeserializer(CustomModelData.class, new CustomModelDataDeserializer()) + ; + + mapper = JsonMapper.builder() + .addModule(module) + .build(); + + } } diff --git a/src/main/java/re/sylfa/itemcreator/commands/ItemCreatorCommand.java b/src/main/java/re/sylfa/itemcreator/commands/ItemCreatorCommand.java index d031857..318a1d1 100644 --- a/src/main/java/re/sylfa/itemcreator/commands/ItemCreatorCommand.java +++ b/src/main/java/re/sylfa/itemcreator/commands/ItemCreatorCommand.java @@ -1,24 +1,22 @@ package re.sylfa.itemcreator.commands; -import static com.mojang.brigadier.Command.SINGLE_SUCCESS; -import static io.papermc.paper.command.brigadier.Commands.argument; -import static io.papermc.paper.command.brigadier.Commands.literal; - -import java.util.List; -import java.util.Optional; - -import org.bukkit.entity.Player; - import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.tree.LiteralCommandNode; - import io.papermc.paper.command.brigadier.CommandSourceStack; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.Player; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.commands.argumentType.CustomItemArgumentType; import re.sylfa.itemcreator.items.CustomItem; +import java.util.List; +import java.util.Optional; + +import static com.mojang.brigadier.Command.SINGLE_SUCCESS; +import static io.papermc.paper.command.brigadier.Commands.argument; +import static io.papermc.paper.command.brigadier.Commands.literal; + public class ItemCreatorCommand implements Command{ @Override diff --git a/src/main/java/re/sylfa/itemcreator/commands/argumentType/CustomItemArgumentType.java b/src/main/java/re/sylfa/itemcreator/commands/argumentType/CustomItemArgumentType.java index da2b3dd..6fbd038 100644 --- a/src/main/java/re/sylfa/itemcreator/commands/argumentType/CustomItemArgumentType.java +++ b/src/main/java/re/sylfa/itemcreator/commands/argumentType/CustomItemArgumentType.java @@ -30,8 +30,8 @@ public static CustomItemArgumentType customItem() { } @Override - public @NotNull Optional convert(@NotNull Key key) throws CommandSyntaxException { - return Optional.ofNullable(ItemCreator.getItemRegistry().get(key)); + public Optional convert(@NotNull Key key) throws CommandSyntaxException { + return ItemCreator.getItemRegistry().get(key); } @Override diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java new file mode 100644 index 0000000..e3b2b53 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/AttributeModifiersDeserializer.java @@ -0,0 +1,126 @@ +package re.sylfa.itemcreator.deserializers.components; + +import io.papermc.paper.datacomponent.item.ItemAttributeModifiers; +import io.papermc.paper.datacomponent.item.attribute.AttributeModifierDisplay; +import net.kyori.adventure.text.Component; +import org.bukkit.NamespacedKey; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.inventory.EquipmentSlotGroup; +import org.checkerframework.checker.nullness.qual.Nullable; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.Objects; +import java.util.Optional; + +public class AttributeModifiersDeserializer extends StdDeserializer { + + public AttributeModifiersDeserializer() { + super(ItemAttributeModifiers.class); + } + + @Override + public ItemAttributeModifiers deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(!node.isArray()) { + return null; + } + + ItemAttributeModifiers.Builder builder = ItemAttributeModifiers.itemAttributes(); + node.valueStream() + .map(parsedAttribute -> ItemCreator.getMapper().convertValue(parsedAttribute, AttributeModification.class)) + .filter(Objects::nonNull) + .forEach(parsedAttribute -> parsedAttribute.addToBuilder(builder)); + + return builder.build(); + } + + public record AttributeModification( + Attribute attribute, + NamespacedKey key, + double amount, + AttributeModifier.Operation operation, + @Nullable AttributeModifierDisplay display, + EquipmentSlotGroup equipmentSlotGroup + ) { + public void addToBuilder(ItemAttributeModifiers.Builder builder) { + AttributeModifier modifier = new AttributeModifier( + key, + amount, + operation + ); + + if(display != null) { + builder.addModifier(attribute, modifier, equipmentSlotGroup, display); + } else { + builder.addModifier(attribute, modifier, equipmentSlotGroup); + } + } + } + + public static class AttributeModifierDeserializer extends StdDeserializer { + public AttributeModifierDeserializer() { + super(AttributeModification.class); + } + + @Override + public AttributeModification deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null || !node.isObject()) { + return null; + } + Optional attribute = Parsers.getNodeValue(node, "attribute", JsonNode::isString, Attribute.class); + Optional key = Parsers.getNamespacedKeyNodeValue(node, "key"); + Optional amount = Parsers.getDoubleNodeValue(node, "amount"); + Optional operation = Parsers.getNodeValue(node, "operation", JsonNode::isString, AttributeModifier.Operation.class); + + if(attribute.isEmpty() || key.isEmpty() || amount.isEmpty() || operation.isEmpty()) { + return null; + } + + Optional display = Parsers.getNodeValue(node, "display", JsonNode::isString, AttributeModifierDisplay.class); + EquipmentSlotGroup slot = Optional.ofNullable(node.get("slot")) + .map(JsonNode::asString) + .map(EquipmentSlotGroup::getByName) + .orElse(EquipmentSlotGroup.ANY); + + return new AttributeModification( + attribute.get(), + key.get(), + amount.get(), + operation.get(), + display.orElse(null), + slot + ); + } + } + + public static class AttributeModifierDisplayDeserializer extends StdDeserializer { + public AttributeModifierDisplayDeserializer() { + super(AttributeModifierDisplay.class); + } + + @Override + public AttributeModifierDisplay deserialize(JsonParser p, DeserializationContext ctxt) { + String value = p.getString(); + return switch (value) { + case "reset" -> AttributeModifierDisplay.reset(); + case "hidden" -> AttributeModifierDisplay.hidden(); + default -> { + // try to parse the display as a component + Component component = ItemCreator.getMapper().convertValue(value, Component.class); + if(component == null) { + yield null; + } + + yield AttributeModifierDisplay.override(component); + } + }; + } + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java new file mode 100644 index 0000000..242fd47 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumableDeserializer.java @@ -0,0 +1,44 @@ +package re.sylfa.itemcreator.deserializers.components; + +import io.papermc.paper.datacomponent.item.Consumable; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.Log; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +public class ConsumableDeserializer extends StdDeserializer { + public ConsumableDeserializer() { super(Consumable.class); } + + @Override + public Consumable deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + Consumable.Builder builder = Consumable.consumable(); + Parsers.getNodeValue(node, "animation", JsonNode::isString, ItemUseAnimation.class) + .ifPresent(builder::animation); + Parsers.getFloatNodeValue(node, "consumeSeconds").ifPresent(builder::consumeSeconds); + Parsers.getBooleanNodeValue(node, "hasConsumeParticles").ifPresent(builder::hasConsumeParticles); + Parsers.getKeyNodeValue(node, "sound").ifPresent(builder::sound); + JsonNode effects = node.get("effects"); + if(effects != null) { + if(effects.isArray()) { + for(JsonNode effect : effects) { + ConsumeEffect consumeEffect = ItemCreator.getMapper().convertValue(effect, ConsumeEffect.class); + if(consumeEffect != null) { + builder.addEffect(consumeEffect); + } + } + } else { + Log.error("Consumable effect is not valid"); + } + } + return builder.build(); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java new file mode 100644 index 0000000..c837bb2 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ConsumeEffectDeserializer.java @@ -0,0 +1,93 @@ +package re.sylfa.itemcreator.deserializers.components; + +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.set.RegistrySet; +import net.kyori.adventure.key.Key; +import org.bukkit.Registry; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public class ConsumeEffectDeserializer extends StdDeserializer { + + public ConsumeEffectDeserializer() { super(ConsumeEffect.class); } + + @Override + public ConsumeEffect deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + Optional type = Parsers.getNodeValue(node, "type", JsonNode::isString, Type.class); + if(type.isEmpty()) { + return null; + } + + return switch (type.get()) { + case APPLY_STATUS_EFFECTS -> { + JsonNode effects = node.get("effects"); + if(effects == null || !effects.isArray()) { + yield null; + } + + List potionEffects = new ArrayList<>(); + for(JsonNode effect : effects) { + PotionEffect potionEffect = ItemCreator.getMapper().convertValue(effect, PotionEffect.class); + if(potionEffect != null) { + potionEffects.add(potionEffect); + } + } + + float probability = Parsers.getFloatNodeValue(node, "probability").orElse(1.0f); + yield ConsumeEffect.applyStatusEffects(potionEffects, probability); + } + case CLEAR_ALL_STATUS_EFFECTS -> ConsumeEffect.clearAllStatusEffects(); + case PLAY_SOUND -> Parsers.getKeyNodeValue(node, "sound") + .map(ConsumeEffect::playSoundConsumeEffect) + .orElse(null); + case REMOVE_STATUS_EFFECTS -> { + JsonNode effects = node.get("effects"); + if(effects == null || !effects.isArray()) { + yield null; + } + + Set effectTypes = new HashSet<>(); + for(JsonNode effect : effects) { + String rawKey = effect.asString(); + if(rawKey == null || rawKey.isBlank()) { + continue; + } + Key key = Key.key(rawKey); + + PotionEffectType effectType = Registry.MOB_EFFECT.get(key); + if(effectType != null) { + effectTypes.add(effectType); + } + } + + + yield ConsumeEffect.removeEffects(RegistrySet.keySetFromValues(RegistryKey.MOB_EFFECT, effectTypes)); + } + case TELEPORT_RANDOMLY -> Parsers.getFloatNodeValue(node, "diameter") + .map(ConsumeEffect::teleportRandomlyEffect) + .orElse(null); + }; + } + + public enum Type { + APPLY_STATUS_EFFECTS, + CLEAR_ALL_STATUS_EFFECTS, + PLAY_SOUND, + REMOVE_STATUS_EFFECTS, + TELEPORT_RANDOMLY + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/CustomModelDataDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/CustomModelDataDeserializer.java new file mode 100644 index 0000000..a7ecb8f --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/CustomModelDataDeserializer.java @@ -0,0 +1,34 @@ +package re.sylfa.itemcreator.deserializers.components; + +import io.papermc.paper.datacomponent.item.CustomModelData; +import org.bukkit.Color; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.Arrays; + +public class CustomModelDataDeserializer extends StdDeserializer { + public CustomModelDataDeserializer() { + super(CustomModelData.class); + } + + @Override + public CustomModelData deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + CustomModelData.Builder builder = CustomModelData.customModelData(); + + Parsers.getNodeValue(node, "flags", JsonNode::isArray, Boolean[].class) + .ifPresent(flags -> builder.addFlags(Arrays.asList(flags))); + Parsers.getNodeValue(node, "floats", JsonNode::isArray, Float[].class) + .ifPresent(floats -> builder.addFloats(Arrays.asList(floats))); + Parsers.getNodeValue(node, "colors", JsonNode::isArray, Color[].class) + .ifPresent(colors -> builder.addColors(Arrays.asList(colors))); + Parsers.getNodeValue(node, "strings", JsonNode::isArray, String[].class) + .ifPresent(strings -> builder.addStrings(Arrays.asList(strings))); + + return builder.build(); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/DeathProtectionDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/DeathProtectionDeserializer.java new file mode 100644 index 0000000..f09c089 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/DeathProtectionDeserializer.java @@ -0,0 +1,32 @@ +package re.sylfa.itemcreator.deserializers.components; + +import io.papermc.paper.datacomponent.item.DeathProtection; +import io.papermc.paper.datacomponent.item.consumable.ConsumeEffect; +import re.sylfa.itemcreator.ItemCreator; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.Optional; + +public class DeathProtectionDeserializer extends StdDeserializer { + public DeathProtectionDeserializer() { super(DeathProtection.class); } + + @Override + public DeathProtection deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null || !node.isArray()) { + return null; + } + + DeathProtection.Builder builder = DeathProtection.deathProtection(); + + for(JsonNode effect : node) { + Optional.ofNullable(ItemCreator.getMapper().convertValue(effect, ConsumeEffect.class)) + .ifPresent(builder::addEffect); + } + + return builder.build(); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/EnchantableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/EnchantableDeserializer.java new file mode 100644 index 0000000..88b6025 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/EnchantableDeserializer.java @@ -0,0 +1,37 @@ +package re.sylfa.itemcreator.deserializers.components; + +import io.papermc.paper.datacomponent.item.Enchantable; +import org.checkerframework.checker.nullness.qual.Nullable; +import re.sylfa.itemcreator.util.Log; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +public class EnchantableDeserializer extends StdDeserializer { + private static final int NOT_FOUND = -1; + public EnchantableDeserializer() { + super(Enchantable.class); + } + + @Override @Nullable + public Enchantable deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + + boolean enabled = node.get("enabled").asBoolean(false); + if(!enabled) { + return null; + } + + int value = node.get("value").asInt(NOT_FOUND); + if(value <= 0) { + Log.error("%s is not valid for enchantable value"); + return null; + } + + return Enchantable.enchantable(value); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java new file mode 100644 index 0000000..5fca660 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/EquippableDeserializer.java @@ -0,0 +1,52 @@ +package re.sylfa.itemcreator.deserializers.components; + +import org.bukkit.Material; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.components.EquippableComponent; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.Log; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.Optional; + +public class EquippableDeserializer extends StdDeserializer { + public EquippableDeserializer() { + super(EquippableComponent.class); + } + + @Override + public EquippableComponent deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + JsonNode slotNode = node.get("slot"); + EquipmentSlot slot; + if(slotNode != null && slotNode.isString()) { + slot = ItemCreator.getMapper().convertValue(slotNode, EquipmentSlot.class); + if(slot == null) { + Log.error("%s is not a valid slot type", slotNode.stringValue()); + } + + } else { + return null; + } + + + + EquippableComponent equippableComponent = new ItemStack(Material.STONE).getItemMeta().getEquippable(); + Parsers.getBooleanNodeValue(node, "dispensable").ifPresent(equippableComponent::setDispensable); + Parsers.getBooleanNodeValue(node, "damageOnHurt").ifPresent(equippableComponent::setDamageOnHurt); + Parsers.getBooleanNodeValue(node, "equipOnInteract").ifPresent(equippableComponent::setEquipOnInteract); + Parsers.getNamespacedKeyNodeValue(node, "model").ifPresent(equippableComponent::setModel); + Parsers.getNamespacedKeyNodeValue(node, "cameraOverlay").ifPresent(equippableComponent::setCameraOverlay); + Optional.ofNullable(slot).ifPresent(equippableComponent::setSlot); + + return equippableComponent; + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java new file mode 100644 index 0000000..f1f4b1d --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/FoodDeserializer.java @@ -0,0 +1,29 @@ +package re.sylfa.itemcreator.deserializers.components; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.components.FoodComponent; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +public class FoodDeserializer extends StdDeserializer { + + public FoodDeserializer() { super(FoodComponent.class); } + + @Override + public FoodComponent deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + + FoodComponent component = new ItemStack(Material.STONE).getItemMeta().getFood(); + Parsers.getBooleanNodeValue(node, "canAlwaysEat").ifPresent(component::setCanAlwaysEat); + Parsers.getIntNodeValue(node, "nutrition").ifPresent(component::setNutrition); + Parsers.getFloatNodeValue(node, "saturation").ifPresent(component::setSaturation); + return component; + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/JukeboxPlayableDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/JukeboxPlayableDeserializer.java new file mode 100644 index 0000000..e35b11d --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/JukeboxPlayableDeserializer.java @@ -0,0 +1,28 @@ +package re.sylfa.itemcreator.deserializers.components; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.Optional; + +public class JukeboxPlayableDeserializer extends StdDeserializer { + public JukeboxPlayableDeserializer() { super(JukeboxPlayableComponent.class); } + + @Override + public JukeboxPlayableComponent deserialize(JsonParser p, DeserializationContext ctxt) { + Optional songKey = Optional.ofNullable(NamespacedKey.fromString(p.getValueAsString())); + if(songKey.isEmpty()) { + return null; + } + + JukeboxPlayableComponent component = new ItemStack(Material.STONE).getItemMeta().getJukeboxPlayable(); + component.setSongKey(songKey.get()); + + return component; + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java new file mode 100644 index 0000000..286423f --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/MusicInstrumentDeserializer.java @@ -0,0 +1,57 @@ +package re.sylfa.itemcreator.deserializers.components; + +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import org.bukkit.MusicInstrument; +import org.bukkit.Sound; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.Optional; + +public class MusicInstrumentDeserializer extends StdDeserializer { + + public MusicInstrumentDeserializer() { super(MusicInstrument.class); } + + @Override + public MusicInstrument deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if (node == null || (!node.isObject() && !node.isString())) { + return null; + } + + // by key + if(node.isString()) { + Key key = Key.key(node.asString()); + return RegistryAccess.registryAccess().getRegistry(RegistryKey.INSTRUMENT).get(key); + } + + // by inline instrument + if(node.isObject()) { + Optional soundKey = Parsers.getKeyNodeValue(node, "soundKey"); + if (soundKey.isEmpty()) { + return null; + } + Optional duration = Parsers.getFloatNodeValue(node, "duration"); + Optional range = Parsers.getFloatNodeValue(node, "range"); + Optional description = Parsers.getComponentNodeValue(node, "description"); + TypedKey typedKey = RegistryKey.SOUND_EVENT.typedKey(soundKey.get()); + + return MusicInstrument.create(builder -> builder.empty() + .soundEvent(typedKey) + .description(description.orElse(Component.empty())) + .duration(duration.orElse(1.0f)) + .range(range.orElse(1.0f)) + ); + } + + // default + return null; + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectDeserializer.java new file mode 100644 index 0000000..ea69773 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectDeserializer.java @@ -0,0 +1,47 @@ +package re.sylfa.itemcreator.deserializers.components; + +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import re.sylfa.itemcreator.util.Log; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.Optional; + +public class PotionEffectDeserializer extends StdDeserializer { + public PotionEffectDeserializer() { super(PotionEffect.class); } + + @Override + public PotionEffect deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null || !node.isObject()) { + return null; + } + Optional type = Parsers.getNodeValue(node, "type", JsonNode::isString, PotionEffectType.class); + Optional duration = Parsers.getIntNodeValue(node, "duration"); + Optional amplifier = Parsers.getIntNodeValue(node, "amplifier"); + boolean ambient = Parsers.getBooleanNodeValue(node, "ambient").orElse(true); + boolean particles = Parsers.getBooleanNodeValue(node, "particles").orElse(true); + boolean icon = Parsers.getBooleanNodeValue(node, "icon").orElse(true); + + if(type.isEmpty()) { + Log.error("Potion type missing"); + return null; + } + + if(duration.isEmpty()) { + Log.error("Potion duration missing"); + return null; + } + + if(amplifier.isEmpty()) { + Log.error("Potion amplifier missing"); + return null; + } + + return new PotionEffect(type.get(), duration.get(), amplifier.get(), ambient, particles, icon); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectTypeDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectTypeDeserializer.java new file mode 100644 index 0000000..da5feb2 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/PotionEffectTypeDeserializer.java @@ -0,0 +1,23 @@ +package re.sylfa.itemcreator.deserializers.components; + +import net.kyori.adventure.key.Key; +import org.bukkit.Registry; +import org.bukkit.potion.PotionEffectType; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +public class PotionEffectTypeDeserializer extends StdDeserializer { + public PotionEffectTypeDeserializer() { super(PotionEffectType.class); } + + @Override + public PotionEffectType deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null || !node.isString()) { + return null; + } + Key key = Key.key(node.asString()); + return Registry.MOB_EFFECT.get(key); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java new file mode 100644 index 0000000..eac3aff --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ProfileDeserializer.java @@ -0,0 +1,71 @@ +package re.sylfa.itemcreator.deserializers.components; + +import com.destroystokyo.paper.profile.ProfileProperty; +import io.papermc.paper.datacomponent.item.ResolvableProfile; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.Objects; +import java.util.Optional; + +public class ProfileDeserializer extends StdDeserializer { + public ProfileDeserializer() { + super(ResolvableProfile.class); + } + @Override + public ResolvableProfile deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + + ResolvableProfile.Builder builder = ResolvableProfile.resolvableProfile(); + + Parsers.getStringNodeValue(node, "name").ifPresent(builder::name); + Parsers.getUUIDNodeValue(node, "uuid").ifPresent(builder::uuid); + + if(node.has("property")) { + Parsers.getNodeValue(node, "property", JsonNode::isObject, ProfileProperty.class) + .ifPresent(builder::addProperty); + } else if (node.has("properties")) { + JsonNode properties = node.get("properties"); + properties.valueStream() + .map(currentProperty -> ItemCreator.getMapper().convertValue(currentProperty, ProfileProperty.class)) + .filter(Objects::nonNull) + .forEach(builder::addProperty); + } + + return builder.build(); + } + + public static class ProfilePropertyDeserializer extends StdDeserializer { + public ProfilePropertyDeserializer() { + super(ProfileProperty.class); + } + + @Override + public ProfileProperty deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + + Optional name = Parsers.getStringNodeValue(node, "name"); + if(name.isEmpty()) { + return null; + } + Optional value = Parsers.getStringNodeValue(node, "value"); + if(value.isEmpty()) { + return null; + } + + Optional signature = Parsers.getStringNodeValue(node, "signature"); + + return new ProfileProperty(name.get(), value.get(), signature.orElse(null)); + } + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java new file mode 100644 index 0000000..0fa8d9a --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolDeserializer.java @@ -0,0 +1,33 @@ +package re.sylfa.itemcreator.deserializers.components; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.components.ToolComponent; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +public class ToolDeserializer extends StdDeserializer { + + public ToolDeserializer() { + super(ToolComponent.class); + } + + @Override + public ToolComponent deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + ToolComponent toolComponent = new ItemStack(Material.STONE).getItemMeta().getTool(); + Parsers.getIntNodeValue(node, "damagePerBlock").ifPresent(toolComponent::setDamagePerBlock); + Parsers.getFloatNodeValue(node, "defaultMiningSpeed").ifPresent(toolComponent::setDefaultMiningSpeed); + Parsers.getToolRulesArrayNodeValue(node, "rules") + .ifPresent(rulesList -> rulesList.forEach(ruleValue -> toolComponent.addRule(ruleValue.block(), ruleValue.speed(), ruleValue.correctForDrops()))); + + + return toolComponent; + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolRuleDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolRuleDeserializer.java new file mode 100644 index 0000000..3ac802a --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/components/ToolRuleDeserializer.java @@ -0,0 +1,33 @@ +package re.sylfa.itemcreator.deserializers.components; + +import org.bukkit.Material; +import org.bukkit.inventory.meta.components.ToolComponent; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.Optional; + +public class ToolRuleDeserializer extends StdDeserializer { + public ToolRuleDeserializer() { super(ToolComponent.ToolRule.class); } + + @Override + public RuleValues deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + Optional blockMaterial = Parsers.getMaterialNodeValue(node, "block"); + Optional speed = Parsers.getFloatNodeValue(node, "speed"); + Optional correctForDrops = Parsers.getBooleanNodeValue(node, "correctForDrops"); + if(blockMaterial.isEmpty() || speed.isEmpty()) { + return null; + } + return new RuleValues( + blockMaterial.get(), + speed.get(), + correctForDrops.orElse(false) + ); + } + + public record RuleValues(Material block, float speed, boolean correctForDrops) {} +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java new file mode 100644 index 0000000..034ed09 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ColorDeserializer.java @@ -0,0 +1,114 @@ +package re.sylfa.itemcreator.deserializers.types; + +import org.bukkit.Color; +import re.sylfa.itemcreator.util.Log; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class ColorDeserializer extends StdDeserializer { + private Color getColorByName(String name) { + return switch (name.toUpperCase()) { + case "AQUA" -> Color.AQUA; + case "BLACK" -> Color.BLACK; + case "BLUE" -> Color.BLUE; + case "FUCHSIA" -> Color.FUCHSIA; + case "GRAY" -> Color.GRAY; + case "GREEN" -> Color.GREEN; + case "LIME" -> Color.LIME; + case "MAROON" -> Color.MAROON; + case "NAVY" -> Color.NAVY; + case "OLIVE" -> Color.OLIVE; + case "ORANGE" -> Color.ORANGE; + case "PURPLE" -> Color.PURPLE; + case "RED" -> Color.RED; + case "SILVER" -> Color.SILVER; + case "TEAL" -> Color.TEAL; + case "WHITE" -> Color.WHITE; + case "YELLOW" -> Color.YELLOW; + default -> null; + }; + } + public ColorDeserializer() { super(Color.class); } + + @Override + public Color deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null) { + return null; + } + + // as an RGB int value + if(node.isIntegralNumber()) { + int colorNumber = node.asInt(); + if(colorNumber < 0 || colorNumber > 0xFFFFFF) { + Log.error("%s is not a valid color int.", colorNumber); + return null; + } + + return Color.fromRGB(colorNumber); + } + + if(node.isString()) { + String textValue = node.asString(); + // as an hex value #RRGGBB or #AARRGGBB + if(textValue.startsWith("#") && (textValue.length() == 7 || textValue.length() == 9)) { + int rgbValue = Integer.valueOf(textValue.substring(1), 16); + if(textValue.length() == 7) { + return Color.fromRGB(rgbValue); + } else { + return Color.fromARGB(rgbValue); + } + } else { + // with color name + return getColorByName(node.asString()); + } + } + + // as an array of 3 or 4 ints for (alpha), red, green, blue [255, 2, 140, 255] + if(node.isArray()) { + List numbers = new ArrayList<>(); + for(JsonNode value : node) { + if(!value.isIntegralNumber() || value.asInt() < 0 || value.asInt() > 255 ) { + Log.error("\"%s\" is not a valid number"); + return null; + } + numbers.add(value.intValue()); + } + if(numbers.size() < 3 || numbers.size() > 4) { + Log.error("%s is not a valid color"); + } + int[] intValues = numbers.stream().mapToInt(i -> i).toArray(); + if(intValues.length == 3) { + return Color.fromRGB(intValues[0], intValues[1], intValues[2]); + } else { + return Color.fromARGB(intValues[0], intValues[1], intValues[2], intValues[3]); + } + } + + // as an object {"red": 255, "blue": 120, "green": 41, "alpha": 255} + if(node.isObject()) { + Optional red = Parsers.getIntNodeValue(node, "red"); + Optional green = Parsers.getIntNodeValue(node, "green"); + Optional blue = Parsers.getIntNodeValue(node, "blue"); + Optional alpha = Parsers.getIntNodeValue(node, "alpha"); + if(red.isEmpty() || blue.isEmpty() || green.isEmpty()) { + return null; + } + return Color.fromARGB( + alpha.orElse(255), + red.get(), + green.get(), + blue.get() + ); + } + + return null; + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java new file mode 100644 index 0000000..8369767 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/CustomItemDeserializer.java @@ -0,0 +1,126 @@ +package re.sylfa.itemcreator.deserializers.types; + +import io.papermc.paper.datacomponent.DataComponentTypes; +import io.papermc.paper.datacomponent.item.Consumable; +import io.papermc.paper.datacomponent.item.CustomModelData; +import io.papermc.paper.datacomponent.item.DeathProtection; +import io.papermc.paper.datacomponent.item.DyedItemColor; +import io.papermc.paper.datacomponent.item.Enchantable; +import io.papermc.paper.datacomponent.item.ItemAttributeModifiers; +import io.papermc.paper.datacomponent.item.ResolvableProfile; +import net.minecraft.core.component.DataComponents; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.MusicInstrument; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.inventory.ItemRarity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.components.EquippableComponent; +import org.bukkit.inventory.meta.components.FoodComponent; +import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; +import org.bukkit.inventory.meta.components.ToolComponent; +import re.sylfa.itemcreator.items.CustomItem; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.List; +import java.util.Objects; + +public class CustomItemDeserializer extends StdDeserializer { + + public CustomItemDeserializer() { + super(CustomItem.class); + } + + @Override + public CustomItem deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) { + JsonNode node = jsonParser.readValueAsTree(); + if(node == null) { + return null; + } + Material material = Parsers.getMaterialNodeValue(node, "material") + .orElse(Material.WOODEN_PICKAXE); + + var itemNms = CraftItemStack.asNMSCopy(new ItemStack(material)); + + // setMaxDamage(null) only resets the *patched* value, not the actual one + itemNms.remove(DataComponents.MAX_DAMAGE); + itemNms.remove(DataComponents.TOOL); + itemNms.remove(DataComponents.WEAPON); + itemNms.remove(DataComponents.ATTRIBUTE_MODIFIERS); + itemNms.remove(DataComponents.FOOD); + itemNms.remove(DataComponents.REPAIRABLE); + itemNms.remove(DataComponents.ENCHANTABLE); + ItemStack item = itemNms.asBukkitCopy(); + + Parsers.getIntNodeValue(node, "amount").ifPresent(item::setAmount); + Parsers.getNodeValue(node, "attributes", JsonNode::isArray, ItemAttributeModifiers.class) + .ifPresent(attributes -> item.setData(DataComponentTypes.ATTRIBUTE_MODIFIERS, attributes)); + Parsers.getNodeValue(node, "baseColor", JsonNode::isString, DyeColor.class) + .ifPresent(color -> item.setData(DataComponentTypes.BASE_COLOR, color)); + Parsers.getKeyNodeValue(node, "breakSound") + .ifPresent(sound -> item.setData(DataComponentTypes.BREAK_SOUND, sound)); + Parsers.getNodeValue(node, "consumable", JsonNode::isObject, Consumable.class) + .ifPresent(consumable -> item.setData(DataComponentTypes.CONSUMABLE, consumable)); + Parsers.getNodeValue(node, "customModelData", JsonNode::isObject, CustomModelData.class) + .ifPresent(modelData -> item.setData(DataComponentTypes.CUSTOM_MODEL_DATA, modelData)); + Parsers.getComponentNodeValue(node, "customName") + .ifPresent(customName -> item.getItemMeta().displayName(customName)); + Parsers.getIntNodeValue(node, "damage") + .ifPresent(damage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setDamage(damage))); + Parsers.getNodeValue(node, "deathProtection", JsonNode::isArray, DeathProtection.class) + .ifPresent(deathProtection -> item.setData(DataComponentTypes.DEATH_PROTECTION, deathProtection)); + Parsers.getColorNodeValue(node, "dyedColor") + .ifPresent(color -> item.setData(DataComponentTypes.DYED_COLOR, DyedItemColor.dyedItemColor(color))); + Parsers.getNodeValue(node, "enchantable", JsonNode::isObject, Enchantable.class) + .ifPresent(enchantable -> item.setData(DataComponentTypes.ENCHANTABLE, enchantable)); + Parsers.getBooleanNodeValue(node, "enchantmentGlintOverride") + .ifPresent(override -> item.editMeta(itemMeta -> itemMeta.setEnchantmentGlintOverride(override))); + Parsers.getNodeValue(node, "equippable", JsonNode::isObject, EquippableComponent.class) + .ifPresent(equippable -> item.editMeta(itemMeta -> itemMeta.setEquippable(equippable))); + Parsers.getNodeValue(node, "food", JsonNode::isObject, FoodComponent.class) + .ifPresent(food -> item.editMeta(itemMeta -> itemMeta.setFood(food))); + Parsers.getBooleanNodeValue(node, "glider") + .ifPresent(glider -> item.editMeta(itemMeta -> itemMeta.setGlider(glider))); + Parsers.getNodeValue(node, "instrument", Objects::nonNull, MusicInstrument.class) + .ifPresent(instrument -> item.setData(DataComponentTypes.INSTRUMENT, instrument)); + Parsers.getNamespacedKeyNodeValue(node, "itemModel") + .ifPresent(modelKey -> item.editMeta(itemMeta -> itemMeta.setItemModel(modelKey))); + Parsers.getComponentNodeValue(node, "itemName") + .ifPresent(itemName -> item.editMeta(ItemMeta.class, meta -> meta.itemName(itemName))); + Parsers.getNodeValue(node, "jukeboxPlayable", JsonNode::isString, JukeboxPlayableComponent.class) + .ifPresent(song -> item.editMeta(itemMeta -> itemMeta.setJukeboxPlayable(song))); + Parsers.getComponentArrayNodeValue(node, "lore") + .ifPresent(loreLines -> item.editMeta(itemMeta -> itemMeta.lore(List.of(loreLines)))); + Parsers.getIntNodeValue(node, "maxStackSize") + .ifPresent(maxStackSize -> item.editMeta(itemMeta -> itemMeta.setMaxStackSize(maxStackSize))); + Parsers.getIntNodeValue(node, "maxDamage") + .ifPresent(maxDamage -> item.editMeta(Damageable.class, itemMeta -> itemMeta.setMaxDamage(maxDamage))); + Parsers.getKeyNodeValue(node, "noteBlockSound") + .ifPresent(soundKey -> item.setData(DataComponentTypes.NOTE_BLOCK_SOUND, soundKey)); + Parsers.getNodeValue(node, "profile", JsonNode::isObject, ResolvableProfile.class) + .ifPresent(profile -> item.setData(DataComponentTypes.PROFILE, profile)); + Parsers.getNodeValue(node, "rarity", JsonNode::isString, ItemRarity.class) + .ifPresent(rarity -> item.editMeta(itemMeta -> itemMeta.setRarity(rarity))); + Parsers.getIntNodeValue(node, "repairCost") + .ifPresent(cost -> item.setData(DataComponentTypes.REPAIR_COST, cost)); + Parsers.getNodeValue(node, "tool", JsonNode::isObject, ToolComponent.class) + .ifPresent(tool -> item.editMeta(itemMeta -> itemMeta.setTool(tool))); + Parsers.getKeyNodeValue(node, "tooltipStyle") + .ifPresent(style -> item.setData(DataComponentTypes.TOOLTIP_STYLE, style)); + Parsers.getBooleanNodeValue(node, "unbreakable") + .ifPresent(unbreakable -> item.getItemMeta().setUnbreakable(unbreakable)); + + + CustomItem.Builder builder = CustomItem.builder() + .item(item); + Parsers.getItemKeyNodeValue(node, "useRemainder").ifPresent(builder::useRemainder); + return builder.build(); + } + +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java new file mode 100644 index 0000000..4fe303f --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemDeserializer.java @@ -0,0 +1,28 @@ +package re.sylfa.itemcreator.deserializers.types; + +import org.bukkit.inventory.ItemStack; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.items.ItemKey; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +public class ItemDeserializer extends StdDeserializer { + public ItemDeserializer() { super(ItemStack.class); } + + @Override + public ItemStack deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null || !node.isObject()) { + return null; + } + + ItemKey itemKey = ItemCreator.getMapper().convertValue(node, ItemKey.class); + if(itemKey != null) { + return itemKey.asItemStack(); + } else { + return null; + } + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java new file mode 100644 index 0000000..aca60c9 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/ItemKeyDeserializer.java @@ -0,0 +1,40 @@ +package re.sylfa.itemcreator.deserializers.types; + +import net.kyori.adventure.key.Key; +import re.sylfa.itemcreator.items.ItemKey; +import re.sylfa.itemcreator.util.Parsers; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.Optional; + +public class ItemKeyDeserializer extends StdDeserializer { + public ItemKeyDeserializer() { super(ItemKey.class);} + + @Override + public ItemKey deserialize(JsonParser p, DeserializationContext ctxt) { + JsonNode node = p.readValueAsTree(); + if(node == null || (!node.isString() && !node.isObject())) { + return null; + } + + Optional key; + Optional amount; + if(node.isObject()) { + key = Parsers.getKeyNodeValue(node, "id"); + amount = Parsers.getIntNodeValue(node, "amount"); + } else if (node.isString() && !node.asString().isBlank()){ + key = Optional.of(Key.key(node.asString())); + amount = Optional.of(1); + } else { + return null; + } + if(key.isEmpty()) { + return null; + } + + return new ItemKey(key.get(), amount.orElse(1)); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/KeyDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/KeyDeserializer.java new file mode 100644 index 0000000..94c8895 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/KeyDeserializer.java @@ -0,0 +1,16 @@ +package re.sylfa.itemcreator.deserializers.types; + +import net.kyori.adventure.key.Key; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.std.StdDeserializer; + +public class KeyDeserializer extends StdDeserializer { + + public KeyDeserializer() { super(Key.class); } + + @Override + public Key deserialize(JsonParser p, DeserializationContext ctxt) { + return Key.key(p.getValueAsString()); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/MaterialDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/MaterialDeserializer.java new file mode 100644 index 0000000..5bde704 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/MaterialDeserializer.java @@ -0,0 +1,21 @@ +package re.sylfa.itemcreator.deserializers.types; + +import org.bukkit.Material; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.Optional; + +public class MaterialDeserializer extends StdDeserializer { + public MaterialDeserializer() { + super(Material.class); + } + + @Override + public Material deserialize(JsonParser p, DeserializationContext ctxt) { + return Optional.ofNullable(p.getValueAsString()) + .map(Material::matchMaterial) + .orElse(Material.STONE); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/NamespacedKeyDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/NamespacedKeyDeserializer.java new file mode 100644 index 0000000..3fcce69 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/NamespacedKeyDeserializer.java @@ -0,0 +1,18 @@ +package re.sylfa.itemcreator.deserializers.types; + +import org.bukkit.NamespacedKey; +import org.jetbrains.annotations.Nullable; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.std.StdDeserializer; + +public class NamespacedKeyDeserializer extends StdDeserializer { + public NamespacedKeyDeserializer() { + super(NamespacedKey.class); + } + + @Override + public @Nullable NamespacedKey deserialize(JsonParser p, DeserializationContext ctxt) { + return NamespacedKey.fromString(p.getValueAsString()); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/deserializers/types/TextComponentDeserializer.java b/src/main/java/re/sylfa/itemcreator/deserializers/types/TextComponentDeserializer.java new file mode 100644 index 0000000..678612e --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/deserializers/types/TextComponentDeserializer.java @@ -0,0 +1,25 @@ +package re.sylfa.itemcreator.deserializers.types; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.deser.std.StdDeserializer; + +import java.util.Optional; + +public class TextComponentDeserializer extends StdDeserializer { + private static final MiniMessage miniMessage = MiniMessage.miniMessage(); + + public TextComponentDeserializer() { + super(Component.class); + } + + @Override + public Component deserialize(JsonParser p, DeserializationContext ctxt){ + + return Optional.ofNullable(p.getValueAsString()) + .map(message -> miniMessage.deserialize(""+message+"")) + .orElse(Component.empty()); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java index e32c129..f9db73a 100644 --- a/src/main/java/re/sylfa/itemcreator/items/CustomItem.java +++ b/src/main/java/re/sylfa/itemcreator/items/CustomItem.java @@ -1,191 +1,85 @@ package re.sylfa.itemcreator.items; -import java.util.List; - -import org.bukkit.JukeboxSong; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import net.kyori.adventure.key.Key; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.StringTag; +import net.minecraft.world.item.component.CustomData; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.inventory.meta.components.JukeboxPlayableComponent; -import org.bukkit.persistence.PersistentDataType; -import io.papermc.paper.registry.RegistryAccess; -import io.papermc.paper.registry.RegistryKey; -import net.kyori.adventure.key.Key; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.minecraft.core.component.DataComponents; -import re.sylfa.itemcreator.ItemCreator; -import re.sylfa.itemcreator.util.Log; +import java.util.Optional; +@Getter @AllArgsConstructor @NoArgsConstructor public class CustomItem { - private static final String ID = "id"; - private Key key; - private boolean hasCustomModelData = false; - private int customModelData; - private Component itemName; - private Material material; - private List lore; - private int maxDamage; - private boolean hasMaxDamage = false; - private int maxStackSize; - private JukeboxPlayableComponent jukeboxPlayableComponent; - private boolean hasEnchantmentGlintOverride; - private boolean enchantmentGlintOverride; + private ItemStack item; + private ItemKey useRemainder; public Key key() { return this.key; } - public int customModelData() { - return customModelData; + public CustomItem(ItemStack item) { + this.item = item; } - public Component itemName() { - return itemName; - } + CustomItem withKey(Key key) { return CustomItem.builder(key).item(getItem()).useRemainder(getUseRemainder()).build(); } - public Material material() { - return material; + public void key(Key key) { + this.key = key; } - public List lore() { - return lore; - } - - public int maxDamage() { - return maxDamage; - } - - public int maxStackSize() { - return maxStackSize; + public ItemStack asItemStack() { + ItemStack finalItem = this.withItemId(key); + if(useRemainder != null) { + Optional.ofNullable(useRemainder.asItemStack()) + .ifPresent(remainder -> finalItem.editMeta(itemMeta -> itemMeta.setUseRemainder(remainder))); + } + return finalItem; } - public JukeboxPlayableComponent jukeboxPlayableComponent() { - return jukeboxPlayableComponent; + public static Builder builder(Key key) { + return new Builder().key(key); } - public ItemStack asItemStack() { - var itemNms = CraftItemStack.asNMSCopy(new ItemStack(material)); - // setMaxDamage(null) only resets the *patched* value, not the actual one - itemNms.remove(DataComponents.MAX_DAMAGE); - itemNms.remove(DataComponents.TOOL); - itemNms.remove(DataComponents.ATTRIBUTE_MODIFIERS); - itemNms.remove(DataComponents.FOOD); - - ItemStack item = itemNms.asBukkitCopy(); - item.editMeta(Damageable.class, meta -> { - meta.getPersistentDataContainer().set(NamespacedKey.fromString(ID, ItemCreator.getInstance()), PersistentDataType.STRING, key.asString()); - meta.itemName(itemName); - meta.setCustomModelData(this.hasCustomModelData ? customModelData : null); - meta.lore(lore); - if(hasMaxDamage) { - meta.setMaxDamage(maxDamage); - } - meta.setMaxStackSize(maxStackSize); - meta.setJukeboxPlayable(jukeboxPlayableComponent); - if(hasEnchantmentGlintOverride) { - meta.setEnchantmentGlintOverride(enchantmentGlintOverride); - } - }); - - return item; + public static Builder builder() { + return new Builder(); } public static class Builder { - CustomItem item = new CustomItem(); - private MiniMessage mm = MiniMessage.miniMessage(); - - public static Builder builder(Key key) { - return new CustomItem.Builder().key(key); - } + CustomItem customItem = new CustomItem(); private Builder key(Key key) { - item.key = key; + customItem.key = key; return this; } - Builder customModelData(int customModelData) { - if(customModelData != 0) { - item.customModelData = customModelData; - item.hasCustomModelData = true; - } - return this; - } - Builder itemName(String rawItemName) { - return itemName(mm.deserialize(rawItemName)); - } - - Builder itemName(Component itemName) { - item.itemName = itemName; + public Builder item(ItemStack item) { + this.customItem.item = item; return this; } - Builder material(Material material) { - item.material = material; + public Builder useRemainder(ItemKey remainderKey) { + this.customItem.useRemainder = remainderKey; return this; } - Builder lore(List lore) { - if(!lore.isEmpty()) { - item.lore = lore.stream().map(line -> mm.deserialize(""+line+"")).toList(); - } - return this; - } - - Builder maxDamage(int maxDamage) { - if(maxDamage > 0) { - item.maxDamage = maxDamage; - item.hasMaxDamage = true; - } else if (maxDamage < 0) { - Log.warn("Negative max damage for %s", item.key.asString()); - } - - return this; - } - - Builder maxStackSize(int maxStackSize) { - if(maxStackSize < 1 || maxStackSize > 99) { - maxStackSize = 1; - Log.warn(String.format("Max stack size for %s is not valid", item.key.asString())); - } - - item.maxStackSize = maxStackSize; - return this; + public CustomItem build() { + return this.customItem; } + } - Builder jukeboxPlayableComponent(String rawJukeboxSong, boolean showInTooltip) { - if(rawJukeboxSong == null) { - return this; - } - JukeboxSong jukeboxSong = RegistryAccess.registryAccess().getRegistry(RegistryKey.JUKEBOX_SONG).get(Key.key(rawJukeboxSong)); - if(jukeboxSong != null) { - // HACK there's no JukeboxPlayableComponent constructor - JukeboxPlayableComponent jukeboxPlayableComponent = new ItemStack(Material.STONE).getItemMeta().getJukeboxPlayable(); - jukeboxPlayableComponent.setSong(jukeboxSong); - jukeboxPlayableComponent.setShowInTooltip(showInTooltip); - return jukeboxPlayableComponent(jukeboxPlayableComponent); - } - return this; - } - - Builder jukeboxPlayableComponent(JukeboxPlayableComponent jukeboxPlayableComponent) { - item.jukeboxPlayableComponent = jukeboxPlayableComponent; - return this; - } + private ItemStack withItemId(Key key) { + net.minecraft.world.item.ItemStack nmsCopy = CraftItemStack.asNMSCopy(item); - Builder enchantmentGlintOverride(boolean set, boolean override) { - item.hasEnchantmentGlintOverride = set; - item.enchantmentGlintOverride = override; - return this; - } + CustomData customData = nmsCopy.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY) + .update(compoundTag -> compoundTag.put("itemcreator:id", StringTag.valueOf(key.asString()))); - CustomItem build() { - return item; - } + nmsCopy.set(DataComponents.CUSTOM_DATA, customData); + return nmsCopy.asBukkitCopy().asOne(); } } diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemKey.java b/src/main/java/re/sylfa/itemcreator/items/ItemKey.java new file mode 100644 index 0000000..2765fb4 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/items/ItemKey.java @@ -0,0 +1,16 @@ +package re.sylfa.itemcreator.items; + +import net.kyori.adventure.key.Key; +import org.bukkit.inventory.ItemStack; +import re.sylfa.itemcreator.ItemCreator; + +public record ItemKey(Key key, int amount) { + public ItemStack asItemStack() { + ItemStack parsedItem = ItemCreator.getItemRegistry().parse(key); + if(parsedItem != null) { + return parsedItem.asQuantity(amount); + } else { + return null; + } + } +} diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java index 923a945..fca7e43 100644 --- a/src/main/java/re/sylfa/itemcreator/items/ItemReader.java +++ b/src/main/java/re/sylfa/itemcreator/items/ItemReader.java @@ -1,19 +1,20 @@ package re.sylfa.itemcreator.items; -import java.io.File; -import java.util.Arrays; -import java.util.List; - -import org.apache.commons.io.FilenameUtils; -import org.bukkit.Material; -import org.bukkit.configuration.file.YamlConfiguration; - import net.kyori.adventure.key.Key; +import org.apache.commons.io.FilenameUtils; import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.JavaUtils; import re.sylfa.itemcreator.util.Log; +import tools.jackson.databind.ObjectMapper; + +import java.io.File; +import java.util.List; +import java.util.Objects; public class ItemReader { - + + static final ObjectMapper mapper = ItemCreator.getMapper(); + public static List readAllItems() { File itemsFolder = new File(ItemCreator.getInstance().getDataFolder(), "items"); if(!itemsFolder.exists()) { @@ -22,58 +23,51 @@ public static List readAllItems() { return List.of(); } - return Arrays.stream(itemsFolder.listFiles()) - .filter(file -> { - if(!file.isDirectory()) { - Log.warn("%s is not an item namespace", file.getName()); - return false; - } else { - return true; - } - }) - .map(folder -> { - String folderName = folder.getName(); - return Arrays.stream(folder.listFiles()) + return JavaUtils.arrayStream(itemsFolder.listFiles()) .filter(file -> { - if(!List.of("yml","yaml").contains(FilenameUtils.getExtension(file.getPath()))) { - Log.warn("%s is not an item file in %s", file.getName(), folderName); + if(!file.isDirectory()) { + Log.warn("%s is not an item namespace", file.getName()); return false; } else { return true; } }) - .map(ItemReader::readItem) - .filter(file -> file != null); - }) - .flatMap(a -> a) - .toList(); + .flatMap(folder -> { + String folderName = folder.getName(); + return JavaUtils.arrayStream(Objects.requireNonNull(folder.listFiles())) + .filter(file -> { + if(!"json".equals(FilenameUtils.getExtension(file.getPath()))) { + Log.warn("%s is not an item file in %s", file.getName(), folderName); + return false; + } else { + return true; + } + }) + .map(ItemReader::readItem) + .filter(Objects::nonNull); + }) + .toList(); } public static CustomItem readItem(File file) { String fileName = FilenameUtils.getBaseName(file.getPath()); String parentName = file.getParentFile().getName(); - if(parentName == "items") { + if("items".equals(parentName)) { Log.warn("%s item is not in a namespace", file.getName()); return null; } - - return readItem(YamlConfiguration.loadConfiguration(file), parentName, fileName); - } - public static CustomItem readItem(YamlConfiguration config, String folderName, String fileName) { - Key key = Key.key(folderName, fileName); - Log.log("Reading item %s", key.asString()); + if(!Key.parseableNamespace(parentName)) { + Log.error("%s namespace should only have characters from [a-z0-9_\\-.]"); + return null; + } + if(!Key.parseableValue(fileName)) { + Log.error("%s is not a valid item name", fileName); + } - return CustomItem.Builder.builder(key) - .customModelData(config.getInt("model")) - .itemName(config.getString("name", "")) - .material(Material.matchMaterial(config.getString("material", "diamond_pickaxe"))) - .lore(config.getStringList("lore")) - .maxDamage(config.getInt("maxDamage")) - .maxStackSize(config.getInt("maxStackSize", 1)) - .jukeboxPlayableComponent(config.getString("jukeboxSong.name"), config.getBoolean("jukeboxSong.showInTooltip", true)) - .enchantmentGlintOverride(config.getBoolean("enchantmentGlintOverride.set"), config.getBoolean("enchantmentGlintOverride.value")) - .build(); + Key key = Key.key(parentName, fileName); + CustomItem customItem = mapper.readValue(file, CustomItem.class); + return customItem.withKey(key); } } diff --git a/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java b/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java index d8e1b93..1441dac 100644 --- a/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java +++ b/src/main/java/re/sylfa/itemcreator/items/ItemRegistry.java @@ -1,28 +1,25 @@ package re.sylfa.itemcreator.items; -import java.util.Map; - -import javax.annotation.Nullable; - +import net.kyori.adventure.key.Key; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Arrays; -import net.kyori.adventure.key.Key; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.util.Log; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + public class ItemRegistry { final Map itemList = new HashMap<>(); public void add(CustomItem... item) { - Arrays.stream(item).forEach(i -> itemList.put(i.key(), i));; + Arrays.stream(item).forEach(i -> itemList.put(i.key(), i)); } - @Nullable - public CustomItem get(Key key) { - return itemList.getOrDefault(key, null); + public Optional get(Key key) { + return Optional.ofNullable(itemList.getOrDefault(key, null)); } public boolean has(Key key) { @@ -44,18 +41,21 @@ public void removeAll() { public ItemStack parse(Key key) { // first, try to get Minecraft material - if(key.namespace() == Key.MINECRAFT_NAMESPACE) { + if(key.namespace().equals(Key.MINECRAFT_NAMESPACE)) { Material material = Material.matchMaterial(key.value()); - if (material != null) return new ItemStack(material); + if (material != null) { + return new ItemStack(material); + } } // it was not a Minecraft item, check if it's a custom one var itemRegistry = ItemCreator.getItemRegistry(); - if(itemRegistry.has(key)) { - return itemRegistry.get(key).asItemStack(); - }; - - Log.warn("Material not found: %s", key.asString()); - return null; + Optional item = itemRegistry.get(key); + if(item.isPresent()) { + return item.get().asItemStack(); + } else { + Log.warn("Material not found: %s", key.asString()); + return null; + } } } diff --git a/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java b/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java index 7f4d34e..dcd500e 100644 --- a/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java +++ b/src/main/java/re/sylfa/itemcreator/recipes/CustomRecipe.java @@ -1,29 +1,21 @@ package re.sylfa.itemcreator.recipes; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - +import net.kyori.adventure.key.Key; import org.bukkit.Material; import org.bukkit.NamespacedKey; -import org.bukkit.inventory.BlastingRecipe; -import org.bukkit.inventory.CampfireRecipe; -import org.bukkit.inventory.FurnaceRecipe; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.Recipe; -import org.bukkit.inventory.RecipeChoice; -import org.bukkit.inventory.ShapedRecipe; -import org.bukkit.inventory.ShapelessRecipe; -import org.bukkit.inventory.SmithingTransformRecipe; -import org.bukkit.inventory.SmokingRecipe; -import org.bukkit.inventory.StonecuttingRecipe; - -import net.kyori.adventure.key.Key; +import org.bukkit.inventory.*; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.value.qual.ArrayLenRange; import re.sylfa.itemcreator.ItemCreator; import re.sylfa.itemcreator.items.ItemRegistry; import re.sylfa.itemcreator.util.Log; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + public class CustomRecipe { String keyValue; RecipeType type; @@ -54,42 +46,42 @@ public Recipe asRecipe() { Log.warn("Shape for %s is not set", keyValue); return null; } - if(shapedIngredients == null || shapedIngredients.entrySet().size() == 0) { + if(shapedIngredients == null || shapedIngredients.isEmpty()) { Log.warn("Recipe %s has no ingredients.", keyValue); return null; } ShapedRecipe shapedRecipe = new ShapedRecipe(key(), result); shapedRecipe.shape(shape); - shapedIngredients.forEach((ingredientKey, ingredient) -> shapedRecipe.setIngredient(ingredientKey, ingredient)); + shapedIngredients.forEach(shapedRecipe::setIngredient); return shapedRecipe; case SHAPELESS: - if(ingredients == null || ingredients.size() == 0) { + if(ingredients == null || ingredients.isEmpty()) { Log.warn("Recipe %s has no ingredients.", keyValue); return null; } ShapelessRecipe shapelessRecipe = new ShapelessRecipe(key(), result); - ingredients.forEach(ingredient -> shapelessRecipe.addIngredient(ingredient)); + ingredients.forEach(shapelessRecipe::addIngredient); return shapelessRecipe; case STONECUTTING: if(ingredient == null) { - Log.warn("Recipe %s has no ingredients.", keyValue); + Log.warn("Recipe %s has no ingredient.", keyValue); return null; } return new StonecuttingRecipe(key(), result, ingredient); case FURNACE: if(ingredient == null) { - Log.warn("Recipe %s has no ingredients.", keyValue); + Log.warn("Recipe %s has no ingredient.", keyValue); return null; } return new FurnaceRecipe(key(), result, ingredient, cookingExperience, cookingTime); case CAMPFIRE: if(ingredient == null) { - Log.warn("Recipe %s has no ingredients.", keyValue); + Log.warn("Recipe %s has no ingredient.", keyValue); return null; } return new CampfireRecipe(key(), result, ingredient, cookingExperience, cookingTime); @@ -131,7 +123,7 @@ public Recipe asRecipe() { public static class Builder { CustomRecipe recipe = new CustomRecipe(); - private ItemRegistry itemRegistry = ItemCreator.getItemRegistry(); + private final ItemRegistry itemRegistry = ItemCreator.getItemRegistry(); public static Builder builder(String keyValue, String type) { return new Builder().keyValue(keyValue).type(type); @@ -143,24 +135,19 @@ private Builder keyValue(String keyValue) { } private Builder type(String rawType) { - RecipeType type = RecipeType.valueOf(rawType.toUpperCase()); - if(type != null) { - recipe.type = type; - } else { - Log.warn("Unknown recipe type for %s: %s", recipe.keyValue, rawType); - } + recipe.type = RecipeType.valueOf(rawType.toUpperCase()); return this; } Builder shapedIngredients(Map shapedIngredients) { - if(shapedIngredients == null || shapedIngredients.entrySet().size() == 0) { + if(shapedIngredients == null || shapedIngredients.isEmpty()) { return this; } Map parsedIngredients = shapedIngredients.entrySet().stream() .collect(Collectors.toMap( - entry -> entry.getKey(), + Map.Entry::getKey, entry -> itemRegistry.parse(Key.key(entry.getValue())) )); var unknownIngredient = parsedIngredients.entrySet().stream() @@ -179,23 +166,26 @@ Builder shapedIngredients(Map shapedIngredients) { } Builder ingredients(List ingredients) { - if(ingredients.size() > 9) { + if(ingredients == null || ingredients.size() > 9) { Log.warn("Too many ingredients for recipe %s", recipe.keyValue); return this; } - List parsedIngredients = ingredients.stream().map(ingredient -> ItemCreator.getItemRegistry().parse(Key.key(ingredient))).toList(); - var unknownIngredient = ingredients.stream().filter(ingredient -> ingredient == null).findAny(); + List parsedIngredients = ingredients.stream() + .map(ingredient -> ItemCreator.getItemRegistry().parse(Key.key(ingredient))).toList(); + var unknownIngredient = ingredients.stream().filter(Objects::isNull).findAny(); if(unknownIngredient.isPresent()) { Log.warn("Unknown ingredient found for recipe %s", recipe.keyValue); } + // HACK why .toList() is not working here?? - List recipeChoices = parsedIngredients.stream().map(RecipeChoice.ExactChoice::new).collect(Collectors.toList()); - recipe.ingredients = recipeChoices; + recipe.ingredients = parsedIngredients.stream() + .map(RecipeChoice.ExactChoice::new) + .collect(Collectors.toList()); return this; } - Builder ingredient(String ingredient) { + Builder ingredient(@Nullable String ingredient) { if(ingredient == null || ingredient.isBlank()) { return this; } @@ -211,6 +201,9 @@ Builder ingredient(String ingredient) { } Builder result(String result, int amount) { + if(result == null || result.isBlank()) { + return this; + } if(amount < 1 || amount > 99) { Log.warn("Bad result amount for %s: %d", recipe.keyValue, amount); return this; @@ -225,7 +218,11 @@ Builder result(String result, int amount) { return this; } - Builder shape(String[] shape) { + Builder shape(@ArrayLenRange(to = 3) String[] shape) { + if(shape == null) { + return this; + } + if(shape.length > 3) { Log.warn("Shape for %s has too many lines", recipe.keyValue); return this; @@ -242,6 +239,9 @@ Builder shape(String[] shape) { } Builder cooking(float experience, int cookingTime) { + if(experience == 0 && cookingTime == 0) { + return this; + } if(experience < 0) { Log.warn("Furnace experience is negative in recipe %s", recipe.keyValue); return this; @@ -255,8 +255,11 @@ Builder cooking(float experience, int cookingTime) { return this; } - Builder smithing(String rawBase, String rawAddition, String rawTemplate) { - if(rawBase == null && rawAddition == null && rawTemplate == null) { + Builder smithing(String rawBase, + String rawAddition, + String rawTemplate) { + + if(rawBase == null || rawAddition == null || rawTemplate == null) { return this; } diff --git a/src/main/java/re/sylfa/itemcreator/recipes/RecipeReader.java b/src/main/java/re/sylfa/itemcreator/recipes/RecipeReader.java index 0f25d7f..698f2f0 100644 --- a/src/main/java/re/sylfa/itemcreator/recipes/RecipeReader.java +++ b/src/main/java/re/sylfa/itemcreator/recipes/RecipeReader.java @@ -1,17 +1,17 @@ package re.sylfa.itemcreator.recipes; -import java.io.File; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - import org.apache.commons.io.FilenameUtils; import org.bukkit.configuration.file.YamlConfiguration; - import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.util.JavaUtils; import re.sylfa.itemcreator.util.Log; +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + public class RecipeReader { public static List readAllRecipes() { @@ -22,16 +22,30 @@ public static List readAllRecipes() { return List.of(); } - return Arrays.stream(recipesFolder.listFiles()) - .map(file -> { - if(!List.of("yml","yaml").contains(FilenameUtils.getExtension(file.getPath()))) { - Log.warn("%s is not an recipe file", file.getName()); - return null; + return JavaUtils.arrayStream(recipesFolder.listFiles()) + .filter(file -> { + if(!file.isDirectory()) { + Log.warn("%s is not a recipe namespace", file.getName()); + return false; } else { - return RecipeReader.readRecipe(file); + return true; } }) - .filter(file -> file != null) + .flatMap(folder -> { + String folderName = folder.getName(); + return JavaUtils.arrayStream(folder.listFiles()) + .filter(file -> { + if(!"json".equals(FilenameUtils.getExtension(file.getPath()))) { + Log.warn("%s is not a recipe file in %s", file.getName(), folderName); + return false; + } else { + return true; + } + }) + .map(RecipeReader::readRecipe); + }) + + .filter(Objects::nonNull) .toList(); } @@ -41,7 +55,8 @@ public static CustomRecipe readRecipe(File file) { } public static CustomRecipe readRecipe(YamlConfiguration config, String fileName) { - Log.log("Reading recipe %s", fileName); + // TODO toggleable in configuration +// Log.log("Reading recipe %s", fileName); Map shapedIngredients; if(config.isSet("shapedIngredients")) { shapedIngredients = config.getConfigurationSection("shapedIngredients").getValues(false) diff --git a/src/main/java/re/sylfa/itemcreator/recipes/RecipeRegistry.java b/src/main/java/re/sylfa/itemcreator/recipes/RecipeRegistry.java index 81e9f6f..2a5dfa4 100644 --- a/src/main/java/re/sylfa/itemcreator/recipes/RecipeRegistry.java +++ b/src/main/java/re/sylfa/itemcreator/recipes/RecipeRegistry.java @@ -1,12 +1,12 @@ package re.sylfa.itemcreator.recipes; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; + import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import org.bukkit.Keyed; -import org.bukkit.NamespacedKey; - public class RecipeRegistry { Map recipes = new HashMap(); diff --git a/src/main/java/re/sylfa/itemcreator/util/JavaUtils.java b/src/main/java/re/sylfa/itemcreator/util/JavaUtils.java new file mode 100644 index 0000000..fd93262 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/util/JavaUtils.java @@ -0,0 +1,20 @@ +package re.sylfa.itemcreator.util; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public interface JavaUtils { + static Stream arrayStream(T[] array) { + return array != null + ? Arrays.stream(array) + : Stream.empty(); + } + + static Stream iteratorStream(Iterator iterator) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false); + } +} diff --git a/src/main/java/re/sylfa/itemcreator/util/Log.java b/src/main/java/re/sylfa/itemcreator/util/Log.java index f700bf5..c9c067b 100644 --- a/src/main/java/re/sylfa/itemcreator/util/Log.java +++ b/src/main/java/re/sylfa/itemcreator/util/Log.java @@ -1,27 +1,32 @@ package re.sylfa.itemcreator.util; +import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.Bukkit; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -public class Log { - private static String PREFIX = "[ItemCreator] "; +public interface Log { + String PREFIX = "[ItemCreator] "; - public static void log(Component message){ + static void log(Component message){ Bukkit.getConsoleSender().sendMessage(Component.text(PREFIX).append(message)); } - public static void log(String message, Object... args){ + static void log(String message, Object... args){ Log.log(Component.text(String.format(message, args))); } - public static void warn(String message, Object... args){ + static void warn(String message, Object... args){ Log.warn(Component.text(String.format(message, args))); } - public static void warn(Component message){ + static void warn(Component message){ log(message.color(NamedTextColor.RED)); } + + static void error(Component message) { log(message.color(NamedTextColor.DARK_RED).decorate(TextDecoration.BOLD));} + + static void error(String message, Object... args) { Log.error(Component.text(String.format(message, args)));} } diff --git a/src/main/java/re/sylfa/itemcreator/util/Parsers.java b/src/main/java/re/sylfa/itemcreator/util/Parsers.java new file mode 100644 index 0000000..50c6ef3 --- /dev/null +++ b/src/main/java/re/sylfa/itemcreator/util/Parsers.java @@ -0,0 +1,121 @@ +package re.sylfa.itemcreator.util; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.checkerframework.checker.nullness.qual.NonNull; +import re.sylfa.itemcreator.ItemCreator; +import re.sylfa.itemcreator.deserializers.components.ToolRuleDeserializer; +import re.sylfa.itemcreator.items.ItemKey; +import tools.jackson.databind.JsonNode; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Predicate; + +public interface Parsers { + + static Optional getNodeValue(@NonNull JsonNode node, @NonNull String valueName, @NonNull Predicate nodePredicate, @NonNull Class valueClass) { + JsonNode foundNode = node.get(valueName); + return foundNode != null && nodePredicate.test(foundNode) + ? Optional.ofNullable(ItemCreator.getMapper().convertValue(foundNode, valueClass)) + : Optional.empty(); + } + + static Optional getDoubleNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isNumber, Double.class); + } + + static Optional getFloatNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isNumber, Float.class); + } + + static Optional getStringNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isString, String.class); + } + + static Optional getBooleanNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isBoolean, Boolean.class); + } + + static Optional getMaterialNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isString, Material.class); + } + + static Optional getComponentArrayNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + JsonNode foundNode = node.get(valueName); + if(foundNode == null || !foundNode.isArray()) { + return Optional.empty(); + } + List components = new ArrayList<>(); + for(JsonNode line : foundNode) { + + components.add(ItemCreator.getMapper().convertValue(line, Component.class)); + } + return components.isEmpty() + ? Optional.empty() + : Optional.of(components.toArray(Component[]::new)); + } + + static Optional> getToolRulesArrayNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + JsonNode foundNode = node.get(valueName); + if(foundNode == null || !foundNode.isArray()) { + return Optional.empty(); + } + + List ruleValues = new ArrayList<>(); + for(JsonNode rule : foundNode) { + if(!rule.isObject()) { + continue; + } + ToolRuleDeserializer.RuleValues ruleValue = ItemCreator.getMapper().convertValue(rule, ToolRuleDeserializer.RuleValues.class); + if(ruleValue != null) { + ruleValues.add(ruleValue); + } + } + return Optional.of(ruleValues); + } + + static Optional getIntNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isIntegralNumber, Integer.class); + } + + static Optional getComponentNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isString, Component.class); + } + + static Optional getNamespacedKeyNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isString, NamespacedKey.class); + } + + static Optional getKeyNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isString, Key.class); + } + + static Optional getItemStackNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, jsonNode -> jsonNode.isString() || jsonNode.isObject(), ItemStack.class); + } + + static Optional getItemKeyNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, jsonNode -> jsonNode.isString() || jsonNode.isObject(), ItemKey.class); + } + + static Optional getPotionEffectNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, JsonNode::isObject, PotionEffect.class); + } + + static Optional getColorNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, Objects::nonNull, Color.class); + } + + static Optional getUUIDNodeValue(@NonNull JsonNode node, @NonNull String valueName) { + return getNodeValue(node, valueName, n -> n.isString() && !n.isEmpty(), UUID.class); + } +}