diff --git a/README.md b/README.md index 29b3c4950..6e4078716 100644 --- a/README.md +++ b/README.md @@ -129,11 +129,10 @@ The code you contribute will be open-sourced under the GPLv3 license. If you pre 3. Once done, submit a **pull request** for review. We appreciate your contributions! ## Differences Between Versions -| Version | Official Support | Max Players | Online Mode Required | Commercial Use | -|-------------------|------------------|-------------|----------------------|----------------| -| Community Edition | ❌ No | 20 | ✔️ Yes | ✔️ Allowed | -| GitHub Edition | ❌ No | Unlimited | ❌ No | ✔️ Allowed | -| Premium Edition | ✔️ Yes | Unlimited | ❌ No | ✔️ Allowed | +| Version | Official Support | Max Players | Dev Builds | +|-------------------|------------------|-------------|------------| +| Community Edition | ❌ No | 20 | ❌ No | +| Premium Edition | ✔️ Yes | Unlimited | ✔️ Yes | ### 💖 Support the Developer Help sustain CraftEngine's development by going Premium! diff --git a/bukkit/build.gradle.kts b/bukkit/build.gradle.kts index 0b9d4ae93..b1d1e570b 100644 --- a/bukkit/build.gradle.kts +++ b/bukkit/build.gradle.kts @@ -5,8 +5,9 @@ plugins { repositories { maven("https://jitpack.io/") - maven("https://repo.papermc.io/repository/maven-public/") maven("https://repo.momirealms.net/releases/") + maven("https://libraries.minecraft.net/") + maven("https://repo.papermc.io/repository/maven-public/") mavenCentral() } @@ -18,6 +19,10 @@ dependencies { compileOnly("net.momirealms:antigrieflib:${rootProject.properties["anti_grief_version"]}") // NBT compileOnly("net.momirealms:sparrow-nbt:${rootProject.properties["sparrow_nbt_version"]}") + compileOnly("net.momirealms:sparrow-nbt-adventure:${rootProject.properties["sparrow_nbt_version"]}") + compileOnly("net.momirealms:sparrow-nbt-codec:${rootProject.properties["sparrow_nbt_version"]}") + compileOnly("net.momirealms:sparrow-nbt-legacy-codec:${rootProject.properties["sparrow_nbt_version"]}") + // Util compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}") // NMS compileOnly("net.momirealms:craft-engine-nms-helper:${rootProject.properties["nms_helper_version"]}") @@ -46,7 +51,6 @@ dependencies { compileOnly("com.saicone.rtag:rtag-item:${rootProject.properties["rtag_version"]}") // Adventure compileOnly("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") - compileOnly("net.kyori:adventure-platform-bukkit:${rootProject.properties["adventure_platform_version"]}") compileOnly("net.kyori:adventure-text-minimessage:${rootProject.properties["adventure_bundle_version"]}") compileOnly("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}") { exclude("com.google.code.gson", "gson") diff --git a/bukkit/compatibility/build.gradle.kts b/bukkit/compatibility/build.gradle.kts index db481a7c5..55151de14 100644 --- a/bukkit/compatibility/build.gradle.kts +++ b/bukkit/compatibility/build.gradle.kts @@ -41,6 +41,8 @@ dependencies { compileOnly("com.viaversion:viaversion-api:5.3.2") // Skript compileOnly("com.github.SkriptLang:Skript:2.11.0") + // AuraSkills + compileOnly("dev.aurelium:auraskills-api-bukkit:2.2.4") // FAWE compileOnly(platform("com.intellectualsites.bom:bom-newest:1.52")) compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core") diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java index c4888be8c..4e00a179d 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/BukkitCompatibilityManager.java @@ -5,6 +5,7 @@ import net.momirealms.craftengine.bukkit.compatibility.bettermodel.BetterModelMo import net.momirealms.craftengine.bukkit.compatibility.item.MMOItemsProvider; import net.momirealms.craftengine.bukkit.compatibility.item.NeigeItemsProvider; import net.momirealms.craftengine.bukkit.compatibility.legacy.slimeworld.LegacySlimeFormatStorageAdaptor; +import net.momirealms.craftengine.bukkit.compatibility.leveler.AuraSkillsLevelerProvider; import net.momirealms.craftengine.bukkit.compatibility.modelengine.ModelEngineModel; import net.momirealms.craftengine.bukkit.compatibility.modelengine.ModelEngineUtils; import net.momirealms.craftengine.bukkit.compatibility.papi.PlaceholderAPIUtils; @@ -16,24 +17,36 @@ import net.momirealms.craftengine.bukkit.compatibility.worldedit.WorldEditBlockR import net.momirealms.craftengine.bukkit.font.BukkitFontManager; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.core.entity.furniture.AbstractExternalModel; +import net.momirealms.craftengine.core.entity.furniture.ExternalModel; import net.momirealms.craftengine.core.entity.player.Player; -import net.momirealms.craftengine.core.plugin.CompatibilityManager; +import net.momirealms.craftengine.core.plugin.compatibility.CompatibilityManager; +import net.momirealms.craftengine.core.plugin.compatibility.LevelerProvider; +import net.momirealms.craftengine.core.plugin.compatibility.ModelProvider; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.WorldManager; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; import java.util.UUID; public class BukkitCompatibilityManager implements CompatibilityManager { private final BukkitCraftEngine plugin; + private final Map modelProviders; + private final Map levelerProviders; private boolean hasPlaceholderAPI; private boolean hasViaVersion; public BukkitCompatibilityManager(BukkitCraftEngine plugin) { this.plugin = plugin; + this.modelProviders = new HashMap<>(Map.of( + "ModelEngine", ModelEngineModel::new, + "BetterModel", BetterModelModel::new + )); + this.levelerProviders = new HashMap<>(); } @Override @@ -67,11 +80,6 @@ public class BukkitCompatibilityManager implements CompatibilityManager { // } // } } - } - - @Override - public void onDelayedEnable() { - this.initItemHooks(); // WorldEdit if (this.isPluginEnabled("FastAsyncWorldEdit")) { this.initFastAsyncWorldEditHook(); @@ -80,10 +88,24 @@ public class BukkitCompatibilityManager implements CompatibilityManager { this.initWorldEditHook(); logHook("WorldEdit"); } + } + + @Override + public void onDelayedEnable() { + this.initItemHooks(); + if (this.isPluginEnabled("LuckPerms")) { this.initLuckPermsHook(); logHook("LuckPerms"); } + if (this.isPluginEnabled("AuraSkills")) { + this.registerLevelerProvider("AuraSkills", new AuraSkillsLevelerProvider()); + } + } + + @Override + public void registerLevelerProvider(String plugin, LevelerProvider provider) { + this.levelerProviders.put(plugin, provider); } private void logHook(String plugin) { @@ -91,13 +113,22 @@ public class BukkitCompatibilityManager implements CompatibilityManager { } @Override - public AbstractExternalModel createModelEngineModel(String id) { - return new ModelEngineModel(id); + public void addLevelerExp(Player player, String plugin, String target, double value) { + Optional.ofNullable(this.levelerProviders.get(plugin)).ifPresentOrElse(leveler -> leveler.addExp(player, target, value), + () -> this.plugin.logger().warn("[Compatibility] '" + plugin + "' leveler provider not found")); } @Override - public AbstractExternalModel createBetterModelModel(String id) { - return new BetterModelModel(id); + public int getLevel(Player player, String plugin, String target) { + return Optional.ofNullable(this.levelerProviders.get(plugin)).map(leveler -> leveler.getLevel(player, target)).orElseGet(() -> { + this.plugin.logger().warn("[Compatibility] '" + plugin + "' leveler provider not found"); + return 0; + }); + } + + @Override + public ExternalModel createModel(String plugin, String id) { + return this.modelProviders.get(plugin).createModel(id); } @Override diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/leveler/AuraSkillsLevelerProvider.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/leveler/AuraSkillsLevelerProvider.java new file mode 100644 index 000000000..9189f2e30 --- /dev/null +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/leveler/AuraSkillsLevelerProvider.java @@ -0,0 +1,19 @@ +package net.momirealms.craftengine.bukkit.compatibility.leveler; + +import dev.aurelium.auraskills.api.AuraSkillsApi; +import dev.aurelium.auraskills.api.registry.NamespacedId; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.compatibility.LevelerProvider; + +public class AuraSkillsLevelerProvider implements LevelerProvider { + + @Override + public void addExp(Player player, String target, double amount) { + AuraSkillsApi.get().getUser(player.uuid()).addSkillXp(AuraSkillsApi.get().getGlobalRegistry().getSkill(NamespacedId.fromDefault(target)), amount); + } + + @Override + public int getLevel(Player player, String target) { + return AuraSkillsApi.get().getUser(player.uuid()).getSkillLevel(AuraSkillsApi.get().getGlobalRegistry().getSkill(NamespacedId.fromDefault(target))); + } +} diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/effect/EffRemoveFurniture.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/effect/EffRemoveFurniture.java index 750dd937c..5996a8ae7 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/effect/EffRemoveFurniture.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/skript/effect/EffRemoveFurniture.java @@ -6,7 +6,7 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.SkriptParser; import ch.njol.util.Kleenean; import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture; -import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; +import net.momirealms.craftengine.core.entity.furniture.Furniture; import org.bukkit.entity.Entity; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; @@ -23,9 +23,9 @@ public class EffRemoveFurniture extends Effect { protected void execute(Event e) { for (Entity entity : entities.getArray(e)) { if (CraftEngineFurniture.isFurniture(entity)) { - LoadedFurniture loadedFurniture = CraftEngineFurniture.getLoadedFurnitureByBaseEntity(entity); - if (loadedFurniture != null) { - loadedFurniture.destroy(); + Furniture bukkitFurniture = CraftEngineFurniture.getLoadedFurnitureByBaseEntity(entity); + if (bukkitFurniture != null) { + bukkitFurniture.destroy(); } } } diff --git a/bukkit/legacy/src/main/java/net/momirealms/craftengine/bukkit/util/LegacyInventoryUtils.java b/bukkit/legacy/src/main/java/net/momirealms/craftengine/bukkit/util/LegacyInventoryUtils.java index c2249eced..f009e3583 100644 --- a/bukkit/legacy/src/main/java/net/momirealms/craftengine/bukkit/util/LegacyInventoryUtils.java +++ b/bukkit/legacy/src/main/java/net/momirealms/craftengine/bukkit/util/LegacyInventoryUtils.java @@ -37,4 +37,32 @@ public class LegacyInventoryUtils { public static InventoryView getView(PrepareAnvilEvent event) { return event.getView(); } + + public static void openAnvil(Player player) { + player.openAnvil(null, true); + } + + public static void openCartographyTable(Player player) { + player.openCartographyTable(null, true); + } + + public static void openEnchanting(Player player) { + player.openEnchanting(null, true); + } + + public static void openGrindstone(Player player) { + player.openGrindstone(null, true); + } + + public static void openLoom(Player player) { + player.openLoom(null, true); + } + + public static void openSmithingTable(Player player) { + player.openSmithingTable(null, true); + } + + public static void openWorkbench(Player player) { + player.openWorkbench(null, true); + } } diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index 5085f47dc..f90ee42e0 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -21,7 +21,6 @@ dependencies { implementation(project(":bukkit:compatibility")) implementation(project(":bukkit:compatibility:legacy")) - implementation("net.kyori:adventure-platform-bukkit:${rootProject.properties["adventure_platform_version"]}") implementation("com.saicone.rtag:rtag-item:${rootProject.properties["rtag_version"]}") implementation("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}") implementation("net.momirealms:antigrieflib:${rootProject.properties["anti_grief_version"]}") diff --git a/bukkit/loader/src/main/resources/commands.yml b/bukkit/loader/src/main/resources/commands.yml index fdf4e1613..d600a8e7c 100644 --- a/bukkit/loader/src/main/resources/commands.yml +++ b/bukkit/loader/src/main/resources/commands.yml @@ -175,6 +175,13 @@ debug_is_section_injected: - /craftengine debug is-section-injected - /ce debug is-section-injected +debug_clear_cooldown: + enable: true + permission: ce.command.debug.clear_cooldown + usage: + - /craftengine debug clear-cooldown + - /ce debug clear-cooldown + debug_test: enable: true permission: ce.command.debug.test diff --git a/bukkit/loader/src/main/resources/config.yml b/bukkit/loader/src/main/resources/config.yml index e42bbb4be..b01304628 100644 --- a/bukkit/loader/src/main/resources/config.yml +++ b/bukkit/loader/src/main/resources/config.yml @@ -62,11 +62,10 @@ resource-pack: remove-tinted-leaves-particle: true merge-external-folders: - ModelEngine/resource pack - - CustomNameplates/ResourcePack - BetterModel/build - - BetterHud/build merge-external-zip-files: - - CraftEngine/external_packs/example.zip + - CustomNameplates/resourcepack.zip + exclude-file-extensions: ["md", "psd", "bbmodel", "db", "ini"] delivery: # Send the resource pack on joining the server send-on-join: true @@ -85,8 +84,8 @@ resource-pack: deny-non-minecraft-request: true one-time-token: true rate-limit: - max-requests: 3 - reset-interval: 20 + max-requests: 10 + reset-interval: 30 # Upload the resource pack automatically on generation # When disabled, you must manually trigger uploads using the /ce upload command auto-upload: true @@ -215,6 +214,7 @@ image: entity-name: false armor-stand: true # Legacy Holograms text-display: true # Modern Holograms + item: true # Defines Unicode characters used for positioning # - Must match the font defined in resource packs # - Do NOT modify unless you understand text rendering mechanics @@ -343,8 +343,6 @@ performance: light-system: # Required for custom light-emitting blocks enable: true - # Turning this option on will reduce lighting system issues to some extent, but will increase server bandwidth consumption. - force-update-light: false chunk-system: # With cache system, those frequently load/unload chunks would consume fewer resources on serialization diff --git a/bukkit/loader/src/main/resources/craft-engine.properties b/bukkit/loader/src/main/resources/craft-engine.properties index 9e875e126..d8ee70d9f 100644 --- a/bukkit/loader/src/main/resources/craft-engine.properties +++ b/bukkit/loader/src/main/resources/craft-engine.properties @@ -23,10 +23,9 @@ commons-io=${commons_io_version} commons-imaging=${commons_imaging_version} byte-buddy=${byte_buddy_version} snake-yaml=${snake_yaml_version} -adventure-text-minimessage=${adventure_bundle_version} -adventure-text-serializer-gson=${adventure_bundle_version} -adventure-text-serializer-json=${adventure_bundle_version} -adventure-text-serializer-json-legacy-impl=${adventure_bundle_version} +examination-api=1.3.0 +option=1.1.0 +adventure-api=${adventure_bundle_version} netty-codec-http=${netty_version} ahocorasick=${ahocorasick_version} lz4=${lz4_version} diff --git a/bukkit/loader/src/main/resources/mappings.yml b/bukkit/loader/src/main/resources/mappings.yml index 5038f1f8f..94fa679ec 100644 --- a/bukkit/loader/src/main/resources/mappings.yml +++ b/bukkit/loader/src/main/resources/mappings.yml @@ -1328,70 +1328,70 @@ minecraft:tripwire[attached=true,disarmed=false,east=true,north=true,south=true, ######################################################################################################################################################################################################################## # Can make transparent blocks, but the collision shape is relatively random. Not as useful as leaves. # Chorus Plant -#minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true]# -minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] -minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +##minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true]# +#minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=false,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=false]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=false,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=false,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=true,south=false,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=true,north=false,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=false,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=true,east=false,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] +#minecraft:chorus_plant[down=false,east=true,north=true,south=true,up=true,west=true]: minecraft:chorus_plant[down=true,east=true,north=true,south=true,up=true,west=true] ######################################################################################################################################################################################################################## # Ideal block for making 1x1x1 interactive blocks. Best substitute when other blocks are insufficient in appearance. # Note Block diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/blocks.yml b/bukkit/loader/src/main/resources/resources/default/configuration/blocks.yml index 02ea65f95..db308dcbc 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/blocks.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/blocks.yml @@ -21,11 +21,9 @@ items#misc: - default:hardness/wool - default:burn_data/planks - default:sound/wood + - default:settings/solid_1x1x1 overrides: push-reaction: NORMAL - replaceable: false - is-redstone-conductor: true - is-suffocating: false instrument: HARP luminance: 15 map-color: 36 @@ -61,6 +59,15 @@ items#misc: type: falling_block hurt-amount: 4 max-hurt: 80 + events: + - on: right_click + functions: + - type: open_window + gui-type: anvil + - type: cancel_event + conditions: + - type: expression + expression: "!" settings: template: - default:pickaxe_power/level_4 @@ -125,9 +132,10 @@ items#misc: behavior: type: block_item block: - behavior: - type: concrete_powder_block - solid-block: default:solid_gunpowder_block + behaviors: + - type: concrete_powder_block + solid-block: default:solid_gunpowder_block + - type: falling_block loot: template: "default:loot_table/basic" arguments: @@ -135,12 +143,10 @@ items#misc: settings: template: - default:sound/sand + - default:settings/solid_1x1x1 overrides: hardness: 0.5 resistance: 0.5 - replaceable: false - is-redstone-conductor: true - is-suffocating: true instrument: SNARE map-color: 45 item: default:gunpowder_block @@ -174,12 +180,10 @@ items#misc: template: - default:sound/sand - default:pickaxe_power/level_1 + - default:settings/solid_1x1x1 overrides: hardness: 1.8 resistance: 1.8 - replaceable: false - is-redstone-conductor: true - is-suffocating: true instrument: BASEDRUM map-color: 45 item: default:solid_gunpowder_block @@ -192,6 +196,75 @@ items#misc: parent: "minecraft:block/cube_all" textures: "all": "minecraft:block/custom/solid_gunpowder_block" + default:copper_coil: + material: nether_brick + custom-model-data: 3004 + data: + item-name: "" + model: + type: "minecraft:model" + path: "minecraft:item/custom/copper_coil" + generation: + parent: "minecraft:block/custom/copper_coil" + behavior: + type: block_item + block: + loot: + template: "default:loot_table/basic" + arguments: + item: default:copper_coil + settings: + template: + - default:sound/metal + - default:pickaxe_power/level_1 + overrides: + hardness: 3.0 + resistance: 4.5 + replaceable: false + is-redstone-conductor: true + is-suffocating: true + instrument: BASEDRUM + map-color: 15 + item: default:copper_coil + behavior: + type: lamp_block + states: + properties: + lit: + type: boolean + default: false + appearances: + off: + state: "cactus:0" + model: + path: "minecraft:block/custom/copper_coil" + generation: + parent: "minecraft:block/cactus" + textures: + "particle": "minecraft:block/custom/copper_coil" + "bottom": "minecraft:block/custom/copper_coil" + "top": "minecraft:block/custom/copper_coil" + "side": "minecraft:block/custom/copper_coil_side" + on: + state: "cactus:1" + model: + path: "minecraft:block/custom/copper_coil_on" + generation: + parent: "minecraft:block/cactus" + textures: + "particle": "minecraft:block/custom/copper_coil_on" + "bottom": "minecraft:block/custom/copper_coil_on" + "top": "minecraft:block/custom/copper_coil_on" + "side": "minecraft:block/custom/copper_coil_on_side" + variants: + lit=false: + appearance: "off" + id: 0 + lit=true: + appearance: "on" + id: 1 + settings: + luminance: 8 recipes#misc: default:chinese_lantern: type: shaped @@ -235,4 +308,15 @@ recipes#misc: A: "minecraft:gunpowder" result: id: default:gunpowder_block + count: 1 + default:copper_coil: + type: shaped + pattern: + - "AAA" + - "A A" + - "AAA" + ingredients: + A: "minecraft:copper_ingot" + result: + id: default:copper_coil count: 1 \ No newline at end of file diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/categories.yml b/bukkit/loader/src/main/resources/resources/default/configuration/categories.yml index fd442e990..d111185e9 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/categories.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/categories.yml @@ -65,4 +65,6 @@ categories: - default:solid_gunpowder_block - default:ender_pearl_flower_seeds - default:gui_head_size_1 - - default:gui_head_size_4 \ No newline at end of file + - default:gui_head_size_4 + - minecraft:air + - default:copper_coil \ No newline at end of file diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/i18n.yml b/bukkit/loader/src/main/resources/resources/default/configuration/i18n.yml index 99e6d9705..602c85f0f 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/i18n.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/i18n.yml @@ -34,6 +34,7 @@ i18n: item.netherite_anvil: "Netherite Anvil" item.gunpowder_block: "GunPowder Block" item.solid_gunpowder_block: "Solid GunPowder Block" + item.copper_coil: "Copper Coil" category.default.name: "Default Assets" category.default.lore: "Contains the default configuration of CraftEngine" category.palm_tree: "Palm Tree" @@ -78,6 +79,7 @@ i18n: item.netherite_anvil: "下界合金砧" item.gunpowder_block: "火药粉末" item.solid_gunpowder_block: "凝固火药块" + item.copper_coil: "铜线圈" category.default.name: "默认资产" category.default.lore: "包含了CraftEngine的默认配置" category.palm_tree: "棕榈树" diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/palm_tree.yml b/bukkit/loader/src/main/resources/resources/default/configuration/palm_tree.yml index 610986d96..de1ec717e 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/palm_tree.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/palm_tree.yml @@ -229,16 +229,17 @@ items: template: "default:settings/sapling" overrides: item: default:palm_sapling - behavior: - type: sapling_block - # This requires you to register a custom tree configuration with data pack - # To prevent errors, we use tree feature from vanilla here - feature: minecraft:fancy_oak - bone-meal-success-chance: 0.45 - bottom-block-tags: - - minecraft:dirt - - minecraft:farmland - - minecraft:sand + behaviors: + - type: bush_block + bottom-block-tags: + - minecraft:dirt + - minecraft:farmland + - minecraft:sand + - type: sapling_block + # This requires you to register a custom tree configuration with data pack + # To prevent errors, we use tree feature from vanilla here + feature: minecraft:fancy_oak + bone-meal-success-chance: 0.45 loot: template: "default:loot_table/basic" arguments: diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/plants.yml b/bukkit/loader/src/main/resources/resources/default/configuration/plants.yml index 1b26e33b2..09e8cce5f 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/plants.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/plants.yml @@ -102,6 +102,8 @@ blocks: behavior: type: on_liquid_block liquid-type: water + positions: + - 0,-1,0 loot: template: "default:loot_table/basic" arguments: @@ -121,19 +123,31 @@ blocks: push-reaction: DESTROY map-color: 15 is-randomly-ticking: true - behavior: - type: sugar_cane_block - max-height: 4 - required-adjacent-liquids: lava - grow-speed: 0.333 - bottom-blocks: - - minecraft:netherrack - - minecraft:soul_sand - - minecraft:soul_soil - - minecraft:magma_block - - minecraft:warped_nylium - - minecraft:crimson_nylium - - minecraft:basalt + behaviors: + - type: vertical_crop_block + max-height: 4 + grow-speed: 0.333 + direction: up + - type: bush_block + stackable: true + delay: 1 + bottom-blocks: + - minecraft:netherrack + - minecraft:soul_sand + - minecraft:soul_soil + - minecraft:magma_block + - minecraft:warped_nylium + - minecraft:crimson_nylium + - minecraft:basalt + - type: near_liquid_block + liquid-type: lava + delay: 1 + stackable: true + positions: + - -1,-1,0 + - 1,-1,0 + - 0,-1,-1 + - 0,-1,1 loot: template: "default:loot_table/basic" arguments: @@ -189,20 +203,67 @@ blocks: push-reaction: DESTROY map-color: 24 is-randomly-ticking: true - behavior: - type: crop_block - grow-speed: 0.25 - light-requirement: 9 - is-bone-meal-target: true - bone-meal-age-bonus: 1 - bottom-blocks: - - minecraft:end_stone + behaviors: + - type: bush_block + bottom-blocks: + - minecraft:end_stone + - type: crop_block + grow-speed: 0.25 + light-requirement: 9 + is-bone-meal-target: true + bone-meal-age-bonus: 1 loot: template: default:loot_table/seed_crop arguments: crop_item: minecraft:ender_pearl crop_seed: default:ender_pearl_flower_seeds ripe_age: 2 + events: + - on: break + conditions: + - type: match_block_property + properties: + age: 2 + functions: + - type: particle + x: " + 0.5" + y: " + 0.5" + z: " + 0.5" + particle: "minecraft:end_rod" + count: 15 + offset-x: 0.05 + offset-y: 0.05 + offset-z: 0.05 + speed: 0.1 + - type: play_sound + sound: minecraft:entity.enderman.teleport + x: " + 0.5" + y: " + 0.5" + z: " + 0.5" + - on: right_click + conditions: + - type: match_block_property + properties: + age: 2 + - type: "!is_null" + argument: "item_in_hand" + - type: "equals" + value1: "" + value2: "default:ender_pearl_flower_seeds" + functions: + - type: break_block + x: "" + y: "" + z: "" + - type: place_block + x: "" + y: "" + z: "" + block-state: default:ender_pearl_flower[age=0] + - type: set_count + add: true + count: -1 + - type: swing_hand states: properties: age: diff --git a/bukkit/loader/src/main/resources/resources/default/configuration/templates.yml b/bukkit/loader/src/main/resources/resources/default/configuration/templates.yml index a281f7e41..4f0314a4e 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/templates.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/templates.yml @@ -25,7 +25,7 @@ templates#models#block: # arguments: # model: model_path # end_texture: end_texture_path - # end_texture: side_texture_path + # side_texture: side_texture_path default:model/cube_column: path: "{model}" generation: @@ -583,10 +583,13 @@ templates#settings#break_level: templates#settings#blocks: default:settings/solid_1x1x1: is-suffocating: true + replaceable: false is-view-blocking: true + is-redstone-conductor: true can-occlude: true default:settings/transparent_1x1x1: is-suffocating: false + replaceable: false is-view-blocking: false can-occlude: false # sapling diff --git a/bukkit/loader/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil.png b/bukkit/loader/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil.png new file mode 100644 index 000000000..40f04a5a8 Binary files /dev/null and b/bukkit/loader/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil.png differ diff --git a/bukkit/loader/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_on.png b/bukkit/loader/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_on.png new file mode 100644 index 000000000..3e59193cb Binary files /dev/null and b/bukkit/loader/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_on.png differ diff --git a/bukkit/loader/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_on_side.png b/bukkit/loader/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_on_side.png new file mode 100644 index 000000000..31b91e1a7 Binary files /dev/null and b/bukkit/loader/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_on_side.png differ diff --git a/bukkit/loader/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_side.png b/bukkit/loader/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_side.png new file mode 100644 index 000000000..859f0234b Binary files /dev/null and b/bukkit/loader/src/main/resources/resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_side.png differ diff --git a/bukkit/loader/src/main/resources/translations/en.yml b/bukkit/loader/src/main/resources/translations/en.yml index 2a6d99bac..31d995f66 100644 --- a/bukkit/loader/src/main/resources/translations/en.yml +++ b/bukkit/loader/src/main/resources/translations/en.yml @@ -91,9 +91,17 @@ warning.config.condition.match_item.missing_id: "Issue found in file Issue found in file - The config '' is missing the required 'enchantment' argument for 'table_bonus' condition." warning.config.condition.table_bonus.missing_chances: "Issue found in file - The config '' is missing the required 'chances' argument for 'table_bonus' condition." warning.config.condition.permission.missing_permission: "Issue found in file - The config '' is missing the required 'permission' argument for 'permission' condition." -warning.config.condition.equals.missing_value1: "Issue found in file - The config '' is missing the required 'value1' argument for 'equals' condition." -warning.config.condition.equals.missing_value2: "Issue found in file - The config '' is missing the required 'value2' argument for 'equals' condition." +warning.config.condition.string_equals.missing_value1: "Issue found in file - The config '' is missing the required 'value1' argument for 'string_equals' condition." +warning.config.condition.string_equals.missing_value2: "Issue found in file - The config '' is missing the required 'value2' argument for 'string_equals' condition." +warning.config.condition.string_contains.missing_value1: "Issue found in file - The config '' is missing the required 'value1' argument for 'string_contains' condition." +warning.config.condition.string_contains.missing_value2: "Issue found in file - The config '' is missing the required 'value2' argument for 'string_contains' condition." +warning.config.condition.string_regex.missing_value: "Issue found in file - The config '' is missing the required 'value' argument for 'string_regex' condition." +warning.config.condition.string_regex.missing_regex: "Issue found in file - The config '' is missing the required 'regex' argument for 'string_regex' condition." warning.config.condition.expression.missing_expression: "Issue found in file - The config '' is missing the required 'expression' argument for 'expression' condition." +warning.config.condition.is_null.missing_argument: "Issue found in file - The config '' is missing the required 'argument' argument for 'is_null' condition." +warning.config.condition.hand.missing_hand: "Issue found in file - The config '' is missing the required 'hand' argument for 'hand' condition." +warning.config.condition.hand.invalid_hand: "Issue found in file - The config '' is using an invalid 'hand' argument '' for 'hand' condition. Allowed hand types: []" +warning.config.condition.on_cooldown.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'on_cooldown' condition." warning.config.structure.not_section: "Issue found in file - The config '' is expected to be a config section while it's actually a(n) ''." warning.config.image.duplicate: "Issue found in file - Duplicated image ''. Please check if there is the same configuration in other files." warning.config.image.missing_height: "Issue found in file - The image '' is missing the required 'height' argument." @@ -145,14 +153,20 @@ warning.config.item.duplicate: "Issue found in file - Duplicated warning.config.item.settings.unknown: "Issue found in file - The item '' is using an unknown setting type ''." warning.config.item.missing_material: "Issue found in file - The item '' is missing the required 'material' argument." warning.config.item.invalid_material: "Issue found in file - The item '' is using an invalid material type ''." +warning.config.item.invalid_custom_model_data: "Issue found in file - The item '' is using a negative custom model data '' which is invalid." warning.config.item.bad_custom_model_data: "Issue found in file - The item '' is using a custom model data '' that is too large. It's recommended to use a value lower than 16,777,216." warning.config.item.custom_model_data_conflict: "Issue found in file - The item '' is using a custom model data '' that has been occupied by item ''." warning.config.item.missing_model_id: "Issue found in file - The item '' is missing the required 'custom-model-data' or 'item-model' argument." +warning.config.item.missing_model: "Issue found in file - The item '' is missing the required 'model' section for 1.21.4+ resource pack support." warning.config.item.behavior.missing_type: "Issue found in file - The item '' is missing the required 'type' argument for its item behavior." warning.config.item.behavior.invalid_type: "Issue found in file - The item '' is using an invalid item behavior type ''." warning.config.item.behavior.block.missing_block: "Issue found in file - The item '' is missing the required 'block' argument for 'block_item' behavior." warning.config.item.behavior.furniture.missing_furniture: "Issue found in file - The item '' is missing the required 'furniture' argument for 'furniture_item' behavior." warning.config.item.behavior.liquid_collision.missing_block: "Issue found in file - The item '' is missing the required 'block' argument for 'liquid_collision_block_item' behavior." +warning.config.item.legacy_model.missing_path: "Issue found in file - The item '' is missing the require 'path' argument for legacy-model." +warning.config.item.legacy_model.overrides.missing_path: "Issue found in file - The item '' is missing the require 'path' argument for legacy-model overrides." +warning.config.item.legacy_model.overrides.missing_predicate: "Issue found in file - The item '' is missing the require 'predicate' argument for legacy-model overrides." +warning.config.item.legacy_model.cannot_convert: "Issue found in file - Cannot convert 1.21.4+ items to legacy format for item ''. Please manually create 'legacy-model' section for this item." warning.config.item.model.invalid_type: "Issue found in file - The item '' is using an invalid model type ''." warning.config.item.model.tint.missing_type: "Issue found in file - The item '' is missing the required 'type' argument for tint." warning.config.item.model.tint.invalid_type: "Issue found in file - The item '' is using an invalid tint type ''." @@ -225,6 +239,7 @@ warning.config.block.behavior.crop.missing_age: "Issue found in file Issue found in file - The block '' is missing the required 'age' property for 'sugar_cane_block' behavior." warning.config.block.behavior.leaves.missing_persistent: "Issue found in file - The block '' is missing the required 'persistent' property for 'leaves_block' behavior." warning.config.block.behavior.leaves.missing_distance: "Issue found in file - The block '' is missing the required 'distance' property for 'leaves_block' behavior." +warning.config.block.behavior.lamp.missing_lit: "Issue found in file - The block '' is missing the required 'lit' property for 'lamp_block' behavior." warning.config.block.behavior.sapling.missing_stage: "Issue found in file - The block '' is missing the required 'stage' property for 'sapling_block' behavior." warning.config.block.behavior.sapling.missing_feature: "Issue found in file - The block '' is missing the required 'feature' argument for 'sapling_block' behavior." warning.config.block.behavior.strippable.missing_stripped: "Issue found in file - The block '' is missing the required 'stripped' argument for 'strippable_block' behavior." @@ -308,6 +323,27 @@ warning.config.function.invalid_type: "Issue found in file - The warning.config.function.command.missing_command: "Issue found in file - The config '' is missing the required 'command' argument for 'command' function." warning.config.function.actionbar.missing_actionbar: "Issue found in file - The config '' is missing the required 'actionbar' argument for 'actionbar' function." warning.config.function.message.missing_message: "Issue found in file - The config '' is missing the required 'message' argument for 'message' function." +warning.config.function.open_window.missing_gui_type: "Issue found in file - The config '' is missing the required 'gui-type' argument for 'open_window' function." +warning.config.function.open_window.invalid_gui_type: "Issue found in file - The config '' is using an invalid gui type for 'open_window' function. Allowed types: []." +warning.config.function.run.missing_functions: "Issue found in file - The config '' is missing the required 'functions' argument for 'run' function." +warning.config.function.place_block.missing_block_state: "Issue found in file - The config '' is missing the required 'block-state' argument for 'place_block' function." +warning.config.function.set_food.missing_food: "Issue found in file - The config '' is missing the required 'food' argument for 'set_food' function." +warning.config.function.set_saturation.missing_saturation: "Issue found in file - The config '' is missing the required 'saturation' argument for 'set_saturation' function." +warning.config.function.play_sound.missing_sound: "Issue found in file - The config '' is missing the required 'sound' argument for 'play_sound' function." +warning.config.function.particle.missing_particle: "Issue found in file - The config '' is missing the required 'particle' argument for 'particle' function." +warning.config.function.particle.missing_color: "Issue found in file - The config '' is missing the required 'color' argument for 'particle' function." +warning.config.function.particle.missing_from: "Issue found in file - The config '' is missing the required 'from' argument for 'particle' function." +warning.config.function.particle.missing_to: "Issue found in file - The config '' is missing the required 'to' argument for 'particle' function." +warning.config.function.particle.missing_item: "Issue found in file - The config '' is missing the required 'item' argument for 'particle' function." +warning.config.function.particle.missing_block_state: "Issue found in file - The config '' is missing the required 'block-state' argument for 'particle' function." +warning.config.function.leveler_exp.missing_count: "Issue found in file - The config '' is missing the required 'count' argument for 'leveler_exp' function." +warning.config.function.leveler_exp.missing_leveler: "Issue found in file - The config '' is missing the required 'leveler' argument for 'leveler_exp' function." +warning.config.function.leveler_exp.missing_plugin: "Issue found in file - The config '' is missing the required 'plugin' argument for 'leveler_exp' function." +warning.config.function.remove_potion_effect.missing_potion_effect: "Issue found in file - The config '' is missing the required 'potion-effect' argument for 'remove_potion_effect' function." +warning.config.function.potion_effect.missing_potion_effect: "Issue found in file - The config '' is missing the required 'potion-effect' argument for 'potion_effect' function." +warning.config.function.set_cooldown.missing_time: "Issue found in file - The config '' is missing the required 'time' argument for 'set_cooldown' function." +warning.config.function.set_cooldown.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'set_cooldown' function." +warning.config.function.remove_cooldown.missing_id: "Issue found in file - The config '' is missing the required 'id' argument for 'remove_cooldown' function." warning.config.selector.missing_type: "Issue found in file - The config '' is missing the required 'type' argument for selector." warning.config.selector.invalid_type: "Issue found in file - The config '' is using an invalid selector type ''." warning.config.selector.invalid_target: "Issue found in file - The config '' is using an invalid selector target ''." diff --git a/bukkit/loader/src/main/resources/translations/zh_cn.yml b/bukkit/loader/src/main/resources/translations/zh_cn.yml index f0f71519f..d9feadebf 100644 --- a/bukkit/loader/src/main/resources/translations/zh_cn.yml +++ b/bukkit/loader/src/main/resources/translations/zh_cn.yml @@ -90,9 +90,19 @@ warning.config.condition.match_block_property.missing_properties: "在 warning.config.condition.match_item.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'match_item' 条件所需的 'id' 参数" warning.config.condition.table_bonus.missing_enchantment: "在文件 发现问题 - 配置项 '' 缺少 'table_bonus' 条件所需的 'enchantment' 参数" warning.config.condition.table_bonus.missing_chances: "在文件 发现问题 - 配置项 '' 缺少 'table_bonus' 条件所需的 'chances' 参数" - - +warning.config.condition.permission.missing_permission: "在文件 中发现问题 - 配置项 '' 缺少 'permission' 条件必需的 'permission' 参数" +warning.config.condition.string_equals.missing_value1: "在文件 中发现问题 - 配置项 '' 缺少 'string_equals' 条件必需的 'value1' 参数" +warning.config.condition.string_equals.missing_value2: "在文件 中发现问题 - 配置项 '' 缺少 'string_equals' 条件必需的 'value2' 参数" +warning.config.condition.string_contains.missing_value1: "在文件 中发现问题 - 配置项 '' 缺少 'string_contains' 条件必需的 'value1' 参数" +warning.config.condition.string_contains.missing_value2: "在文件 中发现问题 - 配置项 '' 缺少 'string_contains' 条件必需的 'value2' 参数" +warning.config.condition.string_regex.missing_value: "在文件 中发现问题 - 配置项 '' 缺少 'string_regex' 条件必需的 'value' 参数" +warning.config.condition.string_regex.missing_regex: "在文件 中发现问题 - 配置项 '' 缺少 'string_regex' 条件必需的 'regex' 参数" +warning.config.condition.expression.missing_expression: "在文件 中发现问题 - 配置项 '' 缺少 'expression' 条件必需的 'expression' 参数" +warning.config.condition.is_null.missing_argument: "在文件 发现问题 - 配置项 '' 缺少 'is_null' 条件的必需的 'argument' 参数." warning.config.structure.not_section: "在文件 发现问题 - 配置项 '' 应为配置段落 但实际类型为 ''" +warning.config.condition.hand.missing_hand: "在文件 发现问题 - 配置项 '' 缺少 'hand' 条件必需的 'hand' 参数" +warning.config.condition.hand.invalid_hand: "在文件 发现问题 - 配置项 '' 使用了无效的 'hand' 参数 ''('hand' 条件)。允许的手部类型: []" +warning.config.condition.on_cooldown.missing_id: "在文件 发现问题 - 配置项 '' 缺少 'on_cooldown' 条件必需的 'id' 参数" warning.config.image.duplicate: "在文件 发现问题 - 重复的图片配置 '' 请检查其他文件中是否存在相同配置" warning.config.image.missing_height: "在文件 发现问题 - 图片 '' 缺少必需的 'height' 参数" warning.config.image.height_ascent_conflict: "在文件 发现问题 - 图片 '' 违反位图规则: 'height' 参数 '' 必须不小于 'ascent' 参数 ''" @@ -143,14 +153,20 @@ warning.config.item.duplicate: "在文件 发现问题 - 重复 warning.config.item.settings.unknown: "在文件 发现问题 - 物品 '' 使用了未知的设置类型 ''" warning.config.item.missing_material: "在文件 发现问题 - 物品 '' 缺少必需的 'material' 参数" warning.config.item.invalid_material: "在文件 发现问题 - 物品 '' 使用了无效的材料类型 ''" +warning.config.item.invalid_custom_model_data: "在文件 发现问题 - 物品 '' 使用了无效的负数模型值 ''." warning.config.item.bad_custom_model_data: "在文件 发现问题 - 物品 '' 使用的自定义模型数据 '' 数值过大 建议使用小于 16,777,216 的值" warning.config.item.custom_model_data_conflict: "在文件 发现问题 - 物品 '' 使用的自定义模型数据 '' 已被物品 '' 占用" warning.config.item.missing_model_id: "在文件 发现问题 - 物品 '' 缺少必需的 'custom-model-data' 或 'item-model' 参数" +warning.config.item.missing_model: "在文件 中发现问题 - 物品 '' 缺少支持 1.21.4+ 资源包必需的 'model' 配置项" warning.config.item.behavior.missing_type: "在文件 发现问题 - 物品 '' 的行为配置缺少必需的 'type' 参数" warning.config.item.behavior.invalid_type: "在文件 发现问题 - 物品 '' 使用了无效的行为类型 ''" warning.config.item.behavior.block.missing_block: "在文件 发现问题 - 物品 '' 的 'block_item' 行为缺少必需的 'block' 参数" warning.config.item.behavior.furniture.missing_furniture: "在文件 发现问题 - 物品 '' 的 'furniture_item' 行为缺少必需的 'furniture' 参数" warning.config.item.behavior.liquid_collision.missing_block: "在文件 发现问题 - 物品 '' 的 'liquid_collision_block_item' 行为缺少必需的 'block' 参数" +warning.config.item.legacy_model.missing_path: "在文件 中发现问题 - 物品 '' 的旧版模型(legacy-model)缺少必需的 'path' 参数" +warning.config.item.legacy_model.overrides.missing_path: "在文件 中发现问题 - 物品 '' 的旧版模型覆写规则(overrides)缺少必需的 'path' 参数" +warning.config.item.legacy_model.overrides.missing_predicate: "在文件 中发现问题 - 物品 '' 的旧版模型覆写规则(overrides)缺少必需的 'predicate' 参数" +warning.config.item.legacy_model.cannot_convert: "在文件 中发现问题 - 无法将物品 '' 自动转换为旧版格式,请手动为此物品创建 'legacy-model' 配置项" warning.config.item.model.invalid_type: "在文件 发现问题 - 物品 '' 使用了无效的模型类型 ''" warning.config.item.model.tint.missing_type: "在文件 发现问题 - 物品 '' 的染色配置缺少必需的 'type' 参数" warning.config.item.model.tint.invalid_type: "在文件 发现问题 - 物品 '' 使用了无效的染色类型 ''" @@ -223,6 +239,7 @@ warning.config.block.behavior.crop.missing_age: "在文件 发 warning.config.block.behavior.sugar_cane.missing_age: "在文件 发现问题 - 方块 '' 的 'sugar_cane_block' 行为缺少必需的 'age' 属性" warning.config.block.behavior.leaves.missing_persistent: "在文件 发现问题 - 方块 '' 的 'leaves_block' 行为缺少必需的 'persistent' 属性" warning.config.block.behavior.leaves.missing_distance: "在文件 发现问题 - 方块 '' 的 'leaves_block' 行为缺少必需的 'distance' 属性" +warning.config.block.behavior.lamp.missing_lit: "在文件 发现问题 - 方块 '' 的 'lamp_block' 行为缺少必需的 'lit' 属性" warning.config.block.behavior.sapling.missing_stage: "在文件 发现问题 - 方块 '' 的 'sapling_block' 行为缺少必需的 'stage' 属性" warning.config.block.behavior.sapling.missing_feature: "在文件 发现问题 - 方块 '' 的 'sapling_block' 行为缺少必需的 'feature' 参数" warning.config.block.behavior.strippable.missing_stripped: "在文件 发现问题 - 方块 '' 的 'strippable_block' 行为缺少必需的 'stripped' 参数" @@ -297,10 +314,39 @@ warning.config.conflict_matcher.all_of.missing_terms: "在 config.yml warning.config.conflict_matcher.any_of.missing_terms: "在 config.yml 的 'resource-pack.duplicated-files-handler' 处发现问题 - 任一匹配器缺少必需的 'terms' 参数" warning.config.conflict_resolution.missing_type: "在 config.yml 的 'resource-pack.duplicated-files-handler' 处发现问题 - 文件冲突处理器的某个解决方案缺少必需的 'type' 参数" warning.config.conflict_resolution.invalid_type: "在 config.yml 的 'resource-pack.duplicated-files-handler' 处发现问题 - 文件冲突处理器的某个解决方案使用了无效类型 ''" -warning.config.event.missing_trigger: "Issue found in file - The config '' is missing the required 'on' argument for event triggers." -warning.config.event.invalid_trigger: "Issue found in file - The config '' is using an invalid event trigger ''." +warning.config.event.missing_trigger: "在文件 中发现问题 - 配置项 '' 缺少事件触发器必需的 'on' 参数" +warning.config.event.invalid_trigger: "在文件 中发现问题 - 配置项 '' 使用了无效的事件触发器 ''" warning.config.event.condition.missing_type: "在文件 - 配置项 '' 的事件条件缺少 'type' 参数" warning.config.event.condition.invalid_type: "在文件 - 配置项 '' 使用了无效的事件条件类型 ''" -warning.config.function.missing_type: "Issue found in file - The config '' is missing the required 'type' argument for function." -warning.config.function.invalid_type: "Issue found in file - The config '' is using an invalid function type ''." -warning.config.function.command.missing_command: "Issue found in file - The config '' is missing the required 'command' argument for 'command' function." \ No newline at end of file +warning.config.function.missing_type: "在文件 中发现问题 - 配置项 '' 缺少函数必需的 'type' 参数" +warning.config.function.invalid_type: "在文件 中发现问题 - 配置项 '' 使用了无效的函数类型 ''" +warning.config.function.command.missing_command: "在文件 中发现问题 - 配置项 '' 缺少 'command' 函数必需的 'command' 参数" +warning.config.function.actionbar.missing_actionbar: "在文件 中发现问题 - 配置项 '' 缺少 'actionbar' 函数必需的 'actionbar' 参数" +warning.config.function.message.missing_message: "在文件 中发现问题 - 配置项 '' 缺少 'message' 函数必需的 'message' 参数" +warning.config.function.open_window.missing_gui_type: "在文件 中发现问题 - 配置项 '' 缺少 'open_window' 函数必需的 'gui-type' 参数" +warning.config.function.open_window.invalid_gui_type: "在文件 中发现问题 - 配置项 '' 为 'open_window' 函数使用了无效的 GUI 类型 . 允许的类型: []。" +warning.config.function.run.missing_functions: "在文件 中发现问题 - 配置项 '' 缺少 'run' 函数必需的 'functions' 参数" +warning.config.function.place_block.missing_block_state: "在文件 中发现问题 - 配置项 '' 缺少 'place_block' 函数必需的 'block-state' 参数." +warning.config.function.set_food.missing_food: "在文件 中发现问题 - 配置项 '' 缺少 'set_food' 函数必需的 'food' 参数" +warning.config.function.set_saturation.missing_saturation: "在文件 中发现问题 - 配置项 '' 缺少 'set_saturation' 函数必需的 'saturation' 参数" +warning.config.function.play_sound.missing_sound: "在文件 中发现问题 - 配置项 '' 缺少 'play_sound' 函数必需的 'sound' 参数" +warning.config.function.particle.missing_particle: "在文件 中发现问题 - 配置项 '' 缺少 'particle' 函数必需的 'particle' 参数" +warning.config.function.particle.missing_color: "在文件 中发现问题 - 配置项 '' 缺少 'particle' 函数必需的 'color' 参数" +warning.config.function.particle.missing_from: "在文件 中发现问题 - 配置项 '' 缺少 'particle' 函数必需的 'from' 参数" +warning.config.function.particle.missing_to: "在文件 中发现问题 - 配置项 '' 缺少 'particle' 函数必需的 'to' 参数" +warning.config.function.particle.missing_item: "在文件 中发现问题 - 配置项 '' 缺少 'particle' 函数必需的 'item' 参数" +warning.config.function.particle.missing_block_state: "在文件 中发现问题 - 配置项 '' 缺少 'particle' 函数必需的 'block-state' 参数" +warning.config.function.leveler_exp.missing_count: "在文件 中发现问题 - 配置项 '' 缺少 'leveler_exp' 函数必需的 'count' 参数" +warning.config.function.leveler_exp.missing_leveler: "在文件 中发现问题 - 配置项 '' 缺少 'leveler_exp' 函数必需的 'leveler' 参数" +warning.config.function.leveler_exp.missing_plugin: "在文件 中发现问题 - 配置项 '' 缺少 'leveler_exp' 函数必需的 'plugin' 参数" +warning.config.function.remove_potion_effect.missing_potion_effect: "在文件 中发现问题 - 配置项 '' 缺少 'remove_potion_effect' 函数必需的 'potion-effect' 参数" +warning.config.function.potion_effect.missing_potion_effect: "在文件 中发现问题 - 配置项 '' 缺少 'potion_effect' 函数必需的 'potion-effect' 参数" +warning.config.function.set_cooldown.missing_time: "在文件 中发现问题 - 配置项 '' 缺少 'set_cooldown' 函数必需的 'time' 参数" +warning.config.function.set_cooldown.missing_id: "在文件 中发现问题 - 配置项 '' 缺少 'set_cooldown' 函数必需的 'id' 参数" +warning.config.function.remove_cooldown.missing_id: "在文件 中发现问题 - 配置项 '' 缺少 'remove_cooldown' 函数必需的 'id' 参数" +warning.config.selector.missing_type: "在文件 中发现问题 - 配置项 '' 缺少选择器必需的 'type' 参数" +warning.config.selector.invalid_type: "在文件 中发现问题 - 配置项 '' 使用了无效的选择器类型 ''" +warning.config.selector.invalid_target: "在文件 中发现问题 - 配置项 '' 使用了无效的选择器目标 ''" +warning.config.resource_pack.item_model.conflict.vanilla: "无法为 '' 生成物品模型,因为该物品模型已被原版物品占用" +warning.config.resource_pack.item_model.already_exist: "无法为 '' 生成物品模型,因为文件 '' 已存在" +warning.config.resource_pack.model.generation.already_exist: "无法生成模型,因为模型文件 '' 已存在" \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java index f11ae4be3..3986509cf 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/BukkitAdaptors.java @@ -3,8 +3,11 @@ package net.momirealms.craftengine.bukkit.api; import net.momirealms.craftengine.bukkit.entity.BukkitEntity; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; import net.momirealms.craftengine.bukkit.world.BukkitWorld; +import net.momirealms.craftengine.core.world.WorldPosition; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Entity; @@ -29,4 +32,8 @@ public final class BukkitAdaptors { public static BukkitBlockInWorld adapt(final Block block) { return new BukkitBlockInWorld(block); } + + public static Location toLocation(WorldPosition position) { + return LocationUtils.toLocation(position); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineFurniture.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineFurniture.java index aa9616867..e899291a4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineFurniture.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineFurniture.java @@ -1,13 +1,14 @@ package net.momirealms.craftengine.bukkit.api; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; -import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; import net.momirealms.craftengine.bukkit.nms.CollisionEntity; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.core.entity.furniture.AnchorType; import net.momirealms.craftengine.core.entity.furniture.CustomFurniture; +import net.momirealms.craftengine.core.entity.furniture.Furniture; import net.momirealms.craftengine.core.entity.furniture.FurnitureExtraData; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.loot.LootTable; @@ -48,7 +49,7 @@ public final class CraftEngineFurniture { * @return the loaded furniture */ @Nullable - public static LoadedFurniture place(Location location, Key furnitureId) { + public static BukkitFurniture place(Location location, Key furnitureId) { CustomFurniture furniture = byId(furnitureId); if (furniture == null) return null; return place(location, furnitureId, furniture.getAnyPlacement()); @@ -63,7 +64,7 @@ public final class CraftEngineFurniture { * @return the loaded furniture */ @Nullable - public static LoadedFurniture place(Location location, Key furnitureId, AnchorType anchorType) { + public static BukkitFurniture place(Location location, Key furnitureId, AnchorType anchorType) { CustomFurniture furniture = byId(furnitureId); if (furniture == null) return null; return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), true); @@ -78,7 +79,7 @@ public final class CraftEngineFurniture { * @return the loaded furniture */ @NotNull - public static LoadedFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType) { + public static BukkitFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType) { return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), true); } @@ -92,7 +93,7 @@ public final class CraftEngineFurniture { * @return the loaded furniture */ @Nullable - public static LoadedFurniture place(Location location, Key furnitureId, AnchorType anchorType, boolean playSound) { + public static BukkitFurniture place(Location location, Key furnitureId, AnchorType anchorType, boolean playSound) { CustomFurniture furniture = byId(furnitureId); if (furniture == null) return null; return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), playSound); @@ -108,7 +109,7 @@ public final class CraftEngineFurniture { * @return the loaded furniture */ @NotNull - public static LoadedFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType, boolean playSound) { + public static BukkitFurniture place(Location location, CustomFurniture furniture, AnchorType anchorType, boolean playSound) { return BukkitFurnitureManager.instance().place(location, furniture, FurnitureExtraData.builder().anchorType(anchorType).build(), playSound); } @@ -152,7 +153,7 @@ public final class CraftEngineFurniture { * @return the loaded furniture */ @Nullable - public static LoadedFurniture getLoadedFurnitureByBaseEntity(@NotNull Entity baseEntity) { + public static BukkitFurniture getLoadedFurnitureByBaseEntity(@NotNull Entity baseEntity) { return BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(baseEntity.getEntityId()); } @@ -163,7 +164,7 @@ public final class CraftEngineFurniture { * @return the loaded furniture */ @Nullable - public static LoadedFurniture getLoadedFurnitureBySeat(@NotNull Entity seat) { + public static BukkitFurniture getLoadedFurnitureBySeat(@NotNull Entity seat) { Integer baseEntityId = seat.getPersistentDataContainer().get(BukkitFurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER); if (baseEntityId == null) return null; return BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(baseEntityId); @@ -172,107 +173,108 @@ public final class CraftEngineFurniture { /** * Removes furniture * - * @param furniture furniture base entity + * @param entity furniture base entity * @return success or not */ - public static boolean remove(@NotNull Entity furniture) { - if (!isFurniture(furniture)) return false; - LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(furniture.getEntityId()); - if (loadedFurniture == null) return false; - loadedFurniture.destroy(); + public static boolean remove(@NotNull Entity entity) { + if (!isFurniture(entity)) return false; + BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId()); + if (furniture == null) return false; + furniture.destroy(); return true; } /** * Removes furniture, with more options * - * @param furniture furniture base entity + * @param entity furniture base entity * @param dropLoot whether to drop loots * @param playSound whether to play break sound * @return success or not */ - public static boolean remove(@NotNull Entity furniture, + public static boolean remove(@NotNull Entity entity, boolean dropLoot, boolean playSound) { - if (!isFurniture(furniture)) return false; - LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(furniture.getEntityId()); - if (loadedFurniture == null) return false; - remove(loadedFurniture, (net.momirealms.craftengine.core.entity.player.Player) null, dropLoot, playSound); + if (!isFurniture(entity)) return false; + BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId()); + if (furniture == null) return false; + remove(furniture, (net.momirealms.craftengine.core.entity.player.Player) null, dropLoot, playSound); return true; } /** * Removes furniture, with more options * - * @param furniture furniture base entity + * @param entity furniture base entity * @param player the player who removes the furniture * @param dropLoot whether to drop loots * @param playSound whether to play break sound * @return success or not */ - public static boolean remove(@NotNull Entity furniture, + public static boolean remove(@NotNull Entity entity, @Nullable Player player, boolean dropLoot, boolean playSound) { - if (!isFurniture(furniture)) return false; - LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(furniture.getEntityId()); - if (loadedFurniture == null) return false; - remove(loadedFurniture, player, dropLoot, playSound); + if (!isFurniture(entity)) return false; + Furniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entity.getEntityId()); + if (furniture == null) return false; + remove(furniture, player, dropLoot, playSound); return true; } /** * Removes furniture by providing furniture instance * - * @param loadedFurniture loaded furniture + * @param furniture loaded furniture * @param dropLoot whether to drop loots * @param playSound whether to play break sound */ - public static void remove(@NotNull LoadedFurniture loadedFurniture, + public static void remove(@NotNull Furniture furniture, boolean dropLoot, boolean playSound) { - remove(loadedFurniture, (net.momirealms.craftengine.core.entity.player.Player) null, dropLoot, playSound); + remove(furniture, (net.momirealms.craftengine.core.entity.player.Player) null, dropLoot, playSound); } /** * Removes furniture by providing furniture instance * - * @param loadedFurniture loaded furniture + * @param furniture loaded furniture * @param player the player who removes the furniture * @param dropLoot whether to drop loots * @param playSound whether to play break sound */ - public static void remove(@NotNull LoadedFurniture loadedFurniture, + public static void remove(@NotNull Furniture furniture, @Nullable Player player, boolean dropLoot, boolean playSound) { - remove(loadedFurniture, player == null ? null : BukkitCraftEngine.instance().adapt(player), dropLoot, playSound); + remove(furniture, player == null ? null : BukkitCraftEngine.instance().adapt(player), dropLoot, playSound); } /** * Removes furniture by providing furniture instance * - * @param loadedFurniture loaded furniture + * @param furniture loaded furniture * @param player the player who removes the furniture * @param dropLoot whether to drop loots * @param playSound whether to play break sound */ @SuppressWarnings("unchecked") - public static void remove(@NotNull LoadedFurniture loadedFurniture, + public static void remove(@NotNull Furniture furniture, @Nullable net.momirealms.craftengine.core.entity.player.Player player, boolean dropLoot, boolean playSound) { - Location location = loadedFurniture.dropLocation(); - loadedFurniture.destroy(); - LootTable lootTable = (LootTable) loadedFurniture.config().lootTable(); + if (!furniture.isValid()) return; + Location location = ((BukkitFurniture) furniture).dropLocation(); + furniture.destroy(); + LootTable lootTable = (LootTable) furniture.config().lootTable(); World world = new BukkitWorld(location.getWorld()); WorldPosition position = new WorldPosition(world, location.getX(), location.getY(), location.getZ()); if (dropLoot && lootTable != null) { ContextHolder.Builder builder = ContextHolder.builder() .withParameter(DirectContextParameters.POSITION, position) - .withParameter(DirectContextParameters.FURNITURE, loadedFurniture) - .withOptionalParameter(DirectContextParameters.FURNITURE_ITEM, loadedFurniture.extraData().item().orElse(null)); + .withParameter(DirectContextParameters.FURNITURE, furniture) + .withOptionalParameter(DirectContextParameters.FURNITURE_ITEM, furniture.extraData().item().orElse(null)); if (player != null) { builder.withParameter(DirectContextParameters.PLAYER, player); } @@ -282,7 +284,7 @@ public final class CraftEngineFurniture { } } if (playSound) { - world.playBlockSound(position, loadedFurniture.config().settings().sounds().breakSound()); + world.playBlockSound(position, furniture.config().settings().sounds().breakSound()); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureBreakEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureBreakEvent.java index 013a25e7a..087004c50 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureBreakEvent.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureBreakEvent.java @@ -1,6 +1,6 @@ package net.momirealms.craftengine.bukkit.api.event; -import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; @@ -11,10 +11,10 @@ import org.jetbrains.annotations.NotNull; public class FurnitureBreakEvent extends PlayerEvent implements Cancellable { private static final HandlerList HANDLER_LIST = new HandlerList(); private boolean cancelled; - private final LoadedFurniture furniture; + private final BukkitFurniture furniture; public FurnitureBreakEvent(@NotNull Player player, - @NotNull LoadedFurniture furniture) { + @NotNull BukkitFurniture furniture) { super(player); this.furniture = furniture; } @@ -25,7 +25,7 @@ public class FurnitureBreakEvent extends PlayerEvent implements Cancellable { } @NotNull - public LoadedFurniture furniture() { + public BukkitFurniture furniture() { return this.furniture; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureInteractEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureInteractEvent.java index 053fa45f6..a9a523484 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureInteractEvent.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurnitureInteractEvent.java @@ -1,6 +1,6 @@ package net.momirealms.craftengine.bukkit.api.event; -import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; import net.momirealms.craftengine.core.entity.player.InteractionHand; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -12,12 +12,12 @@ import org.jetbrains.annotations.NotNull; public class FurnitureInteractEvent extends PlayerEvent implements Cancellable { private static final HandlerList HANDLER_LIST = new HandlerList(); private boolean cancelled; - private final LoadedFurniture furniture; + private final BukkitFurniture furniture; private final InteractionHand hand; private final Location interactionPoint; public FurnitureInteractEvent(@NotNull Player player, - @NotNull LoadedFurniture furniture, + @NotNull BukkitFurniture furniture, @NotNull InteractionHand hand, @NotNull Location interactionPoint) { super(player); @@ -42,7 +42,7 @@ public class FurnitureInteractEvent extends PlayerEvent implements Cancellable { } @NotNull - public LoadedFurniture furniture() { + public BukkitFurniture furniture() { return this.furniture; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurniturePlaceEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurniturePlaceEvent.java index 5c373aa03..8ea0c9645 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurniturePlaceEvent.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/event/FurniturePlaceEvent.java @@ -1,6 +1,6 @@ package net.momirealms.craftengine.bukkit.api.event; -import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; import net.momirealms.craftengine.core.entity.player.InteractionHand; import org.bukkit.Location; import org.bukkit.entity.Player; @@ -12,12 +12,12 @@ import org.jetbrains.annotations.NotNull; public class FurniturePlaceEvent extends PlayerEvent implements Cancellable { private static final HandlerList HANDLER_LIST = new HandlerList(); private final Location location; - private final LoadedFurniture furniture; + private final BukkitFurniture furniture; private final InteractionHand hand; private boolean cancelled; public FurniturePlaceEvent(@NotNull Player player, - @NotNull LoadedFurniture furniture, + @NotNull BukkitFurniture furniture, @NotNull Location location, @NotNull InteractionHand hand) { super(player); @@ -32,7 +32,7 @@ public class FurniturePlaceEvent extends PlayerEvent implements Cancellable { } @NotNull - public LoadedFurniture furniture() { + public BukkitFurniture furniture() { return this.furniture; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java index 31b16f093..042c1d32f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BlockEventListener.java @@ -17,8 +17,8 @@ import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; import net.momirealms.craftengine.core.util.Cancellable; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.BlockPos; @@ -168,7 +168,7 @@ public class BlockEventListener implements Listener { Cancellable cancellable = Cancellable.of(event::isCancelled, event::setCancelled); PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)) - .withParameter(DirectContextParameters.BLOCK_STATE, state) + .withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state) .withParameter(DirectContextParameters.EVENT, cancellable) .withParameter(DirectContextParameters.POSITION, position) .withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, itemInHand) @@ -304,7 +304,7 @@ public class BlockEventListener implements Listener { .withParameter(DirectContextParameters.EVENT, cancellable) .withParameter(DirectContextParameters.POSITION, new WorldPosition(new BukkitWorld(event.getWorld()), LocationUtils.toVec3d(location))) .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)) - .withParameter(DirectContextParameters.BLOCK_STATE, state) + .withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, state) ), EventTrigger.STEP); if (cancellable.isCancelled()) { event.setCancelled(true); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index 7d77e45fe..03b8034aa 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -3,10 +3,13 @@ package net.momirealms.craftengine.bukkit.block; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import dev.dejvokep.boostedyaml.YamlDocument; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector; @@ -23,12 +26,8 @@ import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.ConfigParser; -import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; -import net.momirealms.craftengine.core.plugin.context.function.Function; -import net.momirealms.craftengine.core.plugin.event.EventFunctions; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; +import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.registry.WritableRegistry; @@ -40,10 +39,9 @@ import org.bukkit.Registry; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; -import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.io.File; import java.lang.reflect.Field; import java.nio.file.Path; @@ -53,50 +51,34 @@ public class BukkitBlockManager extends AbstractBlockManager { private static BukkitBlockManager instance; private final BukkitCraftEngine plugin; private final BlockParser blockParser; - - // A temporary map used to detect whether the same block state corresponds to multiple models. - private final Map tempRegistryIdConflictMap = new HashMap<>(); - // A temporary map that converts the custom block registered on the server to the vanilla block ID. - private final Map tempBlockAppearanceConvertor = new HashMap<>(); - // A temporary map that stores the model path of a certain vanilla block state - private final Map tempVanillaBlockStateModels = new HashMap<>(); - // The total amount of blocks registered private int customBlockCount; protected final ImmutableBlockState[] stateId2ImmutableBlockStates; // Minecraft objects // Cached new blocks $ holders - private ImmutableMap internalId2StateId; - private ImmutableMap stateId2BlockHolder; + private Map internalId2StateId; + private Map stateId2BlockHolder; // This map is used to change the block states that are not necessarily needed into a certain block state - private ImmutableMap blockAppearanceMapper; + private Map blockAppearanceMapper; // Used to automatically arrange block states for strings such as minecraft:note_block:0 - private ImmutableMap> blockAppearanceArranger; - private ImmutableMap> realBlockArranger; + private Map> blockAppearanceArranger; + private Map> realBlockArranger; // Record the amount of real blocks by block type - private LinkedHashMap registeredRealBlockSlots; + private Map registeredRealBlockSlots; // A set of blocks that sounds have been removed - private ImmutableSet affectedSoundBlocks; - private ImmutableMap soundMapper; + private Set affectedSoundBlocks; + private Map soundMapper; // A list to record the order of registration - private List blockRegisterOrder = new ArrayList<>(); - - // a reverted mapper - private final Map> appearanceToRealState = new HashMap<>(); - // Used to store override information of json files - private final Map> blockStateOverrides = new HashMap<>(); - // for mod, real block id -> state models - private final Map modBlockStates = new HashMap<>(); + private List blockRegisterOrder = new ObjectArrayList<>(); // Event listeners private final BlockEventListener blockEventListener; private final FallingBlockRemoveListener fallingBlockRemoveListener; - - private Map> clientBoundTags = Map.of(); - private Map> previousTags = Map.of(); + // cached tag packet protected Object cachedUpdateTagsPacket; public BukkitBlockManager(BukkitCraftEngine plugin) { super(plugin); + instance = this; this.plugin = plugin; this.blockParser = new BlockParser(); this.initVanillaRegistry(); @@ -115,42 +97,33 @@ public class BukkitBlockManager extends AbstractBlockManager { if (enableNoteBlocks) { this.recordVanillaNoteBlocks(); } - if (VersionHelper.isOrAbove1_20_3()) { - this.fallingBlockRemoveListener = new FallingBlockRemoveListener(); - } else this.fallingBlockRemoveListener = null; - this.stateId2ImmutableBlockStates = new ImmutableBlockState[customBlockCount]; + this.fallingBlockRemoveListener = VersionHelper.isOrAbove1_20_3() ? new FallingBlockRemoveListener() : null; + this.stateId2ImmutableBlockStates = new ImmutableBlockState[this.customBlockCount]; Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.INSTANCE.defaultState()); - instance = this; this.resetPacketConsumers(); } - public List blockRegisterOrder() { - return Collections.unmodifiableList(this.blockRegisterOrder); - } - public static BukkitBlockManager instance() { return instance; } + public List blockRegisterOrder() { + return Collections.unmodifiableList(this.blockRegisterOrder); + } + @Override public void delayedInit() { - Bukkit.getPluginManager().registerEvents(this.blockEventListener, plugin.bootstrap()); + Bukkit.getPluginManager().registerEvents(this.blockEventListener, this.plugin.bootstrap()); if (this.fallingBlockRemoveListener != null) { - Bukkit.getPluginManager().registerEvents(this.fallingBlockRemoveListener, plugin.bootstrap()); + Bukkit.getPluginManager().registerEvents(this.fallingBlockRemoveListener, this.plugin.bootstrap()); } } @Override public void unload() { super.unload(); - this.clearCache(); - this.appearanceToRealState.clear(); - this.blockStateOverrides.clear(); - this.modBlockStates.clear(); if (EmptyBlock.STATE != null) Arrays.fill(this.stateId2ImmutableBlockStates, EmptyBlock.STATE); - this.previousTags = this.clientBoundTags; - this.clientBoundTags = new HashMap<>(); } @Override @@ -167,15 +140,14 @@ public class BukkitBlockManager extends AbstractBlockManager { @Override public void delayedLoad() { - initSuggestions(); - resetPacketConsumers(); - clearCache(); - resendTags(); + this.resetPacketConsumers(); + super.delayedLoad(); } - private void resendTags() { + @Override + protected void resendTags() { // if there's no change - if (this.clientBoundTags.equals(this.previousTags)) return; + if (this.clientBoundTags.equals(this.previousClientBoundTags)) return; List list = new ArrayList<>(); for (Map.Entry> entry : this.clientBoundTags.entrySet()) { list.add(new TagUtils.TagEntry(entry.getKey(), entry.getValue())); @@ -192,10 +164,19 @@ public class BukkitBlockManager extends AbstractBlockManager { } } - private void clearCache() { - this.tempRegistryIdConflictMap.clear(); - this.tempBlockAppearanceConvertor.clear(); - this.tempVanillaBlockStateModels.clear(); + @Nullable + @Override + public BlockStateWrapper createPackedBlockState(String blockState) { + ImmutableBlockState state = BlockStateParser.deserialize(blockState); + if (state != null) { + return state.customBlockState(); + } + try { + BlockData blockData = Bukkit.createBlockData(blockState); + return BlockStateUtils.toPackedBlockState(blockData); + } catch (IllegalArgumentException e) { + return null; + } } @Nullable @@ -204,11 +185,13 @@ public class BukkitBlockManager extends AbstractBlockManager { } @NotNull + @Override public ImmutableBlockState getImmutableBlockStateUnsafe(int stateId) { return this.stateId2ImmutableBlockStates[stateId - BlockStateUtils.vanillaStateSize()]; } @Nullable + @Override public ImmutableBlockState getImmutableBlockState(int stateId) { if (!BlockStateUtils.isVanillaBlock(stateId)) { return this.stateId2ImmutableBlockStates[stateId - BlockStateUtils.vanillaStateSize()]; @@ -216,41 +199,53 @@ public class BukkitBlockManager extends AbstractBlockManager { return null; } - @Override - public Map modBlockStates() { - return Collections.unmodifiableMap(this.modBlockStates); - } - @Override public ConfigParser parser() { return this.blockParser; } @Override - public Map> blockOverrides() { - return Collections.unmodifiableMap(this.blockStateOverrides); + public void addBlock(Key id, CustomBlock customBlock) { + // bind appearance and real state + for (ImmutableBlockState state : customBlock.variantProvider().states()) { + ImmutableBlockState previous = this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()]; + if (previous != null && !previous.isEmpty()) { + throw new LocalizedResourceConfigException("warning.config.block.state.bind_failed", state.toString(), previous.toString()); + } + this.stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()] = state; + this.tempBlockAppearanceConvertor.put(state.customBlockState().registryId(), state.vanillaBlockState().registryId()); + this.appearanceToRealState.computeIfAbsent(state.vanillaBlockState().registryId(), k -> new IntArrayList()).add(state.customBlockState().registryId()); + } + super.addBlock(id, customBlock); } - public ImmutableMap> blockAppearanceArranger() { - return blockAppearanceArranger; + @Override + public Key getBlockOwnerId(BlockStateWrapper state) { + return BlockStateUtils.getBlockOwnerIdFromState(state.handle()); } - public ImmutableMap> realBlockArranger() { - return realBlockArranger; + @Override + public int availableAppearances(Key blockType) { + return Optional.ofNullable(this.registeredRealBlockSlots.get(blockType)).orElse(0); } - @Nullable - public List appearanceToRealStates(int appearanceStateId) { - return this.appearanceToRealState.get(appearanceStateId); + @NotNull + public Map> blockAppearanceArranger() { + return this.blockAppearanceArranger; + } + + @NotNull + public Map> realBlockArranger() { + return this.realBlockArranger; } private void initMirrorRegistry() { int size = RegistryUtils.currentBlockRegistrySize(); - PackedBlockState[] states = new PackedBlockState[size]; + BlockStateWrapper[] states = new BlockStateWrapper[size]; for (int i = 0; i < size; i++) { - states[i] = new PackedBlockState(BlockStateUtils.idToBlockState(i), i); + states[i] = BlockStateWrapper.create(BlockStateUtils.idToBlockState(i), i, BlockStateUtils.isVanillaBlock(i)); } - BlockRegistryMirror.init(states, new PackedBlockState(Reflections.instance$Blocks$STONE$defaultState, BlockStateUtils.blockStateToId(Reflections.instance$Blocks$STONE$defaultState))); + BlockRegistryMirror.init(states, BlockStateWrapper.vanilla(Reflections.instance$Blocks$STONE$defaultState, BlockStateUtils.blockStateToId(Reflections.instance$Blocks$STONE$defaultState))); } private void registerEmptyBlock() { @@ -266,30 +261,30 @@ public class BukkitBlockManager extends AbstractBlockManager { finalMapping.put(custom, stoneId); } finalMapping.putAll(this.tempBlockAppearanceConvertor); - PacketConsumers.init(finalMapping, RegistryUtils.currentBlockRegistrySize()); + PacketConsumers.initBlocks(finalMapping, RegistryUtils.currentBlockRegistrySize()); } private void initVanillaRegistry() { int vanillaStateCount; - if (plugin.hasMod()) { + if (this.plugin.hasMod()) { try { Class modClass = ReflectionUtils.getClazz(CraftEngine.MOD_CLASS); Field amountField = ReflectionUtils.getDeclaredField(modClass, "vanillaRegistrySize"); - vanillaStateCount = (int) amountField.get(null); + vanillaStateCount = amountField.getInt(null); } catch (Exception e) { vanillaStateCount = RegistryUtils.currentBlockRegistrySize(); - plugin.logger().severe("Fatal error", e); + this.plugin.logger().severe("Fatal error", e); } } else { vanillaStateCount = RegistryUtils.currentBlockRegistrySize(); } - plugin.logger().info("Vanilla block count: " + vanillaStateCount); + this.plugin.logger().info("Vanilla block count: " + vanillaStateCount); BlockStateUtils.init(vanillaStateCount); } @SuppressWarnings("unchecked") private void registerBlocks() { - plugin.logger().info("Registering blocks. Please wait..."); + this.plugin.logger().info("Registering blocks. Please wait..."); try { ImmutableMap.Builder builder1 = ImmutableMap.builder(); ImmutableMap.Builder builder2 = ImmutableMap.builder(); @@ -362,7 +357,7 @@ public class BukkitBlockManager extends AbstractBlockManager { parseVanillaBlock(pack, path, id, section); } else { // check duplicated config - if (byId.containsKey(id)) { + if (BukkitBlockManager.this.byId.containsKey(id)) { throw new LocalizedResourceConfigException("warning.config.block.duplicate"); } parseCustomBlock(pack, path, id, section); @@ -371,128 +366,73 @@ public class BukkitBlockManager extends AbstractBlockManager { private void parseCustomBlock(Pack pack, Path path, Key id, Map section) { // read block settings - BlockSettings settings = BlockSettings.fromMap(MiscUtils.castToMap(section.getOrDefault("settings", Map.of()), false)); - - // read loot table - LootTable lootTable = LootTable.fromMap(MiscUtils.castToMap(section.getOrDefault("loot", Map.of()), false)); - + BlockSettings settings = BlockSettings.fromMap(MiscUtils.castToMap(section.get("settings"), true)); // read states Map> properties; Map appearances; Map variants; - Object stateObj = ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "state", "states"), "warning.config.block.missing_state"); - Map stateSection = MiscUtils.castToMap(stateObj, true); + Map stateSection = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "state", "states"), "warning.config.block.missing_state"), true); + boolean singleState = !stateSection.containsKey("properties"); // single state - if (!stateSection.containsKey("properties")) { + if (singleState) { properties = Map.of(); - int internalId = ResourceConfigUtils.getAsInt(stateSection.getOrDefault("id", -1), "id"); - if (internalId < 0) { - throw new LocalizedResourceConfigException("warning.config.block.state.missing_real_id"); - } - - Pair pair = parseAppearanceSection(id, stateSection); - if (pair == null) return; - - appearances = Map.of("default", pair.right()); - String internalBlock = pair.left().value() + "_" + internalId; - Key internalBlockId = Key.of(Key.DEFAULT_NAMESPACE, internalBlock); + int internalId = ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("id"), "warning.config.block.state.missing_real_id"), "id"); + VanillaBlockState vanillaBlock = getVanillaBlock(id, stateSection); + appearances = Map.of("", vanillaBlock.registryId()); + Key internalBlockId = Key.of(Key.DEFAULT_NAMESPACE, vanillaBlock.type().value() + "_" + internalId); int internalBlockRegistryId = Optional.ofNullable(internalId2StateId.get(internalBlockId)).orElse(-1); if (internalBlockRegistryId == -1) { - throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", - internalBlock, - String.valueOf(registeredRealBlockSlots.get(pair.left()) - 1)); + throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", internalBlockId.toString(), String.valueOf(availableAppearances(vanillaBlock.type()) - 1)); } - variants = Map.of("", new VariantState("default", settings, internalBlockRegistryId)); + variants = Map.of("", new VariantState("", settings, internalBlockRegistryId)); } else { // properties - Map propertySection = MiscUtils.castToMap(stateSection.get("properties"), true); - if (propertySection == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.missing_properties"); - } - properties = parseProperties(propertySection); + properties = getProperties(MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("properties"), "warning.config.block.state.missing_properties"), true)); // appearance - Map appearancesSection = MiscUtils.castToMap(stateSection.get("appearances"), true); - if (appearancesSection == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.missing_appearances"); - } appearances = new HashMap<>(); - Map tempTypeMap = new HashMap<>(); - for (Map.Entry appearanceEntry : appearancesSection.entrySet()) { - if (appearanceEntry.getValue() instanceof Map appearanceSection) { - Pair pair = parseAppearanceSection(id, MiscUtils.castToMap(appearanceSection, false)); - if (pair == null) return; - appearances.put(appearanceEntry.getKey(), pair.right()); - tempTypeMap.put(appearanceEntry.getKey(), pair.left()); + Map appearance2BlockType = new HashMap<>(); + for (Map.Entry appearanceEntry : MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("appearances"), "warning.config.block.state.missing_appearances"), false).entrySet()) { + if (appearanceEntry.getValue() instanceof Map) { + VanillaBlockState vanillaBlock = getVanillaBlock(id, MiscUtils.castToMap(appearanceEntry.getValue(), false)); + appearances.put(appearanceEntry.getKey(), vanillaBlock.registryId()); + appearance2BlockType.put(appearanceEntry.getKey(), vanillaBlock.type()); } } // variants - Map variantsSection = MiscUtils.castToMap(stateSection.get("variants"), true); - if (variantsSection == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.missing_variants"); - } variants = new HashMap<>(); - for (Map.Entry variantEntry : variantsSection.entrySet()) { - if (variantEntry.getValue() instanceof Map variantSection0) { - Map variantSection = MiscUtils.castToMap(variantSection0, false); - String variantName = variantEntry.getKey(); - String appearance = (String) variantSection.get("appearance"); - if (appearance == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.variant.missing_appearance", variantName); - } + for (Map.Entry variantEntry : MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(stateSection.get("variants"), "warning.config.block.state.missing_variants"), false).entrySet()) { + if (variantEntry.getValue() instanceof Map) { + Map variantSection = MiscUtils.castToMap(variantEntry.getValue(), false); + String variantNBT = variantEntry.getKey(); + String appearance = ResourceConfigUtils.requireNonEmptyStringOrThrow(variantSection.get("appearance"), "warning.config.block.state.variant.missing_appearance"); if (!appearances.containsKey(appearance)) { - throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantName, appearance); + throw new LocalizedResourceConfigException("warning.config.block.state.variant.invalid_appearance", variantNBT, appearance); } - int internalId = ResourceConfigUtils.getAsInt(variantSection.getOrDefault("id", -1), "id"); - Key baseBlock = tempTypeMap.get(appearance); + int internalId = ResourceConfigUtils.getAsInt(ResourceConfigUtils.requireNonNullOrThrow(variantSection.get("id"), "warning.config.block.state.missing_real_id"), "id"); + Key baseBlock = appearance2BlockType.get(appearance); Key internalBlockId = Key.of(Key.DEFAULT_NAMESPACE, baseBlock.value() + "_" + internalId); int internalBlockRegistryId = Optional.ofNullable(internalId2StateId.get(internalBlockId)).orElse(-1); if (internalBlockRegistryId == -1) { - throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", - internalBlockId.toString(), - String.valueOf(registeredRealBlockSlots.getOrDefault(baseBlock, 1) - 1)); + throw new LocalizedResourceConfigException("warning.config.block.state.invalid_real_id", internalBlockId.toString(), String.valueOf(availableAppearances(baseBlock) - 1)); } Map anotherSetting = MiscUtils.castToMap(variantSection.get("settings"), true); - variants.put(variantName, new VariantState(appearance, anotherSetting == null ? settings : BlockSettings.ofFullCopy(settings, anotherSetting), internalBlockRegistryId)); + variants.put(variantNBT, new VariantState(appearance, anotherSetting == null ? settings : BlockSettings.ofFullCopy(settings, anotherSetting), internalBlockRegistryId)); } } } - Object eventsObj = ResourceConfigUtils.get(section, "events", "event"); - EnumMap>> events = EventFunctions.parseEvents(eventsObj); - - Map behaviors = MiscUtils.castToMap(section.getOrDefault("behavior", Map.of()), false); CustomBlock block = BukkitCustomBlock.builder(id) .appearances(appearances) .variantMapper(variants) - .lootTable(lootTable) .properties(properties) .settings(settings) - .behavior(behaviors) - .events(events) + .lootTable(LootTable.fromMap(MiscUtils.castToMap(section.get("loot"), true))) + .behavior(MiscUtils.getAsMapList(ResourceConfigUtils.get(section, "behavior", "behaviors"))) + .events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event"))) .build(); - // bind appearance and real state - for (ImmutableBlockState state : block.variantProvider().states()) { - ImmutableBlockState previous = stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()]; - if (previous != null && !previous.isEmpty()) { - TranslationManager.instance().log("warning.config.block.state.bind_failed", path.toString(), id.toString(), state.toString(), previous.toString()); - continue; - } - stateId2ImmutableBlockStates[state.customBlockState().registryId() - BlockStateUtils.vanillaStateSize()] = state; - tempBlockAppearanceConvertor.put(state.customBlockState().registryId(), state.vanillaBlockState().registryId()); - appearanceToRealState.computeIfAbsent(state.vanillaBlockState().registryId(), k -> new ArrayList<>()).add(state.customBlockState().registryId()); - } - - BukkitBlockManager.this.byId.put(id, block); - - // generate mod assets - if (Config.generateModAssets()) { - for (ImmutableBlockState state : block.variantProvider().states()) { - Key realBlockId = BlockStateUtils.getBlockOwnerIdFromState(state.customBlockState()); - modBlockStates.put(realBlockId, tempVanillaBlockStateModels.get(state.vanillaBlockState().registryId())); - } - } + addBlock(id, block); } private void parseVanillaBlock(Pack pack, Path path, Key id, Map section) { @@ -513,7 +453,8 @@ public class BukkitBlockManager extends AbstractBlockManager { } } - private Map> parseProperties(Map propertiesSection) { + @NotNull + private Map> getProperties(Map propertiesSection) { Map> properties = new HashMap<>(); for (Map.Entry entry : propertiesSection.entrySet()) { Property property = Properties.fromMap(entry.getKey(), MiscUtils.castToMap(entry.getValue(), false)); @@ -522,69 +463,40 @@ public class BukkitBlockManager extends AbstractBlockManager { return properties; } - @Nullable - private Pair parseAppearanceSection(Key id, Map section) { + @NotNull + private VanillaBlockState getVanillaBlock(Key id, Map section) { // require state non null - Object vanillaStateString = section.get("state"); - if (vanillaStateString == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.missing_state"); - } - + String vanillaBlockStateTag = ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("state"), "warning.config.block.state.missing_state"); // get its registry id - int vanillaStateRegistryId = parseVanillaStateRegistryId(vanillaStateString.toString()); - - // check conflict - Key ifAny = this.tempRegistryIdConflictMap.get(vanillaStateRegistryId); + int vanillaBlockStateRegistryId = getVanillaBlockStateRegistryId(vanillaBlockStateTag); + // check if another block has occupied the appearance + // TODO blocks share the same look + Key ifAny = this.tempRegistryIdConflictMap.get(vanillaBlockStateRegistryId); if (ifAny != null && !ifAny.equals(id)) { - throw new LocalizedResourceConfigException("warning.config.block.state.conflict", BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(vanillaStateRegistryId)).getAsString(), ifAny.toString()); + throw new LocalizedResourceConfigException("warning.config.block.state.conflict", BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(vanillaBlockStateRegistryId)).getAsString(), ifAny.toString()); } - // require models not to be null - Object models = section.get("models"); - if (models == null) { - models = section.get("model"); - } - if (models == null) { + Object models = ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "models", "model"), "warning.config.block.state.missing_model"); + List variants = ResourceConfigUtils.parseConfigAsList(models, this::getVariantModel); + if (variants.isEmpty()) { throw new LocalizedResourceConfigException("warning.config.block.state.missing_model"); } - - List variants = new ArrayList<>(); - if (models instanceof Map singleModelSection) { - loadVariantModel(variants, MiscUtils.castToMap(singleModelSection, false)); - } else if (models instanceof List modelList) { - for (Object model : modelList) { - if (model instanceof Map singleModelMap) { - loadVariantModel(variants, MiscUtils.castToMap(singleModelMap, false)); - } - } - } - if (variants.isEmpty()) return null; - - this.tempRegistryIdConflictMap.put(vanillaStateRegistryId, id); - String blockState = BlockStateUtils.idToBlockState(vanillaStateRegistryId).toString(); - Key block = Key.of(blockState.substring(blockState.indexOf('{') + 1, blockState.lastIndexOf('}'))); - String propertyData = blockState.substring(blockState.indexOf('[') + 1, blockState.lastIndexOf(']')); - Map paths = this.blockStateOverrides.computeIfAbsent(block, k -> new HashMap<>()); - if (variants.size() == 1) { - paths.put(propertyData, variants.get(0)); - this.tempVanillaBlockStateModels.put(vanillaStateRegistryId, variants.get(0)); - } else { - JsonArray array = new JsonArray(); - for (JsonObject object : variants) { - array.add(object); - } - paths.put(propertyData, array); - this.tempVanillaBlockStateModels.put(vanillaStateRegistryId, array); - } - return Pair.of(block, vanillaStateRegistryId); + // TODO blocks share the same look + this.tempRegistryIdConflictMap.put(vanillaBlockStateRegistryId, id); + // gets the full block state + String blockState = BlockStateUtils.idToBlockState(vanillaBlockStateRegistryId).toString(); + Key blockId = Key.of(blockState.substring(blockState.indexOf('{') + 1, blockState.lastIndexOf('}'))); + String propertyNBT = blockState.substring(blockState.indexOf('[') + 1, blockState.lastIndexOf(']')); + // for generating assets + JsonElement combinedVariant = GsonHelper.combine(variants); + this.blockStateOverrides.computeIfAbsent(blockId, k -> new HashMap<>()).put(propertyNBT, combinedVariant); + this.tempVanillaBlockStateModels.put(vanillaBlockStateRegistryId, combinedVariant); + return new VanillaBlockState(blockId, propertyNBT, vanillaBlockStateRegistryId); } - private void loadVariantModel(List variants, Map singleModelMap) { + private JsonObject getVariantModel(Map singleModelMap) { JsonObject json = new JsonObject(); - String modelPath = (String) singleModelMap.get("path"); - if (modelPath == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.model.missing_path"); - } + String modelPath = ResourceConfigUtils.requireNonEmptyStringOrThrow(singleModelMap.get("path"), "warning.config.block.state.model.missing_path"); if (!ResourceLocation.isValid(modelPath)) { throw new LocalizedResourceConfigException("warning.config.block.state.model.invalid_path", modelPath); } @@ -597,10 +509,10 @@ public class BukkitBlockManager extends AbstractBlockManager { if (generationMap != null) { prepareModelGeneration(ModelGeneration.of(Key.of(modelPath), generationMap)); } - variants.add(json); + return json; } - private int parseVanillaStateRegistryId(String blockState) { + private int getVanillaBlockStateRegistryId(String blockState) { String[] split = blockState.split(":", 3); if (split.length >= 4) { throw new LocalizedResourceConfigException("warning.config.block.state.invalid_vanilla", blockState); @@ -649,9 +561,9 @@ public class BukkitBlockManager extends AbstractBlockManager { YamlDocument mappings = Config.instance().loadOrCreateYamlData("mappings.yml"); Map blockStateMappings = loadBlockStateMappings(mappings); this.validateBlockStateMappings(mappingFile, blockStateMappings); - Map stateMap = new HashMap<>(); + Map stateMap = new Int2ObjectOpenHashMap<>(); Map blockTypeCounter = new LinkedHashMap<>(); - Map appearanceMapper = new HashMap<>(); + Map appearanceMapper = new Int2IntOpenHashMap(); Map> appearanceArranger = new HashMap<>(); for (Map.Entry entry : blockStateMappings.entrySet()) { this.processBlockStateMapping(mappingFile, entry, stateMap, blockTypeCounter, appearanceMapper, appearanceArranger); @@ -727,7 +639,7 @@ public class BukkitBlockManager extends AbstractBlockManager { counter.compute(key, (k, count) -> count == null ? 1 : count + 1); stateMap.put(beforeId, entry.getKey()); stateMap.put(afterId, entry.getValue()); - arranger.computeIfAbsent(key, (k) -> new ArrayList<>()).add(beforeId); + arranger.computeIfAbsent(key, (k) -> new IntArrayList()).add(beforeId); } else { String previousState = stateMap.get(previous); plugin.logger().warn(mappingFile, "Duplicate entry: '" + previousState + "' equals '" + entry.getKey() + "'"); @@ -776,7 +688,7 @@ public class BukkitBlockManager extends AbstractBlockManager { Object clientSideBlock = getBlockFromRegistry(createResourceLocation(clientSideBlockType)); int amount = blockWithCount.getValue(); - List stateIds = new ArrayList<>(); + List stateIds = new IntArrayList(); for (int i = 0; i < amount; i++) { Key realBlockKey = createRealBlockKey(clientSideBlockType, i); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockShape.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockShape.java index a349d33a9..c57f78a20 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockShape.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockShape.java @@ -1,17 +1,30 @@ package net.momirealms.craftengine.bukkit.block; -import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.shared.block.BlockShape; +import org.jetbrains.annotations.Nullable; public class BukkitBlockShape implements BlockShape { private final Object rawBlockState; + private final Object supportBlockState; - public BukkitBlockShape(Object rawBlockState) { + public BukkitBlockShape(Object rawBlockState, @Nullable Object supportBlockState) { this.rawBlockState = rawBlockState; + this.supportBlockState = supportBlockState == null ? rawBlockState : supportBlockState; } @Override - public Object getShape(Object thisObj, Object[] args) throws Exception { - return Reflections.method$BlockBehaviour$getShape.invoke(Reflections.field$StateHolder$owner.get(this.rawBlockState), this.rawBlockState, args[1], args[2], args[3]); + public Object getShape(Object thisObj, Object[] args) { + return FastNMS.INSTANCE.method$BlockState$getShape(this.rawBlockState, args[1], args[2], args[3]); + } + + @Override + public Object getCollisionShape(Object thisObj, Object[] args) { + return FastNMS.INSTANCE.method$BlockState$getCollisionShape(this.rawBlockState, args[1], args[2], args[3]); + } + + @Override + public Object getSupportShape(Object thisObj, Object[] args) { + return FastNMS.INSTANCE.method$BlockState$getBlockSupportShape(this.supportBlockState, args[1], args[2]); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java index 17891cded..b69cc2ece 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitCustomBlock.java @@ -10,8 +10,8 @@ import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.function.Function; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.registry.WritableRegistry; @@ -21,6 +21,7 @@ import net.momirealms.craftengine.core.util.Tristate; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.shared.ObjectHolder; import net.momirealms.craftengine.shared.block.BlockBehavior; +import org.bukkit.Bukkit; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,17 +29,17 @@ import org.jetbrains.annotations.Nullable; import java.lang.reflect.Field; import java.util.*; -public class BukkitCustomBlock extends CustomBlock { +public class BukkitCustomBlock extends AbstractCustomBlock { protected BukkitCustomBlock( - Key id, - Holder.Reference holder, - Map> properties, - Map appearances, - Map variantMapper, - BlockSettings settings, - @NotNull EnumMap>> events, - @Nullable Map behavior, + @NotNull Key id, + @NotNull Holder.Reference holder, + @NotNull Map> properties, + @NotNull Map appearances, + @NotNull Map variantMapper, + @NotNull BlockSettings settings, + @NotNull Map>> events, + @Nullable List> behavior, @Nullable LootTable lootTable ) { super(id, holder, properties, appearances, variantMapper, settings, events, behavior, lootTable); @@ -72,7 +73,13 @@ public class BukkitCustomBlock extends CustomBlock { BlockStateUtils.setHardness(mcBlockState, settings.hardness()); BlockStateUtils.setPushReaction(mcBlockState, settings.pushReaction()); BlockStateUtils.setReplaceable(mcBlockState, settings.replaceable()); - BlockStateUtils.setCanOcclude(mcBlockState, settings.canOcclude()); + if (settings.canOcclude() == Tristate.TRUE) { + BlockStateUtils.setCanOcclude(mcBlockState, true); + } else if (settings.canOcclude() == Tristate.FALSE) { + BlockStateUtils.setCanOcclude(mcBlockState, false); + } else { + BlockStateUtils.setCanOcclude(mcBlockState, BlockStateUtils.isOcclude(state.vanillaBlockState().handle())); + } if (settings.isRedstoneConductor() == Tristate.TRUE) { BlockStateUtils.setIsRedstoneConductor(mcBlockState, StatePredicate.alwaysTrue()); } else if (settings.isRedstoneConductor() == Tristate.FALSE) { @@ -100,7 +107,16 @@ public class BukkitCustomBlock extends CustomBlock { Field shapeField = mcBlock.getClass().getField("shapeHolder"); @SuppressWarnings("unchecked") ObjectHolder shapeHolder = (ObjectHolder) shapeField.get(mcBlock); - shapeHolder.bindValue(new BukkitBlockShape(state.vanillaBlockState().handle())); + shapeHolder.bindValue(new BukkitBlockShape(state.vanillaBlockState().handle(), Optional.ofNullable(state.settings().supportShapeBlockState()).map(it -> { + try { + Object blockState = BlockStateUtils.blockDataToBlockState(Bukkit.createBlockData(it)); + if (!BlockStateUtils.isVanillaBlock(blockState)) return null; + return blockState; + } catch (IllegalArgumentException e) { + CraftEngine.instance().logger().warn("Illegal shape block state: " + it, e); + return null; + } + }).orElse(null))); // bind behavior Field behaviorField = mcBlock.getClass().getField("behaviorHolder"); @SuppressWarnings("unchecked") @@ -128,6 +144,8 @@ public class BukkitCustomBlock extends CustomBlock { } // set random tick later BlockStateUtils.setIsRandomlyTicking(mcBlockState, settings.isRandomlyTicking()); + // set propagates skylight + BlockStateUtils.setPropagatesSkylightDown(mcBlockState, settings.propagatesSkylightDown()); // bind tags Object holder = BukkitCraftEngine.instance().blockManager().getMinecraftBlockHolder(state.customBlockState().registryId()); Set tags = new HashSet<>(); @@ -147,17 +165,67 @@ public class BukkitCustomBlock extends CustomBlock { } public static Builder builder(Key id) { - return new Builder(id); + return new BuilderImpl(id); } - public static class Builder extends CustomBlock.Builder { + public static class BuilderImpl implements Builder { + protected final Key id; + protected Map> properties; + protected Map appearances; + protected Map variantMapper; + protected BlockSettings settings; + protected List> behavior; + protected LootTable lootTable; + protected Map>> events; - protected Builder(Key id) { - super(id); + public BuilderImpl(Key id) { + this.id = id; } @Override - public CustomBlock build() { + public Builder events(Map>> events) { + this.events = events; + return this; + } + + @Override + public Builder appearances(Map appearances) { + this.appearances = appearances; + return this; + } + + @Override + public Builder behavior(List> behavior) { + this.behavior = behavior; + return this; + } + + @Override + public Builder lootTable(LootTable lootTable) { + this.lootTable = lootTable; + return this; + } + + @Override + public Builder properties(Map> properties) { + this.properties = properties; + return this; + } + + @Override + public Builder settings(BlockSettings settings) { + this.settings = settings; + return this; + } + + @Override + public Builder variantMapper(Map variantMapper) { + this.variantMapper = variantMapper; + return this; + } + + @Override + public @NotNull CustomBlock build() { // create or get block holder Holder.Reference holder = BuiltInRegistries.BLOCK.get(id).orElseGet(() -> ((WritableRegistry) BuiltInRegistries.BLOCK).registerForHolder(new ResourceKey<>(BuiltInRegistries.BLOCK.key().location(), id))); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java new file mode 100644 index 000000000..627a1d15b --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/AbstractCanSurviveBlockBehavior.java @@ -0,0 +1,108 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.bukkit.world.BukkitWorld; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.plugin.context.ContextHolder; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.WorldEvents; +import net.momirealms.craftengine.core.world.WorldPosition; + +import java.util.concurrent.Callable; + +public abstract class AbstractCanSurviveBlockBehavior extends BukkitBlockBehavior { + protected final int delay; + + protected AbstractCanSurviveBlockBehavior(CustomBlock customBlock, int delay) { + super(customBlock); + this.delay = delay; + } + + @Override + public void tick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + if (this.delay == 0) return; + Object blockState = args[0]; + Object level = args[1]; + Object blockPos = args[2]; + if (!canSurvive(thisBlock, args, () -> true)) { + int stateId = BlockStateUtils.blockStateToId(blockState); + ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(stateId); + if (currentState != null && !currentState.isEmpty() && currentState.owner().value() == this.customBlock) { + // break the crop + FastNMS.INSTANCE.method$Level$removeBlock(level, blockPos, false); + net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); + WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(LocationUtils.fromBlockPos(blockPos))); + ContextHolder.Builder builder = ContextHolder.builder() + .withParameter(DirectContextParameters.POSITION, position); + for (Item item : currentState.getDrops(builder, world, null)) { + world.dropItemNaturally(position, item); + } + world.playBlockSound(position, currentState.sounds().breakSound()); + FastNMS.INSTANCE.method$Level$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, stateId); + } + } + } + + @Override + public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object state = args[0]; + Object world = args[1]; + Object pos = args[2]; + return canSurvive(thisBlock, state, world, pos); + } + + @Override + public void onPlace(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object world = args[1]; + Object blockPos = args[2]; + Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 2); + } + + @Override + public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object level; + Object blockPos; + Object state = args[0]; + if (VersionHelper.isOrAbove1_21_2()) { + level = args[1]; + blockPos = args[3]; + } else { + level = args[3]; + blockPos = args[4]; + } + int stateId = BlockStateUtils.blockStateToId(state); + ImmutableBlockState previousState = BukkitBlockManager.instance().getImmutableBlockState(stateId); + if (previousState == null || previousState.isEmpty()) { + return state; + } + if (this.delay != 0) { + Reflections.method$LevelAccessor$scheduleTick.invoke(level, blockPos, thisBlock, this.delay); + return state; + } + if (!canSurvive(thisBlock, new Object[] {state, level, blockPos}, () -> true)) { + BlockPos pos = LocationUtils.fromBlockPos(blockPos); + net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); + WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); + ContextHolder.Builder builder = ContextHolder.builder() + .withParameter(DirectContextParameters.POSITION, position); + for (Item item : previousState.getDrops(builder, world, null)) { + world.dropItemNaturally(position, item); + } + world.playBlockSound(position, previousState.sounds().breakSound()); + FastNMS.INSTANCE.method$Level$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, stateId); + return Reflections.method$Block$defaultBlockState.invoke(Reflections.instance$Blocks$AIR); + } + return state; + } + + protected abstract boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws Exception; +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 1ebf13dc8..b0942ed95 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -12,11 +12,13 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key STRIPPABLE_BLOCK = Key.from("craftengine:strippable_block"); public static final Key SAPLING_BLOCK = Key.from("craftengine:sapling_block"); public static final Key ON_LIQUID_BLOCK = Key.from("craftengine:on_liquid_block"); + public static final Key NEAR_LIQUID_BLOCK = Key.from("craftengine:near_liquid_block"); public static final Key WATERLOGGED_BLOCK = Key.from("craftengine:waterlogged_block"); public static final Key CONCRETE_POWDER_BLOCK = Key.from("craftengine:concrete_powder_block"); - public static final Key SUGARCANE_BLOCK = Key.from("craftengine:sugar_cane_block"); + public static final Key VERTICAL_CROP_BLOCK = Key.from("craftengine:vertical_crop_block"); public static final Key CROP_BLOCK = Key.from("craftengine:crop_block"); public static final Key GRASS_BLOCK = Key.from("craftengine:grass_block"); + public static final Key LAMP_BLOCK = Key.from("craftengine:lamp_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -27,10 +29,12 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(STRIPPABLE_BLOCK, StrippableBlockBehavior.FACTORY); register(SAPLING_BLOCK, SaplingBlockBehavior.FACTORY); register(ON_LIQUID_BLOCK, OnLiquidBlockBehavior.FACTORY); + register(NEAR_LIQUID_BLOCK, NearLiquidBlockBehavior.FACTORY); register(WATERLOGGED_BLOCK, WaterLoggedBlockBehavior.FACTORY); register(CONCRETE_POWDER_BLOCK, ConcretePowderBlockBehavior.FACTORY); - register(SUGARCANE_BLOCK, SugarCaneBlockBehavior.FACTORY); + register(VERTICAL_CROP_BLOCK, VerticalCropBlockBehavior.FACTORY); register(CROP_BLOCK, CropBlockBehavior.FACTORY); register(GRASS_BLOCK, GrassBlockBehavior.FACTORY); + register(LAMP_BLOCK, LampBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java index 381c9152f..db5beb2ba 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BushBlockBehavior.java @@ -4,23 +4,14 @@ import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.BlockTags; -import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.Reflections; -import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.plugin.context.ContextHolder; -import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.Tuple; -import net.momirealms.craftengine.core.util.VersionHelper; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.Vec3d; -import net.momirealms.craftengine.core.world.WorldEvents; -import net.momirealms.craftengine.core.world.WorldPosition; import net.momirealms.craftengine.shared.block.BlockBehavior; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -28,68 +19,22 @@ import org.bukkit.NamespacedKey; import org.bukkit.Registry; import java.util.*; -import java.util.concurrent.Callable; -public class BushBlockBehavior extends BukkitBlockBehavior { +public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior { public static final Factory FACTORY = new Factory(); protected final List tagsCanSurviveOn; - protected final Set blocksCansSurviveOn; + protected final Set blockStatesCanSurviveOn; protected final Set customBlocksCansSurviveOn; - protected final boolean any; + protected final boolean blacklistMode; + protected final boolean stackable; - public BushBlockBehavior(CustomBlock block, List tagsCanSurviveOn, Set blocksCansSurviveOn, Set customBlocksCansSurviveOn) { - super(block); + public BushBlockBehavior(CustomBlock block, int delay, boolean blacklist, boolean stackable, List tagsCanSurviveOn, Set blockStatesCanSurviveOn, Set customBlocksCansSurviveOn) { + super(block, delay); + this.blacklistMode = blacklist; + this.stackable = stackable; this.tagsCanSurviveOn = tagsCanSurviveOn; - this.blocksCansSurviveOn = blocksCansSurviveOn; + this.blockStatesCanSurviveOn = blockStatesCanSurviveOn; this.customBlocksCansSurviveOn = customBlocksCansSurviveOn; - this.any = this.tagsCanSurviveOn.isEmpty() && this.blocksCansSurviveOn.isEmpty() && this.customBlocksCansSurviveOn.isEmpty(); - } - - @Override - public void onPlace(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - Object world = args[1]; - Object blockPos = args[2]; - Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 2); - } - - @Override - public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - Object level; - Object blockPos; - Object state = args[0]; - if (VersionHelper.isOrAbove1_21_2()) { - level = args[1]; - blockPos = args[3]; - } else { - level = args[3]; - blockPos = args[4]; - } - if (!canSurvive(thisBlock, state, level, blockPos)) { - int stateId = BlockStateUtils.blockStateToId(state); - ImmutableBlockState previousState = BukkitBlockManager.instance().getImmutableBlockState(stateId); - if (previousState != null && !previousState.isEmpty()) { - BlockPos pos = LocationUtils.fromBlockPos(blockPos); - net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); - WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); - ContextHolder.Builder builder = ContextHolder.builder() - .withParameter(DirectContextParameters.POSITION, position); - for (Item item : previousState.getDrops(builder, world, null)) { - world.dropItemNaturally(position, item); - } - world.playBlockSound(position, previousState.sounds().breakSound()); - FastNMS.INSTANCE.method$Level$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, stateId); - } - return Reflections.method$Block$defaultBlockState.invoke(Reflections.instance$Blocks$AIR); - } - return super.updateShape(thisBlock, args, superMethod); - } - - @Override - public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - Object state = args[0]; - Object world = args[1]; - Object pos = args[2]; - return canSurvive(thisBlock, state, world, pos); } public static class Factory implements BlockBehaviorFactory { @@ -97,7 +42,10 @@ public class BushBlockBehavior extends BukkitBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { Tuple, Set, Set> tuple = readTagsAndState(arguments, false); - return new BushBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right()); + boolean stackable = (boolean) arguments.getOrDefault("stackable", false); + int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); + boolean blacklistMode = (boolean) arguments.getOrDefault("blacklist", false); + return new BushBlockBehavior(block, delay, blacklistMode, stackable, tuple.left(), tuple.mid(), tuple.right()); } } @@ -127,7 +75,8 @@ public class BushBlockBehavior extends BukkitBlockBehavior { return new Tuple<>(mcTags, mcBlocks, customBlocks); } - protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException { + @Override + protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws Exception { int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos); int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos); int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos); @@ -137,28 +86,30 @@ public class BushBlockBehavior extends BukkitBlockBehavior { } protected boolean mayPlaceOn(Object belowState, Object world, Object belowPos) throws ReflectiveOperationException { - if (this.any) return true; for (Object tag : this.tagsCanSurviveOn) { if ((boolean) Reflections.method$BlockStateBase$hasTag.invoke(belowState, tag)) { - return true; + return !this.blacklistMode; } } int id = BlockStateUtils.blockStateToId(belowState); if (BlockStateUtils.isVanillaBlock(id)) { - if (!this.blocksCansSurviveOn.isEmpty() && this.blocksCansSurviveOn.contains(belowState)) { - return true; + if (!this.blockStatesCanSurviveOn.isEmpty() && this.blockStatesCanSurviveOn.contains(belowState)) { + return !this.blacklistMode; } } else { - ImmutableBlockState previousState = BukkitBlockManager.instance().getImmutableBlockState(id); - if (previousState != null && !previousState.isEmpty()) { - if (this.customBlocksCansSurviveOn.contains(previousState.owner().value().id().toString())) { - return true; + ImmutableBlockState belowCustomState = BukkitBlockManager.instance().getImmutableBlockState(id); + if (belowCustomState != null && !belowCustomState.isEmpty()) { + if (belowCustomState.owner().value() == super.customBlock) { + return this.stackable; } - if (this.customBlocksCansSurviveOn.contains(previousState.toString())) { - return true; + if (this.customBlocksCansSurviveOn.contains(belowCustomState.owner().value().id().toString())) { + return !this.blacklistMode; + } + if (this.customBlocksCansSurviveOn.contains(belowCustomState.toString())) { + return !this.blacklistMode; } } } - return false; + return this.blacklistMode; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ConcretePowderBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ConcretePowderBlockBehavior.java index e15ae9e6f..5fb2dff7a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ConcretePowderBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ConcretePowderBlockBehavior.java @@ -25,15 +25,14 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; -// TODO Inject FallingBlockEntity? -public class ConcretePowderBlockBehavior extends FallingBlockBehavior { +public class ConcretePowderBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); - private final Key targetBlock; + private final Key targetBlock; // TODO 更宽泛的,使用state,似乎也不是很好的方案? private Object defaultBlockState; private ImmutableBlockState defaultImmutableBlockState; - public ConcretePowderBlockBehavior(CustomBlock block, float hurtAmount, int maxHurt, Key targetBlock) { - super(block, hurtAmount, maxHurt); + public ConcretePowderBlockBehavior(CustomBlock block, Key targetBlock) { + super(block); this.targetBlock = targetBlock; } @@ -79,7 +78,7 @@ public class ConcretePowderBlockBehavior extends FallingBlockBehavior { return super.updateStateForPlacement(context, state); } } - } catch (ReflectiveOperationException e) { + } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to update state for placement " + context.getClickedPos(), e); } return super.updateStateForPlacement(context, state); @@ -118,7 +117,7 @@ public class ConcretePowderBlockBehavior extends FallingBlockBehavior { } } } - return super.updateShape(thisBlock, args, superMethod); + return args[0]; } private static boolean shouldSolidify(Object level, Object blockPos, Object blockState) throws ReflectiveOperationException { @@ -155,10 +154,8 @@ public class ConcretePowderBlockBehavior extends FallingBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { - float hurtAmount = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("hurt-amount", -1f), "hurt-amount"); - int hurtMax = ResourceConfigUtils.getAsInt(arguments.getOrDefault("max-hurt", -1), "max-hurt"); String solidBlock = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("solid-block"), "warning.config.block.behavior.concrete.missing_solid"); - return new ConcretePowderBlockBehavior(block, hurtAmount, hurtMax, Key.of(solidBlock)); + return new ConcretePowderBlockBehavior(block, Key.of(solidBlock)); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java index 29b2f7e4e..df47ace39 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.ParticleUtils; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.bukkit.world.BukkitWorld; @@ -12,6 +13,11 @@ import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.IntegerProperty; import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemKeys; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.SimpleContext; import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; @@ -19,7 +25,6 @@ import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; import net.momirealms.craftengine.core.util.RandomUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import net.momirealms.craftengine.core.util.Tuple; import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.core.world.Vec3i; import net.momirealms.craftengine.core.world.WorldPosition; @@ -27,12 +32,10 @@ import net.momirealms.craftengine.shared.block.BlockBehavior; import org.bukkit.World; import java.lang.reflect.InvocationTargetException; -import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.Callable; -public class CropBlockBehavior extends BushBlockBehavior { +public class CropBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final IntegerProperty ageProperty; private final float growSpeed; @@ -40,9 +43,8 @@ public class CropBlockBehavior extends BushBlockBehavior { private final boolean isBoneMealTarget; private final NumberProvider boneMealBonus; - public CropBlockBehavior(CustomBlock block, List tagsCanSurviveOn, Set blocksCansSurviveOn, Set customBlocksCansSurviveOn, - Property ageProperty, float growSpeed, int minGrowLight, boolean isBoneMealTarget, NumberProvider boneMealBonus) { - super(block, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn); + public CropBlockBehavior(CustomBlock block, Property ageProperty, float growSpeed, int minGrowLight, boolean isBoneMealTarget, NumberProvider boneMealBonus) { + super(block); this.ageProperty = (IntegerProperty) ageProperty; this.growSpeed = growSpeed; this.minGrowLight = minGrowLight; @@ -99,8 +101,10 @@ public class CropBlockBehavior extends BushBlockBehavior { } @Override - protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException { - return hasSufficientLight(world, blockPos) && super.canSurvive(thisBlock, state, world, blockPos); + public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object world = args[1]; + Object pos = args[2]; + return hasSufficientLight(world, pos); } @Override @@ -125,6 +129,35 @@ public class CropBlockBehavior extends BushBlockBehavior { this.performBoneMeal(args[0], args[2], args[3]); } + @Override + public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { + Item item = context.getItem(); + if (item == null || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || context.getPlayer().isAdventureMode()) + return InteractionResult.PASS; + if (isMaxAge(state)) + return InteractionResult.PASS; + boolean sendSwing = false; + try { + Object visualState = state.vanillaBlockState().handle(); + Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState); + if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { + boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); + if (!is) { + sendSwing = true; + } + } else { + sendSwing = true; + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to check visual state bone meal state", e); + return InteractionResult.FAIL; + } + if (sendSwing) { + context.getPlayer().swingHand(context.getHand()); + } + return InteractionResult.SUCCESS; + } + private void performBoneMeal(Object level, Object pos, Object state) throws InvocationTargetException, IllegalAccessException { ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(state)); if (immutableBlockState == null || immutableBlockState.isEmpty()) { @@ -149,7 +182,7 @@ public class CropBlockBehavior extends BushBlockBehavior { int i = this.getAge(immutableBlockState) + this.boneMealBonus.getInt( SimpleContext.of( ContextHolder.builder() - .withParameter(DirectContextParameters.BLOCK_STATE, immutableBlockState) + .withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, immutableBlockState) .withParameter(DirectContextParameters.POSITION, new WorldPosition(new BukkitWorld(world), Vec3d.atCenterOf(new Vec3i(x, y, z)))) .build() ) @@ -169,13 +202,12 @@ public class CropBlockBehavior extends BushBlockBehavior { @SuppressWarnings("unchecked") @Override public BlockBehavior create(CustomBlock block, Map arguments) { - Tuple, Set, Set> tuple = readTagsAndState(arguments, false); Property ageProperty = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("age"), "warning.config.block.behavior.crop.missing_age"); int minGrowLight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("light-requirement", 9), "light-requirement"); float growSpeed = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("grow-speed", 0.125f), "grow-speed"); boolean isBoneMealTarget = (boolean) arguments.getOrDefault("is-bone-meal-target", true); NumberProvider boneMealAgeBonus = NumberProviders.fromObject(arguments.getOrDefault("bone-meal-age-bonus", 1)); - return new CropBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right(), ageProperty, growSpeed, minGrowLight, isBoneMealTarget, boneMealAgeBonus); + return new CropBlockBehavior(block, ageProperty, growSpeed, minGrowLight, isBoneMealTarget, boneMealAgeBonus); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java index fb98a3e5f..48cd63cb8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FallingBlockBehavior.java @@ -51,7 +51,7 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { blockPos = args[4]; } Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 2); - return super.updateShape(thisBlock, args, superMethod); + return args[0]; } @Override @@ -109,6 +109,8 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { @Override public void onLand(Object thisBlock, Object[] args) throws Exception { Object fallingBlock = args[4]; + Object level = args[0]; + Object pos = args[1]; Object entityData = Reflections.field$Entity$entityData.get(fallingBlock); boolean isSilent = (boolean) Reflections.method$SynchedEntityData$get.invoke(entityData, Reflections.instance$Entity$DATA_SILENT); if (!isSilent) { @@ -116,8 +118,6 @@ public class FallingBlockBehavior extends BukkitBlockBehavior { int stateId = BlockStateUtils.blockStateToId(blockState); ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(stateId); if (immutableBlockState == null || immutableBlockState.isEmpty()) return; - Object level = args[0]; - Object pos = args[1]; net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); world.playBlockSound(Vec3d.atCenterOf(LocationUtils.fromBlockPos(pos)), immutableBlockState.sounds().landSound()); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java index 14b4efb72..a6763fedb 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java @@ -3,14 +3,23 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.ParticleUtils; import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemKeys; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.shared.block.BlockBehavior; import org.bukkit.World; +import org.bukkit.block.Block; import java.util.Map; @@ -57,6 +66,39 @@ public class GrassBlockBehavior extends BukkitBlockBehavior { return true; } + @SuppressWarnings("DuplicatedCode") + @Override + public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { + Item item = context.getItem(); + if (item == null || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || context.getPlayer().isAdventureMode()) + return InteractionResult.PASS; + BlockPos pos = context.getClickedPos(); + BukkitBlockInWorld upper = (BukkitBlockInWorld) context.getLevel().getBlockAt(pos.x(), pos.y() + 1, pos.z()); + Block block = upper.block(); + if (!block.isEmpty()) + return InteractionResult.PASS; + boolean sendSwing = false; + try { + Object visualState = state.vanillaBlockState().handle(); + Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState); + if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { + boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); + if (!is) { + sendSwing = true; + } + } else { + sendSwing = true; + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to check visual state bone meal state", e); + return InteractionResult.FAIL; + } + if (sendSwing) { + context.getPlayer().swingHand(context.getHand()); + } + return InteractionResult.SUCCESS; + } + @Override public void performBoneMeal(Object thisBlock, Object[] args) { FastNMS.INSTANCE.method$GrassBlock$performBoneMeal(args[0], args[1], args[2], args[3], thisBlock); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangingBlockBehavior.java index 7366aad9d..39ff98d0b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/HangingBlockBehavior.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.Tuple; import net.momirealms.craftengine.shared.block.BlockBehavior; @@ -13,8 +14,8 @@ import java.util.Set; public class HangingBlockBehavior extends BushBlockBehavior { public static final Factory FACTORY = new Factory(); - public HangingBlockBehavior(CustomBlock block, List tagsCanSurviveOn, Set blocksCansSurviveOn, Set customBlocksCansSurviveOn) { - super(block, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn); + public HangingBlockBehavior(CustomBlock block, int delay, boolean blacklist, boolean stackable, List tagsCanSurviveOn, Set blocksCansSurviveOn, Set customBlocksCansSurviveOn) { + super(block, delay, blacklist, stackable, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn); } @Override @@ -32,7 +33,10 @@ public class HangingBlockBehavior extends BushBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { Tuple, Set, Set> tuple = readTagsAndState(arguments, true); - return new HangingBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right()); + boolean stackable = (boolean) arguments.getOrDefault("stackable", false); + int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); + boolean blacklistMode = (boolean) arguments.getOrDefault("blacklist", false); + return new HangingBlockBehavior(block, delay, blacklistMode, stackable, tuple.left(), tuple.mid(), tuple.right()); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java new file mode 100644 index 000000000..fd6f67e78 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LampBlockBehavior.java @@ -0,0 +1,81 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.shared.block.BlockBehavior; + +import java.util.Map; +import java.util.concurrent.Callable; + +public class LampBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final Property litProperty; + + public LampBlockBehavior(CustomBlock block, Property litProperty) { + super(block); + this.litProperty = litProperty; + } + + @Override + public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { + Object level = context.getLevel().serverWorld(); + state = state.with(this.litProperty, FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, LocationUtils.toBlockPos(context.getClickedPos()))); + return state; + } + + @Override + public void tick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object blockState = args[0]; + ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState)); + if (state == null || state.isEmpty()) return; + Object world = args[1]; + Object blockPos = args[2]; + if (state.get(this.litProperty)) { + if (!FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(world, blockPos)) { + // TODO Call Event + FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, state.cycle(this.litProperty).customBlockState().handle(), 2); + } + } else { + if (FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(world, blockPos)) { + // TODO Call Event + FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, state.cycle(this.litProperty).customBlockState().handle(), 2); + } + } + } + + @Override + public void neighborChanged(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object blockState = args[0]; + ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState)); + if (state == null || state.isEmpty()) return; + Object world = args[1]; + Object blockPos = args[2]; + boolean lit = state.get(this.litProperty); + if (lit != FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(world, blockPos)) { + if (lit) { + Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 4); + } else { + // TODO Call Event + FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, state.cycle(this.litProperty).customBlockState().handle(), 2); + } + } + } + + @SuppressWarnings("unchecked") + public static class Factory implements BlockBehaviorFactory { + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + Property lit = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("lit"), "warning.config.block.behavior.lamp.missing_lit"); + return new LampBlockBehavior(block, lit); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java index 0da9cba81..d6206696d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/LeavesBlockBehavior.java @@ -30,6 +30,7 @@ import org.bukkit.event.block.LeavesDecayEvent; import org.jetbrains.annotations.Nullable; import java.util.Map; +import java.util.Optional; import java.util.concurrent.Callable; public class LeavesBlockBehavior extends WaterLoggedBlockBehavior { @@ -78,10 +79,14 @@ public class LeavesBlockBehavior extends WaterLoggedBlockBehavior { neighborState = args[2]; } ImmutableBlockState thisState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState)); - if (thisState != null && thisState.behavior() instanceof LeavesBlockBehavior behavior) { - int distance = behavior.getDistanceAt(neighborState) + 1; - if (distance != 1 || behavior.getDistance(thisState) != distance) { - Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 1); + if (thisState != null) { + Optional optionalBehavior = thisState.behavior().getAs(LeavesBlockBehavior.class); + if (optionalBehavior.isPresent()) { + LeavesBlockBehavior behavior = optionalBehavior.get(); + int distance = behavior.getDistanceAt(neighborState) + 1; + if (distance != 1 || behavior.getDistance(thisState) != distance) { + Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 1); + } } } return blockState; @@ -93,13 +98,17 @@ public class LeavesBlockBehavior extends WaterLoggedBlockBehavior { Object level = args[1]; Object blockPos = args[2]; ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState)); - if (currentState != null && !currentState.isEmpty() && currentState.behavior() instanceof LeavesBlockBehavior behavior) { - ImmutableBlockState newState = behavior.updateDistance(currentState, level, blockPos); - if (newState != currentState) { - if (blockState == newState.customBlockState().handle()) { - Reflections.method$BlockStateBase$updateNeighbourShapes.invoke(blockState, level, blockPos, UpdateOption.UPDATE_ALL.flags(), 512); - } else { - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, newState.customBlockState().handle(), UpdateOption.UPDATE_ALL.flags()); + if (currentState != null && !currentState.isEmpty()) { + Optional optionalBehavior = currentState.behavior().getAs(LeavesBlockBehavior.class); + if (optionalBehavior.isPresent()) { + LeavesBlockBehavior behavior = optionalBehavior.get(); + ImmutableBlockState newState = behavior.updateDistance(currentState, level, blockPos); + if (newState != currentState) { + if (blockState == newState.customBlockState().handle()) { + Reflections.method$BlockStateBase$updateNeighbourShapes.invoke(blockState, level, blockPos, UpdateOption.UPDATE_ALL.flags(), 512); + } else { + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, newState.customBlockState().handle(), UpdateOption.UPDATE_ALL.flags()); + } } } } @@ -110,25 +119,31 @@ public class LeavesBlockBehavior extends WaterLoggedBlockBehavior { Object level = args[1]; Object blockPos = args[2]; ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(args[0])); - if (immutableBlockState != null && immutableBlockState.behavior() instanceof LeavesBlockBehavior behavior && behavior.isDecaying(immutableBlockState)) { - World bukkitWorld = FastNMS.INSTANCE.method$Level$getCraftWorld(level); - BlockPos pos = LocationUtils.fromBlockPos(blockPos); - // call bukkit event - LeavesDecayEvent event = new LeavesDecayEvent(bukkitWorld.getBlockAt(pos.x(), pos.y(), pos.z())); - Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) { - return; - } - FastNMS.INSTANCE.method$Level$removeBlock(level, blockPos, false); - if (isWaterLogged(immutableBlockState)) { - bukkitWorld.setBlockData(pos.x(), pos.y(), pos.z(), Material.WATER.createBlockData()); - } - net.momirealms.craftengine.core.world.World world = new BukkitWorld(bukkitWorld); - WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); - ContextHolder.Builder builder = ContextHolder.builder() - .withParameter(DirectContextParameters.POSITION, position); - for (Item item : immutableBlockState.getDrops(builder, world, null)) { - world.dropItemNaturally(position, item); + if (immutableBlockState != null) { + Optional optionalBehavior = immutableBlockState.behavior().getAs(LeavesBlockBehavior.class); + if (optionalBehavior.isPresent()) { + LeavesBlockBehavior behavior = optionalBehavior.get(); + if (behavior.isDecaying(immutableBlockState)) { + World bukkitWorld = FastNMS.INSTANCE.method$Level$getCraftWorld(level); + BlockPos pos = LocationUtils.fromBlockPos(blockPos); + // call bukkit event + LeavesDecayEvent event = new LeavesDecayEvent(bukkitWorld.getBlockAt(pos.x(), pos.y(), pos.z())); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + FastNMS.INSTANCE.method$Level$removeBlock(level, blockPos, false); + if (isWaterLogged(immutableBlockState)) { + bukkitWorld.setBlockData(pos.x(), pos.y(), pos.z(), Material.WATER.createBlockData()); + } + net.momirealms.craftengine.core.world.World world = new BukkitWorld(bukkitWorld); + WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos)); + ContextHolder.Builder builder = ContextHolder.builder() + .withParameter(DirectContextParameters.POSITION, position); + for (Item item : immutableBlockState.getDrops(builder, world, null)) { + world.dropItemNaturally(position, item); + } + } } } } @@ -164,8 +179,8 @@ public class LeavesBlockBehavior extends WaterLoggedBlockBehavior { return (int) Reflections.method$StateHolder$getValue.invoke(blockState, distanceProperty); } else { ImmutableBlockState anotherBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(id); - if (!(anotherBlockState.behavior() instanceof LeavesBlockBehavior otherBehavior)) return this.maxDistance; - return otherBehavior.getDistance(anotherBlockState); + Optional optionalAnotherBehavior = anotherBlockState.behavior().getAs(LeavesBlockBehavior.class); + return optionalAnotherBehavior.map(leavesBlockBehavior -> leavesBlockBehavior.getDistance(anotherBlockState)).orElse(this.maxDistance); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/NearLiquidBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/NearLiquidBlockBehavior.java new file mode 100644 index 000000000..dc5554c0f --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/NearLiquidBlockBehavior.java @@ -0,0 +1,104 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.shared.block.BlockBehavior; + +import java.util.List; +import java.util.Map; + +public class NearLiquidBlockBehavior extends AbstractCanSurviveBlockBehavior { + private static final List WATER = List.of(Reflections.instance$Fluids$WATER, Reflections.instance$Fluids$FLOWING_WATER); + private static final List LAVA = List.of(Reflections.instance$Fluids$LAVA, Reflections.instance$Fluids$FLOWING_LAVA); + public static final Factory FACTORY = new Factory(); + private final boolean onWater; + private final boolean onLava; + private final boolean stackable; + private final BlockPos[] positions; + + public NearLiquidBlockBehavior(CustomBlock block, int delay, BlockPos[] positions, boolean stackable, boolean onWater, boolean onLava) { + super(block, delay); + this.onWater = onWater; + this.onLava = onLava; + this.stackable = stackable; + this.positions = positions; + } + + public boolean onWater() { + return this.onWater; + } + + public boolean onLava() { + return this.onLava; + } + + public static class Factory implements BlockBehaviorFactory { + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + List liquidTypes = MiscUtils.getAsStringList(arguments.getOrDefault("liquid-type", List.of("water"))); + boolean stackable = (boolean) arguments.getOrDefault("stackable", false); + int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); + List positionsToCheck = MiscUtils.getAsStringList(arguments.getOrDefault("positions", List.of())); + if (positionsToCheck.isEmpty()) { + return new NearLiquidBlockBehavior(block, delay, new BlockPos[]{new BlockPos(0,-1,0)}, stackable, liquidTypes.contains("water"), liquidTypes.contains("lava")); + } else { + BlockPos[] pos = new BlockPos[positionsToCheck.size()]; + for (int i = 0; i < pos.length; i++) { + String[] split = positionsToCheck.get(i).split(","); + pos[i] = new BlockPos(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2])); + } + return new NearLiquidBlockBehavior(block, delay, pos, stackable, liquidTypes.contains("water"), liquidTypes.contains("lava")); + } + } + } + + @Override + protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException { + int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos); + int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos); + int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos); + if (this.stackable) { + Object belowPos = FastNMS.INSTANCE.constructor$BlockPos(x, y - 1, z); + Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, belowPos); + int id = BlockStateUtils.blockStateToId(belowState); + if (!BlockStateUtils.isVanillaBlock(id)) { + ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(id); + if (immutableBlockState.owner().value() == super.customBlock) { + return true; + } + } + } + for (BlockPos pos : positions) { + Object belowPos = FastNMS.INSTANCE.constructor$BlockPos(x + pos.x(), y + pos.y(), z + pos.z()); + Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, belowPos); + if (mayPlaceOn(belowState, world, belowPos)) { + return true; + } + } + return false; + } + + protected boolean mayPlaceOn(Object belowState, Object world, Object belowPos) throws ReflectiveOperationException { + Object fluidState = Reflections.method$Level$getFluidState.invoke(world, belowPos); + Object fluidStateAbove = Reflections.method$Level$getFluidState.invoke(world, LocationUtils.above(belowPos)); + if (Reflections.method$FluidState$getType.invoke(fluidStateAbove) != Reflections.instance$Fluids$EMPTY) { + return false; + } + if (this.onWater && (WATER.contains(Reflections.method$FluidState$getType.invoke(fluidState)) || FastNMS.INSTANCE.method$BlockState$getBlock(belowState) == Reflections.instance$Blocks$ICE)) { + return true; + } + if (this.onLava && LAVA.contains(Reflections.method$FluidState$getType.invoke(fluidState))) { + return true; + } + return false; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/OnLiquidBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/OnLiquidBlockBehavior.java index dda596013..5fb023c2b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/OnLiquidBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/OnLiquidBlockBehavior.java @@ -1,25 +1,31 @@ package net.momirealms.craftengine.bukkit.block.behavior; +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.shared.block.BlockBehavior; import java.util.List; import java.util.Map; -import java.util.Set; -public class OnLiquidBlockBehavior extends BushBlockBehavior { +public class OnLiquidBlockBehavior extends AbstractCanSurviveBlockBehavior { public static final Factory FACTORY = new Factory(); private final boolean onWater; private final boolean onLava; + private final boolean stackable; - public OnLiquidBlockBehavior(CustomBlock block, boolean onWater, boolean onLava) { - super(block, List.of(), Set.of(), Set.of()); + public OnLiquidBlockBehavior(CustomBlock block, int delay, boolean stackable, boolean onWater, boolean onLava) { + super(block, delay); this.onWater = onWater; this.onLava = onLava; + this.stackable = stackable; } public boolean onWater() { @@ -34,18 +40,38 @@ public class OnLiquidBlockBehavior extends BushBlockBehavior { @Override public BlockBehavior create(CustomBlock block, Map arguments) { List liquidTypes = MiscUtils.getAsStringList(arguments.getOrDefault("liquid-type", List.of("water"))); - return new OnLiquidBlockBehavior(block, liquidTypes.contains("water"), liquidTypes.contains("lava")); + boolean stackable = (boolean) arguments.getOrDefault("stackable", false); + int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); + return new OnLiquidBlockBehavior(block, delay, stackable, liquidTypes.contains("water"), liquidTypes.contains("lava")); } } @Override + protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException { + int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos); + int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos); + int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos); + Object belowPos = FastNMS.INSTANCE.constructor$BlockPos(x, y - 1, z); + Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, belowPos); + return mayPlaceOn(belowState, world, belowPos); + } + protected boolean mayPlaceOn(Object belowState, Object world, Object belowPos) throws ReflectiveOperationException { + if (this.stackable) { + int id = BlockStateUtils.blockStateToId(belowState); + if (!BlockStateUtils.isVanillaBlock(id)) { + ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(id); + if (immutableBlockState.owner().value() == super.customBlock) { + return true; + } + } + } Object fluidState = Reflections.method$Level$getFluidState.invoke(world, belowPos); Object fluidStateAbove = Reflections.method$Level$getFluidState.invoke(world, LocationUtils.above(belowPos)); if (Reflections.method$FluidState$getType.invoke(fluidStateAbove) != Reflections.instance$Fluids$EMPTY) { return false; } - if (this.onWater && (Reflections.method$FluidState$getType.invoke(fluidState) == Reflections.instance$Fluids$WATER || Reflections.field$StateHolder$owner.get(belowState) == Reflections.instance$Blocks$ICE)) { + if (this.onWater && (Reflections.method$FluidState$getType.invoke(fluidState) == Reflections.instance$Fluids$WATER || FastNMS.INSTANCE.method$BlockState$getBlock(belowState) == Reflections.instance$Blocks$ICE)) { return true; } if (this.onLava && Reflections.method$FluidState$getType.invoke(fluidState) == Reflections.instance$Fluids$LAVA) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java index 6242f1103..c83b5ff1a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java @@ -9,30 +9,31 @@ import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemKeys; +import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.RandomUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import net.momirealms.craftengine.core.util.Tuple; import net.momirealms.craftengine.shared.block.BlockBehavior; import org.bukkit.Location; import org.bukkit.World; -import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.concurrent.Callable; -public class SaplingBlockBehavior extends BushBlockBehavior { +public class SaplingBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final Key feature; private final Property stageProperty; private final double boneMealSuccessChance; private final float growSpeed; - public SaplingBlockBehavior(CustomBlock block, Key feature, Property stageProperty, List tagsCanSurviveOn, Set blocksCansSurviveOn, Set customBlocksCansSurviveOn, double boneMealSuccessChance, float growSpeed) { - super(block, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn); + public SaplingBlockBehavior(CustomBlock block, Key feature, Property stageProperty, double boneMealSuccessChance, float growSpeed) { + super(block); this.feature = feature; this.stageProperty = stageProperty; this.boneMealSuccessChance = boneMealSuccessChance; @@ -49,7 +50,7 @@ public class SaplingBlockBehavior extends BushBlockBehavior { Object blockPos = args[2]; Object blockState = args[0]; Object aboveBlockPos = LocationUtils.above(blockPos); - if ((int) Reflections.method$LevelReader$getMaxLocalRawBrightness.invoke(world, aboveBlockPos) >= 9 && (float) RandomUtils.generateRandomFloat(0, 1) < growSpeed) { + if ((int) Reflections.method$LevelReader$getMaxLocalRawBrightness.invoke(world, aboveBlockPos) >= 9 && RandomUtils.generateRandomFloat(0, 1) < growSpeed) { increaseStage(world, blockPos, blockState, args[3]); } } @@ -135,6 +136,34 @@ public class SaplingBlockBehavior extends BushBlockBehavior { this.increaseStage(args[0], args[2], args[3], args[1]); } + @SuppressWarnings("DuplicatedCode") + @Override + public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { + Item item = context.getItem(); + if (item == null || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || context.getPlayer().isAdventureMode()) + return InteractionResult.PASS; + boolean sendSwing = false; + try { + Object visualState = state.vanillaBlockState().handle(); + Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState); + if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { + boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); + if (!is) { + sendSwing = true; + } + } else { + sendSwing = true; + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to check visual state bone meal state", e); + return InteractionResult.FAIL; + } + if (sendSwing) { + context.getPlayer().swingHand(context.getHand()); + } + return InteractionResult.SUCCESS; + } + public static class Factory implements BlockBehaviorFactory { @SuppressWarnings("unchecked") @@ -143,8 +172,7 @@ public class SaplingBlockBehavior extends BushBlockBehavior { String feature = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("feature"), "warning.config.block.behavior.sapling.missing_feature"); Property stageProperty = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("stage"), "warning.config.block.behavior.sapling.missing_stage"); double boneMealSuccessChance = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("bone-meal-success-chance", 0.45), "bone-meal-success-chance"); - Tuple, Set, Set> tuple = readTagsAndState(arguments, false); - return new SaplingBlockBehavior(block, Key.of(feature), stageProperty, tuple.left(), tuple.mid(), tuple.right(), boneMealSuccessChance, + return new SaplingBlockBehavior(block, Key.of(feature), stageProperty, boneMealSuccessChance, ResourceConfigUtils.getAsFloat(arguments.getOrDefault("grow-speed", 1.0 / 7.0), "grow-speed")); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StrippableBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StrippableBlockBehavior.java index f1a2478fe..5e9b41d6f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StrippableBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/StrippableBlockBehavior.java @@ -1,15 +1,42 @@ package net.momirealms.craftengine.bukkit.block.behavior; +import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.util.*; +import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.entity.player.InteractionHand; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.item.CustomItem; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemKeys; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.shared.block.BlockBehavior; +import net.momirealms.sparrow.nbt.CompoundTag; +import org.bukkit.GameEvent; +import org.bukkit.Material; +import org.bukkit.Statistic; +import org.bukkit.block.Block; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; import java.util.Map; +import java.util.Optional; public class StrippableBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); + private static final Key AXE_STRIP_SOUND = Key.of("minecraft:item.axe.strip"); private final Key stripped; public StrippableBlockBehavior(CustomBlock block, Key stripped) { @@ -18,7 +45,85 @@ public class StrippableBlockBehavior extends BukkitBlockBehavior { } public Key stripped() { - return stripped; + return this.stripped; + } + + @SuppressWarnings("unchecked") + @Override + public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { + Item item = (Item) context.getItem(); + if (item == null) { + return InteractionResult.PASS; + } + Optional> optionalCustomItem = item.getCustomItem(); + if (optionalCustomItem.isEmpty()) { + if (!item.is(ItemTags.AXES)) + return InteractionResult.PASS; + } else { + CustomItem customItem = optionalCustomItem.get(); + if (!customItem.settings().tags().contains(ItemTags.AXES) && !item.is(ItemTags.AXES)) + return InteractionResult.PASS; + } + + Player player = context.getPlayer(); + // no adventure mode + if (player.isAdventureMode()) { + return InteractionResult.PASS; + } + Item offHandItem = player.getItemInHand(InteractionHand.OFF_HAND); + // is using a shield + if (context.getHand() == InteractionHand.MAIN_HAND && offHandItem != null && offHandItem.vanillaId().equals(ItemKeys.SHIELD) && !player.isSecondaryUseActive()) { + return InteractionResult.PASS; + } + + Optional optionalNewCustomBlock = BukkitBlockManager.instance().blockById(stripped()); + if (optionalNewCustomBlock.isEmpty()) { + CraftEngine.instance().logger().warn("stripped block " + stripped() + " does not exist"); + return InteractionResult.FAIL; + } + CustomBlock newCustomBlock = optionalNewCustomBlock.get(); + CompoundTag compoundTag = state.propertiesNbt(); + ImmutableBlockState newState = newCustomBlock.getBlockState(compoundTag); + + org.bukkit.entity.Player bukkitPlayer = ((org.bukkit.entity.Player) player.platformPlayer()); + + BukkitBlockInWorld clicked = (BukkitBlockInWorld) context.getLevel().getBlockAt(context.getClickedPos()); + Block block = clicked.block(); + // Call bukkit event + EntityChangeBlockEvent event = new EntityChangeBlockEvent(bukkitPlayer, block, BlockStateUtils.fromBlockData(newState.customBlockState().handle())); + if (EventUtils.fireAndCheckCancel(event)) { + return InteractionResult.FAIL; + } + + BlockPos pos = context.getClickedPos(); + context.getLevel().playBlockSound(Vec3d.atCenterOf(pos), AXE_STRIP_SOUND, 1, 1); + CraftEngineBlocks.place(block.getLocation(), newState, UpdateOption.UPDATE_ALL_IMMEDIATE, false); + block.getWorld().sendGameEvent(bukkitPlayer, GameEvent.BLOCK_CHANGE, new Vector(pos.x(), pos.y(), pos.z())); + Material material = MaterialUtils.getMaterial(item.vanillaId()); + bukkitPlayer.setStatistic(Statistic.USE_ITEM, material, bukkitPlayer.getStatistic(Statistic.USE_ITEM, material) + 1); + + // resend swing if it's not interactable on client side + if (!InteractUtils.isInteractable( + bukkitPlayer, BlockStateUtils.fromBlockData(state.vanillaBlockState().handle()), + context.getHitResult(), item + ) || player.isSecondaryUseActive()) { + player.swingHand(context.getHand()); + } + // shrink item amount + if (VersionHelper.isOrAbove1_20_5()) { + Object itemStack = item.getLiteralObject(); + Object serverPlayer = player.serverPlayer(); + Object equipmentSlot = context.getHand() == InteractionHand.MAIN_HAND ? Reflections.instance$EquipmentSlot$MAINHAND : Reflections.instance$EquipmentSlot$OFFHAND; + try { + Reflections.method$ItemStack$hurtAndBreak.invoke(itemStack, 1, serverPlayer, equipmentSlot); + } catch (ReflectiveOperationException e) { + CraftEngine.instance().logger().warn("Failed to hurt itemStack", e); + } + } else { + ItemStack itemStack = item.getItem(); + itemStack.damage(1, bukkitPlayer); + } + return InteractionResult.SUCCESS; } public static class Factory implements BlockBehaviorFactory { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SugarCaneBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SugarCaneBlockBehavior.java deleted file mode 100644 index 7606c343c..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SugarCaneBlockBehavior.java +++ /dev/null @@ -1,210 +0,0 @@ -package net.momirealms.craftengine.bukkit.block.behavior; - -import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; -import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; -import net.momirealms.craftengine.bukkit.util.LocationUtils; -import net.momirealms.craftengine.bukkit.util.Reflections; -import net.momirealms.craftengine.bukkit.world.BukkitWorld; -import net.momirealms.craftengine.core.block.CustomBlock; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.UpdateOption; -import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; -import net.momirealms.craftengine.core.block.properties.IntegerProperty; -import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.plugin.context.ContextHolder; -import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.util.*; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.Vec3d; -import net.momirealms.craftengine.core.world.WorldEvents; -import net.momirealms.craftengine.core.world.WorldPosition; -import net.momirealms.craftengine.shared.block.BlockBehavior; - -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; - -public class SugarCaneBlockBehavior extends BushBlockBehavior { - public static final Factory FACTORY = new Factory(); - private static final List WATER = List.of(Reflections.instance$Fluids$WATER, Reflections.instance$Fluids$FLOWING_WATER); - private static final List LAVA = List.of(Reflections.instance$Fluids$LAVA, Reflections.instance$Fluids$FLOWING_LAVA); - private static final List HORIZON_DIRECTIONS = List.of(Reflections.instance$Direction$NORTH, Reflections.instance$Direction$EAST, Reflections.instance$Direction$SOUTH, Reflections.instance$Direction$WEST); - private final int maxHeight; - private final boolean nearWater; - private final boolean nearLava; - private final IntegerProperty ageProperty; - private final float growSpeed; - - public SugarCaneBlockBehavior(CustomBlock customBlock, List tagsCanSurviveOn, Set blocksCansSurviveOn, Set customBlocksCansSurviveOn, Property ageProperty, - int maxHeight, boolean nearWater, boolean nearLava, float growSpeed) { - super(customBlock, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn); - this.nearWater = nearWater; - this.nearLava = nearLava; - this.maxHeight = maxHeight; - this.ageProperty = (IntegerProperty) ageProperty; - this.growSpeed = growSpeed; - } - - @Override - public void tick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - Object blockState = args[0]; - Object level = args[1]; - Object blockPos = args[2]; - if (!canSurvive(thisBlock, blockState, level, blockPos)) { - int stateId = BlockStateUtils.blockStateToId(blockState); - ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(stateId); - if (currentState != null && !currentState.isEmpty()) { - // break the sugar cane - FastNMS.INSTANCE.method$Level$removeBlock(level, blockPos, false); - net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level)); - WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(LocationUtils.fromBlockPos(blockPos))); - ContextHolder.Builder builder = ContextHolder.builder() - .withParameter(DirectContextParameters.POSITION, position); - for (Item item : currentState.getDrops(builder, world, null)) { - world.dropItemNaturally(position, item); - } - world.playBlockSound(position, currentState.sounds().breakSound()); - FastNMS.INSTANCE.method$Level$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, stateId); - } - } - } - - @Override - public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - Object world; - Object blockPos; - if (VersionHelper.isOrAbove1_21_2()) { - world = args[1]; - blockPos = args[3]; - } else { - world = args[3]; - blockPos = args[4]; - } - Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 1); - // return state, do not call super. - return superMethod.call(); - } - - @Override - public void randomTick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - Object blockState = args[0]; - Object level = args[1]; - Object blockPos = args[2]; - // above block is empty - if (FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, LocationUtils.above(blockPos)) == Reflections.instance$Blocks$AIR$defaultState) { - int currentHeight = 1; - BlockPos currentPos = LocationUtils.fromBlockPos(blockPos); - ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState)); - if (currentState != null && !currentState.isEmpty()) { - while (true) { - Object belowPos = LocationUtils.toBlockPos(currentPos.x(), currentPos.y() - currentHeight, currentPos.z()); - Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, belowPos); - ImmutableBlockState belowImmutableState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(belowState)); - if (belowImmutableState != null && !belowImmutableState.isEmpty() && belowImmutableState.owner() == currentState.owner()) { - currentHeight++; - } else { - break; - } - } - } else { - return; - } - - if (currentHeight < this.maxHeight) { - int age = currentState.get(ageProperty); - if (age >= this.ageProperty.max || RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) { - Object abovePos = LocationUtils.above(blockPos); - if (VersionHelper.isOrAbove1_21_5()) { - Reflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, abovePos, super.customBlock.defaultState().customBlockState().handle(), UpdateOption.UPDATE_ALL.flags()); - } else { - Reflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, abovePos, super.customBlock.defaultState().customBlockState().handle()); - } - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, currentState.with(this.ageProperty, this.ageProperty.min).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags()); - } else if (RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) { - FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, currentState.with(this.ageProperty, age + 1).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags()); - } - } - } - } - - @Override - protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException { - int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos); - int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos); - int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos); - Object belowPos = FastNMS.INSTANCE.constructor$BlockPos(x, y - 1, z); - Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, belowPos); - int id = BlockStateUtils.blockStateToId(belowState); - // 如果下方是同种方块 - if (!BlockStateUtils.isVanillaBlock(id)) { - ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(id); - if (immutableBlockState.owner().value() == super.customBlock) { - return true; - } - } - if (!super.mayPlaceOn(belowState, world, belowPos)) { - return false; - } - // 如果不需要依靠流体 - if (!this.nearWater && !this.nearLava) { - return true; - } - // 需要流体 - if (this.nearWater) { - if (hasNearbyLiquid(world, belowPos, true)) { - return true; - } - } - if (this.nearLava) { - if (hasNearbyLiquid(world, belowPos, false)) { - return true; - } - } - return false; - } - - private boolean hasNearbyLiquid(Object world, Object blockPos, boolean waterOrLava) throws ReflectiveOperationException { - for (Object direction : HORIZON_DIRECTIONS) { - Object relativePos = Reflections.method$BlockPos$relative.invoke(blockPos, direction); - if (waterOrLava) { - // water - Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, relativePos); - if (Reflections.method$BlockStateBase$getBlock.invoke(blockState) == Reflections.instance$Blocks$ICE) { - return true; - } - Object fluidState = Reflections.method$Level$getFluidState.invoke(world, relativePos); - Object fluidType = Reflections.method$FluidState$getType.invoke(fluidState); - if (WATER.contains(fluidType)) { - return true; - } - } else { - // lava - Object fluidState = Reflections.method$Level$getFluidState.invoke(world, relativePos); - Object fluidType = Reflections.method$FluidState$getType.invoke(fluidState); - if (LAVA.contains(fluidType)) { - return true; - } - } - } - return false; - } - - public static class Factory implements BlockBehaviorFactory { - - @SuppressWarnings("unchecked") - @Override - public BlockBehavior create(CustomBlock block, Map arguments) { - Tuple, Set, Set> tuple = readTagsAndState(arguments, false); - Property ageProperty = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("age"), "warning.config.block.behavior.sugar_cane.missing_age"); - int maxHeight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("max-height", 3), "max-height"); - List nearbyLiquids = MiscUtils.getAsStringList(arguments.getOrDefault("required-adjacent-liquids", List.of())); - boolean nearWater = nearbyLiquids.contains("water"); - boolean nearLava = nearbyLiquids.contains("lava"); - return new SugarCaneBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right(), ageProperty, maxHeight, nearWater, nearLava, - ResourceConfigUtils.getAsFloat(arguments.getOrDefault("grow-speed", 1), "grow-speed")); - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/VerticalCropBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/VerticalCropBlockBehavior.java new file mode 100644 index 000000000..3c36cce5d --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/VerticalCropBlockBehavior.java @@ -0,0 +1,93 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.UpdateOption; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.properties.IntegerProperty; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.util.RandomUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.shared.block.BlockBehavior; + +import java.util.Map; +import java.util.concurrent.Callable; + +public class VerticalCropBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final int maxHeight; + private final IntegerProperty ageProperty; + private final float growSpeed; + private final boolean direction; + + public VerticalCropBlockBehavior(CustomBlock customBlock, Property ageProperty, + int maxHeight, float growSpeed, boolean direction) { + super(customBlock); + this.maxHeight = maxHeight; + this.ageProperty = (IntegerProperty) ageProperty; + this.growSpeed = growSpeed; + this.direction = direction; + } + + @Override + public void randomTick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object blockState = args[0]; + Object level = args[1]; + Object blockPos = args[2]; + // above block is empty + if (FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, (this.direction ? LocationUtils.above(blockPos) : LocationUtils.below(blockPos))) == Reflections.instance$Blocks$AIR$defaultState) { + int currentHeight = 1; + BlockPos currentPos = LocationUtils.fromBlockPos(blockPos); + ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState)); + if (currentState != null && !currentState.isEmpty()) { + while (true) { + Object nextPos = LocationUtils.toBlockPos(currentPos.x(), this.direction ? currentPos.y() - currentHeight : currentPos.y() + currentHeight, currentPos.z()); + Object nextState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, nextPos); + ImmutableBlockState belowImmutableState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(nextState)); + if (belowImmutableState != null && !belowImmutableState.isEmpty() && belowImmutableState.owner() == currentState.owner()) { + currentHeight++; + } else { + break; + } + } + } else { + return; + } + + if (currentHeight < this.maxHeight) { + int age = currentState.get(ageProperty); + if (age >= this.ageProperty.max || RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) { + Object nextPos = this.direction ? LocationUtils.above(blockPos) : LocationUtils.below(blockPos); + if (VersionHelper.isOrAbove1_21_5()) { + Reflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, nextPos, super.customBlock.defaultState().customBlockState().handle(), UpdateOption.UPDATE_ALL.flags()); + } else { + Reflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, nextPos, super.customBlock.defaultState().customBlockState().handle()); + } + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, currentState.with(this.ageProperty, this.ageProperty.min).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags()); + } else if (RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) { + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, currentState.with(this.ageProperty, age + 1).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags()); + } + } + } + } + + public static class Factory implements BlockBehaviorFactory { + + @SuppressWarnings("unchecked") + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + Property ageProperty = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("age"), "warning.config.block.behavior.sugar_cane.missing_age"); + int maxHeight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("max-height", 3), "max-height"); + boolean direction = arguments.getOrDefault("direction", "up").toString().equalsIgnoreCase("up"); + return new VerticalCropBlockBehavior(block, ageProperty, maxHeight, + ResourceConfigUtils.getAsFloat(arguments.getOrDefault("grow-speed", 1), "grow-speed"), direction); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitEntity.java index ffe08c8fd..2193b86f9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitEntity.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/BukkitEntity.java @@ -42,12 +42,12 @@ public class BukkitEntity extends AbstractEntity { } @Override - public float getXRot() { + public float xRot() { return literalObject().getYaw(); } @Override - public float getYRot() { + public float yRot() { return literalObject().getPitch(); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitCustomFurniture.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitCustomFurniture.java new file mode 100644 index 000000000..5f2d30270 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitCustomFurniture.java @@ -0,0 +1,74 @@ +package net.momirealms.craftengine.bukkit.entity.furniture; + +import net.momirealms.craftengine.core.entity.furniture.AbstractCustomFurniture; +import net.momirealms.craftengine.core.entity.furniture.AnchorType; +import net.momirealms.craftengine.core.entity.furniture.CustomFurniture; +import net.momirealms.craftengine.core.entity.furniture.FurnitureSettings; +import net.momirealms.craftengine.core.loot.LootTable; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; +import net.momirealms.craftengine.core.plugin.context.function.Function; +import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; + +public class BukkitCustomFurniture extends AbstractCustomFurniture { + + protected BukkitCustomFurniture(@NotNull Key id, + @NotNull FurnitureSettings settings, + @NotNull Map placements, + @NotNull Map>> events, + @Nullable LootTable lootTable) { + super(id, settings, placements, events, lootTable); + } + + public static Builder builder() { + return new BuilderImpl(); + } + + public static class BuilderImpl implements Builder { + private Key id; + private Map placements; + private FurnitureSettings settings; + private Map>> events; + private LootTable lootTable; + + @Override + public CustomFurniture build() { + return new BukkitCustomFurniture(id, settings, placements, events, lootTable); + } + + @Override + public Builder id(Key id) { + this.id = id; + return this; + } + + @Override + public Builder placement(Map placements) { + this.placements = placements; + return this; + } + + @Override + public Builder settings(FurnitureSettings settings) { + this.settings = settings; + return this; + } + + @Override + public Builder lootTable(LootTable lootTable) { + this.lootTable = lootTable; + return this; + } + + @Override + public Builder events(Map>> events) { + this.events = events; + return this; + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/LoadedFurniture.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurniture.java similarity index 92% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/LoadedFurniture.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurniture.java index daaeddaf6..fb5e908b8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/LoadedFurniture.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurniture.java @@ -1,19 +1,19 @@ package net.momirealms.craftengine.bukkit.entity.furniture; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; import net.momirealms.craftengine.bukkit.entity.BukkitEntity; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.EntityUtils; import net.momirealms.craftengine.bukkit.util.LegacyAttributeUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.Reflections; -import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.core.entity.furniture.*; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.ArrayUtils; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.QuaternionUtils; import net.momirealms.craftengine.core.util.VersionHelper; -import net.momirealms.craftengine.core.world.Vec3d; -import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldPosition; import net.momirealms.craftengine.core.world.collision.AABB; import org.bukkit.Location; @@ -29,7 +29,7 @@ import java.io.IOException; import java.lang.ref.WeakReference; import java.util.*; -public class LoadedFurniture implements Furniture { +public class BukkitFurniture implements Furniture { private final Key id; private final CustomFurniture furniture; private final AnchorType anchorType; @@ -44,8 +44,8 @@ public class LoadedFurniture implements Furniture { // cache private final List fakeEntityIds; private final List entityIds; - private final Map hitBoxes = new HashMap<>(); - private final Map aabb = new HashMap<>(); + private final Map hitBoxes = new Int2ObjectArrayMap<>(); + private final Map aabb = new Int2ObjectArrayMap<>(); private final boolean minimized; private final boolean hasExternalModel; // seats @@ -55,7 +55,7 @@ public class LoadedFurniture implements Furniture { private Object cachedSpawnPacket; private Object cachedMinimizedSpawnPacket; - public LoadedFurniture(Entity baseEntity, + public BukkitFurniture(Entity baseEntity, CustomFurniture furniture, FurnitureExtraData extraData) { this.id = furniture.id(); @@ -66,8 +66,8 @@ public class LoadedFurniture implements Furniture { this.baseEntity = new WeakReference<>(baseEntity); this.furniture = furniture; this.minimized = furniture.settings().minimized(); - List fakeEntityIds = new ArrayList<>(); - List mainEntityIds = new ArrayList<>(); + List fakeEntityIds = new IntArrayList(); + List mainEntityIds = new IntArrayList(); mainEntityIds.add(this.baseEntityId); CustomFurniture.Placement placement = furniture.getPlacement(anchorType); @@ -84,19 +84,11 @@ public class LoadedFurniture implements Furniture { this.hasExternalModel = false; } - float yaw = this.location.getYaw(); - Quaternionf conjugated = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - yaw), 0).conjugate(); - - double x = location.getX(); - double y = location.getY(); - double z = location.getZ(); - + Quaternionf conjugated = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - this.location.getYaw()), 0).conjugate(); List packets = new ArrayList<>(); List minimizedPackets = new ArrayList<>(); List colliders = new ArrayList<>(); - - World world = world(); - WorldPosition position = new WorldPosition(world, x, y, z, yaw, 0); + WorldPosition position = position(); Integer dyedColor = this.extraData.dyedColor().orElse(null); for (FurnitureElement element : placement.elements()) { int entityId = Reflections.instance$Entity$ENTITY_COUNTER.incrementAndGet(); @@ -154,13 +146,8 @@ public class LoadedFurniture implements Furniture { } @Override - public Vec3d position() { - return new Vec3d(location.getX(), location.getY(), location.getZ()); - } - - @Override - public World world() { - return new BukkitWorld(this.location.getWorld()); + public WorldPosition position() { + return LocationUtils.toWorldPosition(this.location); } @NotNull @@ -170,7 +157,7 @@ public class LoadedFurniture implements Furniture { @NotNull public Entity baseEntity() { - Entity entity = baseEntity.get(); + Entity entity = this.baseEntity.get(); if (entity == null) { throw new RuntimeException("Base entity not found. It might be unloaded."); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureElement.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureElement.java index e2d2a29f8..ab6c56129 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureElement.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureElement.java @@ -7,6 +7,7 @@ import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.entity.Billboard; import net.momirealms.craftengine.core.entity.ItemDisplayContext; import net.momirealms.craftengine.core.entity.furniture.AbstractFurnitureElement; +import net.momirealms.craftengine.core.entity.furniture.FurnitureElement; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; @@ -30,10 +31,10 @@ public class BukkitFurnitureElement extends AbstractFurnitureElement { ItemDisplayContext transform, Vector3f scale, Vector3f translation, - Vector3f offset, + Vector3f position, Quaternionf rotation, boolean applyDyedColor) { - super(item, billboard, transform, scale, translation, offset, rotation, applyDyedColor); + super(item, billboard, transform, scale, translation, position, rotation, applyDyedColor); this.commonValues = new ArrayList<>(); ItemDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(scale(), this.commonValues); ItemDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(rotation(), this.commonValues); @@ -67,4 +68,72 @@ public class BukkitFurnitureElement extends AbstractFurnitureElement { ItemDisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.getLiteralObject(), cachedValues); return cachedValues; } + + public static Builder builder() { + return new BuilderImpl(); + } + + public static class BuilderImpl implements Builder { + private boolean applyDyedColor; + private Key item; + private Billboard billboard; + private ItemDisplayContext transform; + private Vector3f scale; + private Vector3f translation; + private Vector3f position; + private Quaternionf rotation; + + @Override + public Builder applyDyedColor(boolean applyDyedColor) { + this.applyDyedColor = applyDyedColor; + return this; + } + + @Override + public Builder item(Key item) { + this.item = item; + return this; + } + + @Override + public Builder billboard(Billboard billboard) { + this.billboard = billboard; + return this; + } + + @Override + public Builder transform(ItemDisplayContext transform) { + this.transform = transform; + return this; + } + + @Override + public Builder scale(Vector3f scale) { + this.scale = scale; + return this; + } + + @Override + public Builder translation(Vector3f translation) { + this.translation = translation; + return this; + } + + @Override + public Builder position(Vector3f position) { + this.position = position; + return this; + } + + @Override + public Builder rotation(Quaternionf rotation) { + this.rotation = rotation; + return this; + } + + @Override + public FurnitureElement build() { + return new BukkitFurnitureElement(item, billboard, transform, scale, translation, position, rotation, applyDyedColor); + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java index 5d35be647..4f8927967 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java @@ -6,58 +6,45 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.network.handler.FurniturePacketHandler; import net.momirealms.craftengine.bukkit.util.EntityUtils; +import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.Reflections; -import net.momirealms.craftengine.core.entity.Billboard; -import net.momirealms.craftengine.core.entity.ItemDisplayContext; import net.momirealms.craftengine.core.entity.furniture.*; -import net.momirealms.craftengine.core.loot.LootTable; -import net.momirealms.craftengine.core.pack.LoadingSequence; -import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.plugin.config.ConfigParser; -import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; -import net.momirealms.craftengine.core.plugin.context.function.Function; -import net.momirealms.craftengine.core.plugin.event.EventFunctions; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; -import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; -import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.WorldPosition; import org.bukkit.*; import org.bukkit.entity.*; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; -import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; -import javax.annotation.Nullable; import java.io.IOException; -import java.nio.file.Path; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; public class BukkitFurnitureManager extends AbstractFurnitureManager { - public static final NamespacedKey FURNITURE_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:furniture_id")); - // DEPRECATED - // public static final NamespacedKey FURNITURE_ANCHOR_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:anchor_type")); - public static final NamespacedKey FURNITURE_EXTRA_DATA_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:furniture_extra_data")); - public static final NamespacedKey FURNITURE_SEAT_BASE_ENTITY_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:seat_to_base_entity")); - public static final NamespacedKey FURNITURE_SEAT_VECTOR_3F_KEY = Objects.requireNonNull(NamespacedKey.fromString("craftengine:seat_vector")); - public static final NamespacedKey FURNITURE_COLLISION = Objects.requireNonNull(NamespacedKey.fromString("craftengine:collision")); + public static final NamespacedKey FURNITURE_KEY = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_KEY); + public static final NamespacedKey FURNITURE_EXTRA_DATA_KEY = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_EXTRA_DATA_KEY); + public static final NamespacedKey FURNITURE_SEAT_BASE_ENTITY_KEY = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_SEAT_BASE_ENTITY_KEY); + public static final NamespacedKey FURNITURE_SEAT_VECTOR_3F_KEY = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_SEAT_VECTOR_3F_KEY); + public static final NamespacedKey FURNITURE_COLLISION = KeyUtils.toNamespacedKey(FurnitureManager.FURNITURE_COLLISION); public static Class COLLISION_ENTITY_CLASS = Interaction.class; public static Object NMS_COLLISION_ENTITY_TYPE = Reflections.instance$EntityType$INTERACTION; public static ColliderType COLLISION_ENTITY_TYPE = ColliderType.INTERACTION; private static BukkitFurnitureManager instance; private final BukkitCraftEngine plugin; - private final FurnitureParser furnitureParser; - private final Map furnitureByRealEntityId = new ConcurrentHashMap<>(256, 0.5f); - private final Map furnitureByEntityId = new ConcurrentHashMap<>(512, 0.5f); + private final Map furnitureByRealEntityId = new ConcurrentHashMap<>(256, 0.5f); + private final Map furnitureByEntityId = new ConcurrentHashMap<>(512, 0.5f); // Event listeners private final Listener dismountListener; private final FurnitureEventListener furnitureEventListener; @@ -67,9 +54,9 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { } public BukkitFurnitureManager(BukkitCraftEngine plugin) { + super(plugin); instance = this; this.plugin = plugin; - this.furnitureParser = new FurnitureParser(); this.furnitureEventListener = new FurnitureEventListener(this); this.dismountListener = VersionHelper.isOrAbove1_20_3() ? new DismountListener1_20_3(this) : new DismountListener1_20(this::handleDismount); } @@ -79,7 +66,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { return this.place(LocationUtils.toLocation(position), furniture, extraData, playSound); } - public LoadedFurniture place(Location location, CustomFurniture furniture, FurnitureExtraData extraData, boolean playSound) { + public BukkitFurniture place(Location location, CustomFurniture furniture, FurnitureExtraData extraData, boolean playSound) { Optional optionalAnchorType = extraData.anchorType(); if (optionalAnchorType.isEmpty() || !furniture.isAllowedPlacement(optionalAnchorType.get())) { extraData.anchorType(furniture.getAnyPlacement()); @@ -101,128 +88,6 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { return loadedFurnitureByRealEntityId(furnitureEntity.getEntityId()); } - @Override - public ConfigParser parser() { - return this.furnitureParser; - } - - public class FurnitureParser implements ConfigParser { - public static final String[] CONFIG_SECTION_NAME = new String[] { "furniture" }; - - @Override - public String[] sectionId() { - return CONFIG_SECTION_NAME; - } - - @Override - public int loadingSequence() { - return LoadingSequence.FURNITURE; - } - - @SuppressWarnings("unchecked") - @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { - if (byId.containsKey(id)) { - throw new LocalizedResourceConfigException("warning.config.furniture.duplicate", path, id); - } - - Map lootMap = MiscUtils.castToMap(section.get("loot"), true); - Map settingsMap = MiscUtils.castToMap(section.get("settings"), true); - Map placementMap = MiscUtils.castToMap(section.get("placement"), true); - if (placementMap == null) { - throw new LocalizedResourceConfigException("warning.config.furniture.missing_placement", path, id); - } - - EnumMap placements = new EnumMap<>(AnchorType.class); - - for (Map.Entry entry : placementMap.entrySet()) { - // anchor type - AnchorType anchorType = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)); - Map placementArguments = MiscUtils.castToMap(entry.getValue(), true); - - Optional optionalLootSpawnOffset = Optional.ofNullable(placementArguments.get("loot-spawn-offset")).map(it -> MiscUtils.getAsVector3f(it, "loot-spawn-offset")); - - // furniture display elements - List elements = new ArrayList<>(); - List> elementConfigs = (List>) placementArguments.getOrDefault("elements", List.of()); - for (Map element : elementConfigs) { - String key = (String) element.get("item"); - if (key == null) { - throw new LocalizedResourceConfigException("warning.config.furniture.element.missing_item", path, id); - } - ItemDisplayContext transform = ItemDisplayContext.valueOf(element.getOrDefault("transform", "NONE").toString().toUpperCase(Locale.ENGLISH)); - Billboard billboard = Billboard.valueOf(element.getOrDefault("billboard", "FIXED").toString().toUpperCase(Locale.ENGLISH)); - FurnitureElement furnitureElement = new BukkitFurnitureElement(Key.of(key), billboard, transform, - MiscUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale"), - MiscUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation"), - MiscUtils.getAsVector3f(element.getOrDefault("position", "0"), "position"), - MiscUtils.getAsQuaternionf(element.getOrDefault("rotation", "0"), "rotation"), - (boolean) element.getOrDefault("apply-dyed-color", true) - ); - elements.add(furnitureElement); - } - - // external model providers - Optional externalModel; - if (placementArguments.containsKey("model-engine")) { - externalModel = Optional.of(plugin.compatibilityManager().createModelEngineModel(placementArguments.get("model-engine").toString())); - } else if (placementArguments.containsKey("better-model")) { - externalModel = Optional.of(plugin.compatibilityManager().createBetterModelModel(placementArguments.get("better-model").toString())); - } else { - externalModel = Optional.empty(); - } - - // add hitboxes - List> hitboxConfigs = (List>) placementArguments.getOrDefault("hitboxes", List.of()); - List hitboxes = new ArrayList<>(); - for (Map config : hitboxConfigs) { - HitBox hitBox = HitBoxTypes.fromMap(config); - hitboxes.add(hitBox); - } - if (hitboxes.isEmpty() && externalModel.isEmpty()) { - hitboxes.add(InteractionHitBox.DEFAULT); - } - - // rules - Map ruleSection = MiscUtils.castToMap(placementArguments.get("rules"), true); - if (ruleSection != null) { - RotationRule rotationRule = Optional.ofNullable((String) ruleSection.get("rotation")) - .map(it -> RotationRule.valueOf(it.toUpperCase(Locale.ENGLISH))) - .orElse(RotationRule.ANY); - AlignmentRule alignmentRule = Optional.ofNullable((String) ruleSection.get("alignment")) - .map(it -> AlignmentRule.valueOf(it.toUpperCase(Locale.ENGLISH))) - .orElse(AlignmentRule.CENTER); - placements.put(anchorType, new CustomFurniture.Placement( - elements.toArray(new FurnitureElement[0]), - hitboxes.toArray(new HitBox[0]), - rotationRule, - alignmentRule, - externalModel, - optionalLootSpawnOffset - )); - } else { - placements.put(anchorType, new CustomFurniture.Placement( - elements.toArray(new FurnitureElement[0]), - hitboxes.toArray(new HitBox[0]), - RotationRule.ANY, - AlignmentRule.CENTER, - externalModel, - optionalLootSpawnOffset - )); - } - } - - // get furniture settings - FurnitureSettings settings = FurnitureSettings.fromMap(settingsMap); - - // get loot table - LootTable lootTable = lootMap == null ? null : LootTable.fromMap(lootMap); - EnumMap>> events = EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")); - CustomFurniture furniture = new CustomFurniture(id, settings, placements, events, lootTable); - byId.put(id, furniture); - } - } - @Override public void delayedInit() { COLLISION_ENTITY_CLASS = Config.colliderType() == ColliderType.INTERACTION ? Interaction.class : Boat.class; @@ -264,19 +129,29 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { @Nullable @Override - public LoadedFurniture loadedFurnitureByRealEntityId(int entityId) { + public BukkitFurniture loadedFurnitureByRealEntityId(int entityId) { return this.furnitureByRealEntityId.get(entityId); } @Override @Nullable - public LoadedFurniture loadedFurnitureByEntityId(int entityId) { + public BukkitFurniture loadedFurnitureByEntityId(int entityId) { return this.furnitureByEntityId.get(entityId); } + @Override + protected CustomFurniture.Builder furnitureBuilder() { + return BukkitCustomFurniture.builder(); + } + + @Override + protected FurnitureElement.Builder furnitureElementBuilder() { + return BukkitFurnitureElement.builder(); + } + protected void handleBaseEntityUnload(Entity entity) { int id = entity.getEntityId(); - LoadedFurniture furniture = this.furnitureByRealEntityId.remove(id); + BukkitFurniture furniture = this.furnitureByRealEntityId.remove(id); if (furniture != null) { Location location = entity.getLocation(); boolean isPreventing = FastNMS.INSTANCE.isPreventingStatusUpdates(location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4); @@ -305,7 +180,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { if (optionalFurniture.isEmpty()) return; CustomFurniture customFurniture = optionalFurniture.get(); - LoadedFurniture previous = this.furnitureByRealEntityId.get(display.getEntityId()); + BukkitFurniture previous = this.furnitureByRealEntityId.get(display.getEntityId()); if (previous != null) return; Location location = display.getLocation(); @@ -313,7 +188,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { boolean preventChange = FastNMS.INSTANCE.isPreventingStatusUpdates(location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4); if (above1_20_1) { if (!preventChange) { - LoadedFurniture furniture = addNewFurniture(display, customFurniture); + BukkitFurniture furniture = addNewFurniture(display, customFurniture); furniture.initializeColliders(); for (Player player : display.getTrackedPlayers()) { this.plugin.adapt(player).entityPacketHandlers().computeIfAbsent(furniture.baseEntityId(), k -> new FurniturePacketHandler(furniture.fakeEntityIds())); @@ -321,7 +196,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { } } } else { - LoadedFurniture furniture = addNewFurniture(display, customFurniture); + BukkitFurniture furniture = addNewFurniture(display, customFurniture); for (Player player : display.getTrackedPlayers()) { this.plugin.adapt(player).entityPacketHandlers().computeIfAbsent(furniture.baseEntityId(), k -> new FurniturePacketHandler(furniture.fakeEntityIds())); this.plugin.networkManager().sendPacket(player, furniture.spawnPacket(player)); @@ -383,9 +258,9 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { Optional optionalFurniture = furnitureById(key); if (optionalFurniture.isPresent()) { CustomFurniture customFurniture = optionalFurniture.get(); - LoadedFurniture previous = this.furnitureByRealEntityId.get(display.getEntityId()); + BukkitFurniture previous = this.furnitureByRealEntityId.get(display.getEntityId()); if (previous != null) return; - LoadedFurniture furniture = addNewFurniture(display, customFurniture); + BukkitFurniture furniture = addNewFurniture(display, customFurniture); furniture.initializeColliders(); // safely do it here } } @@ -412,23 +287,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { return FurnitureExtraData.fromBytes(extraData); } -// private AnchorType getAnchorType(Entity baseEntity, CustomFurniture furniture) { -// String anchorType = baseEntity.getPersistentDataContainer().get(FURNITURE_ANCHOR_KEY, PersistentDataType.STRING); -// if (anchorType != null) { -// try { -// AnchorType unverified = AnchorType.valueOf(anchorType); -// if (furniture.isAllowedPlacement(unverified)) { -// return unverified; -// } -// } catch (IllegalArgumentException ignored) { -// } -// } -// AnchorType anchorTypeEnum = furniture.getAnyPlacement(); -// baseEntity.getPersistentDataContainer().set(FURNITURE_ANCHOR_KEY, PersistentDataType.STRING, anchorTypeEnum.name()); -// return anchorTypeEnum; -// } - - private synchronized LoadedFurniture addNewFurniture(ItemDisplay display, CustomFurniture furniture) { + private synchronized BukkitFurniture addNewFurniture(ItemDisplay display, CustomFurniture furniture) { FurnitureExtraData extraData; try { extraData = getFurnitureExtraData(display); @@ -436,16 +295,21 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { extraData = FurnitureExtraData.builder().build(); plugin.logger().warn("Furniture extra data could not be loaded", e); } - LoadedFurniture loadedFurniture = new LoadedFurniture(display, furniture, extraData); - this.furnitureByRealEntityId.put(loadedFurniture.baseEntityId(), loadedFurniture); - for (int entityId : loadedFurniture.entityIds()) { - this.furnitureByEntityId.put(entityId, loadedFurniture); + BukkitFurniture bukkitFurniture = new BukkitFurniture(display, furniture, extraData); + this.furnitureByRealEntityId.put(bukkitFurniture.baseEntityId(), bukkitFurniture); + for (int entityId : bukkitFurniture.entityIds()) { + this.furnitureByEntityId.put(entityId, bukkitFurniture); } - for (Collider collisionEntity : loadedFurniture.collisionEntities()) { + for (Collider collisionEntity : bukkitFurniture.collisionEntities()) { int collisionEntityId = FastNMS.INSTANCE.method$Entity$getId(collisionEntity.handle()); - this.furnitureByRealEntityId.put(collisionEntityId, loadedFurniture); + this.furnitureByRealEntityId.put(collisionEntityId, bukkitFurniture); } - return loadedFurniture; + return bukkitFurniture; + } + + @Override + protected HitBox defaultHitBox() { + return InteractionHitBox.DEFAULT; } protected void handleDismount(Player player, Entity entity) { @@ -458,7 +322,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { Integer baseFurniture = vehicle.getPersistentDataContainer().get(FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER); if (baseFurniture == null) return; vehicle.remove(); - LoadedFurniture furniture = loadedFurnitureByRealEntityId(baseFurniture); + BukkitFurniture furniture = loadedFurnitureByRealEntityId(baseFurniture); if (furniture == null) { return; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/projectile/BukkitProjectileManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/projectile/BukkitProjectileManager.java index 3d3ad8f87..0b63e8dc9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/projectile/BukkitProjectileManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/projectile/BukkitProjectileManager.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.bukkit.entity.projectile; +import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent; import com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent; import io.papermc.paper.event.player.PlayerStopUsingItemEvent; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; @@ -16,17 +17,20 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask; import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.Bukkit; +import org.bukkit.World; import org.bukkit.entity.*; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityPortalEvent; import org.bukkit.event.entity.ProjectileLaunchEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; import org.bukkit.event.world.EntitiesLoadEvent; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -45,6 +49,14 @@ public class BukkitProjectileManager implements Listener, ProjectileManager { @Override public void delayedInit() { Bukkit.getPluginManager().registerEvents(this, this.plugin.bootstrap()); + for (World world : Bukkit.getWorlds()) { + List entities = world.getEntities(); + for (Entity entity : entities) { + if (entity instanceof Projectile projectile) { + handleProjectileLoad(projectile); + } + } + } } @Override @@ -57,10 +69,21 @@ public class BukkitProjectileManager implements Listener, ProjectileManager { return Optional.ofNullable(this.projectiles.get(entityId)); } - @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onProjectileLaunch(ProjectileLaunchEvent event) { - Projectile projectile = event.getEntity(); - handleProjectileLoad(projectile); + handleProjectileLoad(event.getEntity()); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + public void onEntityPortal(EntityPortalEvent event) { + this.projectiles.remove(event.getEntity().getEntityId()); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) + public void onEntityAdd(EntityAddToWorldEvent event) { + if (event.getEntity() instanceof Projectile projectile) { + handleProjectileLoad(projectile); + } } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @@ -99,10 +122,9 @@ public class BukkitProjectileManager implements Listener, ProjectileManager { @EventHandler public void onPlayerInteract(PlayerItemConsumeEvent event) { - ItemStack item = event.getItem(); - String type = getType(item); + String type = getType(event.getItem()); if (type == null) return; - if (type.equals("bow") || type.equals("spear")) { + if (type.equals("bow") || type.equals("trident")) { event.setCancelled(true); } } @@ -114,17 +136,14 @@ public class BukkitProjectileManager implements Listener, ProjectileManager { if (type == null) return; int ticksHeldFor = event.getTicksHeldFor(); Player player = event.getPlayer(); - if (type.equals("bow")) { - if (ticksHeldFor < 3) return; - // player.sendMessage("可以投出自定义弓: " + item.getType() + " 持续 " + ticksHeldFor + " 刻"); - } else if (type.equals("trident")) { + if (type.equals("trident")) { if (ticksHeldFor < 10) return; - // player.sendMessage("可以投出自定义三叉戟: " + item.getType() + " 持续 " + ticksHeldFor + " 刻"); Object nmsItemStack = FastNMS.INSTANCE.field$CraftItemStack$handle(item); Object nmsServerLevel = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(player.getWorld()); Object nmsEntity = FastNMS.INSTANCE.method$CraftEntity$getHandle(player); - boolean success = TridentRelease.releaseUsing(nmsItemStack, nmsServerLevel, nmsEntity); - // player.sendMessage("释放成功: " + success); + TridentRelease.releaseUsing(nmsItemStack, nmsServerLevel, nmsEntity); + } else if (type.equals("bow")) { + if (ticksHeldFor < 3) return; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java index fc4745a63..3a8fc1a34 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/font/BukkitFontManager.java @@ -151,7 +151,7 @@ public class BukkitFontManager extends AbstractFontManager implements Listener { EmojiComponentProcessResult replaceProcessResult = replaceComponentEmoji(itemName, plugin.adapt(player), renameText); if (replaceProcessResult.changed()) { Item wrapped = this.plugin.itemManager().wrap(result); - wrapped.customName(AdventureHelper.componentToJson(replaceProcessResult.newText())); + wrapped.customNameJson(AdventureHelper.componentToJson(replaceProcessResult.newText())); event.setResult(wrapped.load()); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitCustomItem.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitCustomItem.java index f3ac95c70..241646d22 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitCustomItem.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitCustomItem.java @@ -1,119 +1,33 @@ package net.momirealms.craftengine.bukkit.item; -import com.google.common.collect.ImmutableMap; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.bukkit.util.MaterialUtils; +import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.core.item.*; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.function.Function; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; +import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.Key; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import java.util.*; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; -public class BukkitCustomItem implements CustomItem { - private final Key id; - private final Key materialKey; +public class BukkitCustomItem extends AbstractCustomItem { private final Material material; - private final ItemDataModifier[] modifiers; - private final Map> modifierMap; - private final ItemDataModifier[] clientBoundModifiers; - private final Map> clientBoundModifierMap; - private final NetworkItemDataProcessor[] networkItemDataProcessors; - private final List behaviors; - private final ItemSettings settings; - private final EnumMap>> events; - @SuppressWarnings("unchecked") - public BukkitCustomItem(Key id, - Key materialKey, - Material material, - List> modifiers, - List> clientBoundModifiers, + public BukkitCustomItem(Holder id, Key materialKey, Material material, List behaviors, + List> modifiers, List> clientBoundModifiers, ItemSettings settings, - EnumMap>> events) { - this.id = id; + Map>> events) { + super(id, materialKey, behaviors, modifiers, clientBoundModifiers, settings, events); this.material = material; - this.materialKey = materialKey; - this.events = events; - // unchecked cast - this.modifiers = modifiers.toArray(new ItemDataModifier[0]); - // unchecked cast - this.clientBoundModifiers = clientBoundModifiers.toArray(new ItemDataModifier[0]); - this.behaviors = List.copyOf(behaviors); - this.settings = settings; - ImmutableMap.Builder> modifierMapBuilder = ImmutableMap.builder(); - for (ItemDataModifier modifier : modifiers) { - modifierMapBuilder.put(modifier.name(), modifier); - } - this.modifierMap = modifierMapBuilder.build(); - ImmutableMap.Builder> clientSideModifierMapBuilder = ImmutableMap.builder(); - List> networkItemDataProcessors = new ArrayList<>(); - for (ItemDataModifier modifier : clientBoundModifiers) { - String name = modifier.name(); - clientSideModifierMapBuilder.put(name, modifier); - if (this.modifierMap.containsKey(name)) { - networkItemDataProcessors.add(NetworkItemDataProcessor.both(this.modifierMap.get(name), modifier)); - } else { - networkItemDataProcessors.add(NetworkItemDataProcessor.clientOnly(modifier)); - } - } - this.clientBoundModifierMap = clientSideModifierMapBuilder.build(); - // unchecked cast - this.networkItemDataProcessors = networkItemDataProcessors.toArray(new NetworkItemDataProcessor[0]); - } - - @Override - public void execute(PlayerOptionalContext context, EventTrigger trigger) { - for (Function function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) { - function.run(context); - } - } - - @Override - public Key id() { - return this.id; - } - - @Override - public Key material() { - return this.materialKey; - } - - @Override - public NetworkItemDataProcessor[] networkItemDataProcessors() { - return this.networkItemDataProcessors; - } - - @Override - public ItemDataModifier[] dataModifiers() { - return this.modifiers; - } - - @Override - public Map> dataModifierMap() { - return this.modifierMap; - } - - @Override - public boolean hasClientBoundDataModifier() { - return this.clientBoundModifiers.length != 0; - } - - @Override - public ItemDataModifier[] clientBoundDataModifiers() { - return this.clientBoundModifiers; - } - - @Override - public Map> clientBoundDataModifierMap() { - return this.clientBoundModifierMap; } @Override @@ -127,11 +41,6 @@ public class BukkitCustomItem implements CustomItem { return wrapped.load(); } - @Override - public ItemSettings settings() { - return this.settings; - } - @Override public Item buildItem(ItemBuildContext context) { ItemStack item = new ItemStack(this.material); @@ -142,27 +51,27 @@ public class BukkitCustomItem implements CustomItem { return BukkitCraftEngine.instance().itemManager().wrap(wrapped.load()); } - @Override - public @NotNull List behaviors() { - return this.behaviors; - } - - public static Builder builder() { - return new BuilderImpl(); + public static Builder builder(Material material) { + return new BuilderImpl(material); } public static class BuilderImpl implements Builder { - private Key id; - private Material material; + private Holder id; private Key materialKey; + private final Material material; + private final Map>> events = new EnumMap<>(EventTrigger.class); + private final List behaviors = new ArrayList<>(4); + private final List> modifiers = new ArrayList<>(4); + private final List> clientBoundModifiers = new ArrayList<>(4); private ItemSettings settings; - private EnumMap>> events = new EnumMap<>(EventTrigger.class); - private final List behaviors = new ArrayList<>(); - private final List> modifiers = new ArrayList<>(); - private final List> clientBoundModifiers = new ArrayList<>(); + + public BuilderImpl(Material material) { + this.material = material; + this.materialKey = KeyUtils.namespacedKey2Key(material.getKey()); + } @Override - public Builder id(Key id) { + public Builder id(Holder id) { this.id = id; return this; } @@ -170,7 +79,6 @@ public class BukkitCustomItem implements CustomItem { @Override public Builder material(Key material) { this.materialKey = material; - this.material = MaterialUtils.getMaterial(material.value()); return this; } @@ -217,15 +125,16 @@ public class BukkitCustomItem implements CustomItem { } @Override - public Builder events(EnumMap>> events) { - this.events = events; + public Builder events(Map>> events) { + this.events.putAll(events); return this; } @Override public CustomItem build() { this.modifiers.addAll(this.settings.modifiers()); - return new BukkitCustomItem(this.id, this.materialKey, this.material, this.modifiers, this.clientBoundModifiers, this.behaviors, this.settings, this.events); + return new BukkitCustomItem(this.id, this.materialKey, this.material, List.copyOf(this.behaviors), + List.copyOf(this.modifiers), List.copyOf(this.clientBoundModifiers), this.settings, this.events); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java index 5210f9e4a..ad0671248 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java @@ -1,8 +1,6 @@ package net.momirealms.craftengine.bukkit.item; import com.saicone.rtag.item.ItemTagStream; -import net.momirealms.craftengine.bukkit.item.behavior.AxeItemBehavior; -import net.momirealms.craftengine.bukkit.item.behavior.BoneMealItemBehavior; import net.momirealms.craftengine.bukkit.item.behavior.BucketItemBehavior; import net.momirealms.craftengine.bukkit.item.behavior.WaterBucketItemBehavior; import net.momirealms.craftengine.bukkit.item.factory.BukkitItemFactory; @@ -13,53 +11,36 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.util.ItemUtils; import net.momirealms.craftengine.bukkit.util.KeyUtils; -import net.momirealms.craftengine.bukkit.util.MaterialUtils; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.*; -import net.momirealms.craftengine.core.item.behavior.ItemBehavior; -import net.momirealms.craftengine.core.item.behavior.ItemBehaviors; -import net.momirealms.craftengine.core.item.modifier.CustomModelDataModifier; import net.momirealms.craftengine.core.item.modifier.IdModifier; -import net.momirealms.craftengine.core.item.modifier.ItemModelModifier; -import net.momirealms.craftengine.core.pack.LoadingSequence; -import net.momirealms.craftengine.core.pack.Pack; -import net.momirealms.craftengine.core.pack.ResourceLocation; -import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration; -import net.momirealms.craftengine.core.pack.model.*; -import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; -import net.momirealms.craftengine.core.pack.model.select.ChargeTypeSelectProperty; -import net.momirealms.craftengine.core.pack.model.select.TrimMaterialSelectProperty; import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.context.ContextHolder; -import net.momirealms.craftengine.core.plugin.event.EventFunctions; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.registry.WritableRegistry; -import net.momirealms.craftengine.core.util.*; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.ResourceKey; +import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.event.HandlerList; import org.bukkit.inventory.ItemStack; -import org.incendo.cloud.suggestion.Suggestion; -import org.incendo.cloud.type.Either; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.Field; -import java.nio.file.Path; -import java.util.*; -import java.util.function.Function; +import java.util.ArrayList; +import java.util.Optional; +import java.util.Set; public class BukkitItemManager extends AbstractItemManager { static { - registerVanillaItemExtraBehavior(AxeItemBehavior.INSTANCE, ItemKeys.AXES); registerVanillaItemExtraBehavior(WaterBucketItemBehavior.INSTANCE, ItemKeys.WATER_BUCKETS); registerVanillaItemExtraBehavior(BucketItemBehavior.INSTANCE, ItemKeys.BUCKET); - registerVanillaItemExtraBehavior(BoneMealItemBehavior.INSTANCE, ItemKeys.BONE_MEAL); } private static BukkitItemManager instance; @@ -68,7 +49,7 @@ public class BukkitItemManager extends AbstractItemManager { private final ItemEventListener itemEventListener; private final DebugStickListener debugStickListener; private final ArmorEventListener armorEventListener; - private final ItemParser itemParser; + private final NetworkItemHandler networkItemHandler; public BukkitItemManager(BukkitCraftEngine plugin) { super(plugin); @@ -78,58 +59,8 @@ public class BukkitItemManager extends AbstractItemManager { this.itemEventListener = new ItemEventListener(plugin); this.debugStickListener = new DebugStickListener(plugin); this.armorEventListener = new ArmorEventListener(); - this.itemParser = new ItemParser(); + this.networkItemHandler = VersionHelper.isOrAbove1_20_5() ? new ModernNetworkItemHandler() : new LegacyNetworkItemHandler(); this.registerAllVanillaItems(); - if (plugin.hasMod()) { - Class clazz$CustomStreamCodec = ReflectionUtils.getClazz("net.momirealms.craftengine.mod.item.CustomStreamCodec"); - if (clazz$CustomStreamCodec != null) { - Field s2cProcessor = ReflectionUtils.getDeclaredField(clazz$CustomStreamCodec, Function.class, 0); - Field c2sProcessor = ReflectionUtils.getDeclaredField(clazz$CustomStreamCodec, Function.class, 1); - Function s2c = (raw) -> { - ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(raw); - Item wrapped = this.wrap(itemStack.clone()); - Optional> customItem = wrapped.getCustomItem(); - if (customItem.isEmpty()) { - return raw; - } - CustomItem custom = customItem.get(); - if (!custom.hasClientBoundDataModifier()) { - return raw; - } - for (NetworkItemDataProcessor processor : custom.networkItemDataProcessors()) { - processor.toClient(wrapped, ItemBuildContext.EMPTY); - } - wrapped.load(); - return wrapped.getLiteralObject(); - }; - - Function c2s = (raw) -> { - ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(raw); - Item wrapped = this.wrap(itemStack); - Optional> customItem = wrapped.getCustomItem(); - if (customItem.isEmpty()) { - return raw; - } - CustomItem custom = customItem.get(); - if (!custom.hasClientBoundDataModifier()) { - return raw; - } - for (NetworkItemDataProcessor processor : custom.networkItemDataProcessors()) { - processor.toServer(wrapped, ItemBuildContext.EMPTY); - } - wrapped.load(); - return wrapped.getLiteralObject(); - }; - try { - assert s2cProcessor != null; - s2cProcessor.set(null, s2c); - assert c2sProcessor != null; - c2sProcessor.set(null, c2s); - } catch (ReflectiveOperationException e) { - plugin.logger().warn("Failed to load custom stream codec", e); - } - } - } } @Override @@ -139,13 +70,44 @@ public class BukkitItemManager extends AbstractItemManager { Bukkit.getPluginManager().registerEvents(this.armorEventListener, this.plugin.bootstrap()); } + @Override + public NetworkItemHandler networkItemHandler() { + return this.networkItemHandler; + } + public static BukkitItemManager instance() { return instance; } + public Optional s2c(ItemStack itemStack, ItemBuildContext context) { + try { + Item wrapped = wrap(itemStack); + if (wrapped == null) return Optional.empty(); + return this.networkItemHandler.s2c(wrapped, context).map(Item::load); + } catch (Throwable e) { + if (Config.debug()) { + this.plugin.logger().warn("Failed to handle s2c items.", e); + } + return Optional.empty(); + } + } + + public Optional c2s(ItemStack itemStack, ItemBuildContext context) { + try { + Item wrapped = wrap(itemStack); + if (wrapped == null) return Optional.empty(); + return this.networkItemHandler.c2s(wrapped, context).map(Item::load); + } catch (Throwable e) { + if (Config.debug()) { + this.plugin.logger().warn("Failed to handle c2s items.", e); + } + return Optional.empty(); + } + } + @Override public Optional> getVanillaItem(Key key) { - Material material = Registry.MATERIAL.get(Objects.requireNonNull(NamespacedKey.fromString(key.toString()))); + Material material = Registry.MATERIAL.get(KeyUtils.toNamespacedKey(key)); if (material == null) { return Optional.empty(); } @@ -177,11 +139,6 @@ public class BukkitItemManager extends AbstractItemManager { return this.factory.wrap(ItemTagStream.INSTANCE.fromBytes(bytes)); } - @Override - public ConfigParser parser() { - return this.itemParser; - } - @Override public ItemStack buildCustomItemStack(Key id, Player player) { return Optional.ofNullable(customItems.get(id)).map(it -> it.buildItemStack(new ItemBuildContext(player, ContextHolder.EMPTY), 1)).orElse(null); @@ -213,7 +170,7 @@ public class BukkitItemManager extends AbstractItemManager { @Override public Item createWrappedItem(Key id, @Nullable Player player) { - return Optional.ofNullable(customItems.get(id)).map(it -> it.buildItem(player)).orElseGet(() -> { + return Optional.ofNullable(this.customItems.get(id)).map(it -> it.buildItem(player)).orElseGet(() -> { ItemStack itemStack = createVanillaItemStack(id); return wrap(itemStack); }); @@ -238,417 +195,10 @@ public class BukkitItemManager extends AbstractItemManager { return wrapped.id(); } - public class ItemParser implements ConfigParser { - public static final String[] CONFIG_SECTION_NAME = new String[] {"items", "item"}; - - @Override - public String[] sectionId() { - return CONFIG_SECTION_NAME; - } - - @Override - public int loadingSequence() { - return LoadingSequence.ITEM; - } - - @Override - public void parseSection(Pack pack, Path path, Key id, Map section) { - if (customItems.containsKey(id)) { - throw new LocalizedResourceConfigException("warning.config.item.duplicate"); - } - - // register for recipes - Holder.Reference holder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(id) - .orElseGet(() -> ((WritableRegistry) BuiltInRegistries.OPTIMIZED_ITEM_ID) - .register(new ResourceKey<>(BuiltInRegistries.OPTIMIZED_ITEM_ID.key().location(), id), id)); - - boolean isVanillaItem = id.namespace().equals("minecraft") && Registry.MATERIAL.get(new NamespacedKey(id.namespace(), id.value())) != null; - String materialStringId; - if (isVanillaItem) { - materialStringId = id.value(); - } else { - materialStringId = ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("material"), "warning.config.item.missing_material"); - } - - Material material = MaterialUtils.getMaterial(materialStringId); - if (material == null) { - throw new LocalizedResourceConfigException("warning.config.item.invalid_material", materialStringId); - } - - Key materialId = Key.of(material.getKey().namespace(), material.getKey().value()); - int customModelData = ResourceConfigUtils.getAsInt(section.getOrDefault("custom-model-data", 0), "custom-model-data"); - Key itemModelKey = null; - - CustomItem.Builder itemBuilder = BukkitCustomItem.builder().id(id).material(materialId); - boolean hasItemModelSection = section.containsKey("item-model"); - - // To get at least one model provider - // Sets some basic model info - if (customModelData != 0) { - itemBuilder.dataModifier(new CustomModelDataModifier<>(customModelData)); - } - // Requires the item to have model before apply item-model - else if (!hasItemModelSection && section.containsKey("model") && VersionHelper.isOrAbove1_21_2()) { - // check server version here because components require 1.21.2+ - // customize or use the id - itemModelKey = Key.from(section.getOrDefault("item-model", id.toString()).toString()); - if (ResourceLocation.isValid(itemModelKey.toString())) { - itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey)); - } else { - itemModelKey = null; - } - } - - if (hasItemModelSection) { - itemModelKey = Key.from(section.get("item-model").toString()); - itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey)); - } - - // Get item behaviors - Object behaviorConfig = section.get("behavior"); - if (behaviorConfig instanceof List) { - @SuppressWarnings("unchecked") - List> behavior = (List>) behaviorConfig; - List behaviors = new ArrayList<>(); - for (Map behaviorMap : behavior) { - behaviors.add(ItemBehaviors.fromMap(pack, path, id, behaviorMap)); - } - itemBuilder.behaviors(behaviors); - } else if (behaviorConfig instanceof Map) { - Map behaviorSection = MiscUtils.castToMap(section.get("behavior"), true); - if (behaviorSection != null) { - itemBuilder.behavior(ItemBehaviors.fromMap(pack, path, id, behaviorSection)); - } - } - - // Get item data - Map dataSection = MiscUtils.castToMap(section.get("data"), true); - if (dataSection != null) { - for (Map.Entry dataEntry : dataSection.entrySet()) { - Optional.ofNullable(dataFunctions.get(dataEntry.getKey())).ifPresent(function -> { - try { - itemBuilder.dataModifier(function.apply(dataEntry.getValue())); - } catch (IllegalArgumentException e) { - plugin.logger().warn("Invalid data format", e); - } - }); - } - } - - // Add it here to make sure that ce id is always applied - if (!isVanillaItem) - itemBuilder.dataModifier(new IdModifier<>(id)); - - // Get item data - Map clientSideDataSection = MiscUtils.castToMap(section.get("client-bound-data"), true); - if (clientSideDataSection != null) { - for (Map.Entry dataEntry : clientSideDataSection.entrySet()) { - Optional.ofNullable(dataFunctions.get(dataEntry.getKey())).ifPresent(function -> { - try { - itemBuilder.clientBoundDataModifier(function.apply(dataEntry.getValue())); - } catch (IllegalArgumentException e) { - plugin.logger().warn("Invalid client bound data format", e); - } - }); - } - } - - ItemSettings itemSettings = ItemSettings.fromMap(MiscUtils.castToMap(section.get("settings"), true)); - if (isVanillaItem) { - itemSettings.canPlaceRelatedVanillaBlock(true); - } - itemBuilder.settings(itemSettings); - itemBuilder.events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event"))); - - CustomItem customItem = itemBuilder.build(); - customItems.put(id, customItem); - - // cache command suggestions - cachedSuggestions.add(Suggestion.suggestion(id.toString())); - - // TODO Deprecated 理论支持任意物品类型 - if (material == Material.TOTEM_OF_UNDYING) - cachedTotemSuggestions.add(Suggestion.suggestion(id.toString())); - - // post process - // register tags - Set tags = customItem.settings().tags(); - for (Key tag : tags) { - customItemTags.computeIfAbsent(tag, k -> new ArrayList<>()).add(holder); - } - - // create trims - EquipmentGeneration equipment = customItem.settings().equipment(); - if (equipment != null) { - EquipmentData modern = equipment.modernData(); - // 1.21.2+ - if (modern != null) { - equipmentsToGenerate.add(equipment); - } - // TODO 1.20 - } - - // add it to category - if (section.containsKey("category")) { - plugin.itemBrowserManager().addExternalCategoryMember(id, MiscUtils.getAsStringList(section.get("category")).stream().map(Key::of).toList()); - } - - // model part, can be null - // but if it exists, either custom model data or item model should be configured - Map modelSection = MiscUtils.castToMap(section.get("model"), true); - if (modelSection == null) { - return; - } - - ItemModel model = ItemModels.fromMap(modelSection); - boolean hasModel = false; - if (customModelData != 0) { - hasModel= true; - // use custom model data - // check conflict - Map conflict = cmdConflictChecker.computeIfAbsent(materialId, k -> new HashMap<>()); - if (conflict.containsKey(customModelData)) { - throw new LocalizedResourceConfigException("warning.config.item.custom_model_data_conflict", String.valueOf(customModelData), conflict.get(customModelData).toString()); - } - - if (customModelData > 16_777_216) { - throw new LocalizedResourceConfigException("warning.config.item.bad_custom_model_data", String.valueOf(customModelData)); - } - - conflict.put(customModelData, id); - - // Parse models - for (ModelGeneration generation : model.modelsToGenerate()) { - prepareModelGeneration(generation); - } - - if (Config.packMaxVersion() > 21.39f) { - TreeMap map = modernOverrides.computeIfAbsent(materialId, k -> new TreeMap<>()); - map.put(customModelData, model); - } - - if (Config.packMinVersion() < 21.39f) { - // TODO 手动指定旧版格式 - List legacyOverridesModels = new ArrayList<>(); - processModelRecursively(model, new LinkedHashMap<>(), legacyOverridesModels, materialId, customModelData); - TreeSet lom = legacyOverrides.computeIfAbsent(materialId, k -> new TreeSet<>()); - lom.addAll(legacyOverridesModels); - } - } - if (itemModelKey != null) { - hasModel = true; - for (ModelGeneration generation : model.modelsToGenerate()) { - prepareModelGeneration(generation); - } - - if (Config.packMaxVersion() > 21.39f) { - modernItemModels1_21_4.put(itemModelKey, model); - } - - if (Config.packMaxVersion() > 21.19f && Config.packMinVersion() < 21.39f) { - List legacyOverridesModels = new ArrayList<>(); - processModelRecursively(model, new LinkedHashMap<>(), legacyOverridesModels, materialId, 0); - if (!legacyOverridesModels.isEmpty()) { - legacyOverridesModels.sort(LegacyOverridesModel::compareTo); - modernItemModels1_21_2.put(itemModelKey, legacyOverridesModels); - } else { - plugin.debug(() -> "Can't convert " + id + "'s model to legacy format."); - } - } - } - if (!hasModel) { - throw new LocalizedResourceConfigException("warning.config.item.missing_model_id"); - } - } - } - - private void processModelRecursively( - ItemModel currentModel, - Map accumulatedPredicates, - List resultList, - Key materialId, - int customModelData - ) { - if (currentModel instanceof ConditionItemModel conditionModel) { - handleConditionModel(conditionModel, accumulatedPredicates, resultList, materialId, customModelData); - } else if (currentModel instanceof RangeDispatchItemModel rangeModel) { - handleRangeModel(rangeModel, accumulatedPredicates, resultList, materialId, customModelData); - } else if (currentModel instanceof SelectItemModel selectModel) { - handleSelectModel(selectModel, accumulatedPredicates, resultList, materialId, customModelData); - } else if (currentModel instanceof BaseItemModel baseModel) { - resultList.add(new LegacyOverridesModel( - new LinkedHashMap<>(accumulatedPredicates), - baseModel.path(), - customModelData - )); - } else if (currentModel instanceof SpecialItemModel specialModel) { - resultList.add(new LegacyOverridesModel( - new LinkedHashMap<>(accumulatedPredicates), - specialModel.base(), - customModelData - )); - } - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - private void handleConditionModel( - ConditionItemModel model, - Map parentPredicates, - List resultList, - Key materialId, - int customModelData - ) { - if (model.property() instanceof LegacyModelPredicate predicate) { - String predicateId = predicate.legacyPredicateId(materialId); - Map truePredicates = mergePredicates( - parentPredicates, - predicateId, - predicate.toLegacyValue(true) - ); - processModelRecursively( - model.onTrue(), - truePredicates, - resultList, - materialId, - customModelData - ); - Map falsePredicates = mergePredicates( - parentPredicates, - predicateId, - predicate.toLegacyValue(false) - ); - processModelRecursively( - model.onFalse(), - falsePredicates, - resultList, - materialId, - customModelData - ); - } - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - private void handleRangeModel( - RangeDispatchItemModel model, - Map parentPredicates, - List resultList, - Key materialId, - int customModelData - ) { - if (model.property() instanceof LegacyModelPredicate predicate) { - String predicateId = predicate.legacyPredicateId(materialId); - for (Map.Entry entry : model.entries().entrySet()) { - Map merged = mergePredicates( - parentPredicates, - predicateId, - predicate.toLegacyValue(entry.getKey()) - ); - processModelRecursively( - entry.getValue(), - merged, - resultList, - materialId, - customModelData - ); - } - if (model.fallBack() != null) { - Map merged = mergePredicates( - parentPredicates, - predicateId, - predicate.toLegacyValue(0f) - ); - processModelRecursively( - model.fallBack(), - merged, - resultList, - materialId, - customModelData - ); - } - } - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - private void handleSelectModel( - SelectItemModel model, - Map parentPredicates, - List resultList, - Key materialId, - int customModelData - ) { - if (model.property() instanceof LegacyModelPredicate predicate) { - String predicateId = predicate.legacyPredicateId(materialId); - for (Map.Entry>, ItemModel> entry : model.whenMap().entrySet()) { - List cases = entry.getKey().fallbackOrMapPrimary(List::of); - for (String caseValue : cases) { - Number legacyValue = predicate.toLegacyValue(caseValue); - if (predicate instanceof TrimMaterialSelectProperty property && property.isArmor(materialId)) { - if (legacyValue.floatValue() > 1f) { - continue; - } - } - Map merged = mergePredicates( - parentPredicates, - predicateId, - legacyValue - ); - // Additional check for crossbow - if (predicate instanceof ChargeTypeSelectProperty && materialId.equals(ItemKeys.CROSSBOW)) { - merged = mergePredicates( - merged, - "charged", - 1 - ); - } - processModelRecursively( - entry.getValue(), - merged, - resultList, - materialId, - customModelData - ); - } - } - // Additional check for crossbow - if (model.fallBack() != null) { - if (predicate instanceof ChargeTypeSelectProperty && materialId.equals(ItemKeys.CROSSBOW)) { - processModelRecursively( - model.fallBack(), - mergePredicates( - parentPredicates, - "charged", - 0 - ), - resultList, - materialId, - customModelData - ); - } else if (predicate instanceof TrimMaterialSelectProperty property && property.isArmor(materialId)) { - processModelRecursively( - model.fallBack(), - mergePredicates( - parentPredicates, - "trim_type", - 0f - ), - resultList, - materialId, - customModelData - ); - } - } - } - } - - private Map mergePredicates( - Map existing, - String newKey, - Number newValue - ) { - Map merged = new LinkedHashMap<>(existing); - if (newKey == null) return merged; - merged.put(newKey, newValue); - return merged; + @Override + protected CustomItem.Builder createPlatformItemBuilder(Holder id, Key materialId) { + Material material = ResourceConfigUtils.requireNonNullOrThrow(Registry.MATERIAL.get(KeyUtils.toNamespacedKey(materialId)), () -> new LocalizedResourceConfigException("warning.config.item.invalid_material", materialId.toString())); + return BukkitCustomItem.builder(material).material(materialId).id(id); } @SuppressWarnings("unchecked") diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ComponentItemWrapper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ComponentItemWrapper.java index b7c1eb500..bda3edb58 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ComponentItemWrapper.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ComponentItemWrapper.java @@ -1,25 +1,31 @@ package net.momirealms.craftengine.bukkit.item; import com.google.gson.JsonElement; -import com.saicone.rtag.data.ComponentType; -import com.saicone.rtag.tag.TagBase; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.item.ItemWrapper; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.sparrow.nbt.Tag; import org.bukkit.inventory.ItemStack; -@SuppressWarnings("UnstableApiUsage") +import java.util.Optional; + public class ComponentItemWrapper implements ItemWrapper { private final ItemStack item; + private final Object handle; public ComponentItemWrapper(final ItemStack item) { this.item = FastNMS.INSTANCE.ensureCraftItemStack(item); + this.handle = FastNMS.INSTANCE.field$CraftItemStack$handle(this.item); } public ComponentItemWrapper(final ItemStack item, int count) { this.item = FastNMS.INSTANCE.ensureCraftItemStack(item); this.item.setAmount(count); + this.handle = FastNMS.INSTANCE.field$CraftItemStack$handle(this.item); } public void removeComponent(Object type) { @@ -33,31 +39,87 @@ public class ComponentItemWrapper implements ItemWrapper { public void setComponent(Object type, final Object value) { if (value instanceof JsonElement jsonElement) { setJsonComponent(type, jsonElement); - } else if (TagBase.isTag(value)) { + } else if (Reflections.clazz$Tag.isInstance(value)) { setNBTComponent(type, value); + } else if (value instanceof Tag tag) { + setSparrowNBTComponent(type, tag); } else { setJavaComponent(type, value); } } - public Object getComponent(Object type) { + public Object getComponentExact(Object type) { return FastNMS.INSTANCE.getComponent(getLiteralObject(), ensureDataComponentType(type)); } + public Optional getJavaComponent(Object type) { + return getComponentInternal(type, Reflections.instance$JAVA_OPS); + } + + public Optional getJsonComponent(Object type) { + return getComponentInternal(type, Reflections.instance$JSON_OPS); + } + + public Optional getNBTComponent(Object type) { + return getComponentInternal(type, Reflections.instance$NBT_OPS); + } + + public Optional getSparrowNBTComponent(Object type) { + return getComponentInternal(type, Reflections.instance$SPARROW_NBT_OPS); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private Optional getComponentInternal(Object type, DynamicOps ops) { + Object componentType = ensureDataComponentType(type); + Codec codec = FastNMS.INSTANCE.method$DataComponentType$codec(componentType); + try { + Object componentData = FastNMS.INSTANCE.getComponent(getLiteralObject(), componentType); + if (componentData == null) return Optional.empty(); + DataResult result = codec.encodeStart(ops, componentData); + return (Optional) result.result(); + } catch (Throwable t) { + throw new RuntimeException("Cannot read component " + type.toString(), t); + } + } + public boolean hasComponent(Object type) { return FastNMS.INSTANCE.hasComponent(getLiteralObject(), ensureDataComponentType(type)); } + public void setComponentExact(Object type, final Object value) { + FastNMS.INSTANCE.setComponent(this.getLiteralObject(), ensureDataComponentType(type), value); + } + public void setJavaComponent(Object type, Object value) { - ComponentType.parseJava(type, value).ifPresent(it -> FastNMS.INSTANCE.setComponent(this.getLiteralObject(), ensureDataComponentType(type), it)); + setComponentInternal(type, Reflections.instance$JAVA_OPS, value); } public void setJsonComponent(Object type, JsonElement value) { - ComponentType.parseJson(type, value).ifPresent(it -> FastNMS.INSTANCE.setComponent(this.getLiteralObject(), ensureDataComponentType(type), it)); + setComponentInternal(type, Reflections.instance$JSON_OPS, value); } public void setNBTComponent(Object type, Object value) { - ComponentType.parseNbt(type, value).ifPresent(it -> FastNMS.INSTANCE.setComponent(this.getLiteralObject(), ensureDataComponentType(type), it)); + setComponentInternal(type, Reflections.instance$NBT_OPS, value); + } + + public void setSparrowNBTComponent(Object type, Tag value) { + setComponentInternal(type, Reflections.instance$SPARROW_NBT_OPS, value); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private void setComponentInternal(Object type, DynamicOps ops, Object value) { + if (value == null) return; + Object componentType = ensureDataComponentType(type); + Codec codec = FastNMS.INSTANCE.method$DataComponentType$codec(componentType); + try { + DataResult result = codec.parse(ops, value); + if (result.isError()) { + throw new IllegalArgumentException(result.toString()); + } + result.result().ifPresent(it -> FastNMS.INSTANCE.setComponent(this.getLiteralObject(), componentType, it)); + } catch (Throwable t) { + throw new RuntimeException("Cannot parse component " + type.toString(), t); + } } private Object ensureDataComponentType(Object type) { @@ -88,7 +150,7 @@ public class ComponentItemWrapper implements ItemWrapper { @Override public Object getLiteralObject() { - return FastNMS.INSTANCE.field$CraftItemStack$handle(this.item); + return this.handle; } @Override @@ -98,6 +160,6 @@ public class ComponentItemWrapper implements ItemWrapper { @Override public void count(int amount) { - this.item.setAmount(amount); + this.item.setAmount(Math.max(amount, 0)); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ComponentTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ComponentTypes.java index d755ab11b..ae037f19f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ComponentTypes.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ComponentTypes.java @@ -26,6 +26,7 @@ public class ComponentTypes { public static final Object CUSTOM_DATA = getComponentType(ComponentKeys.CUSTOM_DATA); public static final Object PROFILE = getComponentType(ComponentKeys.PROFILE); public static final Object DYED_COLOR = getComponentType(ComponentKeys.DYED_COLOR); + public static final Object DEATH_PROTECTION = getComponentType(ComponentKeys.DEATH_PROTECTION); private ComponentTypes() {} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyItemWrapper.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyItemWrapper.java index 5e58510c9..452cf8cb1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyItemWrapper.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyItemWrapper.java @@ -1,9 +1,20 @@ package net.momirealms.craftengine.bukkit.item; import com.saicone.rtag.RtagItem; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.item.ItemWrapper; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.sparrow.nbt.NBT; +import net.momirealms.sparrow.nbt.Tag; import org.bukkit.inventory.ItemStack; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Arrays; + public class LegacyItemWrapper implements ItemWrapper { private final RtagItem rtagItem; private int count; @@ -20,18 +31,32 @@ public class LegacyItemWrapper implements ItemWrapper { return itemStack; } - public boolean set(Object value, Object... path) { - return this.rtagItem.set(value, path); + public boolean setTag(Object value, Object... path) { + if (value instanceof Tag tag) { + return this.rtagItem.set(Reflections.instance$SPARROW_NBT_OPS.convertTo(Reflections.instance$NBT_OPS, tag), path); + } else { + return this.rtagItem.set(value, path); + } } public boolean add(Object value, Object... path) { - return this.rtagItem.add(value, path); + if (value instanceof Tag tag) { + return this.rtagItem.add(Reflections.instance$SPARROW_NBT_OPS.convertTo(Reflections.instance$NBT_OPS, tag), path); + } else { + return this.rtagItem.add(value, path); + } } - public V get(Object... path) { + public V getJavaTag(Object... path) { return this.rtagItem.get(path); } + public Tag getNBTTag(Object... path) { + Object tag = getExactTag(path); + if (tag == null) return null; + return Reflections.instance$NBT_OPS.convertTo(Reflections.instance$SPARROW_NBT_OPS, tag); + } + public int count() { return this.count; } @@ -41,8 +66,8 @@ public class LegacyItemWrapper implements ItemWrapper { this.count = amount; } - public V getExact(Object... path) { - return this.rtagItem.get(path); + public Object getExactTag(Object... path) { + return this.rtagItem.getExact(path); } public boolean remove(Object... path) { @@ -60,7 +85,7 @@ public class LegacyItemWrapper implements ItemWrapper { @Override public ItemStack load() { ItemStack itemStack = this.rtagItem.load(); - itemStack.setAmount(this.count); + itemStack.setAmount(Math.max(this.count, 0)); return itemStack; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyNetworkItemHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyNetworkItemHandler.java new file mode 100644 index 000000000..01e7a45cb --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/LegacyNetworkItemHandler.java @@ -0,0 +1,145 @@ +package net.momirealms.craftengine.bukkit.item; + +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.core.item.CustomItem; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.NetworkItemHandler; +import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.ListTag; +import net.momirealms.sparrow.nbt.StringTag; +import net.momirealms.sparrow.nbt.Tag; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; + +public class LegacyNetworkItemHandler implements NetworkItemHandler { + + @Override + public Optional> c2s(Item wrapped, ItemBuildContext context) { + if (!wrapped.hasTag(NETWORK_ITEM_TAG)) return Optional.empty(); + CompoundTag networkData = (CompoundTag) wrapped.getNBTTag(NETWORK_ITEM_TAG); + if (networkData == null) return Optional.empty(); + wrapped.removeTag(NETWORK_ITEM_TAG); + for (Map.Entry entry : networkData.entrySet()) { + if (entry.getValue() instanceof CompoundTag tag) { + NetworkItemHandler.apply(entry.getKey(), tag, wrapped); + } + } + return Optional.of(wrapped); + } + + @Override + public Optional> s2c(Item wrapped, ItemBuildContext context) { + Optional> optionalCustomItem = wrapped.getCustomItem(); + if (optionalCustomItem.isEmpty()) { + if (!Config.interceptItem()) return Optional.empty(); + return new OtherItem(wrapped).process(); + } else { + CustomItem customItem = optionalCustomItem.get(); + if (!customItem.hasClientBoundDataModifier()) { + if (!Config.interceptItem()) return Optional.empty(); + return new OtherItem(wrapped).process(); + } else { + CompoundTag tag = new CompoundTag(); + for (ItemDataModifier modifier : customItem.clientBoundDataModifiers()) { + modifier.prepareNetworkItem(wrapped, context, tag); + modifier.apply(wrapped, context); + } + if (Config.interceptItem()) { + if (!tag.containsKey("display.Name")) { + processCustomName(wrapped, tag::put); + } + if (!tag.containsKey("display.Lore")) { + processLore(wrapped, tag::put); + } + } + if (tag.isEmpty()) return Optional.empty(); + wrapped.setTag(tag, NETWORK_ITEM_TAG); + return Optional.of(wrapped); + } + } + } + + public static boolean processCustomName(Item item, BiConsumer callback) { + Optional optionalCustomName = item.customNameJson(); + if (optionalCustomName.isPresent()) { + String line = optionalCustomName.get(); + Map tokens = CraftEngine.instance().fontManager().matchTags(line); + if (!tokens.isEmpty()) { + item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens))); + callback.accept("display.Name", NetworkItemHandler.pack(Operation.ADD, new StringTag(line))); + return true; + } + } + return false; + } + + private static boolean processLore(Item item, BiConsumer callback) { + Optional> optionalLore = item.loreJson(); + if (optionalLore.isPresent()) { + boolean changed = false; + List lore = optionalLore.get(); + List newLore = new ArrayList<>(lore.size()); + for (String line : lore) { + Map tokens = CraftEngine.instance().fontManager().matchTags(line); + if (tokens.isEmpty()) { + newLore.add(line); + } else { + newLore.add(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens))); + changed = true; + } + } + if (changed) { + item.loreJson(newLore); + ListTag listTag = new ListTag(); + for (String line : lore) { + listTag.add(new StringTag(line)); + } + callback.accept("display.Lore", NetworkItemHandler.pack(Operation.ADD, listTag)); + return true; + } + } + return false; + } + + static class OtherItem { + private final Item item; + private boolean globalChanged = false; + private CompoundTag networkTag; + + public OtherItem(Item item) { + this.item = item; + } + + public Optional> process() { + if (processLore(this.item, (s, c) -> networkTag().put(s, c))) { + this.globalChanged = true; + } + if (processCustomName(this.item, (s, c) -> networkTag().put(s, c))) { + this.globalChanged = true; + } + if (this.globalChanged) { + this.item.setTag(this.networkTag, NETWORK_ITEM_TAG); + return Optional.of(this.item); + } else { + return Optional.empty(); + } + } + + public CompoundTag networkTag() { + if (this.networkTag == null) { + this.networkTag = new CompoundTag(); + } + return this.networkTag; + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ModernNetworkItemHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ModernNetworkItemHandler.java new file mode 100644 index 000000000..75b09c21c --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ModernNetworkItemHandler.java @@ -0,0 +1,232 @@ +package net.momirealms.craftengine.bukkit.item; + +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.core.item.*; +import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.ListTag; +import net.momirealms.sparrow.nbt.StringTag; +import net.momirealms.sparrow.nbt.Tag; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +@SuppressWarnings("DuplicatedCode") +public final class ModernNetworkItemHandler implements NetworkItemHandler { + + @Override + public Optional> c2s(Item wrapped, ItemBuildContext context) { + Tag customData = wrapped.getNBTComponent(ComponentTypes.CUSTOM_DATA); + if (!(customData instanceof CompoundTag compoundTag)) return Optional.empty(); + CompoundTag networkData = compoundTag.getCompound(NETWORK_ITEM_TAG); + if (networkData == null) return Optional.empty(); + compoundTag.remove(NETWORK_ITEM_TAG); + for (Map.Entry entry : networkData.entrySet()) { + if (entry.getValue() instanceof CompoundTag tag) { + NetworkItemHandler.apply(entry.getKey(), tag, wrapped); + } + } + // todo 可能会是 !custom_data吗,不可能,绝对不可能! + if (compoundTag.isEmpty()) wrapped.resetComponent(ComponentTypes.CUSTOM_DATA); + else wrapped.setNBTComponent(ComponentTypes.CUSTOM_DATA, compoundTag); + return Optional.of(wrapped); + } + + @Override + public Optional> s2c(Item wrapped, ItemBuildContext context) { + Optional> optionalCustomItem = wrapped.getCustomItem(); + if (optionalCustomItem.isEmpty()) { + if (!Config.interceptItem()) return Optional.empty(); + return new OtherItem(wrapped).process(); + } else { + CustomItem customItem = optionalCustomItem.get(); + if (!customItem.hasClientBoundDataModifier()) { + if (!Config.interceptItem()) return Optional.empty(); + return new OtherItem(wrapped).process(); + } else { + CompoundTag customData = Optional.ofNullable(wrapped.getNBTComponent(ComponentTypes.CUSTOM_DATA)).map(CompoundTag.class::cast).orElse(new CompoundTag()); + CompoundTag tag = new CompoundTag(); + for (ItemDataModifier modifier : customItem.clientBoundDataModifiers()) { + modifier.prepareNetworkItem(wrapped, context, tag); + modifier.apply(wrapped, context); + } + if (Config.interceptItem()) { + if (!tag.containsKey(ComponentIds.ITEM_NAME)) { + if (VersionHelper.isOrAbove1_21_5()) processModernItemName(wrapped, () -> tag); + else processLegacyItemName(wrapped, () -> tag); + } + if (!tag.containsKey(ComponentIds.CUSTOM_NAME)) { + if (VersionHelper.isOrAbove1_21_5()) processModernCustomName(wrapped, () -> tag); + else processLegacyCustomName(wrapped, () -> tag); + } + if (!tag.containsKey(ComponentIds.LORE)) { + if (VersionHelper.isOrAbove1_21_5()) processModernLore(wrapped, () -> tag); + else processLegacyLore(wrapped, () -> tag); + } + } + if (tag.isEmpty()) return Optional.empty(); + customData.put(NETWORK_ITEM_TAG, tag); + wrapped.setNBTComponent(ComponentTypes.CUSTOM_DATA, customData); + return Optional.of(wrapped); + } + } + } + + public static boolean processLegacyLore(Item item, Supplier tag) { + Optional> optionalLore = item.loreJson(); + if (optionalLore.isPresent()) { + boolean changed = false; + List lore = optionalLore.get(); + List newLore = new ArrayList<>(lore.size()); + for (String line : lore) { + Map tokens = CraftEngine.instance().fontManager().matchTags(line); + if (tokens.isEmpty()) { + newLore.add(line); + } else { + newLore.add(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens))); + changed = true; + } + } + if (changed) { + item.loreJson(newLore); + ListTag listTag = new ListTag(); + for (String line : lore) { + listTag.add(new StringTag(line)); + } + tag.get().put(ComponentIds.LORE, NetworkItemHandler.pack(Operation.ADD, listTag)); + return true; + } + } + return false; + } + + public static boolean processLegacyCustomName(Item item, Supplier tag) { + Optional optionalCustomName = item.customNameJson(); + if (optionalCustomName.isPresent()) { + String line = optionalCustomName.get(); + Map tokens = CraftEngine.instance().fontManager().matchTags(line); + if (!tokens.isEmpty()) { + item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens))); + tag.get().put(ComponentIds.CUSTOM_NAME, NetworkItemHandler.pack(Operation.ADD, new StringTag(line))); + return true; + } + } + return false; + } + + public static boolean processLegacyItemName(Item item, Supplier tag) { + Optional optionalItemName = item.itemNameJson(); + if (optionalItemName.isPresent()) { + String line = optionalItemName.get(); + Map tokens = CraftEngine.instance().fontManager().matchTags(line); + if (!tokens.isEmpty()) { + item.itemNameJson(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(line), tokens))); + tag.get().put(ComponentIds.ITEM_NAME, NetworkItemHandler.pack(Operation.ADD, new StringTag(line))); + return true; + } + } + return false; + } + + public static boolean processModernItemName(Item item, Supplier tag) { + Tag nameTag = item.getNBTComponent(ComponentTypes.ITEM_NAME); + if (nameTag == null) return false; + String tagStr = nameTag.getAsString(); + Map tokens = CraftEngine.instance().fontManager().matchTags(tagStr); + if (!tokens.isEmpty()) { + item.setNBTComponent(ComponentKeys.ITEM_NAME, AdventureHelper.componentToNbt(AdventureHelper.replaceText(AdventureHelper.nbtToComponent(nameTag), tokens))); + tag.get().put(ComponentIds.ITEM_NAME, NetworkItemHandler.pack(Operation.ADD, nameTag)); + return true; + } + return false; + } + + public static boolean processModernCustomName(Item item, Supplier tag) { + Tag nameTag = item.getNBTComponent(ComponentTypes.CUSTOM_NAME); + if (nameTag == null) return false; + String tagStr = nameTag.getAsString(); + Map tokens = CraftEngine.instance().fontManager().matchTags(tagStr); + if (!tokens.isEmpty()) { + item.setNBTComponent(ComponentKeys.CUSTOM_NAME, AdventureHelper.componentToNbt(AdventureHelper.replaceText(AdventureHelper.nbtToComponent(nameTag), tokens))); + tag.get().put(ComponentIds.CUSTOM_NAME, NetworkItemHandler.pack(Operation.ADD, nameTag)); + return true; + } + return false; + } + + public static boolean processModernLore(Item item, Supplier tagSupplier) { + Tag loreTag = item.getNBTComponent(ComponentTypes.LORE); + boolean changed = false; + if (!(loreTag instanceof ListTag listTag)) { + return false; + } + ListTag newLore = new ListTag(); + for (Tag tag : listTag) { + String tagStr = tag.getAsString(); + Map tokens = CraftEngine.instance().fontManager().matchTags(tagStr); + if (tokens.isEmpty()) { + newLore.add(tag); + } else { + newLore.add(AdventureHelper.componentToNbt(AdventureHelper.replaceText(AdventureHelper.nbtToComponent(tag), tokens))); + changed = true; + } + } + if (changed) { + item.setNBTComponent(ComponentKeys.LORE, newLore); + tagSupplier.get().put(ComponentIds.LORE, NetworkItemHandler.pack(Operation.ADD, listTag)); + return true; + } + return false; + } + + static class OtherItem { + private final Item item; + private boolean globalChanged = false; + private CompoundTag tag; + + public OtherItem(Item item) { + this.item = item; + } + + public Optional> process() { + if (VersionHelper.isOrAbove1_21_5()) { + if (processModernLore(this.item, this::getOrCreateTag)) + this.globalChanged = true; + if (processModernCustomName(this.item, this::getOrCreateTag)) + this.globalChanged = true; + if (processModernItemName(this.item, this::getOrCreateTag)) + this.globalChanged = true; + } else { + if (processLegacyLore(this.item, this::getOrCreateTag)) + this.globalChanged = true; + if (processLegacyCustomName(this.item, this::getOrCreateTag)) + this.globalChanged = true; + if (processLegacyItemName(this.item, this::getOrCreateTag)) + this.globalChanged = true; + } + if (this.globalChanged) { + CompoundTag customData = Optional.ofNullable(this.item.getNBTComponent(ComponentTypes.CUSTOM_DATA)).map(CompoundTag.class::cast).orElse(new CompoundTag()); + customData.put(NETWORK_ITEM_TAG, getOrCreateTag()); + this.item.setNBTComponent(ComponentKeys.CUSTOM_DATA, customData); + return Optional.of(this.item); + } else { + return Optional.empty(); + } + } + + private CompoundTag getOrCreateTag() { + if (this.tag == null) { + this.tag = new CompoundTag(); + } + return this.tag; + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java deleted file mode 100644 index d1e6b038e..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/AxeItemBehavior.java +++ /dev/null @@ -1,122 +0,0 @@ -package net.momirealms.craftengine.bukkit.item.behavior; - -import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; -import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; -import net.momirealms.craftengine.bukkit.block.behavior.StrippableBlockBehavior; -import net.momirealms.craftengine.bukkit.util.*; -import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; -import net.momirealms.craftengine.core.block.CustomBlock; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.UpdateOption; -import net.momirealms.craftengine.core.entity.player.InteractionHand; -import net.momirealms.craftengine.core.entity.player.InteractionResult; -import net.momirealms.craftengine.core.entity.player.Player; -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.item.ItemKeys; -import net.momirealms.craftengine.core.item.behavior.ItemBehavior; -import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; -import net.momirealms.craftengine.core.item.context.UseOnContext; -import net.momirealms.craftengine.core.pack.Pack; -import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.VersionHelper; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.Vec3d; -import net.momirealms.sparrow.nbt.CompoundTag; -import org.bukkit.GameEvent; -import org.bukkit.Material; -import org.bukkit.Statistic; -import org.bukkit.block.Block; -import org.bukkit.event.entity.EntityChangeBlockEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Optional; - -public class AxeItemBehavior extends ItemBehavior { - public static final Factory FACTORY = new Factory(); - public static final AxeItemBehavior INSTANCE = new AxeItemBehavior(); - private static final Key AXE_STRIP_SOUND = Key.of("minecraft:item.axe.strip"); - - @SuppressWarnings("unchecked") - @Override - public InteractionResult useOnBlock(UseOnContext context) { - BukkitBlockInWorld clicked = (BukkitBlockInWorld) context.getLevel().getBlockAt(context.getClickedPos()); - Block block = clicked.block(); - ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockDataToId(block.getBlockData())); - if (state == null || state.isEmpty()) return InteractionResult.PASS; - - if (!(state.behavior() instanceof StrippableBlockBehavior blockBehavior)) { - return InteractionResult.PASS; - } - - Player player = context.getPlayer(); - // no adventure mode - if (player.isAdventureMode()) { - return InteractionResult.PASS; - } - - Item offHandItem = (Item) player.getItemInHand(InteractionHand.OFF_HAND); - // is using a shield - if (context.getHand() == InteractionHand.MAIN_HAND && offHandItem != null && offHandItem.vanillaId().equals(ItemKeys.SHIELD) && !player.isSecondaryUseActive()) { - return InteractionResult.PASS; - } - - Optional optionalNewCustomBlock = BukkitBlockManager.instance().blockById(blockBehavior.stripped()); - if (optionalNewCustomBlock.isEmpty()) { - CraftEngine.instance().logger().warn("stripped block " + blockBehavior.stripped() + " does not exist"); - return InteractionResult.FAIL; - } - CustomBlock newCustomBlock = optionalNewCustomBlock.get(); - CompoundTag compoundTag = state.propertiesNbt(); - ImmutableBlockState newState = newCustomBlock.getBlockState(compoundTag); - - org.bukkit.entity.Player bukkitPlayer = ((org.bukkit.entity.Player) player.platformPlayer()); - // Call bukkit event - EntityChangeBlockEvent event = new EntityChangeBlockEvent(bukkitPlayer, block, BlockStateUtils.fromBlockData(newState.customBlockState().handle())); - if (EventUtils.fireAndCheckCancel(event)) { - return InteractionResult.PASS; - } - - BlockPos pos = context.getClickedPos(); - context.getLevel().playBlockSound(Vec3d.atCenterOf(pos), AXE_STRIP_SOUND, 1, 1); - CraftEngineBlocks.place(block.getLocation(), newState, UpdateOption.UPDATE_ALL_IMMEDIATE, false); - block.getWorld().sendGameEvent(bukkitPlayer, GameEvent.BLOCK_CHANGE, new Vector(pos.x(), pos.y(), pos.z())); - Item item = (Item) context.getItem(); - Material material = MaterialUtils.getMaterial(item.vanillaId()); - bukkitPlayer.setStatistic(Statistic.USE_ITEM, material, bukkitPlayer.getStatistic(Statistic.USE_ITEM, material) + 1); - - // resend swing if it's not interactable on client side - if (!InteractUtils.isInteractable( - bukkitPlayer, BlockStateUtils.fromBlockData(state.vanillaBlockState().handle()), - context.getHitResult(), item - ) || player.isSecondaryUseActive()) { - player.swingHand(context.getHand()); - } - // shrink item amount - if (VersionHelper.isOrAbove1_20_5()) { - Object itemStack = item.getLiteralObject(); - Object serverPlayer = player.serverPlayer(); - Object equipmentSlot = context.getHand() == InteractionHand.MAIN_HAND ? Reflections.instance$EquipmentSlot$MAINHAND : Reflections.instance$EquipmentSlot$OFFHAND; - try { - Reflections.method$ItemStack$hurtAndBreak.invoke(itemStack, 1, serverPlayer, equipmentSlot); - } catch (ReflectiveOperationException e) { - CraftEngine.instance().logger().warn("Failed to hurt itemStack", e); - } - } else { - ItemStack itemStack = item.getItem(); - itemStack.damage(1, bukkitPlayer); - } - return InteractionResult.SUCCESS; - } - - public static class Factory implements ItemBehaviorFactory { - - @Override - public ItemBehavior create(Pack pack, Path path, Key id, Map arguments) { - return INSTANCE; - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java index 00fbf33cc..4f4f19cf8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BlockItemBehavior.java @@ -25,8 +25,8 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Cancellable; import net.momirealms.craftengine.core.util.Direction; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java deleted file mode 100644 index 2996694ed..000000000 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java +++ /dev/null @@ -1,83 +0,0 @@ -package net.momirealms.craftengine.bukkit.item.behavior; - -import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; -import net.momirealms.craftengine.bukkit.block.behavior.CropBlockBehavior; -import net.momirealms.craftengine.bukkit.block.behavior.GrassBlockBehavior; -import net.momirealms.craftengine.bukkit.block.behavior.SaplingBlockBehavior; -import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; -import net.momirealms.craftengine.bukkit.util.LocationUtils; -import net.momirealms.craftengine.bukkit.util.Reflections; -import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; -import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.entity.player.InteractionResult; -import net.momirealms.craftengine.core.item.behavior.ItemBehavior; -import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; -import net.momirealms.craftengine.core.item.context.UseOnContext; -import net.momirealms.craftengine.core.pack.Pack; -import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.util.Key; -import org.bukkit.block.Block; - -import java.nio.file.Path; -import java.util.Map; - -public class BoneMealItemBehavior extends ItemBehavior { - public static final Factory FACTORY = new Factory(); - public static final BoneMealItemBehavior INSTANCE = new BoneMealItemBehavior(); - - @Override - public InteractionResult useOnBlock(UseOnContext context) { - if (context.getPlayer().isAdventureMode()) { - return InteractionResult.PASS; - } - - BukkitBlockInWorld clicked = (BukkitBlockInWorld) context.getLevel().getBlockAt(context.getClickedPos()); - Block block = clicked.block(); - ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockDataToId(block.getBlockData())); - if (state == null || state.isEmpty()) return InteractionResult.PASS; - - boolean shouldHandle =false; - if (state.behavior() instanceof CropBlockBehavior blockBehavior) { - if (!blockBehavior.isMaxAge(state)) { - shouldHandle = true; - } - } else if (state.behavior() instanceof SaplingBlockBehavior) { - shouldHandle = true; - } else if (state.behavior() instanceof GrassBlockBehavior) { - if (block.getLocation().add(0, 1, 0).getBlock().isEmpty()) { - shouldHandle = true; - } - } - - if (!shouldHandle) return InteractionResult.PASS; - - boolean sendSwing = false; - try { - Object visualState = state.vanillaBlockState().handle(); - Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState); - if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { - boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); - if (!is) { - sendSwing = true; - } - } else { - sendSwing = true; - } - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to check visual state bone meal state", e); - } - if (sendSwing) { - context.getPlayer().swingHand(context.getHand()); - } - return InteractionResult.SUCCESS; - } - - public static class Factory implements ItemBehaviorFactory { - - @Override - public ItemBehavior create(Pack pack, Path path, Key id, Map arguments) { - return INSTANCE; - } - } -} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java index ed3e35737..91dc42d40 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java @@ -8,19 +8,16 @@ public class BukkitItemBehaviors extends ItemBehaviors { public static final Key BLOCK_ITEM = Key.from("craftengine:block_item"); public static final Key ON_LIQUID_BLOCK_ITEM = Key.from("craftengine:liquid_collision_block_item"); public static final Key FURNITURE_ITEM = Key.from("craftengine:furniture_item"); - public static final Key AXE_ITEM = Key.from("craftengine:axe_item"); public static final Key WATER_BUCKET_ITEM = Key.from("craftengine:water_bucket_item"); public static final Key BUCKET_ITEM = Key.from("craftengine:bucket_item"); - public static final Key BONE_MEAL_ITEM = Key.from("craftengine:bone_meal_item"); + public static final Key HAT_ITEM = Key.from("craftengine:hat_item"); public static void init() { register(EMPTY, EmptyItemBehavior.FACTORY); register(BLOCK_ITEM, BlockItemBehavior.FACTORY); register(ON_LIQUID_BLOCK_ITEM, LiquidCollisionBlockItemBehavior.FACTORY); register(FURNITURE_ITEM, FurnitureItemBehavior.FACTORY); - register(AXE_ITEM, AxeItemBehavior.FACTORY); register(WATER_BUCKET_ITEM, WaterBucketItemBehavior.FACTORY); register(BUCKET_ITEM, BucketItemBehavior.FACTORY); - register(BONE_MEAL_ITEM, BoneMealItemBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java index 20773f4f1..b604359e9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/FurnitureItemBehavior.java @@ -2,8 +2,8 @@ package net.momirealms.craftengine.bukkit.item.behavior; import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptPlaceEvent; import net.momirealms.craftengine.bukkit.api.event.FurniturePlaceEvent; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; -import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.util.DirectionUtils; @@ -23,8 +23,8 @@ import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.Vec3d; @@ -105,7 +105,7 @@ public class FurnitureItemBehavior extends ItemBehavior { finalPlacePosition = new Vec3d(xz.left(), xz.right(), clickedPosition.z()); } } else { - furnitureYaw = placement.rotationRule().apply(180 + player.getXRot()); + furnitureYaw = placement.rotationRule().apply(180 + player.xRot()); Pair xz = placement.alignmentRule().apply(Pair.of(clickedPosition.x(), clickedPosition.z())); finalPlacePosition = new Vec3d(xz.left(), clickedPosition.y(), xz.right()); } @@ -134,7 +134,7 @@ public class FurnitureItemBehavior extends ItemBehavior { Item item = context.getItem(); - LoadedFurniture loadedFurniture = BukkitFurnitureManager.instance().place( + BukkitFurniture bukkitFurniture = BukkitFurnitureManager.instance().place( furnitureLocation.clone(), customFurniture, FurnitureExtraData.builder() .item(item.copyWithCount(1)) @@ -142,15 +142,15 @@ public class FurnitureItemBehavior extends ItemBehavior { .dyedColor(item.dyedColor().orElse(null)) .build(), false); - FurniturePlaceEvent placeEvent = new FurniturePlaceEvent(bukkitPlayer, loadedFurniture, furnitureLocation, context.getHand()); + FurniturePlaceEvent placeEvent = new FurniturePlaceEvent(bukkitPlayer, bukkitFurniture, furnitureLocation, context.getHand()); if (EventUtils.fireAndCheckCancel(placeEvent)) { - loadedFurniture.destroy(); + bukkitFurniture.destroy(); return InteractionResult.FAIL; } Cancellable dummy = Cancellable.dummy(); PlayerOptionalContext functionContext = PlayerOptionalContext.of(player, ContextHolder.builder() - .withParameter(DirectContextParameters.FURNITURE, loadedFurniture) + .withParameter(DirectContextParameters.FURNITURE, bukkitFurniture) .withParameter(DirectContextParameters.POSITION, LocationUtils.toWorldPosition(furnitureLocation)) .withParameter(DirectContextParameters.EVENT, dummy) .withParameter(DirectContextParameters.HAND, context.getHand()) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/BukkitItemFactory.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/BukkitItemFactory.java index 8606b48a6..45d3ceb87 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/BukkitItemFactory.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/BukkitItemFactory.java @@ -10,8 +10,8 @@ import net.momirealms.craftengine.core.item.ItemWrapper; import net.momirealms.craftengine.core.item.JukeboxPlayable; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.sparrow.nbt.Tag; import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; import java.util.Objects; import java.util.Optional; @@ -90,12 +90,32 @@ public abstract class BukkitItemFactory> extend } @Override - protected JsonElement encodeJson(Object type, Object component) { + protected void setJavaComponent(W item, Object type, Object value) { throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); } @Override - public Object encodeJava(Object componentType, @Nullable Object component) { + protected void setJsonComponent(W item, Object type, JsonElement value) { + throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); + } + + @Override + protected void setNBTComponent(W item, Object type, Tag value) { + throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); + } + + @Override + protected Object getJavaComponent(W item, Object type) { + throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); + } + + @Override + protected JsonElement getJsonComponent(W item, Object type) { + throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); + } + + @Override + protected Tag getNBTComponent(W item, Object type) { throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); } @@ -110,7 +130,7 @@ public abstract class BukkitItemFactory> extend } @Override - protected Object getComponent(W item, Object type) { + protected Object getExactComponent(W item, Object type) { throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_20_5.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_20_5.java index 9261ae98d..f79b48f36 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_20_5.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_20_5.java @@ -1,8 +1,6 @@ package net.momirealms.craftengine.bukkit.item.factory; import com.google.gson.JsonElement; -import com.saicone.rtag.data.ComponentType; -import com.saicone.rtag.item.ItemObject; import net.momirealms.craftengine.bukkit.item.ComponentItemWrapper; import net.momirealms.craftengine.bukkit.item.ComponentTypes; import net.momirealms.craftengine.bukkit.nms.FastNMS; @@ -11,15 +9,14 @@ import net.momirealms.craftengine.core.item.Enchantment; import net.momirealms.craftengine.core.item.Trim; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.sparrow.nbt.Tag; import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -@SuppressWarnings("UnstableApiUsage") public class ComponentItemFactory1_20_5 extends BukkitItemFactory { public ComponentItemFactory1_20_5(CraftEngine plugin) { @@ -42,7 +39,12 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory customModelData(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.CUSTOM_MODEL_DATA)) return Optional.empty(); - return Optional.ofNullable( - (Integer) ComponentType.encodeJava( - ComponentTypes.CUSTOM_MODEL_DATA, - item.getComponent(ComponentTypes.CUSTOM_MODEL_DATA) - ).orElse(null)); + return item.getJavaComponent(ComponentTypes.CUSTOM_MODEL_DATA); } @Override - protected void customName(ComponentItemWrapper item, String json) { + protected void customNameJson(ComponentItemWrapper item, String json) { if (json == null) { item.resetComponent(ComponentTypes.CUSTOM_NAME); } else { @@ -135,18 +152,12 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory customName(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.CUSTOM_NAME)) return Optional.empty(); - return Optional.ofNullable( - (String) ComponentType.encodeJava( - ComponentTypes.CUSTOM_NAME, - item.getComponent(ComponentTypes.CUSTOM_NAME) - ).orElse(null) - ); + protected Optional customNameJson(ComponentItemWrapper item) { + return item.getJavaComponent(ComponentTypes.CUSTOM_NAME); } @Override - protected void itemName(ComponentItemWrapper item, String json) { + protected void itemNameJson(ComponentItemWrapper item, String json) { if (json == null) { item.resetComponent(ComponentTypes.ITEM_NAME); } else { @@ -155,14 +166,8 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory itemName(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.ITEM_NAME)) return Optional.empty(); - return Optional.ofNullable( - (String) ComponentType.encodeJava( - ComponentTypes.ITEM_NAME, - item.getComponent(ComponentTypes.ITEM_NAME) - ).orElse(null) - ); + protected Optional itemNameJson(ComponentItemWrapper item) { + return item.getJavaComponent(ComponentTypes.ITEM_NAME); } @Override @@ -175,20 +180,13 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory> lore(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.LORE)) return Optional.empty(); - return Optional.ofNullable( - (List) ComponentType.encodeJava( - ComponentTypes.LORE, - item.getComponent(ComponentTypes.LORE) - ).orElse(null) - ); + protected Optional> loreJson(ComponentItemWrapper item) { + return item.getJavaComponent(ComponentTypes.LORE); } @Override - protected void lore(ComponentItemWrapper item, List lore) { + protected void loreJson(ComponentItemWrapper item, List lore) { if (lore == null || lore.isEmpty()) { item.resetComponent(ComponentTypes.LORE); } else { @@ -212,7 +210,7 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory glint(ComponentItemWrapper item) { - return Optional.ofNullable((Boolean) item.getComponent(ComponentTypes.ENCHANTMENT_GLINT_OVERRIDE)); + return Optional.ofNullable((Boolean) item.getComponentExact(ComponentTypes.ENCHANTMENT_GLINT_OVERRIDE)); } @Override @@ -226,13 +224,7 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory damage(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.DAMAGE)) return Optional.empty(); - return Optional.ofNullable( - (Integer) ComponentType.encodeJava( - ComponentTypes.DAMAGE, - item.getComponent(ComponentTypes.DAMAGE) - ).orElse(null) - ); + return item.getJavaComponent(ComponentTypes.DAMAGE); } @Override @@ -247,10 +239,7 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory dyedColor(ComponentItemWrapper item) { if (!item.hasComponent(ComponentTypes.DYED_COLOR)) return Optional.empty(); - Object javaObj = ComponentType.encodeJava( - ComponentTypes.DYED_COLOR, - item.getComponent(ComponentTypes.DYED_COLOR) - ).orElse(null); + Object javaObj = getJavaComponent(item, ComponentTypes.DYED_COLOR); if (javaObj instanceof Integer integer) { return Optional.of(integer); } else if (javaObj instanceof Map map) { @@ -269,14 +258,9 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory maxDamage(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.MAX_DAMAGE)) return Optional.of((int) item.getItem().getType().getMaxDurability()); - return Optional.ofNullable( - (Integer) ComponentType.encodeJava( - ComponentTypes.MAX_DAMAGE, - item.getComponent(ComponentTypes.MAX_DAMAGE) - ).orElse(null) - ); + protected int maxDamage(ComponentItemWrapper item) { + Optional damage = item.getJavaComponent(ComponentTypes.MAX_DAMAGE); + return damage.orElseGet(() -> (int) item.getItem().getType().getMaxDurability()); } @Override @@ -290,7 +274,7 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory getEnchantment(ComponentItemWrapper item, Key key) { - Object enchant = item.getComponent(ComponentTypes.ENCHANTMENTS); + Object enchant = item.getComponentExact(ComponentTypes.ENCHANTMENTS); try { Map map = EnchantmentUtils.toMap(enchant); Integer level = map.get(key.toString()); @@ -330,7 +314,7 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory map = EnchantmentUtils.toMap(enchant); map.put(enchantment.id().toString(), enchantment.level()); @@ -342,7 +326,7 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory map = EnchantmentUtils.toMap(enchant); map.put(enchantment.id().toString(), enchantment.level()); @@ -359,9 +343,8 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory stackSize = item.getJavaComponent(ComponentTypes.MAX_STACK_SIZE); + return stackSize.orElseGet(() -> item.getItem().getType().getMaxStackSize()); } @Override @@ -384,8 +367,7 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory repairCost(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.REPAIR_COST)) return Optional.empty(); - return Optional.ofNullable((Integer) ComponentType.encodeJava(ComponentTypes.REPAIR_COST, item.getComponent(ComponentTypes.REPAIR_COST)).orElse(null)); + return item.getJavaComponent(ComponentTypes.REPAIR_COST); } @Override @@ -402,8 +384,7 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory trim(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.TRIM)) return Optional.empty(); - Optional trim = ComponentType.encodeJava(ComponentTypes.TRIM, item.getComponent(ComponentTypes.TRIM)); + Optional trim = item.getJavaComponent(ComponentTypes.TRIM); if (trim.isEmpty()) { return Optional.empty(); } @@ -418,7 +399,7 @@ public class ComponentItemFactory1_20_5 extends BukkitItemFactory jukeboxSong(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.JUKEBOX_PLAYABLE)) return Optional.empty(); - @SuppressWarnings("unchecked") - Map map = (Map) ComponentType.encodeJava( - ComponentTypes.JUKEBOX_PLAYABLE, - item.getComponent(ComponentTypes.JUKEBOX_PLAYABLE) - ).orElse(null); - if (map == null) return Optional.empty(); - return Optional.of(new JukeboxPlayable((String) map.get("song"), (boolean) map.getOrDefault("show_in_tooltip", true))); + Optional> map = item.getJavaComponent(ComponentTypes.JUKEBOX_PLAYABLE); + return map.map(song -> new JukeboxPlayable( + (String) song.get("song"), + (boolean) song.getOrDefault("show_in_tooltip", true)) + ); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_2.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_2.java index 3f05e1c44..13f9be33f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_2.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_2.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.bukkit.item.factory; -import com.saicone.rtag.data.ComponentType; import net.momirealms.craftengine.bukkit.item.ComponentItemWrapper; import net.momirealms.craftengine.bukkit.item.ComponentTypes; import net.momirealms.craftengine.core.item.EquipmentData; @@ -8,7 +7,6 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import java.util.Optional; -@SuppressWarnings("UnstableApiUsage") public class ComponentItemFactory1_21_2 extends ComponentItemFactory1_21 { public ComponentItemFactory1_21_2(CraftEngine plugin) { @@ -26,13 +24,7 @@ public class ComponentItemFactory1_21_2 extends ComponentItemFactory1_21 { @Override protected Optional tooltipStyle(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.TOOLTIP_STYLE)) return Optional.empty(); - return Optional.ofNullable( - (String) ComponentType.encodeJava( - ComponentTypes.TOOLTIP_STYLE, - item.getComponent(ComponentTypes.TOOLTIP_STYLE) - ).orElse(null) - ); + return item.getJavaComponent(ComponentTypes.TOOLTIP_STYLE); } @Override @@ -46,13 +38,7 @@ public class ComponentItemFactory1_21_2 extends ComponentItemFactory1_21 { @Override protected Optional itemModel(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.ITEM_MODEL)) return Optional.empty(); - return Optional.ofNullable( - (String) ComponentType.encodeJava( - ComponentTypes.ITEM_MODEL, - item.getComponent(ComponentTypes.ITEM_MODEL) - ).orElse(null) - ); + return item.getJavaComponent(ComponentTypes.ITEM_MODEL); } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_4.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_4.java index f6d14478b..df4ed6d31 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_4.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_4.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.bukkit.item.factory; -import com.saicone.rtag.data.ComponentType; import net.momirealms.craftengine.bukkit.item.ComponentItemWrapper; import net.momirealms.craftengine.bukkit.item.ComponentTypes; import net.momirealms.craftengine.core.plugin.CraftEngine; @@ -9,7 +8,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -@SuppressWarnings("UnstableApiUsage") public class ComponentItemFactory1_21_4 extends ComponentItemFactory1_21_2 { public ComponentItemFactory1_21_4(CraftEngine plugin) { @@ -18,8 +16,7 @@ public class ComponentItemFactory1_21_4 extends ComponentItemFactory1_21_2 { @Override protected Optional customModelData(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.CUSTOM_MODEL_DATA)) return Optional.empty(); - Optional optional = ComponentType.encodeJava(ComponentTypes.CUSTOM_MODEL_DATA, item.getComponent(ComponentTypes.CUSTOM_MODEL_DATA)); + Optional optional = item.getJavaComponent(ComponentTypes.CUSTOM_MODEL_DATA); if (optional.isEmpty()) return Optional.empty(); @SuppressWarnings("unchecked") Map data = (Map) optional.get(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_5.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_5.java index ab79741f7..3d4adaf74 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_5.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_5.java @@ -3,14 +3,15 @@ package net.momirealms.craftengine.bukkit.item.factory; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.saicone.rtag.data.ComponentType; -import com.saicone.rtag.tag.TagList; -import com.saicone.rtag.util.ChatComponent; +import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.item.ComponentItemWrapper; import net.momirealms.craftengine.bukkit.item.ComponentTypes; -import net.momirealms.craftengine.bukkit.util.ComponentUtils; import net.momirealms.craftengine.core.item.JukeboxPlayable; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.GsonHelper; +import net.momirealms.sparrow.nbt.ListTag; +import net.momirealms.sparrow.nbt.Tag; import java.util.ArrayList; import java.util.List; @@ -24,60 +25,91 @@ public class ComponentItemFactory1_21_5 extends ComponentItemFactory1_21_4 { } @Override - protected void customName(ComponentItemWrapper item, String json) { + protected void customNameJson(ComponentItemWrapper item, String json) { if (json == null) { item.resetComponent(ComponentTypes.CUSTOM_NAME); } else { - item.setNBTComponent(ComponentTypes.CUSTOM_NAME, ChatComponent.toTag(ComponentUtils.jsonToMinecraft(json))); + item.setSparrowNBTComponent(ComponentTypes.CUSTOM_NAME, AdventureHelper.componentToNbt(AdventureHelper.jsonToComponent(json))); } } @Override - protected Optional customName(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.CUSTOM_NAME)) return Optional.empty(); - return ComponentType.encodeJson(ComponentTypes.CUSTOM_NAME, item.getComponent(ComponentTypes.CUSTOM_NAME)).map(jsonElement -> GsonHelper.get().toJson(jsonElement)); + protected Optional customNameJson(ComponentItemWrapper item) { + return item.getJsonComponent(ComponentTypes.CUSTOM_NAME).map(it -> GsonHelper.get().toJson(it)); } @Override - protected void itemName(ComponentItemWrapper item, String json) { + protected void customNameComponent(ComponentItemWrapper item, Component component) { + if (component == null) { + item.resetComponent(ComponentTypes.CUSTOM_NAME); + } else { + item.setSparrowNBTComponent(ComponentTypes.CUSTOM_NAME, AdventureHelper.componentToNbt(component)); + } + } + + @Override + protected Optional customNameComponent(ComponentItemWrapper item) { + return customNameJson(item).map(AdventureHelper::jsonToComponent); + } + + @Override + protected void itemNameJson(ComponentItemWrapper item, String json) { if (json == null) { item.resetComponent(ComponentTypes.ITEM_NAME); } else { - item.setNBTComponent(ComponentTypes.ITEM_NAME, ChatComponent.toTag(ComponentUtils.jsonToMinecraft(json))); + item.setSparrowNBTComponent(ComponentTypes.ITEM_NAME, AdventureHelper.componentToNbt(AdventureHelper.jsonToComponent(json))); } } @Override - protected Optional itemName(ComponentItemWrapper item) { - if (!item.hasComponent(ComponentTypes.ITEM_NAME)) return Optional.empty(); - return ComponentType.encodeJson(ComponentTypes.ITEM_NAME, item.getComponent(ComponentTypes.ITEM_NAME)).map(jsonElement -> GsonHelper.get().toJson(jsonElement)); + protected void itemNameComponent(ComponentItemWrapper item, Component component) { + if (component == null) { + item.resetComponent(ComponentTypes.ITEM_NAME); + } else { + item.setSparrowNBTComponent(ComponentTypes.ITEM_NAME, AdventureHelper.componentToNbt(component)); + } } @Override - protected Optional> lore(ComponentItemWrapper item) { + protected Optional itemNameJson(ComponentItemWrapper item) { + return item.getJsonComponent(ComponentTypes.ITEM_NAME).map(it -> GsonHelper.get().toJson(it)); + } + + @Override + protected Optional> loreJson(ComponentItemWrapper item) { if (!item.hasComponent(ComponentTypes.LORE)) return Optional.empty(); - return ComponentType.encodeJson( - ComponentTypes.LORE, - item.getComponent(ComponentTypes.LORE) - ).map(list -> { - List lore = new ArrayList<>(); - for (JsonElement jsonElement : (JsonArray) list) { - lore.add(GsonHelper.get().toJson(jsonElement)); - } - return lore; - }); + Optional json = item.getJsonComponent(ComponentTypes.LORE); + if (json.isEmpty()) return Optional.empty(); + List lore = new ArrayList<>(); + for (JsonElement jsonElement : (JsonArray) json.get()) { + lore.add(GsonHelper.get().toJson(jsonElement)); + } + return Optional.of(lore); } @Override - protected void lore(ComponentItemWrapper item, List lore) { + protected void loreComponent(ComponentItemWrapper item, List lore) { if (lore == null || lore.isEmpty()) { item.resetComponent(ComponentTypes.LORE); } else { - List loreTags = new ArrayList<>(); - for (String json : lore) { - loreTags.add(ChatComponent.toTag(ComponentUtils.jsonToMinecraft(json))); + List loreTags = new ArrayList<>(); + for (Component component : lore) { + loreTags.add(AdventureHelper.componentToTag(component)); } - item.setNBTComponent(ComponentTypes.LORE, TagList.newTag(loreTags)); + item.setSparrowNBTComponent(ComponentTypes.LORE, new ListTag(loreTags)); + } + } + + @Override + protected void loreJson(ComponentItemWrapper item, List lore) { + if (lore == null || lore.isEmpty()) { + item.resetComponent(ComponentTypes.LORE); + } else { + List loreTags = new ArrayList<>(); + for (String json : lore) { + loreTags.add(AdventureHelper.componentToTag(AdventureHelper.jsonToComponent(json))); + } + item.setSparrowNBTComponent(ComponentTypes.LORE, new ListTag(loreTags)); } } @@ -86,7 +118,7 @@ public class ComponentItemFactory1_21_5 extends ComponentItemFactory1_21_4 { if (!item.hasComponent(ComponentTypes.JUKEBOX_PLAYABLE)) return Optional.empty(); String song = (String) ComponentType.encodeJava( ComponentTypes.JUKEBOX_PLAYABLE, - item.getComponent(ComponentTypes.JUKEBOX_PLAYABLE)).orElse(null); + item.getComponentExact(ComponentTypes.JUKEBOX_PLAYABLE)).orElse(null); if (song == null) return Optional.empty(); return Optional.of(new JukeboxPlayable(song, true)); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/UniversalItemFactory.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/UniversalItemFactory.java index dbf97a69d..8c2677997 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/UniversalItemFactory.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/UniversalItemFactory.java @@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.item.modifier.IdModifier; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.SkullUtils; +import net.momirealms.sparrow.nbt.Tag; import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.inventory.ItemFlag; @@ -33,12 +34,17 @@ public class UniversalItemFactory extends BukkitItemFactory { @Override protected void setTag(LegacyItemWrapper item, Object value, Object... path) { - item.set(value, path); + item.setTag(value, path); } @Override - protected Object getTag(LegacyItemWrapper item, Object... path) { - return item.get(path); + protected Object getJavaTag(LegacyItemWrapper item, Object... path) { + return item.getJavaTag(path); + } + + @Override + protected Tag getNBTTag(LegacyItemWrapper item, Object... path) { + return item.getNBTTag(path); } @Override @@ -53,39 +59,39 @@ public class UniversalItemFactory extends BukkitItemFactory { @Override protected Optional customId(LegacyItemWrapper item) { - Object id = item.get(IdModifier.CRAFT_ENGINE_ID); + Object id = item.getJavaTag(IdModifier.CRAFT_ENGINE_ID); if (id == null) return Optional.empty(); return Optional.of(Key.of(id.toString())); } @Override protected void customId(LegacyItemWrapper item, Key id) { - item.set(id.toString(), IdModifier.CRAFT_ENGINE_ID); + item.setTag(id.toString(), IdModifier.CRAFT_ENGINE_ID); } @Override - protected void customName(LegacyItemWrapper item, String json) { + protected void customNameJson(LegacyItemWrapper item, String json) { if (json != null) { - item.set(json, "display", "Name"); + item.setTag(json, "display", "Name"); } else { item.remove("display", "Name"); } } @Override - protected Optional customName(LegacyItemWrapper item) { + protected Optional customNameJson(LegacyItemWrapper item) { if (!item.hasTag("display", "Name")) return Optional.empty(); - return Optional.of(item.get("display", "Name")); + return Optional.of(item.getJavaTag("display", "Name")); } @Override - protected void itemName(LegacyItemWrapper item, String json) { - customName(item, json); + protected void itemNameJson(LegacyItemWrapper item, String json) { + customNameJson(item, json); } @Override - protected Optional itemName(LegacyItemWrapper item) { - return customName(item); + protected Optional itemNameJson(LegacyItemWrapper item) { + return customNameJson(item); } @Override @@ -93,14 +99,14 @@ public class UniversalItemFactory extends BukkitItemFactory { if (data == null) { item.remove("CustomModelData"); } else { - item.set(data, "CustomModelData"); + item.setTag(data, "CustomModelData"); } } @Override protected Optional customModelData(LegacyItemWrapper item) { if (!item.hasTag("CustomModelData")) return Optional.empty(); - return Optional.of(item.get("CustomModelData")); + return Optional.of(item.getJavaTag("CustomModelData")); } @Override @@ -108,8 +114,8 @@ public class UniversalItemFactory extends BukkitItemFactory { if (skullData == null) { item.remove("SkullOwner"); } else { - item.set(UUID.nameUUIDFromBytes(SkullUtils.identifierFromBase64(skullData).getBytes(StandardCharsets.UTF_8)), "SkullOwner", "Id"); - item.set( + item.setTag(UUID.nameUUIDFromBytes(SkullUtils.identifierFromBase64(skullData).getBytes(StandardCharsets.UTF_8)), "SkullOwner", "Id"); + item.setTag( List.of(Map.of("Value", skullData)), "SkullOwner", "Properties", "textures" ); @@ -117,45 +123,45 @@ public class UniversalItemFactory extends BukkitItemFactory { } @Override - protected Optional> lore(LegacyItemWrapper item) { + protected Optional> loreJson(LegacyItemWrapper item) { if (!item.hasTag("display", "Lore")) return Optional.empty(); - return Optional.of(item.get("display", "Lore")); + return Optional.of(item.getJavaTag("display", "Lore")); } @Override - protected void lore(LegacyItemWrapper item, List lore) { + protected void loreJson(LegacyItemWrapper item, List lore) { if (lore == null || lore.isEmpty()) { item.remove("display", "Lore"); } else { - item.set(lore, "display", "Lore"); + item.setTag(lore, "display", "Lore"); } } @Override protected boolean unbreakable(LegacyItemWrapper item) { - return Optional.ofNullable((Boolean) item.get("Unbreakable")).orElse(false); + return Optional.ofNullable((Boolean) item.getJavaTag("Unbreakable")).orElse(false); } @Override protected void unbreakable(LegacyItemWrapper item, boolean unbreakable) { - item.set(unbreakable, "Unbreakable"); + item.setTag(unbreakable, "Unbreakable"); } @Override protected Optional damage(LegacyItemWrapper item) { if (!item.hasTag("Damage")) return Optional.empty(); - return Optional.of(item.get("Damage")); + return Optional.of(item.getJavaTag("Damage")); } @Override protected void damage(LegacyItemWrapper item, Integer damage) { - item.set(damage, "Damage"); + item.setTag(damage, "Damage"); } @Override protected Optional dyedColor(LegacyItemWrapper item) { if (!item.hasTag("display", "color")) return Optional.empty(); - return Optional.of(item.get("display", "color")); + return Optional.of(item.getJavaTag("display", "color")); } @Override @@ -163,13 +169,13 @@ public class UniversalItemFactory extends BukkitItemFactory { if (color == null) { item.remove("display", "color"); } else { - item.set(color, "display", "color"); + item.setTag(color, "display", "color"); } } @Override - protected Optional maxDamage(LegacyItemWrapper item) { - return Optional.of((int) item.getItem().getType().getMaxDurability()); + protected int maxDamage(LegacyItemWrapper item) { + return item.getItem().getType().getMaxDurability(); } @Override @@ -187,7 +193,7 @@ public class UniversalItemFactory extends BukkitItemFactory { for (Enchantment enchantment : enchantments) { tags.add((Map.of("id", enchantment.id().toString(), "lvl", (short) enchantment.level()))); } - item.set(tags, "Enchantments"); + item.setTag(tags, "Enchantments"); } @Override @@ -200,12 +206,12 @@ public class UniversalItemFactory extends BukkitItemFactory { for (Enchantment enchantment : enchantments) { tags.add((Map.of("id", enchantment.id().toString(), "lvl", (short) enchantment.level()))); } - item.set(tags, "StoredEnchantments"); + item.setTag(tags, "StoredEnchantments"); } @Override protected void addEnchantment(LegacyItemWrapper item, Enchantment enchantment) { - Object enchantments = item.getExact("Enchantments"); + Object enchantments = item.getExactTag("Enchantments"); if (enchantments != null) { for (Object enchant : TagList.getValue(enchantments)) { if (TagBase.getValue(TagCompound.get(enchant, "id")).equals(enchant.toString())) { @@ -215,13 +221,13 @@ public class UniversalItemFactory extends BukkitItemFactory { } item.add(Map.of("id", enchantment.id().toString(), "lvl", (short) enchantment.level()), "Enchantments"); } else { - item.set(List.of(Map.of("id", enchantment.id().toString(), "lvl", (short) enchantment.level())), "Enchantments"); + item.setTag(List.of(Map.of("id", enchantment.id().toString(), "lvl", (short) enchantment.level())), "Enchantments"); } } @Override protected void addStoredEnchantment(LegacyItemWrapper item, Enchantment enchantment) { - Object enchantments = item.getExact("StoredEnchantments"); + Object enchantments = item.getExactTag("StoredEnchantments"); if (enchantments != null) { for (Object enchant : TagList.getValue(enchantments)) { if (TagBase.getValue(TagCompound.get(enchant, "id")).equals(enchant.toString())) { @@ -231,7 +237,7 @@ public class UniversalItemFactory extends BukkitItemFactory { } item.add(Map.of("id", enchantment.id().toString(), "lvl", (short) enchantment.level()), "StoredEnchantments"); } else { - item.set(List.of(Map.of("id", enchantment.id().toString(), "lvl", (short) enchantment.level())), "StoredEnchantments"); + item.setTag(List.of(Map.of("id", enchantment.id().toString(), "lvl", (short) enchantment.level())), "StoredEnchantments"); } } @@ -254,7 +260,7 @@ public class UniversalItemFactory extends BukkitItemFactory { ItemFlag itemFlag = ItemFlag.valueOf(flag); f = f | 1 << itemFlag.ordinal(); } - item.set(f, "HideFlags"); + item.setTag(f, "HideFlags"); } @Override @@ -269,13 +275,13 @@ public class UniversalItemFactory extends BukkitItemFactory { @Override protected void repairCost(LegacyItemWrapper item, Integer data) { - item.set(data, "RepairCost"); + item.setTag(data, "RepairCost"); } @Override protected Optional repairCost(LegacyItemWrapper item) { if (!item.hasTag("RepairCost")) return Optional.empty(); - return Optional.of(item.get("RepairCost")); + return Optional.of(item.getJavaTag("RepairCost")); } @Override @@ -284,14 +290,14 @@ public class UniversalItemFactory extends BukkitItemFactory { item.remove("Trim"); return; } - item.set(trim.material(), "Trim", "material"); - item.set(trim.pattern(), "Trim", "pattern"); + item.setTag(trim.material(), "Trim", "material"); + item.setTag(trim.pattern(), "Trim", "pattern"); } @Override protected Optional trim(LegacyItemWrapper item) { - String material = item.get("Trim", "material"); - String pattern = item.get("Trim", "pattern"); + String material = item.getJavaTag("Trim", "material"); + String pattern = item.getJavaTag("Trim", "pattern"); if (material == null || pattern == null) return Optional.empty(); return Optional.of(new Trim(material, pattern)); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/DebugStickListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/DebugStickListener.java index ff87989ed..8cd11ae9c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/DebugStickListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/DebugStickListener.java @@ -1,9 +1,10 @@ package net.momirealms.craftengine.bukkit.item.listener; +import com.saicone.rtag.RtagItem; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; -import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.item.LegacyItemWrapper; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; @@ -13,7 +14,6 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.UpdateOption; import net.momirealms.craftengine.core.block.properties.Property; -import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.util.MCUtils; import net.momirealms.craftengine.core.util.MiscUtils; import org.bukkit.Material; @@ -25,8 +25,8 @@ import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -73,8 +73,8 @@ public class DebugStickListener implements Listener { ComponentUtils.adventureToMinecraft(Component.translatable("item.minecraft.debug_stick.empty").arguments(Component.text(blockId))), true); player.sendPacket(systemChatPacket, false); } else { - Item wrapped = BukkitItemManager.instance().wrap(itemInHand); - Object storedData = wrapped.getTag("craftengine:debug_stick_state"); + LegacyItemWrapper wrapped = new LegacyItemWrapper(new RtagItem(itemInHand), itemInHand.getAmount()); + Object storedData = wrapped.getJavaTag("craftengine:debug_stick_state"); if (storedData == null) storedData = new HashMap<>(); if (storedData instanceof Map map) { Map data = MiscUtils.castToMap(map, false); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java index 183db3a06..19cc4e5ce 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java @@ -8,6 +8,7 @@ import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.CustomItem; @@ -16,8 +17,8 @@ import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; import net.momirealms.craftengine.core.util.Cancellable; import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.world.BlockHitResult; @@ -75,6 +76,17 @@ public class ItemEventListener implements Listener { ImmutableBlockState immutableBlockState = null; int stateId = BlockStateUtils.blockStateToId(blockState); Item itemInHand = serverPlayer.getItemInHand(hand); + Location interactionPoint = event.getInteractionPoint(); + + + + BlockHitResult hitResult = null; + if (action == Action.RIGHT_CLICK_BLOCK && interactionPoint != null) { + Direction direction = DirectionUtils.toDirection(event.getBlockFace()); + BlockPos pos = LocationUtils.toBlockPos(block.getLocation()); + Vec3d vec3d = new Vec3d(interactionPoint.getX(), interactionPoint.getY(), interactionPoint.getZ()); + hitResult = new BlockHitResult(vec3d, direction, pos, false); + } // 处理自定义方块 if (!BlockStateUtils.isVanillaBlock(stateId)) { @@ -83,7 +95,7 @@ public class ItemEventListener implements Listener { CustomBlockInteractEvent interactEvent = new CustomBlockInteractEvent( player, block.getLocation(), - event.getInteractionPoint(), + interactionPoint, immutableBlockState, block, event.getBlockFace(), @@ -101,7 +113,7 @@ public class ItemEventListener implements Listener { CustomBlock customBlock = immutableBlockState.owner().value(); PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)) - .withParameter(DirectContextParameters.BLOCK_STATE, immutableBlockState) + .withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, immutableBlockState) .withParameter(DirectContextParameters.HAND, hand) .withParameter(DirectContextParameters.EVENT, dummy) .withParameter(DirectContextParameters.POSITION, LocationUtils.toWorldPosition(block.getLocation())) @@ -113,6 +125,20 @@ public class ItemEventListener implements Listener { event.setCancelled(true); return; } + + if (hitResult != null) { + UseOnContext useOnContext = new UseOnContext(serverPlayer, hand, itemInHand, hitResult); + if (immutableBlockState.behavior() instanceof AbstractBlockBehavior behavior) { + InteractionResult result = behavior.useOnBlock(useOnContext, immutableBlockState); + if (result == InteractionResult.SUCCESS_AND_CANCEL) { + event.setCancelled(true); + return; + } + if (result != InteractionResult.PASS) { + return; + } + } + } } Optional> optionalCustomItem = itemInHand == null ? Optional.empty() : itemInHand.getCustomItem(); @@ -121,11 +147,13 @@ public class ItemEventListener implements Listener { // interact block with items if (hasItem && action == Action.RIGHT_CLICK_BLOCK) { - Location interactionPoint = Objects.requireNonNull(event.getInteractionPoint(), "interaction point should not be null"); - Direction direction = DirectionUtils.toDirection(event.getBlockFace()); - BlockPos pos = LocationUtils.toBlockPos(block.getLocation()); - Vec3d vec3d = new Vec3d(interactionPoint.getX(), interactionPoint.getY(), interactionPoint.getZ()); - BlockHitResult hitResult = new BlockHitResult(vec3d, direction, pos, false); + // some plugins would trigger this event without interaction point + if (interactionPoint == null) { + if (hasCustomItem) { + event.setCancelled(true); + } + return; + } // handle block item if (itemInHand.isBlockItem()) { @@ -168,7 +196,7 @@ public class ItemEventListener implements Listener { Cancellable dummy = Cancellable.dummy(); PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)) - .withOptionalParameter(DirectContextParameters.BLOCK_STATE, immutableBlockState) + .withOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, immutableBlockState) .withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, itemInHand) .withParameter(DirectContextParameters.POSITION, LocationUtils.toWorldPosition(block.getLocation())) .withParameter(DirectContextParameters.HAND, hand) @@ -193,9 +221,10 @@ public class ItemEventListener implements Listener { if (!serverPlayer.isSecondaryUseActive() && interactable) { return; } + UseOnContext useOnContext = new UseOnContext(serverPlayer, hand, itemInHand, hitResult); // 依次执行物品行为 for (ItemBehavior itemBehavior : optionalItemBehaviors.get()) { - InteractionResult result = itemBehavior.useOnBlock(new UseOnContext(serverPlayer, hand, hitResult)); + InteractionResult result = itemBehavior.useOnBlock(useOnContext); if (result == InteractionResult.SUCCESS_AND_CANCEL) { event.setCancelled(true); return; @@ -212,7 +241,7 @@ public class ItemEventListener implements Listener { Cancellable dummy = Cancellable.dummy(); PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() .withParameter(DirectContextParameters.BLOCK, new BukkitBlockInWorld(block)) - .withOptionalParameter(DirectContextParameters.BLOCK_STATE, immutableBlockState) + .withOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, immutableBlockState) .withOptionalParameter(DirectContextParameters.ITEM_IN_HAND, itemInHand) .withParameter(DirectContextParameters.POSITION, LocationUtils.toWorldPosition(block.getLocation())) .withParameter(DirectContextParameters.HAND, hand) @@ -286,7 +315,7 @@ public class ItemEventListener implements Listener { Cancellable dummy = Cancellable.dummy(); CustomItem customItem = optionalCustomItem.get(); PlayerOptionalContext context = PlayerOptionalContext.of(this.plugin.adapt(event.getPlayer()), ContextHolder.builder() - .withParameter(DirectContextParameters.CONSUMED_ITEM, wrapped) + .withParameter(DirectContextParameters.ITEM_IN_HAND, wrapped) .withParameter(DirectContextParameters.EVENT, dummy) .withParameter(DirectContextParameters.HAND, event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND) ); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java index 469cb7e1e..ab9c32643 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java @@ -512,7 +512,7 @@ public class RecipeEventListener implements Listener { Item wrappedFirst = BukkitItemManager.instance().wrap(first.clone()); - int maxDamage = wrappedFirst.maxDamage().orElse(0); + int maxDamage = wrappedFirst.maxDamage(); int damage = wrappedFirst.damage().orElse(0); // not a repairable item if (damage == 0 || maxDamage == 0) return; @@ -577,8 +577,8 @@ public class RecipeEventListener implements Listener { if (renameText != null && !renameText.isBlank()) { try { - if (!renameText.equals(Reflections.method$Component$getString.invoke(ComponentUtils.jsonToMinecraft(wrappedFirst.hoverName().orElse(AdventureHelper.EMPTY_COMPONENT))))) { - wrappedFirst.customName(AdventureHelper.componentToJson(Component.text(renameText))); + if (!renameText.equals(Reflections.method$Component$getString.invoke(ComponentUtils.jsonToMinecraft(wrappedFirst.hoverNameJson().orElse(AdventureHelper.EMPTY_COMPONENT))))) { + wrappedFirst.customNameJson(AdventureHelper.componentToJson(Component.text(renameText))); repairCost += 1; } else if (repairCost == 0) { hasResult = false; @@ -588,10 +588,10 @@ public class RecipeEventListener implements Listener { } } else if (VersionHelper.isOrAbove1_20_5() && wrappedFirst.hasComponent(ComponentTypes.CUSTOM_NAME)) { repairCost += 1; - wrappedFirst.customName(null); + wrappedFirst.customNameJson(null); } else if (!VersionHelper.isOrAbove1_20_5() && wrappedFirst.hasTag("display", "Name")) { repairCost += 1; - wrappedFirst.customName(null); + wrappedFirst.customNameJson(null); } int finalCost = repairCost + repairPenalty; @@ -665,7 +665,7 @@ public class RecipeEventListener implements Listener { } if (renameText != null && !renameText.isBlank()) { try { - if (!renameText.equals(Reflections.method$Component$getString.invoke(ComponentUtils.jsonToMinecraft(wrappedFirst.hoverName().orElse(AdventureHelper.EMPTY_COMPONENT))))) { + if (!renameText.equals(Reflections.method$Component$getString.invoke(ComponentUtils.jsonToMinecraft(wrappedFirst.hoverNameJson().orElse(AdventureHelper.EMPTY_COMPONENT))))) { event.setResult(null); } } catch (Exception e) { @@ -721,7 +721,7 @@ public class RecipeEventListener implements Listener { } int totalDamage = right.damage().orElse(0) + left.damage().orElse(0); - int totalMaxDamage = left.maxDamage().get() + right.maxDamage().get(); + int totalMaxDamage = left.maxDamage() + right.maxDamage(); // should be impossible, but take care if (totalDamage >= totalMaxDamage) { inventory.setResult(null); @@ -750,7 +750,7 @@ public class RecipeEventListener implements Listener { Item newItem = customItem.buildItem(ItemBuildContext.of(plugin.adapt(player))); int remainingDurability = totalMaxDamage - totalDamage; - int newItemDamage = Math.max(0, newItem.maxDamage().get() - remainingDurability); + int newItemDamage = Math.max(0, newItem.maxDamage() - remainingDurability); newItem.damage(newItemDamage); inventory.setResult(newItem.load()); } else if (Reflections.clazz$ArmorDyeRecipe.isInstance(mcRecipe)) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java index 918953dee..59490e1a4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/pack/BukkitPackManager.java @@ -121,6 +121,7 @@ public class BukkitPackManager extends AbstractPackManager implements Listener { return; } if (!Config.sendPackOnUpload()) return; + CraftEngine.instance().logger().info("Complete uploading resource pack"); for (BukkitServerPlayer player : this.plugin.networkManager().onlineUsers()) { sendResourcePack(player); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java index 499c2cd36..0fb4724e7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/BukkitCraftEngine.java @@ -19,17 +19,19 @@ import net.momirealms.craftengine.bukkit.plugin.command.BukkitSenderFactory; import net.momirealms.craftengine.bukkit.plugin.gui.BukkitGuiManager; import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector; import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager; +import net.momirealms.craftengine.bukkit.plugin.network.PacketConsumers; import net.momirealms.craftengine.bukkit.plugin.scheduler.BukkitSchedulerAdapter; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.sound.BukkitSoundManager; import net.momirealms.craftengine.bukkit.util.EventUtils; import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.bukkit.util.RegistryUtils; import net.momirealms.craftengine.bukkit.world.BukkitWorldManager; import net.momirealms.craftengine.core.item.ItemManager; -import net.momirealms.craftengine.core.plugin.CompatibilityManager; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.classpath.ReflectionClassPathAppender; import net.momirealms.craftengine.core.plugin.command.sender.SenderFactory; +import net.momirealms.craftengine.core.plugin.compatibility.CompatibilityManager; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.dependency.Dependencies; import net.momirealms.craftengine.core.plugin.dependency.Dependency; @@ -37,6 +39,7 @@ import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManagerImp import net.momirealms.craftengine.core.plugin.logger.JavaPluginLogger; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerAdapter; import net.momirealms.craftengine.core.plugin.scheduler.SchedulerTask; +import net.momirealms.craftengine.core.util.CharacterUtils; import net.momirealms.craftengine.core.util.ReflectionUtils; import net.momirealms.craftengine.core.util.VersionHelper; import org.bstats.bukkit.Metrics; @@ -154,6 +157,7 @@ public class BukkitCraftEngine extends CraftEngine { BukkitBlockBehaviors.init(); BukkitItemBehaviors.init(); BukkitHitBoxTypes.init(); + PacketConsumers.initEntities(RegistryUtils.currentEntityTypeRegistrySize()); super.packManager = new BukkitPackManager(this); super.senderFactory = new BukkitSenderFactory(this); super.itemManager = new BukkitItemManager(this); @@ -205,7 +209,7 @@ public class BukkitCraftEngine extends CraftEngine { @Override public InputStream resourceStream(String filePath) { - return bootstrap.getResource(filePath.replace("\\", "/")); + return bootstrap.getResource(CharacterUtils.replaceBackslashWithSlash(filePath)); } @Override @@ -334,7 +338,7 @@ public class BukkitCraftEngine extends CraftEngine { if (this.antiGrief == null) { this.antiGrief = AntiGriefLib.builder(this.bootstrap) .ignoreOP(true) - .silentLogs(true) + .silentLogs(false) .build(); } return this.antiGrief; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java index c1f1c3f39..0eee9c0aa 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitCommandManager.java @@ -41,6 +41,7 @@ public class BukkitCommandManager extends AbstractCommandManager new DebugGetBlockStateRegistryIdCommand(this, plugin), new DebugGetBlockInternalIdCommand(this, plugin), new DebugAppearanceStateUsageCommand(this, plugin), + new DebugClearCooldownCommand(this, plugin), new DebugRealStateUsageCommand(this, plugin), new DebugItemDataCommand(this, plugin), new DebugSetBlockCommand(this, plugin), diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitSenderFactory.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitSenderFactory.java index a9a2ba020..4725c79eb 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitSenderFactory.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/BukkitSenderFactory.java @@ -1,8 +1,7 @@ package net.momirealms.craftengine.bukkit.plugin.command; -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.util.ComponentUtils; @@ -18,11 +17,9 @@ import org.bukkit.entity.Player; import java.util.UUID; public class BukkitSenderFactory extends SenderFactory { - private final BukkitAudiences audiences; public BukkitSenderFactory(BukkitCraftEngine plugin) { super(plugin); - this.audiences = BukkitAudiences.create(plugin.bootstrap()); } @Override @@ -41,20 +38,18 @@ public class BukkitSenderFactory extends SenderFactory audience(sender).sendMessage(message)); + String legacy = LegacyComponentSerializer.legacySection().serialize(message); + plugin().scheduler().sync().run(() -> sender.sendMessage(legacy)); } } @@ -93,6 +88,5 @@ public class BukkitSenderFactory extends SenderFactory reals = blockManager.appearanceToRealStates(appearance); - if (reals == null) { + if (reals == null || reals.isEmpty()) { Component hover = Component.text(baseBlockId.value() + ":" + i).color(NamedTextColor.GREEN); hover = hover.append(Component.newline()).append(Component.text(BlockStateUtils.fromBlockData(BlockStateUtils.idToBlockState(appearance)).getAsString()).color(NamedTextColor.GREEN)); text = text.color(NamedTextColor.GREEN).hoverEvent(HoverEvent.showText(hover)); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugClearCooldownCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugClearCooldownCommand.java new file mode 100644 index 000000000..757627d11 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DebugClearCooldownCommand.java @@ -0,0 +1,33 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.Command; +import org.incendo.cloud.bukkit.parser.PlayerParser; + +public class DebugClearCooldownCommand extends BukkitCommandFeature { + + public DebugClearCooldownCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { + return builder + .required("player", PlayerParser.playerParser()) + .handler(context -> { + BukkitServerPlayer serverPlayer = plugin().adapt(context.get("player")); + serverPlayer.cooldown().clearCooldowns(); + plugin().senderFactory().wrap(context.sender()).sendMessage(Component.text("Done clearing cooldowns!")); + }); + } + + @Override + public String getFeatureID() { + return "debug_clear_cooldown"; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java index 1f21b667e..63f72aaca 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java @@ -1,16 +1,19 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.item.ComponentTypes; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; -import net.momirealms.craftengine.bukkit.util.MaterialUtils; import net.momirealms.craftengine.bukkit.util.PlayerUtils; import net.momirealms.craftengine.core.item.CustomItem; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.ItemKeys; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; import net.momirealms.craftengine.core.plugin.command.FlagKeys; import net.momirealms.craftengine.core.plugin.locale.MessageConstants; import net.momirealms.craftengine.core.util.Key; -import org.bukkit.Material; +import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.NamespacedKey; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -23,9 +26,12 @@ import org.incendo.cloud.bukkit.parser.NamespacedKeyParser; import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser; import org.incendo.cloud.context.CommandContext; import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.flag.CommandFlag; import org.incendo.cloud.suggestion.Suggestion; import org.incendo.cloud.suggestion.SuggestionProvider; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; public class TotemAnimationCommand extends BukkitCommandFeature { @@ -45,18 +51,38 @@ public class TotemAnimationCommand extends BukkitCommandFeature { return CompletableFuture.completedFuture(plugin().itemManager().cachedTotemSuggestions()); } })) + .flag(CommandFlag.builder("sound_event").withComponent(NamespacedKeyParser.namespacedKeyParser()).build()) + .flag(CommandFlag.builder("sound_location").withComponent(NamespacedKeyParser.namespacedKeyParser()).build()) .handler(context -> { NamespacedKey namespacedKey = context.get("id"); Key key = Key.of(namespacedKey.namespace(), namespacedKey.value()); - CustomItem item = plugin().itemManager().getCustomItem(key).orElse(null); - if (item == null || MaterialUtils.getMaterial(item.material()) != Material.TOTEM_OF_UNDYING) { + CustomItem customItem = plugin().itemManager().getCustomItem(key).orElse(null); + if (customItem == null || (!VersionHelper.isOrAbove1_21_2() && customItem.material().equals(ItemKeys.TOTEM_OF_UNDYING))) { handleFeedback(context, MessageConstants.COMMAND_TOTEM_NOT_TOTEM, Component.text(key.toString())); return; } - ItemStack totem = item.buildItemStack(); + Item item = customItem.buildItem(ItemBuildContext.EMPTY); + if (VersionHelper.isOrAbove1_21_2()) { + if (context.flags().contains("sound_location")) { + String soundResourceLocation = context.flags().getValue("sound_location").get().toString(); + if (soundResourceLocation != null) { + item.setComponent(ComponentTypes.DEATH_PROTECTION, Map.of("death_effects", List.of(Map.of("type", "play_sound", "sound", Map.of( + "sound_id", soundResourceLocation + ))))); + } + } else if (context.flags().contains("sound_event")) { + String soundEvent = context.flags().getValue("sound_event").get().toString(); + if (soundEvent != null) { + item.setComponent(ComponentTypes.DEATH_PROTECTION, Map.of("death_effects", List.of(Map.of("type", "play_sound", "sound", soundEvent)))); + } + } else { + item.setComponent(ComponentTypes.DEATH_PROTECTION, Map.of()); + } + } + ItemStack totemItem = item.load(); MultiplePlayerSelector selector = context.get("players"); for (Player player : selector.values()) { - PlayerUtils.sendTotemAnimation(player, totem); + PlayerUtils.sendTotemAnimation(player, totemItem); } }); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java index 79b1528a5..30201b3d2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitGuiManager.java @@ -1,10 +1,13 @@ package net.momirealms.craftengine.bukkit.plugin.gui; +import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; -import net.momirealms.craftengine.core.plugin.gui.AbstractGui; -import net.momirealms.craftengine.core.plugin.gui.Gui; -import net.momirealms.craftengine.core.plugin.gui.GuiManager; -import net.momirealms.craftengine.core.plugin.gui.Inventory; +import net.momirealms.craftengine.bukkit.util.ComponentUtils; +import net.momirealms.craftengine.bukkit.util.LegacyInventoryUtils; +import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.gui.*; +import net.momirealms.craftengine.core.util.ReflectionUtils; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -13,8 +16,11 @@ import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.MenuType; public class BukkitGuiManager implements GuiManager, Listener { + private static final boolean useNewOpenInventory = ReflectionUtils.getDeclaredMethod(InventoryView.class, void.class, new String[]{"open"}) != null; private final BukkitCraftEngine plugin; public BukkitGuiManager(BukkitCraftEngine plugin) { @@ -31,6 +37,47 @@ public class BukkitGuiManager implements GuiManager, Listener { HandlerList.unregisterAll(this); } + @SuppressWarnings("UnstableApiUsage") + @Override + public void openInventory(net.momirealms.craftengine.core.entity.player.Player player, GuiType guiType) { + Player bukkitPlayer = (Player) player.platformPlayer(); + if (useNewOpenInventory) { + switch (guiType) { + case ANVIL -> MenuType.ANVIL.create(bukkitPlayer).open(); + case LOOM -> MenuType.LOOM.create(bukkitPlayer).open(); + case ENCHANTMENT -> MenuType.ENCHANTMENT.create(bukkitPlayer).open(); + case CRAFTING -> MenuType.CRAFTING.create(bukkitPlayer).open(); + case CARTOGRAPHY -> MenuType.CARTOGRAPHY_TABLE.create(bukkitPlayer).open(); + case SMITHING -> MenuType.SMITHING.create(bukkitPlayer).open(); + case GRINDSTONE -> MenuType.GRINDSTONE.create(bukkitPlayer).open(); + } + } else { + switch (guiType) { + case ANVIL -> LegacyInventoryUtils.openAnvil(bukkitPlayer); + case LOOM -> LegacyInventoryUtils.openLoom(bukkitPlayer); + case GRINDSTONE -> LegacyInventoryUtils.openGrindstone(bukkitPlayer); + case SMITHING -> LegacyInventoryUtils.openSmithingTable(bukkitPlayer); + case CRAFTING -> LegacyInventoryUtils.openWorkbench(bukkitPlayer); + case ENCHANTMENT -> LegacyInventoryUtils.openEnchanting(bukkitPlayer); + case CARTOGRAPHY -> LegacyInventoryUtils.openCartographyTable(bukkitPlayer); + } + } + } + + @Override + public void updateInventoryTitle(net.momirealms.craftengine.core.entity.player.Player player, Component component) { + Object nmsPlayer = player.serverPlayer(); + try { + Object containerMenu = Reflections.field$Player$containerMenu.get(nmsPlayer); + int containerId = Reflections.field$AbstractContainerMenu$containerId.getInt(containerMenu); + Object menuType = Reflections.field$AbstractContainerMenu$menuType.get(containerMenu); + Object packet = Reflections.constructor$ClientboundOpenScreenPacket.newInstance(containerId, menuType, ComponentUtils.adventureToMinecraft(component)); + player.sendPacket(packet, false); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to update inventory title", e); + } + } + @Override public Inventory createInventory(Gui gui, int size) { CraftEngineInventoryHolder holder = new CraftEngineInventoryHolder(gui); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitInventory.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitInventory.java index d7bb0b932..13464fc7b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitInventory.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/gui/BukkitInventory.java @@ -31,7 +31,7 @@ public class BukkitInventory implements Inventory { Reflections.field$Player$containerMenu.set(nmsPlayer, menu); Reflections.method$ServerPlayer$initMenu.invoke(nmsPlayer, menu); } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to update inventory title", e); + CraftEngine.instance().logger().warn("Failed to create bukkit inventory", e); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java index 60f26221c..ace94cb32 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/BukkitInjector.java @@ -29,6 +29,7 @@ import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.item.recipe.BukkitRecipeManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.NoteBlockChainUpdateUtils; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.block.BlockKeys; @@ -56,7 +57,6 @@ import net.momirealms.craftengine.core.world.chunk.InjectedHolder; import net.momirealms.craftengine.shared.ObjectHolder; import net.momirealms.craftengine.shared.block.*; import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.Nullable; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -73,7 +73,7 @@ import java.util.function.Consumer; public class BukkitInjector { private static final ByteBuddy byteBuddy = new ByteBuddy(ClassFileVersion.JAVA_V17); - private static final BukkitBlockShape STONE_SHAPE = new BukkitBlockShape(Reflections.instance$Blocks$STONE$defaultState); + private static final BukkitBlockShape STONE_SHAPE = new BukkitBlockShape(Reflections.instance$Blocks$STONE$defaultState, Reflections.instance$Blocks$STONE$defaultState); private static Class clazz$InjectedPalettedContainer; private static Class clazz$InjectedLevelChunkSection; @@ -248,6 +248,12 @@ public class BukkitInjector { // getShape .method(ElementMatchers.is(Reflections.method$BlockBehaviour$getShape)) .intercept(MethodDelegation.to(GetShapeInterceptor.INSTANCE)) + // getCollisionShape + .method(ElementMatchers.is(Reflections.method$BlockBehaviour$getCollisionShape)) + .intercept(MethodDelegation.to(GetCollisionShapeInterceptor.INSTANCE)) + // getSupportShape + .method(ElementMatchers.is(Reflections.method$BlockBehaviour$getBlockSupportShape)) + .intercept(MethodDelegation.to(GetSupportShapeInterceptor.INSTANCE)) // mirror .method(ElementMatchers.is(Reflections.method$BlockBehaviour$mirror)) .intercept(MethodDelegation.to(MirrorInterceptor.INSTANCE)) @@ -318,6 +324,9 @@ public class BukkitInjector { .and(ElementMatchers.takesArgument(1, Reflections.clazz$LevelReader).or(ElementMatchers.takesArgument(1, Reflections.clazz$Direction))) .and(ElementMatchers.named("updateShape").or(ElementMatchers.named("a")))) .intercept(MethodDelegation.to(UpdateShapeInterceptor.INSTANCE)) + // neighborChanged + .method(ElementMatchers.is(Reflections.method$BlockBehaviour$neighborChanged)) + .intercept(MethodDelegation.to(NeighborChangedInterceptor.INSTANCE)) // // getFluidState // .method(ElementMatchers.returns(Reflections.clazz$FluidState) // .and(ElementMatchers.takesArgument(0, Reflections.clazz$BlockState))) @@ -800,19 +809,18 @@ public class BukkitInjector { // 如果先前不是空气则标记 if (!previous.isEmpty()) { holder.ceChunk().setDirty(true); - } - if (Config.enableLightSystem() && Config.forceUpdateLight()) { - updateLightIfChanged(holder, previousState, newState, null, y, z, x); + if (Config.enableLightSystem()) { + updateLightIfChanged(holder, previousState, newState, newState, x, y, z); + } } } else { ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); ImmutableBlockState previousImmutableBlockState = section.setBlockState(x, y, z, immutableBlockState); - // 如果之前的自定义块(空气)和当前自定义块不同 - if (previousImmutableBlockState != immutableBlockState) { - holder.ceChunk().setDirty(true); - if (Config.enableLightSystem() && !immutableBlockState.isEmpty()) { - updateLightIfChanged(holder, previousState, newState, immutableBlockState.vanillaBlockState().handle(), y, z, x); - } + if (previousImmutableBlockState == immutableBlockState) return; + holder.ceChunk().setDirty(true); + // 如果新方块的光照属性和客户端认为的不同 + if (Config.enableLightSystem() && !immutableBlockState.isEmpty()) { + updateLightIfChanged(holder, previousState, immutableBlockState.vanillaBlockState().handle(), newState, x, y, z); } } } catch (Exception e) { @@ -820,13 +828,19 @@ public class BukkitInjector { } } - protected static void updateLightIfChanged(@This InjectedHolder thisObj, Object previousBlockState, Object newState, @Nullable Object clientSideNewState, int y, int z, int x) throws ReflectiveOperationException { - int previousLight = BlockStateUtils.getLightEmission(previousBlockState); - int newLight = BlockStateUtils.getLightEmission(newState); - if (previousLight != newLight || (clientSideNewState != null && (BlockStateUtils.isOcclude(newState) != BlockStateUtils.isOcclude(clientSideNewState)))) { - CEWorld world = thisObj.ceChunk().world(); + protected static void updateLightIfChanged(@This InjectedHolder thisObj, Object oldServerSideState, Object clientSideState, Object serverSideState, int x, int y, int z) { + CEWorld world = thisObj.ceChunk().world(); + Object blockPos = LocationUtils.toBlockPos(x, y, z); + Object serverWorld = world.world().serverWorld(); + if (clientSideState != serverSideState && FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(clientSideState, serverSideState, serverWorld, blockPos)) { SectionPos sectionPos = thisObj.cePos(); - List pos = SectionPosUtils.calculateAffectedRegions((sectionPos.x() << 4) + x, (sectionPos.y() << 4) + y, (sectionPos.z() << 4) + z, Math.max(newLight, previousLight)); + List pos = SectionPosUtils.calculateAffectedRegions((sectionPos.x() << 4) + x, (sectionPos.y() << 4) + y, (sectionPos.z() << 4) + z, 15); + world.sectionLightUpdated(pos); + return; + } + if (FastNMS.INSTANCE.method$LightEngine$hasDifferentLightProperties(oldServerSideState, serverSideState, serverWorld, blockPos)) { + SectionPos sectionPos = thisObj.cePos(); + List pos = SectionPosUtils.calculateAffectedRegions((sectionPos.x() << 4) + x, (sectionPos.y() << 4) + y, (sectionPos.z() << 4) + z, 15); world.sectionLightUpdated(pos); } } @@ -920,6 +934,36 @@ public class BukkitInjector { } } + public static class GetCollisionShapeInterceptor { + public static final GetCollisionShapeInterceptor INSTANCE = new GetCollisionShapeInterceptor(); + + @RuntimeType + public Object intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) throws Exception { + ObjectHolder holder = ((ShapeHolder) thisObj).getShapeHolder(); + try { + return holder.value().getCollisionShape(thisObj, args); + } catch (Exception e) { + CraftEngine.instance().logger().severe("Failed to run getCollisionShape", e); + return superMethod.call(); + } + } + } + + public static class GetSupportShapeInterceptor { + public static final GetSupportShapeInterceptor INSTANCE = new GetSupportShapeInterceptor(); + + @RuntimeType + public Object intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) throws Exception { + ObjectHolder holder = ((ShapeHolder) thisObj).getShapeHolder(); + try { + return holder.value().getSupportShape(thisObj, args); + } catch (Exception e) { + CraftEngine.instance().logger().severe("Failed to run getSupportShape", e); + return superMethod.call(); + } + } + } + public static class MirrorInterceptor { public static final MirrorInterceptor INSTANCE = new MirrorInterceptor(); @@ -1078,6 +1122,20 @@ public class BukkitInjector { } } } + + public static class NeighborChangedInterceptor { + public static final NeighborChangedInterceptor INSTANCE = new NeighborChangedInterceptor(); + + @RuntimeType + public void intercept(@This Object thisObj, @AllArguments Object[] args, @SuperCall Callable superMethod) { + ObjectHolder holder = ((BehaviorHolder) thisObj).getBehaviorHolder(); + try { + holder.value().neighborChanged(thisObj, args, superMethod); + } catch (Exception e) { + CraftEngine.instance().logger().severe("Failed to run neighborChanged", e); + } + } + } // // public static class PickUpBlockInterceptor { // public static final PickUpBlockInterceptor INSTANCE = new PickUpBlockInterceptor(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index d5998aed6..e3975568f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -9,11 +9,14 @@ import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; +import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIdFinder; import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20; import net.momirealms.craftengine.bukkit.plugin.network.id.PacketIds1_20_5; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.CooldownData; import net.momirealms.craftengine.core.plugin.network.*; import net.momirealms.craftengine.core.util.*; import org.bukkit.Bukkit; @@ -24,10 +27,12 @@ import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.persistence.PersistentDataType; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.IOException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; @@ -36,16 +41,28 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes private static BukkitNetworkManager instance; private static final Map, TriConsumer> NMS_PACKET_HANDLERS = new HashMap<>(); // only for game stage for the moment - private static final Map> BYTE_BUFFER_PACKET_HANDLERS = new HashMap<>(); + private static BiConsumer[] S2C_BYTE_BUFFER_PACKET_HANDLERS; + private static BiConsumer[] C2S_BYTE_BUFFER_PACKET_HANDLERS; private static void registerNMSPacketConsumer(final TriConsumer function, @Nullable Class packet) { if (packet == null) return; NMS_PACKET_HANDLERS.put(packet, function); } - private static void registerByteBufPacketConsumer(final BiConsumer function, int id) { + private static void registerS2CByteBufPacketConsumer(final BiConsumer function, int id) { if (id == -1) return; - BYTE_BUFFER_PACKET_HANDLERS.put(id, function); + if (id < 0 || id >= S2C_BYTE_BUFFER_PACKET_HANDLERS.length) { + throw new IllegalArgumentException("Invalid packet id: " + id); + } + S2C_BYTE_BUFFER_PACKET_HANDLERS[id] = function; + } + + private static void registerC2SByteBufPacketConsumer(final BiConsumer function, int id) { + if (id == -1) return; + if (id < 0 || id >= C2S_BYTE_BUFFER_PACKET_HANDLERS.length) { + throw new IllegalArgumentException("Invalid packet id: " + id); + } + C2S_BYTE_BUFFER_PACKET_HANDLERS[id] = function; } private final BiConsumer> packetsConsumer; @@ -70,8 +87,13 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes private static boolean hasModelEngine; private static boolean hasViaVersion; + @SuppressWarnings("unchecked") public BukkitNetworkManager(BukkitCraftEngine plugin) { instance = this; + S2C_BYTE_BUFFER_PACKET_HANDLERS = new BiConsumer[PacketIdFinder.maxS2CPacketId()]; + C2S_BYTE_BUFFER_PACKET_HANDLERS = new BiConsumer[PacketIdFinder.maxC2SPacketId()]; + Arrays.fill(S2C_BYTE_BUFFER_PACKET_HANDLERS, Handlers.DO_NOTHING); + Arrays.fill(C2S_BYTE_BUFFER_PACKET_HANDLERS, Handlers.DO_NOTHING); hasModelEngine = Bukkit.getPluginManager().getPlugin("ModelEngine") != null; hasViaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion") != null; this.plugin = plugin; @@ -151,25 +173,32 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes registerNMSPacketConsumer(PacketConsumers.RESOURCE_PACK_RESPONSE, Reflections.clazz$ServerboundResourcePackPacket); registerNMSPacketConsumer(PacketConsumers.ENTITY_EVENT, Reflections.clazz$ClientboundEntityEventPacket); registerNMSPacketConsumer(PacketConsumers.MOVE_POS_AND_ROTATE_ENTITY, Reflections.clazz$ClientboundMoveEntityPacket$PosRot); - registerByteBufPacketConsumer(PacketConsumers.LEVEL_CHUNK_WITH_LIGHT, this.packetIds.clientboundLevelChunkWithLightPacket()); - registerByteBufPacketConsumer(PacketConsumers.SECTION_BLOCK_UPDATE, this.packetIds.clientboundSectionBlocksUpdatePacket()); - registerByteBufPacketConsumer(PacketConsumers.BLOCK_UPDATE, this.packetIds.clientboundBlockUpdatePacket()); - registerByteBufPacketConsumer(VersionHelper.isOrAbove1_21_4() ? PacketConsumers.LEVEL_PARTICLE_1_21_4 : (VersionHelper.isOrAbove1_20_5() ? PacketConsumers.LEVEL_PARTICLE_1_20_5 : PacketConsumers.LEVEL_PARTICLE_1_20), this.packetIds.clientboundLevelParticlesPacket()); - registerByteBufPacketConsumer(PacketConsumers.LEVEL_EVENT, this.packetIds.clientboundLevelEventPacket()); - registerByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.OPEN_SCREEN_1_20_3 : PacketConsumers.OPEN_SCREEN_1_20, this.packetIds.clientboundOpenScreenPacket()); - registerByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SET_TITLE_TEXT_1_20_3 : PacketConsumers.SET_TITLE_TEXT_1_20, this.packetIds.clientboundSetTitleTextPacket()); - registerByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SET_SUBTITLE_TEXT_1_20_3 : PacketConsumers.SET_SUBTITLE_TEXT_1_20, this.packetIds.clientboundSetSubtitleTextPacket()); - registerByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SET_ACTIONBAR_TEXT_1_20_3 : PacketConsumers.SET_ACTIONBAR_TEXT_1_20, this.packetIds.clientboundSetActionBarTextPacket()); - registerByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.BOSS_EVENT_1_20_3 : PacketConsumers.BOSS_EVENT_1_20, this.packetIds.clientboundBossEventPacket()); - registerByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SYSTEM_CHAT_1_20_3 : PacketConsumers.SYSTEM_CHAT_1_20, this.packetIds.clientboundSystemChatPacket()); - registerByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.TAB_LIST_1_20_3 : PacketConsumers.TAB_LIST_1_20, this.packetIds.clientboundTabListPacket()); - registerByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.TEAM_1_20_3 : PacketConsumers.TEAM_1_20, this.packetIds.clientboundSetPlayerTeamPacket()); - registerByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SET_OBJECTIVE_1_20_3 : PacketConsumers.SET_OBJECTIVE_1_20, this.packetIds.clientboundSetObjectivePacket()); - registerByteBufPacketConsumer(PacketConsumers.SET_SCORE_1_20_3, VersionHelper.isOrAbove1_20_3() ? this.packetIds.clientboundSetScorePacket() : -1); - registerByteBufPacketConsumer(PacketConsumers.REMOVE_ENTITY, this.packetIds.clientboundRemoveEntitiesPacket()); - registerByteBufPacketConsumer(PacketConsumers.ADD_ENTITY_BYTEBUFFER, this.packetIds.clientboundAddEntityPacket()); - registerByteBufPacketConsumer(PacketConsumers.SOUND, this.packetIds.clientboundSoundPacket()); - registerByteBufPacketConsumer(PacketConsumers.SET_ENTITY_DATA, this.packetIds.clientboundSetEntityDataPacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.LEVEL_CHUNK_WITH_LIGHT, this.packetIds.clientboundLevelChunkWithLightPacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.SECTION_BLOCK_UPDATE, this.packetIds.clientboundSectionBlocksUpdatePacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.BLOCK_UPDATE, this.packetIds.clientboundBlockUpdatePacket()); + registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_21_4() ? PacketConsumers.LEVEL_PARTICLE_1_21_4 : (VersionHelper.isOrAbove1_20_5() ? PacketConsumers.LEVEL_PARTICLE_1_20_5 : PacketConsumers.LEVEL_PARTICLE_1_20), this.packetIds.clientboundLevelParticlesPacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.LEVEL_EVENT, this.packetIds.clientboundLevelEventPacket()); + registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.OPEN_SCREEN_1_20_3 : PacketConsumers.OPEN_SCREEN_1_20, this.packetIds.clientboundOpenScreenPacket()); + registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SET_TITLE_TEXT_1_20_3 : PacketConsumers.SET_TITLE_TEXT_1_20, this.packetIds.clientboundSetTitleTextPacket()); + registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SET_SUBTITLE_TEXT_1_20_3 : PacketConsumers.SET_SUBTITLE_TEXT_1_20, this.packetIds.clientboundSetSubtitleTextPacket()); + registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SET_ACTIONBAR_TEXT_1_20_3 : PacketConsumers.SET_ACTIONBAR_TEXT_1_20, this.packetIds.clientboundSetActionBarTextPacket()); + registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.BOSS_EVENT_1_20_3 : PacketConsumers.BOSS_EVENT_1_20, this.packetIds.clientboundBossEventPacket()); + registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SYSTEM_CHAT_1_20_3 : PacketConsumers.SYSTEM_CHAT_1_20, this.packetIds.clientboundSystemChatPacket()); + registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.TAB_LIST_1_20_3 : PacketConsumers.TAB_LIST_1_20, this.packetIds.clientboundTabListPacket()); + registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.TEAM_1_20_3 : PacketConsumers.TEAM_1_20, this.packetIds.clientboundSetPlayerTeamPacket()); + registerS2CByteBufPacketConsumer(VersionHelper.isOrAbove1_20_3() ? PacketConsumers.SET_OBJECTIVE_1_20_3 : PacketConsumers.SET_OBJECTIVE_1_20, this.packetIds.clientboundSetObjectivePacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.SET_SCORE_1_20_3, VersionHelper.isOrAbove1_20_3() ? this.packetIds.clientboundSetScorePacket() : -1); + registerS2CByteBufPacketConsumer(PacketConsumers.REMOVE_ENTITY, this.packetIds.clientboundRemoveEntitiesPacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.ADD_ENTITY_BYTEBUFFER, this.packetIds.clientboundAddEntityPacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.SOUND, this.packetIds.clientboundSoundPacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.SET_ENTITY_DATA, this.packetIds.clientboundSetEntityDataPacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.CONTAINER_SET_CONTENT, this.packetIds.clientboundContainerSetContentPacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.CONTAINER_SET_SLOT, this.packetIds.clientboundContainerSetSlotPacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.SET_CURSOR_ITEM, this.packetIds.clientboundSetCursorItemPacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.SET_EQUIPMENT, this.packetIds.clientboundSetEquipmentPacket()); + registerS2CByteBufPacketConsumer(PacketConsumers.SET_PLAYER_INVENTORY_1_21_2, this.packetIds.clientboundSetPlayerInventoryPacket()); + registerC2SByteBufPacketConsumer(PacketConsumers.SET_CREATIVE_MODE_SLOT, this.packetIds.serverboundSetCreativeModeSlotPacket()); + registerC2SByteBufPacketConsumer(PacketConsumers.CONTAINER_CLICK_1_20, this.packetIds.serverboundContainerClickPacket()); } public static BukkitNetworkManager instance() { @@ -191,15 +220,26 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } } - @EventHandler(priority = EventPriority.HIGHEST) + @EventHandler(priority = EventPriority.LOWEST) public void onPlayerQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); - Channel channel = getChannel(player); - NetWorkUser user = removeUser(channel); - if (user == null) return; - handleDisconnection(channel); - this.onlineUsers.remove(player.getUniqueId()); - this.resetUserArray(); + BukkitServerPlayer serverPlayer = this.onlineUsers.remove(player.getUniqueId()); + if (serverPlayer != null) { + this.resetUserArray(); + this.saveCooldown(player, serverPlayer.cooldown()); + } + } + + private void saveCooldown(Player player, CooldownData cd) { + if (cd != null && player != null) { + try { + byte[] data = CooldownData.toBytes(cd); + player.getPersistentDataContainer().set(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY), PersistentDataType.BYTE_ARRAY, data); + } catch (IOException e) { + player.getPersistentDataContainer().remove(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY)); + this.plugin.logger().warn("Failed to save cooldown for player " + player.getName(), e); + } + } } private void resetUserArray() { @@ -445,7 +485,9 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes String encoderName = pipeline.names().contains("outbound_config") ? "outbound_config" : "encoder"; pipeline.addBefore(encoderName, PACKET_ENCODER, new PluginChannelEncoder(user)); - channel.closeFuture().addListener((ChannelFutureListener) future -> handleDisconnection(user.nettyChannel())); + channel.closeFuture().addListener((ChannelFutureListener) future -> { + handleDisconnection(user.nettyChannel()); + }); setUser(channel, user); } @@ -523,17 +565,25 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } private boolean handleCompression(ChannelHandlerContext ctx, ByteBuf buffer) { - if (handledCompression) return false; + if (this.handledCompression) return false; int compressIndex = ctx.pipeline().names().indexOf("compress"); if (compressIndex == -1) return false; - handledCompression = true; + this.handledCompression = true; int encoderIndex = ctx.pipeline().names().indexOf(PACKET_ENCODER); if (encoderIndex == -1) return false; if (compressIndex > encoderIndex) { decompress(ctx, buffer, buffer); + PluginChannelDecoder decoder = (PluginChannelDecoder) ctx.pipeline().get(PACKET_DECODER); + if (decoder != null) { + if (decoder.relocated) return true; + decoder.relocated = true; + } PluginChannelEncoder encoder = (PluginChannelEncoder) ctx.pipeline().remove(PACKET_ENCODER); String encoderName = ctx.pipeline().names().contains("outbound_config") ? "outbound_config" : "encoder"; ctx.pipeline().addBefore(encoderName, PACKET_ENCODER, new PluginChannelEncoder(encoder)); + decoder = (PluginChannelDecoder) ctx.pipeline().remove(PACKET_DECODER); + String decoderName = ctx.pipeline().names().contains("inbound_config") ? "inbound_config" : "decoder"; + ctx.pipeline().addBefore(decoderName, PACKET_DECODER, new PluginChannelDecoder(decoder)); return true; } return false; @@ -548,30 +598,66 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes int preProcessIndex = buf.readerIndex(); int packetId = buf.readVarInt(); int preIndex = buf.readerIndex(); - ByteBufPacketEvent event = new ByteBufPacketEvent(packetId, buf, preIndex); - BukkitNetworkManager.this.handleByteBufPacket(this.player, event); - if (event.isCancelled()) { - buf.clear(); - } else if (!event.changed()) { + try { + ByteBufPacketEvent event = new ByteBufPacketEvent(packetId, buf, preIndex); + BukkitNetworkManager.this.handleS2CByteBufPacket(this.player, event); + if (event.isCancelled()) { + buf.clear(); + } else if (!event.changed()) { + buf.readerIndex(preProcessIndex); + } + } catch (Throwable e) { + CraftEngine.instance().logger().warn("An error occurred when writing packet " + packetId, e); buf.readerIndex(preProcessIndex); } } } } - public static class PluginChannelDecoder extends MessageToMessageDecoder { + public class PluginChannelDecoder extends MessageToMessageDecoder { private final NetWorkUser player; + public boolean relocated = false; public PluginChannelDecoder(NetWorkUser player) { this.player = player; } + public PluginChannelDecoder(PluginChannelDecoder decoder) { + this.player = decoder.player; + this.relocated = decoder.relocated; + } + @Override - protected void decode(ChannelHandlerContext context, ByteBuf byteBuf, List list) { + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) { + this.onByteBufReceive(byteBuf); if (byteBuf.isReadable()) { list.add(byteBuf.retain()); } } + + private void onByteBufReceive(ByteBuf buffer) { + // I don't care packets before PLAY for the moment + if (player.decoderState() != ConnectionState.PLAY) return; + int size = buffer.readableBytes(); + if (size != 0) { + FriendlyByteBuf buf = new FriendlyByteBuf(buffer); + int preProcessIndex = buf.readerIndex(); + int packetId = buf.readVarInt(); + int preIndex = buf.readerIndex(); + try { + ByteBufPacketEvent event = new ByteBufPacketEvent(packetId, buf, preIndex); + BukkitNetworkManager.this.handleC2SByteBufPacket(this.player, event); + if (event.isCancelled()) { + buf.clear(); + } else if (!event.changed()) { + buf.readerIndex(preProcessIndex); + } + } catch (Throwable e) { + CraftEngine.instance().logger().warn("An error occurred when reading packet " + packetId, e); + buf.readerIndex(preProcessIndex); + } + } + } } private void onNMSPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { @@ -594,9 +680,15 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes .ifPresent(function -> function.accept(user, event, packet)); } - protected void handleByteBufPacket(NetWorkUser user, ByteBufPacketEvent event) { + protected void handleS2CByteBufPacket(NetWorkUser user, ByteBufPacketEvent event) { int packetID = event.packetID(); - Optional.ofNullable(BYTE_BUFFER_PACKET_HANDLERS.get(packetID)) + Optional.ofNullable(S2C_BYTE_BUFFER_PACKET_HANDLERS[packetID]) + .ifPresent(function -> function.accept(user, event)); + } + + protected void handleC2SByteBufPacket(NetWorkUser user, ByteBufPacketEvent event) { + int packetID = event.packetID(); + Optional.ofNullable(C2S_BYTE_BUFFER_PACKET_HANDLERS[packetID]) .ifPresent(function -> function.accept(user, event)); } @@ -642,4 +734,14 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } return output; } + + @FunctionalInterface + public interface Handlers extends BiConsumer { + Handlers DO_NOTHING = doNothing(); + + static Handlers doNothing() { + return (user, byteBufPacketEvent) -> { + }; + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index abd76f731..31acebdf2 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -1,8 +1,11 @@ package net.momirealms.craftengine.bukkit.plugin.network; +import com.google.common.collect.Lists; import com.mojang.datafixers.util.Either; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntList; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TranslationArgument; @@ -10,15 +13,19 @@ import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture; import net.momirealms.craftengine.bukkit.api.event.FurnitureBreakEvent; import net.momirealms.craftengine.bukkit.api.event.FurnitureInteractEvent; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurniture; import net.momirealms.craftengine.bukkit.entity.furniture.BukkitFurnitureManager; -import net.momirealms.craftengine.bukkit.entity.furniture.LoadedFurniture; import net.momirealms.craftengine.bukkit.entity.projectile.BukkitProjectileManager; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.item.behavior.FurnitureItemBehavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.pack.BukkitPackManager; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.injector.BukkitInjector; import net.momirealms.craftengine.bukkit.plugin.network.handler.*; +import net.momirealms.craftengine.bukkit.plugin.network.payload.DiscardedPayload; +import net.momirealms.craftengine.bukkit.plugin.network.payload.NetWorkDataTypes; +import net.momirealms.craftengine.bukkit.plugin.network.payload.Payload; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.core.block.ImmutableBlockState; @@ -27,6 +34,7 @@ import net.momirealms.craftengine.core.font.FontManager; import net.momirealms.craftengine.core.font.IllegalCharacterProcessResult; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; @@ -35,11 +43,14 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; import net.momirealms.craftengine.core.plugin.network.*; import net.momirealms.craftengine.core.util.*; -import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.BlockHitResult; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.EntityHitResult; +import net.momirealms.craftengine.core.world.WorldEvents; import net.momirealms.craftengine.core.world.chunk.Palette; import net.momirealms.craftengine.core.world.chunk.PalettedContainer; import net.momirealms.craftengine.core.world.chunk.packet.BlockEntityData; @@ -47,7 +58,6 @@ import net.momirealms.craftengine.core.world.chunk.packet.MCSection; import net.momirealms.craftengine.core.world.collision.AABB; import net.momirealms.sparrow.nbt.Tag; import org.bukkit.*; -import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; @@ -63,12 +73,73 @@ import java.util.*; import java.util.function.BiConsumer; public class PacketConsumers { + private static BukkitNetworkManager.Handlers[] ADD_ENTITY_HANDLERS; private static int[] mappings; private static int[] mappingsMOD; private static IntIdentityList BLOCK_LIST; private static IntIdentityList BIOME_LIST; - public static void init(Map map, int registrySize) { + public static void initEntities(int registrySize) { + ADD_ENTITY_HANDLERS = new BukkitNetworkManager.Handlers[registrySize]; + Arrays.fill(ADD_ENTITY_HANDLERS, BukkitNetworkManager.Handlers.DO_NOTHING); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$FALLING_BLOCK$registryId] = (user, event) -> { + FriendlyByteBuf buf = event.getBuffer(); + int id = buf.readVarInt(); + UUID uuid = buf.readUUID(); + int type = buf.readVarInt(); + double x = buf.readDouble(); + double y = buf.readDouble(); + double z = buf.readDouble(); + byte xRot = buf.readByte(); + byte yRot = buf.readByte(); + byte yHeadRot = buf.readByte(); + int data = buf.readVarInt(); + // Falling blocks + int remapped = remap(data); + if (remapped != data) { + int xa = buf.readShort(); + int ya = buf.readShort(); + int za = buf.readShort(); + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(id); + buf.writeUUID(uuid); + buf.writeVarInt(type); + buf.writeDouble(x); + buf.writeDouble(y); + buf.writeDouble(z); + buf.writeByte(xRot); + buf.writeByte(yRot); + buf.writeByte(yHeadRot); + buf.writeVarInt(remapped); + buf.writeShort(xa); + buf.writeShort(ya); + buf.writeShort(za); + } + }; + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$BLOCK_DISPLAY$registryId] = simpleAddEntityHandler(BlockDisplayPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$TEXT_DISPLAY$registryId] = simpleAddEntityHandler(TextDisplayPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$ARMOR_STAND$registryId] = simpleAddEntityHandler(ArmorStandPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$ITEM_DISPLAY$registryId] = simpleAddEntityHandler(ItemDisplayPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$FIREBALL$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$EYE_OF_ENDER$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$FIREWORK_ROCKET$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$ITEM$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$ITEM_FRAME$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$GLOW_ITEM_FRAME$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$SMALL_FIREBALL$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$EGG$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$ENDER_PEARL$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$EXPERIENCE_BOTTLE$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$SNOWBALL$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$POTION$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + if (VersionHelper.isOrAbove1_20_5()) { + ADD_ENTITY_HANDLERS[Reflections.instance$EntityType$OMINOUS_ITEM_SPAWNER$registryId] = simpleAddEntityHandler(CommonItemPacketHandler.INSTANCE); + } + } + + public static void initBlocks(Map map, int registrySize) { mappings = new int[registrySize]; for (int i = 0; i < registrySize; i++) { mappings[i] = i; @@ -328,23 +399,12 @@ public class PacketConsumers { FriendlyByteBuf buf = event.getBuffer(); String name = buf.readUtf(); byte method = buf.readByte(); - if (method != 2 && method != 0) { - return; - } + if (method != 2 && method != 0) return; Tag displayName = buf.readNbt(false); if (displayName == null) return; byte friendlyFlags = buf.readByte(); - - Either eitherVisibility; - Either eitherCollisionRule; - - if (VersionHelper.isOrAbove1_21_5()) { - eitherVisibility = Either.right(buf.readVarInt()); - eitherCollisionRule = Either.right(buf.readVarInt()); - } else { - eitherVisibility = Either.left(buf.readUtf(40)); - eitherCollisionRule = Either.left(buf.readUtf(40)); - } + Either eitherVisibility = VersionHelper.isOrAbove1_21_5() ? Either.right(buf.readVarInt()) : Either.left(buf.readUtf(40)); + Either eitherCollisionRule = VersionHelper.isOrAbove1_21_5() ? Either.right(buf.readVarInt()) : Either.left(buf.readUtf(40)); int color = buf.readVarInt(); Tag prefix = buf.readNbt(false); if (prefix == null) return; @@ -355,55 +415,19 @@ public class PacketConsumers { Map tokens2 = CraftEngine.instance().fontManager().matchTags(prefix.getAsString()); Map tokens3 = CraftEngine.instance().fontManager().matchTags(suffix.getAsString()); if (tokens1.isEmpty() && tokens2.isEmpty() && tokens3.isEmpty()) return; + List entities = method == 0 ? buf.readStringList() : null; event.setChanged(true); - - List entities; - if (method == 0) { - entities = buf.readStringList(); - } else { - entities = null; - } - buf.clear(); buf.writeVarInt(event.packetID()); buf.writeUtf(name); buf.writeByte(method); - - if (!tokens1.isEmpty()) { - Component component = AdventureHelper.tagToComponent(displayName); - for (Map.Entry token : tokens1.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - buf.writeNbt(AdventureHelper.componentToTag(component), false); - } else { - buf.writeNbt(displayName, false); - } - + buf.writeNbt(tokens1.isEmpty() ? displayName : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens1)), false); buf.writeByte(friendlyFlags); eitherVisibility.ifLeft(buf::writeUtf).ifRight(buf::writeVarInt); eitherCollisionRule.ifLeft(buf::writeUtf).ifRight(buf::writeVarInt); buf.writeVarInt(color); - - if (!tokens2.isEmpty()) { - Component component = AdventureHelper.tagToComponent(prefix); - for (Map.Entry token : tokens2.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - buf.writeNbt(AdventureHelper.componentToTag(component), false); - } else { - buf.writeNbt(prefix, false); - } - - if (!tokens3.isEmpty()) { - Component component = AdventureHelper.tagToComponent(suffix); - for (Map.Entry token : tokens3.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - buf.writeNbt(AdventureHelper.componentToTag(component), false); - } else { - buf.writeNbt(suffix, false); - } - + buf.writeNbt(tokens2.isEmpty() ? prefix : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(prefix), tokens2)), false); + buf.writeNbt(tokens3.isEmpty() ? suffix : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(suffix), tokens3)), false); if (entities != null) { buf.writeStringList(entities); } @@ -421,8 +445,7 @@ public class PacketConsumers { return; } EnumSet> enums = FastNMS.INSTANCE.field$ClientboundPlayerInfoUpdatePacket$actions(packet); - outer: - { + outer: { for (Object entry : enums) { if (entry == Reflections.instance$ClientboundPlayerInfoUpdatePacket$Action$UPDATE_DISPLAY_NAME) { break outer; @@ -430,28 +453,23 @@ public class PacketConsumers { } return; } - boolean isChanged = false; List newEntries = new MarkedArrayList<>(); for (Object entry : entries) { Object mcComponent = FastNMS.INSTANCE.field$ClientboundPlayerInfoUpdatePacket$Entry$displayName(entry); if (mcComponent == null) { newEntries.add(entry); - continue; + } else { + String json = ComponentUtils.minecraftToJson(mcComponent); + Map tokens = CraftEngine.instance().fontManager().matchTags(json); + if (tokens.isEmpty()) { + newEntries.add(entry); + } else { + Object newEntry = FastNMS.INSTANCE.constructor$ClientboundPlayerInfoUpdatePacket$Entry(entry, ComponentUtils.adventureToMinecraft(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens))); + newEntries.add(newEntry); + isChanged = true; + } } - String json = ComponentUtils.minecraftToJson(mcComponent); - Map tokens = CraftEngine.instance().fontManager().matchTags(json); - if (tokens.isEmpty()) { - newEntries.add(entry); - continue; - } - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - Object newEntry = FastNMS.INSTANCE.constructor$ClientboundPlayerInfoUpdatePacket$Entry(entry, ComponentUtils.adventureToMinecraft(component)); - newEntries.add(newEntry); - isChanged = true; } if (isChanged) { event.replacePacket(FastNMS.INSTANCE.constructor$ClientboundPlayerInfoUpdatePacket(enums, newEntries)); @@ -467,9 +485,8 @@ public class PacketConsumers { FriendlyByteBuf buf = event.getBuffer(); String name = buf.readUtf(); byte method = buf.readByte(); - if (method != 2 && method != 0) { + if (method != 2 && method != 0) return; - } String displayName = buf.readUtf(); byte friendlyFlags = buf.readByte(); String nameTagVisibility = buf.readUtf(40); @@ -484,53 +501,18 @@ public class PacketConsumers { if (tokens1.isEmpty() && tokens2.isEmpty() && tokens3.isEmpty()) return; event.setChanged(true); - List entities; - if (method == 0) { - entities = buf.readStringList(); - } else { - entities = null; - } - + List entities = method == 0 ? buf.readStringList() : null; buf.clear(); buf.writeVarInt(event.packetID()); buf.writeUtf(name); buf.writeByte(method); - - if (!tokens1.isEmpty()) { - Component component = AdventureHelper.jsonToComponent(displayName); - for (Map.Entry token : tokens1.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - buf.writeUtf(AdventureHelper.componentToJson(component)); - } else { - buf.writeUtf(displayName); - } - + buf.writeUtf(tokens1.isEmpty() ? displayName : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(displayName), tokens1))); buf.writeByte(friendlyFlags); buf.writeUtf(nameTagVisibility); buf.writeUtf(collisionRule); buf.writeVarInt(color); - - if (!tokens2.isEmpty()) { - Component component = AdventureHelper.jsonToComponent(prefix); - for (Map.Entry token : tokens2.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - buf.writeUtf(AdventureHelper.componentToJson(component)); - } else { - buf.writeUtf(prefix); - } - - if (!tokens3.isEmpty()) { - Component component = AdventureHelper.jsonToComponent(suffix); - for (Map.Entry token : tokens3.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - buf.writeUtf(AdventureHelper.componentToJson(component)); - } else { - buf.writeUtf(suffix); - } - + buf.writeUtf(tokens2.isEmpty() ? prefix : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(prefix), tokens2))); + buf.writeUtf(tokens3.isEmpty() ? suffix : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(suffix), tokens3))); if (entities != null) { buf.writeStringList(entities); } @@ -549,10 +531,6 @@ public class PacketConsumers { String json = buf.readUtf(); Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) return; - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } float health = buf.readFloat(); int color = buf.readVarInt(); int division = buf.readVarInt(); @@ -562,7 +540,7 @@ public class PacketConsumers { buf.writeVarInt(event.packetID()); buf.writeUUID(uuid); buf.writeVarInt(actionType); - buf.writeUtf(AdventureHelper.componentToJson(component)); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens))); buf.writeFloat(health); buf.writeVarInt(color); buf.writeVarInt(division); @@ -572,15 +550,11 @@ public class PacketConsumers { Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) return; event.setChanged(true); - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); buf.writeUUID(uuid); buf.writeVarInt(actionType); - buf.writeUtf(AdventureHelper.componentToJson(component)); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens))); } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundBossEventPacket", e); @@ -598,10 +572,6 @@ public class PacketConsumers { if (nbt == null) return; Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); if (tokens.isEmpty()) return; - Component component = AdventureHelper.tagToComponent(nbt); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } float health = buf.readFloat(); int color = buf.readVarInt(); int division = buf.readVarInt(); @@ -611,7 +581,7 @@ public class PacketConsumers { buf.writeVarInt(event.packetID()); buf.writeUUID(uuid); buf.writeVarInt(actionType); - buf.writeNbt(AdventureHelper.componentToTag(component), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens)), false); buf.writeFloat(health); buf.writeVarInt(color); buf.writeVarInt(division); @@ -622,15 +592,11 @@ public class PacketConsumers { Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); if (tokens.isEmpty()) return; event.setChanged(true); - Component component = AdventureHelper.tagToComponent(nbt); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); buf.writeUUID(uuid); buf.writeVarInt(actionType); - buf.writeNbt(AdventureHelper.componentToTag(component), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens)), false); } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundBossEventPacket", e); @@ -649,15 +615,11 @@ public class PacketConsumers { Map tokens = CraftEngine.instance().fontManager().matchTags(displayName); if (tokens.isEmpty()) return; event.setChanged(true); - Component component = AdventureHelper.jsonToComponent(displayName); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); buf.writeUtf(objective); buf.writeByte(mode); - buf.writeUtf(AdventureHelper.componentToJson(component)); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(displayName), tokens))); buf.writeVarInt(renderType); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundSetObjectivePacket", e); @@ -681,15 +643,11 @@ public class PacketConsumers { Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); if (tokens.isEmpty()) return; event.setChanged(true); - Component component = AdventureHelper.tagToComponent(displayName); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); buf.writeUtf(objective); buf.writeByte(mode); - buf.writeNbt(AdventureHelper.componentToTag(component), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens)), false); buf.writeVarInt(renderType); buf.writeBoolean(true); buf.writeVarInt(0); @@ -698,15 +656,11 @@ public class PacketConsumers { if (tokens.isEmpty()) return; Tag style = buf.readNbt(false); event.setChanged(true); - Component component = AdventureHelper.tagToComponent(displayName); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); buf.writeUtf(objective); buf.writeByte(mode); - buf.writeNbt(AdventureHelper.componentToTag(component), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens)), false); buf.writeVarInt(renderType); buf.writeBoolean(true); buf.writeVarInt(1); @@ -722,41 +676,21 @@ public class PacketConsumers { buf.writeVarInt(event.packetID()); buf.writeUtf(objective); buf.writeByte(mode); - if (!tokens1.isEmpty()) { - Component component = AdventureHelper.tagToComponent(displayName); - for (Map.Entry token : tokens1.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - buf.writeNbt(AdventureHelper.componentToTag(component), false); - } else { - buf.writeNbt(displayName, false); - } + buf.writeNbt(tokens1.isEmpty() ? displayName : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens1)), false); buf.writeVarInt(renderType); buf.writeBoolean(true); buf.writeVarInt(2); - if (!tokens2.isEmpty()) { - Component component = AdventureHelper.tagToComponent(fixed); - for (Map.Entry token : tokens2.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - buf.writeNbt(AdventureHelper.componentToTag(component), false); - } else { - buf.writeNbt(fixed, false); - } + buf.writeNbt(tokens2.isEmpty() ? fixed : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(fixed), tokens2)), false); } } else { Map tokens = CraftEngine.instance().fontManager().matchTags(displayName.getAsString()); if (tokens.isEmpty()) return; event.setChanged(true); - Component component = AdventureHelper.tagToComponent(displayName); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); buf.writeUtf(objective); buf.writeByte(mode); - buf.writeNbt(AdventureHelper.componentToTag(component), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(displayName), tokens)), false); buf.writeVarInt(renderType); buf.writeBoolean(false); } @@ -774,13 +708,9 @@ public class PacketConsumers { if (tokens.isEmpty()) return; boolean overlay = buf.readBoolean(); event.setChanged(true); - Component component = AdventureHelper.jsonToComponent(jsonOrPlainString); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); - buf.writeUtf(AdventureHelper.componentToJson(component)); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(jsonOrPlainString), tokens))); buf.writeBoolean(overlay); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundSystemChatPacket", e); @@ -797,13 +727,9 @@ public class PacketConsumers { if (tokens.isEmpty()) return; boolean overlay = buf.readBoolean(); event.setChanged(true); - Component component = AdventureHelper.tagToComponent(nbt); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); - buf.writeNbt(AdventureHelper.componentToTag(component), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens)), false); buf.writeBoolean(overlay); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundSystemChatPacket", e); @@ -818,13 +744,9 @@ public class PacketConsumers { Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) return; event.setChanged(true); - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); - buf.writeUtf(AdventureHelper.componentToJson(component)); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens))); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundSetSubtitleTextPacket", e); } @@ -839,13 +761,9 @@ public class PacketConsumers { Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); if (tokens.isEmpty()) return; event.setChanged(true); - Component component = AdventureHelper.tagToComponent(nbt); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); - buf.writeNbt(AdventureHelper.componentToTag(component), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens)), false); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundSetSubtitleTextPacket", e); } @@ -859,13 +777,9 @@ public class PacketConsumers { Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) return; event.setChanged(true); - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); - buf.writeUtf(AdventureHelper.componentToJson(component)); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens))); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundSetTitleTextPacket", e); } @@ -880,13 +794,9 @@ public class PacketConsumers { Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); if (tokens.isEmpty()) return; event.setChanged(true); - Component component = AdventureHelper.tagToComponent(nbt); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); - buf.writeNbt(AdventureHelper.componentToTag(component), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens)), false); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundSetTitleTextPacket", e); } @@ -900,13 +810,9 @@ public class PacketConsumers { Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) return; event.setChanged(true); - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); - buf.writeUtf(AdventureHelper.componentToJson(component)); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens))); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundSetActionBarTextPacket", e); } @@ -921,13 +827,9 @@ public class PacketConsumers { Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); if (tokens.isEmpty()) return; event.setChanged(true); - Component component = AdventureHelper.tagToComponent(nbt); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); - buf.writeNbt(AdventureHelper.componentToTag(component), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens)), false); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundSetActionBarTextPacket", e); } @@ -945,26 +847,10 @@ public class PacketConsumers { event.setChanged(true); buf.clear(); buf.writeVarInt(event.packetID()); - if (!tokens1.isEmpty()) { - Component component = AdventureHelper.jsonToComponent(json1); - for (Map.Entry token : tokens1.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - buf.writeUtf(AdventureHelper.componentToJson(component)); - } else { - buf.writeUtf(json1); - } - if (!tokens2.isEmpty()) { - Component component = AdventureHelper.jsonToComponent(json2); - for (Map.Entry token : tokens2.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - buf.writeUtf(AdventureHelper.componentToJson(component)); - } else { - buf.writeUtf(json2); - } + buf.writeUtf(tokens1.isEmpty() ? json1 : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json1), tokens1))); + buf.writeUtf(tokens2.isEmpty() ? json2 : AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json2), tokens2))); } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSet[(Sub)Title/ActionBar]TextPacket", e); + CraftEngine.instance().logger().warn("Failed to handle ClientboundTabListPacket", e); } }; @@ -982,26 +868,10 @@ public class PacketConsumers { event.setChanged(true); buf.clear(); buf.writeVarInt(event.packetID()); - if (!tokens1.isEmpty()) { - Component component = AdventureHelper.tagToComponent(nbt1); - for (Map.Entry token : tokens1.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - buf.writeNbt(AdventureHelper.componentToTag(component), false); - } else { - buf.writeNbt(nbt1, false); - } - if (!tokens2.isEmpty()) { - Component component = AdventureHelper.tagToComponent(nbt2); - for (Map.Entry token : tokens2.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } - buf.writeNbt(AdventureHelper.componentToTag(component), false); - } else { - buf.writeNbt(nbt2, false); - } + buf.writeNbt(tokens1.isEmpty() ? nbt1 : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt1), tokens1)), false); + buf.writeNbt(tokens2.isEmpty() ? nbt2 : AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt2), tokens2)), false); } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to handle ClientboundSet[(Sub)Title/ActionBar]TextPacket", e); + CraftEngine.instance().logger().warn("Failed to handle ClientboundTabListPacket", e); } }; @@ -1015,15 +885,11 @@ public class PacketConsumers { Map tokens = CraftEngine.instance().fontManager().matchTags(json); if (tokens.isEmpty()) return; event.setChanged(true); - Component component = AdventureHelper.jsonToComponent(json); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); buf.writeVarInt(containerId); buf.writeVarInt(type); - buf.writeUtf(AdventureHelper.componentToJson(component)); + buf.writeUtf(AdventureHelper.componentToJson(AdventureHelper.replaceText(AdventureHelper.jsonToComponent(json), tokens))); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundOpenScreenPacket", e); } @@ -1039,15 +905,11 @@ public class PacketConsumers { if (nbt == null) return; Map tokens = CraftEngine.instance().fontManager().matchTags(nbt.getAsString()); if (tokens.isEmpty()) return; - Component component = AdventureHelper.tagToComponent(nbt); - for (Map.Entry token : tokens.entrySet()) { - component = component.replaceText(b -> b.matchLiteral(token.getKey()).replacement(token.getValue())); - } buf.clear(); buf.writeVarInt(event.packetID()); buf.writeVarInt(containerId); buf.writeVarInt(type); - buf.writeNbt(AdventureHelper.componentToTag(component), false); + buf.writeNbt(AdventureHelper.componentToTag(AdventureHelper.replaceText(AdventureHelper.tagToComponent(nbt), tokens)), false); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundOpenScreenPacket", e); } @@ -1198,7 +1060,7 @@ public class PacketConsumers { } }; - private static void handlePlayerActionPacketOnMainThread(BukkitServerPlayer player, World world, BlockPos pos, Object packet) throws Exception { + private static void handlePlayerActionPacketOnMainThread(BukkitServerPlayer player, World world, BlockPos pos, Object packet) { Object action = FastNMS.INSTANCE.field$ServerboundPlayerActionPacket$action(packet); if (action == Reflections.instance$ServerboundPlayerActionPacket$Action$START_DESTROY_BLOCK) { Object serverLevel = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(world); @@ -1207,7 +1069,7 @@ public class PacketConsumers { // not a custom block if (BlockStateUtils.isVanillaBlock(stateId)) { if (Config.enableSoundSystem()) { - Object blockOwner = Reflections.field$StateHolder$owner.get(blockState); + Object blockOwner = FastNMS.INSTANCE.method$BlockState$getBlock(blockState); if (BukkitBlockManager.instance().isBlockSoundRemoved(blockOwner)) { player.startMiningBlock(pos, blockState, null); return; @@ -1489,7 +1351,7 @@ public class PacketConsumers { public static final TriConsumer PICK_ITEM_FROM_ENTITY = (user, event, packet) -> { try { int entityId = (int) Reflections.field$ServerboundPickItemFromEntityPacket$id.get(packet); - LoadedFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId); + BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId); if (furniture == null) return; Player player = (Player) user.platformPlayer(); if (player == null) return; @@ -1518,7 +1380,7 @@ public class PacketConsumers { } }; - private static void handlePickItemFromEntityOnMainThread(Player player, LoadedFurniture furniture) throws Exception { + private static void handlePickItemFromEntityOnMainThread(Player player, BukkitFurniture furniture) throws Exception { Key itemId = furniture.config().settings().itemId(); if (itemId == null) return; pickItem(player, itemId, null, FastNMS.INSTANCE.method$CraftEntity$getHandle(furniture.baseEntity())); @@ -1544,47 +1406,10 @@ public class PacketConsumers { public static final BiConsumer ADD_ENTITY_BYTEBUFFER = (user, event) -> { try { FriendlyByteBuf buf = event.getBuffer(); - int id = buf.readVarInt(); - UUID uuid = buf.readUUID(); + buf.readVarInt(); + buf.readUUID(); int type = buf.readVarInt(); - double x = buf.readDouble(); - double y = buf.readDouble(); - double z = buf.readDouble(); - byte xRot = buf.readByte(); - byte yRot = buf.readByte(); - byte yHeadRot = buf.readByte(); - int data = buf.readVarInt(); - int xa = buf.readShort(); - int ya = buf.readShort(); - int za = buf.readShort(); - // Falling blocks - if (type == Reflections.instance$EntityType$FALLING_BLOCK$registryId) { - int remapped = remap(data); - if (remapped != data) { - event.setChanged(true); - buf.clear(); - buf.writeVarInt(event.packetID()); - buf.writeVarInt(id); - buf.writeUUID(uuid); - buf.writeVarInt(type); - buf.writeDouble(x); - buf.writeDouble(y); - buf.writeDouble(z); - buf.writeByte(xRot); - buf.writeByte(yRot); - buf.writeByte(yHeadRot); - buf.writeVarInt(remapped); - buf.writeShort(xa); - buf.writeShort(ya); - buf.writeShort(za); - } - } else if (type == Reflections.instance$EntityType$BLOCK_DISPLAY$registryId) { - user.entityPacketHandlers().put(id, BlockDisplayPacketHandler.INSTANCE); - } else if (type == Reflections.instance$EntityType$TEXT_DISPLAY$registryId) { - user.entityPacketHandlers().put(id, TextDisplayPacketHandler.INSTANCE); - } else if (type == Reflections.instance$EntityType$ARMOR_STAND$registryId) { - user.entityPacketHandlers().put(id, ArmorStandPacketHandler.INSTANCE); - } + ADD_ENTITY_HANDLERS[type].accept(user, event); } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to handle ClientboundAddEntityPacket", e); } @@ -1596,7 +1421,7 @@ public class PacketConsumers { int entityId = FastNMS.INSTANCE.field$ClientboundAddEntityPacket$entityId(packet); if (entityType == Reflections.instance$EntityType$ITEM_DISPLAY) { // Furniture - LoadedFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entityId); + BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entityId); if (furniture != null) { user.entityPacketHandlers().computeIfAbsent(entityId, k -> new FurniturePacketHandler(furniture.fakeEntityIds())); user.sendPacket(furniture.spawnPacket((Player) user.platformPlayer()), false); @@ -1606,7 +1431,7 @@ public class PacketConsumers { } } else if (entityType == BukkitFurnitureManager.NMS_COLLISION_ENTITY_TYPE) { // Cancel collider entity packet - LoadedFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entityId); + BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByRealEntityId(entityId); if (furniture != null) { event.setCancelled(true); user.entityPacketHandlers().put(entityId, FurnitureCollisionPacketHandler.INSTANCE); @@ -1670,7 +1495,7 @@ public class PacketConsumers { } else { entityId = FastNMS.INSTANCE.field$ServerboundInteractPacket$entityId(packet); } - LoadedFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId); + BukkitFurniture furniture = BukkitFurnitureManager.instance().loadedFurnitureByEntityId(entityId); if (furniture == null) return; Object action = Reflections.field$ServerboundInteractPacket$action.get(packet); Object actionType = Reflections.method$ServerboundInteractPacket$Action$getType.invoke(action); @@ -1694,7 +1519,7 @@ public class PacketConsumers { // execute functions PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() .withParameter(DirectContextParameters.FURNITURE, furniture) - .withParameter(DirectContextParameters.POSITION, new WorldPosition(furniture.world(), furniture.position())) + .withParameter(DirectContextParameters.POSITION, furniture.position()) ); furniture.config().execute(context, EventTrigger.LEFT_CLICK); furniture.config().execute(context, EventTrigger.BREAK); @@ -1724,9 +1549,9 @@ public class PacketConsumers { // execute functions PlayerOptionalContext context = PlayerOptionalContext.of(serverPlayer, ContextHolder.builder() .withParameter(DirectContextParameters.FURNITURE, furniture) - .withParameter(DirectContextParameters.POSITION, new WorldPosition(furniture.world(), furniture.position())) + .withParameter(DirectContextParameters.POSITION, furniture.position()) ); - furniture.config().execute(context, EventTrigger.RIGHT_CLICK);; + furniture.config().execute(context, EventTrigger.RIGHT_CLICK); if (player.isSneaking()) { // try placing another furniture above it @@ -1966,36 +1791,21 @@ public class PacketConsumers { try { if (!VersionHelper.isOrAbove1_20_5()) return; Object payload = Reflections.field$ServerboundCustomPayloadPacket$payload.get(packet); - if (payload.getClass().equals(Reflections.clazz$DiscardedPayload)) { - Object type = Reflections.method$CustomPacketPayload$type.invoke(payload); - Object id = Reflections.method$CustomPacketPayload$Type$id.invoke(type); - String channel = id.toString(); - if (!channel.equals(NetworkManager.MOD_CHANNEL)) return; - byte[] data; - if (Reflections.method$DiscardedPayload$data != null) { - ByteBuf buf = (ByteBuf) Reflections.method$DiscardedPayload$data.invoke(payload); - data = new byte[buf.readableBytes()]; - buf.readBytes(data); - } else { - data = (byte[]) Reflections.method$DiscardedPayload$dataByteArray.invoke(payload); - } - FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(data)); + if (Reflections.clazz$DiscardedPayload.isInstance(payload)) { + Payload discardedPayload = DiscardedPayload.from(payload); + if (discardedPayload == null || !discardedPayload.channel().equals(NetworkManager.MOD_CHANNEL_KEY)) + return; + FriendlyByteBuf buf = discardedPayload.toBuffer(); NetWorkDataTypes dataType = NetWorkDataTypes.readType(buf); if (dataType == NetWorkDataTypes.CLIENT_CUSTOM_BLOCK) { int clientBlockRegistrySize = dataType.as(Integer.class).decode(buf); int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize(); if (clientBlockRegistrySize != serverBlockRegistrySize) { - Object kickPacket = Reflections.constructor$ClientboundDisconnectPacket.newInstance( - ComponentUtils.adventureToMinecraft( - Component.translatable( - "disconnect.craftengine.block_registry_mismatch", - TranslationArgument.numeric(clientBlockRegistrySize), - TranslationArgument.numeric(serverBlockRegistrySize) - ) - ) - ); - user.nettyChannel().writeAndFlush(kickPacket); - user.nettyChannel().disconnect(); + user.kick(Component.translatable( + "disconnect.craftengine.block_registry_mismatch", + TranslationArgument.numeric(clientBlockRegistrySize), + TranslationArgument.numeric(serverBlockRegistrySize) + )); return; } user.setClientModState(true); @@ -2005,10 +1815,7 @@ public class PacketConsumers { FriendlyByteBuf bufPayload = new FriendlyByteBuf(Unpooled.buffer()); dataType.writeType(bufPayload); dataType.as(Boolean.class).encode(bufPayload, true); - Object channelKey = KeyUtils.toResourceLocation(Key.of(NetworkManager.MOD_CHANNEL)); - Object dataPayload = Reflections.constructor$DiscardedPayload.newInstance(channelKey, bufPayload.array()); - Object responsePacket = Reflections.constructor$ClientboundCustomPayloadPacket.newInstance(dataPayload); - user.nettyChannel().writeAndFlush(responsePacket); + user.sendCustomPayload(NetworkManager.MOD_CHANNEL_KEY, bufPayload.array()); } } } @@ -2145,6 +1952,230 @@ public class PacketConsumers { } }; + public static final BiConsumer CONTAINER_SET_CONTENT = (user, event) -> { + try { + FriendlyByteBuf buf = event.getBuffer(); + int containerId = buf.readContainerId(); + int stateId = buf.readVarInt(); + int listSize = buf.readVarInt(); + ItemBuildContext context = ItemBuildContext.of((BukkitServerPlayer) user); + List items = new ArrayList<>(listSize); + boolean changed = false; + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + for (int i = 0; i < listSize; i++) { + ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + Optional optional = BukkitItemManager.instance().s2c(itemStack, context); + if (optional.isPresent()) { + items.add(optional.get()); + changed = true; + } else { + items.add(itemStack); + } + } + ItemStack carriedItem = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + ItemStack newCarriedItem = carriedItem; + Optional optional = BukkitItemManager.instance().s2c(carriedItem, context); + if (optional.isPresent()) { + changed = true; + newCarriedItem = optional.get(); + } + if (!changed) return; + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeContainerId(containerId); + buf.writeVarInt(stateId); + buf.writeVarInt(listSize); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + for (ItemStack itemStack : items) { + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, itemStack); + } + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newCarriedItem); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ClientboundContainerSetContentPacket", e); + } + }; + + public static final BiConsumer CONTAINER_SET_SLOT = (user, event) -> { + try { + FriendlyByteBuf buf = event.getBuffer(); + ItemBuildContext context = ItemBuildContext.of((BukkitServerPlayer) user); + int containerId = buf.readContainerId(); + int stateId = buf.readVarInt(); + int slot = buf.readShort(); + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + BukkitItemManager.instance().s2c(itemStack, context).ifPresent((newItemStack) -> { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeContainerId(containerId); + buf.writeVarInt(stateId); + buf.writeShort(slot); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newItemStack); + }); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ClientboundContainerSetSlotPacket", e); + } + }; + + public static final BiConsumer SET_CURSOR_ITEM = (user, event) -> { + try { + FriendlyByteBuf buf = event.getBuffer(); + ItemBuildContext context = ItemBuildContext.of((BukkitServerPlayer) user); + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + BukkitItemManager.instance().s2c(itemStack, context).ifPresent((newItemStack) -> { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newItemStack); + }); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ClientboundSetCursorItemPacket", e); + } + }; + + public static final BiConsumer SET_EQUIPMENT = (user, event) -> { + try { + FriendlyByteBuf buf = event.getBuffer(); + boolean changed = false; + ItemBuildContext context = ItemBuildContext.of((BukkitServerPlayer) user); + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + int entity = buf.readVarInt(); + List> slots = Lists.newArrayList(); + int slotMask; + do { + slotMask = buf.readByte(); + Object equipmentSlot = Reflections.instance$EquipmentSlot$values[slotMask & 127]; + ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + Optional optional = BukkitItemManager.instance().s2c(itemStack, context); + if (optional.isPresent()) { + changed = true; + itemStack = optional.get(); + } + slots.add(com.mojang.datafixers.util.Pair.of(equipmentSlot, itemStack)); + } while ((slotMask & -128) != 0); + if (changed) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(entity); + int i = slots.size(); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + for (int j = 0; j < i; ++j) { + com.mojang.datafixers.util.Pair pair = slots.get(j); + Enum equipmentSlot = (Enum) pair.getFirst(); + boolean bl = j != i - 1; + int k = equipmentSlot.ordinal(); + buf.writeByte(bl ? k | -128 : k); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, pair.getSecond()); + } + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ClientboundSetEquipmentPacket", e); + } + }; + + public static final BiConsumer SET_PLAYER_INVENTORY_1_21_2 = (user, event) -> { + try { + FriendlyByteBuf buf = event.getBuffer(); + ItemBuildContext context = ItemBuildContext.of((BukkitServerPlayer) user); + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + int slot = buf.readVarInt(); + BukkitItemManager.instance().s2c(itemStack, context).ifPresent((newItemStack) -> { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(slot); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newItemStack); + }); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ClientboundSetPlayerInventoryPacket", e); + } + }; + + public static final BiConsumer SET_CREATIVE_MODE_SLOT = (user, event) -> { + try { + FriendlyByteBuf buf = event.getBuffer(); + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + short slotNum = buf.readShort(); + ItemStack itemStack = VersionHelper.isOrAbove1_20_5() ? + FastNMS.INSTANCE.method$FriendlyByteBuf$readUntrustedItem(friendlyBuf) : FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + ItemBuildContext context = ItemBuildContext.of((BukkitServerPlayer) user); + BukkitItemManager.instance().c2s(itemStack, context).ifPresent((newItemStack) -> { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeShort(slotNum); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + if (VersionHelper.isOrAbove1_20_5()) { + FastNMS.INSTANCE.method$FriendlyByteBuf$writeUntrustedItem(newFriendlyBuf, newItemStack); + } else { + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, newItemStack); + } + }); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundSetCreativeModeSlotPacket", e); + } + }; + + public static final BiConsumer CONTAINER_CLICK_1_20 = (user, event) -> { + try { + if (VersionHelper.isOrAbove1_21_5()) return; // 1.21.5+需要其他办法解决同步问题 + FriendlyByteBuf buf = event.getBuffer(); + boolean changed = false; + ItemBuildContext context = ItemBuildContext.of((BukkitServerPlayer) user); + Object friendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + int containerId = buf.readContainerId(); + int stateId = buf.readVarInt(); + short slotNum = buf.readShort(); + byte buttonNum = buf.readByte(); + int clickType = buf.readVarInt(); + int i = buf.readVarInt(); + Int2ObjectMap changedSlots = new Int2ObjectOpenHashMap<>(i); + for (int j = 0; j < i; ++j) { + int k = buf.readShort(); + ItemStack itemStack = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + Optional optional = BukkitItemManager.instance().c2s(itemStack, context); + if (optional.isPresent()) { + changed = true; + itemStack = optional.get(); + } + changedSlots.put(k, itemStack); + } + ItemStack carriedItem = FastNMS.INSTANCE.method$FriendlyByteBuf$readItem(friendlyBuf); + Optional optional = BukkitItemManager.instance().c2s(carriedItem, context); + if (optional.isPresent()) { + changed = true; + carriedItem = optional.get(); + } + if (changed) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeContainerId(containerId); + buf.writeVarInt(stateId); + buf.writeShort(slotNum); + buf.writeByte(buttonNum); + buf.writeVarInt(clickType); + buf.writeVarInt(changedSlots.size()); + Object newFriendlyBuf = FastNMS.INSTANCE.constructor$FriendlyByteBuf(buf); + changedSlots.forEach((k, v) -> { + buf.writeShort(k); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, v); + }); + FastNMS.INSTANCE.method$FriendlyByteBuf$writeItem(newFriendlyBuf, carriedItem); + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundContainerClickPacket", e); + } + }; + public static final TriConsumer RESOURCE_PACK_PUSH = (user, event, packet) -> { try { if (!VersionHelper.isOrAbove1_20_2()) return; @@ -2255,4 +2286,11 @@ public class PacketConsumers { CraftEngine.instance().logger().warn("Failed to handle ClientboundMoveEntityPacket$PosRot", e); } }; + + private static BukkitNetworkManager.Handlers simpleAddEntityHandler(EntityPacketHandler handler) { + return (user, event) -> { + FriendlyByteBuf buf = event.getBuffer(); + user.entityPacketHandlers().put(buf.readVarInt(), handler); + }; + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java index 3ac33c779..00d588a66 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketIds.java @@ -41,4 +41,18 @@ public interface PacketIds { int clientboundPlayerInfoUpdatePacket(); int clientboundSetScorePacket(); + + int clientboundContainerSetContentPacket(); + + int clientboundContainerSetSlotPacket(); + + int clientboundSetCursorItemPacket(); + + int clientboundSetEquipmentPacket(); + + int clientboundSetPlayerInventoryPacket(); + + int serverboundContainerClickPacket(); + + int serverboundSetCreativeModeSlotPacket(); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ArmorStandPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ArmorStandPacketHandler.java index 769363ce5..c726a7a54 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ArmorStandPacketHandler.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ArmorStandPacketHandler.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +@SuppressWarnings("DuplicatedCode") public class ArmorStandPacketHandler implements EntityPacketHandler { public static final ArmorStandPacketHandler INSTANCE = new ArmorStandPacketHandler(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/CommonItemPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/CommonItemPacketHandler.java new file mode 100644 index 000000000..a17a53691 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/CommonItemPacketHandler.java @@ -0,0 +1,53 @@ +package net.momirealms.craftengine.bukkit.plugin.network.handler; + +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.bukkit.util.EntityDataUtils; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; +import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.Optional; + +public class CommonItemPacketHandler implements EntityPacketHandler { + public static final CommonItemPacketHandler INSTANCE = new CommonItemPacketHandler(); + + @Override + public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + ItemBuildContext context = ItemBuildContext.of((BukkitServerPlayer) user); + int id = buf.readVarInt(); + boolean isChanged = false; + List packedItems = FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$unpack(buf); + for (int i = 0; i < packedItems.size(); i++) { + Object packedItem = packedItems.get(i); + int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem); + if (entityDataId == EntityDataUtils.ITEM_DATA_ID) { + Object nmsItemStack = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); + ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsItemStack); + Optional optional = BukkitItemManager.instance().s2c(itemStack, context); + if (optional.isPresent()) { + isChanged = true; + itemStack = optional.get(); + Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); + packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue( + entityDataId, serializer, FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack) + )); + break; + } + } + } + if (isChanged) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(id); + FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$pack(packedItems, buf); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemDisplayPacketHandler.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemDisplayPacketHandler.java new file mode 100644 index 000000000..3ffba123c --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/handler/ItemDisplayPacketHandler.java @@ -0,0 +1,53 @@ +package net.momirealms.craftengine.bukkit.plugin.network.handler; + +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.bukkit.util.EntityDataUtils; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.plugin.network.ByteBufPacketEvent; +import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; +import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.Optional; + +public class ItemDisplayPacketHandler implements EntityPacketHandler { + public static final ItemDisplayPacketHandler INSTANCE = new ItemDisplayPacketHandler(); + + @Override + public void handleSetEntityData(NetWorkUser user, ByteBufPacketEvent event) { + FriendlyByteBuf buf = event.getBuffer(); + ItemBuildContext context = ItemBuildContext.of((BukkitServerPlayer) user); + int id = buf.readVarInt(); + boolean isChanged = false; + List packedItems = FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$unpack(buf); + for (int i = 0; i < packedItems.size(); i++) { + Object packedItem = packedItems.get(i); + int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem); + if (entityDataId == EntityDataUtils.DISPLAYED_ITEM_DATA_ID) { + Object nmsItemStack = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); + ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(nmsItemStack); + Optional optional = BukkitItemManager.instance().s2c(itemStack, context); + if (optional.isPresent()) { + isChanged = true; + itemStack = optional.get(); + Object serializer = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$serializer(packedItem); + packedItems.set(i, FastNMS.INSTANCE.constructor$SynchedEntityData$DataValue( + entityDataId, serializer, FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(itemStack) + )); + break; + } + } + } + if (isChanged) { + event.setChanged(true); + buf.clear(); + buf.writeVarInt(event.packetID()); + buf.writeVarInt(id); + FastNMS.INSTANCE.method$ClientboundSetEntityDataPacket$pack(packedItems, buf); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIdFinder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIdFinder.java index fc5594bfe..c399ea948 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIdFinder.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIdFinder.java @@ -6,12 +6,15 @@ import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.VersionHelper; +import java.util.Collections; import java.util.HashMap; import java.util.Map; public class PacketIdFinder { private static final Map> gamePacketIdsByName = new HashMap<>(); private static final Map, Integer>> gamePacketIdsByClazz = new HashMap<>(); + private static final int maxC2SPacketId; + private static final int maxS2CPacketId; static { try { @@ -34,6 +37,23 @@ public class PacketIdFinder { } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to get packets", e); } + maxS2CPacketId = calculateMaxId("clientbound"); + maxC2SPacketId = calculateMaxId("serverbound"); + } + private static int calculateMaxId(String direction) { + if (VersionHelper.isOrAbove1_20_5()) { + return gamePacketIdsByName.getOrDefault(direction, Collections.emptyMap()).size(); + } else { + return gamePacketIdsByClazz.getOrDefault(direction, Collections.emptyMap()).size(); + } + } + + public static int maxC2SPacketId() { + return maxC2SPacketId; + } + + public static int maxS2CPacketId() { + return maxS2CPacketId; } public static int clientboundByName(String packetName) { @@ -43,4 +63,12 @@ public class PacketIdFinder { public static int clientboundByClazz(Class clazz) { return gamePacketIdsByClazz.get("clientbound").getOrDefault(clazz, -1); } + + public static int serverboundByName(String packetName) { + return gamePacketIdsByName.get("serverbound").getOrDefault(packetName, -1); + } + + public static int serverboundByClazz(Class clazz) { + return gamePacketIdsByClazz.get("serverbound").getOrDefault(clazz, -1); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java index a1c2c9831..f9af24136 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20.java @@ -104,4 +104,39 @@ public class PacketIds1_20 implements PacketIds { public int clientboundSetScorePacket() { return PacketIdFinder.clientboundByClazz(Reflections.clazz$ClientboundSetScorePacket); } + + @Override + public int clientboundContainerSetContentPacket() { + return PacketIdFinder.clientboundByClazz(Reflections.clazz$ClientboundContainerSetContentPacket); + } + + @Override + public int clientboundContainerSetSlotPacket() { + return PacketIdFinder.clientboundByClazz(Reflections.clazz$ClientboundContainerSetSlotPacket); + } + + @Override + public int clientboundSetCursorItemPacket() { + return PacketIdFinder.clientboundByClazz(Reflections.clazz$ClientboundSetCursorItemPacket); + } + + @Override + public int clientboundSetEquipmentPacket() { + return PacketIdFinder.clientboundByClazz(Reflections.clazz$ClientboundSetEquipmentPacket); + } + + @Override + public int clientboundSetPlayerInventoryPacket() { + return PacketIdFinder.clientboundByClazz(Reflections.clazz$ClientboundSetPlayerInventoryPacket); + } + + @Override + public int serverboundContainerClickPacket() { + return PacketIdFinder.serverboundByClazz(Reflections.clazz$ServerboundContainerClickPacket); + } + + @Override + public int serverboundSetCreativeModeSlotPacket() { + return PacketIdFinder.serverboundByClazz(Reflections.clazz$ServerboundSetCreativeModeSlotPacket); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java index ea3437281..bb29be78b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/id/PacketIds1_20_5.java @@ -103,4 +103,39 @@ public class PacketIds1_20_5 implements PacketIds { public int clientboundSetScorePacket() { return PacketIdFinder.clientboundByName("minecraft:set_score"); } + + @Override + public int clientboundContainerSetContentPacket() { + return PacketIdFinder.clientboundByName("minecraft:container_set_content"); + } + + @Override + public int clientboundContainerSetSlotPacket() { + return PacketIdFinder.clientboundByName("minecraft:container_set_slot"); + } + + @Override + public int clientboundSetCursorItemPacket() { + return PacketIdFinder.clientboundByName("minecraft:set_cursor_item"); + } + + @Override + public int clientboundSetEquipmentPacket() { + return PacketIdFinder.clientboundByName("minecraft:set_equipment"); + } + + @Override + public int clientboundSetPlayerInventoryPacket() { + return PacketIdFinder.clientboundByName("minecraft:set_player_inventory"); + } + + @Override + public int serverboundContainerClickPacket() { + return PacketIdFinder.serverboundByName("minecraft:container_click"); + } + + @Override + public int serverboundSetCreativeModeSlotPacket() { + return PacketIdFinder.serverboundByName("minecraft:set_creative_mode_slot"); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/DiscardedPayload.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/DiscardedPayload.java new file mode 100644 index 000000000..d0319220a --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/DiscardedPayload.java @@ -0,0 +1,44 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.FriendlyByteBuf; +import net.momirealms.craftengine.core.util.Key; + +public record DiscardedPayload(Key channel, Object rawPayload) implements Payload { + public static final boolean useNewMethod = Reflections.method$DiscardedPayload$data == null; + + public static DiscardedPayload from(Object payload) { + try { + Object type = Reflections.method$CustomPacketPayload$type.invoke(payload); + Object id = Reflections.method$CustomPacketPayload$Type$id.invoke(type); + Key channel = Key.of(id.toString()); + return new DiscardedPayload(channel, payload); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to create DiscardedPayload", e); + return null; + } + } + + public byte[] getData() { + try { + if (useNewMethod) { + return (byte[]) Reflections.method$DiscardedPayload$dataByteArray.invoke(this.rawPayload()); + } else { + ByteBuf buf = (ByteBuf) Reflections.method$DiscardedPayload$data.invoke(this.rawPayload()); + byte[] data = new byte[buf.readableBytes()]; + buf.readBytes(data); + return data; + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to get data from DiscardedPayload", e); + return new byte[0]; + } + } + + public FriendlyByteBuf toBuffer() { + return new FriendlyByteBuf(Unpooled.wrappedBuffer(this.getData())); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodec.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodec.java new file mode 100644 index 000000000..7fa1d4131 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodec.java @@ -0,0 +1,22 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload; + +import io.netty.buffer.ByteBuf; + +import java.util.function.Function; + +public interface NetWorkCodec extends NetWorkEncoder, NetWorkDecoder { + + default NetWorkCodec map(Function factory, Function getter) { + return new NetWorkCodec<>() { + @Override + public O decode(ByteBuf in) { + return factory.apply(NetWorkCodec.this.decode(in)); + } + + @Override + public void encode(ByteBuf out, O value) { + NetWorkCodec.this.encode(out, getter.apply(value)); + } + }; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodecs.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodecs.java new file mode 100644 index 000000000..4642578bc --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkCodecs.java @@ -0,0 +1,394 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.ByteBufUtil; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.EncoderException; +import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.NBT; +import net.momirealms.sparrow.nbt.Tag; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.OptionalInt; + +/** + * 随便写了点方便后面重构和客户端通讯 + */ +public interface NetWorkCodecs { + + NetWorkCodec BOOLEAN = new NetWorkCodec<>() { + @Override + public Boolean decode(ByteBuf in) { + return in.readBoolean(); + } + + @Override + public void encode(ByteBuf out, Boolean value) { + out.writeBoolean(value); + } + }; + + NetWorkCodec BYTE = new NetWorkCodec<>() { + @Override + public Byte decode(ByteBuf in) { + return in.readByte(); + } + + @Override + public void encode(ByteBuf out, Byte value) { + out.writeByte(value); + } + }; + + NetWorkCodec ROTATION_BYTE = BYTE.map(MCUtils::unpackDegrees, MCUtils::packDegrees); + + NetWorkCodec SHORT = new NetWorkCodec<>() { + @Override + public Short decode(ByteBuf in) { + return in.readShort(); + } + + @Override + public void encode(ByteBuf out, Short value) { + out.writeShort(value); + } + }; + + NetWorkCodec UNSIGNED_SHORT = new NetWorkCodec<>() { + @Override + public Integer decode(ByteBuf in) { + return in.readUnsignedShort(); + } + + @Override + public void encode(ByteBuf out, Integer value) { + out.writeShort(value); + } + }; + + NetWorkCodec INTEGER = new NetWorkCodec<>() { + @Override + public Integer decode(ByteBuf in) { + return in.readInt(); + } + + @Override + public void encode(ByteBuf out, Integer value) { + out.writeInt(value); + } + }; + + NetWorkCodec VAR_INTEGER = new NetWorkCodec<>() { + @Override + public Integer decode(ByteBuf in) { + int result = 0; + int bytesRead = 0; + byte currentByte; + do { + currentByte = in.readByte(); + result |= (currentByte & 127) << bytesRead++ * 7; + if (bytesRead > 5) { + throw new RuntimeException("VarInt too big"); + } + } while ((currentByte & 128) == 128); + return result; + } + + @Override + public void encode(ByteBuf out, Integer value) { + while ((value & -128) != 0) { + out.writeByte(value & 127 | 128); + value >>>= 7; + } + out.writeByte(value); + } + }; + + NetWorkCodec OPTIONAL_VAR_INTEGER = VAR_INTEGER.map( + integer -> integer == 0 ? OptionalInt.empty() : OptionalInt.of(integer - 1), + optionalInt -> optionalInt.isPresent() ? optionalInt.getAsInt() + 1 : 0 + ); + + NetWorkCodec LONG = new NetWorkCodec<>() { + @Override + public Long decode(ByteBuf in) { + return in.readLong(); + } + + @Override + public void encode(ByteBuf out, Long value) { + out.writeLong(value); + } + }; + + NetWorkCodec VAR_LONG = new NetWorkCodec<>() { + @Override + public Long decode(ByteBuf in) { + long result = 0L; + int bytesRead = 0; + byte currentByte; + do { + currentByte = in.readByte(); + result |= (long)(currentByte & 127) << bytesRead++ * 7; + if (bytesRead > 10) { + throw new RuntimeException("VarLong too big"); + } + } while ((currentByte & 128) == 128); + return result; + } + + @Override + public void encode(ByteBuf out, Long value) { + while ((value & -128L) != 0L) { + out.writeByte((int)(value & 127L) | 128); + value >>>= 7; + } + out.writeByte(value.intValue()); + } + }; + + NetWorkCodec FLOAT = new NetWorkCodec<>() { + @Override + public Float decode(ByteBuf in) { + return in.readFloat(); + } + + @Override + public void encode(ByteBuf out, Float value) { + out.writeFloat(value); + } + }; + + NetWorkCodec DOUBLE = new NetWorkCodec<>() { + @Override + public Double decode(ByteBuf in) { + return in.readDouble(); + } + + @Override + public void encode(ByteBuf out, Double value) { + out.writeDouble(value); + } + }; + + NetWorkCodec BYTE_ARRAY = new NetWorkCodec<>() { + @Override + public byte[] decode(ByteBuf in) { + int maxSize = in.readableBytes(); + int size = VAR_INTEGER.decode(in); + if (size > maxSize) { + throw new DecoderException("ByteArray with size " + size + " is bigger than allowed " + maxSize); + } else { + byte[] bytes = new byte[size]; + in.readBytes(bytes); + return bytes; + } + } + + @Override + public void encode(ByteBuf out, byte[] value) { + VAR_INTEGER.encode(out, value.length); + out.writeBytes(value); + } + }; + + NetWorkCodec LONG_ARRAY = new NetWorkCodec<>() { + @Override + public long[] decode(ByteBuf in) { + int arrayLength = VAR_INTEGER.decode(in); + int maxPossibleElements = in.readableBytes() / 8; + if (arrayLength > maxPossibleElements) { + throw new DecoderException("LongArray with size " + arrayLength + " is bigger than allowed " + maxPossibleElements); + } else { + long[] longArray = new long[arrayLength]; + for (int i = 0; i < longArray.length; i++) { + longArray[i] = in.readLong(); + } + return longArray; + } + } + + @Override + public void encode(ByteBuf out, long[] value) { + VAR_INTEGER.encode(out, value.length); + for (long element : value) { + out.writeLong(element); + } + } + }; + + NetWorkCodec STRING_UTF8 = new NetWorkCodec<>() { + private static final int MAX_STRING_LENGTH = 32767; + + @Override + public String decode(ByteBuf in) { + int maxEncodedBytes = ByteBufUtil.utf8MaxBytes(MAX_STRING_LENGTH); + int encodedLength = VAR_INTEGER.decode(in); + if (encodedLength > maxEncodedBytes) { + throw new DecoderException("The received encoded string buffer length is longer than maximum allowed (" + encodedLength + " > " + maxEncodedBytes + ")"); + } else if (encodedLength < 0) { + throw new DecoderException("The received encoded string buffer length is less than zero! Weird string!"); + } else { + int availableBytes = in.readableBytes(); + if (encodedLength > availableBytes) { + throw new DecoderException("Not enough bytes in buffer, expected " + encodedLength + ", but got " + availableBytes); + } else { + String decodedString = in.toString(in.readerIndex(), encodedLength, StandardCharsets.UTF_8); + in.readerIndex(in.readerIndex() + encodedLength); + if (decodedString.length() > MAX_STRING_LENGTH) { + throw new DecoderException("The received string length is longer than maximum allowed (" + decodedString.length() + " > " + MAX_STRING_LENGTH + ")"); + } else { + return decodedString; + } + } + } + } + + @Override + public void encode(ByteBuf out, String value) { + if (value.length() > MAX_STRING_LENGTH) { + throw new EncoderException("String too big (was " + value.length() + " characters, max " + MAX_STRING_LENGTH + ")"); + } else { + int maxPossibleBytes = ByteBufUtil.utf8MaxBytes(value); + ByteBuf tempBuffer = out.alloc().buffer(maxPossibleBytes); + try { + int actualEncodedBytes = ByteBufUtil.writeUtf8(tempBuffer, value); + int maxAllowedBytes = ByteBufUtil.utf8MaxBytes(MAX_STRING_LENGTH); + if (actualEncodedBytes > maxAllowedBytes) { + throw new EncoderException("String too big (was " + actualEncodedBytes + " bytes encoded, max " + maxAllowedBytes + ")"); + } + VAR_INTEGER.encode(out, actualEncodedBytes); + out.writeBytes(tempBuffer); + } finally { + tempBuffer.release(); + } + } + } + }; + + NetWorkCodec TAG = new NetWorkCodec<>() { + @Override + public Tag decode(ByteBuf in) { + int initialIndex = in.readerIndex(); + byte marker = in.readByte(); + if (marker == 0) { + return null; + } else { + in.readerIndex(initialIndex); + try { + return NBT.readUnnamedTag(new ByteBufInputStream(in), false); + } catch (IOException e) { + throw new EncoderException("Failed to read NBT compound: " + e.getMessage(), e); + } + } + } + + @Override + public void encode(ByteBuf out, Tag value) { + if (value == null) { + out.writeByte(0); + } else { + try { + NBT.writeUnnamedTag(value, new ByteBufOutputStream(out), false); + } catch (IOException e) { + throw new EncoderException("Failed to write NBT compound: " + e.getMessage(), e); + } + } + } + }; + + NetWorkCodec COMPOUND_TAG = TAG.map(tag -> { + if (tag instanceof CompoundTag compoundTag) { + return compoundTag; + } else { + throw new DecoderException("Not a compound tag: " + tag); + } + }, tag -> tag); + + NetWorkCodec> OPTIONAL_COMPOUND_TAG = new NetWorkCodec<>() { + @Override + public Optional decode(ByteBuf in) { + int initialIndex = in.readerIndex(); + byte marker = in.readByte(); + if (marker == 0) { + return Optional.empty(); + } else { + in.readerIndex(initialIndex); + try { + if (NBT.readUnnamedTag(new ByteBufInputStream(in), false) instanceof CompoundTag compoundTag) { + return Optional.of(compoundTag); + } + } catch (IOException e) { + throw new EncoderException("Failed to read NBT compound: " + e.getMessage(), e); + } + } + return Optional.empty(); + } + + @Override + public void encode(ByteBuf out, Optional value) { + CompoundTag compound = value.orElse(null); + if (compound == null) { + out.writeByte(0); + } else { + try { + NBT.writeUnnamedTag(compound, new ByteBufOutputStream(out), false); + } catch (IOException e) { + throw new EncoderException("Failed to write NBT compound: " + e.getMessage(), e); + } + } + } + }; + + NetWorkCodec VECTOR3F = new NetWorkCodec<>() { + @Override + public Vector3f decode(ByteBuf in) { + return new Vector3f(in.readFloat(), in.readFloat(), in.readFloat()); + } + + @Override + public void encode(ByteBuf out, Vector3f value) { + out.writeFloat(value.x()); + out.writeFloat(value.y()); + out.writeFloat(value.z()); + } + }; + + NetWorkCodec QUATERNIONF = new NetWorkCodec<>() { + @Override + public Quaternionf decode(ByteBuf in) { + return new Quaternionf(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat()); + } + + @Override + public void encode(ByteBuf out, Quaternionf value) { + out.writeFloat(value.x()); + out.writeFloat(value.y()); + out.writeFloat(value.z()); + out.writeFloat(value.w()); + } + }; + + NetWorkCodec CONTAINER_ID = VAR_INTEGER; + + NetWorkCodec RGB_COLOR = new NetWorkCodec<>() { + @Override + public Integer decode(ByteBuf in) { + return 255 << 24 | in.readByte() & 0xFF << 16 | in.readByte() & 0xFF << 8 | in.readByte() & 0xFF; + } + + @Override + public void encode(ByteBuf out, Integer value) { + out.writeByte(value >> 16 & 0xFF); + out.writeByte(value >> 8 & 0xFF); + out.writeByte(value & 0xFF); + } + }; +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/NetWorkDataTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDataTypes.java similarity index 94% rename from bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/NetWorkDataTypes.java rename to bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDataTypes.java index 50b8e57bf..5655e1028 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/NetWorkDataTypes.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDataTypes.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.bukkit.plugin.network; +package net.momirealms.craftengine.bukkit.plugin.network.payload; import net.momirealms.craftengine.core.util.FriendlyByteBuf; @@ -57,7 +57,7 @@ public class NetWorkDataTypes { return id2NetWorkDataTypes.get(id); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "unused"}) public NetWorkDataTypes as(Class clazz) { return (NetWorkDataTypes) this; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDecoder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDecoder.java new file mode 100644 index 000000000..fc3024db0 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkDecoder.java @@ -0,0 +1,7 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload; + +import io.netty.buffer.ByteBuf; + +public interface NetWorkDecoder { + T decode(ByteBuf in); +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkEncoder.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkEncoder.java new file mode 100644 index 000000000..3c346115a --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/NetWorkEncoder.java @@ -0,0 +1,7 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload; + +import io.netty.buffer.ByteBuf; + +public interface NetWorkEncoder { + void encode(ByteBuf out, T value); +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/Payload.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/Payload.java new file mode 100644 index 000000000..2c82d9463 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/payload/Payload.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.bukkit.plugin.network.payload; + +import net.momirealms.craftengine.core.util.FriendlyByteBuf; +import net.momirealms.craftengine.core.util.Key; + +public interface Payload { + FriendlyByteBuf toBuffer(); + + Key channel(); +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index 5e045570f..9c6b7b5ef 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -11,14 +11,15 @@ import net.momirealms.craftengine.bukkit.plugin.gui.CraftEngineInventoryHolder; import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.bukkit.world.BukkitWorld; import net.momirealms.craftengine.core.block.BlockSettings; +import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.ImmutableBlockState; -import net.momirealms.craftengine.core.block.PackedBlockState; import net.momirealms.craftengine.core.entity.player.GameMode; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.context.CooldownData; import net.momirealms.craftengine.core.plugin.network.ConnectionState; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; import net.momirealms.craftengine.core.plugin.network.ProtocolVersion; @@ -28,19 +29,20 @@ import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldEvents; -import org.bukkit.FluidCollisionMode; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.SoundCategory; +import org.bukkit.*; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; import org.bukkit.block.Block; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; import org.bukkit.util.RayTraceResult; import org.jetbrains.annotations.Nullable; +import java.io.IOException; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.*; @@ -95,6 +97,8 @@ public class BukkitServerPlayer extends Player { // cache interaction range here private int lastUpdateInteractionRangeTick; private double cachedInteractionRange; + // cooldown data + private CooldownData cooldownData; private final Map entityTypeView = new ConcurrentHashMap<>(); @@ -108,6 +112,13 @@ public class BukkitServerPlayer extends Player { this.serverPlayerRef = new WeakReference<>(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)); this.uuid = player.getUniqueId(); this.name = player.getName(); + byte[] bytes = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY), PersistentDataType.BYTE_ARRAY); + try { + this.cooldownData = CooldownData.fromBytes(bytes); + } catch (IOException e) { + this.cooldownData = new CooldownData(); + this.plugin.logger().warn("Failed to parse cooldown data", e); + } } @Override @@ -276,6 +287,30 @@ public class BukkitServerPlayer extends Player { this.plugin.networkManager().sendPacket(this, packet, immediately); } + @Override + public void sendCustomPayload(Key channel, byte[] data) { + try { + Object channelKey = KeyUtils.toResourceLocation(channel); + Object dataPayload = Reflections.constructor$DiscardedPayload.newInstance(channelKey, data); + Object responsePacket = Reflections.constructor$ClientboundCustomPayloadPacket.newInstance(dataPayload); + this.nettyChannel().writeAndFlush(responsePacket); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to send custom payload to " + name(), e); + } + } + + @Override + public void kick(Component message) { + try { + Object reason = ComponentUtils.adventureToMinecraft(message); + Object kickPacket = Reflections.constructor$ClientboundDisconnectPacket.newInstance(reason); + this.nettyChannel().writeAndFlush(kickPacket); + this.nettyChannel().disconnect(); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to kick " + name(), e); + } + } + @Override public void sendPackets(List packet, boolean immediately) { this.plugin.networkManager().sendPackets(this, packet, immediately); @@ -405,7 +440,7 @@ public class BukkitServerPlayer extends Player { // instant break boolean custom = immutableBlockState != null; if (custom && getDestroyProgress(state, pos) >= 1f) { - PackedBlockState vanillaBlockState = immutableBlockState.vanillaBlockState(); + BlockStateWrapper vanillaBlockState = immutableBlockState.vanillaBlockState(); // if it's not an instant break on client side, we should resend level event if (vanillaBlockState != null && getDestroyProgress(vanillaBlockState.handle(), pos) < 1f) { Object levelEventPacket = FastNMS.INSTANCE.constructor$ClientboundLevelEventPacket( @@ -520,7 +555,7 @@ public class BukkitServerPlayer extends Player { // send hit sound if the sound is removed if (currentTick - this.lastHitBlockTime > 3) { - Object blockOwner = Reflections.field$StateHolder$owner.get(this.destroyedState); + Object blockOwner = FastNMS.INSTANCE.method$BlockState$getBlock(this.destroyedState); Object soundType = Reflections.field$BlockBehaviour$soundType.get(blockOwner); Object soundEvent = Reflections.field$SoundType$hitSound.get(soundType); Object soundId = FastNMS.INSTANCE.field$SoundEvent$location(soundEvent); @@ -613,6 +648,11 @@ public class BukkitServerPlayer extends Player { } } + @Override + public void breakBlock(int x, int y, int z) { + platformPlayer().breakBlock(new Location(platformPlayer().getWorld(), x, y, z).getBlock()); + } + private void broadcastDestroyProgress(org.bukkit.entity.Player player, BlockPos hitPos, Object blockPos, int stage) { Object packet = FastNMS.INSTANCE.constructor$ClientboundBlockDestructionPacket(Integer.MAX_VALUE - entityID(), blockPos, stage); for (org.bukkit.entity.Player other : player.getWorld().getPlayers()) { @@ -670,12 +710,12 @@ public class BukkitServerPlayer extends Player { } @Override - public float getYRot() { + public float yRot() { return platformPlayer().getPitch(); } @Override - public float getXRot() { + public float xRot() { return platformPlayer().getYaw(); } @@ -833,4 +873,48 @@ public class BukkitServerPlayer extends Player { public boolean isFlying() { return platformPlayer().isFlying(); } + + @Override + public int foodLevel() { + return platformPlayer().getFoodLevel(); + } + + @Override + public void setFoodLevel(int foodLevel) { + this.platformPlayer().setFoodLevel(Math.min(Math.max(0, foodLevel), 20)); + } + + @Override + public float saturation() { + return platformPlayer().getSaturation(); + } + + @Override + public void setSaturation(float saturation) { + this.platformPlayer().setSaturation(saturation); + } + + @Override + public void addPotionEffect(Key potionEffectType, int duration, int amplifier, boolean ambient, boolean particles) { + PotionEffectType type = Registry.POTION_EFFECT_TYPE.get(KeyUtils.toNamespacedKey(potionEffectType)); + if (type == null) return; + this.platformPlayer().addPotionEffect(new PotionEffect(type, duration, amplifier, ambient, particles)); + } + + @Override + public void removePotionEffect(Key potionEffectType) { + PotionEffectType type = Registry.POTION_EFFECT_TYPE.get(KeyUtils.toNamespacedKey(potionEffectType)); + if (type == null) return; + this.platformPlayer().removePotionEffect(type); + } + + @Override + public void clearPotionEffects() { + this.platformPlayer().clearActivePotionEffects(); + } + + @Override + public CooldownData cooldown() { + return this.cooldownData; + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java index 81163c601..49ab01ffe 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/BlockStateUtils.java @@ -33,6 +33,12 @@ public class BlockStateUtils { hasInit = true; } + public static BlockStateWrapper toPackedBlockState(BlockData blockData) { + Object state = blockDataToBlockState(blockData); + int id = blockStateToId(state); + return BlockStateWrapper.create(state, id, isVanillaBlock(id)); + } + public static boolean isCorrectTool(@NotNull ImmutableBlockState state, @Nullable Item itemInHand) { BlockSettings settings = state.settings(); if (settings.requireCorrectTool()) { @@ -101,10 +107,10 @@ public class BlockStateUtils { } public static Key getBlockOwnerId(Block block) { - return getBlockOwnerId(block.getBlockData()); + return getBlockOwnerIdFromData(block.getBlockData()); } - public static Key getBlockOwnerId(BlockData block) { + public static Key getBlockOwnerIdFromData(BlockData block) { Object blockState = blockDataToBlockState(block); return getBlockOwnerIdFromState(blockState); } @@ -134,11 +140,7 @@ public class BlockStateUtils { } public static Object getBlockOwner(Object blockState) { - try { - return Reflections.field$StateHolder$owner.get(blockState); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } + return FastNMS.INSTANCE.method$BlockState$getBlock(blockState); } public static int physicsEventToId(BlockPhysicsEvent event) throws ReflectiveOperationException { @@ -187,6 +189,10 @@ public class BlockStateUtils { Reflections.field$BlockStateBase$isRandomlyTicking.set(state, randomlyTicking); } + public static void setPropagatesSkylightDown(Object state, boolean propagatesSkylightDown) throws ReflectiveOperationException { + Reflections.field$BlockStateBase$propagatesSkylightDown.set(state, propagatesSkylightDown); + } + public static void setReplaceable(Object state, boolean replaceable) throws ReflectiveOperationException { Reflections.field$BlockStateBase$replaceable.set(state, replaceable); } @@ -203,7 +209,7 @@ public class BlockStateUtils { Reflections.field$BlockStateBase$canOcclude.set(state, canOcclude); } - public static boolean isOcclude(Object state) throws ReflectiveOperationException { + public static boolean isOcclude(Object state) { return FastNMS.INSTANCE.method$BlockStateBase$canOcclude(state); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ColorUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ColorUtils.java new file mode 100644 index 000000000..18fcde2d3 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ColorUtils.java @@ -0,0 +1,11 @@ +package net.momirealms.craftengine.bukkit.util; + +import org.bukkit.Color; + +public final class ColorUtils { + private ColorUtils() {} + + public static Color toBukkit(net.momirealms.craftengine.core.util.Color color) { + return Color.fromARGB(color.a(), color.r(), color.g(), color.b()); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ComponentUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ComponentUtils.java index b9622ed40..c3339ea88 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ComponentUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ComponentUtils.java @@ -13,6 +13,10 @@ public class ComponentUtils { return jsonElementToMinecraft(AdventureHelper.componentToJsonElement(component)); } + public static Object adventureToPaperAdventure(Component component) { + return jsonElementToPaperAdventure(AdventureHelper.componentToJsonElement(component)); + } + public static Object jsonElementToMinecraft(JsonElement json) { return FastNMS.INSTANCE.method$Component$Serializer$fromJson(json); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityDataUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityDataUtils.java index 8c70d577f..715c28741 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityDataUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityDataUtils.java @@ -13,7 +13,9 @@ public class EntityDataUtils { private static final int RIGHT_ALIGNMENT = 0x10; // 16 public static final int BLOCK_STATE_DATA_ID = VersionHelper.isOrAbove1_20_2() ? 23 : 22; public static final int TEXT_DATA_ID = VersionHelper.isOrAbove1_20_2() ? 23 : 22; + public static final int DISPLAYED_ITEM_DATA_ID = VersionHelper.isOrAbove1_20_2() ? 23 : 22; public static final int CUSTOM_NAME_DATA_ID = 2; + public static final int ITEM_DATA_ID = 8; public static byte encodeTextDisplayMask(boolean hasShadow, boolean isSeeThrough, boolean useDefaultBackground, int alignment) { int bitMask = 0; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java index 7152b3f98..fa6c73e44 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java @@ -274,7 +274,7 @@ public class InteractUtils { } public static boolean isInteractable(Player player, BlockData state, BlockHitResult hit, Item item) { - Key blockType = BlockStateUtils.getBlockOwnerId(state); + Key blockType = BlockStateUtils.getBlockOwnerIdFromData(state); if (INTERACTIONS.containsKey(blockType)) { return INTERACTIONS.get(blockType).apply(player, item, state, hit); } else { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LocationUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LocationUtils.java index bb3d7feec..c7fa13c15 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LocationUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/LocationUtils.java @@ -37,10 +37,14 @@ public class LocationUtils { return toBlockPos(pos.x(), pos.y(), pos.z()); } - public static Object above(Object blockPos) throws ReflectiveOperationException { + public static Object above(Object blockPos) { return toBlockPos(FastNMS.INSTANCE.field$Vec3i$x(blockPos), FastNMS.INSTANCE.field$Vec3i$y(blockPos) + 1, FastNMS.INSTANCE.field$Vec3i$z(blockPos)); } + public static Object below(Object blockPos) { + return toBlockPos(FastNMS.INSTANCE.field$Vec3i$x(blockPos), FastNMS.INSTANCE.field$Vec3i$y(blockPos) - 1, FastNMS.INSTANCE.field$Vec3i$z(blockPos)); + } + public static Object toBlockPos(int x, int y, int z) { return FastNMS.INSTANCE.constructor$BlockPos(x, y, z); } @@ -49,7 +53,7 @@ public class LocationUtils { return new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); } - public static BlockPos fromBlockPos(Object pos) throws ReflectiveOperationException { + public static BlockPos fromBlockPos(Object pos) { return new BlockPos( FastNMS.INSTANCE.field$Vec3i$x(pos), FastNMS.INSTANCE.field$Vec3i$y(pos), diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/MaterialUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/MaterialUtils.java index f6cc84aa0..0ef3c5caf 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/MaterialUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/MaterialUtils.java @@ -4,8 +4,8 @@ import net.momirealms.craftengine.core.util.Key; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Registry; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.Locale; import java.util.Objects; import java.util.Optional; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java index dc8c5aeba..a15f0091f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java @@ -1,9 +1,23 @@ package net.momirealms.craftengine.bukkit.util; +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.world.particle.*; +import org.bukkit.Location; import org.bukkit.Particle; +import org.bukkit.Vibration; +import org.bukkit.World; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; public final class ParticleUtils { + private static final Map CACHE = new HashMap<>(); + private ParticleUtils() {} public static Particle getParticle(String particle) { @@ -19,6 +33,34 @@ public final class ParticleUtils { } } + @Nullable + public static Particle getParticle(Key particle) { + return CACHE.computeIfAbsent(particle, k -> { + try { + Object nmsParticle = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$PARTICLE_TYPE, KeyUtils.toResourceLocation(particle)); + if (nmsParticle == null) return null; + return FastNMS.INSTANCE.method$CraftParticle$toBukkit(nmsParticle); + } catch (ReflectiveOperationException e) { + CraftEngine.instance().logger().warn("Failed to get particle: " + particle, e); + return null; + } + }); + } + public static final Particle HAPPY_VILLAGER = getParticle("HAPPY_VILLAGER"); public static final Particle BUBBLE = getParticle("BUBBLE"); + + public static Object toBukkitParticleData(ParticleData particleData, Context context, World world, double x, double y, double z) { + return switch (particleData) { + case BlockStateData data -> BlockStateUtils.fromBlockData(data.blockState().handle()); + case ColorData data -> ColorUtils.toBukkit(data.color()); + case DustData data -> new Particle.DustOptions(ColorUtils.toBukkit(data.color()), data.size()); + case DustTransitionData data -> new Particle.DustTransition(ColorUtils.toBukkit(data.from()), ColorUtils.toBukkit(data.to()), data.size()); + case ItemStackData data -> data.item().getItem(); + case JavaTypeData data -> data.data(); + case VibrationData data -> new Vibration(new Vibration.Destination.BlockDestination(new Location(world, x + data.destinationX().getDouble(context), y + data.destinationY().getDouble(context), y + data.destinationZ().getDouble(context))), data.arrivalTime().getInt(context)); + case TrailData data -> new Particle.Trail(new Location(world, x + data.targetX().getDouble(context), y + data.targetZ().getDouble(context), z + data.targetZ().getDouble(context)), ColorUtils.toBukkit(data.color()), data.duration().getInt(context)); + default -> null; + }; + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java index a8a39911e..ede8f4f74 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java @@ -3,6 +3,8 @@ package net.momirealms.craftengine.bukkit.util; import com.google.common.collect.ImmutableList; import com.google.gson.Gson; import com.google.gson.JsonElement; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -14,6 +16,9 @@ import net.kyori.adventure.text.Component; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.core.util.ReflectionUtils; import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.sparrow.nbt.Tag; +import net.momirealms.sparrow.nbt.codec.LegacyNBTOps; +import net.momirealms.sparrow.nbt.codec.NBTOps; import org.bukkit.NamespacedKey; import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; @@ -37,7 +42,7 @@ import java.util.function.Consumer; import static java.util.Objects.requireNonNull; -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "unchecked"}) public class Reflections { public static void init() { @@ -88,7 +93,6 @@ public class Reflections { ) ); - public static final Class clazz$ClientboundSetActionBarTextPacket = requireNonNull( ReflectionUtils.getClazz( BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundSetActionBarTextPacket") @@ -2424,6 +2428,12 @@ public class Reflections { ) ); + public static final Field field$BlockStateBase$propagatesSkylightDown = requireNonNull( + ReflectionUtils.getDeclaredField( + clazz$BlockStateBase, boolean.class, 11 + ) + ); + public static final Field field$BlockStateBase$requiresCorrectToolForDrops = requireNonNull( ReflectionUtils.getDeclaredField( clazz$BlockStateBase, boolean.class, 5 @@ -2524,11 +2534,11 @@ public class Reflections { ) ); - public static final Field field$StateHolder$owner = requireNonNull( - ReflectionUtils.getDeclaredField( - clazz$StateHolder, Object.class, 0 - ) - ); +// public static final Field field$StateHolder$owner = requireNonNull( +// ReflectionUtils.getDeclaredField( +// clazz$StateHolder, Object.class, 0 +// ) +// ); public static final Class clazz$CollisionContext = requireNonNull( BukkitReflectionUtils.findReobfOrMojmapClass( @@ -2543,6 +2553,18 @@ public class Reflections { ) ); + public static final Method method$BlockBehaviour$getCollisionShape = requireNonNull( + ReflectionUtils.getDeclaredMethod( + clazz$BlockBehaviour, clazz$VoxelShape, new String[]{"getCollisionShape", VersionHelper.isOrAbove1_20_3() ? "b" : "c"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos, clazz$CollisionContext + ) + ); + + public static final Method method$BlockBehaviour$getBlockSupportShape = requireNonNull( + ReflectionUtils.getDeclaredMethod( + clazz$BlockBehaviour, clazz$VoxelShape, new String[]{"getBlockSupportShape", "b_"}, clazz$BlockState, clazz$BlockGetter, clazz$BlockPos + ) + ); + public static final Method method$BlockBehaviour$tick = requireNonNull( ReflectionUtils.getDeclaredMethod( clazz$BlockBehaviour, void.class, new String[]{"tick", "a"}, clazz$BlockState, clazz$ServerLevel, clazz$BlockPos, clazz$RandomSource @@ -3094,6 +3116,7 @@ public class Reflections { ) ); + public static final Object[] instance$EquipmentSlot$values; public static final Object instance$EquipmentSlot$MAINHAND; public static final Object instance$EquipmentSlot$OFFHAND; public static final Object instance$EquipmentSlot$FEET; @@ -3104,14 +3127,14 @@ public class Reflections { static { try { - Object[] values = (Object[]) method$EquipmentSlot$values.invoke(null); - instance$EquipmentSlot$MAINHAND = values[0]; - instance$EquipmentSlot$OFFHAND = values[1]; - instance$EquipmentSlot$FEET = values[2]; - instance$EquipmentSlot$LEGS = values[3]; - instance$EquipmentSlot$CHEST = values[4]; - instance$EquipmentSlot$HEAD = values[5]; -// instance$EquipmentSlot$BODY = values[6]; + instance$EquipmentSlot$values = (Object[]) method$EquipmentSlot$values.invoke(null); + instance$EquipmentSlot$MAINHAND = instance$EquipmentSlot$values[0]; + instance$EquipmentSlot$OFFHAND = instance$EquipmentSlot$values[1]; + instance$EquipmentSlot$FEET = instance$EquipmentSlot$values[2]; + instance$EquipmentSlot$LEGS = instance$EquipmentSlot$values[3]; + instance$EquipmentSlot$CHEST = instance$EquipmentSlot$values[4]; + instance$EquipmentSlot$HEAD = instance$EquipmentSlot$values[5]; +// instance$EquipmentSlot$BODY = instance$EquipmentSlot$values[6]; } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } @@ -3824,6 +3847,18 @@ public class Reflections { public static final Object instance$EntityType$OAK_BOAT; public static final Object instance$EntityType$TRIDENT; public static final Object instance$EntityType$SNOWBALL; + public static final Object instance$EntityType$FIREBALL; + public static final Object instance$EntityType$EYE_OF_ENDER; + public static final Object instance$EntityType$FIREWORK_ROCKET; + public static final Object instance$EntityType$ITEM; + public static final Object instance$EntityType$ITEM_FRAME; + public static final Object instance$EntityType$GLOW_ITEM_FRAME; + public static final Object instance$EntityType$OMINOUS_ITEM_SPAWNER; + public static final Object instance$EntityType$SMALL_FIREBALL; + public static final Object instance$EntityType$EGG; + public static final Object instance$EntityType$ENDER_PEARL; + public static final Object instance$EntityType$EXPERIENCE_BOTTLE; + public static final Object instance$EntityType$POTION; static { try { @@ -3847,6 +3882,33 @@ public class Reflections { instance$EntityType$TRIDENT = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, trident); Object snowball = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "snowball"); instance$EntityType$SNOWBALL = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, snowball); + Object fireball = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "fireball"); + instance$EntityType$FIREBALL = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, fireball); + Object eyeOfEnder = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "eye_of_ender"); + instance$EntityType$EYE_OF_ENDER = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, eyeOfEnder); + Object fireworkRocket = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "firework_rocket"); + instance$EntityType$FIREWORK_ROCKET = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, fireworkRocket); + Object item = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "item"); + instance$EntityType$ITEM = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, item); + Object itemFrame = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "item_frame"); + instance$EntityType$ITEM_FRAME = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, itemFrame); + instance$EntityType$GLOW_ITEM_FRAME = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "glow_item_frame")); + Object smallFireball = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "small_fireball"); + instance$EntityType$SMALL_FIREBALL = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, smallFireball); + Object egg = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "egg"); + instance$EntityType$EGG = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, egg); + Object enderPearl = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "ender_pearl"); + instance$EntityType$ENDER_PEARL = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, enderPearl); + Object experienceBottle = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "experience_bottle"); + instance$EntityType$EXPERIENCE_BOTTLE = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, experienceBottle); + Object potion = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "potion"); + instance$EntityType$POTION = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, potion); + if (VersionHelper.isOrAbove1_20_5()) { + Object ominousItemSpawner = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "ominous_item_spawner"); + instance$EntityType$OMINOUS_ITEM_SPAWNER = Reflections.method$Registry$get.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, ominousItemSpawner); + } else { + instance$EntityType$OMINOUS_ITEM_SPAWNER = null; + } } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } @@ -6429,6 +6491,19 @@ public class Reflections { public static final int instance$EntityType$FALLING_BLOCK$registryId; public static final int instance$EntityType$TRIDENT$registryId; public static final int instance$EntityType$ARMOR_STAND$registryId; + public static final int instance$EntityType$FIREBALL$registryId; + public static final int instance$EntityType$EYE_OF_ENDER$registryId; + public static final int instance$EntityType$FIREWORK_ROCKET$registryId; + public static final int instance$EntityType$ITEM$registryId; + public static final int instance$EntityType$ITEM_FRAME$registryId; + public static final int instance$EntityType$GLOW_ITEM_FRAME$registryId; + public static final int instance$EntityType$OMINOUS_ITEM_SPAWNER$registryId; + public static final int instance$EntityType$SMALL_FIREBALL$registryId; + public static final int instance$EntityType$EGG$registryId; + public static final int instance$EntityType$ENDER_PEARL$registryId; + public static final int instance$EntityType$EXPERIENCE_BOTTLE$registryId; + public static final int instance$EntityType$SNOWBALL$registryId; + public static final int instance$EntityType$POTION$registryId; static { try { @@ -6438,6 +6513,23 @@ public class Reflections { instance$EntityType$FALLING_BLOCK$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$FALLING_BLOCK); instance$EntityType$TRIDENT$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$TRIDENT); instance$EntityType$ARMOR_STAND$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$ARMOR_STAND); + instance$EntityType$FIREBALL$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$FIREBALL); + instance$EntityType$EYE_OF_ENDER$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$EYE_OF_ENDER); + instance$EntityType$FIREWORK_ROCKET$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$FIREWORK_ROCKET); + instance$EntityType$ITEM$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$ITEM); + instance$EntityType$ITEM_FRAME$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$ITEM_FRAME); + instance$EntityType$GLOW_ITEM_FRAME$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$GLOW_ITEM_FRAME); + instance$EntityType$SMALL_FIREBALL$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$SMALL_FIREBALL); + instance$EntityType$EGG$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$EGG); + instance$EntityType$ENDER_PEARL$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$ENDER_PEARL); + instance$EntityType$EXPERIENCE_BOTTLE$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$EXPERIENCE_BOTTLE); + instance$EntityType$SNOWBALL$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$SNOWBALL); + instance$EntityType$POTION$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$POTION); + if (VersionHelper.isOrAbove1_20_5()) { + instance$EntityType$OMINOUS_ITEM_SPAWNER$registryId = (int) Reflections.method$Registry$getId.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE, instance$EntityType$OMINOUS_ITEM_SPAWNER); + } else { + instance$EntityType$OMINOUS_ITEM_SPAWNER$registryId = -1; + } } catch (Exception e) { throw new RuntimeException(e); } @@ -6792,4 +6884,102 @@ public class Reflections { } } + public static final Class clazz$Orientation = + BukkitReflectionUtils.findReobfOrMojmapClass( + "world.level.redstone.Orientation", + "world.level.redstone.Orientation" + ); + + public static final Method method$BlockBehaviour$neighborChanged = requireNonNull( + VersionHelper.isOrAbove1_21_2() ? + ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, void.class, clazz$BlockState, clazz$Level, clazz$BlockPos, clazz$Block, clazz$Orientation, boolean.class) : + Optional.ofNullable(ReflectionUtils.getDeclaredMethod(clazz$BlockBehaviour, void.class, clazz$BlockState, clazz$Level, clazz$BlockPos, clazz$Block, clazz$BlockPos, boolean.class)) + .orElse(ReflectionUtils.getMethod(clazz$BlockBehaviour, void.class, clazz$BlockState, clazz$Level, clazz$BlockPos, clazz$Block, clazz$BlockPos, boolean.class)) + ); + + public static final Class clazz$ClientboundContainerSetContentPacket = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "network.protocol.game.PacketPlayOutWindowItems", + "network.protocol.game.ClientboundContainerSetContentPacket" + ) + ); + + public static final Class clazz$ClientboundContainerSetSlotPacket = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "network.protocol.game.PacketPlayOutSetSlot", + "network.protocol.game.ClientboundContainerSetSlotPacket" + ) + ); + + // 1.21.2+ + public static final Class clazz$ClientboundSetCursorItemPacket = + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundSetCursorItemPacket") + ); + + // 1.21.2+ + public static final Class clazz$ClientboundSetPlayerInventoryPacket = + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundSetPlayerInventoryPacket") + ); + + public static final Class clazz$ServerboundContainerClickPacket = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "network.protocol.game.PacketPlayInWindowClick", + "network.protocol.game.ServerboundContainerClickPacket" + ) + ); + + public static final Class clazz$RegistryOps = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "resources.RegistryOps", + "resources.RegistryOps" + ) + ); + + // 1.20.5+ + public static final Class clazz$JavaOps = ReflectionUtils.getClazz("com.mojang.serialization.JavaOps"); + + public static final Class clazz$NbtOps = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "nbt.DynamicOpsNBT", + "nbt.NbtOps" + ) + ); + + public static final Method method$RegistryOps$create = requireNonNull( + ReflectionUtils.getStaticMethod( + clazz$RegistryOps, clazz$RegistryOps, DynamicOps.class, clazz$HolderLookup$Provider + ) + ); + + public static final DynamicOps instance$NBT_OPS; + public static final DynamicOps instance$SPARROW_NBT_OPS; + public static final DynamicOps instance$JAVA_OPS; + public static final DynamicOps instance$JSON_OPS; + + static { + try { + Object nbtOps = ReflectionUtils.getDeclaredField(clazz$NbtOps, clazz$NbtOps, 0).get(null); + instance$NBT_OPS = (DynamicOps) method$RegistryOps$create.invoke(null, nbtOps, instance$MinecraftRegistry); + if (clazz$JavaOps != null) { + Object javaOps = ReflectionUtils.getDeclaredField(clazz$JavaOps, clazz$JavaOps, 0).get(null); + instance$JAVA_OPS = (DynamicOps) method$RegistryOps$create.invoke(null, javaOps, instance$MinecraftRegistry); + } else { + // TODO Create a JavaOps + instance$JAVA_OPS = null; + } + instance$JSON_OPS = (DynamicOps) method$RegistryOps$create.invoke(null, JsonOps.INSTANCE, instance$MinecraftRegistry); + instance$SPARROW_NBT_OPS = (DynamicOps) method$RegistryOps$create.invoke(null, VersionHelper.isOrAbove1_20_5() ? NBTOps.INSTANCE : LegacyNBTOps.INSTANCE, instance$MinecraftRegistry); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + public static final Class clazz$Tag = requireNonNull( + BukkitReflectionUtils.findReobfOrMojmapClass( + "nbt.NBTBase", + "nbt.Tag" + ) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/RegistryUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/RegistryUtils.java index 52390c0bf..063ce0797 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/RegistryUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/RegistryUtils.java @@ -20,4 +20,13 @@ public class RegistryUtils { throw new RuntimeException(e); } } + + public static int currentEntityTypeRegistrySize() { + try { + Object idMap = Reflections.method$Registry$asHolderIdMap.invoke(Reflections.instance$BuiltInRegistries$ENTITY_TYPE); + return (int) Reflections.method$IdMap$size.invoke(idMap); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/SoundUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/SoundUtils.java index a733074e5..de80480fa 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/SoundUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/SoundUtils.java @@ -1,7 +1,9 @@ package net.momirealms.craftengine.bukkit.util; import net.momirealms.craftengine.core.block.BlockSounds; +import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Key; +import org.bukkit.SoundCategory; public class SoundUtils { @@ -21,4 +23,19 @@ public class SoundUtils { public static Object getOrRegisterSoundEvent(Key key) throws ReflectiveOperationException { return Reflections.method$SoundEvent$createVariableRangeEvent.invoke(null, KeyUtils.toResourceLocation(key)); } + + public static SoundCategory toBukkit(SoundSource source) { + return switch (source) { + case BLOCK -> SoundCategory.BLOCKS; + case MUSIC -> SoundCategory.MUSIC; + case VOICE -> SoundCategory.VOICE; + case MASTER -> SoundCategory.MASTER; + case PLAYER -> SoundCategory.PLAYERS; + case RECORD -> SoundCategory.RECORDS; + case AMBIENT -> SoundCategory.AMBIENT; + case HOSTILE -> SoundCategory.HOSTILE; + case NEUTRAL -> SoundCategory.NEUTRAL; + case WEATHER -> SoundCategory.WEATHER; + }; + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/TagUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/TagUtils.java index 28e6e971e..530ccc248 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/TagUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/TagUtils.java @@ -43,11 +43,14 @@ public final class TagUtils { public static Object createUpdateTagsPacket(Map> tags) { Map registriesNetworkPayload = (Map) FastNMS.INSTANCE.method$TagNetworkSerialization$serializeTagsToNetwork(); Map modified = new HashMap<>(); - for (Map.Entry> entry : tags.entrySet()) { - Object existingPayload = registriesNetworkPayload.get(entry.getKey()); - if (existingPayload == null) continue; + for (Map.Entry payload : registriesNetworkPayload.entrySet()) { + List overrides = tags.get(payload.getKey()); + if (overrides == null || overrides.isEmpty()) { + modified.put(payload.getKey(), payload.getValue()); + continue; + } FriendlyByteBuf deserializeBuf = new FriendlyByteBuf(Unpooled.buffer()); - FastNMS.INSTANCE.method$TagNetworkSerialization$NetworkPayload$write(existingPayload, deserializeBuf); + FastNMS.INSTANCE.method$TagNetworkSerialization$NetworkPayload$write(payload.getValue(), deserializeBuf); Map originalTags = deserializeBuf.readMap( FriendlyByteBuf::readUtf, FriendlyByteBuf::readIntIdList @@ -58,7 +61,7 @@ public final class TagUtils { reversedTags.computeIfAbsent(id, k -> new ArrayList<>()).add(tagEntry.getKey()); } } - for (TagEntry tagEntry : entry.getValue()) { + for (TagEntry tagEntry : overrides) { reversedTags.remove(tagEntry.id); for (String tag : tagEntry.tags) { reversedTags.computeIfAbsent(tagEntry.id, k -> new ArrayList<>()).add(tag); @@ -76,7 +79,7 @@ public final class TagUtils { FriendlyByteBuf::writeIntIdList ); Object mergedPayload = FastNMS.INSTANCE.method$TagNetworkSerialization$NetworkPayload$read(serializeBuf); - modified.put(entry.getKey(), mergedPayload); + modified.put(payload.getKey(), mergedPayload); } return FastNMS.INSTANCE.constructor$ClientboundUpdateTagsPacket(modified); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java index 67c8e080c..0a7cee294 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitBlockInWorld.java @@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; @@ -40,10 +41,12 @@ public class BukkitBlockInWorld implements BlockInWorld { Optional> customItem = BukkitItemManager.instance().getCustomItem(item.id()); if (customItem.isPresent()) { CustomItem custom = customItem.get(); - if (custom.behaviors() instanceof BlockItemBehavior blockItemBehavior) { - Key blockId = blockItemBehavior.blockId(); - if (blockId.equals(clickedBlockId)) { - return false; + for (ItemBehavior behavior : custom.behaviors()) { + if (behavior instanceof BlockItemBehavior blockItemBehavior) { + Key blockId = blockItemBehavior.blockId(); + if (blockId.equals(clickedBlockId)) { + return false; + } } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java index 1554d3d79..839f8055a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java @@ -3,19 +3,28 @@ package net.momirealms.craftengine.bukkit.world; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.EntityUtils; import net.momirealms.craftengine.bukkit.util.ItemUtils; +import net.momirealms.craftengine.bukkit.util.ParticleUtils; +import net.momirealms.craftengine.bukkit.util.SoundUtils; +import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.BlockInWorld; import net.momirealms.craftengine.core.world.Position; import net.momirealms.craftengine.core.world.World; import net.momirealms.craftengine.core.world.WorldHeight; +import net.momirealms.craftengine.core.world.particle.ParticleData; import org.bukkit.Location; +import org.bukkit.Particle; import org.bukkit.SoundCategory; import org.bukkit.entity.EntityType; import org.bukkit.entity.ExperienceOrb; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.nio.file.Path; import java.util.UUID; @@ -86,13 +95,33 @@ public class BukkitWorld implements World { }); } + @Override + public void playSound(Position location, Key sound, float volume, float pitch, SoundSource source) { + platformWorld().playSound(new Location(null, location.x(), location.y(), location.z()), sound.toString(), SoundUtils.toBukkit(source), volume, pitch); + } + @Override public void playBlockSound(Position location, Key sound, float volume, float pitch) { platformWorld().playSound(new Location(null, location.x(), location.y(), location.z()), sound.toString(), SoundCategory.BLOCKS, volume, pitch); } + @Override + public void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context) { + Particle particleType = ParticleUtils.getParticle(particle); + if (particleType == null) return; + org.bukkit.World platformWorld = platformWorld(); + platformWorld.spawnParticle(particleType, location.x(), location.y(), location.z(), count, xOffset, yOffset, zOffset, speed, extraData == null ? null : ParticleUtils.toBukkitParticleData(extraData, context, platformWorld, location.x(), location.y(), location.z())); + } + @Override public long time() { return platformWorld().getTime(); } + + @Override + public void setBlockAt(int x, int y, int z, BlockStateWrapper blockState, int flags) { + Object worldServer = serverWorld(); + Object blockPos = FastNMS.INSTANCE.constructor$BlockPos(x, y, z); + FastNMS.INSTANCE.method$LevelWriter$setBlock(worldServer, blockPos, blockState.handle(), flags); + } } diff --git a/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/config/ModConfig.java b/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/config/ModConfig.java index dc6689b15..085b3371a 100644 --- a/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/config/ModConfig.java +++ b/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/config/ModConfig.java @@ -18,7 +18,7 @@ import java.nio.file.Path; public class ModConfig { private static final Path CONFIG_PATH = FabricLoader.getInstance().getConfigDir().resolve("craft-engine-fabric-mod/config.yml"); public static boolean enableNetwork = false; - public static boolean enableCancelBlockUpdate = false; + public static boolean enableCancelBlockUpdate = true; public static Screen getConfigScreen(Screen parent) { ConfigBuilder builder = ConfigBuilder.create() diff --git a/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/util/NetWorkDataTypes.java b/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/util/NetWorkDataTypes.java index 63c8bf8be..e1276a008 100644 --- a/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/util/NetWorkDataTypes.java +++ b/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/util/NetWorkDataTypes.java @@ -56,7 +56,7 @@ public class NetWorkDataTypes { return id2NetWorkDataTypes.get(id); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "unused"}) public NetWorkDataTypes as(Class clazz) { return (NetWorkDataTypes) this; } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index c577bb564..29856d15b 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -18,9 +18,14 @@ dependencies { compileOnly("org.yaml:snakeyaml:${rootProject.properties["snake_yaml_version"]}") // NBT implementation("net.momirealms:sparrow-nbt:${rootProject.properties["sparrow_nbt_version"]}") + implementation("net.momirealms:sparrow-nbt-adventure:${rootProject.properties["sparrow_nbt_version"]}") + implementation("net.momirealms:sparrow-nbt-codec:${rootProject.properties["sparrow_nbt_version"]}") + implementation("net.momirealms:sparrow-nbt-legacy-codec:${rootProject.properties["sparrow_nbt_version"]}") + // Util compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}") // Adventure - implementation("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") + // TODO Create an API module + compileOnly("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") compileOnly("net.kyori:adventure-text-minimessage:${rootProject.properties["adventure_bundle_version"]}") compileOnly("net.kyori:adventure-text-serializer-gson:${rootProject.properties["adventure_bundle_version"]}") { exclude("com.google.code.gson", "gson") @@ -40,6 +45,7 @@ dependencies { compileOnly("org.apache.logging.log4j:log4j-core:${rootProject.properties["log4j_version"]}") // Netty compileOnly("io.netty:netty-all:${rootProject.properties["netty_version"]}") + compileOnly("io.netty:netty-codec-http:${rootProject.properties["netty_version"]}") // Cache compileOnly("com.github.ben-manes.caffeine:caffeine:${rootProject.properties["caffeine_version"]}") // Compression @@ -51,13 +57,12 @@ dependencies { compileOnly("com.mojang:datafixerupper:${rootProject.properties["datafixerupper_version"]}") // Aho-Corasick java implementation compileOnly("org.ahocorasick:ahocorasick:${rootProject.properties["ahocorasick_version"]}") - // Amazon S3 - compileOnly("software.amazon.awssdk:s3:${rootProject.properties["amazon_awssdk_version"]}") - compileOnly("software.amazon.awssdk:netty-nio-client:${rootProject.properties["amazon_awssdk_version"]}") // EvalEx compileOnly("com.ezylang:EvalEx:${rootProject.properties["evalex_version"]}") // Jimfs compileOnly("com.google.jimfs:jimfs:${rootProject.properties["jimfs_version"]}") + // S3 + implementation("net.momirealms:craft-engine-s3:0.1") } java { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index ba3e03d8f..48bde480b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -1,9 +1,14 @@ package net.momirealms.craftengine.core.block; +import com.google.gson.JsonElement; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.Key; import org.incendo.cloud.suggestion.Suggestion; +import org.jetbrains.annotations.NotNull; import java.util.*; @@ -14,11 +19,46 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem protected final List cachedSuggestions = new ArrayList<>(); // Cached Namespace protected final Set namespacesInUse = new HashSet<>(); + // for mod, real block id -> state models + protected final Map modBlockStates = new HashMap<>(); + // A temporary map that stores the model path of a certain vanilla block state + protected final Map tempVanillaBlockStateModels = new Int2ObjectOpenHashMap<>(); + // A temporary map used to detect whether the same block state corresponds to multiple models. + protected final Map tempRegistryIdConflictMap = new Int2ObjectOpenHashMap<>(); + // A temporary map that converts the custom block registered on the server to the vanilla block ID. + protected final Map tempBlockAppearanceConvertor = new Int2IntOpenHashMap(); + // Used to store override information of json files + protected final Map> blockStateOverrides = new HashMap<>(); + // a reverted mapper + protected final Map> appearanceToRealState = new Int2ObjectOpenHashMap<>(); + // client side block tags + protected Map> clientBoundTags = Map.of(); + protected Map> previousClientBoundTags = Map.of(); - public AbstractBlockManager(CraftEngine plugin) { + protected AbstractBlockManager(CraftEngine plugin) { super(plugin); } + @Override + public void unload() { + super.clearModelsToGenerate(); + this.clearCache(); + this.cachedSuggestions.clear(); + this.blockStateOverrides.clear(); + this.modBlockStates.clear(); + this.byId.clear(); + this.previousClientBoundTags = this.clientBoundTags; + this.clientBoundTags = new HashMap<>(); + this.appearanceToRealState.clear(); + } + + @Override + public void delayedLoad() { + this.initSuggestions(); + this.clearCache(); + this.resendTags(); + } + @Override public Map blocks() { return Collections.unmodifiableMap(this.byId); @@ -30,10 +70,24 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } @Override - public void unload() { - super.clearModelsToGenerate(); - this.cachedSuggestions.clear(); - this.byId.clear(); + public void addBlock(Key id, CustomBlock customBlock) { + this.byId.put(id, customBlock); + // generate mod assets + if (Config.generateModAssets()) { + for (ImmutableBlockState state : customBlock.variantProvider().states()) { + this.modBlockStates.put(getBlockOwnerId(state.customBlockState()), this.tempVanillaBlockStateModels.get(state.vanillaBlockState().registryId())); + } + } + } + + @Override + public Map modBlockStates() { + return Collections.unmodifiableMap(this.modBlockStates); + } + + @Override + public Map> blockOverrides() { + return Collections.unmodifiableMap(this.blockStateOverrides); } @Override @@ -45,6 +99,12 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem return Collections.unmodifiableSet(this.namespacesInUse); } + protected void clearCache() { + this.tempRegistryIdConflictMap.clear(); + this.tempBlockAppearanceConvertor.clear(); + this.tempVanillaBlockStateModels.clear(); + } + protected void initSuggestions() { this.cachedSuggestions.clear(); this.namespacesInUse.clear(); @@ -60,4 +120,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.cachedSuggestions.add(Suggestion.suggestion(state)); } } + + @NotNull + public List appearanceToRealStates(int appearanceStateId) { + return Optional.ofNullable(this.appearanceToRealState.get(appearanceStateId)).orElse(List.of()); + } + + protected abstract void resendTags(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java new file mode 100644 index 000000000..c91a7bacf --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractCustomBlock.java @@ -0,0 +1,207 @@ +package net.momirealms.craftengine.core.block; + +import com.google.common.collect.ImmutableMap; +import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviors; +import net.momirealms.craftengine.core.block.behavior.UnsafeCompositeBlockBehavior; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.loot.LootTable; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; +import net.momirealms.craftengine.core.plugin.context.function.Function; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.registry.Holder; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.shared.block.BlockBehavior; +import net.momirealms.craftengine.shared.block.EmptyBlockBehavior; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.BiFunction; + +public abstract class AbstractCustomBlock implements CustomBlock { + protected final Holder holder; + protected final Key id; + protected final BlockStateVariantProvider variantProvider; + protected final Map> properties; + protected final BlockBehavior behavior; + protected final BiFunction placementFunction; + protected final ImmutableBlockState defaultState; + protected final Map>> events; + @Nullable + protected final LootTable lootTable; + + protected AbstractCustomBlock( + @NotNull Key id, + @NotNull Holder.Reference holder, + @NotNull Map> properties, + @NotNull Map appearances, + @NotNull Map variantMapper, + @NotNull BlockSettings settings, + @NotNull Map>> events, + @NotNull List> behaviorConfig, + @Nullable LootTable lootTable + ) { + holder.bindValue(this); + this.holder = holder; + this.id = id; + this.lootTable = lootTable; + this.properties = ImmutableMap.copyOf(properties); + this.events = events; + this.variantProvider = new BlockStateVariantProvider(holder, ImmutableBlockState::new, properties); + this.defaultState = this.variantProvider.getDefaultState(); + if (behaviorConfig.isEmpty()) { + this.behavior = new EmptyBlockBehavior(); + } else if (behaviorConfig.size() == 1) { + this.behavior = BlockBehaviors.fromMap(this, behaviorConfig.get(0)); + } else { + List behaviors = new ArrayList<>(); + for (Map config : behaviorConfig) { + behaviors.add((AbstractBlockBehavior) BlockBehaviors.fromMap(this, config)); + } + this.behavior = new UnsafeCompositeBlockBehavior(this, behaviors); + } + List> placements = new ArrayList<>(4); + for (Map.Entry> propertyEntry : this.properties.entrySet()) { + placements.add(Property.createStateForPlacement(propertyEntry.getKey(), propertyEntry.getValue())); + } + this.placementFunction = composite(placements); + for (Map.Entry entry : variantMapper.entrySet()) { + String nbtString = entry.getKey(); + CompoundTag tag = BlockNbtParser.deserialize(this, nbtString); + if (tag == null) { + throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", nbtString); + } + VariantState variantState = entry.getValue(); + int vanillaStateRegistryId = appearances.getOrDefault(variantState.appearance(), -1); + // This should never happen + if (vanillaStateRegistryId == -1) { + vanillaStateRegistryId = appearances.values().iterator().next(); + } + // Late init states + for (ImmutableBlockState state : this.getPossibleStates(tag)) { + state.setBehavior(this.behavior); + state.setSettings(variantState.settings()); + state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId)); + state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(variantState.internalRegistryId())); + } + } + // double check if there's any invalid state + for (ImmutableBlockState state : this.variantProvider().states()) { + if (state.settings() == null) { + state.setSettings(settings); + } + } + this.applyPlatformSettings(); + } + + private static BiFunction composite(List> placements) { + return switch (placements.size()) { + case 0 -> (c, i) -> i; + case 1 -> placements.get(0); + case 2 -> { + BiFunction f1 = placements.get(0); + BiFunction f2 = placements.get(1); + yield (c, i) -> f2.apply(c, f1.apply(c, i)); + } + default -> (c, i) -> { + for (BiFunction f : placements) { + i = f.apply(c, i); + } + return i; + }; + }; + } + + protected abstract void applyPlatformSettings(); + + @Override + public @Nullable LootTable lootTable() { + return this.lootTable; + } + + @Override + public void execute(PlayerOptionalContext context, EventTrigger trigger) { + for (Function function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) { + function.run(context); + } + } + + @NotNull + @Override + public BlockStateVariantProvider variantProvider() { + return this.variantProvider; + } + + @NotNull + @Override + public final Key id() { + return this.id; + } + + @Override + public List getPossibleStates(CompoundTag nbt) { + List tempStates = new ArrayList<>(); + tempStates.add(defaultState()); + for (Property property : this.variantProvider.getDefaultState().getProperties()) { + Tag value = nbt.get(property.name()); + if (value != null) { + tempStates.replaceAll(immutableBlockState -> ImmutableBlockState.with(immutableBlockState, property, property.unpack(value))); + } else { + List newStates = new ArrayList<>(); + for (ImmutableBlockState state : tempStates) { + for (Object possibleValue : property.possibleValues()) { + newStates.add(ImmutableBlockState.with(state, property, possibleValue)); + } + } + tempStates = newStates; + } + } + return tempStates; + } + + @Override + public ImmutableBlockState getBlockState(CompoundTag nbt) { + ImmutableBlockState state = defaultState(); + for (Map.Entry entry : nbt.tags.entrySet()) { + Property property = this.variantProvider.getProperty(entry.getKey()); + if (property != null) { + try { + state = ImmutableBlockState.with(state, property, property.unpack(entry.getValue())); + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to parse block state: " + entry.getKey(), e); + } + } + } + return state; + } + + @Override + public @Nullable Property getProperty(String name) { + return this.properties.get(name); + } + + @Override + public @NotNull Collection> properties() { + return this.properties.values(); + } + + @Override + public final ImmutableBlockState defaultState() { + return this.defaultState; + } + + @Override + public ImmutableBlockState getStateForPlacement(BlockPlaceContext context) { + ImmutableBlockState state = this.placementFunction.apply(context, defaultState()); + if (this.behavior instanceof AbstractBlockBehavior blockBehavior) { + state = blockBehavior.updateStateForPlacement(context, state); + } + return state; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java index 8cbeb5a5a..0319ecfd9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockManager.java @@ -7,6 +7,8 @@ import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.util.Key; import org.incendo.cloud.suggestion.Suggestion; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Map; @@ -26,7 +28,22 @@ public interface BlockManager extends Manageable, ModelGenerator { Optional blockById(Key key); + void addBlock(Key id, CustomBlock customBlock); + Collection cachedSuggestions(); Map soundMapper(); + + int availableAppearances(Key blockType); + + Key getBlockOwnerId(BlockStateWrapper state); + + @NotNull + ImmutableBlockState getImmutableBlockStateUnsafe(int stateId); + + @Nullable + ImmutableBlockState getImmutableBlockState(int stateId); + + @Nullable + BlockStateWrapper createPackedBlockState(String blockState); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java index f93bf3a57..72cb6de1c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockRegistryMirror.java @@ -1,15 +1,15 @@ package net.momirealms.craftengine.core.block; public class BlockRegistryMirror { - private static PackedBlockState[] customBlockStates; - private static PackedBlockState stoneState; + private static BlockStateWrapper[] customBlockStates; + private static BlockStateWrapper stoneState; - public static void init(PackedBlockState[] states, PackedBlockState state) { + public static void init(BlockStateWrapper[] states, BlockStateWrapper state) { customBlockStates = states; stoneState = state; } - public static PackedBlockState stateByRegistryId(int vanillaId) { + public static BlockStateWrapper stateByRegistryId(int vanillaId) { if (vanillaId < 0) return stoneState; return customBlockStates[vanillaId]; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java index cfcfd042c..aefe694fb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockSettings.java @@ -12,11 +12,11 @@ public class BlockSettings { boolean burnable; int burnChance; int fireSpreadChance; - int blockLight = -1; // TODO investigate how starlight works + int blockLight = -1; boolean replaceable; float hardness = 2f; float resistance = 2f; - boolean canOcclude; + Tristate canOcclude = Tristate.UNDEFINED; boolean fluidState; boolean requireCorrectTools; boolean respectToolComponent; @@ -34,6 +34,8 @@ public class BlockSettings { float incorrectToolSpeed = 0.3f; Set correctTools = Set.of(); String name; + String supportShapeBlockState; + boolean propagatesSkylightDown; private BlockSettings() {} @@ -42,6 +44,7 @@ public class BlockSettings { } public static BlockSettings fromMap(Map map) { + if (map == null || map.isEmpty()) return BlockSettings.of(); return applyModifiers(BlockSettings.of(), map); } @@ -88,6 +91,8 @@ public class BlockSettings { newSettings.blockLight = settings.blockLight; newSettings.name = settings.name; newSettings.incorrectToolSpeed = settings.incorrectToolSpeed; + newSettings.supportShapeBlockState = settings.supportShapeBlockState; + newSettings.propagatesSkylightDown = settings.propagatesSkylightDown; return newSettings; } @@ -127,7 +132,7 @@ public class BlockSettings { return hardness; } - public boolean canOcclude() { + public Tristate canOcclude() { return canOcclude; } @@ -191,6 +196,14 @@ public class BlockSettings { return respectToolComponent; } + public String supportShapeBlockState() { + return supportShapeBlockState; + } + + public boolean propagatesSkylightDown() { + return propagatesSkylightDown; + } + public BlockSettings correctTools(Set correctTools) { this.correctTools = correctTools; return this; @@ -256,7 +269,7 @@ public class BlockSettings { return this; } - public BlockSettings canOcclude(boolean canOcclude) { + public BlockSettings canOcclude(Tristate canOcclude) { this.canOcclude = canOcclude; return this; } @@ -286,6 +299,11 @@ public class BlockSettings { return this; } + public BlockSettings propagatesSkylightDown(boolean propagatesSkylightDown) { + this.propagatesSkylightDown = propagatesSkylightDown; + return this; + } + public BlockSettings blockLight(int intValue) { this.blockLight = intValue; return this; @@ -316,6 +334,11 @@ public class BlockSettings { return this; } + public BlockSettings supportShapeBlockState(String supportShapeBlockState) { + this.supportShapeBlockState = supportShapeBlockState; + return this; + } + public interface Modifier { void apply(BlockSettings settings); @@ -350,6 +373,10 @@ public class BlockSettings { boolean booleanValue = (boolean) value; return settings -> settings.isRandomlyTicking(booleanValue); })); + registerFactory("propagate-skylight", (value -> { + boolean booleanValue = (boolean) value; + return settings -> settings.propagatesSkylightDown(booleanValue); + })); registerFactory("push-reaction", (value -> { PushReaction reaction = PushReaction.valueOf(value.toString().toUpperCase(Locale.ENGLISH)); return settings -> settings.pushReaction(reaction); @@ -408,7 +435,7 @@ public class BlockSettings { })); registerFactory("can-occlude", (value -> { boolean booleanValue = (boolean) value; - return settings -> settings.canOcclude(booleanValue); + return settings -> settings.canOcclude(booleanValue ? Tristate.FALSE : Tristate.TRUE); })); registerFactory("correct-tools", (value -> { List tools = MiscUtils.getAsStringList(value); @@ -430,6 +457,10 @@ public class BlockSettings { String name = value.toString(); return settings -> settings.name(name); })); + registerFactory("support-shape", (value -> { + String shape = value.toString(); + return settings -> settings.supportShapeBlockState(shape); + })); } private static void registerFactory(String id, Modifier.Factory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java index 7d9c265b3..445f9c518 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateHolder.java @@ -19,7 +19,7 @@ public class BlockStateHolder { } public Holder owner() { - return owner; + return this.owner; } public > ImmutableBlockState cycle(Property property) { @@ -38,14 +38,14 @@ public class BlockStateHolder { @Override public String toString() { - if (propertyMap.isEmpty()) { - return owner.value().id().toString(); + if (this.propertyMap.isEmpty()) { + return this.owner.value().id().toString(); } - return owner.value().id() + "[" + getPropertiesAsString() + "]"; + return this.owner.value().id() + "[" + getPropertiesAsString() + "]"; } public String getPropertiesAsString() { - return propertyMap.entrySet().stream() + return this.propertyMap.entrySet().stream() .map(entry -> { Property property = entry.getKey(); return property.name() + "=" + Property.formatValue(property, entry.getValue()); @@ -54,17 +54,17 @@ public class BlockStateHolder { } public Collection> getProperties() { - return Collections.unmodifiableSet(propertyMap.keySet()); + return Collections.unmodifiableSet(this.propertyMap.keySet()); } public > boolean contains(Property property) { - return propertyMap.containsKey(property); + return this.propertyMap.containsKey(property); } public > T get(Property property) { T value = getNullable(property); if (value == null) { - throw new IllegalArgumentException("Property " + property + " not found in " + owner.value().id()); + throw new IllegalArgumentException("Property " + property + " not found in " + this.owner.value().id()); } return value; } @@ -75,19 +75,19 @@ public class BlockStateHolder { @Nullable public > T getNullable(Property property) { - Comparable value = propertyMap.get(property); + Comparable value = this.propertyMap.get(property); return value != null ? property.valueClass().cast(value) : null; } public , V extends T> ImmutableBlockState with(Property property, V value) { - if (!propertyMap.containsKey(property)) { - throw new IllegalArgumentException("Property " + property + " not found in " + owner.value().id()); + if (!this.propertyMap.containsKey(property)) { + throw new IllegalArgumentException("Property " + property + " not found in " + this.owner.value().id()); } return withInternal(property, value); } private , V extends T> ImmutableBlockState withInternal(Property property, V newValue) { - if (newValue.equals(propertyMap.get(property))) { + if (newValue.equals(this.propertyMap.get(property))) { return (ImmutableBlockState) this; } @@ -96,20 +96,20 @@ public class BlockStateHolder { throw new IllegalArgumentException("Invalid value " + newValue + " for property " + property); } - return withMap.get(property)[index]; + return this.withMap.get(property)[index]; } public void createWithMap(Map, Comparable>, ImmutableBlockState> states) { - if (withMap != null) { + if (this.withMap != null) { throw new IllegalStateException("WithMap already initialized"); } - Reference2ObjectArrayMap, ImmutableBlockState[]> map = new Reference2ObjectArrayMap<>(propertyMap.size()); + Reference2ObjectArrayMap, ImmutableBlockState[]> map = new Reference2ObjectArrayMap<>(this.propertyMap.size()); - for (Property property : propertyMap.keySet()) { + for (Property property : this.propertyMap.keySet()) { ImmutableBlockState[] statesArray = property.possibleValues().stream() .map(value -> { - Map, Comparable> testMap = new Reference2ObjectArrayMap<>(propertyMap); + Map, Comparable> testMap = new Reference2ObjectArrayMap<>(this.propertyMap); testMap.put(property, value); ImmutableBlockState state = states.get(testMap); if (state == null) { @@ -126,6 +126,6 @@ public class BlockStateHolder { } public Map, Comparable> propertyEntries() { - return Collections.unmodifiableMap(propertyMap); + return Collections.unmodifiableMap(this.propertyMap); } } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateMatcher.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateMatcher.java index 3886220dc..87678487c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateMatcher.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateMatcher.java @@ -24,7 +24,7 @@ public class BlockStateMatcher { } public boolean matches(ImmutableBlockState state) { - if (!state.owner().value().id.equals(this.id)) { + if (!state.owner().value().id().equals(this.id)) { return false; } for (Pair, Comparable> pair : this.properties) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java new file mode 100644 index 000000000..319a105af --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateWrapper.java @@ -0,0 +1,67 @@ +package net.momirealms.craftengine.core.block; + +public interface BlockStateWrapper { + + Object handle(); + + int registryId(); + + boolean isVanillaBlock(); + + static BlockStateWrapper vanilla(Object handle, int registryId) { + return new VanillaBlockState(handle, registryId); + } + + static BlockStateWrapper custom(Object handle, int registryId) { + return new CustomBlockState(handle, registryId); + } + + static BlockStateWrapper create(Object handle, int registryId, boolean isVanillaBlock) { + if (isVanillaBlock) return new VanillaBlockState(handle, registryId); + else return new CustomBlockState(handle, registryId); + } + + abstract class AbstractBlockState implements BlockStateWrapper { + protected final Object handle; + protected final int registryId; + + public AbstractBlockState(Object handle, int registryId) { + this.handle = handle; + this.registryId = registryId; + } + + @Override + public Object handle() { + return this.handle; + } + + @Override + public int registryId() { + return this.registryId; + } + } + + class VanillaBlockState extends AbstractBlockState { + + public VanillaBlockState(Object handle, int registryId) { + super(handle, registryId); + } + + @Override + public boolean isVanillaBlock() { + return true; + } + } + + class CustomBlockState extends AbstractBlockState { + + public CustomBlockState(Object handle, int registryId) { + super(handle, registryId); + } + + @Override + public boolean isVanillaBlock() { + return false; + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java index 73c324414..0ff330d4b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/CustomBlock.java @@ -1,251 +1,58 @@ package net.momirealms.craftengine.core.block; -import net.momirealms.craftengine.core.block.behavior.AbstractBlockBehavior; -import net.momirealms.craftengine.core.block.behavior.BlockBehaviors; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.loot.LootTable; -import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.function.Function; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; -import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; -import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.shared.block.BlockBehavior; import net.momirealms.sparrow.nbt.CompoundTag; -import net.momirealms.sparrow.nbt.Tag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; -import java.util.function.BiFunction; +import java.util.Collection; +import java.util.List; +import java.util.Map; -public abstract class CustomBlock { - protected final Holder holder; - protected final Key id; - protected final BlockStateVariantProvider variantProvider; - protected final Map> properties; - protected final BlockBehavior behavior; - protected final List> placements; - protected final ImmutableBlockState defaultState; - protected final EnumMap>> events; - @Nullable - protected final LootTable lootTable; +public interface CustomBlock { - protected CustomBlock( - @NotNull Key id, - @NotNull Holder.Reference holder, - @NotNull Map> properties, - @NotNull Map appearances, - @NotNull Map variantMapper, - @NotNull BlockSettings settings, - @NotNull EnumMap>> events, - @Nullable Map behavior, - @Nullable LootTable lootTable - ) { - holder.bindValue(this); - this.holder = holder; - this.id = id; - this.lootTable = lootTable; - this.properties = properties; - this.placements = new ArrayList<>(); - this.events = events; - this.variantProvider = new BlockStateVariantProvider(holder, ImmutableBlockState::new, properties); - this.defaultState = this.variantProvider.getDefaultState(); - this.behavior = BlockBehaviors.fromMap(this, behavior); - for (Map.Entry entry : variantMapper.entrySet()) { - String nbtString = entry.getKey(); - CompoundTag tag = BlockNbtParser.deserialize(this, nbtString); - if (tag == null) { - throw new LocalizedResourceConfigException("warning.config.block.state.property.invalid_format", nbtString); - } - VariantState variantState = entry.getValue(); - int vanillaStateRegistryId = appearances.getOrDefault(variantState.appearance(), -1); - // This should never happen - if (vanillaStateRegistryId == -1) { - vanillaStateRegistryId = appearances.values().iterator().next(); - } - // Late init states - for (ImmutableBlockState state : this.getPossibleStates(tag)) { - state.setBehavior(this.behavior); - state.setSettings(variantState.settings()); - state.setVanillaBlockState(BlockRegistryMirror.stateByRegistryId(vanillaStateRegistryId)); - state.setCustomBlockState(BlockRegistryMirror.stateByRegistryId(variantState.internalRegistryId())); - } - } - // double check if there's any invalid state - for (ImmutableBlockState state : this.variantProvider().states()) { - if (state.settings() == null) { - state.setSettings(settings); - } - } - this.applyPlatformSettings(); - for (Map.Entry> propertyEntry : this.properties.entrySet()) { - this.placements.add(Property.createStateForPlacement(propertyEntry.getKey(), propertyEntry.getValue())); - } - } + Key id(); - protected abstract void applyPlatformSettings(); + @Nullable LootTable lootTable(); - @Nullable - public LootTable lootTable() { - return lootTable; - } + void execute(PlayerOptionalContext context, EventTrigger trigger); - public void execute(PlayerOptionalContext context, EventTrigger trigger) { - for (Function function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) { - function.run(context); - } - } + @NotNull BlockStateVariantProvider variantProvider(); - @NotNull - public BlockStateVariantProvider variantProvider() { - return variantProvider; - } + List getPossibleStates(CompoundTag nbt); - @NotNull - public final Key id() { - return id; - } + ImmutableBlockState getBlockState(CompoundTag nbt); - private List getPossibleStates(CompoundTag nbt) { - List tempStates = new ArrayList<>(); - tempStates.add(defaultState()); - for (Property property : variantProvider.getDefaultState().getProperties()) { - Tag value = nbt.get(property.name()); - if (value != null) { - tempStates.replaceAll(immutableBlockState -> ImmutableBlockState.with(immutableBlockState, property, property.unpack(value))); - } else { - List newStates = new ArrayList<>(); - for (ImmutableBlockState state : tempStates) { - for (Object possibleValue : property.possibleValues()) { - newStates.add(ImmutableBlockState.with(state, property, possibleValue)); - } - } - tempStates = newStates; - } - } - return tempStates; - } + @Nullable Property getProperty(String name); - public ImmutableBlockState getBlockState(CompoundTag nbt) { - ImmutableBlockState state = defaultState(); - for (Map.Entry entry : nbt.tags.entrySet()) { - Property property = this.variantProvider.getProperty(entry.getKey()); - if (property != null) { - try { - state = ImmutableBlockState.with(state, property, property.unpack(entry.getValue())); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to parse block state: " + entry.getKey(), e); - } - } - } - return state; - } + @NotNull Collection> properties(); - @Nullable - public Property getProperty(String name) { - return this.properties.get(name); - } + ImmutableBlockState defaultState(); - @NotNull - public Collection> properties() { - return this.properties.values(); - } + ImmutableBlockState getStateForPlacement(BlockPlaceContext context); - public final ImmutableBlockState defaultState() { - return this.defaultState; - } + interface Builder { - public ImmutableBlockState getStateForPlacement(BlockPlaceContext context) { - ImmutableBlockState state = defaultState(); - for (BiFunction placement : this.placements) { - state = placement.apply(context, state); - } - if (this.behavior instanceof AbstractBlockBehavior blockBehavior) { - state = blockBehavior.updateStateForPlacement(context, state); - } - return state; - } + Builder events(Map>> events); - public abstract static class Builder { - protected final Key id; - protected Map> properties; - protected Map appearances; - protected Map variantMapper; - protected BlockSettings settings; - protected Map behavior; - protected LootTable lootTable; - protected EnumMap>> events; + Builder appearances(Map appearances); - protected Builder(Key id) { - this.id = id; - } + Builder behavior(List> behavior); - public Builder events(EnumMap>> events) { - this.events = events; - return this; - } + Builder lootTable(LootTable lootTable); - public Builder appearances(Map appearances) { - this.appearances = appearances; - return this; - } + Builder properties(Map> properties); - public Builder behavior(Map behavior) { - this.behavior = behavior; - return this; - } + Builder settings(BlockSettings settings); - public Builder lootTable(LootTable lootTable) { - this.lootTable = lootTable; - return this; - } + Builder variantMapper(Map variantMapper); - public Builder properties(Map> properties) { - this.properties = properties; - return this; - } - - public Builder settings(BlockSettings settings) { - this.settings = settings; - return this; - } - - public Builder variantMapper(Map variantMapper) { - this.variantMapper = variantMapper; - return this; - } - - public Map appearances() { - return appearances; - } - - public Map behavior() { - return behavior; - } - - public Key id() { - return id; - } - - public LootTable lootTable() { - return lootTable; - } - - public Map> properties() { - return properties; - } - - public BlockSettings settings() { - return settings; - } - - public Map variantMapper() { - return variantMapper; - } - - public abstract CustomBlock build(); + @NotNull CustomBlock build(); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/DelayedInitBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/DelayedInitBlockState.java new file mode 100644 index 000000000..253601aaa --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/DelayedInitBlockState.java @@ -0,0 +1,22 @@ +package net.momirealms.craftengine.core.block; + +import net.momirealms.craftengine.core.plugin.CraftEngine; + +public class DelayedInitBlockState { + private final String state; + private BlockStateWrapper packedBlockState; + + public DelayedInitBlockState(String state) { + this.state = state; + } + + public BlockStateWrapper getState() { + if (this.packedBlockState == null) { + this.packedBlockState = CraftEngine.instance().blockManager().createPackedBlockState(state); + if (this.packedBlockState == null) { + CraftEngine.instance().logger().warn("Could not create block state: " + this.state); + } + } + return this.packedBlockState; + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java index 4e5cca009..d6e65cf34 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/EmptyBlock.java @@ -1,18 +1,17 @@ package net.momirealms.craftengine.core.block; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.Key; -import java.util.EnumMap; +import java.util.List; import java.util.Map; -public class EmptyBlock extends CustomBlock { +public class EmptyBlock extends AbstractCustomBlock { public static EmptyBlock INSTANCE; public static ImmutableBlockState STATE; public EmptyBlock(Key id, Holder.Reference holder) { - super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), new EnumMap<>(EventTrigger.class), null, null); + super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), Map.of(), List.of(), null); INSTANCE = this; STATE = defaultState(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index 772468379..0123b54d9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -14,14 +14,14 @@ import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.NBT; import net.momirealms.sparrow.nbt.Tag; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.List; public class ImmutableBlockState extends BlockStateHolder { private CompoundTag tag; - private PackedBlockState customBlockState; - private PackedBlockState vanillaBlockState; + private BlockStateWrapper customBlockState; + private BlockStateWrapper vanillaBlockState; private BlockBehavior behavior; private Integer hashCode; @@ -35,7 +35,7 @@ public class ImmutableBlockState extends BlockStateHolder { } public BlockBehavior behavior() { - return behavior; + return this.behavior; } public void setBehavior(BlockBehavior behavior) { @@ -43,7 +43,7 @@ public class ImmutableBlockState extends BlockStateHolder { } public BlockSettings settings() { - return settings; + return this.settings; } public void setSettings(BlockSettings settings) { @@ -81,19 +81,19 @@ public class ImmutableBlockState extends BlockStateHolder { return settings.pushReaction; } - public PackedBlockState customBlockState() { + public BlockStateWrapper customBlockState() { return this.customBlockState; } - public PackedBlockState vanillaBlockState() { + public BlockStateWrapper vanillaBlockState() { return this.vanillaBlockState; } - public void setCustomBlockState(@NotNull PackedBlockState customBlockState) { + public void setCustomBlockState(@NotNull BlockStateWrapper customBlockState) { this.customBlockState = customBlockState; } - public void setVanillaBlockState(@NotNull PackedBlockState vanillaBlockState) { + public void setVanillaBlockState(@NotNull BlockStateWrapper vanillaBlockState) { this.vanillaBlockState = vanillaBlockState; } @@ -116,16 +116,16 @@ public class ImmutableBlockState extends BlockStateHolder { } public CompoundTag getNbtToSave() { - if (tag == null) { - tag = toNbtToSave(propertiesNbt()); + if (this.tag == null) { + this.tag = toNbtToSave(propertiesNbt()); } - return tag; + return this.tag; } public CompoundTag toNbtToSave(CompoundTag properties) { CompoundTag tag = new CompoundTag(); tag.put("properties", properties); - tag.put("id", NBT.createString(owner.value().id().toString())); + tag.put("id", NBT.createString(this.owner.value().id().asString())); return tag; } @@ -140,10 +140,10 @@ public class ImmutableBlockState extends BlockStateHolder { @SuppressWarnings("unchecked") public List> getDrops(@NotNull ContextHolder.Builder builder, @NotNull World world, @Nullable Player player) { - CustomBlock block = owner.value(); + CustomBlock block = this.owner.value(); if (block == null) return List.of(); LootTable lootTable = (LootTable) block.lootTable(); if (lootTable == null) return List.of(); - return lootTable.getRandomItems(builder.withParameter(DirectContextParameters.BLOCK_STATE, this).build(), world, player); + return lootTable.getRandomItems(builder.withParameter(DirectContextParameters.CUSTOM_BLOCK_STATE, this).build(), world, player); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java b/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java index a733f2bfd..70bfa59b9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/InactiveCustomBlock.java @@ -1,20 +1,19 @@ package net.momirealms.craftengine.core.block; import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.Key; import net.momirealms.sparrow.nbt.CompoundTag; -import java.util.EnumMap; import java.util.HashMap; +import java.util.List; import java.util.Map; -public class InactiveCustomBlock extends CustomBlock { +public class InactiveCustomBlock extends AbstractCustomBlock { private final Map cachedData = new HashMap<>(); public InactiveCustomBlock(Key id, Holder.Reference holder) { - super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), new EnumMap<>(EventTrigger.class), null, null); + super(id, holder, Map.of(), Map.of(), Map.of(), BlockSettings.of(), Map.of(), List.of(), null); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/PackedBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/PackedBlockState.java deleted file mode 100644 index 9abfb6db8..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/block/PackedBlockState.java +++ /dev/null @@ -1,4 +0,0 @@ -package net.momirealms.craftengine.core.block; - -public record PackedBlockState(Object handle, int registryId) { -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/UnsafeBlockStateMatcher.java b/core/src/main/java/net/momirealms/craftengine/core/block/UnsafeBlockStateMatcher.java index 00b9d4b49..f0174a4ad 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/UnsafeBlockStateMatcher.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/UnsafeBlockStateMatcher.java @@ -24,7 +24,7 @@ public class UnsafeBlockStateMatcher { } public boolean matches(ImmutableBlockState state) { - if (!state.owner().value().id.equals(this.id)) { + if (!state.owner().value().id().equals(this.id)) { return false; } CustomBlock customBlock = state.owner().value(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/VanillaBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/VanillaBlockState.java new file mode 100644 index 000000000..e86fcd003 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/VanillaBlockState.java @@ -0,0 +1,27 @@ +package net.momirealms.craftengine.core.block; + +import net.momirealms.craftengine.core.util.Key; + +public class VanillaBlockState { + private final Key type; + private final String properties; + private final int registryId; + + public VanillaBlockState(Key type, String properties, int registryId) { + this.properties = properties; + this.registryId = registryId; + this.type = type; + } + + public String properties() { + return properties; + } + + public int registryId() { + return registryId; + } + + public Key type() { + return type; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/AbstractBlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/AbstractBlockBehavior.java index ae8d38e1e..1a6dbbc56 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/AbstractBlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/AbstractBlockBehavior.java @@ -2,7 +2,9 @@ package net.momirealms.craftengine.core.block.behavior; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.shared.block.BlockBehavior; public abstract class AbstractBlockBehavior extends BlockBehavior { @@ -15,4 +17,8 @@ public abstract class AbstractBlockBehavior extends BlockBehavior { public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { return state; } + + public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { + return InteractionResult.PASS; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/BlockBehaviors.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/BlockBehaviors.java index d75ffc494..610dbb221 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/BlockBehaviors.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/BlockBehaviors.java @@ -30,7 +30,7 @@ public class BlockBehaviors { Key key = Key.withDefaultNamespace(type, Key.DEFAULT_NAMESPACE); BlockBehaviorFactory factory = BuiltInRegistries.BLOCK_BEHAVIOR_FACTORY.getValue(key); if (factory == null) { - throw new LocalizedResourceConfigException("warning.config.block.behavior.invalid_type", type.toString()); + throw new LocalizedResourceConfigException("warning.config.block.behavior.invalid_type", type); } return factory.create(block, map); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/UnsafeCompositeBlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/UnsafeCompositeBlockBehavior.java new file mode 100644 index 000000000..58f844744 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/UnsafeCompositeBlockBehavior.java @@ -0,0 +1,166 @@ +package net.momirealms.craftengine.core.block.behavior; + +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.shared.block.BlockBehavior; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Callable; + +public class UnsafeCompositeBlockBehavior extends AbstractBlockBehavior { + private final AbstractBlockBehavior[] behaviors; + + public UnsafeCompositeBlockBehavior(CustomBlock customBlock, List behaviors) { + super(customBlock); + this.behaviors = behaviors.toArray(new AbstractBlockBehavior[0]); + } + + @SuppressWarnings("unchecked") + @Override + public Optional getAs(Class tClass) { + for (AbstractBlockBehavior behavior : this.behaviors) { + if (tClass.isInstance(behavior)) { + return Optional.of((T) behavior); + } + } + return Optional.empty(); + } + + @Override + public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) { + for (AbstractBlockBehavior behavior : this.behaviors) { + InteractionResult result = behavior.useOnBlock(context, state); + if (result != InteractionResult.PASS) { + return result; + } + } + return super.useOnBlock(context, state); + } + + @Override + public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { + for (AbstractBlockBehavior behavior : this.behaviors) { + state = behavior.updateStateForPlacement(context, state); + } + return state; + } + + @Override + public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object previous = args[0]; + for (AbstractBlockBehavior behavior : this.behaviors) { + Object processed = behavior.updateShape(thisBlock, args, superMethod); + if (processed != previous) { + return processed; + } + } + return previous; + } + + @Override + public void tick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + behavior.tick(thisBlock, args, superMethod); + } + } + + @Override + public void randomTick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + behavior.randomTick(thisBlock, args, superMethod); + } + } + + @Override + public Object rotate(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object previous = args[0]; + for (AbstractBlockBehavior behavior : this.behaviors) { + Object processed = behavior.rotate(thisBlock, args, superMethod); + if (processed != previous) { + return processed; + } + } + return previous; + } + + @Override + public Object mirror(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Object previous = args[0]; + for (AbstractBlockBehavior behavior : this.behaviors) { + Object processed = behavior.mirror(thisBlock, args, superMethod); + if (processed != previous) { + return processed; + } + } + return previous; + } + + @Override + public void performBoneMeal(Object thisBlock, Object[] args) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + behavior.performBoneMeal(thisBlock, args); + } + } + + @Override + public void onPlace(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + behavior.onPlace(thisBlock, args, superMethod); + } + } + + @Override + public void onLand(Object thisBlock, Object[] args) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + behavior.onLand(thisBlock, args); + } + } + + @Override + public void onBrokenAfterFall(Object thisBlock, Object[] args) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + behavior.onBrokenAfterFall(thisBlock, args); + } + } + + @Override + public void neighborChanged(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + behavior.neighborChanged(thisBlock, args, superMethod); + } + } + + @Override + public boolean isValidBoneMealTarget(Object thisBlock, Object[] args) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + if (behavior.isValidBoneMealTarget(thisBlock, args)) { + return true; + } + } + return false; + } + + @Override + public boolean isBoneMealSuccess(Object thisBlock, Object[] args) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + if (behavior.isBoneMealSuccess(thisBlock, args)) { + return true; + } + } + return false; + } + + @Override + public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + for (AbstractBlockBehavior behavior : this.behaviors) { + if (!behavior.canSurvive(thisBlock, args, superMethod)) { + return false; + } + } + return true; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java index a61d33e00..84bbfda65 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Property.java @@ -5,8 +5,8 @@ import net.momirealms.craftengine.core.item.context.BlockPlaceContext; import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.HorizontalDirection; import net.momirealms.sparrow.nbt.Tag; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/AbstractEntity.java b/core/src/main/java/net/momirealms/craftengine/core/entity/AbstractEntity.java index c9eb5a247..fa9643892 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/AbstractEntity.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/AbstractEntity.java @@ -6,6 +6,6 @@ public abstract class AbstractEntity implements Entity { @Override public WorldPosition position() { - return new WorldPosition(world(), x(), y(), z(), getXRot(), getYRot()); + return new WorldPosition(world(), x(), y(), z(), xRot(), yRot()); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/Entity.java b/core/src/main/java/net/momirealms/craftengine/core/entity/Entity.java index 15720fa5c..aac181c4b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/Entity.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/Entity.java @@ -20,12 +20,12 @@ public interface Entity { void tick(); - float getXRot(); + float xRot(); + + float yRot(); int entityID(); - float getYRot(); - World world(); Direction getDirection(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractCustomFurniture.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractCustomFurniture.java new file mode 100644 index 000000000..d617a91ac --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractCustomFurniture.java @@ -0,0 +1,80 @@ +package net.momirealms.craftengine.core.entity.furniture; + +import net.momirealms.craftengine.core.loot.LootTable; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; +import net.momirealms.craftengine.core.plugin.context.function.Function; +import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public abstract class AbstractCustomFurniture implements CustomFurniture { + private final Key id; + private final FurnitureSettings settings; + private final Map placements; + private final Map>> events; + @Nullable + private final LootTable lootTable; + + private final AnchorType anyType; + + protected AbstractCustomFurniture(@NotNull Key id, + @NotNull FurnitureSettings settings, + @NotNull Map placements, + @NotNull Map>> events, + @Nullable LootTable lootTable) { + this.id = id; + this.settings = settings; + this.placements = placements; + this.lootTable = lootTable; + this.events = events; + this.anyType = placements.keySet().stream().findFirst().orElse(null); + } + + @Override + public void execute(PlayerOptionalContext context, EventTrigger trigger) { + for (Function function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) { + function.run(context); + } + } + + @Override + public Key id() { + return this.id; + } + + @Override + public Map placements() { + return this.placements; + } + + @Override + public FurnitureSettings settings() { + return this.settings; + } + + @Override + public @Nullable LootTable lootTable() { + return this.lootTable; + } + + @Override + public AnchorType getAnyPlacement() { + return this.anyType; + } + + @Override + public boolean isAllowedPlacement(AnchorType anchorType) { + return this.placements.containsKey(anchorType); + } + + @Override + public Placement getPlacement(AnchorType anchorType) { + return this.placements.get(anchorType); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureElement.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureElement.java index 18cc2fa80..3b4863b0f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureElement.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureElement.java @@ -12,7 +12,7 @@ public abstract class AbstractFurnitureElement implements FurnitureElement { private final ItemDisplayContext transform; private final Vector3f scale; private final Vector3f translation; - private final Vector3f offset; + private final Vector3f position; private final Quaternionf rotation; private final boolean applyDyedColor; @@ -21,7 +21,7 @@ public abstract class AbstractFurnitureElement implements FurnitureElement { ItemDisplayContext transform, Vector3f scale, Vector3f translation, - Vector3f offset, + Vector3f position, Quaternionf rotation, boolean applyDyedColor) { this.billboard = billboard; @@ -30,7 +30,7 @@ public abstract class AbstractFurnitureElement implements FurnitureElement { this.translation = translation; this.item = item; this.rotation = rotation; - this.offset = offset; + this.position = position; this.applyDyedColor = applyDyedColor; } @@ -71,6 +71,6 @@ public abstract class AbstractFurnitureElement implements FurnitureElement { @Override public Vector3f position() { - return offset; + return position; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java index c9839cb60..d8980df7b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java @@ -1,15 +1,40 @@ package net.momirealms.craftengine.core.entity.furniture; +import net.momirealms.craftengine.core.entity.Billboard; +import net.momirealms.craftengine.core.entity.ItemDisplayContext; +import net.momirealms.craftengine.core.loot.LootTable; +import net.momirealms.craftengine.core.pack.LoadingSequence; +import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; import org.incendo.cloud.suggestion.Suggestion; +import org.joml.Vector3f; +import java.nio.file.Path; import java.util.*; public abstract class AbstractFurnitureManager implements FurnitureManager { protected final Map byId = new HashMap<>(); + private final CraftEngine plugin; + private final FurnitureParser furnitureParser; // Cached command suggestions private final List cachedSuggestions = new ArrayList<>(); + public AbstractFurnitureManager(CraftEngine plugin) { + this.plugin = plugin; + this.furnitureParser = new FurnitureParser(); + } + + @Override + public ConfigParser parser() { + return this.furnitureParser; + } + @Override public void delayedLoad() { this.initSuggestions(); @@ -37,4 +62,104 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { public void unload() { this.byId.clear(); } + + protected abstract HitBox defaultHitBox(); + + protected abstract FurnitureElement.Builder furnitureElementBuilder(); + + protected abstract CustomFurniture.Builder furnitureBuilder(); + + public class FurnitureParser implements ConfigParser { + public static final String[] CONFIG_SECTION_NAME = new String[] { "furniture" }; + + @Override + public String[] sectionId() { + return CONFIG_SECTION_NAME; + } + + @Override + public int loadingSequence() { + return LoadingSequence.FURNITURE; + } + + @SuppressWarnings("unchecked") + @Override + public void parseSection(Pack pack, Path path, Key id, Map section) { + if (byId.containsKey(id)) { + throw new LocalizedResourceConfigException("warning.config.furniture.duplicate", path, id); + } + + EnumMap placements = new EnumMap<>(AnchorType.class); + Map placementMap = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(section.get("placement"), "warning.config.furniture.missing_placement"), false); + for (Map.Entry entry : placementMap.entrySet()) { + // anchor type + AnchorType anchorType = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH)); + Map placementArguments = MiscUtils.castToMap(entry.getValue(), false); + Optional optionalLootSpawnOffset = Optional.ofNullable(placementArguments.get("loot-spawn-offset")).map(it -> MiscUtils.getAsVector3f(it, "loot-spawn-offset")); + // furniture display elements + List elements = new ArrayList<>(); + List> elementConfigs = (List>) placementArguments.getOrDefault("elements", List.of()); + for (Map element : elementConfigs) { + FurnitureElement furnitureElement = furnitureElementBuilder() + .item(Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(element.get("item"), "warning.config.furniture.element.missing_item"))) + .applyDyedColor((boolean) element.getOrDefault("apply-dyed-color", true)) + .billboard(ResourceConfigUtils.getOrDefault(element.get("billboard"), o -> Billboard.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), Billboard.FIXED)) + .transform(ResourceConfigUtils.getOrDefault(element.get("transform"), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE)) + .scale(MiscUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale")) + .position(MiscUtils.getAsVector3f(element.getOrDefault("position", "0"), "position")) + .translation(MiscUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation")) + .rotation(MiscUtils.getAsQuaternionf(element.getOrDefault("rotation", "0"), "rotation")) + .build(); + elements.add(furnitureElement); + } + + // external model providers + Optional externalModel; + if (placementArguments.containsKey("model-engine")) { + externalModel = Optional.of(plugin.compatibilityManager().createModel("ModelEngine", placementArguments.get("model-engine").toString())); + } else if (placementArguments.containsKey("better-model")) { + externalModel = Optional.of(plugin.compatibilityManager().createModel("BetterModel", placementArguments.get("better-model").toString())); + } else { + externalModel = Optional.empty(); + } + + // add hitboxes + List hitboxes = ResourceConfigUtils.parseConfigAsList(placementArguments.get("hitboxes"), HitBoxTypes::fromMap); + if (hitboxes.isEmpty() && externalModel.isEmpty()) { + hitboxes = List.of(defaultHitBox()); + } + + // rules + Map ruleSection = MiscUtils.castToMap(placementArguments.get("rules"), true); + if (ruleSection != null) { + placements.put(anchorType, new CustomFurniture.Placement( + elements.toArray(new FurnitureElement[0]), + hitboxes.toArray(new HitBox[0]), + ResourceConfigUtils.getOrDefault(ruleSection.get("rotation"), o -> RotationRule.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), RotationRule.ANY), + ResourceConfigUtils.getOrDefault(ruleSection.get("alignment"), o -> AlignmentRule.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), AlignmentRule.CENTER), + externalModel, + optionalLootSpawnOffset + )); + } else { + placements.put(anchorType, new CustomFurniture.Placement( + elements.toArray(new FurnitureElement[0]), + hitboxes.toArray(new HitBox[0]), + RotationRule.ANY, + AlignmentRule.CENTER, + externalModel, + optionalLootSpawnOffset + )); + } + } + + CustomFurniture furniture = furnitureBuilder() + .id(id) + .settings(FurnitureSettings.fromMap(MiscUtils.castToMap(section.get("settings"), true))) + .placement(placements) + .events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event"))) + .lootTable(LootTable.fromMap(MiscUtils.castToMap(section.get("loot"), true))) + .build(); + AbstractFurnitureManager.this.byId.put(id, furniture); + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurniture.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurniture.java index 8d2d93885..5357caabb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurniture.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/CustomFurniture.java @@ -2,80 +2,56 @@ package net.momirealms.craftengine.core.entity.furniture; import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.function.Function; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; import net.momirealms.craftengine.core.util.Key; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; -import java.util.Collections; -import java.util.EnumMap; import java.util.List; +import java.util.Map; import java.util.Optional; -public class CustomFurniture { - private final Key id; - private final FurnitureSettings settings; - private final EnumMap placements; - private final AnchorType anyType; - private final EnumMap>> events; - @Nullable - private final LootTable lootTable; +// TODO 家具的设计存在问题。家具也应该存在不同的状态,而不是根据放置规则直接决定状态类型 +public interface CustomFurniture { - public CustomFurniture(@NotNull Key id, - @NotNull FurnitureSettings settings, - @NotNull EnumMap placements, - @NotNull EnumMap>> events, - @Nullable LootTable lootTable) { - this.id = id; - this.settings = settings; - this.placements = placements; - this.lootTable = lootTable; - this.events = events; - this.anyType = placements.keySet().stream().findFirst().orElse(null); - } + void execute(PlayerOptionalContext context, EventTrigger trigger); - public void execute(PlayerOptionalContext context, EventTrigger trigger) { - for (Function function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) { - function.run(context); - } - } + Key id(); - public Key id() { - return id; - } + Map placements(); - public EnumMap placements() { - return placements; - } - - public FurnitureSettings settings() { - return settings; - } + FurnitureSettings settings(); @Nullable - public LootTable lootTable() { - return lootTable; + LootTable lootTable(); + + AnchorType getAnyPlacement(); + + boolean isAllowedPlacement(AnchorType anchorType); + + Placement getPlacement(AnchorType anchorType); + + interface Builder { + + Builder id(Key id); + + Builder placement(Map placements); + + Builder settings(FurnitureSettings settings); + + Builder lootTable(LootTable lootTable); + + Builder events(Map>> events); + + CustomFurniture build(); } - public AnchorType getAnyPlacement() { - return this.anyType; - } - - public boolean isAllowedPlacement(AnchorType anchorType) { - return placements.containsKey(anchorType); - } - - public Placement getPlacement(AnchorType anchorType) { - return placements.get(anchorType); - } - - public record Placement(FurnitureElement[] elements, - HitBox[] hitBoxes, - RotationRule rotationRule, - AlignmentRule alignmentRule, - Optional externalModel, - Optional dropOffset) { + record Placement(FurnitureElement[] elements, + HitBox[] hitBoxes, + RotationRule rotationRule, + AlignmentRule alignmentRule, + Optional externalModel, + Optional dropOffset) { } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Furniture.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Furniture.java index fea19d1e0..691092619 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Furniture.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/Furniture.java @@ -2,8 +2,7 @@ package net.momirealms.craftengine.core.entity.furniture; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.world.Vec3d; -import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.WorldPosition; import org.jetbrains.annotations.NotNull; import org.joml.Vector3f; @@ -13,9 +12,7 @@ import java.util.UUID; public interface Furniture { void initializeColliders(); - Vec3d position(); - - World world(); + WorldPosition position(); boolean isValid(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureElement.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureElement.java index c730621e8..f997ce0fe 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureElement.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureElement.java @@ -29,4 +29,25 @@ public interface FurnitureElement { Vector3f position(); void initPackets(int entityId, @NotNull WorldPosition position, @NotNull Quaternionf conjugated, @Nullable Integer dyedColor, Consumer packets); + + interface Builder { + + Builder item(Key item); + + Builder billboard(Billboard billboard); + + Builder transform(ItemDisplayContext transform); + + Builder scale(Vector3f scale); + + Builder translation(Vector3f translation); + + Builder position(Vector3f position); + + Builder rotation(Quaternionf rotation); + + Builder applyDyedColor(boolean applyDyedColor); + + FurnitureElement build(); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureManager.java index 50d4787c1..2a31d46de 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/FurnitureManager.java @@ -6,12 +6,18 @@ import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.WorldPosition; import org.incendo.cloud.suggestion.Suggestion; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.Collection; import java.util.Optional; public interface FurnitureManager extends Manageable { + Key FURNITURE_KEY = Key.of("craftengine:furniture_id"); + Key FURNITURE_EXTRA_DATA_KEY = Key.of("craftengine:furniture_extra_data"); + Key FURNITURE_SEAT_BASE_ENTITY_KEY = Key.of("craftengine:seat_to_base_entity"); + Key FURNITURE_SEAT_VECTOR_3F_KEY = Key.of("craftengine:seat_vector"); + Key FURNITURE_COLLISION = Key.of("craftengine:collision"); + String FURNITURE_ADMIN_NODE = "craftengine.furniture.admin"; ConfigParser parser(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index bb50aec7d..5e0309c97 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.entity.player; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.entity.AbstractEntity; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.plugin.context.CooldownData; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.world.BlockPos; @@ -36,6 +37,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void abortMiningBlock(); + public abstract void breakBlock(int x, int y, int z); + public abstract double getCachedInteractionRange(); public abstract void onSwingHand(); @@ -114,4 +117,20 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public boolean isAdventureMode() { return gameMode() == GameMode.ADVENTURE; } + + public abstract int foodLevel(); + + public abstract void setFoodLevel(int foodLevel); + + public abstract float saturation(); + + public abstract void setSaturation(float saturation); + + public abstract void addPotionEffect(Key potionEffectType, int duration, int amplifier, boolean ambient, boolean particles); + + public abstract void removePotionEffect(Key potionEffectType); + + public abstract void clearPotionEffects(); + + public abstract CooldownData cooldown(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java index 6cd120eb1..92ae329cb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/AbstractFontManager.java @@ -47,6 +47,7 @@ public abstract class AbstractFontManager implements FontManager { protected List emojiList; protected List allEmojiSuggestions; + protected Set existingImagePaths = new HashSet<>(); public AbstractFontManager(CraftEngine plugin) { this.plugin = plugin; @@ -67,6 +68,7 @@ public abstract class AbstractFontManager implements FontManager { this.images.clear(); this.illegalChars.clear(); this.emojis.clear(); + this.existingImagePaths.clear(); } @Override @@ -424,12 +426,12 @@ public abstract class AbstractFontManager implements FontManager { throw new LocalizedResourceConfigException("warning.config.image.missing_file", path, id); } - String resourceLocation = file.toString().replace("\\", "/"); + String resourceLocation = CharacterUtils.replaceBackslashWithSlash(file.toString()); if (!ResourceLocation.isValid(resourceLocation)) { throw new LocalizedResourceConfigException("warning.config.image.invalid_file_chars", path, id, resourceLocation); } - String fontName = (String) section.getOrDefault("font", "minecraft:default"); + String fontName = section.getOrDefault("font", "minecraft:default").toString(); if (!ResourceLocation.isValid(fontName)) { throw new LocalizedResourceConfigException("warning.config.image.invalid_font_chars", path, id, fontName); } @@ -437,10 +439,7 @@ public abstract class AbstractFontManager implements FontManager { Key fontKey = Key.withDefaultNamespace(fontName, id.namespace()); Font font = getOrCreateFont(fontKey); List chars; - Object charsObj = section.get("chars"); - if (charsObj == null) { - charsObj = section.get("char"); - } + Object charsObj = ResourceConfigUtils.get(section, "chars", "char"); if (charsObj == null) { throw new LocalizedResourceConfigException("warning.config.image.missing_char", path, id); } @@ -516,8 +515,8 @@ public abstract class AbstractFontManager implements FontManager { .resolve("textures") .resolve(namespacedPath.value()); - if (!Files.exists(targetImagePath)) { - TranslationManager.instance().log("warning.config.image.file_not_found", path.toString(), id.toString(), targetImagePath.toString()); + if (!doesImageFileExist(targetImagePath)) { +// TranslationManager.instance().log("warning.config.image.file_not_found", path.toString(), id.toString(), targetImagePath.toString()); // DO NOT RETURN, JUST GIVE WARNINGS } else if (heightObj == null) { try (InputStream in = Files.newInputStream(targetImagePath)) { @@ -548,5 +547,16 @@ public abstract class AbstractFontManager implements FontManager { images.put(id, bitmapImage); } + + private boolean doesImageFileExist(Path path) { + if (existingImagePaths.contains(path)) { + return true; + } + boolean exist = Files.exists(path); + if (exist) { + existingImagePaths.add(path); + } + return exist; + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/font/Emoji.java b/core/src/main/java/net/momirealms/craftengine/core/font/Emoji.java index 81a51429b..8ce6168d8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/font/Emoji.java +++ b/core/src/main/java/net/momirealms/craftengine/core/font/Emoji.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.font; -import javax.annotation.Nullable; +import org.jetbrains.annotations.Nullable; + import java.util.List; public class Emoji { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractCustomItem.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractCustomItem.java new file mode 100644 index 000000000..e774f409a --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractCustomItem.java @@ -0,0 +1,111 @@ +package net.momirealms.craftengine.core.item; + +import com.google.common.collect.ImmutableMap; +import net.momirealms.craftengine.core.item.behavior.ItemBehavior; +import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; +import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; +import net.momirealms.craftengine.core.plugin.context.function.Function; +import net.momirealms.craftengine.core.registry.Holder; +import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public abstract class AbstractCustomItem implements CustomItem { + protected final Holder id; + protected final Key material; + protected final ItemDataModifier[] modifiers; + protected final Map> modifierMap; + protected final ItemDataModifier[] clientBoundModifiers; + protected final Map> clientBoundModifierMap; + protected final List behaviors; + protected final ItemSettings settings; + protected final Map>> events; + + @SuppressWarnings("unchecked") + public AbstractCustomItem(Holder id, Key material, + List behaviors, + List> modifiers, + List> clientBoundModifiers, + ItemSettings settings, + Map>> events) { + this.id = id; + this.material = material; + this.events = events; + // unchecked cast + this.modifiers = modifiers.toArray(new ItemDataModifier[0]); + // unchecked cast + this.clientBoundModifiers = clientBoundModifiers.toArray(new ItemDataModifier[0]); + this.behaviors = List.copyOf(behaviors); + this.settings = settings; + ImmutableMap.Builder> modifierMapBuilder = ImmutableMap.builder(); + for (ItemDataModifier modifier : modifiers) { + modifierMapBuilder.put(modifier.name(), modifier); + } + this.modifierMap = modifierMapBuilder.build(); + ImmutableMap.Builder> clientSideModifierMapBuilder = ImmutableMap.builder(); + this.clientBoundModifierMap = clientSideModifierMapBuilder.build(); + + } + + @Override + public void execute(PlayerOptionalContext context, EventTrigger trigger) { + for (Function function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) { + function.run(context); + } + } + + @Override + public Key id() { + return this.id.value(); + } + + @Override + public Holder idHolder() { + return this.id; + } + + @Override + public Key material() { + return this.material; + } + + @Override + public ItemDataModifier[] dataModifiers() { + return this.modifiers; + } + + @Override + public Map> dataModifierMap() { + return this.modifierMap; + } + + @Override + public boolean hasClientBoundDataModifier() { + return this.clientBoundModifiers.length != 0; + } + + @Override + public ItemDataModifier[] clientBoundDataModifiers() { + return this.clientBoundModifiers; + } + + @Override + public Map> clientBoundDataModifierMap() { + return this.clientBoundModifierMap; + } + + @Override + public ItemSettings settings() { + return this.settings; + } + + @Override + public @NotNull List behaviors() { + return this.behaviors; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItem.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItem.java index 70fbf6d8f..48e4b70af 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItem.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItem.java @@ -1,8 +1,10 @@ package net.momirealms.craftengine.core.item; import com.google.gson.JsonElement; +import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.sparrow.nbt.Tag; import java.util.List; import java.util.Optional; @@ -89,7 +91,7 @@ public class AbstractItem, I> implements Item { } @Override - public Optional maxDamage() { + public int maxDamage() { return this.factory.maxDamage(this.item); } @@ -180,25 +182,47 @@ public class AbstractItem, I> implements Item { } @Override - public Optional customName() { - return this.factory.customName(this.item); + public Optional customNameJson() { + return this.factory.customNameJson(this.item); } @Override - public Item customName(String displayName) { - this.factory.customName(this.item, displayName); + public Item customNameJson(String displayName) { + this.factory.customNameJson(this.item, displayName); return this; } @Override - public Item lore(List lore) { - this.factory.lore(this.item, lore); + public Optional customNameComponent() { + return this.factory.customNameComponent(this.item); + } + + @Override + public Item customNameComponent(Component displayName) { + this.factory.customNameComponent(this.item, displayName); return this; } @Override - public Optional> lore() { - return this.factory.lore(this.item); + public Item loreJson(List lore) { + this.factory.loreJson(this.item, lore); + return this; + } + + @Override + public Optional> loreJson() { + return this.factory.loreJson(this.item); + } + + @Override + public Item loreComponent(List lore) { + this.factory.loreComponent(this.item, lore); + return this; + } + + @Override + public Optional> loreComponent() { + return this.factory.loreComponent(this.item); } @Override @@ -212,18 +236,28 @@ public class AbstractItem, I> implements Item { return this.factory.unbreakable(this.item); } - @Override - public Optional itemName() { - return this.factory.itemName(this.item); + public Item itemNameJson(String itemName) { + this.factory.itemNameJson(this.item, itemName); + return this; } @Override - public Item itemName(String itemName) { - this.factory.itemName(this.item, itemName); + public Optional itemNameJson() { + return this.factory.itemNameJson(this.item); + } + + @Override + public Item itemNameComponent(Component itemName) { + this.factory.itemNameComponent(this.item, itemName); return this; } + @Override + public Optional itemNameComponent() { + return this.factory.itemNameComponent(this.item); + } + @Override public Item skull(String data) { this.factory.skull(this.item, data); @@ -277,8 +311,13 @@ public class AbstractItem, I> implements Item { } @Override - public Object getTag(Object... path) { - return this.factory.getTag(this.item, path); + public Object getJavaTag(Object... path) { + return this.factory.getJavaTag(this.item, path); + } + + @Override + public Tag getNBTTag(Object... path) { + return this.factory.getNBTTag(this.item, path); } @Override @@ -308,18 +347,23 @@ public class AbstractItem, I> implements Item { } @Override - public Object getComponent(Object type) { - return this.factory.getComponent(this.item, type); + public Object getExactComponent(Object type) { + return this.factory.getExactComponent(this.item, type); } @Override - public Object getJavaTypeComponent(Object type) { - return this.factory.encodeJava(type, getComponent(type)); + public Object getJavaComponent(Object type) { + return this.factory.getJavaComponent(this.item, type); } @Override - public JsonElement getJsonTypeComponent(Object type) { - return this.factory.encodeJson(type, getComponent(type)); + public JsonElement getJsonComponent(Object type) { + return this.factory.getJsonComponent(this.item, type); + } + + @Override + public Tag getNBTComponent(Object type) { + return this.factory.getNBTComponent(this.item, type); } @Override @@ -327,6 +371,21 @@ public class AbstractItem, I> implements Item { this.factory.setComponent(this.item, type, value); } + @Override + public void setJavaComponent(Object type, Object value) { + this.factory.setJavaComponent(this.item, type, value); + } + + @Override + public void setJsonComponent(Object type, JsonElement value) { + this.factory.setJsonComponent(this.item, type, value); + } + + @Override + public void setNBTComponent(Object type, Tag value) { + this.factory.setNBTComponent(this.item, type, value); + } + @Override public void resetComponent(Object type) { this.factory.resetComponent(this.item, type); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index 4548fadab..cf3df095b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -1,35 +1,49 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; +import net.momirealms.craftengine.core.item.behavior.ItemBehaviors; import net.momirealms.craftengine.core.item.modifier.*; +import net.momirealms.craftengine.core.pack.LoadingSequence; +import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.pack.ResourceLocation; import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration; -import net.momirealms.craftengine.core.pack.model.ItemModel; -import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; +import net.momirealms.craftengine.core.pack.model.*; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; +import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; +import net.momirealms.craftengine.core.pack.model.select.ChargeTypeSelectProperty; +import net.momirealms.craftengine.core.pack.model.select.TrimMaterialSelectProperty; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.plugin.config.ConfigParser; +import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.plugin.locale.TranslationManager; +import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; -import net.momirealms.craftengine.core.util.TypeUtils; -import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.registry.WritableRegistry; +import net.momirealms.craftengine.core.util.*; import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.type.Either; +import java.nio.file.Path; import java.util.*; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; public abstract class AbstractItemManager extends AbstractModelGenerator implements ItemManager { protected static final Map> VANILLA_ITEM_EXTRA_BEHAVIORS = new HashMap<>(); - protected static final List VANILLA_ITEMS = new ArrayList<>(); + protected static final Set VANILLA_ITEMS = new HashSet<>(1024); protected static final Map>> VANILLA_ITEM_TAGS = new HashMap<>(); + private final ItemParser itemParser; protected final Map> externalItemProviders = new HashMap<>(); protected final Map>> dataFunctions = new HashMap<>(); protected final Map> customItems = new HashMap<>(); protected final Map>> customItemTags; protected final Map> cmdConflictChecker; protected final Map modernItemModels1_21_4; - protected final Map> modernItemModels1_21_2; + protected final Map> modernItemModels1_21_2; protected final Map> legacyOverrides; protected final Map> modernOverrides; protected final Set equipmentsToGenerate; @@ -37,6 +51,19 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl protected final List cachedSuggestions = new ArrayList<>(); protected final List cachedTotemSuggestions = new ArrayList<>(); + protected AbstractItemManager(CraftEngine plugin) { + super(plugin); + this.itemParser = new ItemParser(); + this.registerFunctions(); + this.legacyOverrides = new HashMap<>(); + this.modernOverrides = new HashMap<>(); + this.customItemTags = new HashMap<>(); + this.cmdConflictChecker = new HashMap<>(); + this.modernItemModels1_21_4 = new HashMap<>(); + this.modernItemModels1_21_2 = new HashMap<>(); + this.equipmentsToGenerate = new HashSet<>(); + } + protected void registerDataFunction(Function> function, String... alias) { for (String a : alias) { dataFunctions.put(a, function); @@ -49,6 +76,25 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } } + protected void applyDataFunctions(Map dataSection, Consumer> consumer) { + if (dataSection != null) { + for (Map.Entry dataEntry : dataSection.entrySet()) { + Optional.ofNullable(dataFunctions.get(dataEntry.getKey())).ifPresent(function -> { + try { + consumer.accept(function.apply(dataEntry.getValue())); + } catch (IllegalArgumentException e) { + plugin.logger().warn("Invalid data format", e); + } + }); + } + } + } + + @Override + public ConfigParser parser() { + return this.itemParser; + } + @Override public ExternalItemProvider getExternalItemProvider(String name) { return this.externalItemProviders.get(name); @@ -81,16 +127,35 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl return Optional.ofNullable(this.customItems.get(key)); } - public AbstractItemManager(CraftEngine plugin) { - super(plugin); - this.registerFunctions(); - this.legacyOverrides = new HashMap<>(); - this.modernOverrides = new HashMap<>(); - this.customItemTags = new HashMap<>(); - this.cmdConflictChecker = new HashMap<>(); - this.modernItemModels1_21_4 = new HashMap<>(); - this.modernItemModels1_21_2 = new HashMap<>(); - this.equipmentsToGenerate = new HashSet<>(); + @Override + public boolean addCustomItem(CustomItem customItem) { + Key id = customItem.id(); + if (this.customItems.containsKey(id)) return false; + this.customItems.put(id, customItem); + // cache command suggestions + this.cachedSuggestions.add(Suggestion.suggestion(id.toString())); + // totem animations + if (VersionHelper.isOrAbove1_21_2()) { + this.cachedTotemSuggestions.add(Suggestion.suggestion(id.toString())); + } else if (customItem.material().equals(ItemKeys.TOTEM_OF_UNDYING)) { + this.cachedTotemSuggestions.add(Suggestion.suggestion(id.toString())); + } + // tags + Set tags = customItem.settings().tags(); + for (Key tag : tags) { + this.customItemTags.computeIfAbsent(tag, k -> new ArrayList<>()).add(customItem.idHolder()); + } + // equipment generation + EquipmentGeneration equipment = customItem.settings().equipment(); + if (equipment != null) { + EquipmentData modern = equipment.modernData(); + // 1.21.2+ + if (modern != null) { + this.equipmentsToGenerate.add(equipment); + } + // TODO 1.20 + } + return true; } @Override @@ -100,7 +165,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl if (holders != null) { items.addAll(holders); } - List> customItems = customItemTags.get(tag); + List> customItems = this.customItemTags.get(tag); if (customItems != null) { items.addAll(customItems); } @@ -160,7 +225,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl } @Override - public Map> modernItemModels1_21_2() { + public Map> modernItemModels1_21_2() { return Collections.unmodifiableMap(this.modernItemModels1_21_2); } @@ -184,6 +249,184 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl return Collections.unmodifiableCollection(this.equipmentsToGenerate); } + @Override + public boolean isVanillaItem(Key item) { + return VANILLA_ITEMS.contains(item); + } + + protected abstract CustomItem.Builder createPlatformItemBuilder(Holder id, Key material); + + public class ItemParser implements ConfigParser { + public static final String[] CONFIG_SECTION_NAME = new String[] {"items", "item"}; + private static final float VERSION_1_21_2 = 21.2f; + private static final float VERSION_1_21_4 = 21.4f; + + private boolean isModernFormatRequired() { + return Config.packMaxVersion() >= VERSION_1_21_4; + } + + private boolean needsLegacyCompatibility() { + return Config.packMinVersion() < VERSION_1_21_4; + } + + @Override + public String[] sectionId() { + return CONFIG_SECTION_NAME; + } + + @Override + public int loadingSequence() { + return LoadingSequence.ITEM; + } + + @Override + public void parseSection(Pack pack, Path path, Key id, Map section) { + if (AbstractItemManager.this.customItems.containsKey(id)) { + throw new LocalizedResourceConfigException("warning.config.item.duplicate"); + } + + // register for recipes + Holder.Reference holder = BuiltInRegistries.OPTIMIZED_ITEM_ID.get(id) + .orElseGet(() -> ((WritableRegistry) BuiltInRegistries.OPTIMIZED_ITEM_ID) + .register(new ResourceKey<>(BuiltInRegistries.OPTIMIZED_ITEM_ID.key().location(), id), id)); + + boolean isVanillaItem = isVanillaItem(id); + Key material = Key.from(isVanillaItem ? id.value() : ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("material"), "warning.config.item.missing_material").toLowerCase(Locale.ENGLISH)); + int customModelData = ResourceConfigUtils.getAsInt(section.getOrDefault("custom-model-data", 0), "custom-model-data"); + if (customModelData < 0) { + throw new LocalizedResourceConfigException("warning.config.item.invalid_custom_model_data", String.valueOf(customModelData)); + } + if (customModelData > 16_777_216) { + throw new LocalizedResourceConfigException("warning.config.item.bad_custom_model_data", String.valueOf(customModelData)); + } + + Key itemModelKey = null; + + CustomItem.Builder itemBuilder = createPlatformItemBuilder(holder, material); + boolean hasItemModelSection = section.containsKey("item-model"); + + // To get at least one model provider + // Sets some basic model info + if (customModelData > 0) { + itemBuilder.dataModifier(new CustomModelDataModifier<>(customModelData)); + } + // Requires the item to have model before apply item-model + else if (!hasItemModelSection && section.containsKey("model") && VersionHelper.isOrAbove1_21_2()) { + // check server version here because components require 1.21.2+ + // customize or use the id + itemModelKey = Key.from(section.getOrDefault("item-model", id.toString()).toString()); + if (ResourceLocation.isValid(itemModelKey.toString())) { + itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey)); + } else { + itemModelKey = null; + } + } + + if (hasItemModelSection && VersionHelper.isOrAbove1_21_2()) { + itemModelKey = Key.from(section.get("item-model").toString()); + itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey)); + } + + // Get item data + applyDataFunctions(MiscUtils.castToMap(section.get("data"), true), itemBuilder::dataModifier); + applyDataFunctions(MiscUtils.castToMap(section.get("client-bound-data"), true), itemBuilder::clientBoundDataModifier); + + // Add custom it here to make sure that id is always applied + if (!isVanillaItem) + itemBuilder.dataModifier(new IdModifier<>(id)); + + CustomItem customItem = itemBuilder + .behaviors(ItemBehaviors.fromObj(pack, path, id, ResourceConfigUtils.get(section, "behavior", "behaviors"))) + .settings(Optional.ofNullable(ResourceConfigUtils.get(section, "settings")) + .map(map -> ItemSettings.fromMap(MiscUtils.castToMap(map, true))) + .map(it -> isVanillaItem ? it.canPlaceRelatedVanillaBlock(true) : it) + .orElse(ItemSettings.of().canPlaceRelatedVanillaBlock(isVanillaItem))) + .events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event"))) + .build(); + + addCustomItem(customItem); + + // add it to category + if (section.containsKey("category")) { + AbstractItemManager.this.plugin.itemBrowserManager().addExternalCategoryMember(id, MiscUtils.getAsStringList(section.get("category")).stream().map(Key::of).toList()); + } + + // model part, can be null + // but if it exists, either custom model data or item model should be configured + Map modelSection = MiscUtils.castToMap(section.get("model"), true); + Map legacyModelSection = MiscUtils.castToMap(section.get("legacy-model"), true); + if (modelSection == null && legacyModelSection == null) { + return; + } + // 如果设置了model,但是没有模型值? + if (customModelData == 0 && itemModelKey == null) { + throw new LocalizedResourceConfigException("warning.config.item.missing_model_id"); + } + // 1.21.4+必须要配置model区域 + if (isModernFormatRequired() && modelSection == null) { + throw new LocalizedResourceConfigException("warning.config.item.missing_model"); + } + + // 新版格式 + ItemModel modernModel = null; + // 旧版格式 + TreeSet legacyOverridesModels = null; + + if (isModernFormatRequired() || (needsLegacyCompatibility() && legacyModelSection == null)) { + modernModel = ItemModels.fromMap(modelSection); + for (ModelGeneration generation : modernModel.modelsToGenerate()) { + prepareModelGeneration(generation); + } + } + if (needsLegacyCompatibility()) { + if (legacyModelSection != null) { + LegacyItemModel legacyItemModel = LegacyItemModel.fromMap(legacyModelSection, customModelData); + for (ModelGeneration generation : legacyItemModel.modelsToGenerate()) { + prepareModelGeneration(generation); + } + legacyOverridesModels = new TreeSet<>(legacyItemModel.overrides()); + } else { + legacyOverridesModels = new TreeSet<>(); + processModelRecursively(modernModel, new LinkedHashMap<>(), legacyOverridesModels, material, customModelData); + if (legacyOverridesModels.isEmpty()) { + TranslationManager.instance().log("warning.config.item.legacy_model.cannot_convert", path.toString(), id.asString()); + } + } + } + + // use custom model data + if (customModelData != 0) { + // use custom model data + // check conflict + Map conflict = AbstractItemManager.this.cmdConflictChecker.computeIfAbsent(material, k -> new HashMap<>()); + if (conflict.containsKey(customModelData)) { + throw new LocalizedResourceConfigException("warning.config.item.custom_model_data_conflict", String.valueOf(customModelData), conflict.get(customModelData).toString()); + } + conflict.put(customModelData, id); + // Parse models + if (isModernFormatRequired() && modernModel != null) { + TreeMap map = AbstractItemManager.this.modernOverrides.computeIfAbsent(material, k -> new TreeMap<>()); + map.put(customModelData, modernModel); + } + if (needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) { + TreeSet lom = AbstractItemManager.this.legacyOverrides.computeIfAbsent(material, k -> new TreeSet<>()); + lom.addAll(legacyOverridesModels); + } + } + + // use item model + if (itemModelKey != null) { + if (isModernFormatRequired() && modernModel != null) { + AbstractItemManager.this.modernItemModels1_21_4.put(itemModelKey, modernModel); + } + if (Config.packMaxVersion() >= VERSION_1_21_2 && needsLegacyCompatibility() && legacyOverridesModels != null && !legacyOverridesModels.isEmpty()) { + TreeSet lom = AbstractItemManager.this.modernItemModels1_21_2.computeIfAbsent(itemModelKey, k -> new TreeSet<>()); + lom.addAll(legacyOverridesModels); + } + } + } + } + private void registerFunctions() { registerDataFunction((obj) -> { Map data = MiscUtils.castToMap(obj, false); @@ -194,7 +437,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl }, "external"); registerDataFunction((obj) -> { String name = obj.toString(); - return new DisplayNameModifier<>(name); + return new CustomNameModifier<>(name); }, "custom-name"); registerDataFunction((obj) -> { String name = obj.toString(); @@ -258,4 +501,193 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl }, "equippable"); } } + + protected void processModelRecursively( + ItemModel currentModel, + Map accumulatedPredicates, + Collection resultList, + Key materialId, + int customModelData + ) { + if (currentModel instanceof ConditionItemModel conditionModel) { + handleConditionModel(conditionModel, accumulatedPredicates, resultList, materialId, customModelData); + } else if (currentModel instanceof RangeDispatchItemModel rangeModel) { + handleRangeModel(rangeModel, accumulatedPredicates, resultList, materialId, customModelData); + } else if (currentModel instanceof SelectItemModel selectModel) { + handleSelectModel(selectModel, accumulatedPredicates, resultList, materialId, customModelData); + } else if (currentModel instanceof BaseItemModel baseModel) { + resultList.add(new LegacyOverridesModel( + new LinkedHashMap<>(accumulatedPredicates), + baseModel.path(), + customModelData + )); + } else if (currentModel instanceof SpecialItemModel specialModel) { + resultList.add(new LegacyOverridesModel( + new LinkedHashMap<>(accumulatedPredicates), + specialModel.base(), + customModelData + )); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private void handleConditionModel( + ConditionItemModel model, + Map parentPredicates, + Collection resultList, + Key materialId, + int customModelData + ) { + if (model.property() instanceof LegacyModelPredicate predicate) { + String predicateId = predicate.legacyPredicateId(materialId); + Map truePredicates = mergePredicates( + parentPredicates, + predicateId, + predicate.toLegacyValue(true) + ); + processModelRecursively( + model.onTrue(), + truePredicates, + resultList, + materialId, + customModelData + ); + Map falsePredicates = mergePredicates( + parentPredicates, + predicateId, + predicate.toLegacyValue(false) + ); + processModelRecursively( + model.onFalse(), + falsePredicates, + resultList, + materialId, + customModelData + ); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private void handleRangeModel( + RangeDispatchItemModel model, + Map parentPredicates, + Collection resultList, + Key materialId, + int customModelData + ) { + if (model.property() instanceof LegacyModelPredicate predicate) { + String predicateId = predicate.legacyPredicateId(materialId); + for (Map.Entry entry : model.entries().entrySet()) { + Map merged = mergePredicates( + parentPredicates, + predicateId, + predicate.toLegacyValue(entry.getKey()) + ); + processModelRecursively( + entry.getValue(), + merged, + resultList, + materialId, + customModelData + ); + } + if (model.fallBack() != null) { + Map merged = mergePredicates( + parentPredicates, + predicateId, + predicate.toLegacyValue(0f) + ); + processModelRecursively( + model.fallBack(), + merged, + resultList, + materialId, + customModelData + ); + } + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private void handleSelectModel( + SelectItemModel model, + Map parentPredicates, + Collection resultList, + Key materialId, + int customModelData + ) { + if (model.property() instanceof LegacyModelPredicate predicate) { + String predicateId = predicate.legacyPredicateId(materialId); + for (Map.Entry>, ItemModel> entry : model.whenMap().entrySet()) { + List cases = entry.getKey().fallbackOrMapPrimary(List::of); + for (String caseValue : cases) { + Number legacyValue = predicate.toLegacyValue(caseValue); + if (predicate instanceof TrimMaterialSelectProperty property && property.isArmor(materialId)) { + if (legacyValue.floatValue() > 1f) { + continue; + } + } + Map merged = mergePredicates( + parentPredicates, + predicateId, + legacyValue + ); + // Additional check for crossbow + if (predicate instanceof ChargeTypeSelectProperty && materialId.equals(ItemKeys.CROSSBOW)) { + merged = mergePredicates( + merged, + "charged", + 1 + ); + } + processModelRecursively( + entry.getValue(), + merged, + resultList, + materialId, + customModelData + ); + } + } + // Additional check for crossbow + if (model.fallBack() != null) { + if (predicate instanceof ChargeTypeSelectProperty && materialId.equals(ItemKeys.CROSSBOW)) { + processModelRecursively( + model.fallBack(), + mergePredicates( + parentPredicates, + "charged", + 0 + ), + resultList, + materialId, + customModelData + ); + } else if (predicate instanceof TrimMaterialSelectProperty property && property.isArmor(materialId)) { + processModelRecursively( + model.fallBack(), + mergePredicates( + parentPredicates, + "trim_type", + 0f + ), + resultList, + materialId, + customModelData + ); + } + } + } + } + + private Map mergePredicates( + Map existing, + String newKey, + Number newValue + ) { + Map merged = new LinkedHashMap<>(existing); + if (newKey == null) return merged; + merged.put(newKey, newValue); + return merged; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ComponentIds.java b/core/src/main/java/net/momirealms/craftengine/core/item/ComponentIds.java new file mode 100644 index 000000000..9cfa7771f --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ComponentIds.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.core.item; + +public final class ComponentIds { + private ComponentIds() {} + + public static final String ITEM_NAME = "minecraft:item_name"; + public static final String CUSTOM_NAME = "minecraft:custom_name"; + public static final String ENCHANTMENTS = "minecraft:enchantments"; + public static final String LORE = "minecraft:lore"; +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ComponentKeys.java b/core/src/main/java/net/momirealms/craftengine/core/item/ComponentKeys.java index 597639121..813ac6486 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ComponentKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ComponentKeys.java @@ -2,7 +2,9 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.util.Key; -public class ComponentKeys { +public final class ComponentKeys { + private ComponentKeys() {} + public static final Key CUSTOM_MODEL_DATA = Key.of("minecraft", "custom_model_data"); public static final Key CUSTOM_NAME = Key.of("minecraft", "custom_name"); public static final Key ITEM_NAME = Key.of("minecraft", "item_name"); @@ -23,4 +25,5 @@ public class ComponentKeys { public static final Key CUSTOM_DATA = Key.of("minecraft", "custom_data"); public static final Key PROFILE = Key.of("minecraft", "profile"); public static final Key DYED_COLOR = Key.of("minecraft", "dyed_color"); + public static final Key DEATH_PROTECTION = Key.of("minecraft", "death_protection"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/CustomItem.java b/core/src/main/java/net/momirealms/craftengine/core/item/CustomItem.java index 8f92db2d7..89078fb27 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/CustomItem.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/CustomItem.java @@ -4,12 +4,12 @@ import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; +import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.function.Function; -import net.momirealms.craftengine.core.plugin.event.EventTrigger; +import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.NotNull; -import java.util.EnumMap; import java.util.List; import java.util.Map; @@ -17,9 +17,9 @@ public interface CustomItem extends BuildableItem { Key id(); - Key material(); + Holder idHolder(); - NetworkItemDataProcessor[] networkItemDataProcessors(); + Key material(); ItemDataModifier[] dataModifiers(); @@ -49,7 +49,7 @@ public interface CustomItem extends BuildableItem { List behaviors(); interface Builder { - Builder id(Key id); + Builder id(Holder id); Builder material(Key material); @@ -67,7 +67,7 @@ public interface CustomItem extends BuildableItem { Builder settings(ItemSettings settings); - Builder events(EnumMap>> events); + Builder events(Map>> events); CustomItem build(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/DelayedInitItem.java b/core/src/main/java/net/momirealms/craftengine/core/item/DelayedInitItem.java new file mode 100644 index 000000000..2e6db264f --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/DelayedInitItem.java @@ -0,0 +1,23 @@ +package net.momirealms.craftengine.core.item; + +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.Key; + +public class DelayedInitItem { + private final Key itemId; + private Item item; + + public DelayedInitItem(Key itemId) { + this.itemId = itemId; + } + + public Item getItem() { + if (this.item == null) { + this.item = CraftEngine.instance().itemManager().createWrappedItem(this.itemId, null); + if (this.item == null) { + CraftEngine.instance().logger().warn("Could not create item: " + this.itemId); + } + } + return this.item; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/EmptyItem.java b/core/src/main/java/net/momirealms/craftengine/core/item/EmptyItem.java new file mode 100644 index 000000000..5fcfdbefb --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/EmptyItem.java @@ -0,0 +1,342 @@ +//package net.momirealms.craftengine.core.item; +// +//import com.google.gson.JsonElement; +//import net.momirealms.craftengine.core.item.behavior.ItemBehavior; +//import net.momirealms.craftengine.core.util.Key; +// +//import java.util.List; +//import java.util.Optional; +// +//public class EmptyItem implements Item { +// private final I item; +// +// public EmptyItem(I item) { +// this.item = item; +// } +// +// @Override +// public Item addEnchantment(Enchantment enchantment) { +// return this; +// } +// +// @Override +// public Optional> getCustomItem() { +// return Optional.empty(); +// } +// +// @Override +// public Optional> getItemBehavior() { +// return Optional.empty(); +// } +// +// @Override +// public boolean isCustomItem() { +// return false; +// } +// +// @Override +// public boolean isBlockItem() { +// return false; +// } +// +// @Override +// public Key id() { +// return ItemKeys.AIR; +// } +// +// @Override +// public Key vanillaId() { +// return ItemKeys.AIR; +// } +// +// @Override +// public Optional customId() { +// return Optional.empty(); +// } +// +// @Override +// public Item customId(Key id) { +// return this; +// } +// +// @Override +// public int count() { +// return 0; +// } +// +// @Override +// public Item count(int amount) { +// return this; +// } +// +// @Override +// public Item trim(Trim trim) { +// return this; +// } +// +// @Override +// public Optional trim() { +// return Optional.empty(); +// } +// +// @Override +// public Item customModelData(Integer data) { +// return this; +// } +// +// @Override +// public Optional customModelData() { +// return Optional.empty(); +// } +// +// @Override +// public Item damage(Integer data) { +// return this; +// } +// +// @Override +// public Optional damage() { +// return Optional.empty(); +// } +// +// @Override +// public Item repairCost(Integer data) { +// return this; +// } +// +// @Override +// public Optional repairCost() { +// return Optional.empty(); +// } +// +// @Override +// public Item maxDamage(Integer data) { +// return this; +// } +// +// @Override +// public Optional maxDamage() { +// return Optional.empty(); +// } +// +// @Override +// public Item dyedColor(Integer data) { +// return this; +// } +// +// @Override +// public Optional dyedColor() { +// return Optional.empty(); +// } +// +// @Override +// public Item customName(String displayName) { +// return this; +// } +// +// @Override +// public Optional customName() { +// return Optional.empty(); +// } +// +// @Override +// public Item itemName(String itemName) { +// return this; +// } +// +// @Override +// public Optional itemName() { +// return Optional.empty(); +// } +// +// @Override +// public Item itemModel(String itemModel) { +// return this; +// } +// +// @Override +// public Optional itemModel() { +// return Optional.empty(); +// } +// +// @Override +// public Item tooltipStyle(String tooltipStyle) { +// return this; +// } +// +// @Override +// public Optional tooltipStyle() { +// return Optional.empty(); +// } +// +// @Override +// public Item lore(List lore) { +// return this; +// } +// +// @Override +// public Optional jukeboxSong() { +// return Optional.empty(); +// } +// +// @Override +// public Item jukeboxSong(JukeboxPlayable song) { +// return this; +// } +// +// @Override +// public Optional equippable() { +// return Optional.empty(); +// } +// +// @Override +// public Item equippable(EquipmentData equipmentData) { +// return this; +// } +// +// @Override +// public Optional> lore() { +// return Optional.empty(); +// } +// +// @Override +// public Item unbreakable(boolean unbreakable) { +// return this; +// } +// +// @Override +// public boolean unbreakable() { +// return false; +// } +// +// @Override +// public Item skull(String data) { +// return this; +// } +// +// @Override +// public Optional getEnchantment(Key enchantmentId) { +// return Optional.empty(); +// } +// +// @Override +// public Item setEnchantments(List enchantments) { +// return this; +// } +// +// @Override +// public Item setStoredEnchantments(List enchantments) { +// return this; +// } +// +// @Override +// public Item addStoredEnchantment(Enchantment enchantment) { +// return this; +// } +// +// @Override +// public Item itemFlags(List flags) { +// return this; +// } +// +// @Override +// public Object getTag(Object... path) { +// return null; +// } +// +// @Override +// public Item setTag(Object value, Object... path) { +// return this; +// } +// +// @Override +// public boolean hasTag(Object... path) { +// return false; +// } +// +// @Override +// public boolean removeTag(Object... path) { +// return false; +// } +// +// @Override +// public boolean hasComponent(Object type) { +// return false; +// } +// +// @Override +// public void removeComponent(Object type) { +// } +// +// @Override +// public Object getComponent(Object type) { +// return null; +// } +// +// @Override +// public Object getJavaTypeComponent(Object type) { +// return null; +// } +// +// @Override +// public JsonElement getJsonTypeComponent(Object type) { +// return null; +// } +// +// @Override +// public void setComponent(Object type, Object value) { +// } +// +// @Override +// public void resetComponent(Object type) { +// } +// +// @Override +// public I getItem() { +// return this.item; +// } +// +// @Override +// public I load() { +// return this.item; +// } +// +// @Override +// public int maxStackSize() { +// return 0; +// } +// +// @Override +// public Item maxStackSize(int amount) { +// return this; +// } +// +// @Override +// public Item copyWithCount(int count) { +// return this; +// } +// +// @Override +// public boolean is(Key itemTag) { +// return false; +// } +// +// @Override +// public Object getLiteralObject() { +// return null; +// } +// +// @Override +// public Item mergeCopy(Item another) { +// return this; +// } +// +// @Override +// public void merge(Item another) { +// } +// +// @Override +// public byte[] toByteArray() { +// return new byte[0]; +// } +//} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/Helmet.java b/core/src/main/java/net/momirealms/craftengine/core/item/Helmet.java new file mode 100644 index 000000000..0235c411e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/Helmet.java @@ -0,0 +1,15 @@ +package net.momirealms.craftengine.core.item; + +import net.momirealms.craftengine.core.sound.SoundData; + +public class Helmet { + private final SoundData equipSound; + + public Helmet(SoundData equipSound) { + this.equipSound = equipSound; + } + + public SoundData equipSound() { + return equipSound; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/Item.java b/core/src/main/java/net/momirealms/craftengine/core/item/Item.java index 50786ace9..26f3f39e8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/Item.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/Item.java @@ -1,8 +1,10 @@ package net.momirealms.craftengine.core.item; import com.google.gson.JsonElement; +import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.sparrow.nbt.Tag; import java.util.List; import java.util.Optional; @@ -54,23 +56,35 @@ public interface Item { Item maxDamage(Integer data); - Optional maxDamage(); + int maxDamage(); Item dyedColor(Integer data); Optional dyedColor(); - Item customName(String displayName); + Item customNameJson(String displayName); - Optional customName(); + Item customNameComponent(Component displayName); - default Optional hoverName() { - return customName().or(this::itemName); + Optional customNameJson(); + + Optional customNameComponent(); + + default Optional hoverNameJson() { + return customNameJson().or(this::itemNameJson); } - Item itemName(String itemName); + default Optional hoverNameComponent() { + return customNameComponent().or(this::itemNameComponent); + } - Optional itemName(); + Item itemNameJson(String itemName); + + Item itemNameComponent(Component itemName); + + Optional itemNameJson(); + + Optional itemNameComponent(); Item itemModel(String itemModel); @@ -80,7 +94,13 @@ public interface Item { Optional tooltipStyle(); - Item lore(List lore); + Item loreJson(List lore); + + Item loreComponent(List lore); + + Optional> loreJson(); + + Optional> loreComponent(); Optional jukeboxSong(); @@ -90,7 +110,6 @@ public interface Item { Item equippable(EquipmentData equipmentData); - Optional> lore(); Item unbreakable(boolean unbreakable); @@ -110,7 +129,9 @@ public interface Item { Item itemFlags(List flags); - Object getTag(Object... path); + Object getJavaTag(Object... path); + + Tag getNBTTag(Object... path); Item setTag(Object value, Object... path); @@ -122,14 +143,22 @@ public interface Item { void removeComponent(Object type); - Object getComponent(Object type); + Object getExactComponent(Object type); - Object getJavaTypeComponent(Object type); + Object getJavaComponent(Object type); - JsonElement getJsonTypeComponent(Object type); + JsonElement getJsonComponent(Object type); + + Tag getNBTComponent(Object type); void setComponent(Object type, Object value); + void setJavaComponent(Object type, Object value); + + void setJsonComponent(Object type, JsonElement value); + + void setNBTComponent(Object type, Tag value); + void resetComponent(Object type); I getItem(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java index eb3c9fada..b27853e93 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemFactory.java @@ -1,13 +1,16 @@ package net.momirealms.craftengine.core.item; import com.google.gson.JsonElement; +import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.Key; -import org.jetbrains.annotations.Nullable; +import net.momirealms.sparrow.nbt.Tag; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; public abstract class ItemFactory, I> { protected final CraftEngine plugin; @@ -25,13 +28,11 @@ public abstract class ItemFactory, I> { protected abstract void merge(W item1, W item2); - protected abstract Object encodeJava(Object type, @Nullable Object component); - - protected abstract JsonElement encodeJson(Object type, Object component); - protected abstract W wrapInternal(I item); - protected abstract Object getTag(W item, Object... path); + protected abstract Object getJavaTag(W item, Object... path); + + protected abstract Tag getNBTTag(W item, Object... path); protected abstract void setTag(W item, Object value, Object... path); @@ -41,7 +42,13 @@ public abstract class ItemFactory, I> { protected abstract void setComponent(W item, Object type, Object value); - protected abstract Object getComponent(W item, Object type); + protected abstract Object getExactComponent(W item, Object type); + + protected abstract Object getJavaComponent(W item, Object type); + + protected abstract JsonElement getJsonComponent(W item, Object type); + + protected abstract Tag getNBTComponent(W item, Object type); protected abstract boolean hasComponent(W item, Object type); @@ -57,20 +64,56 @@ public abstract class ItemFactory, I> { protected abstract Optional customModelData(W item); - protected abstract void customName(W item, String json); + protected abstract void customNameJson(W item, String json); - protected abstract Optional customName(W item); + protected abstract Optional customNameJson(W item); - protected abstract void itemName(W item, String json); + protected void customNameComponent(W item, Component component) { + if (component != null) { + customNameJson(item, AdventureHelper.componentToJson(component)); + } else { + customNameJson(item, null); + } + } - protected abstract Optional itemName(W item); + protected Optional customNameComponent(W item) { + return customNameJson(item).map(AdventureHelper::jsonToComponent); + } + + protected abstract void itemNameJson(W item, String json); + + protected abstract Optional itemNameJson(W item); + + protected void itemNameComponent(W item, Component component) { + if (component != null) { + itemNameJson(item, AdventureHelper.componentToJson(component)); + } else { + itemNameJson(item, null); + } + } + + protected Optional itemNameComponent(W item) { + return itemNameJson(item).map(AdventureHelper::jsonToComponent); + } + + protected abstract Optional> loreJson(W item); + + protected abstract void loreJson(W item, List lore); + + protected void loreComponent(W item, List component) { + if (component != null && !component.isEmpty()) { + loreJson(item, component.stream().map(AdventureHelper::componentToJson).collect(Collectors.toList())); + } else { + loreJson(item, null); + } + } + + protected Optional> loreComponent(W item) { + return loreJson(item).map(list -> list.stream().map(AdventureHelper::jsonToComponent).toList()); + } protected abstract void skull(W item, String skullData); - protected abstract Optional> lore(W item); - - protected abstract void lore(W item, List lore); - protected abstract boolean unbreakable(W item); protected abstract void unbreakable(W item, boolean unbreakable); @@ -87,7 +130,7 @@ public abstract class ItemFactory, I> { protected abstract void dyedColor(W item, Integer color); - protected abstract Optional maxDamage(W item); + protected abstract int maxDamage(W item); protected abstract void maxDamage(W item, Integer damage); @@ -145,4 +188,9 @@ public abstract class ItemFactory, I> { protected abstract byte[] toByteArray(W item); + protected abstract void setJavaComponent(W item, Object type, Object value); + + protected abstract void setJsonComponent(W item, Object type, JsonElement value); + + protected abstract void setNBTComponent(W item, Object type, Tag value); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java index 05c1c2f78..f5fc439c2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java @@ -28,6 +28,8 @@ public class ItemKeys { public static final Key BUCKET = Key.of("minecraft:bucket"); public static final Key BONE_MEAL = Key.of("minecraft:bone_meal"); public static final Key ENCHANTED_BOOK = Key.of("minecraft:enchanted_book"); + public static final Key TOTEM_OF_UNDYING = Key.of("minecraft:totem_of_undying"); + public static final Key BARRIER = Key.of("minecraft:barrier"); public static final Key[] AXES = new Key[] { WOODEN_AXE, STONE_AXE, IRON_AXE, GOLDEN_AXE, DIAMOND_AXE, NETHERITE_AXE diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java index a5320e710..0ab35daea 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java @@ -11,8 +11,8 @@ import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.Key; import org.incendo.cloud.suggestion.Suggestion; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.*; public interface ItemManager extends Manageable, ModelGenerator { @@ -27,7 +27,7 @@ public interface ItemManager extends Manageable, ModelGenerator { Map modernItemModels1_21_4(); - Map> modernItemModels1_21_2(); + Map> modernItemModels1_21_2(); Collection vanillaItems(); @@ -59,6 +59,8 @@ public interface ItemManager extends Manageable, ModelGenerator { Optional> getVanillaItem(Key key); + NetworkItemHandler networkItemHandler(); + default Optional> getBuildableItem(Key key) { Optional> item = getCustomItem(key); if (item.isPresent()) { @@ -67,6 +69,8 @@ public interface ItemManager extends Manageable, ModelGenerator { return getVanillaItem(key); } + boolean addCustomItem(CustomItem customItem); + List> tagToItems(Key tag); List> tagToVanillaItems(Key tag); @@ -80,4 +84,6 @@ public interface ItemManager extends Manageable, ModelGenerator { Collection cachedSuggestions(); Collection cachedTotemSuggestions(); + + boolean isVanillaItem(Key item); } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java index c2f41b13c..a9f93ebb5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java @@ -6,6 +6,7 @@ import net.momirealms.craftengine.core.item.modifier.EquippableModifier; import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -28,6 +29,7 @@ public class ItemSettings { boolean canPlaceRelatedVanillaBlock = false; ProjectileMeta projectileMeta; boolean dyeable = true; + Helmet helmet = null; private ItemSettings() {} @@ -105,6 +107,11 @@ public class ItemSettings { return anvilRepairItems; } + @Nullable + public Helmet helmet() { + return helmet; + } + @Nullable public EquipmentGeneration equipment() { return equipment; @@ -155,6 +162,11 @@ public class ItemSettings { return this; } + public ItemSettings helmet(Helmet helmet) { + this.helmet = helmet; + return this; + } + @FunctionalInterface public interface Modifier { @@ -229,6 +241,10 @@ public class ItemSettings { String type = args.getOrDefault("type", "none").toString(); return settings -> settings.projectileMeta(new ProjectileMeta(customTridentItemId, displayType, scale, translation, rotation, type)); })); + registerFactory("helmet", (value -> { + Map args = MiscUtils.castToMap(value, false); + return settings -> settings.helmet(new Helmet(SoundData.create(args.getOrDefault("equip-sound", "minecraft:intentionally_empty"), 1f, 1f))); + })); registerFactory("dyeable", (value -> { boolean bool = (boolean) value; return settings -> settings.dyeable(bool); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/NetworkItemDataProcessor.java b/core/src/main/java/net/momirealms/craftengine/core/item/NetworkItemDataProcessor.java deleted file mode 100644 index 352559f86..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/item/NetworkItemDataProcessor.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.momirealms.craftengine.core.item; - -import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public record NetworkItemDataProcessor(@Nullable ItemDataModifier server, @NotNull ItemDataModifier client) { - - public static NetworkItemDataProcessor clientOnly(ItemDataModifier client) { - return new NetworkItemDataProcessor<>(null, client); - } - - public static NetworkItemDataProcessor both(ItemDataModifier server, ItemDataModifier client) { - return new NetworkItemDataProcessor<>(server, client); - } - - public void toClient(Item item, ItemBuildContext context) { - this.client.apply(item, context); - } - - public void toServer(Item item, ItemBuildContext context) { - this.client.remove(item); - if (this.server != null) { - this.server.apply(item, context); - } - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/NetworkItemHandler.java b/core/src/main/java/net/momirealms/craftengine/core/item/NetworkItemHandler.java new file mode 100644 index 000000000..09635b18d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/NetworkItemHandler.java @@ -0,0 +1,65 @@ +package net.momirealms.craftengine.core.item; + +import net.momirealms.craftengine.core.util.StringUtils; +import net.momirealms.craftengine.core.util.TriConsumer; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.sparrow.nbt.ByteTag; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.Optional; + +public interface NetworkItemHandler { + Operation[] BY_INDEX = new Operation[] {Operation.ADD, Operation.REMOVE, Operation.RESET}; + String NETWORK_ITEM_TAG = "craftengine:network_modifiers"; + String NETWORK_OPERATION = "type"; + String NETWORK_VALUE = "value"; + + Optional> s2c(Item itemStack, ItemBuildContext context); + + Optional> c2s(Item itemStack, ItemBuildContext context); + + static CompoundTag pack(Operation operation, @Nullable Tag value) { + if (value == null) { + return new CompoundTag(Map.of(NETWORK_OPERATION, operation.tag())); + } else { + return new CompoundTag(Map.of(NETWORK_OPERATION, operation.tag(), NETWORK_VALUE, value)); + } + } + + static CompoundTag pack(Operation operation) { + return new CompoundTag(Map.of(NETWORK_OPERATION, operation.tag())); + } + + static void apply(String tagPath, CompoundTag networkData, Item item) { + byte index = networkData.getByte(NETWORK_OPERATION); + Operation operation = BY_INDEX[index]; + operation.consumer.accept(item, tagPath, operation == Operation.ADD ? networkData.get(NETWORK_VALUE) : null); + } + + enum Operation { + ADD(0, Item::setNBTComponent, (i, s, t) -> i.setTag(t, (Object[]) StringUtils.splitByDot(s))), + REMOVE(1, (i, s, t) -> i.removeComponent(s), (i, s, t) -> i.removeTag((Object[]) StringUtils.splitByDot(s))), + RESET(2, (i, s, t) -> i.resetComponent(s), (i, s, t) -> i.removeTag((Object[]) StringUtils.splitByDot(s))); + + private final int id; + private final ByteTag tag; + private final TriConsumer, String, Tag> consumer; + + Operation(int id, TriConsumer, String, Tag> componentConsumer, TriConsumer, String, Tag> nbtConsumer) { + this.id = id; + this.tag = new ByteTag((byte) id); + this.consumer = VersionHelper.isOrAbove1_20_5() ? componentConsumer : nbtConsumer; + } + + public int id() { + return this.id; + } + + public ByteTag tag() { + return tag; + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehavior.java index 3c73c14e7..cd243ad72 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehavior.java @@ -15,4 +15,9 @@ public abstract class ItemBehavior { public InteractionResult use(World world, Player player, InteractionHand hand) { return InteractionResult.PASS; } + + // TODO + public InteractionResult useOnEntity() { + return InteractionResult.PASS; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviors.java b/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviors.java index 09d5eadc1..28c122108 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviors.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/behavior/ItemBehaviors.java @@ -7,10 +7,13 @@ import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.registry.Registries; import net.momirealms.craftengine.core.registry.WritableRegistry; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.ResourceKey; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.Map; public class ItemBehaviors { @@ -32,4 +35,23 @@ public class ItemBehaviors { } return factory.create(pack, path, id, map); } + + public static List fromList(Pack pack, Path path, Key id, List> list) { + List behaviors = new ArrayList<>(list.size()); + for (Map map : list) { + behaviors.add(fromMap(pack, path, id, map)); + } + return behaviors; + } + + @SuppressWarnings("unchecked") + public static List fromObj(Pack pack, Path path, Key id, Object behaviorObj) { + if (behaviorObj instanceof Map) { + return List.of(fromMap(pack, path, id, MiscUtils.castToMap(behaviorObj, false))); + } else if (behaviorObj instanceof List) { + return fromList(pack, path, id, (List>) behaviorObj); + } else { + return List.of(); + } + } } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/context/UseOnContext.java b/core/src/main/java/net/momirealms/craftengine/core/item/context/UseOnContext.java index 5e6280e02..cb65aff39 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/context/UseOnContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/context/UseOnContext.java @@ -20,6 +20,10 @@ public class UseOnContext { this(player.world(), player, hand, player.getItemInHand(hand), hit); } + public UseOnContext(Player player, InteractionHand hand, Item stack, BlockHitResult hit) { + this(player.world(), player, hand, stack, hit); + } + public UseOnContext(World world, Player player, InteractionHand hand, Item stack, BlockHitResult hit) { this.player = player; this.hand = hand; @@ -73,6 +77,6 @@ public class UseOnContext { } public float getRotation() { - return this.player == null ? 0.0F : this.player.getYRot(); + return this.player == null ? 0.0F : this.player.yRot(); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ComponentModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ComponentModifier.java index 216c7740c..5c769b2bc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ComponentModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ComponentModifier.java @@ -5,9 +5,12 @@ import com.google.gson.JsonObject; import net.momirealms.craftengine.core.item.ComponentKeys; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.NetworkItemHandler; import net.momirealms.craftengine.core.util.GsonHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.Pair; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; import java.util.ArrayList; import java.util.List; @@ -65,7 +68,7 @@ public class ComponentModifier implements ItemDataModifier { item.setComponent(entry.left(), entry.right()); } if (this.customData != null) { - JsonObject tag = (JsonObject) item.getJsonTypeComponent(ComponentKeys.CUSTOM_DATA); + JsonObject tag = (JsonObject) item.getJsonComponent(ComponentKeys.CUSTOM_DATA); if (tag != null) { item.setComponent(ComponentKeys.CUSTOM_DATA, GsonHelper.shallowMerge(this.customData, tag)); } else { @@ -75,18 +78,13 @@ public class ComponentModifier implements ItemDataModifier { } @Override - public void remove(Item item) { + public void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { for (Pair entry : this.arguments) { - item.resetComponent(entry.left()); - } - if (this.customData != null) { - JsonObject tag = (JsonObject) item.getJsonTypeComponent(ComponentKeys.CUSTOM_DATA); - if (tag != null) { - // crude method - for (String key : this.customData.keySet()) { - tag.remove(key); - } - item.setComponent(ComponentKeys.CUSTOM_DATA, tag); + Tag previous = item.getNBTComponent(entry.left()); + if (previous != null) { + networkData.put(entry.left().asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(entry.left().asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/CustomModelDataModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/CustomModelDataModifier.java index 68a71ed3d..fb6ec08d1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/CustomModelDataModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/CustomModelDataModifier.java @@ -1,7 +1,12 @@ package net.momirealms.craftengine.core.item.modifier; +import net.momirealms.craftengine.core.item.ComponentKeys; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.NetworkItemHandler; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; public class CustomModelDataModifier implements ItemDataModifier { private final int argument; @@ -21,7 +26,21 @@ public class CustomModelDataModifier implements ItemDataModifier { } @Override - public void remove(Item item) { - item.customModelData(null); + public void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + if (VersionHelper.isOrAbove1_20_5()) { + Tag previous = item.getNBTComponent(ComponentKeys.CUSTOM_MODEL_DATA); + if (previous != null) { + networkData.put(ComponentKeys.CUSTOM_MODEL_DATA.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(ComponentKeys.CUSTOM_MODEL_DATA.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } else { + Tag previous = item.getNBTTag("CustomModelData"); + if (previous != null) { + networkData.put("CustomModelData", NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put("CustomModelData", NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/CustomNameModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/CustomNameModifier.java new file mode 100644 index 000000000..d16b5281b --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/CustomNameModifier.java @@ -0,0 +1,48 @@ +package net.momirealms.craftengine.core.item.modifier; + +import net.momirealms.craftengine.core.item.ComponentKeys; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.NetworkItemHandler; +import net.momirealms.craftengine.core.plugin.config.Config; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; + +public class CustomNameModifier implements ItemDataModifier { + private final String argument; + + public CustomNameModifier(String argument) { + this.argument = Config.nonItalic() ? "" + argument : argument; + } + + @Override + public String name() { + return "custom-name"; + } + + @Override + public void apply(Item item, ItemBuildContext context) { + item.customNameComponent(AdventureHelper.miniMessage().deserialize(this.argument, context.tagResolvers())); + } + + @Override + public void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + if (VersionHelper.isOrAbove1_20_5()) { + Tag previous = item.getNBTComponent(ComponentKeys.CUSTOM_NAME); + if (previous != null) { + networkData.put(ComponentKeys.CUSTOM_NAME.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(ComponentKeys.CUSTOM_NAME.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } else { + Tag previous = item.getNBTTag("display", "Name"); + if (previous != null) { + networkData.put("display.Name", NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put("display.Name", NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DisplayNameModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DisplayNameModifier.java deleted file mode 100644 index cf468756c..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/DisplayNameModifier.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.momirealms.craftengine.core.item.modifier; - -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.item.ItemBuildContext; -import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.util.AdventureHelper; - -public class DisplayNameModifier implements ItemDataModifier { - private final String argument; - - public DisplayNameModifier(String argument) { - this.argument = Config.nonItalic() ? "" + argument : argument; - } - - @Override - public String name() { - return "display-name"; - } - - @Override - public void apply(Item item, ItemBuildContext context) { - item.customName(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(this.argument, context.tagResolvers()))); - } - - @Override - public void remove(Item item) { - item.customName(null); - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/EnchantmentModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/EnchantmentModifier.java index 15c2137b6..88a06c183 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/EnchantmentModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/EnchantmentModifier.java @@ -1,9 +1,9 @@ package net.momirealms.craftengine.core.item.modifier; -import net.momirealms.craftengine.core.item.Enchantment; -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.item.ItemBuildContext; -import net.momirealms.craftengine.core.item.ItemKeys; +import net.momirealms.craftengine.core.item.*; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; import java.util.List; @@ -21,13 +21,47 @@ public class EnchantmentModifier implements ItemDataModifier { @Override public void apply(Item item, ItemBuildContext context) { - if (item.vanillaId().equals(ItemKeys.ENCHANTED_BOOK)) item.setStoredEnchantments(enchantments); - else item.setEnchantments(enchantments); + if (item.vanillaId().equals(ItemKeys.ENCHANTED_BOOK)) { + item.setStoredEnchantments(this.enchantments); + } else { + item.setEnchantments(this.enchantments); + } } @Override - public void remove(Item item) { - if (item.vanillaId().equals(ItemKeys.ENCHANTED_BOOK)) item.setStoredEnchantments(null); - else item.setEnchantments(null); + public void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + if (item.vanillaId().equals(ItemKeys.ENCHANTED_BOOK)) { + if (VersionHelper.isOrAbove1_20_5()) { + Tag previous = item.getNBTComponent(ComponentKeys.STORED_ENCHANTMENTS); + if (previous != null) { + networkData.put(ComponentKeys.STORED_ENCHANTMENTS.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(ComponentKeys.STORED_ENCHANTMENTS.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } else { + Tag previous = item.getNBTTag("StoredEnchantments"); + if (previous != null) { + networkData.put("StoredEnchantments", NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put("StoredEnchantments", NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } + } else { + if (VersionHelper.isOrAbove1_20_5()) { + Tag previous = item.getNBTComponent(ComponentKeys.ENCHANTMENTS); + if (previous != null) { + networkData.put(ComponentKeys.ENCHANTMENTS.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(ComponentKeys.ENCHANTMENTS.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } else { + Tag previous = item.getNBTTag("Enchantments"); + if (previous != null) { + networkData.put("Enchantments", NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put("Enchantments", NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/EquippableModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/EquippableModifier.java index 1f470455d..52845cb07 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/EquippableModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/EquippableModifier.java @@ -20,9 +20,4 @@ public class EquippableModifier implements ItemDataModifier { public void apply(Item item, ItemBuildContext context) { item.equippable(this.data); } - - @Override - public void remove(Item item) { - item.equippable(null); - } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java index 76219096e..0cf95a6e1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ExternalModifier.java @@ -30,9 +30,4 @@ public class ExternalModifier implements ItemDataModifier { Item anotherWrapped = (Item) CraftEngine.instance().itemManager().wrap(another); item.merge(anotherWrapped); } - - @Override - public void remove(Item item) { - // cannot remove - } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/IdModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/IdModifier.java index 4ed13c5a0..e82c960dc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/IdModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/IdModifier.java @@ -21,9 +21,4 @@ public class IdModifier implements ItemDataModifier { public void apply(Item item, ItemBuildContext context) { item.customId(this.argument); } - - @Override - public void remove(Item item) { - // WHY DO YOU WANT TO REMOVE CRAFTENGINE ID? - } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifier.java index 46258a96f..73720c53a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifier.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.item.modifier; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.sparrow.nbt.CompoundTag; public interface ItemDataModifier { @@ -9,5 +10,6 @@ public interface ItemDataModifier { void apply(Item item, ItemBuildContext context); - void remove(Item item); + default void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemModelModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemModelModifier.java index b2e1b031b..537ed5aee 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemModelModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemModelModifier.java @@ -1,8 +1,12 @@ package net.momirealms.craftengine.core.item.modifier; +import net.momirealms.craftengine.core.item.ComponentKeys; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.NetworkItemHandler; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; public class ItemModelModifier implements ItemDataModifier { private final Key data; @@ -22,7 +26,12 @@ public class ItemModelModifier implements ItemDataModifier { } @Override - public void remove(Item item) { - item.itemModel(null); + public void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + Tag previous = item.getNBTComponent(ComponentKeys.ITEM_MODEL); + if (previous != null) { + networkData.put(ComponentKeys.ITEM_MODEL.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(ComponentKeys.ITEM_MODEL.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemNameModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemNameModifier.java index 8ba7e0967..f59c44abb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemNameModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemNameModifier.java @@ -1,9 +1,14 @@ package net.momirealms.craftengine.core.item.modifier; +import net.momirealms.craftengine.core.item.ComponentKeys; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.NetworkItemHandler; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; public class ItemNameModifier implements ItemDataModifier { private final String argument; @@ -19,11 +24,25 @@ public class ItemNameModifier implements ItemDataModifier { @Override public void apply(Item item, ItemBuildContext context) { - item.itemName(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(this.argument, context.tagResolvers()))); + item.itemNameComponent(AdventureHelper.miniMessage().deserialize(this.argument, context.tagResolvers())); } @Override - public void remove(Item item) { - item.itemName(null); + public void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + if (VersionHelper.isOrAbove1_20_5()) { + Tag previous = item.getNBTComponent(ComponentKeys.ITEM_NAME); + if (previous != null) { + networkData.put(ComponentKeys.ITEM_NAME.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(ComponentKeys.ITEM_NAME.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } else { + Tag previous = item.getNBTTag("display", "Name"); + if (previous != null) { + networkData.put("display.Name", NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put("display.Name", NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/JukeboxSongModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/JukeboxSongModifier.java index 6f323c087..b8523edd2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/JukeboxSongModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/JukeboxSongModifier.java @@ -20,9 +20,4 @@ public class JukeboxSongModifier implements ItemDataModifier { public void apply(Item item, ItemBuildContext context) { item.jukeboxSong(this.song); } - - @Override - public void remove(Item item) { - item.jukeboxSong(null); - } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/LoreModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/LoreModifier.java index 5515ab4d2..6328fe5c8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/LoreModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/LoreModifier.java @@ -1,9 +1,14 @@ package net.momirealms.craftengine.core.item.modifier; +import net.momirealms.craftengine.core.item.ComponentKeys; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.NetworkItemHandler; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; import java.util.List; @@ -21,12 +26,26 @@ public class LoreModifier implements ItemDataModifier { @Override public void apply(Item item, ItemBuildContext context) { - item.lore(this.argument.stream().map(it -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize( - it, context.tagResolvers()))).toList()); + item.loreComponent(this.argument.stream().map(it -> AdventureHelper.miniMessage().deserialize( + it, context.tagResolvers())).toList()); } @Override - public void remove(Item item) { - item.lore(null); + public void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + if (VersionHelper.isOrAbove1_20_5()) { + Tag previous = item.getNBTComponent(ComponentKeys.LORE); + if (previous != null) { + networkData.put(ComponentKeys.LORE.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(ComponentKeys.LORE.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } else { + Tag previous = item.getNBTTag("display", "Lore"); + if (previous != null) { + networkData.put("display.Lore", NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put("display.Lore", NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/RemoveComponentModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/RemoveComponentModifier.java index b44deeca0..90dbb508c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/RemoveComponentModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/RemoveComponentModifier.java @@ -2,6 +2,9 @@ package net.momirealms.craftengine.core.item.modifier; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.NetworkItemHandler; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; import java.util.Collections; import java.util.List; @@ -24,13 +27,18 @@ public class RemoveComponentModifier implements ItemDataModifier { @Override public void apply(Item item, ItemBuildContext context) { - for (String argument : arguments) { + for (String argument : this.arguments) { item.removeComponent(argument); } } @Override - public void remove(Item item) { - // I can't guess + public void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + for (String component : this.arguments) { + Tag previous = item.getNBTComponent(component); + if (previous != null) { + networkData.put(component, NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/TagsModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/TagsModifier.java index 69c29c859..582fce937 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/TagsModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/TagsModifier.java @@ -2,8 +2,11 @@ package net.momirealms.craftengine.core.item.modifier; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.NetworkItemHandler; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.TypeUtils; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; import java.util.LinkedHashMap; import java.util.Map; @@ -26,17 +29,23 @@ public class TagsModifier implements ItemDataModifier { @Override public void apply(Item item, ItemBuildContext context) { - for (Map.Entry entry : arguments.entrySet()) { + for (Map.Entry entry : this.arguments.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); item.setTag(value, key); } } + // TODO NOT PERFECT @Override - public void remove(Item item) { - for (Map.Entry entry : arguments.entrySet()) { - item.removeTag(entry.getKey()); + public void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + for (Map.Entry entry : this.arguments.entrySet()) { + Tag previous = item.getNBTTag(entry.getKey()); + if (previous != null) { + networkData.put(entry.getKey(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(entry.getKey(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/TooltipStyleModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/TooltipStyleModifier.java index 3e078af4b..de060f05d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/TooltipStyleModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/TooltipStyleModifier.java @@ -1,8 +1,12 @@ package net.momirealms.craftengine.core.item.modifier; +import net.momirealms.craftengine.core.item.ComponentKeys; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.NetworkItemHandler; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; public class TooltipStyleModifier implements ItemDataModifier { private final Key argument; @@ -22,7 +26,12 @@ public class TooltipStyleModifier implements ItemDataModifier { } @Override - public void remove(Item item) { - item.tooltipStyle(null); + public void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + Tag previous = item.getNBTComponent(ComponentKeys.TOOLTIP_STYLE); + if (previous != null) { + networkData.put(ComponentKeys.TOOLTIP_STYLE.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(ComponentKeys.TOOLTIP_STYLE.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/TrimModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/TrimModifier.java index 011809750..4d8a4facd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/TrimModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/TrimModifier.java @@ -1,8 +1,9 @@ package net.momirealms.craftengine.core.item.modifier; -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.item.ItemBuildContext; -import net.momirealms.craftengine.core.item.Trim; +import net.momirealms.craftengine.core.item.*; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; public class TrimModifier implements ItemDataModifier { private final String material; @@ -24,7 +25,21 @@ public class TrimModifier implements ItemDataModifier { } @Override - public void remove(Item item) { - item.trim(null); + public void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + if (VersionHelper.isOrAbove1_20_5()) { + Tag previous = item.getNBTComponent(ComponentKeys.TRIM); + if (previous != null) { + networkData.put(ComponentKeys.TRIM.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(ComponentKeys.TRIM.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } else { + Tag previous = item.getNBTTag("Trim"); + if (previous != null) { + networkData.put("Trim", NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put("Trim", NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/UnbreakableModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/UnbreakableModifier.java index c673c82a4..34c58ed56 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/UnbreakableModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/UnbreakableModifier.java @@ -1,7 +1,12 @@ package net.momirealms.craftengine.core.item.modifier; +import net.momirealms.craftengine.core.item.ComponentKeys; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.NetworkItemHandler; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; public class UnbreakableModifier implements ItemDataModifier { private final boolean argument; @@ -21,9 +26,21 @@ public class UnbreakableModifier implements ItemDataModifier { } @Override - public void remove(Item item) { - if (this.argument) { - item.unbreakable(false); + public void prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + if (VersionHelper.isOrAbove1_20_5()) { + Tag previous = item.getNBTComponent(ComponentKeys.UNBREAKABLE); + if (previous != null) { + networkData.put(ComponentKeys.UNBREAKABLE.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(ComponentKeys.UNBREAKABLE.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + } else { + Tag previous = item.getNBTTag("Unbreakable"); + if (previous != null) { + networkData.put("Unbreakable", NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put("Unbreakable", NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTransformRecipe.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTransformRecipe.java index b4698e00b..2a8ee7318 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTransformRecipe.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/CustomSmithingTransformRecipe.java @@ -219,7 +219,7 @@ public class CustomSmithingTransformRecipe implements Recipe { @Override public void accept(Item item1, Item item2, Item item3) { for (Key component : this.components) { - Object componentObj = item1.getComponent(component); + Object componentObj = item1.getExactComponent(component); if (componentObj != null) { item3.setComponent(component, componentObj); } @@ -256,7 +256,7 @@ public class CustomSmithingTransformRecipe implements Recipe { @Override public void accept(Item item1, Item item2, Item item3) { for (String[] tag : this.tags) { - Object tagObj = item1.getTag((Object[]) tag); + Object tagObj = item1.getJavaTag((Object[]) tag); if (tagObj != null) { item3.setTag(tagObj, (Object[]) tag); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/StackedContents.java b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/StackedContents.java index c6a483ac6..15442f74c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/recipe/StackedContents.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/recipe/StackedContents.java @@ -4,8 +4,8 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.BitSet; import java.util.List; diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/LootConditions.java b/core/src/main/java/net/momirealms/craftengine/core/loot/LootConditions.java index 43ca9b0aa..0f43e96d8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/LootConditions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/LootConditions.java @@ -31,8 +31,13 @@ public class LootConditions { register(CommonConditions.RANDOM, new RandomCondition.FactoryImpl<>()); register(CommonConditions.DISTANCE, new DistanceCondition.FactoryImpl<>()); register(CommonConditions.PERMISSION, new PermissionCondition.FactoryImpl<>()); - register(CommonConditions.EQUALS, new EqualsCondition.FactoryImpl<>()); + register(CommonConditions.EQUALS, new StringEqualsCondition.FactoryImpl<>()); + register(CommonConditions.STRING_REGEX, new StringRegexCondition.FactoryImpl<>()); + register(CommonConditions.STRING_EQUALS, new StringEqualsCondition.FactoryImpl<>()); + register(CommonConditions.STRING_CONTAINS, new StringContainsCondition.FactoryImpl<>()); register(CommonConditions.EXPRESSION, new ExpressionCondition.FactoryImpl<>()); + register(CommonConditions.IS_NULL, new IsNullCondition.FactoryImpl<>()); + register(CommonConditions.HAND, new HandCondition.FactoryImpl<>()); } public static void register(Key key, ConditionFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/LootContext.java b/core/src/main/java/net/momirealms/craftengine/core/loot/LootContext.java index 5e3c9092d..8ca3cf5e2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/LootContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/LootContext.java @@ -5,8 +5,7 @@ import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.world.World; import org.jetbrains.annotations.NotNull; - -import javax.annotation.Nullable; +import org.jetbrains.annotations.Nullable; public class LootContext extends PlayerOptionalContext { private final World world; diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/LootTable.java b/core/src/main/java/net/momirealms/craftengine/core/loot/LootTable.java index 3ea626c53..662cefcea 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/LootTable.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/LootTable.java @@ -78,15 +78,15 @@ public class LootTable { ); } - public ArrayList> getRandomItems(ContextHolder parameters, World world) { + public List> getRandomItems(ContextHolder parameters, World world) { return this.getRandomItems(parameters, world, null); } - public ArrayList> getRandomItems(ContextHolder parameters, World world, @Nullable Player player) { + public List> getRandomItems(ContextHolder parameters, World world, @Nullable Player player) { return this.getRandomItems(new LootContext(world, player, player == null ? 1f : (float) player.luck(), parameters)); } - private ArrayList> getRandomItems(LootContext context) { + private List> getRandomItems(LootContext context) { ArrayList> list = new ArrayList<>(); this.getRandomItems(context, list::add); return list; @@ -127,7 +127,6 @@ public class LootTable { } } - // TODO https://mo-mi.gitbook.io/xiaomomi-plugins/craftengine/plugin-wiki/craftengine/loot-system/flat-format public static LootPool readFlatFormatLootPool(String pool) { return null; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index caf68d43b..bad5c544c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -5,6 +5,7 @@ import com.google.common.jimfs.Jimfs; import com.google.gson.*; import dev.dejvokep.boostedyaml.YamlDocument; import dev.dejvokep.boostedyaml.block.implementation.Section; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.momirealms.craftengine.core.font.BitmapImage; import net.momirealms.craftengine.core.font.Font; import net.momirealms.craftengine.core.item.EquipmentData; @@ -42,6 +43,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.file.*; +import java.nio.file.FileSystem; import java.nio.file.attribute.BasicFileAttributes; import java.util.*; import java.util.function.BiConsumer; @@ -145,21 +147,26 @@ public abstract class AbstractPackManager implements PackManager { @Override public void load() { - List> list = Config.instance().settings().getMapList("resource-pack.delivery.hosting"); - if (list == null || list.isEmpty()) { - this.resourcePackHost = NoneHost.INSTANCE; + Object hostingObj = Config.instance().settings().get("resource-pack.delivery.hosting"); + Map arguments; + if (hostingObj instanceof Map) { + arguments = MiscUtils.castToMap(hostingObj, false); + } else if (hostingObj instanceof List list && !list.isEmpty()) { + arguments = MiscUtils.castToMap(list.get(0), false); } else { - try { - // we might add multiple host methods in future versions - this.resourcePackHost = ResourcePackHosts.fromMap(MiscUtils.castToMap(list.get(0), false)); - } catch (LocalizedException e) { - if (e instanceof LocalizedResourceConfigException exception) { - exception.setPath(plugin.dataFolderPath().resolve("config.yml")); - e.setArgument(1, "hosting"); - } - TranslationManager.instance().log(e.node(), e.arguments()); - this.resourcePackHost = NoneHost.INSTANCE; + this.resourcePackHost = NoneHost.INSTANCE; + return; + } + try { + // we might add multiple host methods in future versions + this.resourcePackHost = ResourcePackHosts.fromMap(arguments); + } catch (LocalizedException e) { + if (e instanceof LocalizedResourceConfigException exception) { + exception.setPath(plugin.dataFolderPath().resolve("config.yml")); + e.setArgument(1, "hosting"); } + TranslationManager.instance().log(e.node(), e.arguments()); + this.resourcePackHost = NoneHost.INSTANCE; } } @@ -195,7 +202,7 @@ public abstract class AbstractPackManager implements PackManager { Object magicObject = magicConstructor.newInstance(p1, p2); magicMethod.invoke(magicObject); } catch (Exception e) { - this.plugin.logger().warn("Failed to generate zip files", e); + this.plugin.logger().warn("Failed to generate zip files\n" + new StringWriter(){{e.printStackTrace(new PrintWriter(this));}}.toString().replaceAll("\\.[Il]{2,}", "")); } }; } else { @@ -344,6 +351,10 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/netherite_anvil_top.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/solid_gunpowder_block.png"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/gunpowder_block.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_side.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_on.png"); + plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/block/custom/copper_coil_on_side.png"); // items plugin.saveResource("resources/default/configuration/items.yml"); plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/item/custom/topaz_rod.png"); @@ -419,48 +430,54 @@ public abstract class AbstractPackManager implements PackManager { plugin.saveResource("resources/default/resourcepack/assets/minecraft/textures/gui/sprites/tooltip/topaz_frame.png.mcmeta"); } - private void updateCachedConfigFiles() { + private TreeMap> updateCachedConfigFiles() { + TreeMap> cachedConfigs = new TreeMap<>(); Map previousFiles = this.cachedConfigFiles; - this.cachedConfigFiles = new HashMap<>(); + this.cachedConfigFiles = new Object2ObjectOpenHashMap<>(32); + Yaml yaml = new Yaml(new StringKeyConstructor(new LoaderOptions())); for (Pack pack : loadedPacks()) { if (!pack.enabled()) continue; - List files = FileUtils.getYmlConfigsDeeply(pack.configurationFolder()); - for (Path path : files) { - CachedConfigFile cachedFile = previousFiles.get(path); - try { - long lastModifiedTime = Files.getLastModifiedTime(path).toMillis(); - long size = Files.size(path); - if (cachedFile != null && cachedFile.lastModified() == lastModifiedTime && cachedFile.size() == size) { - this.cachedConfigFiles.put(path, cachedFile); - continue; + Path configurationFolderPath = pack.configurationFolder(); + if (!Files.isDirectory(configurationFolderPath)) continue; + try { + Files.walkFileTree(configurationFolderPath, new SimpleFileVisitor<>() { + @Override + public @NotNull FileVisitResult visitFile(@NotNull Path path, @NotNull BasicFileAttributes attrs) { + if (Files.isRegularFile(path) && path.getFileName().toString().endsWith(".yml")) { + CachedConfigFile cachedFile = previousFiles.get(path); + long lastModifiedTime = attrs.lastModifiedTime().toMillis(); + long size = attrs.size(); + if (cachedFile != null && cachedFile.lastModified() == lastModifiedTime && cachedFile.size() == size) { + AbstractPackManager.this.cachedConfigFiles.put(path, cachedFile); + } else { + try (InputStreamReader inputStream = new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8)) { + Map data = yaml.load(inputStream); + if (data == null) return FileVisitResult.CONTINUE; + cachedFile = new CachedConfigFile(data, pack, lastModifiedTime, size); + AbstractPackManager.this.cachedConfigFiles.put(path, cachedFile); + } catch (IOException e) { + AbstractPackManager.this.plugin.logger().severe("Error while reading config file: " + path, e); + return FileVisitResult.CONTINUE; + } + } + for (Map.Entry entry : cachedFile.config().entrySet()) { + processConfigEntry(entry, path, cachedFile.pack(), (p, c) -> + cachedConfigs.computeIfAbsent(p, k -> new ArrayList<>()).add(c)); + } + } + return FileVisitResult.CONTINUE; } - try (InputStreamReader inputStream = new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8)) { - Yaml yaml = new Yaml(new StringKeyConstructor(new LoaderOptions())); - Map data = yaml.load(inputStream); - if (data == null) continue; - - this.cachedConfigFiles.put(path, new CachedConfigFile(data, pack, lastModifiedTime, size)); - } catch (Exception e) { - this.plugin.logger().warn(path, "Error loading config file", e); - } - } catch (IOException e) { - this.plugin.logger().warn(path, "Error reading last modified time", e); - } + }); + } catch (IOException e) { + this.plugin.logger().severe("Error while reading config file", e); } } + return cachedConfigs; } private void loadResourceConfigs(Predicate predicate) { - TreeMap> cachedConfigs = new TreeMap<>(); long o1 = System.nanoTime(); - this.updateCachedConfigFiles(); - for (Map.Entry fileEntry : this.cachedConfigFiles.entrySet()) { - CachedConfigFile cachedFile = fileEntry.getValue(); - for (Map.Entry entry : cachedFile.config().entrySet()) { - processConfigEntry(entry, fileEntry.getKey(), cachedFile.pack(), (p, c) -> - cachedConfigs.computeIfAbsent(p, k -> new ArrayList<>()).add(c)); - } - } + TreeMap> cachedConfigs = this.updateCachedConfigFiles(); long o2 = System.nanoTime(); this.plugin.logger().info("Loaded packs. Took " + String.format("%.2f", ((o2 - o1) / 1_000_000.0)) + " ms"); for (Map.Entry> entry : cachedConfigs.entrySet()) { @@ -469,29 +486,27 @@ public abstract class AbstractPackManager implements PackManager { for (CachedConfigSection cached : entry.getValue()) { for (Map.Entry configEntry : cached.config().entrySet()) { String key = configEntry.getKey(); + Key id = Key.withDefaultNamespace(key, cached.pack().namespace()); try { - Key id = Key.withDefaultNamespace(key, cached.pack().namespace()); - try { - if (parser.supportsParsingObject()) { - parser.parseObject(cached.pack(), cached.filePath(), id, configEntry.getValue()); - } else if (predicate.test(parser)) { - if (configEntry.getValue() instanceof Map configSection0) { - Map configSection1 = castToMap(configSection0, false); - if ((boolean) configSection1.getOrDefault("enable", true)) { - parser.parseSection(cached.pack(), cached.filePath(), id, plugin.templateManager().applyTemplates(configSection1)); - } - } else { - TranslationManager.instance().log("warning.config.structure.not_section", cached.filePath().toString(), cached.prefix() + "." + key, configEntry.getValue().getClass().getSimpleName()); + if (parser.supportsParsingObject()) { + parser.parseObject(cached.pack(), cached.filePath(), id, configEntry.getValue()); + } else if (predicate.test(parser)) { + if (configEntry.getValue() instanceof Map configSection0) { + Map configSection1 = castToMap(configSection0, false); + if ((boolean) configSection1.getOrDefault("enable", true)) { + parser.parseSection(cached.pack(), cached.filePath(), id, plugin.templateManager().applyTemplates(configSection1)); } + } else { + TranslationManager.instance().log("warning.config.structure.not_section", cached.filePath().toString(), cached.prefix() + "." + key, configEntry.getValue().getClass().getSimpleName()); } - } catch (LocalizedException e) { - if (e instanceof LocalizedResourceConfigException exception) { - exception.setPath(cached.filePath()); - exception.setId(cached.prefix() + "." + key); - } - TranslationManager.instance().log(e.node(), e.arguments()); - this.plugin.debug(e::node); } + } catch (LocalizedException e) { + if (e instanceof LocalizedResourceConfigException exception) { + exception.setPath(cached.filePath()); + exception.setId(cached.prefix() + "." + key); + } + TranslationManager.instance().log(e.node(), e.arguments()); + this.plugin.debug(e::node); } catch (Exception e) { this.plugin.logger().warn("Unexpected error loading file " + cached.filePath() + " - '" + parser.sectionId()[0] + "." + key + "'. Please find the cause according to the stacktrace or seek developer help.", e); } @@ -514,99 +529,6 @@ public abstract class AbstractPackManager implements PackManager { } } -// private static void initFileSystemProvider() { -// String osName = System.getProperty("os.name").toLowerCase(); -// String providerClass = null; -// if (osName.contains("win")) { -// providerClass = "sun.nio.fs.WindowsFileSystemProvider"; -// } else if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) { -// providerClass = "sun.nio.fs.LinuxFileSystemProvider"; -// } else if (osName.contains("mac")) { -// providerClass = "sun.nio.fs.MacOSXFileSystemProvider"; -// } -// if (providerClass != null) { -// try { -// System.setProperty("java.nio.file.spi.DefaultFileSystemProvider", providerClass); -// } catch (Exception ignored) {} -// } -// } -// -// private static void deleteDirectory(Path folder) throws IOException { -// if (!Files.exists(folder)) return; -// try (Stream walk = Files.walk(folder)) { -// walk.sorted(Comparator.reverseOrder()) -// .parallel() -// .forEach(path -> { -// try { -// Files.delete(path); -// } catch (IOException ignored) {} -// }); -// } -// } - - private List>> updateCachedAssets(@Nullable FileSystem fs) throws IOException { - List folders = new ArrayList<>(); - Map> conflictChecker = new HashMap<>(Math.max(128, this.cachedAssetFiles.size())); - Map previousFiles = this.cachedAssetFiles; - this.cachedAssetFiles = new HashMap<>(Math.max(128, this.cachedAssetFiles.size())); - folders.addAll(loadedPacks().stream().filter(Pack::enabled).map(Pack::resourcePackFolder).toList()); - folders.addAll(Config.foldersToMerge().stream().map(it -> plugin.dataFolderPath().getParent().resolve(it)).filter(Files::exists).toList()); - for (Path sourceFolder : folders) { - if (Files.exists(sourceFolder)) { - Files.walkFileTree(sourceFolder, new SimpleFileVisitor<>() { - @Override - public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException { - CachedAssetFile cachedAsset = previousFiles.get(file); - long lastModified = Files.getLastModifiedTime(file).toMillis(); - long size = Files.size(file); - if (cachedAsset != null && cachedAsset.lastModified() == lastModified && cachedAsset.size() == size) { - AbstractPackManager.this.cachedAssetFiles.put(file, cachedAsset); - } else { - cachedAsset = new CachedAssetFile(Files.readAllBytes(file), lastModified, size); - AbstractPackManager.this.cachedAssetFiles.put(file, cachedAsset); - } - if (fs == null) return FileVisitResult.CONTINUE; - Path relative = sourceFolder.relativize(file); - Path targetPath = fs.getPath("resource_pack/" + relative.toString().replace("\\", "/")); - List conflicts = conflictChecker.get(relative); - if (conflicts == null) { - Files.createDirectories(targetPath.getParent()); - Files.write(targetPath, cachedAsset.data()); - conflictChecker.put(relative, List.of(file)); - } else { - PathContext relativeCTX = PathContext.of(relative); - PathContext targetCTX = PathContext.of(targetPath); - PathContext fileCTX = PathContext.of(file); - for (ResolutionConditional resolution : Config.resolutions()) { - if (resolution.matcher().test(relativeCTX)) { - resolution.resolution().run(targetCTX, fileCTX); - return FileVisitResult.CONTINUE; - } - } - switch (conflicts.size()) { - case 1 -> conflictChecker.put(relative, List.of(conflicts.get(0), file)); - case 2 -> conflictChecker.put(relative, List.of(conflicts.get(0), conflicts.get(1), file)); - case 3 -> conflictChecker.put(relative, List.of(conflicts.get(0), conflicts.get(1), conflicts.get(2), file)); - case 4 -> conflictChecker.put(relative, List.of(conflicts.get(0), conflicts.get(1), conflicts.get(2), conflicts.get(3), file)); - default -> { - // just ignore it if it has many conflict files - } - } - } - return FileVisitResult.CONTINUE; - } - }); - } - } - List>> conflicts = new ArrayList<>(); - for (Map.Entry> entry : conflictChecker.entrySet()) { - if (entry.getValue().size() > 1) { - conflicts.add(Pair.of(entry.getKey(), entry.getValue())); - } - } - return conflicts; - } - @Override public void generateResourcePack() throws IOException { this.plugin.logger().info("Generating resource pack..."); @@ -616,11 +538,11 @@ public abstract class AbstractPackManager implements PackManager { try (FileSystem fs = Jimfs.newFileSystem(Configuration.forCurrentPlatform())) { // firstly merge existing folders Path generatedPackPath = fs.getPath("resource_pack"); - List>> duplicated = this.updateCachedAssets(fs); + List>> duplicated = this.updateCachedAssets(fs); if (!duplicated.isEmpty()) { plugin.logger().severe(AdventureHelper.miniMessage().stripTags(TranslationManager.instance().miniMessageTranslation("warning.config.pack.duplicated_files"))); int x = 1; - for (Pair> path : duplicated) { + for (Pair> path : duplicated) { this.plugin.logger().warn("[ " + (x++) + " ] " + path.left()); for (int i = 0, size = path.right().size(); i < size; i++) { if (i == size - 1) { @@ -976,9 +898,9 @@ public abstract class AbstractPackManager implements PackManager { if (Config.packMinVersion() > 21.39f) return; // 此段代码生成1.21.2专用的item model文件,情况非常复杂! - for (Map.Entry> entry : this.plugin.itemManager().modernItemModels1_21_2().entrySet()) { + for (Map.Entry> entry : this.plugin.itemManager().modernItemModels1_21_2().entrySet()) { Key itemModelPath = entry.getKey(); - List legacyOverridesModels = entry.getValue(); + TreeSet legacyOverridesModels = entry.getValue(); // 检测item model合法性 if (PRESET_MODERN_MODELS_ITEM.containsKey(itemModelPath) || PRESET_LEGACY_MODELS_ITEM.containsKey(itemModelPath)) { @@ -1224,7 +1146,7 @@ public abstract class AbstractPackManager implements PackManager { } try { - Files.writeString(fontPath, fontJson.toString().replace("\\\\u", "\\u")); + Files.writeString(fontPath, CharacterUtils.replaceDoubleBackslashU(fontJson.toString())); } catch (IOException e) { this.plugin.logger().severe("Error writing font to " + fontPath.toAbsolutePath(), e); } @@ -1248,4 +1170,131 @@ public abstract class AbstractPackManager implements PackManager { } } } + + private List>> updateCachedAssets(@Nullable FileSystem fs) throws IOException { + Map> conflictChecker = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size())); + Map previousFiles = this.cachedAssetFiles; + this.cachedAssetFiles = new Object2ObjectOpenHashMap<>(Math.max(128, this.cachedAssetFiles.size())); + + List folders = new ArrayList<>(); + folders.addAll(loadedPacks().stream() + .filter(Pack::enabled) + .map(Pack::resourcePackFolder) + .toList()); + folders.addAll(Config.foldersToMerge().stream() + .map(it -> this.plugin.dataFolderPath().getParent().resolve(it)) + .filter(Files::exists) + .toList()); + for (Path sourceFolder : folders) { + if (Files.exists(sourceFolder)) { + Files.walkFileTree(sourceFolder, new SimpleFileVisitor<>() { + @Override + public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) throws IOException { + processRegularFile(file, attrs, sourceFolder, fs, conflictChecker, previousFiles); + return FileVisitResult.CONTINUE; + } + }); + } + } + List externalZips = Config.zipsToMerge().stream() + .map(it -> this.plugin.dataFolderPath().getParent().resolve(it)) + .filter(Files::exists) + .filter(Files::isRegularFile) + .filter(file -> file.getFileName().toString().endsWith(".zip")) + .toList(); + for (Path zip : externalZips) { + processZipFile(zip, zip.getParent(), fs, conflictChecker, previousFiles); + } + + List>> conflicts = new ArrayList<>(); + for (Map.Entry> entry : conflictChecker.entrySet()) { + if (entry.getValue().size() > 1) { + conflicts.add(Pair.of(entry.getKey(), entry.getValue())); + } + } + return conflicts; + } + + private void processRegularFile(Path file, BasicFileAttributes attrs, Path sourceFolder, @Nullable FileSystem fs, + Map> conflictChecker, Map previousFiles) throws IOException { + if (Config.excludeFileExtensions().contains(FileUtils.getExtension(file))) { + return; + } + CachedAssetFile cachedAsset = previousFiles.get(file); + long lastModified = attrs.lastModifiedTime().toMillis(); + long size = attrs.size(); + if (cachedAsset != null && cachedAsset.lastModified() == lastModified && cachedAsset.size() == size) { + this.cachedAssetFiles.put(file, cachedAsset); + } else { + cachedAsset = new CachedAssetFile(Files.readAllBytes(file), lastModified, size); + this.cachedAssetFiles.put(file, cachedAsset); + } + if (fs == null) return; + Path relative = sourceFolder.relativize(file); + updateConflictChecker(fs, conflictChecker, file, file, relative, cachedAsset.data()); + } + + private void processZipFile(Path zipFile, Path sourceFolder, @Nullable FileSystem fs, + Map> conflictChecker, Map previousFiles) throws IOException { + try (FileSystem zipFs = FileSystems.newFileSystem(zipFile)) { + long zipLastModified = Files.getLastModifiedTime(zipFile).toMillis(); + long zipSize = Files.size(zipFile); + Path zipRoot = zipFs.getPath("/"); + Files.walkFileTree(zipRoot, new SimpleFileVisitor<>() { + @Override + public @NotNull FileVisitResult visitFile(@NotNull Path entry, @NotNull BasicFileAttributes entryAttrs) throws IOException { + if (entryAttrs.isDirectory()) { + return FileVisitResult.CONTINUE; + } + if (Config.excludeFileExtensions().contains(FileUtils.getExtension(entry))) { + return FileVisitResult.CONTINUE; + } + Path entryPathInZip = zipRoot.relativize(entry); + Path sourcePath = Path.of(zipFile + "!" + entryPathInZip); + CachedAssetFile cachedAsset = previousFiles.get(sourcePath); + if (cachedAsset != null && cachedAsset.lastModified() == zipLastModified && cachedAsset.size() == zipSize) { + cachedAssetFiles.put(sourcePath, cachedAsset); + } else { + byte[] data = Files.readAllBytes(entry); + cachedAsset = new CachedAssetFile(data, zipLastModified, zipSize); + cachedAssetFiles.put(sourcePath, cachedAsset); + } + if (fs != null) { + updateConflictChecker(fs, conflictChecker, entry, sourcePath, entryPathInZip, cachedAsset.data()); + } + return FileVisitResult.CONTINUE; + } + }); + } + } + + private void updateConflictChecker(FileSystem fs, Map> conflictChecker, Path sourcePath, Path namedSourcePath, Path relative, byte[] data) throws IOException { + String relativePath = CharacterUtils.replaceBackslashWithSlash(relative.toString()); + Path targetPath = fs.getPath("resource_pack/" + relativePath); + List conflicts = conflictChecker.get(relativePath); + if (conflicts == null) { + Files.createDirectories(targetPath.getParent()); + Files.write(targetPath, data); + conflictChecker.put(relativePath, List.of(namedSourcePath)); + } else { + PathContext relativeCTX = PathContext.of(relative); + PathContext targetCTX = PathContext.of(targetPath); + PathContext sourceCTX = PathContext.of(sourcePath); + for (ResolutionConditional resolution : Config.resolutions()) { + if (resolution.matcher().test(relativeCTX)) { + resolution.resolution().run(targetCTX, sourceCTX); + return; + } + } + switch (conflicts.size()) { + case 1 -> conflictChecker.put(relativePath, List.of(conflicts.get(0), namedSourcePath)); + case 2 -> conflictChecker.put(relativePath, List.of(conflicts.get(0), conflicts.get(1), namedSourcePath)); + case 3 -> conflictChecker.put(relativePath, List.of(conflicts.get(0), conflicts.get(1), conflicts.get(2), namedSourcePath)); + case 4 -> conflictChecker.put(relativePath, List.of(conflicts.get(0), conflicts.get(1), conflicts.get(2), conflicts.get(3), namedSourcePath)); + default -> { + // just ignore it if it has many conflict files + } + } + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java b/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java index b684aa54f..496ce89e6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/LoadingSequence.java @@ -1,6 +1,8 @@ package net.momirealms.craftengine.core.pack; -public class LoadingSequence { +public final class LoadingSequence { + private LoadingSequence() {} + public static final int TEMPLATE = 0; public static final int GLOBAL_VAR = 10; public static final int LANG = 20; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/Pack.java b/core/src/main/java/net/momirealms/craftengine/core/pack/Pack.java index 3953b2076..331469c9e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/Pack.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/Pack.java @@ -12,7 +12,7 @@ import java.nio.file.Path; * This class provides access to the resource pack folder * and configuration folder within the specified directory. */ -public class Pack { +public final class Pack { private final Path folder; private final PackMeta meta; private final boolean enabled; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/ResourceLocation.java b/core/src/main/java/net/momirealms/craftengine/core/pack/ResourceLocation.java index 471e5ead5..4e580df18 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/ResourceLocation.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/ResourceLocation.java @@ -1,6 +1,6 @@ package net.momirealms.craftengine.core.pack; -public class ResourceLocation { +public final class ResourceLocation { public static boolean isValid(final String resourceLocation) { int index = resourceLocation.indexOf(":"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherContains.java b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherContains.java index 3c17607ca..6c9cb8076 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherContains.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherContains.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.pack.conflict.PathContext; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.condition.ConditionFactory; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.util.CharacterUtils; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -18,7 +19,7 @@ public class PathMatcherContains implements Condition { @Override public boolean test(PathContext path) { - String pathStr = path.path().toString().replace("\\", "/"); + String pathStr = CharacterUtils.replaceBackslashWithSlash(path.path().toString()); return pathStr.contains(this.path); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherExact.java b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherExact.java index bebd9c61a..b2f1e800f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherExact.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherExact.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.pack.conflict.PathContext; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.condition.ConditionFactory; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.util.CharacterUtils; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -18,7 +19,7 @@ public class PathMatcherExact implements Condition { @Override public boolean test(PathContext path) { - String pathStr = path.path().toString().replace("\\", "/"); + String pathStr = CharacterUtils.replaceBackslashWithSlash(path.path().toString()); return pathStr.equals(this.path); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherParentPrefix.java b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherParentPrefix.java index 212ff4c39..940e8cbe3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherParentPrefix.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherParentPrefix.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.pack.conflict.PathContext; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.condition.ConditionFactory; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.util.CharacterUtils; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -21,7 +22,7 @@ public class PathMatcherParentPrefix implements Condition { public boolean test(PathContext path) { Path parent = path.path().getParent(); if (parent == null) return false; - String pathStr = parent.toString().replace("\\", "/"); + String pathStr = CharacterUtils.replaceBackslashWithSlash(parent.toString()); return pathStr.startsWith(this.prefix); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherParentSuffix.java b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherParentSuffix.java index 77c061228..8110dbcf0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherParentSuffix.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathMatcherParentSuffix.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.pack.conflict.PathContext; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.condition.ConditionFactory; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.util.CharacterUtils; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -21,7 +22,7 @@ public class PathMatcherParentSuffix implements Condition { public boolean test(PathContext path) { Path parent = path.path().getParent(); if (parent == null) return false; - String pathStr = parent.toString().replace("\\", "/"); + String pathStr = CharacterUtils.replaceBackslashWithSlash(parent.toString()); return pathStr.endsWith(suffix); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathPatternMatcher.java b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathPatternMatcher.java index 6826a42aa..cf8c72a11 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathPatternMatcher.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/conflict/matcher/PathPatternMatcher.java @@ -4,6 +4,7 @@ import net.momirealms.craftengine.core.pack.conflict.PathContext; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.condition.ConditionFactory; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; +import net.momirealms.craftengine.core.util.CharacterUtils; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -23,7 +24,7 @@ public class PathPatternMatcher implements Condition { @Override public boolean test(PathContext path) { - String pathStr = path.path().toString().replace("\\", "/"); + String pathStr = CharacterUtils.replaceBackslashWithSlash(path.path().toString()); return this.pattern.matcher(pathStr).matches(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java index 2ae5335c1..26a20125b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/AlistHost.java @@ -10,8 +10,8 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.util.*; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java deleted file mode 100644 index 21f20a97d..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3Host.java +++ /dev/null @@ -1,235 +0,0 @@ -package net.momirealms.craftengine.core.pack.host.impl; - -import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; -import net.momirealms.craftengine.core.pack.host.ResourcePackHost; -import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; -import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; -import net.momirealms.craftengine.core.plugin.CraftEngine; -import net.momirealms.craftengine.core.plugin.locale.LocalizedException; -import net.momirealms.craftengine.core.util.HashUtils; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; -import net.momirealms.craftengine.core.util.ResourceConfigUtils; -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.auth.signer.AwsS3V4Signer; -import software.amazon.awssdk.core.async.AsyncRequestBody; -import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; -import software.amazon.awssdk.http.async.SdkAsyncHttpClient; -import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; -import software.amazon.awssdk.http.nio.netty.ProxyConfiguration; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.s3.S3AsyncClient; -import software.amazon.awssdk.services.s3.S3AsyncClientBuilder; -import software.amazon.awssdk.services.s3.model.HeadObjectRequest; -import software.amazon.awssdk.services.s3.model.NoSuchKeyException; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; -import software.amazon.awssdk.services.s3.presigner.S3Presigner; -import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; - -import java.net.URI; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.time.Duration; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; - -public class S3Host implements ResourcePackHost { - public static final Factory FACTORY = new Factory(); - private final S3AsyncClient s3AsyncClient; - private final S3Presigner preSigner; - private final String bucket; - private final String uploadPath; - private final String cdnDomain; - private final String cdnProtocol; - private final Duration validity; - - public S3Host( - S3AsyncClient s3AsyncClient, - S3Presigner preSigner, - String bucket, - String uploadPath, - String cdnDomain, - String cdnProtocol, - Duration validity - ) { - this.s3AsyncClient = s3AsyncClient; - this.preSigner = preSigner; - this.bucket = bucket; - this.uploadPath = uploadPath; - this.cdnDomain = cdnDomain; - this.cdnProtocol = cdnProtocol; - this.validity = validity; - } - - @Override - public boolean canUpload() { - return true; - } - - @Override - public Key type() { - return ResourcePackHosts.S3; - } - - @Override - public CompletableFuture> requestResourcePackDownloadLink(UUID player) { - return this.s3AsyncClient.headObject(HeadObjectRequest.builder() - .bucket(this.bucket) - .key(this.uploadPath) - .build()) - .handle((headResponse, exception) -> { - if (exception != null) { - Throwable cause = exception.getCause(); - if (cause instanceof NoSuchKeyException) { - CraftEngine.instance().logger().warn("[S3] Resource pack not found in bucket '" + this.bucket + "'. Path: " + this.uploadPath); - return Collections.emptyList(); - } else { - CraftEngine.instance().logger().warn( - "[S3] Failed to retrieve resource pack metadata. Reason: " + - cause.getClass().getSimpleName() + " - " + cause.getMessage() - ); - throw new CompletionException("Metadata request failed for path: " + this.uploadPath, cause); - } - } - String sha1 = headResponse.metadata().get("sha1"); - if (sha1 == null) { - CraftEngine.instance().logger().warn("[S3] Missing SHA-1 checksum in object metadata. Path: " + this.uploadPath); - throw new CompletionException(new IllegalStateException("Missing SHA-1 metadata for S3 object: " + this.uploadPath)); - } - GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() - .signatureDuration(this.validity) - .getObjectRequest(b -> b.bucket(this.bucket).key(this.uploadPath)) - .build(); - return Collections.singletonList( - ResourcePackDownloadData.of( - replaceWithCdnUrl(this.preSigner.presignGetObject(presignRequest).url()), - UUID.nameUUIDFromBytes(sha1.getBytes(StandardCharsets.UTF_8)), - sha1 - ) - ); - }); - } - - @Override - public CompletableFuture upload(Path resourcePackPath) { - String sha1 = HashUtils.calculateLocalFileSha1(resourcePackPath); - PutObjectRequest putObjectRequest = PutObjectRequest.builder() - .bucket(this.bucket) - .key(this.uploadPath) - .metadata(Map.of("sha1", sha1)) - .build(); - long uploadStart = System.currentTimeMillis(); - CraftEngine.instance().logger().info("[S3] Initiating resource pack upload to '" + this.uploadPath + "'"); - return this.s3AsyncClient.putObject(putObjectRequest, AsyncRequestBody.fromFile(resourcePackPath)) - .handle((response, exception) -> { - if (exception != null) { - Throwable cause = exception instanceof CompletionException ? - exception.getCause() : - exception; - CraftEngine.instance().logger().warn( - "[S3] Upload failed for path '" + this.uploadPath + "'. Error: " + - cause.getClass().getSimpleName() + " - " + cause.getMessage() - ); - throw new CompletionException("Failed to upload to S3 path: " + this.uploadPath, cause); - } - CraftEngine.instance().logger().info( - "[S3] Successfully uploaded resource pack to '" + this.uploadPath + "' in " + - (System.currentTimeMillis() - uploadStart) + " ms" - ); - return null; - }); - } - - private String replaceWithCdnUrl(URL originalUrl) { - if (this.cdnDomain == null) return originalUrl.toString(); - return this.cdnProtocol + "://" + this.cdnDomain - + originalUrl.getPath() - + (originalUrl.getQuery() != null ? "?" + originalUrl.getQuery() : ""); - } - - public static class Factory implements ResourcePackHostFactory { - - @Override - @SuppressWarnings("deprecation") - public ResourcePackHost create(Map arguments) { - boolean useEnv = (boolean) arguments.getOrDefault("use-environment-variables", false); - String endpoint = Optional.ofNullable(arguments.get("endpoint")).map(String::valueOf).orElse(null); - if (endpoint == null || endpoint.isEmpty()) { - throw new LocalizedException("warning.config.host.s3.missing_endpoint"); - } - String protocol = arguments.getOrDefault("protocol", "https").toString(); - boolean usePathStyle = (boolean) arguments.getOrDefault("path-style", false); - String bucket = Optional.ofNullable(arguments.get("bucket")).map(String::valueOf).orElse(null); - if (bucket == null || bucket.isEmpty()) { - throw new LocalizedException("warning.config.host.s3.missing_bucket"); - } - String region = arguments.getOrDefault("region", "auto").toString(); - String accessKeyId = useEnv ? System.getenv("CE_S3_ACCESS_KEY_ID") : Optional.ofNullable(arguments.get("access-key-id")).map(String::valueOf).orElse(null); - if (accessKeyId == null || accessKeyId.isEmpty()) { - throw new LocalizedException("warning.config.host.s3.missing_access_key"); - } - String accessKeySecret = useEnv ? System.getenv("CE_S3_ACCESS_KEY_SECRET") : Optional.ofNullable(arguments.get("access-key-secret")).map(String::valueOf).orElse(null); - if (accessKeySecret == null || accessKeySecret.isEmpty()) { - throw new LocalizedException("warning.config.host.s3.missing_secret"); - } - String uploadPath = arguments.getOrDefault("upload-path", "craftengine/resource_pack.zip").toString(); - if (uploadPath == null || uploadPath.isEmpty()) { - throw new LocalizedException("warning.config.host.s3.missing_upload_path"); - } - boolean useLegacySignature = (boolean) arguments.getOrDefault("use-legacy-signature", true); - Duration validity = Duration.ofSeconds((int) arguments.getOrDefault("validity", 10)); - - Map cdn = MiscUtils.castToMap(arguments.get("cdn"), true); - String cdnDomain = null; - String cdnProtocol = "https"; - if (cdn != null) { - cdnDomain = Optional.ofNullable(arguments.get("domain")).map(String::valueOf).orElse(null); - cdnProtocol = arguments.getOrDefault("protocol", "https").toString(); - } - - AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKeyId, accessKeySecret); - - S3AsyncClientBuilder s3AsyncClientBuilder = S3AsyncClient.builder() - .endpointOverride(URI.create(protocol + "://" + endpoint)) - .region(Region.of(region)) - .credentialsProvider(StaticCredentialsProvider.create(credentials)) - .serviceConfiguration(b -> b.pathStyleAccessEnabled(usePathStyle)); - - if (useLegacySignature) { - s3AsyncClientBuilder.overrideConfiguration(b -> b - .putAdvancedOption(SdkAdvancedClientOption.SIGNER, AwsS3V4Signer.create()) - ); - } - - Map proxySetting = MiscUtils.castToMap(arguments.get("proxy"), true); - if (proxySetting != null) { - String host = ResourceConfigUtils.requireNonEmptyStringOrThrow(proxySetting.get("host"), () -> new LocalizedException("warning.config.host.proxy.missing_host")); - int port = ResourceConfigUtils.getAsInt(proxySetting.get("port"), "port"); - if (port <= 0 || port > 65535) { - throw new LocalizedException("warning.config.host.proxy.missing_port"); - } - String scheme = ResourceConfigUtils.requireNonEmptyStringOrThrow(proxySetting.get("scheme"), () -> new LocalizedException("warning.config.host.proxy.missing_scheme")); - String username = Optional.ofNullable(proxySetting.get("username")).map(String::valueOf).orElse(null); - String password = Optional.ofNullable(proxySetting.get("password")).map(String::valueOf).orElse(null); - ProxyConfiguration.Builder builder = ProxyConfiguration.builder().host(host).port(port).scheme(scheme); - if (username != null) builder.username(username); - if (password != null) builder.password(password); - SdkAsyncHttpClient httpClient = NettyNioAsyncHttpClient.builder().proxyConfiguration(builder.build()).build(); - s3AsyncClientBuilder.httpClient(httpClient); - } - - S3AsyncClient s3AsyncClient = s3AsyncClientBuilder.build(); - - S3Presigner preSigner = S3Presigner.builder() - .endpointOverride(URI.create(protocol + "://" + endpoint)) - .region(Region.of(region)) - .credentialsProvider(StaticCredentialsProvider.create(credentials)) - .build(); - - return new S3Host(s3AsyncClient, preSigner, bucket, uploadPath, cdnDomain, cdnProtocol, validity); - } - } -} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3HostFactory.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3HostFactory.java index 5de2fa3b0..3f52ff790 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3HostFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/S3HostFactory.java @@ -15,7 +15,6 @@ public class S3HostFactory implements ResourcePackHostFactory { public ResourcePackHost create(Map arguments) { CraftEngine.instance().dependencyManager().loadDependencies( List.of( - Dependencies.NETTY_HTTP, Dependencies.NETTY_HTTP2, Dependencies.REACTIVE_STREAMS, Dependencies.AMAZON_AWSSDK_S3, diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index 785a00c6d..1706684c2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -2,15 +2,20 @@ package net.momirealms.craftengine.core.pack.host.impl; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpServer; +import com.github.benmanes.caffeine.cache.Scheduler; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.*; +import io.netty.util.CharsetUtil; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.plugin.CraftEngine; import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -18,11 +23,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.Map; import java.util.UUID; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; @@ -30,16 +31,15 @@ public class SelfHostHttpServer { private static SelfHostHttpServer instance; private final Cache oneTimePackUrls = Caffeine.newBuilder() .maximumSize(256) - .expireAfterAccess(1, TimeUnit.MINUTES) + .scheduler(Scheduler.systemScheduler()) + .expireAfterWrite(1, TimeUnit.MINUTES) .build(); private final Cache ipAccessCache = Caffeine.newBuilder() .maximumSize(256) - .expireAfterAccess(10, TimeUnit.MINUTES) + .scheduler(Scheduler.systemScheduler()) + .expireAfterWrite(10, TimeUnit.MINUTES) .build(); - private ExecutorService threadPool; - private HttpServer server; - private final AtomicLong totalRequests = new AtomicLong(); private final AtomicLong blockedRequests = new AtomicLong(); @@ -52,43 +52,13 @@ public class SelfHostHttpServer { private boolean denyNonMinecraft = true; private boolean useToken; - private volatile byte[] resourcePackBytes; + private byte[] resourcePackBytes; private String packHash; private UUID packUUID; - public void updateProperties(String ip, - int port, - String url, - boolean denyNonMinecraft, - String protocol, - int maxRequests, - int resetInternal, - boolean token) { - this.ip = ip; - this.url = url; - this.denyNonMinecraft = denyNonMinecraft; - this.protocol = protocol; - this.rateLimit = maxRequests; - this.rateLimitInterval = resetInternal; - this.useToken = token; - if (port <= 0 || port > 65535) { - throw new IllegalArgumentException("Invalid port number: " + port); - } - if (port == this.port && this.server != null) return; - if (this.server != null) disable(); - this.port = port; - try { - this.threadPool = Executors.newFixedThreadPool(1); - this.server = HttpServer.create(new InetSocketAddress("::", port), 0); - this.server.createContext("/download", new ResourcePackHandler()); - this.server.createContext("/metrics", this::handleMetrics); - this.server.setExecutor(this.threadPool); - this.server.start(); - CraftEngine.instance().logger().info("HTTP server started on port: " + port); - } catch (IOException e) { - CraftEngine.instance().logger().warn("Failed to start HTTP server", e); - } - } + private EventLoopGroup bossGroup; + private EventLoopGroup workerGroup; + private Channel serverChannel; public static SelfHostHttpServer instance() { if (instance == null) { @@ -97,21 +67,31 @@ public class SelfHostHttpServer { return instance; } - @Nullable - public ResourcePackDownloadData generateOneTimeUrl() { - if (this.resourcePackBytes == null) { - return null; + public void updateProperties(String ip, + int port, + String url, + boolean denyNonMinecraft, + String protocol, + int maxRequests, + int resetInterval, + boolean token) { + this.ip = ip; + this.url = url; + this.denyNonMinecraft = denyNonMinecraft; + this.protocol = protocol; + this.rateLimit = maxRequests; + this.rateLimitInterval = resetInterval; + this.useToken = token; + + if (port <= 0 || port > 65535) { + throw new IllegalArgumentException("Invalid port: " + port); } - if (!this.useToken) { - return new ResourcePackDownloadData(url() + "download", this.packUUID, this.packHash); - } - String token = UUID.randomUUID().toString(); - this.oneTimePackUrls.put(token, true); - return new ResourcePackDownloadData( - url() + "download?token=" + URLEncoder.encode(token, StandardCharsets.UTF_8), - this.packUUID, - this.packHash - ); + + if (this.port == port && serverChannel != null) return; + disable(); + + this.port = port; + initializeServer(); } public String url() { @@ -121,6 +101,189 @@ public class SelfHostHttpServer { return this.protocol + "://" + this.ip + ":" + this.port + "/"; } + private void initializeServer() { + bossGroup = new NioEventLoopGroup(1); + workerGroup = new NioEventLoopGroup(); + + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new HttpObjectAggregator(1048576)); + pipeline.addLast(new RequestHandler()); + } + }); + try { + serverChannel = b.bind(port).sync().channel(); + CraftEngine.instance().logger().info("Netty HTTP server started on port: " + port); + } catch (InterruptedException e) { + CraftEngine.instance().logger().warn("Failed to start Netty server", e); + Thread.currentThread().interrupt(); + } + } + + @ChannelHandler.Sharable + private class RequestHandler extends SimpleChannelInboundHandler { + @Override + protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) { + totalRequests.incrementAndGet(); + + try { + String clientIp = ((InetSocketAddress) ctx.channel().remoteAddress()) + .getAddress().getHostAddress(); + + if (checkRateLimit(clientIp)) { + sendError(ctx, HttpResponseStatus.TOO_MANY_REQUESTS, "Rate limit exceeded"); + blockedRequests.incrementAndGet(); + return; + } + + QueryStringDecoder queryDecoder = new QueryStringDecoder(request.uri()); + String path = queryDecoder.path(); + + if ("/download".equals(path)) { + handleDownload(ctx, request, queryDecoder); + } else if ("/metrics".equals(path)) { + handleMetrics(ctx); + } else { + sendError(ctx, HttpResponseStatus.NOT_FOUND, "Not Found"); + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Request handling failed", e); + sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, "Internal Error"); + } + } + + private void handleDownload(ChannelHandlerContext ctx, FullHttpRequest request, QueryStringDecoder queryDecoder) { + if (useToken) { + String token = queryDecoder.parameters().getOrDefault("token", java.util.Collections.emptyList()).stream().findFirst().orElse(null); + if (!validateToken(token)) { + sendError(ctx, HttpResponseStatus.FORBIDDEN, "Invalid token"); + blockedRequests.incrementAndGet(); + return; + } + } + + if (denyNonMinecraft) { + String userAgent = request.headers().get(HttpHeaderNames.USER_AGENT); + if (userAgent == null || !userAgent.startsWith("Minecraft Java/")) { + sendError(ctx, HttpResponseStatus.FORBIDDEN, "Invalid client"); + blockedRequests.incrementAndGet(); + return; + } + } + + if (resourcePackBytes == null) { + sendError(ctx, HttpResponseStatus.NOT_FOUND, "Resource pack missing"); + blockedRequests.incrementAndGet(); + return; + } + + FullHttpResponse response = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, + HttpResponseStatus.OK, + Unpooled.wrappedBuffer(resourcePackBytes) + ); + response.headers() + .set(HttpHeaderNames.CONTENT_TYPE, "application/zip") + .set(HttpHeaderNames.CONTENT_LENGTH, resourcePackBytes.length); + + ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); + } + + private void handleMetrics(ChannelHandlerContext ctx) { + String metrics = "# TYPE total_requests counter\n" + + "total_requests " + totalRequests.get() + "\n" + + "# TYPE blocked_requests counter\n" + + "blocked_requests " + blockedRequests.get(); + + FullHttpResponse response = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, + HttpResponseStatus.OK, + Unpooled.copiedBuffer(metrics, CharsetUtil.UTF_8) + ); + response.headers() + .set(HttpHeaderNames.CONTENT_TYPE, "text/plain") + .set(HttpHeaderNames.CONTENT_LENGTH, metrics.length()); + + ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); + } + + private boolean checkRateLimit(String clientIp) { + IpAccessRecord record = ipAccessCache.getIfPresent(clientIp); + long now = System.currentTimeMillis(); + + if (record == null) { + record = new IpAccessRecord(now, 1); + ipAccessCache.put(clientIp, record); + return false; + } + + if (now - record.lastAccessTime > rateLimitInterval) { + record.lastAccessTime = now; + record.accessCount = 1; + return false; + } + + return ++record.accessCount > rateLimit; + } + + private boolean validateToken(String token) { + if (token == null || token.length() != 36) return false; + Boolean valid = oneTimePackUrls.getIfPresent(token); + if (valid != null) { + oneTimePackUrls.invalidate(token); + return true; + } + return false; + } + + private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status, String message) { + FullHttpResponse response = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, + status, + Unpooled.copiedBuffer(message, CharsetUtil.UTF_8) + ); + ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + CraftEngine.instance().logger().warn("Channel error", cause); + ctx.close(); + } + } + + @Nullable + public ResourcePackDownloadData generateOneTimeUrl() { + if (this.resourcePackBytes == null) return null; + + if (!this.useToken) { + return new ResourcePackDownloadData(url() + "download", this.packUUID, this.packHash); + } + + String token = UUID.randomUUID().toString(); + oneTimePackUrls.put(token, true); + return new ResourcePackDownloadData( + url() + "download?token=" + URLEncoder.encode(token, StandardCharsets.UTF_8), + packUUID, + packHash + ); + } + + public void disable() { + if (serverChannel != null) { + serverChannel.close().awaitUninterruptibly(); + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + serverChannel = null; + } + } + public void readResourcePack(Path path) { try { if (Files.exists(path)) { @@ -151,142 +314,8 @@ public class SelfHostHttpServer { } } - private void handleMetrics(HttpExchange exchange) throws IOException { - String metrics = "# TYPE total_requests counter\n" - + "total_requests " + totalRequests.get() + "\n" - + "# TYPE blocked_requests counter\n" - + "blocked_requests " + blockedRequests.get(); - - exchange.getResponseHeaders().set("Content-Type", "text/plain"); - exchange.sendResponseHeaders(200, metrics.length()); - try (OutputStream os = exchange.getResponseBody()) { - os.write(metrics.getBytes(StandardCharsets.UTF_8)); - } - } - - public void disable() { - if (this.server != null) { - this.server.stop(0); - this.server = null; - if (this.threadPool != null) { - this.threadPool.shutdownNow(); - } - } - } - - private class ResourcePackHandler implements HttpHandler { - @Override - public void handle(HttpExchange exchange) throws IOException { - totalRequests.incrementAndGet(); - - String clientIp = getClientIp(exchange); - if (checkRateLimit(clientIp)) { - handleBlockedRequest(exchange, 429, "Rate limit exceeded"); - return; - } - if (useToken) { - String token = parseToken(exchange); - if (!validateToken(token)) { - handleBlockedRequest(exchange, 403, "Invalid token"); - return; - } - } - if (!validateClient(exchange)) { - handleBlockedRequest(exchange, 403, "Invalid client"); - return; - } - if (resourcePackBytes == null) { - handleBlockedRequest(exchange, 404, "Resource pack missing"); - return; - } - sendResourcePack(exchange); - } - - private String getClientIp(HttpExchange exchange) { - return exchange.getRemoteAddress().getAddress().getHostAddress(); - } - - private boolean checkRateLimit(String clientIp) { - IpAccessRecord record = ipAccessCache.getIfPresent(clientIp); - long now = System.currentTimeMillis(); - if (record == null) { - record = new IpAccessRecord(now, 1); - ipAccessCache.put(clientIp, record); - } else { - if (now - record.lastAccessTime > rateLimitInterval) { - record = new IpAccessRecord(now, 1); - ipAccessCache.put(clientIp, record); - } else { - record.accessCount++; - } - } - return record.accessCount > rateLimit; - } - - private String parseToken(HttpExchange exchange) { - Map params = parseQuery(exchange.getRequestURI().getQuery()); - return params.get("token"); - } - - private boolean validateToken(String token) { - if (token == null || token.length() != 36) return false; - - Boolean valid = oneTimePackUrls.getIfPresent(token); - if (valid != null) { - oneTimePackUrls.invalidate(token); - return true; - } - return false; - } - - private boolean validateClient(HttpExchange exchange) { - if (!denyNonMinecraft) return true; - - String userAgent = exchange.getRequestHeaders().getFirst("User-Agent"); - return userAgent != null && userAgent.startsWith("Minecraft Java/"); - } - - private void sendResourcePack(HttpExchange exchange) throws IOException { - exchange.getResponseHeaders().set("Content-Type", "application/zip"); - exchange.getResponseHeaders().set("Content-Length", String.valueOf(resourcePackBytes.length)); - exchange.sendResponseHeaders(200, resourcePackBytes.length); - - try (OutputStream os = exchange.getResponseBody()) { - os.write(resourcePackBytes); - } catch (IOException e) { - CraftEngine.instance().logger().warn("Failed to send resource pack", e); - throw e; - } - } - - private void handleBlockedRequest(HttpExchange exchange, int code, String reason) throws IOException { - blockedRequests.incrementAndGet(); - CraftEngine.instance().debug(() -> - String.format("Blocked request [%s] %s: %s", - code, - exchange.getRemoteAddress(), - reason) - ); - exchange.sendResponseHeaders(code, -1); - exchange.close(); - } - - private Map parseQuery(String query) { - Map params = new HashMap<>(); - if (query == null) return params; - - for (String pair : query.split("&")) { - int idx = pair.indexOf("="); - String key = idx > 0 ? pair.substring(0, idx) : pair; - String value = idx > 0 ? pair.substring(idx + 1) : ""; - params.put(key, value); - } - return params; - } - } - private static class IpAccessRecord { - final long lastAccessTime; + long lastAccessTime; int accessCount; IpAccessRecord(long lastAccessTime, int accessCount) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyItemModel.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyItemModel.java new file mode 100644 index 000000000..925b2bb87 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyItemModel.java @@ -0,0 +1,71 @@ +package net.momirealms.craftengine.core.pack.model; + +import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LegacyItemModel { + private final List modelsToGenerate; + private final String path; + private final List overrides; + + public LegacyItemModel(String path, List overrides, List modelsToGenerate) { + this.modelsToGenerate = modelsToGenerate; + this.path = path; + this.overrides = overrides; + } + + public List modelsToGenerate() { + return modelsToGenerate; + } + + public List overrides() { + return overrides; + } + + public String path() { + return path; + } + + @SuppressWarnings("unchecked") + public static LegacyItemModel fromMap(Map legacyModel, int customModelData) { + String legacyModelPath = ResourceConfigUtils.requireNonEmptyStringOrThrow(legacyModel.get("path"), "warning.config.item.legacy_model.missing_path"); + Map generation = MiscUtils.castToMap(legacyModel.get("generation"), true); + ModelGeneration baseModelGeneration = null; + if (generation != null) { + baseModelGeneration = ModelGeneration.of(Key.of(legacyModelPath), generation); + } + List> overrides = (List>) legacyModel.get("overrides"); + if (overrides != null) { + List modelGenerations = new ArrayList<>(); + List legacyOverridesModels = new ArrayList<>(); + if (baseModelGeneration != null) modelGenerations.add(baseModelGeneration); + legacyOverridesModels.add(new LegacyOverridesModel(new HashMap<>(), legacyModelPath, customModelData)); + for (Map override : overrides) { + String overrideModelPath = ResourceConfigUtils.requireNonEmptyStringOrThrow(override.get("path"), () -> new LocalizedResourceConfigException("warning.config.item.legacy_model.overrides.missing_path")); + Map predicate = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(override.get("predicate"), "warning.config.item.legacy_model.overrides.missing_predicate"), false); + if (predicate.isEmpty()) { + throw new LocalizedResourceConfigException("warning.config.item.legacy_model.overrides.missing_predicate"); + } + Map overrideGeneration = MiscUtils.castToMap(override.get("generation"), true); + if (overrideGeneration != null) { + modelGenerations.add(ModelGeneration.of(Key.of(overrideModelPath), overrideGeneration)); + } + legacyOverridesModels.add(new LegacyOverridesModel(predicate, overrideModelPath, customModelData)); + } + return new LegacyItemModel(legacyModelPath, legacyOverridesModels, modelGenerations); + } else { + return new LegacyItemModel(legacyModelPath, + List.of(new LegacyOverridesModel(new HashMap<>(), legacyModelPath, customModelData)), + baseModelGeneration == null ? List.of() : List.of(baseModelGeneration) + ); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyOverridesModel.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyOverridesModel.java index 07d6d1e7f..6d63b08f2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyOverridesModel.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/LegacyOverridesModel.java @@ -2,7 +2,9 @@ package net.momirealms.craftengine.core.pack.model; import com.google.gson.JsonObject; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -11,8 +13,8 @@ public class LegacyOverridesModel implements Comparable { private final String model; private final int customModelData; - public LegacyOverridesModel(Map predicate, String model, int customModelData) { - this.predicate = predicate; + public LegacyOverridesModel(@Nullable Map predicate, @NotNull String model, int customModelData) { + this.predicate = predicate == null ? new HashMap<>() : predicate; this.model = model; this.customModelData = customModelData; if (customModelData > 0) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/model/condition/HasComponentConditionProperty.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/condition/HasComponentConditionProperty.java index 7da522b40..660d7a264 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/model/condition/HasComponentConditionProperty.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/condition/HasComponentConditionProperty.java @@ -16,6 +16,14 @@ public class HasComponentConditionProperty implements ConditionProperty { this.ignoreDefault = ignoreDefault; } + public String component() { + return component; + } + + public boolean ignoreDefault() { + return ignoreDefault; + } + @Override public Key type() { return ConditionProperties.HAS_COMPONENT; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 53f8b5da5..5996289c3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.pack.PackManager; import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; import net.momirealms.craftengine.core.plugin.command.sender.SenderFactory; +import net.momirealms.craftengine.core.plugin.compatibility.CompatibilityManager; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.template.TemplateManager; import net.momirealms.craftengine.core.plugin.config.template.TemplateManagerImpl; @@ -221,7 +222,6 @@ public abstract class CraftEngine implements Plugin { this.fontManager.delayedInit(); this.vanillaLootManager.delayedInit(); this.advancementManager.delayedInit(); - this.projectileManager.delayedInit(); // reload the plugin try { this.reloadPlugin(Runnable::run, Runnable::run, true); @@ -229,6 +229,7 @@ public abstract class CraftEngine implements Plugin { this.logger.warn("Failed to reload plugin on enable stage", e); } // must be after reloading because this process loads furniture + this.projectileManager.delayedInit(); this.worldManager.delayedInit(); this.furnitureManager.delayedInit(); // set up some platform extra tasks @@ -306,11 +307,15 @@ public abstract class CraftEngine implements Plugin { Dependencies.BYTE_BUDDY, Dependencies.SNAKE_YAML, Dependencies.BOOSTED_YAML, + Dependencies.OPTION, + Dependencies.EXAMINATION_API, Dependencies.EXAMINATION_STRING, + Dependencies.ADVENTURE_KEY, Dependencies.ADVENTURE_API, Dependencies.ADVENTURE_NBT, Dependencies.MINIMESSAGE, - Dependencies.TEXT_SERIALIZER_GSON, Dependencies.TEXT_SERIALIZER_GSON_LEGACY, Dependencies.TEXT_SERIALIZER_JSON, + Dependencies.TEXT_SERIALIZER_COMMONS, Dependencies.TEXT_SERIALIZER_LEGACY, Dependencies.TEXT_SERIALIZER_GSON, Dependencies.TEXT_SERIALIZER_GSON_LEGACY, Dependencies.TEXT_SERIALIZER_JSON, Dependencies.AHO_CORASICK, Dependencies.LZ4, Dependencies.EVALEX, + Dependencies.NETTY_HTTP, Dependencies.JIMFS, Dependencies.COMMONS_IMAGING ); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java index cbb4feb74..ea2888a6d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/Plugin.java @@ -11,6 +11,7 @@ import net.momirealms.craftengine.core.loot.VanillaLootManager; import net.momirealms.craftengine.core.pack.PackManager; import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender; import net.momirealms.craftengine.core.plugin.command.sender.SenderFactory; +import net.momirealms.craftengine.core.plugin.compatibility.CompatibilityManager; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.template.TemplateManager; import net.momirealms.craftengine.core.plugin.context.GlobalVariableManager; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/command/sender/SenderFactory.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/command/sender/SenderFactory.java index a9d6f4eb8..2d89bdd97 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/command/sender/SenderFactory.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/command/sender/SenderFactory.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.plugin.command.sender; -import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.util.Tristate; @@ -25,8 +24,6 @@ public abstract class SenderFactory

{ protected abstract String name(T sender); - protected abstract Audience audience(T sender); - protected abstract void sendMessage(T sender, Component message); protected abstract Tristate permissionState(T sender, String node); @@ -44,7 +41,7 @@ public abstract class SenderFactory

{ } public Sender console() { - return console; + return this.console; } public Sender wrap(C sender) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CompatibilityManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/CompatibilityManager.java similarity index 55% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/CompatibilityManager.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/CompatibilityManager.java index 7749be015..83b15f39b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CompatibilityManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/CompatibilityManager.java @@ -1,6 +1,6 @@ -package net.momirealms.craftengine.core.plugin; +package net.momirealms.craftengine.core.plugin.compatibility; -import net.momirealms.craftengine.core.entity.furniture.AbstractExternalModel; +import net.momirealms.craftengine.core.entity.furniture.ExternalModel; import net.momirealms.craftengine.core.entity.player.Player; import java.util.UUID; @@ -13,9 +13,13 @@ public interface CompatibilityManager { void onDelayedEnable(); - AbstractExternalModel createModelEngineModel(String id); + void registerLevelerProvider(String plugin, LevelerProvider provider); - AbstractExternalModel createBetterModelModel(String id); + void addLevelerExp(Player player, String plugin, String target, double value); + + int getLevel(Player player, String plugin, String target); + + ExternalModel createModel(String plugin, String id); int interactionToBaseEntity(int id); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/LevelerProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/LevelerProvider.java new file mode 100644 index 000000000..228115d40 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/LevelerProvider.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.core.plugin.compatibility; + +import net.momirealms.craftengine.core.entity.player.Player; + +public interface LevelerProvider { + + void addExp(Player player, String target, double amount); + + int getLevel(Player player, String target); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/ModelProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/ModelProvider.java new file mode 100644 index 000000000..f636bf857 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/compatibility/ModelProvider.java @@ -0,0 +1,8 @@ +package net.momirealms.craftengine.core.plugin.compatibility; + +import net.momirealms.craftengine.core.entity.furniture.ExternalModel; + +public interface ModelProvider { + + ExternalModel createModel(String id); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index d6faf0675..e33d35e57 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -34,10 +34,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; public class Config { @@ -59,6 +56,7 @@ public class Config { protected List resource_pack$duplicated_files_handler; protected List resource_pack$merge_external_folders; protected List resource_pack$merge_external_zips; + protected Set resource_pack$exclude_file_extensions; protected boolean resource_pack$protection$crash_tools$method_1; protected boolean resource_pack$protection$crash_tools$method_2; @@ -142,6 +140,7 @@ public class Config { protected boolean image$intercept_packets$armor_stand; protected boolean image$intercept_packets$player_info; protected boolean image$intercept_packets$set_score; + protected boolean image$intercept_packets$item; protected boolean emoji$chat; protected boolean emoji$book; @@ -220,6 +219,7 @@ public class Config { resource_pack$supported_version$max = getVersion(config.get("resource-pack.supported-version.max", "LATEST").toString()); resource_pack$merge_external_folders = config.getStringList("resource-pack.merge-external-folders"); resource_pack$merge_external_zips = config.getStringList("resource-pack.merge-external-zip-files"); + resource_pack$exclude_file_extensions = new HashSet<>(config.getStringList("resource-pack.exclude-file-extensions")); resource_pack$delivery$send_on_join = config.getBoolean("resource-pack.delivery.send-on-join", true); resource_pack$delivery$resend_on_upload = config.getBoolean("resource-pack.delivery.resend-on-upload", true); resource_pack$delivery$kick_if_declined = config.getBoolean("resource-pack.delivery.kick-if-declined", true); @@ -333,6 +333,7 @@ public class Config { image$intercept_packets$armor_stand = config.getBoolean("image.intercept-packets.armor-stand", true); image$intercept_packets$player_info = config.getBoolean("image.intercept-packets.player-info", true); image$intercept_packets$set_score = config.getBoolean("image.intercept-packets.set-score", true); + image$intercept_packets$item = config.getBoolean("image.intercept-packets.item", true); // emoji emoji$chat = config.getBoolean("emoji.chat", true); @@ -405,9 +406,9 @@ public class Config { return instance.furniture$handle_invalid_furniture_on_chunk_load$mapping; } - public static boolean forceUpdateLight() { - return instance.light_system$force_update_light; - } +// public static boolean forceUpdateLight() { +// return instance.light_system$force_update_light; +// } public static boolean enableLightSystem() { return instance.light_system$enable; @@ -465,6 +466,14 @@ public class Config { return instance.resource_pack$merge_external_folders; } + public static List zipsToMerge() { + return instance.resource_pack$merge_external_zips; + } + + public static Set excludeFileExtensions() { + return instance.resource_pack$exclude_file_extensions; + } + public static boolean kickOnDeclined() { return instance.resource_pack$delivery$kick_if_declined; } @@ -669,6 +678,10 @@ public class Config { return instance.image$intercept_packets$set_score; } + public static boolean interceptItem() { + return instance.image$intercept_packets$item; + } + public static boolean predictBreaking() { return instance.block$predict_breaking; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/AbstractChainParameterContext.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/AbstractChainParameterContext.java index 9eed4f51b..f85e82333 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/AbstractChainParameterContext.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/AbstractChainParameterContext.java @@ -20,7 +20,6 @@ public abstract class AbstractChainParameterContext extends AbstractCommonContex CHAIN_PARAMETERS.put(DirectContextParameters.MAIN_HAND_ITEM, itemProvider); CHAIN_PARAMETERS.put(DirectContextParameters.OFF_HAND_ITEM, itemProvider); CHAIN_PARAMETERS.put(DirectContextParameters.FURNITURE_ITEM, itemProvider); - CHAIN_PARAMETERS.put(DirectContextParameters.CONSUMED_ITEM, itemProvider); CHAIN_PARAMETERS.put(DirectContextParameters.ITEM_IN_HAND, itemProvider); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java index 037b53e39..5e54f2ea8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java @@ -1,8 +1,8 @@ package net.momirealms.craftengine.core.plugin.context; import com.google.common.collect.ImmutableMap; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; import java.util.NoSuchElementException; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/CooldownData.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/CooldownData.java new file mode 100644 index 000000000..235888530 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/CooldownData.java @@ -0,0 +1,82 @@ +package net.momirealms.craftengine.core.plugin.context; + +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.LongTag; +import net.momirealms.sparrow.nbt.NBT; +import net.momirealms.sparrow.nbt.Tag; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class CooldownData { + public static final Key COOLDOWN_KEY = Key.of("craftengine:cooldown"); + private final Map cooldownMap = Collections.synchronizedMap(new HashMap<>()); + + public boolean isOnCooldown(String key) { + long currentTime = System.currentTimeMillis(); + if (this.cooldownMap.containsKey(key)) { + long expirationTime = this.cooldownMap.get(key); + return currentTime < expirationTime; + } + return false; + } + + public void setCooldown(String key, long duration) { + this.cooldownMap.put(key, System.currentTimeMillis() + duration); + } + + public void addCooldown(String key, long duration) { + if (this.cooldownMap.containsKey(key)) { + this.cooldownMap.put(key, this.cooldownMap.get(key) + duration); + } else { + setCooldown(key, duration); + } + } + + public void removeCooldown(String key) { + this.cooldownMap.remove(key); + } + + public void clearCooldowns() { + this.cooldownMap.clear(); + } + + public static byte[] toBytes(CooldownData data) throws IOException { + CompoundTag tag = new CompoundTag(); + long currentTime = System.currentTimeMillis(); + for (Map.Entry entry : data.cooldownMap.entrySet()) { + if (currentTime < entry.getValue()) { + tag.putLong(entry.getKey(), entry.getValue()); + } + } + return NBT.toBytes(tag); + } + + public static CooldownData fromBytes(byte[] data) throws IOException { + if (data == null || data.length == 0) return new CooldownData(); + CooldownData cd = new CooldownData(); + long currentTime = System.currentTimeMillis(); + CompoundTag tag = NBT.fromBytes(data); + if (tag != null) { + for (Map.Entry entry : tag.tags.entrySet()) { + if (entry.getValue() instanceof LongTag longTag) { + long expire = longTag.getAsLong(); + if (currentTime < expire) { + cd.cooldownMap.put(entry.getKey(), expire); + } + } + } + } + return cd; + } + + @Override + public String toString() { + return "CooldownData{" + + "cooldownMap=" + cooldownMap + + '}'; + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java index 600d204c8..a5521169c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/CommonConditions.java @@ -18,6 +18,12 @@ public final class CommonConditions { public static final Key FALLING_BLOCK = Key.from("craftengine:falling_block"); public static final Key DISTANCE = Key.from("craftengine:distance"); public static final Key PERMISSION = Key.from("craftengine:permission"); + public static final Key ON_COOLDOWN = Key.from("craftengine:on_cooldown"); public static final Key EQUALS = Key.from("craftengine:equals"); + public static final Key STRING_EQUALS = Key.from("craftengine:string_equals"); + public static final Key STRING_CONTAINS = Key.from("craftengine:string_contains"); + public static final Key STRING_REGEX = Key.from("craftengine:regex"); public static final Key EXPRESSION = Key.from("craftengine:expression"); + public static final Key IS_NULL = Key.from("craftengine:is_null"); + public static final Key HAND = Key.from("craftengine:hand"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/HandCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/HandCondition.java new file mode 100644 index 000000000..8cc171076 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/HandCondition.java @@ -0,0 +1,49 @@ +package net.momirealms.craftengine.core.plugin.context.condition; + +import net.momirealms.craftengine.core.entity.player.InteractionHand; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public class HandCondition implements Condition { + private final InteractionHand hand; + + public HandCondition(InteractionHand hand) { + this.hand = hand; + } + + @Override + public Key type() { + return CommonConditions.HAND; + } + + @Override + public boolean test(CTX ctx) { + Optional optional = ctx.getOptionalParameter(DirectContextParameters.HAND); + if (optional.isPresent()) { + InteractionHand hand = optional.get(); + return hand.equals(this.hand); + } + return false; + } + + public static class FactoryImpl implements ConditionFactory { + + @Override + public Condition create(Map arguments) { + String hand = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("hand"), "warning.config.condition.hand.missing_hand"); + try { + return new HandCondition<>(InteractionHand.valueOf(hand.toUpperCase(Locale.ENGLISH))); + } catch (IllegalArgumentException e) { + throw new LocalizedResourceConfigException("warning.config.condition.hand.invalid_hand", hand); + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/IsNullCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/IsNullCondition.java new file mode 100644 index 000000000..010221218 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/IsNullCondition.java @@ -0,0 +1,38 @@ +package net.momirealms.craftengine.core.plugin.context.condition; + +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.ContextKey; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; +import java.util.Optional; + +public class IsNullCondition implements Condition { + private final ContextKey key; + + public IsNullCondition(ContextKey key) { + this.key = key; + } + + @Override + public Key type() { + return CommonConditions.IS_NULL; + } + + @Override + public boolean test(CTX ctx) { + Optional optional = ctx.getOptionalParameter(this.key); + return optional.isEmpty(); + } + + public static class FactoryImpl implements ConditionFactory { + + @Override + public Condition create(Map arguments) { + String argument = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("argument"), "warning.config.condition.is_null.missing_argument"); + return new IsNullCondition<>(ContextKey.chain(argument)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockPropertyCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockPropertyCondition.java index 44ae3625b..b8a213d27 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockPropertyCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockPropertyCondition.java @@ -29,7 +29,7 @@ public class MatchBlockPropertyCondition implements Conditi @Override public boolean test(CTX ctx) { - return ctx.getOptionalParameter(DirectContextParameters.BLOCK_STATE).map(state -> { + return ctx.getOptionalParameter(DirectContextParameters.CUSTOM_BLOCK_STATE).map(state -> { CustomBlock block = state.owner().value(); for (Pair property : this.properties) { Property propertyIns = block.getProperty(property.left()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/OnCooldownCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/OnCooldownCondition.java new file mode 100644 index 000000000..fd47802b5 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/OnCooldownCondition.java @@ -0,0 +1,43 @@ +package net.momirealms.craftengine.core.plugin.context.condition; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; +import java.util.Optional; + +public class OnCooldownCondition implements Condition { + private final String key; + + public OnCooldownCondition(String key) { + this.key = key; + } + + @Override + public Key type() { + return CommonConditions.ON_COOLDOWN; + } + + @Override + public boolean test(CTX ctx) { + Optional player = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + if (player.isPresent()) { + Player p = player.get(); + return p.cooldown().isOnCooldown(this.key); + } + return false; + } + + public static class FactoryImpl implements ConditionFactory { + + @Override + public Condition create(Map arguments) { + String id = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("id"), "warning.config.condition.on_cooldown.missing_id"); + return new OnCooldownCondition<>(id); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/StringContainsCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/StringContainsCondition.java new file mode 100644 index 000000000..702bc54d9 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/StringContainsCondition.java @@ -0,0 +1,40 @@ +package net.momirealms.craftengine.core.plugin.context.condition; + +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.text.TextProvider; +import net.momirealms.craftengine.core.plugin.context.text.TextProviders; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; + +public class StringContainsCondition implements Condition { + private final TextProvider value1; + private final TextProvider value2; + + public StringContainsCondition(TextProvider value1, TextProvider value2) { + this.value1 = value1; + this.value2 = value2; + } + + @Override + public Key type() { + return CommonConditions.STRING_CONTAINS; + } + + @Override + public boolean test(CTX ctx) { + return this.value1.get(ctx).contains(this.value2.get(ctx)); + } + + public static class FactoryImpl implements ConditionFactory { + + @Override + public Condition create(Map arguments) { + String value1 = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("value1"), "warning.config.condition.string_contains.missing_value1"); + String value2 = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("value2"), "warning.config.condition.string_contains.missing_value2"); + return new StringContainsCondition<>(TextProviders.fromString(value1), TextProviders.fromString(value2)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/EqualsCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/StringEqualsCondition.java similarity index 75% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/EqualsCondition.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/StringEqualsCondition.java index bc48c0a1a..d00160f58 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/EqualsCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/StringEqualsCondition.java @@ -9,11 +9,11 @@ import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.util.Map; -public class EqualsCondition implements Condition { +public class StringEqualsCondition implements Condition { private final TextProvider value1; private final TextProvider value2; - public EqualsCondition(TextProvider value1, TextProvider value2) { + public StringEqualsCondition(TextProvider value1, TextProvider value2) { this.value1 = value1; this.value2 = value2; } @@ -32,9 +32,9 @@ public class EqualsCondition implements Condition { @Override public Condition create(Map arguments) { - String value1 = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("value1"), "warning.config.condition.equals.missing_value1"); - String value2 = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("value1"), "warning.config.condition.equals.missing_value2"); - return new EqualsCondition<>(TextProviders.fromString(value1), TextProviders.fromString(value2)); + String value1 = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("value1"), "warning.config.condition.string_equals.missing_value1"); + String value2 = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("value2"), "warning.config.condition.string_equals.missing_value2"); + return new StringEqualsCondition<>(TextProviders.fromString(value1), TextProviders.fromString(value2)); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/StringRegexCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/StringRegexCondition.java new file mode 100644 index 000000000..122e14bf2 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/StringRegexCondition.java @@ -0,0 +1,40 @@ +package net.momirealms.craftengine.core.plugin.context.condition; + +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.text.TextProvider; +import net.momirealms.craftengine.core.plugin.context.text.TextProviders; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; + +public class StringRegexCondition implements Condition { + private final TextProvider value; + private final TextProvider regex; + + public StringRegexCondition(TextProvider value, TextProvider regex) { + this.value = value; + this.regex = regex; + } + + @Override + public Key type() { + return CommonConditions.STRING_REGEX; + } + + @Override + public boolean test(CTX ctx) { + return this.value.get(ctx).matches(this.regex.get(ctx)); + } + + public static class FactoryImpl implements ConditionFactory { + + @Override + public Condition create(Map arguments) { + String value = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("value"), "warning.config.condition.string_regex.missing_value"); + String regex = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("regex"), "warning.config.condition.string_regex.missing_regex"); + return new StringRegexCondition<>(TextProviders.fromString(value), TextProviders.fromString(regex)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/event/EventConditions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java similarity index 83% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/event/EventConditions.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java index b1c578046..5e6a110fa 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/event/EventConditions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventConditions.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.plugin.event; +package net.momirealms.craftengine.core.plugin.context.event; import net.momirealms.craftengine.core.plugin.context.Condition; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; @@ -29,8 +29,14 @@ public class EventConditions { register(CommonConditions.RANDOM, new RandomCondition.FactoryImpl<>()); register(CommonConditions.DISTANCE, new DistanceCondition.FactoryImpl<>()); register(CommonConditions.PERMISSION, new PermissionCondition.FactoryImpl<>()); - register(CommonConditions.EQUALS, new EqualsCondition.FactoryImpl<>()); + register(CommonConditions.EQUALS, new StringEqualsCondition.FactoryImpl<>()); + register(CommonConditions.STRING_REGEX, new StringRegexCondition.FactoryImpl<>()); + register(CommonConditions.STRING_EQUALS, new StringEqualsCondition.FactoryImpl<>()); + register(CommonConditions.STRING_CONTAINS, new StringContainsCondition.FactoryImpl<>()); register(CommonConditions.EXPRESSION, new ExpressionCondition.FactoryImpl<>()); + register(CommonConditions.IS_NULL, new IsNullCondition.FactoryImpl<>()); + register(CommonConditions.HAND, new HandCondition.FactoryImpl<>()); + register(CommonConditions.ON_COOLDOWN, new OnCooldownCondition.FactoryImpl<>()); } public static void register(Key key, ConditionFactory factory) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/event/EventFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java similarity index 58% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/event/EventFunctions.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java index 91d28760f..8613eb5c4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/event/EventFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventFunctions.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.plugin.event; +package net.momirealms.craftengine.core.plugin.context.event; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.function.*; @@ -12,10 +12,7 @@ import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import net.momirealms.craftengine.core.util.ResourceKey; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class EventFunctions { @@ -24,7 +21,24 @@ public class EventFunctions { register(CommonFunctions.MESSAGE, new MessageFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.ACTIONBAR, new ActionBarFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.TITLE, new TitleFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.OPEN_WINDOW, new OpenWindowFunction.FactoryImpl<>(EventConditions::fromMap)); register(CommonFunctions.CANCEL_EVENT, new CancelEventFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.RUN, new RunFunction.FactoryImpl<>(EventFunctions::fromMap, EventConditions::fromMap)); + register(CommonFunctions.PLACE_BLOCK, new PlaceBlockFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.BREAK_BLOCK, new BreakBlockFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.UPDATE_INTERACTION_TICK, new UpdateInteractionFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.SET_COUNT, new SetCountFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.DROP_LOOT, new DropLootFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.SWING_HAND, new SwingHandFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.SET_FOOD, new SetFoodFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.SET_SATURATION, new SetSaturationFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.PLAY_SOUND, new PlaySoundFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.PARTICLE, new ParticleFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.POTION_EFFECT, new PotionEffectFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.REMOVE_POTION_EFFECT, new RemovePotionEffectFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.LEVELER_EXP, new LevelerExpFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.SET_COOLDOWN, new SetCooldownFunction.FactoryImpl<>(EventConditions::fromMap)); + register(CommonFunctions.REMOVE_COOLDOWN, new RemoveCooldownFunction.FactoryImpl<>(EventConditions::fromMap)); } public static void register(Key key, FunctionFactory factory) { @@ -43,31 +57,15 @@ public class EventFunctions { return factory.create(map); } - public static EnumMap>> parseEvents(Object eventsObj) { + public static Map>> parseEvents(Object eventsObj) { + if (eventsObj == null) return Map.of(); EnumMap>> events = new EnumMap<>(EventTrigger.class); if (eventsObj instanceof Map eventsSection) { Map eventsSectionMap = MiscUtils.castToMap(eventsSection, false); for (Map.Entry eventEntry : eventsSectionMap.entrySet()) { try { EventTrigger eventTrigger = EventTrigger.byName(eventEntry.getKey()); - if (eventEntry.getValue() instanceof List list) { - if (list.size() == 1) { - events.put(eventTrigger, List.of(EventFunctions.fromMap(MiscUtils.castToMap(list.get(0), false)))); - } else if (list.size() == 2) { - events.put(eventTrigger, List.of( - EventFunctions.fromMap(MiscUtils.castToMap(list.get(0), false)), - EventFunctions.fromMap(MiscUtils.castToMap(list.get(1), false)) - )); - } else { - List> eventsList = new ArrayList<>(); - for (Object event : list) { - eventsList.add(EventFunctions.fromMap(MiscUtils.castToMap(event, false))); - } - events.put(eventTrigger, eventsList); - } - } else if (eventEntry.getValue() instanceof Map eventSection) { - events.put(eventTrigger, List.of(EventFunctions.fromMap(MiscUtils.castToMap(eventSection, false)))); - } + events.put(eventTrigger, ResourceConfigUtils.parseConfigAsList(eventEntry.getValue(), EventFunctions::fromMap)); } catch (IllegalArgumentException e) { throw new LocalizedResourceConfigException("warning.config.event.invalid_trigger", eventEntry.getKey()); } @@ -76,24 +74,17 @@ public class EventFunctions { @SuppressWarnings("unchecked") List> eventsList = (List>) list; for (Map eventSection : eventsList) { - Object onObj = eventSection.get("on"); - if (onObj == null) { - throw new LocalizedResourceConfigException("warning.config.event.missing_trigger"); - } + String on = ResourceConfigUtils.requireNonEmptyStringOrThrow(eventSection.get("on"), "warning.config.event.missing_trigger"); try { - EventTrigger eventTrigger = EventTrigger.byName(onObj.toString()); + EventTrigger eventTrigger = EventTrigger.byName(on); if (eventSection.containsKey("type")) { Function function = EventFunctions.fromMap(eventSection); events.computeIfAbsent(eventTrigger, k -> new ArrayList<>(4)).add(function); } else if (eventSection.containsKey("functions")) { - @SuppressWarnings("unchecked") - List> functionList = (List>) eventSection.get("functions"); - for (Map function : functionList) { - events.computeIfAbsent(eventTrigger, k -> new ArrayList<>(4)).add(EventFunctions.fromMap(function)); - } + events.computeIfAbsent(eventTrigger, k -> new ArrayList<>(4)).add(Objects.requireNonNull(BuiltInRegistries.EVENT_FUNCTION_FACTORY.getValue(CommonFunctions.RUN)).create(eventSection)); } } catch (IllegalArgumentException e) { - throw new LocalizedResourceConfigException("warning.config.event.invalid_trigger", onObj.toString()); + throw new LocalizedResourceConfigException("warning.config.event.invalid_trigger", on); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/event/EventTrigger.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventTrigger.java similarity index 93% rename from core/src/main/java/net/momirealms/craftengine/core/plugin/event/EventTrigger.java rename to core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventTrigger.java index 8c7060079..7e5f3c882 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/event/EventTrigger.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/event/EventTrigger.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.plugin.event; +package net.momirealms.craftengine.core.plugin.context.event; import java.util.HashMap; import java.util.Map; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/AbstractConditionalFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/AbstractConditionalFunction.java index 394552424..44d22ba1a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/AbstractConditionalFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/AbstractConditionalFunction.java @@ -51,7 +51,7 @@ public abstract class AbstractConditionalFunction implement } else if (predicates instanceof Map map) { return List.of(factory.apply(MiscUtils.castToMap(map, false))); } - throw new IllegalArgumentException("Unsupported condition type: " + predicates.getClass().getSimpleName()); + throw new UnsupportedOperationException("Unsupported conditions argument class type: " + predicates.getClass().getSimpleName()); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ActionBarFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ActionBarFunction.java index 1db327924..9f3781528 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ActionBarFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ActionBarFunction.java @@ -10,11 +10,10 @@ import net.momirealms.craftengine.core.plugin.context.text.TextProviders; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; -import java.util.Optional; public class ActionBarFunction extends AbstractConditionalFunction { private final TextProvider message; @@ -28,9 +27,8 @@ public class ActionBarFunction extends AbstractConditionalF @Override public void runInternal(CTX ctx) { - Optional owner = ctx.getOptionalParameter(DirectContextParameters.PLAYER); if (this.selector == null) { - owner.ifPresent(it -> { + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { it.sendActionBar(AdventureHelper.miniMessage().deserialize(this.message.get(ctx), ctx.tagResolvers())); }); } else { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java new file mode 100644 index 000000000..025cda880 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java @@ -0,0 +1,53 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MCUtils; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class BreakBlockFunction extends AbstractConditionalFunction { + private final NumberProvider x; + private final NumberProvider y; + private final NumberProvider z; + + public BreakBlockFunction(NumberProvider x, NumberProvider y, NumberProvider z, List> predicates) { + super(predicates); + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public void runInternal(CTX ctx) { + Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + optionalPlayer.ifPresent(player -> player.breakBlock(MCUtils.fastFloor(x.getDouble(ctx)), MCUtils.fastFloor(y.getDouble(ctx)), MCUtils.fastFloor(z.getDouble(ctx)))); + } + + @Override + public Key type() { + return CommonFunctions.BREAK_BLOCK; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "")); + NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); + NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); + return new BreakBlockFunction<>(x, y, z, getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommandFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommandFunction.java index 0b203a447..d85f44950 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommandFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommandFunction.java @@ -12,11 +12,10 @@ import net.momirealms.craftengine.core.plugin.context.text.TextProviders; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; -import java.util.Optional; public class CommandFunction extends AbstractConditionalFunction { private final List command; @@ -33,9 +32,8 @@ public class CommandFunction extends AbstractConditionalFun @Override public void runInternal(CTX ctx) { if (this.asPlayer) { - Optional owner = ctx.getOptionalParameter(DirectContextParameters.PLAYER); if (this.selector == null) { - owner.ifPresent(it -> { + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { for (TextProvider c : this.command) { it.performCommand(c.get(ctx)); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java index ed0bd1e32..7f490b6d9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CommonFunctions.java @@ -5,19 +5,26 @@ import net.momirealms.craftengine.core.util.Key; public final class CommonFunctions { private CommonFunctions() {} - public static final Key RUN_ALL = Key.of("craftengine:run_all"); + public static final Key RUN = Key.of("craftengine:run"); public static final Key COMMAND = Key.of("craftengine:command"); public static final Key MESSAGE = Key.of("craftengine:message"); public static final Key ACTIONBAR = Key.of("craftengine:actionbar"); public static final Key TITLE = Key.of("craftengine:title"); + public static final Key OPEN_WINDOW = Key.of("craftengine:open_window"); public static final Key PARTICLE = Key.of("craftengine:particle"); - public static final Key SOUND = Key.of("craftengine:sound"); + public static final Key PLAY_SOUND = Key.of("craftengine:play_sound"); public static final Key POTION_EFFECT = Key.of("craftengine:potion_effect"); + public static final Key REMOVE_POTION_EFFECT = Key.of("craftengine:remove_potion_effect"); public static final Key BREAK_BLOCK = Key.of("craftengine:break_block"); public static final Key CANCEL_EVENT = Key.of("craftengine:cancel_event"); - public static final Key FOOD = Key.of("craftengine:food"); - public static final Key SATURATION = Key.of("craftengine:saturation"); - public static final Key MONEY = Key.of("craftengine:money"); - public static final Key OXYGEN = Key.of("craftengine:oxygen"); - public static final Key MINE_RADIUS = Key.of("craftengine:mine_radius"); + public static final Key UPDATE_INTERACTION_TICK = Key.of("craftengine:update_interaction_tick"); + public static final Key SET_COUNT = Key.of("craftengine:set_count"); + public static final Key PLACE_BLOCK = Key.of("craftengine:place_block"); + public static final Key SET_FOOD = Key.of("craftengine:food"); + public static final Key SET_COOLDOWN = Key.of("craftengine:set_cooldown"); + public static final Key REMOVE_COOLDOWN = Key.of("craftengine:remove_cooldown"); + public static final Key SET_SATURATION = Key.of("craftengine:saturation"); + public static final Key DROP_LOOT = Key.of("craftengine:drop_loot"); + public static final Key SWING_HAND = Key.of("craftengine:swing_hand"); + public static final Key LEVELER_EXP = Key.of("craftengine:leveler_exp"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DropLootFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DropLootFunction.java new file mode 100644 index 000000000..216c1c511 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/DropLootFunction.java @@ -0,0 +1,68 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.loot.LootTable; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.WorldPosition; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class DropLootFunction extends AbstractConditionalFunction { + private final NumberProvider x; + private final NumberProvider y; + private final NumberProvider z; + private final LootTable lootTable; + + public DropLootFunction(NumberProvider x, NumberProvider y, NumberProvider z, LootTable lootTable, List> predicates) { + super(predicates); + this.x = x; + this.y = y; + this.z = z; + this.lootTable = lootTable; + } + + @Override + public void runInternal(CTX ctx) { + Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); + if (optionalWorldPosition.isPresent()) { + World world = optionalWorldPosition.get().world(); + WorldPosition position = new WorldPosition(world, x.getDouble(ctx), y.getDouble(ctx), z.getDouble(ctx)); + Player player = ctx.getOptionalParameter(DirectContextParameters.PLAYER).orElse(null); + List> items = lootTable.getRandomItems(ctx.contexts(), world, player); + for (Item item : items) { + world.dropItemNaturally(position, item); + } + } + } + + @Override + public Key type() { + return CommonFunctions.DROP_LOOT; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "")); + NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); + NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); + LootTable loots = LootTable.fromMap(MiscUtils.castToMap(arguments.get("loot"), true)); + return new DropLootFunction<>(x, y, z, loots, getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/LevelerExpFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/LevelerExpFunction.java new file mode 100644 index 000000000..dd369f792 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/LevelerExpFunction.java @@ -0,0 +1,64 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.*; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; + +public class LevelerExpFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final NumberProvider count; + private final String leveler; + private final String plugin; + + public LevelerExpFunction(NumberProvider count, String leveler, String plugin, PlayerSelector selector, List> predicates) { + super(predicates); + this.count = count; + this.leveler = leveler; + this.plugin = plugin; + this.selector = selector; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { + CraftEngine.instance().compatibilityManager().addLevelerExp(it, this.plugin, this.leveler, this.count.getDouble(ctx)); + }); + } else { + for (Player target : this.selector.get(ctx)) { + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target, ContextHolder.EMPTY)); + CraftEngine.instance().compatibilityManager().addLevelerExp(target, this.plugin, this.leveler, this.count.getDouble(relationalContext)); + } + } + } + + @Override + public Key type() { + return CommonFunctions.LEVELER_EXP; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + Object count = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("count"), "warning.config.function.leveler_exp.missing_count"); + String leveler = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("leveler"), "warning.config.function.leveler_exp.missing_leveler"); + String plugin = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("plugin"), "warning.config.function.leveler_exp.missing_plugin"); + return new LevelerExpFunction<>(NumberProviders.fromObject(count), leveler, plugin, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MessageFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MessageFunction.java index 1b4f2ddbd..8c43e06f6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MessageFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/MessageFunction.java @@ -11,11 +11,10 @@ import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; -import java.util.Optional; public class MessageFunction extends AbstractConditionalFunction { private final List messages; @@ -31,9 +30,8 @@ public class MessageFunction extends AbstractConditionalFun @Override public void runInternal(CTX ctx) { - Optional owner = ctx.getOptionalParameter(DirectContextParameters.PLAYER); if (this.selector == null) { - owner.ifPresent(it -> { + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { for (TextProvider c : this.messages) { it.sendMessage(AdventureHelper.miniMessage().deserialize(c.get(ctx), ctx.tagResolvers()), this.overlay); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java new file mode 100644 index 000000000..9799f63e2 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/OpenWindowFunction.java @@ -0,0 +1,79 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.*; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.plugin.context.text.TextProvider; +import net.momirealms.craftengine.core.plugin.context.text.TextProviders; +import net.momirealms.craftengine.core.plugin.gui.GuiType; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import net.momirealms.craftengine.core.util.AdventureHelper; +import net.momirealms.craftengine.core.util.EnumUtils; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public class OpenWindowFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final GuiType guiType; + private final TextProvider optionalTitle; + + public OpenWindowFunction(List> predicates, @Nullable PlayerSelector selector, GuiType guiType, TextProvider optionalTitle) { + super(predicates); + this.selector = selector; + this.guiType = guiType; + this.optionalTitle = optionalTitle; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { + CraftEngine.instance().guiManager().openInventory(it, this.guiType); + if (this.optionalTitle != null) { + CraftEngine.instance().guiManager().updateInventoryTitle(it, AdventureHelper.miniMessage().deserialize(this.optionalTitle.get(ctx), ctx.tagResolvers())); + } + }); + } else { + for (Player viewer : this.selector.get(ctx)) { + CraftEngine.instance().guiManager().openInventory(viewer, this.guiType); + if (this.optionalTitle != null) { + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(viewer, ContextHolder.EMPTY)); + CraftEngine.instance().guiManager().updateInventoryTitle(viewer, AdventureHelper.miniMessage().deserialize(this.optionalTitle.get(relationalContext), relationalContext.tagResolvers())); + } + } + } + } + + @Override + public Key type() { + return CommonFunctions.OPEN_WINDOW; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + TextProvider title = Optional.ofNullable(arguments.get("title")).map(String::valueOf).map(TextProviders::fromString).orElse(null); + String rawType = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("gui-type"), "warning.config.function.open_window.missing_gui_type"); + try { + GuiType type = GuiType.valueOf(rawType.toUpperCase(Locale.ENGLISH)); + return new OpenWindowFunction<>(getPredicates(arguments), PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), type, title); + } catch (IllegalArgumentException e) { + throw new LocalizedResourceConfigException("warning.config.function.open_window.invalid_gui_type", e, rawType, EnumUtils.toString(GuiType.values())); + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ParticleFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ParticleFunction.java new file mode 100644 index 000000000..098f09d2c --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/ParticleFunction.java @@ -0,0 +1,135 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.block.DelayedInitBlockState; +import net.momirealms.craftengine.core.item.DelayedInitItem; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.Color; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.Position; +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.WorldPosition; +import net.momirealms.craftengine.core.world.particle.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class ParticleFunction extends AbstractConditionalFunction { + public static final Map, ParticleData>> DATA_TYPES = new HashMap<>(); + + static { + registerParticleData(map -> new BlockStateData( + new DelayedInitBlockState(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("block-state"), "warning.config.function.particle.missing_block_state"))), + ParticleTypes.BLOCK, ParticleTypes.FALLING_DUST, ParticleTypes.DUST_PILLAR, ParticleTypes.BLOCK_CRUMBLE, ParticleTypes.BLOCK_MARKER); + registerParticleData(map -> new ColorData( + Color.fromString(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(","))), + ParticleTypes.ENTITY_EFFECT, ParticleTypes.TINTED_LEAVES); + registerParticleData(map -> new JavaTypeData( + ResourceConfigUtils.getAsFloat(map.get("charge"), "charge")), + ParticleTypes.SCULK_CHARGE); + registerParticleData(map -> new JavaTypeData( + ResourceConfigUtils.getAsInt(map.get("shriek"), "shriek")), + ParticleTypes.SHRIEK); + registerParticleData(map -> new DustData( + Color.fromString(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(",")), + ResourceConfigUtils.getAsFloat(map.getOrDefault("scale", 1), "scale")), + ParticleTypes.DUST); + registerParticleData(map -> new DustTransitionData( + Color.fromString(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("from"), "warning.config.function.particle.missing_from").split(",")), + Color.fromString(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("to"), "warning.config.function.particle.missing_to").split(",")), + ResourceConfigUtils.getAsFloat(map.getOrDefault("scale", 1), "scale")), + ParticleTypes.DUST_COLOR_TRANSITION); + registerParticleData(map -> new ItemStackData( + new DelayedInitItem(Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("item"), "warning.config.function.particle.missing_item")))), + ParticleTypes.ITEM); + registerParticleData(map -> new VibrationData( + NumberProviders.fromObject(map.getOrDefault("target-x", 0)), + NumberProviders.fromObject(map.getOrDefault("target-y", 0)), + NumberProviders.fromObject(map.getOrDefault("target-z", 0)), + NumberProviders.fromObject(map.getOrDefault("arrival-time", 10))), + ParticleTypes.VIBRATION); + registerParticleData(map -> new TrailData( + NumberProviders.fromObject(map.getOrDefault("target-x", 0)), + NumberProviders.fromObject(map.getOrDefault("target-y", 0)), + NumberProviders.fromObject(map.getOrDefault("target-z", 0)), + Color.fromString(ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("color"), "warning.config.function.particle.missing_color").split(",")), + NumberProviders.fromObject(map.getOrDefault("duration", 10))), + ParticleTypes.TRAIL); + } + + public static void registerParticleData(java.util.function.Function, ParticleData> function, Key... types) { + for (Key type : types) { + DATA_TYPES.put(type, function); + } + } + + private final Key particleType; + private final NumberProvider x; + private final NumberProvider y; + private final NumberProvider z; + private final NumberProvider count; + private final NumberProvider xOffset; + private final NumberProvider yOffset; + private final NumberProvider zOffset; + private final NumberProvider speed; + private final ParticleData particleData; + + public ParticleFunction(Key particleType, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider count, + NumberProvider xOffset, NumberProvider yOffset, NumberProvider zOffset, NumberProvider speed, ParticleData particleData, List> predicates) { + super(predicates); + this.particleType = particleType; + this.count = count; + this.xOffset = xOffset; + this.yOffset = yOffset; + this.zOffset = zOffset; + this.speed = speed; + this.x = x; + this.y = y; + this.z = z; + this.particleData = particleData; + } + + @Override + public void runInternal(CTX ctx) { + Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); + if (optionalWorldPosition.isPresent()) { + World world = optionalWorldPosition.get().world(); + Position position = new Vec3d(this.x.getDouble(ctx), this.y.getDouble(ctx), this.z.getDouble(ctx)); + world.spawnParticle(position, this.particleType, this.count.getInt(ctx), this.xOffset.getDouble(ctx), this.yOffset.getDouble(ctx), this.zOffset.getDouble(ctx), this.speed.getDouble(ctx), this.particleData, ctx); + } + } + + @Override + public Key type() { + return CommonFunctions.PARTICLE; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + Key particleType = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("particle"), "warning.config.function.particle.missing_particle")); + NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "")); + NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); + NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); + NumberProvider count = NumberProviders.fromObject(arguments.getOrDefault("count", 1)); + NumberProvider xOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-x", 0)); + NumberProvider yOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-y", 0)); + NumberProvider zOffset = NumberProviders.fromObject(arguments.getOrDefault("offset-z", 0)); + NumberProvider speed = NumberProviders.fromObject(arguments.getOrDefault("speed", 0)); + return new ParticleFunction<>(particleType, x, y, z, count, xOffset, yOffset, zOffset, speed, + Optional.ofNullable(ParticleFunction.DATA_TYPES.get(particleType)).map(it -> it.apply(arguments)).orElse(null), getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java new file mode 100644 index 000000000..b5ee0bbeb --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java @@ -0,0 +1,67 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.block.DelayedInitBlockState; +import net.momirealms.craftengine.core.block.UpdateOption; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.WorldPosition; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class PlaceBlockFunction extends AbstractConditionalFunction { + private final DelayedInitBlockState delayedInitBlockState; + private final NumberProvider x; + private final NumberProvider y; + private final NumberProvider z; + private final NumberProvider updateFlags; + + public PlaceBlockFunction(DelayedInitBlockState delayedInitBlockState, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider updateFlags, List> predicates) { + super(predicates); + this.delayedInitBlockState = delayedInitBlockState; + this.x = x; + this.y = y; + this.z = z; + this.updateFlags = updateFlags; + } + + @Override + public void runInternal(CTX ctx) { + Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); + if (optionalWorldPosition.isPresent()) { + World world = optionalWorldPosition.get().world(); + world.setBlockAt(MCUtils.fastFloor(this.x.getDouble(ctx)), MCUtils.fastFloor(this.y.getDouble(ctx)), MCUtils.fastFloor(this.z.getDouble(ctx)), this.delayedInitBlockState.getState(), this.updateFlags.getInt(ctx)); + } + } + + @Override + public Key type() { + return CommonFunctions.PLACE_BLOCK; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + String state = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("block-state"), "warning.config.function.place_block.missing_block_state"); + DelayedInitBlockState delayedInitBlockState = new DelayedInitBlockState(state); + NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "")); + NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); + NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); + NumberProvider flags = Optional.ofNullable(arguments.get("update-flags")).map(NumberProviders::fromObject).orElse(NumberProviders.direct(UpdateOption.UPDATE_ALL.flags())); + return new PlaceBlockFunction<>(delayedInitBlockState, x, y, z, flags, getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaySoundFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaySoundFunction.java new file mode 100644 index 000000000..ea5f4c547 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaySoundFunction.java @@ -0,0 +1,73 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.sound.SoundSource; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.WorldPosition; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public class PlaySoundFunction extends AbstractConditionalFunction { + private final Key soundEvent; + private final NumberProvider x; + private final NumberProvider y; + private final NumberProvider z; + private final NumberProvider volume; + private final NumberProvider pitch; + private final SoundSource source; + + public PlaySoundFunction(Key soundEvent, NumberProvider x, NumberProvider y, NumberProvider z, NumberProvider volume, NumberProvider pitch, SoundSource source, List> predicates) { + super(predicates); + this.soundEvent = soundEvent; + this.x = x; + this.y = y; + this.z = z; + this.volume = volume; + this.pitch = pitch; + this.source = source; + } + + @Override + public void runInternal(CTX ctx) { + Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); + if (optionalWorldPosition.isPresent()) { + World world = optionalWorldPosition.get().world(); + world.playSound(new Vec3d(this.x.getDouble(ctx), this.y.getDouble(ctx), this.z.getDouble(ctx)), + this.soundEvent, this.volume.getFloat(ctx), this.pitch.getFloat(ctx), this.source); + } + } + + @Override + public Key type() { + return CommonFunctions.PLAY_SOUND; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + Key soundEvent = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("sound"), "warning.config.function.play_sound.missing_sound")); + NumberProvider x = NumberProviders.fromObject(arguments.getOrDefault("x", "")); + NumberProvider y = NumberProviders.fromObject(arguments.getOrDefault("y", "")); + NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "")); + NumberProvider volume = NumberProviders.fromObject(arguments.getOrDefault("volume", 1)); + NumberProvider pitch = NumberProviders.fromObject(arguments.getOrDefault("pitch", 1)); + SoundSource source = Optional.ofNullable(arguments.get("source")).map(String::valueOf).map(it -> SoundSource.valueOf(it.toUpperCase(Locale.ENGLISH))).orElse(SoundSource.MASTER); + return new PlaySoundFunction<>(soundEvent, x, y, z, volume, pitch, source, getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PotionEffectFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PotionEffectFunction.java new file mode 100644 index 000000000..ff13ddde0 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PotionEffectFunction.java @@ -0,0 +1,69 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.*; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; + +public class PotionEffectFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final Key potionEffectType; + private final NumberProvider duration; + private final NumberProvider amplifier; + private final boolean ambient; + private final boolean particles; + + public PotionEffectFunction(Key potionEffectType, NumberProvider duration, NumberProvider amplifier, boolean ambient, boolean particles, PlayerSelector selector, List> predicates) { + super(predicates); + this.potionEffectType = potionEffectType; + this.duration = duration; + this.amplifier = amplifier; + this.selector = selector; + this.ambient = ambient; + this.particles = particles; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { + it.addPotionEffect(this.potionEffectType, this.duration.getInt(ctx), this.amplifier.getInt(ctx), this.ambient, this.particles); + }); + } else { + for (Player target : this.selector.get(ctx)) { + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target, ContextHolder.EMPTY)); + target.addPotionEffect(this.potionEffectType, this.duration.getInt(relationalContext), this.amplifier.getInt(relationalContext), this.ambient, this.particles); + } + } + } + + @Override + public Key type() { + return CommonFunctions.POTION_EFFECT; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + Key effectType = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("potion-effect"), "warning.config.function.potion_effect.missing_potion_effect")); + NumberProvider duration = NumberProviders.fromObject(arguments.getOrDefault("duration", 20)); + NumberProvider amplifier = NumberProviders.fromObject(arguments.getOrDefault("amplifier", 0)); + boolean ambient = (boolean) arguments.getOrDefault("ambient", false); + boolean particles = (boolean) arguments.getOrDefault("particles", true); + return new PotionEffectFunction<>(effectType, duration, amplifier, ambient, particles, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveCooldownFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveCooldownFunction.java new file mode 100644 index 000000000..7463d8bf2 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemoveCooldownFunction.java @@ -0,0 +1,69 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.CooldownData; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class RemoveCooldownFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final String id; + private final boolean all; + + public RemoveCooldownFunction(String id, boolean all, PlayerSelector selector, List> predicates) { + super(predicates); + this.selector = selector; + this.id = id; + this.all = all; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + optionalPlayer.ifPresent(player -> { + CooldownData data = player.cooldown(); + if (this.all) data.clearCooldowns(); + else data.removeCooldown(this.id); + }); + } else { + for (Player target : this.selector.get(ctx)) { + CooldownData data = target.cooldown(); + if (this.all) data.clearCooldowns(); + else data.removeCooldown(this.id); + } + } + } + + @Override + public Key type() { + return CommonFunctions.REMOVE_COOLDOWN; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + boolean all = (boolean) arguments.getOrDefault("all", false); + if (all) { + return new RemoveCooldownFunction<>(null, true, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } else { + String id = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("id"), "warning.config.function.remove_cooldown.missing_id"); + return new RemoveCooldownFunction<>(id, false, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemovePotionEffectFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemovePotionEffectFunction.java new file mode 100644 index 000000000..cc0148996 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RemovePotionEffectFunction.java @@ -0,0 +1,64 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; + +public class RemovePotionEffectFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final Key potionEffectType; + private final boolean all; + + public RemovePotionEffectFunction(Key potionEffectType, boolean all, PlayerSelector selector, List> predicates) { + super(predicates); + this.potionEffectType = potionEffectType; + this.selector = selector; + this.all = all; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> { + if (this.all) it.clearPotionEffects(); + else it.removePotionEffect(this.potionEffectType); + }); + } else { + for (Player target : this.selector.get(ctx)) { + if (this.all) target.clearPotionEffects(); + else target.removePotionEffect(this.potionEffectType); + } + } + } + + @Override + public Key type() { + return CommonFunctions.REMOVE_POTION_EFFECT; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + boolean all = (boolean) arguments.getOrDefault("all", false); + if (all) { + return new RemovePotionEffectFunction<>(null, true, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } else { + Key effectType = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("potion-effect"), "warning.config.function.remove_potion_effect.missing_potion_effect")); + return new RemovePotionEffectFunction<>(effectType, false, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java new file mode 100644 index 000000000..801e13667 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java @@ -0,0 +1,81 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MCUtils; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.world.WorldPosition; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class RunFunction extends AbstractConditionalFunction { + private final List> functions; + private final NumberProvider delay; + + public RunFunction(List> functions, NumberProvider delay, List> predicates) { + super(predicates); + this.functions = functions; + this.delay = delay; + } + + @Override + public void runInternal(CTX ctx) { + int delay = this.delay.getInt(ctx); + if (delay <= 0) { + for (Function function : functions) { + function.run(ctx); + } + } else { + Optional position = ctx.getOptionalParameter(DirectContextParameters.POSITION); + if (!VersionHelper.isFolia() || position.isEmpty()) { + CraftEngine.instance().scheduler().sync().runLater(() -> { + for (Function function : functions) { + function.run(ctx); + } + }, delay); + } else { + WorldPosition pos = position.get(); + CraftEngine.instance().scheduler().sync().runLater(() -> { + for (Function function : functions) { + function.run(ctx); + } + }, delay, pos.world().platformWorld(), MCUtils.fastFloor(pos.x()) >> 4, MCUtils.fastFloor(pos.z()) >> 4); + } + } + } + + @Override + public Key type() { + return CommonFunctions.RUN; + } + + public static class FactoryImpl extends AbstractFactory { + private final java.util.function.Function, Function> functionFactory; + + public FactoryImpl(java.util.function.Function, Function> functionFactory, java.util.function.Function, Condition> conditionFactory) { + super(conditionFactory); + this.functionFactory = functionFactory; + } + + @Override + public Function create(Map arguments) { + NumberProvider delay = NumberProviders.fromObject(arguments.getOrDefault("delay", 0)); + @SuppressWarnings("unchecked") + List> functions = (List>) ResourceConfigUtils.requireNonNullOrThrow(arguments.get("functions"), "warning.config.function.run.missing_functions"); + List> fun = new ArrayList<>(); + for (Map function : functions) { + fun.add(this.functionFactory.apply(function)); + } + return new RunFunction<>(fun, delay, getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java new file mode 100644 index 000000000..68eb002d6 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCooldownFunction.java @@ -0,0 +1,72 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.*; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.plugin.context.text.TextProvider; +import net.momirealms.craftengine.core.plugin.context.text.TextProviders; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.TimeUtils; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class SetCooldownFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final TextProvider time; + private final String id; + private final boolean add; + + public SetCooldownFunction(TextProvider time, String id, boolean add, PlayerSelector selector, List> predicates) { + super(predicates); + this.time = time; + this.add = add; + this.selector = selector; + this.id = id; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + optionalPlayer.ifPresent(player -> { + long millis = TimeUtils.parseToMillis(this.time.get(ctx)); + CooldownData data = player.cooldown(); + if (this.add) data.addCooldown(this.id, millis); + else data.setCooldown(this.id, millis); + }); + } else { + for (Player target : this.selector.get(ctx)) { + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target, ContextHolder.EMPTY)); + long millis = TimeUtils.parseToMillis(this.time.get(relationalContext)); + CooldownData data = target.cooldown(); + if (this.add) data.addCooldown(this.id, millis); + else data.setCooldown(this.id, millis); + } + } + } + + @Override + public Key type() { + return CommonFunctions.SET_COOLDOWN; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + String id = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("id"), "warning.config.function.set_cooldown.missing_id"); + String time = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("time"), "warning.config.function.set_cooldown.missing_time"); + boolean add = (boolean) arguments.getOrDefault("add", false); + return new SetCooldownFunction<>(TextProviders.fromString(time), id, add, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCountFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCountFunction.java new file mode 100644 index 000000000..6d9e6b496 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetCountFunction.java @@ -0,0 +1,57 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class SetCountFunction extends AbstractConditionalFunction { + private final NumberProvider count; + private final boolean add; + + public SetCountFunction(NumberProvider count, boolean add, List> predicates) { + super(predicates); + this.count = count; + this.add = add; + } + + @Override + public void runInternal(CTX ctx) { + Optional> optionalItem = ctx.getOptionalParameter(DirectContextParameters.ITEM_IN_HAND); + if (optionalItem.isPresent()) { + Item item = optionalItem.get(); + if (this.add) { + item.count(Math.min(item.count() + (this.count.getInt(ctx)), item.maxStackSize())); + } else { + item.count(Math.min(this.count.getInt(ctx), item.maxStackSize())); + } + } + } + + @Override + public Key type() { + return CommonFunctions.SET_COUNT; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + Object value = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("count"), "warning.config.function.set_count.missing_count"); + boolean add = (boolean) arguments.getOrDefault("add", false); + return new SetCountFunction<>(NumberProviders.fromObject(value), add, getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetFoodFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetFoodFunction.java new file mode 100644 index 000000000..ebb1705a3 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetFoodFunction.java @@ -0,0 +1,60 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.*; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class SetFoodFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final NumberProvider count; + private final boolean add; + + public SetFoodFunction(NumberProvider count, boolean add, PlayerSelector selector, List> predicates) { + super(predicates); + this.count = count; + this.add = add; + this.selector = selector; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + optionalPlayer.ifPresent(player -> player.setFoodLevel(this.add ? player.foodLevel() + this.count.getInt(ctx) : this.count.getInt(ctx))); + } else { + for (Player target : this.selector.get(ctx)) { + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target, ContextHolder.EMPTY)); + target.setFoodLevel(this.add ? target.foodLevel() + this.count.getInt(relationalContext) : this.count.getInt(relationalContext)); + } + } + } + + @Override + public Key type() { + return CommonFunctions.SET_FOOD; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + Object value = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("food"), "warning.config.function.set_food.missing_food"); + boolean add = (boolean) arguments.getOrDefault("add", false); + return new SetFoodFunction<>(NumberProviders.fromObject(value), add, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetSaturationFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetSaturationFunction.java new file mode 100644 index 000000000..7afedc1c5 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SetSaturationFunction.java @@ -0,0 +1,60 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.*; +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.plugin.context.number.NumberProviders; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelector; +import net.momirealms.craftengine.core.plugin.context.selector.PlayerSelectors; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class SetSaturationFunction extends AbstractConditionalFunction { + private final PlayerSelector selector; + private final NumberProvider count; + private final boolean add; + + public SetSaturationFunction(NumberProvider count, boolean add, PlayerSelector selector, List> predicates) { + super(predicates); + this.count = count; + this.add = add; + this.selector = selector; + } + + @Override + public void runInternal(CTX ctx) { + if (this.selector == null) { + Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + optionalPlayer.ifPresent(player -> player.setSaturation(this.add ? player.saturation() + this.count.getFloat(ctx) : this.count.getFloat(ctx))); + } else { + for (Player target : this.selector.get(ctx)) { + RelationalContext relationalContext = ViewerContext.of(ctx, PlayerOptionalContext.of(target, ContextHolder.EMPTY)); + target.setSaturation(this.add ? target.saturation() + this.count.getFloat(relationalContext) : this.count.getFloat(relationalContext)); + } + } + } + + @Override + public Key type() { + return CommonFunctions.SET_SATURATION; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + Object value = ResourceConfigUtils.requireNonNullOrThrow(arguments.get("saturation"), "warning.config.function.set_saturation.missing_saturation"); + boolean add = (boolean) arguments.getOrDefault("add", false); + return new SetSaturationFunction<>(NumberProviders.fromObject(value), add, PlayerSelectors.fromObject(arguments.get("target"), conditionFactory()), getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SwingHandFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SwingHandFunction.java new file mode 100644 index 000000000..935fa210e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/SwingHandFunction.java @@ -0,0 +1,52 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.InteractionHand; +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.Key; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public class SwingHandFunction extends AbstractConditionalFunction { + private final Optional hand; + + public SwingHandFunction(Optional hand, List> predicates) { + super(predicates); + this.hand = hand; + } + + @Override + public void runInternal(CTX ctx) { + Optional cancellable = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + cancellable.ifPresent(value -> { + if (this.hand.isPresent()) { + value.swingHand(this.hand.get()); + } else { + value.swingHand(ctx.getOptionalParameter(DirectContextParameters.HAND).orElse(InteractionHand.MAIN_HAND)); + } + }); + } + + @Override + public Key type() { + return CommonFunctions.SWING_HAND; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + Optional optionalHand = Optional.ofNullable(arguments.get("hand")).map(it -> InteractionHand.valueOf(it.toString().toUpperCase(Locale.ENGLISH))); + return new SwingHandFunction<>(optionalHand, getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TitleFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TitleFunction.java index 6d55589ae..43eb8d195 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TitleFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TitleFunction.java @@ -11,11 +11,10 @@ import net.momirealms.craftengine.core.plugin.context.text.TextProvider; import net.momirealms.craftengine.core.plugin.context.text.TextProviders; import net.momirealms.craftengine.core.util.AdventureHelper; import net.momirealms.craftengine.core.util.Key; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.List; import java.util.Map; -import java.util.Optional; public class TitleFunction extends AbstractConditionalFunction { private final PlayerSelector selector; @@ -38,9 +37,8 @@ public class TitleFunction extends AbstractConditionalFunct @Override public void runInternal(CTX ctx) { - Optional owner = ctx.getOptionalParameter(DirectContextParameters.PLAYER); if (this.selector == null) { - owner.ifPresent(it -> it.sendTitle( + ctx.getOptionalParameter(DirectContextParameters.PLAYER).ifPresent(it -> it.sendTitle( AdventureHelper.miniMessage().deserialize(this.main.get(ctx), ctx.tagResolvers()), AdventureHelper.miniMessage().deserialize(this.sub.get(ctx), ctx.tagResolvers()), this.fadeIn.getInt(ctx), this.stay.getInt(ctx), this.fadeOut.getInt(ctx) diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateInteractionFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateInteractionFunction.java new file mode 100644 index 000000000..6ef587ad5 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateInteractionFunction.java @@ -0,0 +1,41 @@ +package net.momirealms.craftengine.core.plugin.context.function; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.context.Condition; +import net.momirealms.craftengine.core.plugin.context.Context; +import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters; +import net.momirealms.craftengine.core.util.Key; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class UpdateInteractionFunction extends AbstractConditionalFunction { + + public UpdateInteractionFunction(List> predicates) { + super(predicates); + } + + @Override + public void runInternal(CTX ctx) { + Optional cancellable = ctx.getOptionalParameter(DirectContextParameters.PLAYER); + cancellable.ifPresent(value -> value.updateLastSuccessfulInteractionTick(value.gameTicks())); + } + + @Override + public Key type() { + return CommonFunctions.UPDATE_INTERACTION_TICK; + } + + public static class FactoryImpl extends AbstractFactory { + + public FactoryImpl(java.util.function.Function, Condition> factory) { + super(factory); + } + + @Override + public Function create(Map arguments) { + return new UpdateInteractionFunction<>(getPredicates(arguments)); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/ExpressionNumberProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/ExpressionNumberProvider.java index 6428df308..43bf33fcf 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/ExpressionNumberProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/ExpressionNumberProvider.java @@ -31,6 +31,18 @@ public class ExpressionNumberProvider implements NumberProvider { } } + @Override + public double getDouble(Context context) { + Component resultComponent = AdventureHelper.customMiniMessage().deserialize(this.expr, context.tagResolvers()); + String resultString = AdventureHelper.plainTextContent(resultComponent); + Expression expression = new Expression(resultString); + try { + return expression.evaluate().getNumberValue().doubleValue(); + } catch (EvaluationException | ParseException e) { + throw new RuntimeException("Invalid expression: " + this.expr + " -> " + resultString + " -> Cannot parse", e); + } + } + @Override public Key type() { return NumberProviders.EXPRESSION; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/FixedNumberProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/FixedNumberProvider.java index 3bbf7729f..b4662a547 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/FixedNumberProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/FixedNumberProvider.java @@ -10,14 +10,19 @@ import java.util.Map; public class FixedNumberProvider implements NumberProvider { public static final FactoryImpl FACTORY = new FactoryImpl(); - private final float value; + private final double value; - public FixedNumberProvider(float value) { + public FixedNumberProvider(double value) { this.value = value; } @Override public float getFloat(Context context) { + return (float) this.value; + } + + @Override + public double getDouble(Context context) { return this.value; } @@ -32,12 +37,12 @@ public class FixedNumberProvider implements NumberProvider { public NumberProvider create(Map arguments) { String plainOrExpression = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("value"), "warning.config.number.fixed.missing_value"); try { - float value = Float.parseFloat(plainOrExpression); + double value = Double.parseDouble(plainOrExpression); return new FixedNumberProvider(value); } catch (NumberFormatException e) { Expression expression = new Expression(plainOrExpression); try { - return new FixedNumberProvider(expression.evaluate().getNumberValue().floatValue()); + return new FixedNumberProvider(expression.evaluate().getNumberValue().doubleValue()); } catch (Exception e1) { throw new LocalizedResourceConfigException("warning.config.number.fixed.invalid_value", e1, plainOrExpression); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/NumberProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/NumberProvider.java index 93cdd64a0..9ea8a03d1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/NumberProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/NumberProvider.java @@ -7,6 +7,8 @@ public interface NumberProvider { float getFloat(Context context); + double getDouble(Context context); + default int getInt(Context context) { return Math.round(this.getFloat(context)); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/NumberProviders.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/NumberProviders.java index 8baef2d0b..2d7fb1591 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/NumberProviders.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/NumberProviders.java @@ -41,6 +41,10 @@ public class NumberProviders { return functions; } + public static NumberProvider direct(double value) { + return new FixedNumberProvider(value); + } + public static NumberProvider fromMap(Map map) { String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("type"), "warning.config.number.missing_type"); Key key = Key.withDefaultNamespace(type, Key.DEFAULT_NAMESPACE); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/UniformNumberProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/UniformNumberProvider.java index 62d7c4ddc..dca387d0e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/UniformNumberProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/number/UniformNumberProvider.java @@ -30,6 +30,11 @@ public class UniformNumberProvider implements NumberProvider { return RandomUtils.generateRandomInt(this.min.getInt(context), this.max.getInt(context) + 1); } + @Override + public double getDouble(Context context) { + return RandomUtils.generateRandomDouble(this.min.getDouble(context), this.max.getDouble(context)); + } + @Override public float getFloat(Context context) { return RandomUtils.generateRandomFloat(this.min.getFloat(context), this.max.getFloat(context)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/BlockParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/BlockParameterProvider.java index e707aca80..1aac69f9c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/BlockParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/BlockParameterProvider.java @@ -19,7 +19,7 @@ public class BlockParameterProvider implements ChainParameterProvider LAST_RANDOM = ContextKey.direct("last_random"); public static final ContextKey WORLD = ContextKey.direct("world"); public static final ContextKey> FURNITURE_ITEM = ContextKey.direct("furniture_item"); - public static final ContextKey> CONSUMED_ITEM = ContextKey.direct("consumed_item"); public static final ContextKey> ITEM_IN_HAND = ContextKey.direct("item_in_hand"); public static final ContextKey FALLING_BLOCK = ContextKey.direct("falling_block"); public static final ContextKey EXPLOSION_RADIUS = ContextKey.direct("explosion_radius"); public static final ContextKey PLAYER = ContextKey.direct("player"); public static final ContextKey ENTITY = ContextKey.direct("entity"); - public static final ContextKey BLOCK_STATE = ContextKey.direct("custom_block_state"); + public static final ContextKey CUSTOM_BLOCK_STATE = ContextKey.direct("custom_block_state"); public static final ContextKey COORDINATE = ContextKey.direct("coordinate"); public static final ContextKey POSITION = ContextKey.direct("position"); public static final ContextKey NAME = ContextKey.direct("name"); public static final ContextKey X = ContextKey.direct("x"); public static final ContextKey Y = ContextKey.direct("y"); public static final ContextKey Z = ContextKey.direct("z"); + public static final ContextKey YAW = ContextKey.direct("yaw"); + public static final ContextKey PITCH = ContextKey.direct("pitch"); public static final ContextKey BLOCK_X = ContextKey.direct("block_x"); public static final ContextKey BLOCK_Y = ContextKey.direct("block_y"); public static final ContextKey BLOCK_Z = ContextKey.direct("block_z"); + public static final ContextKey FOOD = ContextKey.direct("food"); + public static final ContextKey SATURATION = ContextKey.direct("saturation"); public static final ContextKey UUID = ContextKey.direct("uuid"); public static final ContextKey> MAIN_HAND_ITEM = ContextKey.direct("main_hand_item"); public static final ContextKey> OFF_HAND_ITEM = ContextKey.direct("off_hand_item"); @@ -57,5 +60,6 @@ public final class DirectContextParameters { public static final ContextKey IS_FLYING = ContextKey.direct("is_flying"); public static final ContextKey IS_SNEAKING = ContextKey.direct("is_sneaking"); public static final ContextKey IS_CUSTOM = ContextKey.direct("is_custom"); + public static final ContextKey IS_BLOCK_ITEM = ContextKey.direct("is_block_item"); public static final ContextKey GAMEMODE = ContextKey.direct("gamemode"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java index b36225670..465b49809 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java @@ -16,6 +16,8 @@ public class EntityParameterProvider implements ChainParameterProvider { CONTEXT_FUNCTIONS.put(DirectContextParameters.X, Entity::x); CONTEXT_FUNCTIONS.put(DirectContextParameters.Y, Entity::y); CONTEXT_FUNCTIONS.put(DirectContextParameters.Z, Entity::z); + CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::xRot); + CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::yRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MCUtils.fastFloor(p.x())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MCUtils.fastFloor(p.y())); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/ItemParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/ItemParameterProvider.java index a4709072f..f9d4617f6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/ItemParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/ItemParameterProvider.java @@ -15,6 +15,7 @@ public class ItemParameterProvider implements ChainParameterProvider> { CONTEXT_FUNCTIONS.put(DirectContextParameters.ID, Item::id); CONTEXT_FUNCTIONS.put(DirectContextParameters.CUSTOM_MODEL_DATA, i -> i.customModelData().orElse(null)); CONTEXT_FUNCTIONS.put(DirectContextParameters.IS_CUSTOM, Item::isCustomItem); + CONTEXT_FUNCTIONS.put(DirectContextParameters.IS_BLOCK_ITEM, Item::isBlockItem); } @SuppressWarnings("unchecked") diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java index 4075c6ff8..042b08c05 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java @@ -1,6 +1,5 @@ package net.momirealms.craftengine.core.plugin.context.parameter; -import net.momirealms.craftengine.core.entity.AbstractEntity; import net.momirealms.craftengine.core.entity.Entity; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.Player; @@ -16,13 +15,17 @@ import java.util.function.Function; public class PlayerParameterProvider implements ChainParameterProvider { private static final Map, Function> CONTEXT_FUNCTIONS = new HashMap<>(); static { - CONTEXT_FUNCTIONS.put(DirectContextParameters.X, AbstractEntity::x); - CONTEXT_FUNCTIONS.put(DirectContextParameters.Y, AbstractEntity::y); - CONTEXT_FUNCTIONS.put(DirectContextParameters.Z, AbstractEntity::z); - CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, AbstractEntity::position); + CONTEXT_FUNCTIONS.put(DirectContextParameters.X, Entity::x); + CONTEXT_FUNCTIONS.put(DirectContextParameters.Y, Entity::y); + CONTEXT_FUNCTIONS.put(DirectContextParameters.Z, Entity::z); + CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::xRot); + CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::yRot); + CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MCUtils.fastFloor(p.x())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MCUtils.fastFloor(p.y())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MCUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.FOOD, Player::foodLevel); + CONTEXT_FUNCTIONS.put(DirectContextParameters.SATURATION, Player::saturation); CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Player::name); CONTEXT_FUNCTIONS.put(DirectContextParameters.UUID, Player::uuid); CONTEXT_FUNCTIONS.put(DirectContextParameters.WORLD, Entity::world); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java index 710917c19..b7e2b26b6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java @@ -18,6 +18,8 @@ public class PositionParameterProvider implements ChainParameterProvider MCUtils.fastFloor(p.x())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MCUtils.fastFloor(p.y())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MCUtils.fastFloor(p.z())); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java index 2499f83d8..2616f5142 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java @@ -11,7 +11,6 @@ public class Dependencies { "asm", "org.ow2.asm", "asm", - "asm", Collections.emptyList() ); @@ -19,7 +18,6 @@ public class Dependencies { "asm-commons", "org.ow2.asm", "asm-commons", - "asm-commons", Collections.emptyList() ); @@ -27,7 +25,6 @@ public class Dependencies { "jar-relocator", "me.lucko", "jar-relocator", - "jar-relocator", Collections.emptyList() ); @@ -35,7 +32,6 @@ public class Dependencies { "geantyref", "io{}leangen{}geantyref", "geantyref", - "geantyref", List.of(Relocation.of("geantyref", "io{}leangen{}geantyref")) ); @@ -43,7 +39,6 @@ public class Dependencies { "cloud-core", "org{}incendo", "cloud-core", - "cloud-core", List.of(Relocation.of("cloud", "org{}incendo{}cloud"), Relocation.of("geantyref", "io{}leangen{}geantyref")) ); @@ -52,7 +47,6 @@ public class Dependencies { "cloud-brigadier", "org{}incendo", "cloud-brigadier", - "cloud-brigadier", List.of(Relocation.of("cloud", "org{}incendo{}cloud"), Relocation.of("geantyref", "io{}leangen{}geantyref")) ); @@ -61,7 +55,6 @@ public class Dependencies { "cloud-services", "org{}incendo", "cloud-services", - "cloud-services", List.of(Relocation.of("cloud", "org{}incendo{}cloud"), Relocation.of("geantyref", "io{}leangen{}geantyref")) ); @@ -70,7 +63,6 @@ public class Dependencies { "cloud-bukkit", "org{}incendo", "cloud-bukkit", - "cloud-bukkit", List.of(Relocation.of("cloud", "org{}incendo{}cloud"), Relocation.of("geantyref", "io{}leangen{}geantyref"), Relocation.of("adventure", "net{}kyori{}adventure"), @@ -82,7 +74,6 @@ public class Dependencies { "cloud-paper", "org{}incendo", "cloud-paper", - "cloud-paper", List.of(Relocation.of("cloud", "org{}incendo{}cloud"), Relocation.of("geantyref", "io{}leangen{}geantyref"), Relocation.of("adventure", "net{}kyori{}adventure"), @@ -94,7 +85,6 @@ public class Dependencies { "cloud-minecraft-extras", "org{}incendo", "cloud-minecraft-extras", - "cloud-minecraft-extras", List.of(Relocation.of("cloud", "org{}incendo{}cloud"), Relocation.of("geantyref", "io{}leangen{}geantyref"), Relocation.of("adventure", "net{}kyori{}adventure"), @@ -106,7 +96,6 @@ public class Dependencies { "boosted-yaml", "dev{}dejvokep", "boosted-yaml", - "boosted-yaml", List.of(Relocation.of("boostedyaml", "dev{}dejvokep{}boostedyaml")) ); @@ -114,7 +103,6 @@ public class Dependencies { "bstats-base", "org{}bstats", "bstats-base", - "bstats-base", List.of(Relocation.of("bstats", "org{}bstats")) ); @@ -122,7 +110,6 @@ public class Dependencies { "bstats-bukkit", "org{}bstats", "bstats-bukkit", - "bstats-bukkit", List.of(Relocation.of("bstats", "org{}bstats")) ) { @Override @@ -135,7 +122,6 @@ public class Dependencies { "gson", "com.google.code.gson", "gson", - "gson", Collections.emptyList() ); @@ -143,7 +129,6 @@ public class Dependencies { "caffeine", "com{}github{}ben-manes{}caffeine", "caffeine", - "caffeine", List.of(Relocation.of("caffeine", "com{}github{}benmanes{}caffeine")) ); @@ -151,7 +136,6 @@ public class Dependencies { "zstd-jni", "com.github.luben", "zstd-jni", - "zstd-jni", Collections.emptyList() ); @@ -159,7 +143,6 @@ public class Dependencies { "slf4j-api", "org.slf4j", "slf4j-api", - "slf4j-api", Collections.emptyList() ); @@ -167,7 +150,6 @@ public class Dependencies { "slf4j-simple", "org.slf4j", "slf4j-simple", - "slf4j-simple", Collections.emptyList() ) { @Override @@ -180,7 +162,6 @@ public class Dependencies { "commons-io", "commons-io", "commons-io", - "commons-io", List.of(Relocation.of("commons", "org{}apache{}commons")) ); @@ -188,7 +169,6 @@ public class Dependencies { "byte-buddy", "net{}bytebuddy", "byte-buddy", - "byte-buddy", List.of(Relocation.of("bytebuddy", "net{}bytebuddy")) ); @@ -196,47 +176,166 @@ public class Dependencies { "snake-yaml", "org{}yaml", "snakeyaml", - "snakeyaml", List.of(Relocation.of("snakeyaml", "org{}yaml{}snakeyaml")) ); + public static final Dependency OPTION = new Dependency( + "option", + "net{}kyori", + "option", + List.of(Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("adventure", "net{}kyori{}adventure")) + ); + + public static final Dependency ADVENTURE_API = new Dependency( + "adventure-api", + "net{}kyori", + "adventure-api", + List.of(Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("adventure", "net{}kyori{}adventure")) + ); + + public static final Dependency ADVENTURE_NBT = new Dependency( + "adventure-nbt", + "net{}kyori", + "adventure-nbt", + List.of(Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("adventure", "net{}kyori{}adventure")) + ) { + @Override + public String getVersion() { + return ADVENTURE_API.getVersion(); + } + }; + + public static final Dependency ADVENTURE_KEY = new Dependency( + "adventure-key", + "net{}kyori", + "adventure-key", + List.of(Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("adventure", "net{}kyori{}adventure")) + ) { + @Override + public String getVersion() { + return ADVENTURE_API.getVersion(); + } + }; + + public static final Dependency EXAMINATION_API = new Dependency( + "examination-api", + "net{}kyori", + "examination-api", + List.of(Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("adventure", "net{}kyori{}adventure")) + ); + + public static final Dependency EXAMINATION_STRING = new Dependency( + "examination-string", + "net{}kyori", + "examination-string", + List.of(Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("adventure", "net{}kyori{}adventure")) + ) { + @Override + public String getVersion() { + return EXAMINATION_API.getVersion(); + } + }; + public static final Dependency MINIMESSAGE = new Dependency( "adventure-text-minimessage", "net{}kyori", "adventure-text-minimessage", - "adventure-text-minimessage", - List.of(Relocation.of("adventure", "net{}kyori{}adventure")) - ); + List.of(Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("adventure", "net{}kyori{}adventure")) + ) { + @Override + public String getVersion() { + return ADVENTURE_API.getVersion(); + } + }; + + public static final Dependency TEXT_SERIALIZER_COMMONS = new Dependency( + "adventure-text-serializer-commons", + "net{}kyori", + "adventure-text-serializer-commons", + List.of(Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("adventure", "net{}kyori{}adventure")) + ) { + @Override + public String getVersion() { + return ADVENTURE_API.getVersion(); + } + }; public static final Dependency TEXT_SERIALIZER_GSON = new Dependency( "adventure-text-serializer-gson", "net{}kyori", "adventure-text-serializer-gson", - "adventure-text-serializer-gson", - List.of(Relocation.of("adventure", "net{}kyori{}adventure")) - ); + List.of(Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("adventure", "net{}kyori{}adventure")) + ) { + @Override + public String getVersion() { + return ADVENTURE_API.getVersion(); + } + }; public static final Dependency TEXT_SERIALIZER_GSON_LEGACY = new Dependency( "adventure-text-serializer-json-legacy-impl", "net{}kyori", "adventure-text-serializer-json-legacy-impl", - "adventure-text-serializer-json-legacy-impl", - List.of(Relocation.of("adventure", "net{}kyori{}adventure")) - ); + List.of(Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("adventure", "net{}kyori{}adventure")) + ) { + @Override + public String getVersion() { + return ADVENTURE_API.getVersion(); + } + }; + + public static final Dependency TEXT_SERIALIZER_LEGACY = new Dependency( + "adventure-text-serializer-legacy", + "net{}kyori", + "adventure-text-serializer-legacy", + List.of(Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("adventure", "net{}kyori{}adventure")) + ) { + @Override + public String getVersion() { + return ADVENTURE_API.getVersion(); + } + }; public static final Dependency TEXT_SERIALIZER_JSON = new Dependency( "adventure-text-serializer-json", "net{}kyori", "adventure-text-serializer-json", - "adventure-text-serializer-json", - List.of(Relocation.of("adventure", "net{}kyori{}adventure")) - ); + List.of(Relocation.of("option", "net{}kyori{}option"), + Relocation.of("examination", "net{}kyori{}examination"), + Relocation.of("adventure", "net{}kyori{}adventure")) + ) { + @Override + public String getVersion() { + return ADVENTURE_API.getVersion(); + } + }; public static final Dependency AHO_CORASICK = new Dependency( "ahocorasick", "org{}ahocorasick", "ahocorasick", - "aho-corasick", List.of(Relocation.of("ahocorasick", "org{}ahocorasick")) ); @@ -244,7 +343,6 @@ public class Dependencies { "lz4", "org{}lz4", "lz4-java", - "lz4-java", List.of(Relocation.of("jpountz", "net{}jpountz")) ); @@ -252,7 +350,6 @@ public class Dependencies { "evalex", "com{}ezylang", "EvalEx", - "evalex", List.of(Relocation.of("evalex", "com{}ezylang{}evalex")) ); @@ -260,7 +357,6 @@ public class Dependencies { "netty-codec-http", "io{}netty", "netty-codec-http", - "netty-codec-http", Collections.emptyList() ); @@ -268,7 +364,6 @@ public class Dependencies { "netty-codec-http2", "io{}netty", "netty-codec-http2", - "netty-codec-http2", Collections.emptyList() ); @@ -276,7 +371,6 @@ public class Dependencies { "reactive-streams", "org{}reactivestreams", "reactive-streams", - "reactive-streams", List.of(Relocation.of("reactivestreams", "org{}reactivestreams")) ); @@ -284,7 +378,6 @@ public class Dependencies { "jimfs", "com{}google{}jimfs", "jimfs", - "jimfs", List.of(Relocation.of("jimfs", "com{}google{}common{}jimfs")) ); @@ -292,15 +385,13 @@ public class Dependencies { "commons-imaging", "org{}apache{}commons", "commons-imaging", - "commons-imaging", - List.of(Relocation.of("imaging", "org{}apache{}commons{}imaging")) + List.of(Relocation.of("commons", "org{}apache{}commons")) ); public static final Dependency AMAZON_AWSSDK_S3 = new Dependency( "amazon-sdk-s3", "software{}amazon{}awssdk", "s3", - "amazon-s3", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -311,7 +402,6 @@ public class Dependencies { "amazon-sdk-netty-nio-client", "software{}amazon{}awssdk", "netty-nio-client", - "amazon-netty-nio-client", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -327,7 +417,6 @@ public class Dependencies { "amazon-sdk-core", "software{}amazon{}awssdk", "sdk-core", - "amazon-sdk-core", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -343,7 +432,6 @@ public class Dependencies { "amazon-sdk-auth", "software{}amazon{}awssdk", "auth", - "amazon-auth", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -359,7 +447,6 @@ public class Dependencies { "amazon-sdk-regions", "software{}amazon{}awssdk", "regions", - "amazon-regions", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -375,7 +462,6 @@ public class Dependencies { "amazon-sdk-identity-spi", "software{}amazon{}awssdk", "identity-spi", - "amazon-identity-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -391,7 +477,6 @@ public class Dependencies { "amazon-sdk-http-client-spi", "software{}amazon{}awssdk", "http-client-spi", - "amazon-http-client-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -407,7 +492,6 @@ public class Dependencies { "amazon-sdk-protocol-core", "software{}amazon{}awssdk", "protocol-core", - "amazon-protocol-core", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -423,7 +507,6 @@ public class Dependencies { "amazon-sdk-aws-xml-protocol", "software{}amazon{}awssdk", "aws-xml-protocol", - "amazon-aws-xml-protocol", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -439,7 +522,6 @@ public class Dependencies { "amazon-sdk-json-utils", "software{}amazon{}awssdk", "json-utils", - "amazon-json-utils", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -455,7 +537,6 @@ public class Dependencies { "amazon-sdk-aws-core", "software{}amazon{}awssdk", "aws-core", - "amazon-aws-core", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -471,7 +552,6 @@ public class Dependencies { "amazon-sdk-utils", "software{}amazon{}awssdk", "utils", - "amazon-utils", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -487,7 +567,6 @@ public class Dependencies { "amazon-sdk-annotations", "software{}amazon{}awssdk", "annotations", - "amazon-annotations", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -503,7 +582,6 @@ public class Dependencies { "amazon-sdk-crt-core", "software{}amazon{}awssdk", "crt-core", - "amazon-crt-core", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -519,7 +597,6 @@ public class Dependencies { "amazon-sdk-checksums", "software{}amazon{}awssdk", "checksums", - "amazon-checksums", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -535,7 +612,6 @@ public class Dependencies { "amazon-sdk-eventstream", "software{}amazon{}eventstream", "eventstream", - "amazon-eventstream", List.of( Relocation.of("eventstream", "software{}amazon{}eventstream"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -546,7 +622,6 @@ public class Dependencies { "amazon-sdk-profiles", "software{}amazon{}awssdk", "profiles", - "amazon-profiles", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -562,7 +637,6 @@ public class Dependencies { "amazon-sdk-retries", "software{}amazon{}awssdk", "retries", - "amazon-retries", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -578,7 +652,6 @@ public class Dependencies { "amazon-sdk-endpoints-spi", "software{}amazon{}awssdk", "endpoints-spi", - "amazon-endpoints-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -594,7 +667,6 @@ public class Dependencies { "amazon-sdk-arns", "software{}amazon{}awssdk", "arns", - "amazon-arns", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -610,7 +682,6 @@ public class Dependencies { "amazon-sdk-aws-query-protocol", "software{}amazon{}awssdk", "aws-query-protocol", - "amazon-aws-query-protocol", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -626,7 +697,6 @@ public class Dependencies { "amazon-sdk-http-auth-aws", "software{}amazon{}awssdk", "http-auth-aws", - "amazon-http-auth-aws", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -642,7 +712,6 @@ public class Dependencies { "amazon-sdk-http-auth-spi", "software{}amazon{}awssdk", "http-auth-spi", - "amazon-http-auth-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -658,7 +727,6 @@ public class Dependencies { "amazon-sdk-http-auth", "software{}amazon{}awssdk", "http-auth", - "amazon-http-auth", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -674,7 +742,6 @@ public class Dependencies { "amazon-sdk-http-auth-aws-eventstream", "software{}amazon{}awssdk", "http-auth-aws-eventstream", - "amazon-http-auth-aws-eventstream", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -690,7 +757,6 @@ public class Dependencies { "amazon-sdk-checksums-spi", "software{}amazon{}awssdk", "checksums-spi", - "amazon-checksums-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -706,7 +772,6 @@ public class Dependencies { "amazon-sdk-retries-spi", "software{}amazon{}awssdk", "retries-spi", - "amazon-retries-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -722,7 +787,6 @@ public class Dependencies { "amazon-sdk-metrics-spi", "software{}amazon{}awssdk", "metrics-spi", - "amazon-metrics-spi", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") @@ -738,7 +802,6 @@ public class Dependencies { "amazon-sdk-third-party-jackson-core", "software{}amazon{}awssdk", "third-party-jackson-core", - "amazon-third-party-jackson-core", List.of( Relocation.of("awssdk", "software{}amazon{}awssdk"), Relocation.of("reactivestreams", "org{}reactivestreams") diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependency.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependency.java index 6a6c7e4b4..6dd449cb9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependency.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependency.java @@ -11,14 +11,12 @@ public class Dependency { private final String id; private final String groupId; private final String rawArtifactId; - private final String customArtifactID; private final List relocations; - public Dependency(String id, String groupId, String rawArtifactId, String customArtifactID, List relocations) { + public Dependency(String id, String groupId, String artifactId, List relocations) { this.id = id; this.groupId = groupId; - this.rawArtifactId = rawArtifactId; - this.customArtifactID = customArtifactID; + this.rawArtifactId = artifactId; this.relocations = relocations; } @@ -34,14 +32,14 @@ public class Dependency { return rawArtifactId; } - public String customArtifactID() { - return customArtifactID; - } - public List relocations() { return relocations; } + public String toLocalPath() { + return rewriteEscaping(groupId).replace(".", "/") + "/" + this.rawArtifactId + "/" + getVersion(); + } + private static final String MAVEN_FORMAT = "%s/%s/%s/%s-%s.jar"; public String mavenPath() { @@ -55,7 +53,7 @@ public class Dependency { } public String fileName(String classifier) { - String name = customArtifactID.toLowerCase(Locale.ENGLISH).replace('_', '-'); + String name = this.rawArtifactId.toLowerCase(Locale.ENGLISH).replace('_', '-'); String extra = classifier == null || classifier.isEmpty() ? "" : "-" + classifier; @@ -66,7 +64,7 @@ public class Dependency { return PluginProperties.getValue(id); } - private static String rewriteEscaping(String s) { + public static String rewriteEscaping(String s) { return s.replace("{}", "."); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyManagerImpl.java index 0f3f15376..a6ecf30a3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyManagerImpl.java @@ -5,16 +5,19 @@ import net.momirealms.craftengine.core.plugin.classpath.ClassPathAppender; import net.momirealms.craftengine.core.plugin.dependency.classloader.IsolatedClassLoader; import net.momirealms.craftengine.core.plugin.dependency.relocation.Relocation; import net.momirealms.craftengine.core.plugin.dependency.relocation.RelocationHandler; +import net.momirealms.craftengine.core.util.FileUtils; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.DirectoryStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; +import java.util.stream.Stream; public class DependencyManagerImpl implements DependencyManager { private final DependencyRegistry registry; @@ -112,18 +115,38 @@ public class DependencyManagerImpl implements DependencyManager { private Path downloadDependency(Dependency dependency) throws DependencyDownloadException { String fileName = dependency.fileName(null); - Path file = this.cacheDirectory.resolve(fileName); + Path file = this.cacheDirectory.resolve(dependency.toLocalPath()).resolve(fileName); // if the file already exists, don't attempt to re-download it. if (Files.exists(file)) { return file; } + // before downloading a newer version, delete those outdated files + Path versionFolder = file.getParent().getParent(); + if (Files.exists(versionFolder) && Files.isDirectory(versionFolder)) { + String version = dependency.getVersion(); + try (Stream dirStream = Files.list(versionFolder)) { + dirStream.filter(Files::isDirectory) + .filter(it -> !it.getFileName().toString().equals(version)) + .forEach(dir -> { + try { + FileUtils.deleteDirectory(dir); + plugin.logger().info("Cleaned up outdated dependency " + dir); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (IOException e) { + throw new RuntimeException("Failed to clean " + versionFolder, e); + } + } + DependencyDownloadException lastError = null; List repository = DependencyRepository.getByID("maven"); if (!repository.isEmpty()) { int i = 0; while (i < repository.size()) { try { - plugin.logger().info("Downloading dependency(" + fileName + ")[" + repository.get(i).getUrl() + dependency.mavenPath() + "]"); + plugin.logger().info("Downloading dependency " + repository.get(i).getUrl() + dependency.mavenPath()); repository.get(i).download(dependency, file); plugin.logger().info("Successfully downloaded " + fileName); return file; @@ -142,7 +165,7 @@ public class DependencyManagerImpl implements DependencyManager { return normalFile; } - Path remappedFile = this.cacheDirectory.resolve(dependency.fileName(DependencyRegistry.isGsonRelocated() ? "remapped-legacy" : "remapped")); + Path remappedFile = this.cacheDirectory.resolve(dependency.toLocalPath()).resolve(dependency.fileName(DependencyRegistry.isGsonRelocated() ? "remapped-legacy" : "remapped")); // if the remapped source exists already, just use that. if (Files.exists(remappedFile)) { @@ -159,6 +182,7 @@ public class DependencyManagerImpl implements DependencyManager { Path cacheDirectory = plugin.dataFolderPath().resolve("libs"); try { if (Files.exists(cacheDirectory) && (Files.isDirectory(cacheDirectory) || Files.isSymbolicLink(cacheDirectory))) { + cleanDirectoryJars(cacheDirectory); return cacheDirectory; } @@ -174,6 +198,16 @@ public class DependencyManagerImpl implements DependencyManager { return cacheDirectory; } + private static void cleanDirectoryJars(Path directory) throws IOException { + try (DirectoryStream stream = Files.newDirectoryStream(directory)) { + for (Path file : stream) { + if (Files.isRegularFile(file) && file.getFileName().toString().endsWith(".jar")) { + Files.delete(file); + } + } + } + } + @Override public void close() { IOException firstEx = null; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyRepository.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyRepository.java index b7f4c0d66..8fe0fa762 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyRepository.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/DependencyRepository.java @@ -82,6 +82,7 @@ public enum DependencyRepository { public void download(Dependency dependency, Path file) throws DependencyDownloadException { try { + Files.createDirectories(file.getParent()); Files.write(file, download(dependency)); } catch (IOException e) { throw new DependencyDownloadException(e); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/GuiElement.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/GuiElement.java index ae89d0b8e..cbecc4ba5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/GuiElement.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/GuiElement.java @@ -11,7 +11,7 @@ import java.util.function.Function; public interface GuiElement { - static GuiElement EMPTY = GuiElement.constant(null, (e, c) -> c.cancel()); + GuiElement EMPTY = GuiElement.constant(null, (e, c) -> c.cancel()); @Nullable Item item(); @@ -129,7 +129,7 @@ public interface GuiElement { @Override public Item item() { - return gui().itemAt(index).item(); + return gui().itemAt(this.index).item(); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/GuiManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/GuiManager.java index 8c4e25cb1..c23e6f7e8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/GuiManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/GuiManager.java @@ -1,8 +1,14 @@ package net.momirealms.craftengine.core.plugin.gui; +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.Manageable; public interface GuiManager extends Manageable { + void openInventory(Player player, GuiType guiType); + + void updateInventoryTitle(Player player, Component component); + Inventory createInventory(Gui gui, int size); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/GuiType.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/GuiType.java new file mode 100644 index 000000000..f004bfa2e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/GuiType.java @@ -0,0 +1,11 @@ +package net.momirealms.craftengine.core.plugin.gui; + +public enum GuiType { + ANVIL, + CARTOGRAPHY, + ENCHANTMENT, + GRINDSTONE, + LOOM, + SMITHING, + CRAFTING +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java index 024b294df..ca5695c38 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/gui/category/ItemBrowserManagerImpl.java @@ -1,5 +1,8 @@ package net.momirealms.craftengine.core.plugin.gui.category; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; @@ -24,9 +27,11 @@ import java.util.*; @SuppressWarnings("DuplicatedCode") public class ItemBrowserManagerImpl implements ItemBrowserManager { + private static final String SHIFT_LEFT = "SHIFT_LEFT"; + private static final String SHIFT_RIGHT = "SHIFT_RIGHT"; private static final Set MOVE_TO_OTHER_INV = Set.of("SHIFT_LEFT", "SHIFT_RIGHT"); - private static final Set LEFT_CLICK = Set.of("LEFT", "SHIFT_LEFT"); - private static final Set RIGHT_CLICK = Set.of("RIGHT", "SHIFT_RIGHT"); + private static final Set LEFT_CLICK = Set.of("LEFT", SHIFT_LEFT); + private static final Set RIGHT_CLICK = Set.of("RIGHT", SHIFT_RIGHT); private static final Set MIDDLE_CLICK = Set.of("MIDDLE"); private static final Set DOUBLE_CLICK = Set.of("DOUBLE_CLICK"); private final CraftEngine plugin; @@ -158,8 +163,8 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { this.plugin.logger().warn("Can't not find item " + it.icon() + " for category icon"); return null; } - item.customName(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(it.displayName(), ItemBuildContext.EMPTY.tagResolvers()))); - item.lore(it.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY.tagResolvers()))).toList()); + item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(it.displayName(), ItemBuildContext.EMPTY.tagResolvers()))); + item.loreJson(it.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY.tagResolvers()))).toList()); item.load(); return new ItemWithAction(item, (element, click) -> { click.cancel(); @@ -241,12 +246,18 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { if (subCategory == null) return null; Item item = this.plugin.itemManager().createWrappedItem(subCategory.icon(), player); if (item == null) { - this.plugin.logger().warn("Can't not find item " + subCategory.icon() + " for category icon"); - return null; + if (!subCategory.icon().equals(ItemKeys.AIR)) { + this.plugin.logger().warn("Can't find item " + subCategory.icon() + " as icon for sub category " + subCategoryId); + item = this.plugin.itemManager().createWrappedItem(ItemKeys.BARRIER, player); + item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(subCategory.displayName(), ItemBuildContext.EMPTY.tagResolvers()))); + item.loreJson(subCategory.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY.tagResolvers()))).toList()); + item.load(); + } + } else { + item.customNameJson(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(subCategory.displayName(), ItemBuildContext.EMPTY.tagResolvers()))); + item.loreJson(subCategory.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY.tagResolvers()))).toList()); + item.load(); } - item.customName(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(subCategory.displayName(), ItemBuildContext.EMPTY.tagResolvers()))); - item.lore(subCategory.displayLore().stream().map(lore -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(lore, ItemBuildContext.EMPTY.tagResolvers()))).toList()); - item.load(); return new ItemWithAction(item, (element, click) -> { click.cancel(); player.playSound(Constants.SOUND_CLICK_BUTTON); @@ -255,15 +266,40 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { } else { Key itemId = Key.of(it); Item item = this.plugin.itemManager().createWrappedItem(itemId, player); - if (item == null) return null; + boolean canGoFurther; + if (item == null) { + if (!itemId.equals(ItemKeys.AIR)) { + this.plugin.logger().warn("Can't find item " + itemId + " for category " + categoryId); + item = this.plugin.itemManager().createWrappedItem(ItemKeys.BARRIER, player); + item.customNameJson(AdventureHelper.componentToJson(Component.text(it).decoration(TextDecoration.ITALIC, TextDecoration.State.FALSE).color(NamedTextColor.RED))); + } + canGoFurther = false; + } else { + canGoFurther = true; + } return new ItemWithAction(item, (e, c) -> { c.cancel(); - if (MIDDLE_CLICK.contains(c.type()) && player.isCreativeMode() && player.hasPermission(GET_ITEM_PERMISSION) && c.itemOnCursor() == null) { - Item newItem = this.plugin.itemManager().createWrappedItem(e.item().id(), player); - newItem.count(newItem.maxStackSize()); - c.setItemOnCursor(newItem); + Item eItem = e.item(); + if (!canGoFurther) { return; } + if (player.isCreativeMode() && player.hasPermission(GET_ITEM_PERMISSION)) { + if (MIDDLE_CLICK.contains(c.type()) && c.itemOnCursor() == null) { + Item newItem = this.plugin.itemManager().createWrappedItem(eItem.id(), player); + newItem.count(newItem.maxStackSize()); + c.setItemOnCursor(newItem); + return; + } + if (SHIFT_LEFT.equals(c.type())) { + player.giveItem(this.plugin.itemManager().createWrappedItem(eItem.id(), player)); + return; + } else if (SHIFT_RIGHT.equals(c.type())) { + Item newItem = this.plugin.itemManager().createWrappedItem(eItem.id(), player); + newItem.count(newItem.maxStackSize()); + player.giveItem(newItem); + return; + } + } if (LEFT_CLICK.contains(c.type())) { List> inRecipes = this.plugin.recipeManager().recipeByResult(itemId); player.playSound(Constants.SOUND_CLICK_BUTTON); @@ -281,7 +317,7 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager { } }); } - }).filter(Objects::nonNull).toList(); + }).toList(); PagedGui.builder() .addIngredients(itemList) diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index cf9442fee..d8fe6ec0c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.core.plugin.network; import io.netty.channel.Channel; +import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.ApiStatus; @@ -25,6 +26,10 @@ public interface NetWorkUser { void sendPacket(Object packet, boolean immediately); + void sendCustomPayload(Key channel, byte[] data); + + void kick(Component message); + void simulatePacket(Object packet); @ApiStatus.Internal diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetworkManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetworkManager.java index 8d2b0c0ba..e24a4ba29 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetworkManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetworkManager.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.plugin.network; import io.netty.channel.Channel; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.plugin.Manageable; +import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -10,6 +11,8 @@ import java.util.List; public interface NetworkManager extends Manageable { String MOD_CHANNEL = "craftengine:payload"; String VIA_CHANNEL = "vv:proxy_details"; + Key MOD_CHANNEL_KEY = Key.of(MOD_CHANNEL); + Key VIA_CHANNEL_KEY = Key.of(VIA_CHANNEL); void setUser(Channel channel, NetWorkUser user); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/RegionExecutor.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/RegionExecutor.java index cbe7c938a..c77bbb0e1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/RegionExecutor.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/scheduler/RegionExecutor.java @@ -20,6 +20,10 @@ public interface RegionExecutor extends Executor { SchedulerTask runAsyncLater(Runnable runnable, long delay); + default SchedulerTask runLater(Runnable runnable, long delay) { + return runLater(runnable, delay, null, 0 ,0); + } + SchedulerTask runLater(Runnable runnable, long delay, W world, int x, int z); SchedulerTask runRepeating(Runnable runnable, long delay, long period, W world, int x, int z); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/GlobalVariableTag.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/GlobalVariableTag.java index 257a3a72e..f26585b0c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/GlobalVariableTag.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/GlobalVariableTag.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.plugin.text.minimessage; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.ParsingException; import net.kyori.adventure.text.minimessage.tag.Tag; import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; @@ -10,6 +11,9 @@ import net.momirealms.craftengine.core.util.AdventureHelper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; + public class GlobalVariableTag implements TagResolver { private final Context context; @@ -27,7 +31,18 @@ public class GlobalVariableTag implements TagResolver { if (value == null) { throw ctx.newException("Unknown variable: ", arguments); } - return Tag.selfClosingInserting(AdventureHelper.miniMessage().deserialize(value, this.context.tagResolvers())); + if (!arguments.hasNext()) { + return Tag.selfClosingInserting(AdventureHelper.miniMessage().deserialize(value, this.context.tagResolvers())); + } else { + List args = new ArrayList<>(); + while (arguments.hasNext()) { + args.add(AdventureHelper.miniMessage().deserialize(arguments.popOr("No index argument variable id provided").toString(), this.context.tagResolvers())); + } + return Tag.selfClosingInserting(AdventureHelper.miniMessage().deserialize(value, TagResolver.builder() + .resolvers(this.context.tagResolvers()) + .resolver(new IndexedArgumentTag(args)) + .build())); + } } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/Holder.java b/core/src/main/java/net/momirealms/craftengine/core/registry/Holder.java index 06e1c25f9..d4aae38ea 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/Holder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/Holder.java @@ -3,8 +3,8 @@ package net.momirealms.craftengine.core.registry; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceKey; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.Optional; diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/MappedRegistry.java b/core/src/main/java/net/momirealms/craftengine/core/registry/MappedRegistry.java index 2a20fd589..219c1d5b7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/MappedRegistry.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/MappedRegistry.java @@ -3,8 +3,8 @@ package net.momirealms.craftengine.core.registry; import com.google.common.collect.Maps; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceKey; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.*; public class MappedRegistry implements WritableRegistry { diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/Registry.java b/core/src/main/java/net/momirealms/craftengine/core/registry/Registry.java index 4540d9c19..970d47b15 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/Registry.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/Registry.java @@ -2,8 +2,8 @@ package net.momirealms.craftengine.core.registry; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.ResourceKey; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.Map; import java.util.Optional; import java.util.Set; diff --git a/core/src/main/java/net/momirealms/craftengine/core/sound/SoundSource.java b/core/src/main/java/net/momirealms/craftengine/core/sound/SoundSource.java new file mode 100644 index 000000000..e48a6c429 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/sound/SoundSource.java @@ -0,0 +1,27 @@ +package net.momirealms.craftengine.core.sound; + +import org.jetbrains.annotations.NotNull; + +public enum SoundSource { + MASTER("master"), + MUSIC("music"), + RECORD("record"), + WEATHER("weather"), + BLOCK("block"), + HOSTILE("hostile"), + NEUTRAL("neutral"), + PLAYER("player"), + AMBIENT("ambient"), + VOICE("voice"); + + private final String id; + + SoundSource(final String id) { + this.id = id; + } + + @NotNull + public String id() { + return this.id; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java index 0062e5bd6..c404f15a8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/AdventureHelper.java @@ -12,8 +12,12 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.json.JSONOptions; import net.kyori.adventure.text.serializer.json.legacyimpl.NBTLegacyHoverEventSerializer; import net.momirealms.sparrow.nbt.Tag; -import net.momirealms.sparrow.nbt.serializer.NBTComponentSerializer; -import net.momirealms.sparrow.nbt.serializer.NBTSerializerOptions; +import net.momirealms.sparrow.nbt.adventure.NBTComponentSerializer; +import net.momirealms.sparrow.nbt.adventure.NBTSerializerOptions; + +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Helper class for handling Adventure components and related functionalities. @@ -44,6 +48,10 @@ public class AdventureHelper { } this.gsonComponentSerializer = builder.build(); this.nbtComponentSerializer = NBTComponentSerializer.builder() + .editItem(item -> { + if (VersionHelper.isOrAbove1_20_5()) { + } + }) .editOptions((b) -> { if (!VersionHelper.isOrAbove1_21_5()) { b.value(NBTSerializerOptions.EMIT_CLICK_EVENT_TYPE, false); @@ -171,6 +179,14 @@ public class AdventureHelper { return getInstance().gsonComponentSerializer.deserializeFromTree(json); } + public static Component nbtToComponent(Tag tag) { + return getInstance().nbtComponentSerializer.deserialize(tag); + } + + public static Tag componentToNbt(Component component) { + return getInstance().nbtComponentSerializer.serialize(component); + } + /** * Converts a Component to a JSON string. * @@ -299,4 +315,13 @@ public class AdventureHelper { } return true; } + + public static Component replaceText(Component text, Map replacements) { + String patternString = replacements.keySet().stream() + .map(Pattern::quote) + .collect(Collectors.joining("|")); + return text.replaceText(builder -> + builder.match(Pattern.compile(patternString)) + .replacement((result, b) -> replacements.get(result.group()))); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java index 7e5e154da..208625927 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/CharacterUtils.java @@ -97,4 +97,38 @@ public class CharacterUtils { } return false; } + + public static String replaceBackslashWithSlash(String input) { + if (input == null || input.isEmpty()) { + return input; + } + StringBuilder sb = new StringBuilder(input.length()); + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + sb.append(c == '\\' ? '/' : c); + } + return sb.toString(); + } + + public static String replaceDoubleBackslashU(String input) { + if (input == null || input.isEmpty()) { + return input; + } + int length = input.length(); + StringBuilder sb = new StringBuilder(length); + int i = 0; + while (i < length) { + if (i + 2 < length + && input.charAt(i) == '\\' + && input.charAt(i + 1) == '\\' + && input.charAt(i + 2) == 'u') { + sb.append("\\u"); + i += 3; + } else { + sb.append(input.charAt(i)); + i++; + } + } + return sb.toString(); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Color.java b/core/src/main/java/net/momirealms/craftengine/core/util/Color.java new file mode 100644 index 000000000..24da61b00 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Color.java @@ -0,0 +1,48 @@ +package net.momirealms.craftengine.core.util; + +import java.util.Arrays; + +public class Color { + private static final byte DEFAULT_ALPHA = (byte) 255; + private final byte r; + private final byte g; + private final byte b; + private final byte a; + + public Color(byte r, byte g, byte b, byte a) { + this.b = b; + this.g = g; + this.r = r; + this.a = a; + } + + public Color(byte r, byte g, byte b) { + this(r, g, b, DEFAULT_ALPHA); + } + + public static Color fromString(String[] strings) { + if (strings.length == 3) { + return new Color(Byte.parseByte(strings[0]), Byte.parseByte(strings[1]), Byte.parseByte(strings[2])); + } else if (strings.length == 4) { + return new Color(Byte.parseByte(strings[0]), Byte.parseByte(strings[1]), Byte.parseByte(strings[2]), Byte.parseByte(strings[3])); + } else { + throw new IllegalArgumentException("Invalid color format: " + Arrays.toString(strings)); + } + } + + public byte a() { + return a; + } + + public byte b() { + return b; + } + + public byte g() { + return g; + } + + public byte r() { + return r; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Direction.java b/core/src/main/java/net/momirealms/craftengine/core/util/Direction.java index 87cd8df73..a48bfdc14 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Direction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Direction.java @@ -94,8 +94,8 @@ public enum Direction { } public static Direction[] orderedByNearest(AbstractEntity entity) { - float xRotation = entity.getXRot() * (float) (Math.PI / 180.0); - float yRotation = -entity.getYRot() * (float) (Math.PI / 180.0); + float xRotation = entity.xRot() * (float) (Math.PI / 180.0); + float yRotation = -entity.yRot() * (float) (Math.PI / 180.0); float sinX = (float) Math.sin(xRotation); float cosX = (float) Math.cos(xRotation); float sinY = (float) Math.sin(yRotation); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/FileUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/FileUtils.java index a4aec22f6..1d2a9a5b6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/FileUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/FileUtils.java @@ -1,18 +1,26 @@ package net.momirealms.craftengine.core.util; import java.io.IOException; -import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; +import java.util.Comparator; import java.util.List; +import java.util.stream.Stream; public class FileUtils { private FileUtils() {} + public static String getExtension(Path path) { + final String name = path.getFileName().toString(); + int index = name.lastIndexOf('.'); + if (index == -1) { + return ""; + } else { + return name.substring(index + 1); + } + } + public static String pathWithoutExtension(String path) { int i = path.lastIndexOf('.'); return i == -1 ? path : path.substring(0, i); @@ -22,56 +30,31 @@ public class FileUtils { Files.createDirectories(Files.exists(path) ? path.toRealPath() : path); } - public static List getYmlConfigsDeeply(Path configFolder) { - if (!Files.exists(configFolder)) return List.of(); - List validYaml = new ArrayList<>(); - Deque pathDeque = new ArrayDeque<>(); - pathDeque.push(configFolder); - while (!pathDeque.isEmpty()) { - Path path = pathDeque.pop(); - try (DirectoryStream stream = Files.newDirectoryStream(path)) { - for (Path subPath : stream) { - if (Files.isDirectory(subPath)) { - pathDeque.push(subPath); - } else if (Files.isRegularFile(subPath)) { - String pathString = subPath.toString(); - if (pathString.endsWith(".yml")) { - validYaml.add(subPath); + public static void deleteDirectory(Path folder) throws IOException { + if (!Files.exists(folder)) return; + try (Stream walk = Files.walk(folder)) { + walk.sorted(Comparator.reverseOrder()) + .forEach(path -> { + try { + Files.delete(path); + } catch (IOException ioException) { + throw new RuntimeException(ioException); } - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } + }); } - return validYaml; } - public static Pair, List> getConfigsDeeply(Path configFolder) { - if (!Files.exists(configFolder)) return Pair.of(List.of(), List.of()); - List validYaml = new ArrayList<>(); - List validJson = new ArrayList<>(); - Deque pathDeque = new ArrayDeque<>(); - pathDeque.push(configFolder); - while (!pathDeque.isEmpty()) { - Path path = pathDeque.pop(); - try (DirectoryStream stream = Files.newDirectoryStream(path)) { - for (Path subPath : stream) { - if (Files.isDirectory(subPath)) { - pathDeque.push(subPath); - } else if (Files.isRegularFile(subPath)) { - String pathString = subPath.toString(); - if (pathString.endsWith(".yml")) { - validYaml.add(subPath); - } else if (pathString.endsWith(".json")) { - validJson.add(subPath); - } - } - } - } catch (IOException e) { - throw new RuntimeException(e); - } + public static List getYmlConfigsDeeply(Path configFolder) { + if (!Files.exists(configFolder)) { + return List.of(); + } + try (Stream stream = Files.walk(configFolder)) { + return stream.parallel() + .filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".yml")) + .toList(); + } catch (IOException e) { + throw new RuntimeException("Failed to traverse directory: " + configFolder, e); } - return Pair.of(validYaml, validJson); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java b/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java index 8f12573cb..a21cc9692 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/FriendlyByteBuf.java @@ -48,6 +48,18 @@ public class FriendlyByteBuf extends ByteBuf { return BlockPos.of(buf.readLong()); } + public int readContainerId() { + return VersionHelper.isOrAbove1_21_2() ? this.readVarInt() : this.readUnsignedByte(); + } + + public void writeContainerId(int id) { + if (VersionHelper.isOrAbove1_21_2()) { + this.writeVarInt(id); + } else { + this.writeByte(id); + } + } + public List readStringList() { int i = this.readVarInt(); List list = new ArrayList<>(i); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java b/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java index 7dcb40ce1..6245e85d6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/GsonHelper.java @@ -8,6 +8,7 @@ import java.io.BufferedWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Map; public class GsonHelper { @@ -101,4 +102,16 @@ public class GsonHelper { throw new RuntimeException("Invalid JSON response: " + json, e); } } + + public static JsonElement combine(List jo) { + if (jo.size() == 1) { + return jo.get(0); + } else { + JsonArray ja = new JsonArray(); + for (JsonElement je : jo) { + ja.add(je); + } + return ja; + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/IntIdentityList.java b/core/src/main/java/net/momirealms/craftengine/core/util/IntIdentityList.java index c6f539f05..a60be053e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/IntIdentityList.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/IntIdentityList.java @@ -1,9 +1,9 @@ package net.momirealms.craftengine.core.util; +import it.unimi.dsi.fastutil.ints.IntArrayList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -13,7 +13,7 @@ public class IntIdentityList implements IndexedIterable { public IntIdentityList(int size) { this.size = size; - list = new ArrayList<>(size); + list = new IntArrayList(size); for (int i = 0; i < size; i++) { list.add(i); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Key.java b/core/src/main/java/net/momirealms/craftengine/core/util/Key.java index 994cf4b6c..6ec44fbc6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Key.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Key.java @@ -34,12 +34,14 @@ public record Key(String namespace, String value) { } public String[] decompose() { - return new String[] { namespace, value }; + return new String[] { this.namespace, this.value }; } @Override public int hashCode() { - return toString().hashCode(); + int result = this.namespace.hashCode(); + result = 31 * result + this.value.hashCode(); + return result; } @Override @@ -54,11 +56,11 @@ public record Key(String namespace, String value) { @Override public @NotNull String toString() { - return namespace + ":" + value; + return this.namespace + ":" + this.value; } public String asString() { - return namespace + ":" + value; + return this.namespace + ":" + this.value; } private static String[] decompose(String id, String namespace) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ListUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ListUtils.java new file mode 100644 index 000000000..7c8ef0ae0 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ListUtils.java @@ -0,0 +1,16 @@ +package net.momirealms.craftengine.core.util; + +import java.util.Collections; +import java.util.List; + +public final class ListUtils { + + private ListUtils() {} + + public static List compact(final List list) { + if (list.isEmpty()) return Collections.emptyList(); + if (list.size() == 1) return List.of(list.get(0)); + if (list.size() == 2) return List.of(list.get(0), list.get(1)); + return list; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java index 7886f0e0f..95e1621f7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MCUtils.java @@ -1,8 +1,8 @@ package net.momirealms.craftengine.core.util; import com.google.common.collect.Iterators; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.Iterator; import java.util.List; import java.util.function.Consumer; diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java index 3e9c4ba60..d3c9b178b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java @@ -23,6 +23,17 @@ public class MiscUtils { throw new IllegalArgumentException("Expected Map, got: " + (obj == null ? null : obj.getClass().getSimpleName())); } + @SuppressWarnings("unchecked") + public static List> getAsMapList(Object obj) { + if (obj == null) return List.of(); + if (obj instanceof List list) { + return (List>) list; + } else if (obj instanceof Map) { + return List.of((Map) obj); + } + throw new IllegalArgumentException("Expected MapList/Map, got: " + obj.getClass().getSimpleName()); + } + public static List getAsStringList(Object o) { List list = new ArrayList<>(); if (o instanceof List) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java index b81e56ca0..338ed70c0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java @@ -1,15 +1,25 @@ package net.momirealms.craftengine.core.util; +import com.mojang.datafixers.util.Either; import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; +import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.function.Supplier; public final class ResourceConfigUtils { private ResourceConfigUtils() {} + public static T getOrDefault(@Nullable O raw, Function function, T defaultValue) { + return raw != null ? function.apply(raw) : defaultValue; + } + public static T requireNonNullOrThrow(T obj, String node) { if (obj == null) throw new LocalizedResourceConfigException(node); @@ -36,6 +46,50 @@ public final class ResourceConfigUtils { return s; } + @SuppressWarnings("unchecked") + public static Either> parseConfigAsEither(Object config, Function, T> converter) { + if (config instanceof Map) { + return Either.left(converter.apply((Map) config)); + } else if (config instanceof List list) { + return switch (list.size()) { + case 0 -> Either.right(Collections.emptyList()); + case 1 -> Either.left(converter.apply((Map) list.get(0))); + case 2 -> Either.right(List.of(converter.apply((Map) list.get(0)), converter.apply((Map) list.get(1)))); + default -> { + List result = new ArrayList<>(list.size()); + for (Object o : list) { + result.add(converter.apply((Map) o)); + } + yield Either.right(result); + } + }; + } else { + return Either.right(Collections.emptyList()); + } + } + + @SuppressWarnings("unchecked") + public static List parseConfigAsList(Object config, Function, T> converter) { + if (config instanceof Map) { + return List.of(converter.apply((Map) config)); + } else if (config instanceof List list) { + return switch (list.size()) { + case 0 -> Collections.emptyList(); + case 1 -> List.of(converter.apply((Map) list.get(0))); + case 2 -> List.of(converter.apply((Map) list.get(0)), converter.apply((Map) list.get(1))); + default -> { + List result = new ArrayList<>(list.size()); + for (Object o : list) { + result.add(converter.apply((Map) o)); + } + yield result; + } + }; + } else { + return Collections.emptyList(); + } + } + public static Object get(Map arguments, String... keys) { for (String key : keys) { Object value = arguments.get(key); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/StringUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/StringUtils.java new file mode 100644 index 000000000..a0cbe3adb --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/StringUtils.java @@ -0,0 +1,28 @@ +package net.momirealms.craftengine.core.util; + +public final class StringUtils { + private StringUtils() {} + + public static String[] splitByDot(String s) { + if (s == null || s.isEmpty()) { + return new String[0]; + } + int dotCount = 0; + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == '.') { + dotCount++; + } + } + String[] result = new String[dotCount + 1]; + int start = 0; + int index = 0; + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == '.') { + result[index++] = s.substring(start, i); + start = i + 1; + } + } + result[index] = s.substring(start); + return result; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/TimeUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/TimeUtils.java new file mode 100644 index 000000000..072e4d74a --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/util/TimeUtils.java @@ -0,0 +1,62 @@ +package net.momirealms.craftengine.core.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class TimeUtils { + private TimeUtils() {} + + private static final Map TIME_UNITS = new HashMap<>(Map.of( + 'w', 604800000L, + 'd', 86400000L, + 'h', 3600000L, + 'm', 60000L, + 's', 1000L + )); + + private static final Pattern TIME_PATTERN = Pattern.compile("(\\d+)([dhmsDwHMSW])", Pattern.CASE_INSENSITIVE); + + public static long parseToMillis(String timeStr) { + if (timeStr == null || timeStr.trim().isEmpty()) { + throw new IllegalArgumentException("Time string cannot be null or empty"); + } + String trimmedStr = timeStr.trim(); + if (trimmedStr.matches("^\\d+$")) { + return Long.parseLong(trimmedStr); + } + long totalMillis = 0; + Matcher matcher = TIME_PATTERN.matcher(trimmedStr); + int lastEnd = 0; + while (matcher.find()) { + if (matcher.start() != lastEnd) { + throw new IllegalArgumentException("Invalid characters in time string: " + + trimmedStr.substring(lastEnd, matcher.start())); + } + lastEnd = matcher.end(); + long value = Long.parseLong(matcher.group(1)); + if (value < 0) { + throw new IllegalArgumentException("Time value cannot be negative: " + value); + } + char unit = Character.toLowerCase(matcher.group(2).charAt(0)); + if (!TIME_UNITS.containsKey(unit)) { + throw new IllegalArgumentException("Unknown time unit: " + unit); + } + try { + totalMillis = Math.addExact(totalMillis, Math.multiplyExact(value, TIME_UNITS.get(unit))); + } catch (ArithmeticException e) { + throw new IllegalArgumentException("Time value too large, would overflow long: " + timeStr); + } + } + + if (lastEnd != trimmedStr.length()) { + throw new IllegalArgumentException("Invalid time format at position " + lastEnd + + ": " + trimmedStr.substring(lastEnd)); + } + if (totalMillis < 0) { + throw new IllegalArgumentException("Resulting time cannot be negative"); + } + return totalMillis; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/World.java b/core/src/main/java/net/momirealms/craftengine/core/world/World.java index 565d93067..f49424a47 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/World.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/World.java @@ -1,9 +1,15 @@ package net.momirealms.craftengine.core.world; +import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.plugin.context.Context; import net.momirealms.craftengine.core.sound.SoundData; +import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.world.particle.ParticleData; +import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; import java.nio.file.Path; import java.util.UUID; @@ -21,6 +27,8 @@ public interface World { return getBlockAt(pos.x(), pos.y(), pos.z()); } + void setBlockAt(int x, int y, int z, BlockStateWrapper blockState, int flags); + String name(); Path directory(); @@ -31,11 +39,15 @@ public interface World { void dropExp(Position location, int amount); + void playSound(Position location, Key sound, float volume, float pitch, SoundSource source); + void playBlockSound(Position location, Key sound, float volume, float pitch); default void playBlockSound(Position location, SoundData data) { playBlockSound(location, data.id(), data.volume(), data.pitch()); } + void spawnParticle(Position location, Key particle, int count, double xOffset, double yOffset, double zOffset, double speed, @Nullable ParticleData extraData, @NotNull Context context); + long time(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/CompressionMethod.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/CompressionMethod.java index bf9f74ecd..cfe5268c0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/CompressionMethod.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/CompressionMethod.java @@ -5,8 +5,8 @@ import net.jpountz.lz4.LZ4BlockInputStream; import net.jpountz.lz4.LZ4BlockOutputStream; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.dependency.Dependencies; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/RegionFile.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/RegionFile.java index b2e8ad51b..d1feed960 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/RegionFile.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/storage/RegionFile.java @@ -5,8 +5,8 @@ import net.momirealms.craftengine.core.plugin.logger.PluginLogger; import net.momirealms.craftengine.core.world.ChunkPos; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.NBT; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.io.*; import java.nio.Buffer; import java.nio.ByteBuffer; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java b/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java index faf6b5bc2..d0a02fd09 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java @@ -3,8 +3,8 @@ package net.momirealms.craftengine.core.world.collision; import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.world.EntityHitResult; import net.momirealms.craftengine.core.world.Vec3d; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.Optional; public class AABB { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/BlockStateData.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/BlockStateData.java new file mode 100644 index 000000000..09a835011 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/BlockStateData.java @@ -0,0 +1,16 @@ +package net.momirealms.craftengine.core.world.particle; + +import net.momirealms.craftengine.core.block.BlockStateWrapper; +import net.momirealms.craftengine.core.block.DelayedInitBlockState; + +public class BlockStateData implements ParticleData { + private final DelayedInitBlockState blockState; + + public BlockStateData(DelayedInitBlockState blockState) { + this.blockState = blockState; + } + + public BlockStateWrapper blockState() { + return blockState.getState(); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/ColorData.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ColorData.java new file mode 100644 index 000000000..e2a2b295d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ColorData.java @@ -0,0 +1,15 @@ +package net.momirealms.craftengine.core.world.particle; + +import net.momirealms.craftengine.core.util.Color; + +public class ColorData implements ParticleData { + private final Color color; + + public ColorData(Color color) { + this.color = color; + } + + public Color color() { + return color; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/DustData.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/DustData.java new file mode 100644 index 000000000..a00b2cc72 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/DustData.java @@ -0,0 +1,21 @@ +package net.momirealms.craftengine.core.world.particle; + +import net.momirealms.craftengine.core.util.Color; + +public class DustData implements ParticleData { + private final Color color; + private final float size; + + public DustData(Color color, float size) { + this.color = color; + this.size = size; + } + + public Color color() { + return color; + } + + public float size() { + return size; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/DustTransitionData.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/DustTransitionData.java new file mode 100644 index 000000000..c4edbd9e2 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/DustTransitionData.java @@ -0,0 +1,27 @@ +package net.momirealms.craftengine.core.world.particle; + +import net.momirealms.craftengine.core.util.Color; + +public class DustTransitionData implements ParticleData { + private final Color from; + private final Color to; + private final float size; + + public DustTransitionData(Color from, Color to, float size) { + this.from = from; + this.to = to; + this.size = size; + } + + public Color from() { + return from; + } + + public Color to() { + return to; + } + + public float size() { + return size; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/ItemStackData.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ItemStackData.java new file mode 100644 index 000000000..17eaa1fb9 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ItemStackData.java @@ -0,0 +1,16 @@ +package net.momirealms.craftengine.core.world.particle; + +import net.momirealms.craftengine.core.item.DelayedInitItem; +import net.momirealms.craftengine.core.item.Item; + +public class ItemStackData implements ParticleData { + private final DelayedInitItem item; + + public ItemStackData(DelayedInitItem item) { + this.item = item; + } + + public Item item() { + return item.getItem(); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/JavaTypeData.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/JavaTypeData.java new file mode 100644 index 000000000..052e01f3d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/JavaTypeData.java @@ -0,0 +1,13 @@ +package net.momirealms.craftengine.core.world.particle; + +public class JavaTypeData implements ParticleData { + private final Object data; + + public JavaTypeData(Object data) { + this.data = data; + } + + public Object data() { + return data; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleData.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleData.java new file mode 100644 index 000000000..dfb22cfc3 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleData.java @@ -0,0 +1,4 @@ +package net.momirealms.craftengine.core.world.particle; + +public interface ParticleData { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleTypes.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleTypes.java new file mode 100644 index 000000000..7bd64ca92 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/ParticleTypes.java @@ -0,0 +1,22 @@ +package net.momirealms.craftengine.core.world.particle; + +import net.momirealms.craftengine.core.util.Key; + +public final class ParticleTypes { + private ParticleTypes() {} + + public static final Key ENTITY_EFFECT = Key.of("entity_effect"); + public static final Key DUST = Key.of("dust"); + public static final Key ITEM = Key.of("item"); + public static final Key BLOCK = Key.of("block"); + public static final Key FALLING_DUST = Key.of("falling_dust"); + public static final Key DUST_COLOR_TRANSITION = Key.of("dust_color_transition"); + public static final Key SCULK_CHARGE = Key.of("sculk_charge"); + public static final Key SHRIEK = Key.of("shriek"); + public static final Key TINTED_LEAVES = Key.of("tinted_leaves"); + public static final Key DUST_PILLAR = Key.of("dust_pillar"); + public static final Key BLOCK_CRUMBLE = Key.of("block_crumble"); + public static final Key BLOCK_MARKER = Key.of("block_marker"); + public static final Key TRAIL = Key.of("trail"); + public static final Key VIBRATION = Key.of("vibration"); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/TrailData.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/TrailData.java new file mode 100644 index 000000000..8ee59c346 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/TrailData.java @@ -0,0 +1,40 @@ +package net.momirealms.craftengine.core.world.particle; + +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; +import net.momirealms.craftengine.core.util.Color; + +public class TrailData implements ParticleData { + private final NumberProvider targetX; + private final NumberProvider targetY; + private final NumberProvider targetZ; + private final Color color; + private final NumberProvider duration; + + public TrailData(NumberProvider targetX, NumberProvider targetY, NumberProvider targetZ, Color color, NumberProvider duration) { + this.color = color; + this.duration = duration; + this.targetX = targetX; + this.targetY = targetY; + this.targetZ = targetZ; + } + + public Color color() { + return color; + } + + public NumberProvider duration() { + return duration; + } + + public NumberProvider targetX() { + return targetX; + } + + public NumberProvider targetY() { + return targetY; + } + + public NumberProvider targetZ() { + return targetZ; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/particle/VibrationData.java b/core/src/main/java/net/momirealms/craftengine/core/world/particle/VibrationData.java new file mode 100644 index 000000000..1f8da18a2 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/particle/VibrationData.java @@ -0,0 +1,33 @@ +package net.momirealms.craftengine.core.world.particle; + +import net.momirealms.craftengine.core.plugin.context.number.NumberProvider; + +public class VibrationData implements ParticleData { + private final NumberProvider destinationX; + private final NumberProvider destinationY; + private final NumberProvider destinationZ; + private final NumberProvider arrivalTime; + + public VibrationData(NumberProvider destinationX, NumberProvider destinationY, NumberProvider destinationZ, NumberProvider arrivalTime) { + this.arrivalTime = arrivalTime; + this.destinationX = destinationX; + this.destinationY = destinationY; + this.destinationZ = destinationZ; + } + + public NumberProvider arrivalTime() { + return arrivalTime; + } + + public NumberProvider destinationX() { + return destinationX; + } + + public NumberProvider destinationY() { + return destinationY; + } + + public NumberProvider destinationZ() { + return destinationZ; + } +} diff --git a/gradle.properties b/gradle.properties index d97675755..5aefedf21 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,9 +2,9 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.54 -config_version=32 -lang_version=12 +project_version=0.0.55 +config_version=34 +lang_version=14 project_group=net.momirealms latest_supported_version=1.21.5 @@ -21,7 +21,6 @@ asm_version=9.8 asm_commons_version=9.8 jar_relocator_version=1.7 adventure_bundle_version=4.21.0 -adventure_platform_version=4.4.0 cloud_core_version=2.0.0 cloud_services_version=2.0.0 cloud_brigadier_version=2.0.0-beta.10 @@ -40,18 +39,18 @@ geantyref_version=1.3.16 zstd_version=1.5.7-2 commons_io_version=2.18.0 commons_imaging_version=1.0.0-alpha6 -sparrow_nbt_version=0.7.3 +sparrow_nbt_version=0.9.0 sparrow_util_version=0.47 fastutil_version=8.5.15 -netty_version=4.1.119.Final +netty_version=4.1.121.Final joml_version=1.10.8 -datafixerupper_version=6.0.8 +datafixerupper_version=8.0.16 mojang_brigadier_version=1.0.18 byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 -anti_grief_version=0.15 -nms_helper_version=0.65.24 +anti_grief_version=0.17 +nms_helper_version=0.66.5 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c6f003026..3c44eb1b6 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.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/readme/README_zh-CN.md b/readme/README_zh-CN.md index 10a25b6c5..ea29545c3 100644 --- a/readme/README_zh-CN.md +++ b/readme/README_zh-CN.md @@ -9,6 +9,9 @@ Gitbook + + 询问DeepWiki + SCC数量标识 @@ -25,37 +28,39 @@ CraftEngine 重新定义了 Minecraft 插件架构,作为下一代自定义内容实现的解决方案。通过 JVM 级别的注入,它提供了前所未有的性能、稳定性和可扩展性。该框架提供了一个代码优先的 API,用于注册原生集成的方块行为和物品交互逻辑。 ## 构建 +只要您安装了 JDK21,即可免费获取完整版 JAR。请按照以下指南进行构建。 ### 🐚 命令行 -1. 安装 JDK 21。 -2. 打开终端并切换到项目文件夹。 -3. 执行 `./gradlew build`,构建产物将生成在 `/target` 文件夹中。 ++ 打开终端并切换到项目文件夹。 ++ 执行 `./gradlew build`,构建产物将生成在 `/target` 文件夹中。 ### 💻 IDE -1. 导入项目并执行 Gradle 构建操作。 -2. 构建产物将生成在 `/target` 文件夹中。 ++ 导入项目并执行 Gradle 构建操作。 ++ 构建产物将生成在 `/target` 文件夹中。 ## 安装 ### 💻 环境要求 -1. 确保您正在运行 [Paper](https://papermc.io/)(或其分支)1.20.1+ 服务器。CraftEngine 不支持 Spigot,且未来也不太可能支持。 -2. 使用 JDK 21 来运行服务器。 ++ 确保您正在运行 [Paper](https://papermc.io/)(或其分支)1.20.1+ 服务器。CraftEngine 不支持 Spigot,且未来也不太可能支持。 ++ 使用 JDK 21 来运行服务器。我相信这对你来说很简单。 ### 🔍 安装方式 CraftEngine 提供了两种安装模式:标准安装和 Mod 模式。标准安装与传统插件安装方式相同,即将插件放入插件文件夹中。下面我们将详细介绍 Mod 模式的安装步骤。 -### 🔧 安装服务器 Mod -1. 下载最新的 [ignite.jar](https://github.com/vectrix-space/ignite/releases) 到您的服务器根目录。 -2. 选择以下任一操作: - - 将您的服务器 JAR 文件重命名为 `paper.jar` - - 添加启动参数:`-Dignite.locator=paper -Dignite.paper.jar=./paper-xxx.jar` - - 示例:`java -Dignite.locator=paper -Dignite.paper.jar=./paper-1.21.4-164.jar -jar ignite.jar` -3. 启动服务器以生成 `/mods` 目录。 -4. 将最新的 [mod.jar](https://github.com/Xiao-MoMi/craft-engine/releases) 放入 `/mods` 文件夹。 -5. 将插件的 JAR 文件放入 `/plugins` 文件夹进行安装。 -6. 执行两次重启: - 1. 第一次重启用于文件初始化。 - 2. 第二次重启以激活所有组件。 +### 🔧 安装服务端模组 +- 下载最新的 [ignite.jar](https://github.com/vectrix-space/ignite/releases) 到服务器根目录 +- 可以: + - 将服务器 JAR 重命名为 `paper.jar` 并修改启动命令为: `-jar ignite.jar` +- 或者: + - 使用高级启动参数 + - 对于 paper 或 folia: `-Dignite.locator=paper -Dignite.paper.jar=./server-xxx.jar -jar ignite.jar` + - 对于特殊 Paper 分支 `-Dignite.locator=paper -Dignite.paper.target=cn.dreeam.leaper.QuantumLeaper -Dignite.paper.jar=./leaf-xxx.jar -jar ignite.jar` +- 启动服务器生成 `/mods` 目录 +- 将最新的 [mod.jar](https://github.com/Xiao-MoMi/craft-engine/releases) 放入 `/mods` 目录 +- 将插件 JAR 放入 `/plugins` 目录 +- 最后执行两次重启: + 1. 首次重启以进行文件初始化 + 2. 最后重启以激活所有组件 ## 技术概述 @@ -63,19 +68,19 @@ CraftEngine 提供了两种安装模式:标准安装和 Mod 模式。标准安 CraftEngine 使用运行时字节码生成技术,在服务器原生级别注册自定义方块,并结合客户端数据包修改以实现视觉同步。此架构提供了以下功能: 🧱 自定义原生方块 -- 动态注册方块,完全可控。 -- 物理属性:硬度、引燃几率、亮度等所有标准属性。 -- 自定义行为:通过 API 实现树苗、作物、下落的方块等。 -- 原版兼容性:完全保留原版方块机制(例如音符盒、绊线)。 ++ 动态注册方块,完全可控。 ++ 物理属性:硬度、引燃几率、亮度等所有标准属性。 ++ 自定义行为:通过 API 实现树苗、作物、下落的方块等。 ++ 原版兼容性:完全保留原版方块机制(例如音符盒、绊线)。 📦 数据包集成 -- 定义自定义矿脉。 -- 生成自定义树木。 -- 配置自定义地形生成。 ++ 定义自定义矿脉。 ++ 生成自定义树木。 ++ 配置自定义地形生成。 ⚡ 性能优势 -- 比传统的 Bukkit 事件监听器更快、更稳定。 -- 策略性代码注入以最小化开销。 ++ 比传统的 Bukkit 事件监听器更快、更稳定。 ++ 策略性代码注入以最小化开销。 ### 🥘 配方 CraftEngine 通过底层注入实现完全可定制的合成系统。与传统插件不同,它在处理 NBT 修改时不会失效,确保配方结果仅与唯一的物品标识符绑定。 @@ -89,34 +94,47 @@ CraftEngine 通过底层注入实现完全可定制的合成系统。与传统 ### 🛠️ 模型 该插件通过配置实现模型继承和纹理覆盖,同时支持从 1.21.4 版本开始的[所有物品模型](https://misode.github.io/assets/item/)。它包含一个版本迁移系统,可以自动将 1.21.4+ 的物品模型降级为旧格式,以实现最大向后兼容性。 +### 您必须了解的破坏性变更及可能与其他插件的不兼容性 +- CraftEngine 注入 PalettedContainer 以确保插件方块数据的高效存储和同步。这可能会导致与一些直接修改调色盘的插件冲突。当使用 Spark 分析服务器性能时,调色盘操作开销将在分析结果中划归给 CraftEngine 插件。 +- CraftEngine 注入 FurnaceBlockEntity 以修改其配方获取逻辑。 +- CraftEngine 使用真服务端侧方块,任何依赖 Bukkit 的 Material 类的插件都将无法正确识别自定义方块类型。正确的方法是使用替代方案,如 BlockState#getBlock (mojmap) 而不是 Material 类。**(译者注: 对于不想直接使用nms的项目可以使用org.bukkit.block.Block#getBlockData来正确获取方块)** +- CraftEngine 通过继承某些 Minecraft 实体实现 0-tick 碰撞实体,确保硬碰撞在服务端侧正常工作(例如,让猪站在椅子上)。然而,一些反作弊插件在检测玩家移动时没有正确检查实体的 AABB(轴对齐包围盒),这可能导致误报。**(译者注: 还有可能是因为没有正确检查玩家接触的实体是否有硬碰撞箱导致的误报)** +- CraftEngine 的自定义配方处理可能与其他配方管理插件不完全兼容。 + ## 灵感来源 CraftEngine 从以下开源项目中汲取了灵感: -- [Paper](https://github.com/PaperMC/Paper) -- [LuckPerms](https://github.com/LuckPerms/LuckPerms) -- [Fabric](https://github.com/FabricMC/fabric) -- [packetevents](https://github.com/retrooper/packetevents) -- [NBT](https://github.com/Querz/NBT) -- [DataFixerUpper](https://github.com/Mojang/DataFixerUpper) -- [ViaVersion](https://github.com/ViaVersion/ViaVersion) ++ [Paper](https://github.com/PaperMC/Paper) ++ [LuckPerms](https://github.com/LuckPerms/LuckPerms) ++ [Fabric](https://github.com/FabricMC/fabric) ++ [packetevents](https://github.com/retrooper/packetevents) ++ [DataFixerUpper](https://github.com/Mojang/DataFixerUpper) ++ [ViaVersion](https://github.com/ViaVersion/ViaVersion) ### 核心依赖 CraftEngine 的实现依赖于以下基础库: -- [ignite](https://github.com/vectrix-space/ignite) -- [cloud-minecraft](https://github.com/Incendo/cloud-minecraft) -- [rtag](https://github.com/saicone/rtag) -- [adventure](https://github.com/KyoriPowered/adventure) -- [byte-buddy](https://github.com/raphw/byte-buddy) ++ [ignite](https://github.com/vectrix-space/ignite) ++ [cloud-minecraft](https://github.com/Incendo/cloud-minecraft) ++ [rtag](https://github.com/saicone/rtag) ++ [adventure](https://github.com/KyoriPowered/adventure) ++ [byte-buddy](https://github.com/raphw/byte-buddy) ## 如何贡献 ### 🔌 新功能与 Bug 修复 如果您提交的 PR 是关于 Bug 修复的,它很可能会被合并。如果您想提交新功能,请提前在 [Discord](https://discord.com/invite/WVKdaUPR3S) 上联系我。 +您贡献的代码将遵循 GPLv3 许可证开源。如果您希望使用更宽松的许可证(例如 MIT),可以在文件顶部明确注明。 ### 🌍 翻译 1. 克隆此仓库。 2. 在 `/bukkit/loader/src/main/resources/translations` 中创建一个新的语言文件。 3. 完成后,提交 **pull request** 以供审核。我们感谢您的贡献! +## Differences Between Versions +| 版本 | 官方支持 | 最大玩家数 | 开发版本 | +|-----|------|-------|------| +| 社区版 | ❌ 无 | 20 | ❌ 无 | +| 付费版 | ✔️ 有 | 无限制 | ✔️ 有 | + ### 💖 支持开发者 如果您喜欢使用 CraftEngine,请考虑支持开发者! diff --git a/readme/README_zh-TW.md b/readme/README_zh-TW.md index d4f4281f7..a9c5470fb 100644 --- a/readme/README_zh-TW.md +++ b/readme/README_zh-TW.md @@ -129,8 +129,6 @@ CraftEngine 的實現依賴於以下基礎庫: ```kotlin repositories { maven("https://repo.momirealms.net/releases/") - // 如果你的網路環境受限可以嘗試下面的存儲庫位址 - // maven("https://repo-momi.gtemc.cn/releases/") } ``` ```kotlin diff --git a/server-mod/v1_20_1/build.gradle.kts b/server-mod/v1_20_1/build.gradle.kts index 0266dd510..c6e5910ba 100644 --- a/server-mod/v1_20_1/build.gradle.kts +++ b/server-mod/v1_20_1/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("java-library") id("com.gradleup.shadow") version "9.0.0-beta13" - id("io.papermc.paperweight.userdev") version "2.0.0-beta.16" + id("io.papermc.paperweight.userdev") version "2.0.0-beta.17" } repositories { diff --git a/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java b/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java index 52214c52a..ccba3bc24 100644 --- a/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java +++ b/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java @@ -67,6 +67,26 @@ public class CraftEngineBlock extends Block } } + @Override + public @NotNull VoxelShape getCollisionShape(@NotNull BlockState state, @NotNull BlockGetter level, @NotNull BlockPos pos, @NotNull CollisionContext context) { + try { + return (VoxelShape) this.shapeHolder.value().getCollisionShape(this, new Object[]{state, level, pos, context}); + } catch (Exception e) { + LOGGER.error(e); + return super.getCollisionShape(state, level, pos, context); + } + } + + @Override + public @NotNull VoxelShape getBlockSupportShape(@NotNull BlockState state, @NotNull BlockGetter level, @NotNull BlockPos pos) { + try { + return (VoxelShape) this.shapeHolder.value().getSupportShape(this, new Object[]{state, level, pos}); + } catch (Exception e) { + LOGGER.error(e); + return super.getBlockSupportShape(state, level, pos); + } + } + @Override public @NotNull BlockState rotate(@NotNull BlockState state, @NotNull Rotation rotation) { try { @@ -225,4 +245,16 @@ public class CraftEngineBlock extends Block LOGGER.error(e); } } + + @Override + public void neighborChanged(@NotNull BlockState state, @NotNull Level world, @NotNull BlockPos pos, @NotNull Block sourceBlock, @NotNull BlockPos sourcePos, boolean notify) { + try { + this.behaviorHolder.value().neighborChanged(this, new Object[]{state, world, pos, sourceBlock, sourcePos, notify}, () -> { + super.neighborChanged(state, world, pos, sourceBlock, sourcePos, notify); + return null; + }); + } catch (Exception e) { + LOGGER.error(e); + } + } } diff --git a/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java b/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java index 6ebc977a6..8766f8568 100644 --- a/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java +++ b/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java @@ -17,4 +17,14 @@ public class StoneBlockShape implements BlockShape { public Object getShape(Object thisObj, Object[] args) { return rawBlockState.getShape((BlockGetter) args[1], (BlockPos) args[2], (CollisionContext) args[3]); } + + @Override + public Object getCollisionShape(Object thisObj, Object[] args) { + return rawBlockState.getCollisionShape((BlockGetter) args[1], (BlockPos) args[2], (CollisionContext) args[3]); + } + + @Override + public Object getSupportShape(Object thisObj, Object[] args) { + return rawBlockState.getBlockSupportShape((BlockGetter) args[1], (BlockPos) args[2]); + } } diff --git a/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/item/CustomStreamCodec.java b/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/item/CustomStreamCodec.java deleted file mode 100644 index 4fae02230..000000000 --- a/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/item/CustomStreamCodec.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.momirealms.craftengine.mod.item; - -import net.minecraft.world.item.ItemStack; -import org.jetbrains.annotations.NotNull; - -import java.util.function.Function; - -public class CustomStreamCodec { - public static Function clientBoundDataProcessor; - public static Function serverBoundDataProcessor; - - public static @NotNull ItemStack s2c(@NotNull ItemStack itemStack) { - if (clientBoundDataProcessor != null) { - itemStack = clientBoundDataProcessor.apply(itemStack); - } - return itemStack; - } - - public static @NotNull ItemStack c2s(@NotNull ItemStack itemStack) { - if (serverBoundDataProcessor != null) { - itemStack = serverBoundDataProcessor.apply(itemStack); - } - return itemStack; - } -} \ No newline at end of file diff --git a/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/mixin/FriendlyByteBufMixin.java b/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/mixin/FriendlyByteBufMixin.java deleted file mode 100644 index 2e3fae0e7..000000000 --- a/server-mod/v1_20_1/src/main/java/net/momirealms/craftengine/mod/mixin/FriendlyByteBufMixin.java +++ /dev/null @@ -1,30 +0,0 @@ -package net.momirealms.craftengine.mod.mixin; - -import com.llamalad7.mixinextras.injector.ModifyReturnValue; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.world.item.ItemStack; -import net.momirealms.craftengine.mod.item.CustomStreamCodec; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyVariable; - -@Mixin(value = FriendlyByteBuf.class) -public class FriendlyByteBufMixin { - - @ModifyVariable( - method = "a(Lnet/minecraft/world/item/ItemStack;)Lnet/minecraft/network/PacketDataSerializer;", - at = @At("HEAD"), - argsOnly = true - ) - private ItemStack modifyWriteItemParam(ItemStack stack) { - return stack.isEmpty() ? stack : CustomStreamCodec.s2c(stack); - } - - @ModifyReturnValue( - method = "r()Lnet/minecraft/world/item/ItemStack;", - at = @At("RETURN") - ) - private ItemStack modifyReadItemStack(ItemStack original) { - return original.isEmpty() ? original : CustomStreamCodec.c2s(original); - } -} diff --git a/server-mod/v1_20_1/src/main/resources/mixins.craftengine.json b/server-mod/v1_20_1/src/main/resources/mixins.craftengine.json index b6396bab4..69e19f9d0 100644 --- a/server-mod/v1_20_1/src/main/resources/mixins.craftengine.json +++ b/server-mod/v1_20_1/src/main/resources/mixins.craftengine.json @@ -6,7 +6,6 @@ "target": "@env(DEFAULT)", "compatibilityLevel": "JAVA_21", "server": [ - "BlocksMixin", - "FriendlyByteBufMixin" + "BlocksMixin" ] } diff --git a/server-mod/v1_20_5/build.gradle.kts b/server-mod/v1_20_5/build.gradle.kts index db31453d2..bcc393e9d 100644 --- a/server-mod/v1_20_5/build.gradle.kts +++ b/server-mod/v1_20_5/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("java-library") id("com.gradleup.shadow") version "9.0.0-beta13" - id("io.papermc.paperweight.userdev") version "2.0.0-beta.16" + id("io.papermc.paperweight.userdev") version "2.0.0-beta.17" } repositories { @@ -44,7 +44,7 @@ artifacts { tasks { shadowJar { archiveClassifier = "" - archiveFileName = "${rootProject.name}-ignite-mod-${rootProject.properties["project_version"]}+mc1.20.5-1.21.4-mojmap.jar" + archiveFileName = "${rootProject.name}-ignite-mod-${rootProject.properties["project_version"]}+mc1.21.2-1.21.4-mojmap.jar" destinationDirectory.set(file("$rootDir/target")) } } diff --git a/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java index 4e37a60ef..50381f67b 100644 --- a/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java +++ b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java @@ -12,6 +12,7 @@ import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.ScheduledTickAccess; import net.minecraft.world.level.block.*; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.redstone.Orientation; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; import net.momirealms.craftengine.mod.CraftEnginePlugin; @@ -21,6 +22,7 @@ import net.momirealms.craftengine.shared.block.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class CraftEngineBlock extends Block implements BehaviorHolder, ShapeHolder, NoteBlockIndicator, Fallable, BonemealableBlock { @@ -66,6 +68,26 @@ public class CraftEngineBlock extends Block } } + @Override + public @NotNull VoxelShape getCollisionShape(@NotNull BlockState state, @NotNull BlockGetter level, @NotNull BlockPos pos, @NotNull CollisionContext context) { + try { + return (VoxelShape) this.shapeHolder.value().getCollisionShape(this, new Object[]{state, level, pos, context}); + } catch (Exception e) { + LOGGER.error(e); + return super.getCollisionShape(state, level, pos, context); + } + } + + @Override + public @NotNull VoxelShape getBlockSupportShape(@NotNull BlockState state, @NotNull BlockGetter level, @NotNull BlockPos pos) { + try { + return (VoxelShape) this.shapeHolder.value().getSupportShape(this, new Object[]{state, level, pos}); + } catch (Exception e) { + LOGGER.error(e); + return super.getBlockSupportShape(state, level, pos); + } + } + @Override protected @NotNull BlockState rotate(@NotNull BlockState state, @NotNull Rotation rotation) { try { @@ -226,4 +248,16 @@ public class CraftEngineBlock extends Block LOGGER.error(e); } } + + @Override + protected void neighborChanged(@NotNull BlockState state, @NotNull Level level, @NotNull BlockPos pos, @NotNull Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) { + try { + this.behaviorHolder.value().neighborChanged(this, new Object[]{state, level, pos, neighborBlock, orientation, movedByPiston}, () -> { + super.neighborChanged(state, level, pos, neighborBlock, orientation, movedByPiston); + return null; + }); + } catch (Exception e) { + LOGGER.error(e); + } + } } diff --git a/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java index 6ebc977a6..8766f8568 100644 --- a/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java +++ b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java @@ -17,4 +17,14 @@ public class StoneBlockShape implements BlockShape { public Object getShape(Object thisObj, Object[] args) { return rawBlockState.getShape((BlockGetter) args[1], (BlockPos) args[2], (CollisionContext) args[3]); } + + @Override + public Object getCollisionShape(Object thisObj, Object[] args) { + return rawBlockState.getCollisionShape((BlockGetter) args[1], (BlockPos) args[2], (CollisionContext) args[3]); + } + + @Override + public Object getSupportShape(Object thisObj, Object[] args) { + return rawBlockState.getBlockSupportShape((BlockGetter) args[1], (BlockPos) args[2]); + } } diff --git a/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/item/CustomStreamCodec.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/item/CustomStreamCodec.java deleted file mode 100644 index 2d1a47861..000000000 --- a/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/item/CustomStreamCodec.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.momirealms.craftengine.mod.item; - -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.StreamCodec; -import net.minecraft.world.item.ItemStack; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; -import java.util.function.Function; - -public class CustomStreamCodec implements StreamCodec { - public static Function clientBoundDataProcessor; - public static Function serverBoundDataProcessor; - - private final StreamCodec original; - - public CustomStreamCodec(StreamCodec original) { - this.original = Objects.requireNonNull(original); - } - - @Override - public @NotNull ItemStack decode(@NotNull RegistryFriendlyByteBuf buffer) { - ItemStack itemStack = this.original.decode(buffer); - if (!itemStack.isEmpty()) { - if (serverBoundDataProcessor != null) { - itemStack = serverBoundDataProcessor.apply(itemStack); - } - } - return itemStack; - } - - @Override - public void encode(@NotNull RegistryFriendlyByteBuf buffer, @NotNull ItemStack value) { - if (!value.isEmpty()) { - if (clientBoundDataProcessor != null) { - value = clientBoundDataProcessor.apply(value); - } - } - this.original.encode(buffer, value); - } -} \ No newline at end of file diff --git a/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/mixin/ItemStackMixin.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/mixin/ItemStackMixin.java deleted file mode 100644 index 726e9981d..000000000 --- a/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/mixin/ItemStackMixin.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.momirealms.craftengine.mod.mixin; - -import net.minecraft.core.NonNullList; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.ByteBufCodecs; -import net.minecraft.network.codec.StreamCodec; -import net.minecraft.world.item.ItemStack; -import net.momirealms.craftengine.mod.item.CustomStreamCodec; -import org.objectweb.asm.Opcodes; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.List; - -@Mixin(ItemStack.class) -public abstract class ItemStackMixin { - @Shadow(remap = false) - @Mutable - public static StreamCodec OPTIONAL_STREAM_CODEC; - @Shadow(remap = false) - @Mutable - public static StreamCodec> OPTIONAL_LIST_STREAM_CODEC; - - private static StreamCodec ORIGINAL_OPTIONAL_STREAM_CODEC; - - @Inject( - method = "", - at = @At( - value = "FIELD", - target = "Lnet/minecraft/world/item/ItemStack;OPTIONAL_STREAM_CODEC:Lnet/minecraft/network/codec/StreamCodec;", - opcode = Opcodes.PUTSTATIC, - shift = At.Shift.AFTER - ) - ) - private static void captureOriginalAfterAssignment(CallbackInfo ci) { - ORIGINAL_OPTIONAL_STREAM_CODEC = OPTIONAL_STREAM_CODEC; - } - - @Inject(method = "", at = @At("RETURN")) - private static void replaceStreamCodec(CallbackInfo ci) { - OPTIONAL_STREAM_CODEC = new CustomStreamCodec(ORIGINAL_OPTIONAL_STREAM_CODEC); - OPTIONAL_LIST_STREAM_CODEC = OPTIONAL_STREAM_CODEC.apply(ByteBufCodecs.collection(NonNullList::createWithCapacity)); - } -} diff --git a/server-mod/v1_20_5/src/main/resources/mixins.craftengine.json b/server-mod/v1_20_5/src/main/resources/mixins.craftengine.json index 288b6501f..69e19f9d0 100644 --- a/server-mod/v1_20_5/src/main/resources/mixins.craftengine.json +++ b/server-mod/v1_20_5/src/main/resources/mixins.craftengine.json @@ -6,7 +6,6 @@ "target": "@env(DEFAULT)", "compatibilityLevel": "JAVA_21", "server": [ - "BlocksMixin", - "ItemStackMixin" + "BlocksMixin" ] } diff --git a/server-mod/v1_21_5/build.gradle.kts b/server-mod/v1_21_5/build.gradle.kts index 617f52ce2..ef78707c3 100644 --- a/server-mod/v1_21_5/build.gradle.kts +++ b/server-mod/v1_21_5/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("java-library") id("com.gradleup.shadow") version "9.0.0-beta13" - id("io.papermc.paperweight.userdev") version "2.0.0-beta.16" + id("io.papermc.paperweight.userdev") version "2.0.0-beta.17" } repositories { diff --git a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java index 212e34087..218248c70 100644 --- a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java @@ -12,6 +12,7 @@ import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.ScheduledTickAccess; import net.minecraft.world.level.block.*; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.redstone.Orientation; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; import net.momirealms.craftengine.mod.CraftEnginePlugin; @@ -21,6 +22,7 @@ import net.momirealms.craftengine.shared.block.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class CraftEngineBlock extends Block implements BehaviorHolder, ShapeHolder, NoteBlockIndicator, Fallable, BonemealableBlock { @@ -66,6 +68,26 @@ public class CraftEngineBlock extends Block } } + @Override + public @NotNull VoxelShape getCollisionShape(@NotNull BlockState state, @NotNull BlockGetter level, @NotNull BlockPos pos, @NotNull CollisionContext context) { + try { + return (VoxelShape) this.shapeHolder.value().getCollisionShape(this, new Object[]{state, level, pos, context}); + } catch (Exception e) { + LOGGER.error(e); + return super.getCollisionShape(state, level, pos, context); + } + } + + @Override + public @NotNull VoxelShape getBlockSupportShape(@NotNull BlockState state, @NotNull BlockGetter level, @NotNull BlockPos pos) { + try { + return (VoxelShape) this.shapeHolder.value().getSupportShape(this, new Object[]{state, level, pos}); + } catch (Exception e) { + LOGGER.error(e); + return super.getBlockSupportShape(state, level, pos); + } + } + @Override protected @NotNull BlockState rotate(@NotNull BlockState state, @NotNull Rotation rotation) { try { @@ -226,4 +248,16 @@ public class CraftEngineBlock extends Block LOGGER.error(e); } } + + @Override + protected void neighborChanged(@NotNull BlockState state, @NotNull Level level, @NotNull BlockPos pos, @NotNull Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) { + try { + this.behaviorHolder.value().neighborChanged(this, new Object[]{state, level, pos, neighborBlock, orientation, movedByPiston}, () -> { + super.neighborChanged(state, level, pos, neighborBlock, orientation, movedByPiston); + return null; + }); + } catch (Exception e) { + LOGGER.error(e); + } + } } diff --git a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java index 6ebc977a6..8766f8568 100644 --- a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java @@ -17,4 +17,14 @@ public class StoneBlockShape implements BlockShape { public Object getShape(Object thisObj, Object[] args) { return rawBlockState.getShape((BlockGetter) args[1], (BlockPos) args[2], (CollisionContext) args[3]); } + + @Override + public Object getCollisionShape(Object thisObj, Object[] args) { + return rawBlockState.getCollisionShape((BlockGetter) args[1], (BlockPos) args[2], (CollisionContext) args[3]); + } + + @Override + public Object getSupportShape(Object thisObj, Object[] args) { + return rawBlockState.getBlockSupportShape((BlockGetter) args[1], (BlockPos) args[2]); + } } diff --git a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/item/CustomStreamCodec.java b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/item/CustomStreamCodec.java deleted file mode 100644 index 2d1a47861..000000000 --- a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/item/CustomStreamCodec.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.momirealms.craftengine.mod.item; - -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.StreamCodec; -import net.minecraft.world.item.ItemStack; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; -import java.util.function.Function; - -public class CustomStreamCodec implements StreamCodec { - public static Function clientBoundDataProcessor; - public static Function serverBoundDataProcessor; - - private final StreamCodec original; - - public CustomStreamCodec(StreamCodec original) { - this.original = Objects.requireNonNull(original); - } - - @Override - public @NotNull ItemStack decode(@NotNull RegistryFriendlyByteBuf buffer) { - ItemStack itemStack = this.original.decode(buffer); - if (!itemStack.isEmpty()) { - if (serverBoundDataProcessor != null) { - itemStack = serverBoundDataProcessor.apply(itemStack); - } - } - return itemStack; - } - - @Override - public void encode(@NotNull RegistryFriendlyByteBuf buffer, @NotNull ItemStack value) { - if (!value.isEmpty()) { - if (clientBoundDataProcessor != null) { - value = clientBoundDataProcessor.apply(value); - } - } - this.original.encode(buffer, value); - } -} \ No newline at end of file diff --git a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/mixin/ItemStackMixin.java b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/mixin/ItemStackMixin.java deleted file mode 100644 index 88bc5d916..000000000 --- a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/mixin/ItemStackMixin.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.momirealms.craftengine.mod.mixin; - -import net.minecraft.core.NonNullList; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.ByteBufCodecs; -import net.minecraft.network.codec.StreamCodec; -import net.minecraft.world.item.ItemStack; -import net.momirealms.craftengine.mod.item.CustomStreamCodec; -import org.objectweb.asm.Opcodes; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.util.List; - -@Mixin(ItemStack.class) -public abstract class ItemStackMixin { - @Shadow(remap = false) - @Mutable - public static StreamCodec OPTIONAL_STREAM_CODEC; - @Shadow(remap = false) - @Mutable - public static StreamCodec> OPTIONAL_LIST_STREAM_CODEC; - @Shadow(remap = false) - @Mutable - public static StreamCodec OPTIONAL_UNTRUSTED_STREAM_CODEC; - - private static StreamCodec ORIGINAL_OPTIONAL_STREAM_CODEC; - private static StreamCodec ORIGINAL_OPTIONAL_UNTRUSTED_STREAM_CODEC; - - @Inject( - method = "", - at = @At( - value = "FIELD", - target = "Lnet/minecraft/world/item/ItemStack;OPTIONAL_STREAM_CODEC:Lnet/minecraft/network/codec/StreamCodec;", - opcode = Opcodes.PUTSTATIC, - shift = At.Shift.AFTER - ) - ) - private static void captureOriginalAfterAssignment0(CallbackInfo ci) { - ORIGINAL_OPTIONAL_STREAM_CODEC = OPTIONAL_STREAM_CODEC; - } - - @Inject( - method = "", - at = @At( - value = "FIELD", - target = "Lnet/minecraft/world/item/ItemStack;OPTIONAL_UNTRUSTED_STREAM_CODEC:Lnet/minecraft/network/codec/StreamCodec;", - opcode = Opcodes.PUTSTATIC, - shift = At.Shift.AFTER - ) - ) - private static void captureOriginalAfterAssignment1(CallbackInfo ci) { - ORIGINAL_OPTIONAL_UNTRUSTED_STREAM_CODEC = OPTIONAL_UNTRUSTED_STREAM_CODEC; - } - - @Inject(method = "", at = @At("RETURN")) - private static void replaceStreamCodec(CallbackInfo ci) { - OPTIONAL_STREAM_CODEC = new CustomStreamCodec(ORIGINAL_OPTIONAL_STREAM_CODEC); - OPTIONAL_UNTRUSTED_STREAM_CODEC = new CustomStreamCodec(ORIGINAL_OPTIONAL_UNTRUSTED_STREAM_CODEC); - OPTIONAL_LIST_STREAM_CODEC = OPTIONAL_STREAM_CODEC.apply(ByteBufCodecs.collection(NonNullList::createWithCapacity)); - } -} diff --git a/shared/src/main/java/net/momirealms/craftengine/shared/block/BlockBehavior.java b/shared/src/main/java/net/momirealms/craftengine/shared/block/BlockBehavior.java index 24e08c8b3..a39948fb9 100644 --- a/shared/src/main/java/net/momirealms/craftengine/shared/block/BlockBehavior.java +++ b/shared/src/main/java/net/momirealms/craftengine/shared/block/BlockBehavior.java @@ -1,9 +1,18 @@ package net.momirealms.craftengine.shared.block; +import java.util.Optional; import java.util.concurrent.Callable; public abstract class BlockBehavior { + @SuppressWarnings("unchecked") + public Optional getAs(Class tClass) { + if (tClass.isInstance(this)) { + return Optional.of((T) this); + } + return Optional.empty(); + } + public Object rotate(Object thisBlock, Object[] args, Callable superMethod) throws Exception { return superMethod.call(); } @@ -13,7 +22,11 @@ public abstract class BlockBehavior { } public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { - return superMethod.call(); + return args[0]; + } + + public void neighborChanged(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + superMethod.call(); } public void tick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { diff --git a/shared/src/main/java/net/momirealms/craftengine/shared/block/BlockShape.java b/shared/src/main/java/net/momirealms/craftengine/shared/block/BlockShape.java index 636e09ace..8f11da262 100644 --- a/shared/src/main/java/net/momirealms/craftengine/shared/block/BlockShape.java +++ b/shared/src/main/java/net/momirealms/craftengine/shared/block/BlockShape.java @@ -3,4 +3,8 @@ package net.momirealms.craftengine.shared.block; public interface BlockShape { Object getShape(Object thisObj, Object[] args) throws Exception; + + Object getCollisionShape(Object thisObj, Object[] args); + + Object getSupportShape(Object thisObj, Object[] args); }