diff --git a/bukkit/build.gradle.kts b/bukkit/build.gradle.kts index c2520f8e1..adbf3432f 100644 --- a/bukkit/build.gradle.kts +++ b/bukkit/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { // NMS compileOnly("net.momirealms:craft-engine-nms-helper:${rootProject.properties["nms_helper_version"]}") // Platform - compileOnly("dev.folia:folia-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT") // OpenGL Math compileOnly("org.joml:joml:${rootProject.properties["joml_version"]}") // Gson diff --git a/bukkit/compatibility/build.gradle.kts b/bukkit/compatibility/build.gradle.kts index 8a7807cc9..0165818bd 100644 --- a/bukkit/compatibility/build.gradle.kts +++ b/bukkit/compatibility/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { compileOnly(project(":core")) compileOnly("net.momirealms:sparrow-nbt:${rootProject.properties["sparrow_nbt_version"]}") // Platform - compileOnly("dev.folia:folia-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT") // NeigeItems compileOnly("pers.neige.neigeitems:NeigeItems:1.21.42") // Placeholder diff --git a/bukkit/legacy/build.gradle.kts b/bukkit/legacy/build.gradle.kts index 4d8b59eca..ae80f6f46 100644 --- a/bukkit/legacy/build.gradle.kts +++ b/bukkit/legacy/build.gradle.kts @@ -9,7 +9,7 @@ repositories { dependencies { // Platform - compileOnly("dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT") } java { diff --git a/bukkit/legacy/src/main/java/net/momirealms/craftengine/bukkit/util/LegacyEntityUtils.java b/bukkit/legacy/src/main/java/net/momirealms/craftengine/bukkit/util/LegacyEntityUtils.java new file mode 100644 index 000000000..35a23f0ad --- /dev/null +++ b/bukkit/legacy/src/main/java/net/momirealms/craftengine/bukkit/util/LegacyEntityUtils.java @@ -0,0 +1,16 @@ +package net.momirealms.craftengine.bukkit.util; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.event.entity.CreatureSpawnEvent; + +import java.util.function.Consumer; + +public class LegacyEntityUtils { + + public static Entity spawnEntity(World world, Location loc, EntityType type, Consumer function) { + return world.spawnEntity(loc, type, CreatureSpawnEvent.SpawnReason.CUSTOM, function::accept); + } +} diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index 7f2a8a975..49c1cecf5 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -12,7 +12,7 @@ repositories { dependencies { // Platform - compileOnly("dev.folia:folia-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:${rootProject.properties["paper_version"]}-R0.1-SNAPSHOT") implementation(project(":shared")) implementation(project(":core")) diff --git a/bukkit/loader/src/main/resources/commands.yml b/bukkit/loader/src/main/resources/commands.yml index 125d3e971..81f4a1076 100644 --- a/bukkit/loader/src/main/resources/commands.yml +++ b/bukkit/loader/src/main/resources/commands.yml @@ -76,6 +76,27 @@ totem_animation: - /craftengine feature totem-animation - /ce feature totem-animation +enable_resource: + enable: true + permission: ce.command.admin.resource + usage: + - /craftengine resource enable + - /ce resource enable + +disable_resource: + enable: true + permission: ce.command.admin.resource + usage: + - /craftengine resource disable + - /ce resource disable + +list_resource: + enable: true + permission: ce.command.admin.resource + usage: + - /craftengine resource list + - /ce resource list + # Debug commands debug_set_block: enable: true diff --git a/bukkit/loader/src/main/resources/config.yml b/bukkit/loader/src/main/resources/config.yml index e09b121c9..28d44c613 100644 --- a/bukkit/loader/src/main/resources/config.yml +++ b/bukkit/loader/src/main/resources/config.yml @@ -130,30 +130,61 @@ item: non-italic-tag: false block: + # Enables the sound system, which prevents the client from hearing some non-custom block sounds and improves the client experience. sound-system: enable: true + # In Adventure Mode, players need the correct tool to break custom blocks. + # Vanilla clients DO NOT recognize custom block IDs (e.g., craftengine:note_block_0). + # + # - When ENABLED: + # - Players can break custom blocks if their tools can mine their VANILLA EQUIVALENTS. + # Example: A tool for "note_block" can break "craftengine:note_block_0". + # + # - When DISABLED: + # ⚠️ WARNING: + # - Server MUST list ACTUAL CUSTOM BLOCK IDs in item's `can_break` component. + # - Sending custom IDs (e.g., craftengine:note_block_0) to vanilla clients WILL CRASH THEM! + # ✅ Solution: + # - Use `client-bound-item-data` to safely sync custom block data to clients. + # Documentation: https://mo-mi.gitbook.io/xiaomomi-plugins/craftengine/plugin-wiki/craftengine/add-new-contents/items/item-data/client-bound-item-data + simplify-adventure-break-check: false + # Similar to the option above, but designed for block placement + simplify-adventure-place-check: false + # Whether plugin should predict the next block to break + # This can help improve mining experience to some extent at the cost of performance + predict-breaking: + enable: false + interval: 10 + extended-interaction-range: 0.5 furniture: - # Should the plugin remove invalid furniture on chunk load + # Automatically remove outdated furniture entities when a chunk is loaded. remove-invalid-furniture-on-chunk-load: + # Enable/disable the cleanup system enable: false - # If you want to remove all invalid furniture, please set this list to empty, otherwise only furniture in the list will be removed. + # - When EMPTY: Remove ALL invalid furniture entities + # - When POPULATED: Only remove specified furniture types + # Example for targeted removal: + # list: [ "xxx:invalid_furniture", "yyy:broken_sofa" ] list: - "xxx:invalid_furniture" - # Whether to hide the entity containing metadata + # Hide technical entities used for storing furniture metadata. + # NOTE: + # - These are INVISIBLE entities used internally for tracking furniture states + # - Recommended to keep enabled for better performance hide-base-entity: true image: - # Prevent players from using images set in minecraft:default font - # Players with `craftengine.filter.bypass.xxx` would ignore the limitation + # Block image tags using minecraft:default font in these interfaces + # Permission bypass: craftengine.filter.bypass.xxx (replace xxx with context: anvil/book/chat/etc) illegal-characters-filter: anvil: true book: true chat: true command: true sign: true - # By intercepting packets, you are allowed to use in other plugins - # Turning off some unused options would help reduce CPU usage on async threads + # Allow and tags in third-party plugins via packet manipulation + # ⚠️ Disable unused handlers to reduce async thread workload intercept-packets: system-chat: true tab-list: true # Tab list header and footer @@ -168,17 +199,69 @@ image: entity-name: false armor-stand: true # Legacy Holograms text-display: true # Modern Holograms + # Defines Unicode characters used for positioning + # - Must match the font defined in resource packs + # - Do NOT modify unless you understand text rendering mechanics + offset-characters: + font: minecraft:offset_chars + -1: '\uf800' + -2: '\uf801' + -3: '\uf802' + -4: '\uf803' + -5: '\uf804' + -6: '\uf805' + -7: '\uf806' + -8: '\uf807' + -9: '\uf808' + -10: '\uf809' + -11: '\uf80a' + -12: '\uf80b' + -13: '\uf80c' + -14: '\uf80d' + -15: '\uf80e' + -16: '\uf80f' + -24: '\uf810' + -32: '\uf811' + -48: '\uf812' + -64: '\uf813' + -128: '\uf814' + -256: '\uf815' + 1: '\uf830' + 2: '\uf831' + 3: '\uf832' + 4: '\uf833' + 5: '\uf834' + 6: '\uf835' + 7: '\uf836' + 8: '\uf837' + 9: '\uf838' + 10: '\uf839' + 11: '\uf83a' + 12: '\uf83b' + 13: '\uf83c' + 14: '\uf83d' + 15: '\uf83e' + 16: '\uf83f' + 24: '\uf840' + 32: '\uf841' + 48: '\uf842' + 64: '\uf843' + 128: '\uf844' + 256: '\uf845' emoji: {} recipe: - # Enable the plugin's recipe system + # Master switch for custom recipes + # NOTE: When enabled, plugin recipes will OVERRIDE vanilla recipes enable: true - # Disable vanilla recipes + # Manage Minecraft's default recipe behavior disable-vanilla-recipes: - # Disable all vanilla recipes + # ⚠️ WARNING: When true, DISABLES ALL VANILLA RECIPES + # - Conflicts with 'list' option (list will be ignored) all: false - # Disable the recipes in list + # Selective recipe disabling (safer alternative to 'all: true') + # Example: ["minecraft:wooden_sword", "minecraft:stone_hoe"] list: [] gui: @@ -240,12 +323,13 @@ gui: performance: # Maximum chain update depth when fixing client visuals max-block-chain-update-limit: 64 - # Maximum number of emojis to parse per operation + # Prevent lag or oversized packet when processing emoji-heavy content max-emojis-per-parse: 16 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 + # Turning this option on will reduce lighting system issues to some extent, but will increase server bandwidth consumption. force-update-light: false chunk-system: @@ -255,59 +339,28 @@ chunk-system: # 4 = LZ4 | Blazing-Fast Blazing-Fast Low Low | # 5 = ZSTD | Medium-Fast Fast High Medium | compression-method: 4 - # Disabling this option prevents the plugin from converting custom blocks to vanilla states when chunks are unloaded. - # While this can improve performance, custom blocks will turn into air if the plugin is uninstalled. + # Auto-convert custom blocks -> vanilla blocks when unloading chunks + # + # - When ENABLED (true): + # - Prevents custom blocks becoming AIR if plugin is uninstalled + # - Ensures world portability for vanilla environments + # + # - When DISABLED (false): + # ⚠️ IRREVERSIBLE DATA LOSS WARNING: + # - Custom blocks permanently turn to AIR without plugin + # - Recommended for temporary/test worlds only restore-vanilla-blocks-on-chunk-unload: true + # Convert vanilla blocks -> custom blocks when loading chunks + # + # - Performance Mode (false): + # ⚠️ REQUIRED CONDITIONS: + # 1. Must disable restore-vanilla-blocks-on-chunk-unload + # 2. Accept risk of custom block data loss on plugin removal + # + # - Compatibility Mode (true): + # - Full state recovery with minor performance cost + restore-custom-blocks-on-chunk-load: true # When you edit a map locally using CraftEngine fabric mod, the custom block data is not immediately synchronized with the # server's CraftEngine internal data. Enabling this option will synchronize the data when the chunk is loaded. # (This option only slightly impacts performance, which has been fully optimized, so you don't need to worry too much.) - sync-custom-blocks-on-chunk-load: false - # If you disable this, it's a must to disable the above option. - restore-custom-blocks-on-chunk-load: true - -offset-characters: - font: minecraft:offset_chars - -1: '\uf800' - -2: '\uf801' - -3: '\uf802' - -4: '\uf803' - -5: '\uf804' - -6: '\uf805' - -7: '\uf806' - -8: '\uf807' - -9: '\uf808' - -10: '\uf809' - -11: '\uf80a' - -12: '\uf80b' - -13: '\uf80c' - -14: '\uf80d' - -15: '\uf80e' - -16: '\uf80f' - -24: '\uf810' - -32: '\uf811' - -48: '\uf812' - -64: '\uf813' - -128: '\uf814' - -256: '\uf815' - 1: '\uf830' - 2: '\uf831' - 3: '\uf832' - 4: '\uf833' - 5: '\uf834' - 6: '\uf835' - 7: '\uf836' - 8: '\uf837' - 9: '\uf838' - 10: '\uf839' - 11: '\uf83a' - 12: '\uf83b' - 13: '\uf83c' - 14: '\uf83d' - 15: '\uf83e' - 16: '\uf83f' - 24: '\uf840' - 32: '\uf841' - 48: '\uf842' - 64: '\uf843' - 128: '\uf844' - 256: '\uf845' \ No newline at end of file + sync-custom-blocks-on-chunk-load: false \ No newline at end of file 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 08e34f0a2..4476e5c87 100644 --- a/bukkit/loader/src/main/resources/resources/default/configuration/templates.yml +++ b/bukkit/loader/src/main/resources/resources/default/configuration/templates.yml @@ -661,7 +661,7 @@ templates#settings#blocks: - "default:sound/stone" - "default:pickaxe_power/level_{break_power}" overrides: - hardness: 4.5 + hardness: 3.0 resistance: 3.0 push-reaction: NORMAL is-redstone-conductor: true @@ -677,7 +677,7 @@ templates#settings#blocks: - "default:sound/deepslate" - "default:pickaxe_power/level_{break_power}" overrides: - hardness: 6.0 + hardness: 4.5 resistance: 3.0 push-reaction: NORMAL is-redstone-conductor: true diff --git a/bukkit/loader/src/main/resources/translations/en.yml b/bukkit/loader/src/main/resources/translations/en.yml index 007676fe9..eee50f283 100644 --- a/bukkit/loader/src/main/resources/translations/en.yml +++ b/bukkit/loader/src/main/resources/translations/en.yml @@ -54,6 +54,11 @@ command.search_usage.not_found: "No usage found for this item" command.search_recipe.no_item: "Please hold an item before running this command" command.search_usage.no_item: "Please hold an item before running this command" command.totem_animation.failure.not_totem: "Item '' is not minecraft:totem_of_undying" +command.resource.enable.success: "Enabled resource . Run /ce reload all to apply changes" +command.resource.enable.failure.unknown: "Unknown resource " +command.resource.disable.success: "Disabled resource . Run /ce reload all to apply changes" +command.resource.disable.failure.unknown: "Unknown resource " +command.resource.list: "Enabled resources(): Disabled resources(): " warning.config.image.duplicated: "Issue found in file - Duplicated image ''." warning.config.image.lack_height: "Issue found in file - The image '' is missing the required 'height' argument." warning.config.image.height_smaller_than_ascent: "Issue found in file - The image '' violates the bitmap image rule: 'height' should be no lower than 'ascent'." diff --git a/bukkit/loader/src/main/resources/translations/zh_cn.yml b/bukkit/loader/src/main/resources/translations/zh_cn.yml index 66acc8f30..0f03c5226 100644 --- a/bukkit/loader/src/main/resources/translations/zh_cn.yml +++ b/bukkit/loader/src/main/resources/translations/zh_cn.yml @@ -54,6 +54,11 @@ command.search_usage.not_found: "找不到此物品的用途" command.search_recipe.no_item: "请手持物品后再执行此命令" command.search_usage.no_item: "请手持物品后再执行此命令" command.totem_animation.failure.not_totem: "'' 不是 totem_of_undying 类型" +command.resource.enable.success: "已启用 . 执行 /ce reload all 以应用更改" +command.resource.enable.failure.unknown: "未知资源 " +command.resource.disable.success: "已禁用 . 执行 /ce reload all 以应用更改" +command.resource.disable.failure.unknown: "未知资源 " +command.resource.list: "启用的资源(): 禁用的资源(): " warning.config.image.duplicated: "在文件 中发现问题 - 图片 '' 重复定义" warning.config.image.lack_height: "在文件 中发现问题 - 图片 '' 缺少必要的 'height' 高度参数" warning.config.image.height_smaller_than_ascent: "在文件 中发现问题 - 图片 '' 违反位图规则:'height' 高度值不应小于 'ascent' 基准线高度" diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java index b7bbe8e5a..0a840939a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/api/CraftEngineBlocks.java @@ -17,6 +17,7 @@ import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.context.ContextHolder; import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.WorldEvents; import net.momirealms.sparrow.nbt.CompoundTag; import org.bukkit.Location; import org.bukkit.Material; @@ -183,7 +184,7 @@ public final class CraftEngineBlocks { world.playBlockSound(vec3d, state.sounds().breakSound()); } if (sendParticles) { - FastNMS.INSTANCE.method$Level$levelEvent(world.serverWorld(), 2001, LocationUtils.toBlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), state.customBlockState().registryId()); + FastNMS.INSTANCE.method$Level$levelEvent(world.serverWorld(), WorldEvents.BLOCK_BREAK_EFFECT, LocationUtils.toBlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()), state.customBlockState().registryId()); } block.setType(Material.AIR, applyPhysics); return true; 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 fda52637e..c23e2774d 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 @@ -7,15 +7,14 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; 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.ImmutableBlockState; import net.momirealms.craftengine.core.block.properties.Property; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.item.ItemKeys; import net.momirealms.craftengine.core.loot.LootTable; import net.momirealms.craftengine.core.loot.parameter.LootParameters; import net.momirealms.craftengine.core.plugin.config.Config; -import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.util.context.ContextHolder; import net.momirealms.craftengine.core.world.BlockPos; @@ -38,7 +37,6 @@ import org.bukkit.event.world.GenericGameEvent; import org.bukkit.inventory.ItemStack; import java.util.List; -import java.util.Optional; public class BlockEventListener implements Listener { private final BukkitCraftEngine plugin; @@ -95,7 +93,7 @@ public class BlockEventListener implements Listener { } } - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) // I forget why it's LOW before + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) public void onPlayerBreak(BlockBreakEvent event) { org.bukkit.block.Block block = event.getBlock(); Object blockState = BlockStateUtils.blockDataToBlockState(block.getBlockData()); @@ -105,6 +103,11 @@ public class BlockEventListener implements Listener { ImmutableBlockState state = manager.getImmutableBlockStateUnsafe(stateId); if (!state.isEmpty()) { Location location = block.getLocation(); + BukkitServerPlayer serverPlayer = this.plugin.adapt(player); + // double check adventure mode to prevent dupe + if (!FastNMS.INSTANCE.mayBuild(serverPlayer.serverPlayer()) && !serverPlayer.canBreak(LocationUtils.toBlockPos(location), null)) { + return; + } // trigger event CustomBlockBreakEvent customBreakEvent = new CustomBlockBreakEvent(event.getPlayer(), location, block, state); @@ -127,16 +130,19 @@ public class BlockEventListener implements Listener { // play sound Vec3d vec3d = new Vec3d(location.getBlockX() + 0.5, location.getBlockY() + 0.5, location.getBlockZ() + 0.5); world.playBlockSound(vec3d, state.sounds().breakSound()); - if (player.getGameMode() == GameMode.CREATIVE) { + if (player.getGameMode() == GameMode.CREATIVE || !customBreakEvent.dropItems()) { return; } - BukkitServerPlayer serverPlayer = this.plugin.adapt(player); Item itemInHand = serverPlayer.getItemInHand(InteractionHand.MAIN_HAND); - Key itemId = Optional.ofNullable(itemInHand).map(Item::id).orElse(ItemKeys.AIR); // do not drop if it's not the correct tool - if (!state.settings().isCorrectTool(itemId) || !customBreakEvent.dropItems()) { - return; + BlockSettings settings = state.settings(); + if (settings.requireCorrectTool()) { + if (itemInHand == null) return; + if (!settings.isCorrectTool(itemInHand.id()) && + (!settings.respectToolComponent() || !FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(itemInHand.getLiteralObject(), state.customBlockState().handle()))) { + return; + } } // drop items ContextHolder.Builder builder = ContextHolder.builder(); 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 0c0b50c31..154a947b2 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 @@ -133,6 +133,7 @@ public class BukkitCustomBlock extends CustomBlock { if (settings.burnable()) { Reflections.method$FireBlock$setFlammable.invoke(Reflections.instance$Blocks$FIRE, mcBlock, settings.burnChance(), settings.fireSpreadChance()); } + Reflections.field$BlockStateBase$requiresCorrectToolForDrops.set(mcBlockState, settings.requireCorrectTool()); } } catch (Exception e) { CraftEngine.instance().logger().warn("Failed to init block settings", e); 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 77a537757..316912626 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 @@ -19,6 +19,7 @@ import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.util.context.ContextHolder; 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.shared.block.BlockBehavior; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -76,7 +77,7 @@ public class BushBlockBehavior extends BukkitBlockBehavior { world.dropItemNaturally(vec3d, item); } world.playBlockSound(vec3d, previousState.sounds().breakSound()); - FastNMS.INSTANCE.method$Level$levelEvent(level, 2001, blockPos, stateId); + FastNMS.INSTANCE.method$Level$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, stateId); } return Reflections.method$Block$defaultBlockState.invoke(Reflections.instance$Blocks$AIR); } 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 index f819125c8..d00f514cd 100644 --- 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 @@ -21,6 +21,7 @@ import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.util.context.ContextHolder; 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.shared.block.BlockBehavior; import java.util.List; @@ -69,7 +70,7 @@ public class SugarCaneBlockBehavior extends BushBlockBehavior { world.dropItemNaturally(vec3d, item); } world.playBlockSound(vec3d, currentState.sounds().breakSound()); - FastNMS.INSTANCE.method$Level$levelEvent(level, 2001, blockPos, stateId); + FastNMS.INSTANCE.method$Level$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, stateId); } } } 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 6d32b362d..0e5eb17fb 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,63 +1,116 @@ 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.core.item.CustomItem; -import net.momirealms.craftengine.core.item.Item; -import net.momirealms.craftengine.core.item.ItemBuildContext; -import net.momirealms.craftengine.core.item.ItemSettings; +import net.momirealms.craftengine.core.item.*; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; -import net.momirealms.craftengine.core.item.modifier.ItemModifier; +import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; import net.momirealms.craftengine.core.util.Key; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Map; public class BukkitCustomItem implements CustomItem { private final Key id; private final Key materialKey; private final Material material; - private final List> modifiers; - private final List behavior; + 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; - public BukkitCustomItem(Key id, Key materialKey, Material material, List> modifiers, List behavior, ItemSettings settings) { + @SuppressWarnings("unchecked") + public BukkitCustomItem(Key id, + Key materialKey, + Material material, + List> modifiers, + List> clientBoundModifiers, + List behaviors, + ItemSettings settings) { this.id = id; this.material = material; - this.modifiers = modifiers; - this.behavior = behavior; this.materialKey = materialKey; + // 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 Key id() { - return id; + return this.id; } @Override public Key material() { - return materialKey; + return this.materialKey; } @Override - public List> modifiers() { - return modifiers; + 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 public ItemStack buildItemStack(ItemBuildContext context, int count) { - ItemStack item = new ItemStack(material); - if (this.modifiers.isEmpty()) { - return item; - } + ItemStack item = new ItemStack(this.material); Item wrapped = BukkitCraftEngine.instance().itemManager().wrap(item); wrapped.count(count); - for (ItemModifier modifier : this.modifiers) { + for (ItemDataModifier modifier : this.modifiers) { modifier.apply(wrapped, context); } return wrapped.load(); @@ -65,14 +118,14 @@ public class BukkitCustomItem implements CustomItem { @Override public ItemSettings settings() { - return settings; + return this.settings; } @Override public Item buildItem(ItemBuildContext context) { - ItemStack item = new ItemStack(material); + ItemStack item = new ItemStack(this.material); Item wrapped = BukkitCraftEngine.instance().itemManager().wrap(item); - for (ItemModifier modifier : modifiers()) { + for (ItemDataModifier modifier : dataModifiers()) { modifier.apply(wrapped, context); } wrapped.load(); @@ -81,7 +134,7 @@ public class BukkitCustomItem implements CustomItem { @Override public @NotNull List behaviors() { - return this.behavior; + return this.behaviors; } public static Builder builder() { @@ -92,9 +145,10 @@ public class BukkitCustomItem implements CustomItem { private Key id; private Material material; private Key materialKey; - private List behavior = List.of(); - private ItemSettings settings = ItemSettings.of(); - private final List> modifiers = new ArrayList<>(); + private ItemSettings settings; + private final List behaviors = new ArrayList<>(); + private final List> modifiers = new ArrayList<>(); + private final List> clientBoundModifiers = new ArrayList<>(); @Override public Builder id(Key id) { @@ -110,26 +164,38 @@ public class BukkitCustomItem implements CustomItem { } @Override - public Builder modifier(ItemModifier modifier) { + public Builder dataModifier(ItemDataModifier modifier) { this.modifiers.add(modifier); return this; } @Override - public Builder modifiers(List> list) { - this.modifiers.addAll(list); + public Builder dataModifiers(List> modifiers) { + this.modifiers.addAll(modifiers); return this; } + @Override + public Builder clientBoundDataModifier(ItemDataModifier modifier) { + this.clientBoundModifiers.add(modifier); + return this; + } + + @Override + public Builder clientBoundDataModifiers(List> modifiers) { + this.clientBoundModifiers.addAll(modifiers); + return null; + } + @Override public Builder behavior(ItemBehavior behavior) { - this.behavior= List.of(behavior); + this.behaviors.add(behavior); return this; } @Override - public Builder behavior(List behaviors) { - this.behavior = behaviors; + public Builder behaviors(List behaviors) { + this.behaviors.addAll(behaviors); return this; } @@ -142,7 +208,7 @@ public class BukkitCustomItem implements CustomItem { @Override public CustomItem build() { this.modifiers.addAll(this.settings.modifiers()); - return new BukkitCustomItem(id, materialKey, material, Collections.unmodifiableList(modifiers), behavior, settings); + return new BukkitCustomItem(this.id, this.materialKey, this.material, this.modifiers, this.clientBoundModifiers, this.behaviors, this.settings); } } } 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 ab17f6790..b2baff59b 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 @@ -35,10 +35,7 @@ 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; -import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.MiscUtils; -import net.momirealms.craftengine.core.util.ResourceKey; -import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.util.context.ContextHolder; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -50,8 +47,10 @@ 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; public class BukkitItemManager extends AbstractItemManager { static { @@ -70,13 +69,63 @@ public class BukkitItemManager extends AbstractItemManager { public BukkitItemManager(BukkitCraftEngine plugin) { super(plugin); + instance = this; this.plugin = plugin; this.factory = BukkitItemFactory.create(plugin); this.itemEventListener = new ItemEventListener(plugin); this.debugStickListener = new DebugStickListener(plugin); this.itemParser = new ItemParser(); this.registerAllVanillaItems(); - instance = this; + if (plugin.hasMod() && VersionHelper.isVersionNewerThan1_20_5()) { + 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 @@ -218,7 +267,10 @@ public class BukkitItemManager extends AbstractItemManager { .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 = (String) section.get("material"); + if (isVanillaItem) + materialStringId = id.value(); if (materialStringId == null) { TranslationManager.instance().log("warning.config.item.lack_material", path.toString(), id.toString()); return; @@ -235,14 +287,12 @@ public class BukkitItemManager extends AbstractItemManager { Key itemModelKey = null; CustomItem.Builder itemBuilder = BukkitCustomItem.builder().id(id).material(materialId); - itemBuilder.modifier(new IdModifier<>(id)); - boolean hasItemModelSection = section.containsKey("item-model"); // To get at least one model provider // Sets some basic model info if (customModelData != 0) { - itemBuilder.modifier(new CustomModelDataModifier<>(customModelData)); + itemBuilder.dataModifier(new CustomModelDataModifier<>(customModelData)); } // Requires the item to have model before apply item-model else if (!hasItemModelSection && section.containsKey("model") && VersionHelper.isVersionNewerThan1_21_2()) { @@ -250,7 +300,7 @@ public class BukkitItemManager extends AbstractItemManager { // customize or use the id itemModelKey = Key.from(section.getOrDefault("item-model", id.toString()).toString()); if (ResourceLocation.isValid(itemModelKey.toString())) { - itemBuilder.modifier(new ItemModelModifier<>(itemModelKey)); + itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey)); } else { itemModelKey = null; } @@ -258,7 +308,7 @@ public class BukkitItemManager extends AbstractItemManager { if (hasItemModelSection) { itemModelKey = Key.from(section.get("item-model").toString()); - itemBuilder.modifier(new ItemModelModifier<>(itemModelKey)); + itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey)); } // Get item behaviors @@ -270,7 +320,7 @@ public class BukkitItemManager extends AbstractItemManager { for (Map behaviorMap : behavior) { behaviors.add(ItemBehaviors.fromMap(pack, path, id, behaviorMap)); } - itemBuilder.behavior(behaviors); + itemBuilder.behaviors(behaviors); } else if (behaviorConfig instanceof Map) { Map behaviorSection = MiscUtils.castToMap(section.get("behavior"), true); if (behaviorSection != null) { @@ -284,7 +334,7 @@ public class BukkitItemManager extends AbstractItemManager { for (Map.Entry dataEntry : dataSection.entrySet()) { Optional.ofNullable(dataFunctions.get(dataEntry.getKey())).ifPresent(function -> { try { - itemBuilder.modifier(function.apply(dataEntry.getValue())); + itemBuilder.dataModifier(function.apply(dataEntry.getValue())); } catch (IllegalArgumentException e) { plugin.logger().warn("Invalid data format", e); } @@ -292,10 +342,35 @@ public class BukkitItemManager extends AbstractItemManager { } } + // 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; if (section.containsKey("settings")) { Map settings = MiscUtils.castToMap(section.get("settings"), false); - itemBuilder.settings(ItemSettings.fromMap(settings)); + itemSettings = ItemSettings.fromMap(settings); + } else { + itemSettings = ItemSettings.of(); } + if (isVanillaItem) { + itemSettings.canPlaceRelatedVanillaBlock(true); + } + itemBuilder.settings(itemSettings); CustomItem customItem = itemBuilder.build(); customItems.put(id, customItem); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ItemEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ItemEventListener.java index 93d669faf..27319d9ba 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ItemEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/ItemEventListener.java @@ -11,6 +11,7 @@ import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.InteractionResult; +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.UseOnContext; @@ -78,15 +79,15 @@ public class ItemEventListener implements Listener { @EventHandler public void onInteractAir(PlayerInteractEvent event) { - if (event.getAction() != Action.RIGHT_CLICK_AIR) return; + if (event.getAction() != Action.RIGHT_CLICK_AIR) + return; Player bukkitPlayer = event.getPlayer(); BukkitServerPlayer player = this.plugin.adapt(bukkitPlayer); InteractionHand hand = event.getHand() == EquipmentSlot.HAND ? InteractionHand.MAIN_HAND : InteractionHand.OFF_HAND; if (cancelEventIfHasInteraction(event, player, hand)) { return; } - - if (player.isSpectatorMode() || player.isAdventureMode()) { + if (player.isSpectatorMode()) { return; } @@ -138,7 +139,12 @@ public class ItemEventListener implements Listener { boolean interactable = InteractUtils.isInteractable(BlockStateUtils.getBlockOwnerId(clickedBlock), bukkitPlayer, clickedBlock.getBlockData(), hitResult, itemInHand); // do not allow to place block if it's a vanilla block - if (itemInHand.isBlockItem() && itemInHand.isCustomItem()) { + Optional> optionalCustomItem = itemInHand.getCustomItem(); + if (itemInHand.isBlockItem() && optionalCustomItem.isPresent()) { + // it's a custom item, but now it's ignored + if (optionalCustomItem.get().settings().canPlaceRelatedVanillaBlock()) { + return; + } if (!interactable || player.isSecondaryUseActive()) { event.setCancelled(true); } @@ -149,9 +155,8 @@ public class ItemEventListener implements Listener { return; } - // TODO We need to further investigate how to handle adventure mode // no spectator interactions - if (player.isSpectatorMode() || player.isAdventureMode()) { + if (player.isSpectatorMode()) { return; } @@ -214,7 +219,7 @@ public class ItemEventListener implements Listener { int currentTicks = player.gameTicks(); // The client will send multiple packets to the server if the client thinks it should // However, if the main hand item interaction is successful, the off-hand item should be blocked. - if (!player.updateLastSuccessfulInteractionTick(currentTicks)) { + if (player.lastSuccessfulInteractionTick() == currentTicks) { event.setCancelled(true); return true; } 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 index c19b855ed..5b85ac0fc 100644 --- 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 @@ -53,6 +53,10 @@ public class AxeItemBehavior extends ItemBehavior { } 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 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 563a4cfb7..6f4014452 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 @@ -5,10 +5,7 @@ import net.momirealms.craftengine.bukkit.api.event.CustomBlockAttemptPlaceEvent; import net.momirealms.craftengine.bukkit.api.event.CustomBlockPlaceEvent; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.util.DirectionUtils; -import net.momirealms.craftengine.bukkit.util.EventUtils; -import net.momirealms.craftengine.bukkit.util.LocationUtils; -import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.UpdateOption; @@ -22,6 +19,7 @@ import net.momirealms.craftengine.core.item.context.BlockPlaceContext; 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.plugin.config.Config; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.world.BlockPos; @@ -76,20 +74,36 @@ public class BlockItemBehavior extends ItemBehavior { return InteractionResult.FAIL; } Player player = placeContext.getPlayer(); - int gameTicks = player.gameTicks(); - if (!player.updateLastSuccessfulInteractionTick(gameTicks)) { - return InteractionResult.FAIL; - } - BlockPos pos = placeContext.getClickedPos(); BlockPos againstPos = placeContext.getAgainstPos(); World world = (World) placeContext.getLevel().platformWorld(); Location placeLocation = new Location(world, pos.x(), pos.y(), pos.z()); + int gameTicks = player.gameTicks(); + if (!player.updateLastSuccessfulInteractionTick(gameTicks)) { + return InteractionResult.FAIL; + } + Block bukkitBlock = world.getBlockAt(placeLocation); Block againstBlock = world.getBlockAt(againstPos.x(), againstPos.y(), againstPos.z()); org.bukkit.entity.Player bukkitPlayer = (org.bukkit.entity.Player) player.platformPlayer(); + if (player.isAdventureMode()) { + Object againstBlockState = BlockStateUtils.blockDataToBlockState(againstBlock.getBlockData()); + int stateId = BlockStateUtils.blockStateToId(againstBlockState); + if (BlockStateUtils.isVanillaBlock(stateId)) { + if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, againstBlockState)) { + return InteractionResult.FAIL; + } + } else { + ImmutableBlockState customState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); + // custom block + if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, Config.simplifyAdventurePlaceCheck() ? customState.vanillaBlockState().handle() : againstBlockState)) { + return InteractionResult.FAIL; + } + } + } + // trigger event CustomBlockAttemptPlaceEvent attemptPlaceEvent = new CustomBlockAttemptPlaceEvent(bukkitPlayer, placeLocation.clone(), blockStateToPlace, DirectionUtils.toBlockFace(context.getClickedFace()), bukkitBlock, context.getHand()); 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 index 4a71f53bf..fcac2aa5c 100644 --- 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 @@ -26,6 +26,10 @@ public class BoneMealItemBehavior extends ItemBehavior { @Override public InteractionResult useOnBlock(UseOnContext context) { + if (context.getPlayer().isAdventureMode()) { + return InteractionResult.PASS; + } + BukkitWorldBlock clicked = (BukkitWorldBlock) context.getLevel().getBlockAt(context.getClickedPos()); Block block = clicked.block(); ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockDataToId(block.getBlockData())); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BucketItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BucketItemBehavior.java index 1c1e279a6..e8f90596f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BucketItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BucketItemBehavior.java @@ -34,6 +34,7 @@ public class BucketItemBehavior extends ItemBehavior { @SuppressWarnings("unchecked") @Override public InteractionResult useOnBlock(UseOnContext context) { + if (context.getPlayer().isAdventureMode()) return InteractionResult.PASS; BukkitWorldBlock clicked = (BukkitWorldBlock) context.getLevel().getBlockAt(context.getClickedPos()); Block block = clicked.block(); ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockDataToId(block.getBlockData())); 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 76f777fbd..f4a2c324b 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 @@ -67,6 +67,11 @@ public class FurnitureItemBehavior extends ItemBehavior { } Player player = context.getPlayer(); + // todo adventure check + if (player.isAdventureMode()) { + return InteractionResult.FAIL; + } + int gameTicks = player.gameTicks(); if (!player.updateLastSuccessfulInteractionTick(gameTicks)) { return InteractionResult.FAIL; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WaterBucketItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WaterBucketItemBehavior.java index 49c60ee74..1ec481214 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WaterBucketItemBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/WaterBucketItemBehavior.java @@ -31,6 +31,7 @@ public class WaterBucketItemBehavior extends ItemBehavior { @SuppressWarnings("unchecked") @Override public InteractionResult useOnBlock(UseOnContext context) { + if (context.getPlayer().isAdventureMode()) return InteractionResult.PASS; BlockPos pos = context.getClickedPos(); BukkitWorldBlock clicked = (BukkitWorldBlock) context.getLevel().getBlockAt(pos); Block block = clicked.block(); 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 1b1f1c15f..37fbd9f6b 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 @@ -9,6 +9,7 @@ import net.momirealms.craftengine.core.item.ItemWrapper; import net.momirealms.craftengine.core.item.modifier.IdModifier; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; +import org.bukkit.NamespacedKey; import org.bukkit.inventory.ItemStack; import java.util.Objects; @@ -43,7 +44,10 @@ public abstract class BukkitItemFactory extends ItemFactory item) { Object id = item.get(IdModifier.CRAFT_ENGINE_ID); - if (id == null) return Key.of(item.getItem().getType().getKey().asString()); + if (id == null) { + NamespacedKey key = item.getItem().getType().getKey(); + return Key.of(key.getNamespace(), key.getKey()); + } return Key.of(id.toString()); } @@ -89,26 +93,6 @@ public abstract class BukkitItemFactory extends ItemFactory item, String type, Object value) { - item.setComponent(type, value); - } - - @Override - protected Object getComponent(ItemWrapper item, String type) { - return item.getComponent(type); - } - - @Override - protected boolean hasComponent(ItemWrapper item, String type) { - return item.hasComponent(type); - } - - @Override - protected void removeComponent(ItemWrapper item, String type) { - item.removeComponent(type); - } - @Override protected void update(ItemWrapper item) { item.update(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory.java index 07ca4d7c7..afe9f24b4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory.java @@ -1,14 +1,18 @@ package net.momirealms.craftengine.bukkit.item.factory; +import com.google.gson.JsonElement; import com.saicone.rtag.RtagItem; import com.saicone.rtag.data.ComponentType; import com.saicone.rtag.item.ItemObject; import net.momirealms.craftengine.bukkit.item.RTagItemWrapper; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.EnchantmentUtils; +import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.item.ComponentKeys; import net.momirealms.craftengine.core.item.Enchantment; import net.momirealms.craftengine.core.item.ItemWrapper; +import net.momirealms.craftengine.core.item.Trim; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; import org.bukkit.inventory.ItemStack; @@ -26,17 +30,63 @@ public class ComponentItemFactory extends BukkitItemFactory { super(plugin); } + @Override + protected void setComponent(ItemWrapper item, Key type, Object value) { + if (value instanceof JsonElement jsonElement) { + setJsonComponentDirectly(item, type, jsonElement); + } else { + setJavaComponentDirectly(item, type, value); + } + } + + @Override + protected void resetComponent(ItemWrapper item, Key type) { + FastNMS.INSTANCE.resetComponent(item.getLiteralObject(), KeyUtils.toResourceLocation(type)); + } + + @Override + protected Object getComponent(ItemWrapper item, Key type) { + return item.getComponent(type); + } + + @Override + protected boolean hasComponent(ItemWrapper item, Key type) { + return item.hasComponent(type); + } + + @Override + protected void removeComponent(ItemWrapper item, Key type) { + FastNMS.INSTANCE.removeComponent(item.getLiteralObject(), KeyUtils.toResourceLocation(type)); + } + + protected void setJavaComponentDirectly(ItemWrapper item, Key type, Object value) { + ComponentType.parseJava(type, value).ifPresent(it -> FastNMS.INSTANCE.setComponent(item.getLiteralObject(), KeyUtils.toResourceLocation(type), it)); + } + + protected void setJsonComponentDirectly(ItemWrapper item, Key type, JsonElement value) { + ComponentType.parseJson(type, value).ifPresent(it -> FastNMS.INSTANCE.setComponent(item.getLiteralObject(), KeyUtils.toResourceLocation(type), it)); + } + + protected void setNBTComponentDirectly(ItemWrapper item, Key type, Object value) { + ComponentType.parseNbt(type, value).ifPresent(it -> FastNMS.INSTANCE.setComponent(item.getLiteralObject(), KeyUtils.toResourceLocation(type), it)); + } + @Override public Object encodeJava(Key componentType, @Nullable Object component) { return ComponentType.encodeJava(componentType, component).orElse(null); } + @Override + protected JsonElement encodeJson(Key type, Object component) { + return ComponentType.encodeJson(type, component).orElse(null); + } + @Override protected void customModelData(ItemWrapper item, Integer data) { if (data == null) { - item.removeComponent(ComponentKeys.CUSTOM_MODEL_DATA); + resetComponent(item, ComponentKeys.CUSTOM_MODEL_DATA); } else { - item.setComponent(ComponentKeys.CUSTOM_MODEL_DATA, data); + setJavaComponentDirectly(item, ComponentKeys.CUSTOM_MODEL_DATA, data); } } @@ -53,9 +103,9 @@ public class ComponentItemFactory extends BukkitItemFactory { @Override protected void customName(ItemWrapper item, String json) { if (json == null) { - item.removeComponent(ComponentKeys.CUSTOM_NAME); + resetComponent(item, ComponentKeys.CUSTOM_NAME); } else { - item.setComponent(ComponentKeys.CUSTOM_NAME, json); + setJavaComponentDirectly(item, ComponentKeys.CUSTOM_NAME, json); } } @@ -73,9 +123,9 @@ public class ComponentItemFactory extends BukkitItemFactory { @Override protected void itemName(ItemWrapper item, String json) { if (json == null) { - item.removeComponent(ComponentKeys.ITEM_NAME); + resetComponent(item, ComponentKeys.ITEM_NAME); } else { - item.setComponent(ComponentKeys.ITEM_NAME, json); + setJavaComponentDirectly(item, ComponentKeys.ITEM_NAME, json); } } @@ -92,15 +142,12 @@ public class ComponentItemFactory extends BukkitItemFactory { @Override protected void skull(ItemWrapper item, String skullData) { - final Map profile = Map.of( - "properties", List.of( - Map.of( - "name", "textures", - "value", skullData - ) - ) - ); - item.setComponent("minecraft:profile", profile); + if (skullData == null) { + resetComponent(item, ComponentKeys.PROFILE); + } else { + Map profile = Map.of("properties", List.of(Map.of("name", "textures", "value", skullData))); + setJavaComponentDirectly(item, ComponentKeys.PROFILE, profile); + } } @SuppressWarnings("unchecked") @@ -118,9 +165,9 @@ public class ComponentItemFactory extends BukkitItemFactory { @Override protected void lore(ItemWrapper item, List lore) { if (lore == null || lore.isEmpty()) { - item.removeComponent(ComponentKeys.LORE); + resetComponent(item, ComponentKeys.LORE); } else { - item.setComponent(ComponentKeys.LORE, lore); + setJavaComponentDirectly(item, ComponentKeys.LORE, lore); } } @@ -132,9 +179,9 @@ public class ComponentItemFactory extends BukkitItemFactory { @Override protected void unbreakable(ItemWrapper item, boolean unbreakable) { if (unbreakable) { - item.removeComponent(ComponentKeys.UNBREAKABLE); + resetComponent(item, ComponentKeys.UNBREAKABLE); } else { - item.setComponent(ComponentKeys.UNBREAKABLE, true); + setJavaComponentDirectly(item, ComponentKeys.UNBREAKABLE, true); } } @@ -145,7 +192,11 @@ public class ComponentItemFactory extends BukkitItemFactory { @Override protected void glint(ItemWrapper item, Boolean glint) { - item.setComponent(ComponentKeys.ENCHANTMENT_GLINT_OVERRIDE, glint); + if (glint == null) { + resetComponent(item, ComponentKeys.ENCHANTMENT_GLINT_OVERRIDE); + } else { + setJavaComponentDirectly(item, ComponentKeys.ENCHANTMENT_GLINT_OVERRIDE, glint); + } } @Override @@ -161,8 +212,11 @@ public class ComponentItemFactory extends BukkitItemFactory { @Override protected void damage(ItemWrapper item, Integer damage) { - if (damage == null) damage = 0; - item.setComponent(ComponentKeys.DAMAGE, damage); + if (damage == null) { + resetComponent(item, ComponentKeys.DAMAGE); + } else { + setJavaComponentDirectly(item, ComponentKeys.DAMAGE, damage); + } } @Override @@ -179,9 +233,9 @@ public class ComponentItemFactory extends BukkitItemFactory { @Override protected void maxDamage(ItemWrapper item, Integer damage) { if (damage == null) { - item.removeComponent(ComponentKeys.MAX_DAMAGE); + resetComponent(item, ComponentKeys.MAX_DAMAGE); } else { - item.setComponent(ComponentKeys.MAX_DAMAGE, damage); + setJavaComponentDirectly(item, ComponentKeys.MAX_DAMAGE, damage); } } @@ -201,20 +255,28 @@ public class ComponentItemFactory extends BukkitItemFactory { @Override protected void enchantments(ItemWrapper item, List enchantments) { - Map enchants = new HashMap<>(); - for (Enchantment enchantment : enchantments) { - enchants.put(enchantment.id().toString(), enchantment.level()); + if (enchantments == null || enchantments.isEmpty()) { + resetComponent(item, ComponentKeys.ENCHANTMENTS); + } else { + Map enchants = new HashMap<>(); + for (Enchantment enchantment : enchantments) { + enchants.put(enchantment.id().toString(), enchantment.level()); + } + setJavaComponentDirectly(item, ComponentKeys.ENCHANTMENTS, enchants); } - item.setComponent(ComponentKeys.ENCHANTMENTS, enchants); } @Override protected void storedEnchantments(ItemWrapper item, List enchantments) { - Map enchants = new HashMap<>(); - for (Enchantment enchantment : enchantments) { - enchants.put(enchantment.id().toString(), enchantment.level()); + if (enchantments == null || enchantments.isEmpty()) { + resetComponent(item, ComponentKeys.STORED_ENCHANTMENTS); + } else { + Map enchants = new HashMap<>(); + for (Enchantment enchantment : enchantments) { + enchants.put(enchantment.id().toString(), enchantment.level()); + } + setJavaComponentDirectly(item, ComponentKeys.STORED_ENCHANTMENTS, enchants); } - item.setComponent(ComponentKeys.STORED_ENCHANTMENTS, enchants); } @Override @@ -223,7 +285,7 @@ public class ComponentItemFactory extends BukkitItemFactory { try { Map map = EnchantmentUtils.toMap(enchant); map.put(enchantment.toString(), enchantment.level()); - item.setComponent(ComponentKeys.ENCHANTMENTS, map); + setJavaComponentDirectly(item, ComponentKeys.ENCHANTMENTS, map); } catch (ReflectiveOperationException e) { plugin.logger().warn("Failed to add enchantment", e); } @@ -235,7 +297,7 @@ public class ComponentItemFactory extends BukkitItemFactory { try { Map map = EnchantmentUtils.toMap(enchant); map.put(enchantment.toString(), enchantment.level()); - item.setComponent(ComponentKeys.STORED_ENCHANTMENTS, map); + setJavaComponentDirectly(item, ComponentKeys.STORED_ENCHANTMENTS, map); } catch (ReflectiveOperationException e) { plugin.logger().warn("Failed to add stored enchantment", e); } @@ -256,18 +318,18 @@ public class ComponentItemFactory extends BukkitItemFactory { @Override protected void maxStackSize(ItemWrapper item, Integer maxStackSize) { if (maxStackSize == null) { - item.removeComponent(ComponentKeys.MAX_STACK_SIZE); + resetComponent(item, ComponentKeys.MAX_STACK_SIZE); } else { - item.setComponent(ComponentKeys.MAX_STACK_SIZE, maxStackSize); + setJavaComponentDirectly(item, ComponentKeys.MAX_STACK_SIZE, maxStackSize); } } @Override protected void repairCost(ItemWrapper item, Integer data) { if (data == null) { - item.removeComponent(ComponentKeys.REPAIR_COST); + resetComponent(item, ComponentKeys.REPAIR_COST); } else { - item.setComponent(ComponentKeys.REPAIR_COST, data); + setJavaComponentDirectly(item, ComponentKeys.REPAIR_COST, data); } } @@ -294,7 +356,6 @@ public class ComponentItemFactory extends BukkitItemFactory { @Override protected void merge(ItemWrapper item1, ItemWrapper item2) { // load previous changes on nms items - item1.load(); Object itemStack1 = item1.getLiteralObject(); Object itemStack2 = item2.getLiteralObject(); try { @@ -303,4 +364,28 @@ public class ComponentItemFactory extends BukkitItemFactory { plugin.logger().warn("Failed to merge item", e); } } + + @Override + protected void trim(ItemWrapper item, Trim trim) { + if (trim == null) { + resetComponent(item, ComponentKeys.TRIM); + } else { + setJavaComponentDirectly(item, ComponentKeys.TRIM, Map.of( + "pattern", trim.pattern(), + "material", trim.material() + )); + } + } + + @Override + protected Optional trim(ItemWrapper item) { + if (!item.hasComponent(ComponentKeys.TRIM)) return Optional.empty(); + Optional trim = ComponentType.encodeJava(ComponentKeys.TRIM, item.getComponent(ComponentKeys.TRIM)); + if (trim.isEmpty()) { + return Optional.empty(); + } + @SuppressWarnings("unchecked") + Map trimMap = (Map) trim.get(); + return Optional.of(new Trim(trimMap.get("pattern"), trimMap.get("material"))); + } } \ No newline at end of file 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 850ea9e8a..d87b5dfe0 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 @@ -33,9 +33,9 @@ public class ComponentItemFactory1_21_4 extends ComponentItemFactory { @Override protected void customModelData(ItemWrapper item, Integer data) { if (data == null) { - item.removeComponent(ComponentKeys.CUSTOM_MODEL_DATA); + resetComponent(item, ComponentKeys.CUSTOM_MODEL_DATA); } else { - item.setComponent(ComponentKeys.CUSTOM_MODEL_DATA, Map.of("floats", List.of(data.floatValue()))); + setComponent(item, ComponentKeys.CUSTOM_MODEL_DATA, Map.of("floats", List.of(data.floatValue()))); } } } 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 557420dab..1d9f23576 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 @@ -26,9 +26,9 @@ public class ComponentItemFactory1_21_5 extends ComponentItemFactory1_21_4 { @Override protected void customName(ItemWrapper item, String json) { if (json == null) { - item.removeComponent(ComponentKeys.CUSTOM_NAME); + resetComponent(item, ComponentKeys.CUSTOM_NAME); } else { - item.setComponent(ComponentKeys.CUSTOM_NAME, ChatComponent.toTag(ComponentUtils.jsonToMinecraft(json))); + setNBTComponentDirectly(item, ComponentKeys.CUSTOM_NAME, ChatComponent.toTag(ComponentUtils.jsonToMinecraft(json))); } } @@ -41,9 +41,9 @@ public class ComponentItemFactory1_21_5 extends ComponentItemFactory1_21_4 { @Override protected void itemName(ItemWrapper item, String json) { if (json == null) { - item.removeComponent(ComponentKeys.ITEM_NAME); + resetComponent(item, ComponentKeys.ITEM_NAME); } else { - item.setComponent(ComponentKeys.ITEM_NAME, ChatComponent.toTag(ComponentUtils.jsonToMinecraft(json))); + setNBTComponentDirectly(item, ComponentKeys.ITEM_NAME, ChatComponent.toTag(ComponentUtils.jsonToMinecraft(json))); } } @@ -71,13 +71,13 @@ public class ComponentItemFactory1_21_5 extends ComponentItemFactory1_21_4 { @Override protected void lore(ItemWrapper item, List lore) { if (lore == null || lore.isEmpty()) { - item.removeComponent(ComponentKeys.LORE); + resetComponent(item, ComponentKeys.LORE); } else { List loreTags = new ArrayList<>(); for (String json : lore) { loreTags.add(ChatComponent.toTag(ComponentUtils.jsonToMinecraft(json))); } - item.setComponent(ComponentKeys.LORE, TagList.newTag(loreTags)); + setNBTComponentDirectly(item, ComponentKeys.LORE, TagList.newTag(loreTags)); } } } 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 085c997e8..324a799df 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 @@ -1,5 +1,6 @@ package net.momirealms.craftengine.bukkit.item.factory; +import com.google.gson.JsonElement; import com.saicone.rtag.RtagItem; import com.saicone.rtag.item.ItemObject; import com.saicone.rtag.tag.TagBase; @@ -8,6 +9,7 @@ import com.saicone.rtag.tag.TagList; import net.momirealms.craftengine.bukkit.item.RTagItemWrapper; import net.momirealms.craftengine.core.item.Enchantment; import net.momirealms.craftengine.core.item.ItemWrapper; +import net.momirealms.craftengine.core.item.Trim; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.SkullUtils; @@ -26,6 +28,36 @@ public class UniversalItemFactory extends BukkitItemFactory { super(plugin); } + @Override + protected void resetComponent(ItemWrapper item, Key type) { + throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); + } + + @Override + protected JsonElement encodeJson(Key type, Object component) { + throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); + } + + @Override + protected void setComponent(ItemWrapper item, Key type, Object value) { + throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); + } + + @Override + protected Object getComponent(ItemWrapper item, Key type) { + throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); + } + + @Override + protected boolean hasComponent(ItemWrapper item, Key type) { + throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); + } + + @Override + protected void removeComponent(ItemWrapper item, Key type) { + throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); + } + @Override public Object encodeJava(Key componentType, @Nullable Object component) { throw new UnsupportedOperationException("This feature is only available on 1.20.5+"); @@ -142,6 +174,10 @@ public class UniversalItemFactory extends BukkitItemFactory { @Override protected void enchantments(ItemWrapper item, List enchantments) { + if (enchantments == null || enchantments.isEmpty()) { + item.remove("Enchantments"); + return; + } ArrayList tags = new ArrayList<>(); for (Enchantment enchantment : enchantments) { tags.add((Map.of("id", enchantment.id().toString(), "lvl", (short) enchantment.level()))); @@ -151,6 +187,10 @@ public class UniversalItemFactory extends BukkitItemFactory { @Override protected void storedEnchantments(ItemWrapper item, List enchantments) { + if (enchantments == null || enchantments.isEmpty()) { + item.remove("StoredEnchantments"); + return; + } ArrayList tags = new ArrayList<>(); for (Enchantment enchantment : enchantments) { tags.add((Map.of("id", enchantment.id().toString(), "lvl", (short) enchantment.level()))); @@ -248,4 +288,22 @@ public class UniversalItemFactory extends BukkitItemFactory { item1.load(); TagCompound.merge(ItemObject.getCustomDataTag(item1.getLiteralObject()), ItemObject.getCustomDataTag(item2.getLiteralObject()), true, true); } + + @Override + protected void trim(ItemWrapper item, Trim trim) { + if (trim == null) { + item.remove("Trim"); + return; + } + item.set(trim.material(), "Trim", "material"); + item.set(trim.pattern(), "Trim", "pattern"); + } + + @Override + protected Optional trim(ItemWrapper item) { + String material = item.get("Trim", "material"); + String pattern = item.get("Trim", "pattern"); + if (material == null || pattern == null) return Optional.empty(); + return Optional.of(new Trim(material, pattern)); + } } \ No newline at end of file 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 515ac12ec..556ceaf64 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 @@ -46,7 +46,10 @@ public class BukkitCommandManager extends AbstractCommandManager new DebugSetBlockCommand(this, plugin), new DebugSpawnFurnitureCommand(this, plugin), new DebugTargetBlockCommand(this, plugin), - new TotemAnimationCommand(this, plugin) + new TotemAnimationCommand(this, plugin), + new EnableResourceCommand(this, plugin), + new DisableResourceCommand(this, plugin), + new ListResourceCommand(this, plugin) )); final LegacyPaperCommandManager manager = (LegacyPaperCommandManager) getCommandManager(); manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DisableResourceCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DisableResourceCommand.java new file mode 100644 index 000000000..c92513366 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/DisableResourceCommand.java @@ -0,0 +1,72 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import dev.dejvokep.boostedyaml.YamlDocument; +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.plugin.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +public class DisableResourceCommand extends BukkitCommandFeature { + + public DisableResourceCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { + return builder + .flag(manager.flagBuilder("silent").withAliases("s")) + .required("pack", StringParser.stringComponent(StringParser.StringMode.GREEDY).suggestionProvider(new SuggestionProvider<>() { + @Override + public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + return CompletableFuture.completedFuture(plugin().packManager().loadedPacks().stream().filter(Pack::enabled).map(pack -> Suggestion.suggestion(pack.name())).toList()); + } + })) + .handler(context -> { + String packFolder = context.get("pack"); + Path path = plugin().dataFolderPath().resolve("resources").resolve(packFolder); + if (!Files.exists(path)) { + handleFeedback(context, MessageConstants.COMMAND_RESOURCE_DISABLE_FAILURE, Component.text(packFolder)); + return; + } + Path packMetaPath = path.resolve("pack.yml"); + if (!Files.exists(packMetaPath)) { + try { + Files.createFile(packMetaPath); + } catch (IOException e) { + plugin().logger().warn("Could not create pack.yml file: " + packMetaPath); + return; + } + } + YamlDocument document = plugin().config().loadYamlData(packMetaPath.toFile()); + document.set("enable", false); + try { + document.save(packMetaPath.toFile()); + } catch (IOException e) { + plugin().logger().warn("Could not save pack.yml file: " + packMetaPath); + return; + } + handleFeedback(context, MessageConstants.COMMAND_RESOURCE_DISABLE_SUCCESS, Component.text(packFolder)); + }); + } + + @Override + public String getFeatureID() { + return "disable_resource"; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/EnableResourceCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/EnableResourceCommand.java new file mode 100644 index 000000000..e565f9935 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/EnableResourceCommand.java @@ -0,0 +1,71 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import dev.dejvokep.boostedyaml.YamlDocument; +import net.kyori.adventure.text.Component; +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.plugin.locale.MessageConstants; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.incendo.cloud.Command; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.suggestion.SuggestionProvider; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +public class EnableResourceCommand extends BukkitCommandFeature { + + public EnableResourceCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { + return builder + .flag(manager.flagBuilder("silent").withAliases("s")) + .required("pack", StringParser.stringComponent(StringParser.StringMode.GREEDY).suggestionProvider(new SuggestionProvider<>() { + @Override + public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + return CompletableFuture.completedFuture(plugin().packManager().loadedPacks().stream().filter(pack -> !pack.enabled()).map(pack -> Suggestion.suggestion(pack.name())).toList()); + } + })) + .handler(context -> { + String packFolder = context.get("pack"); + Path path = plugin().dataFolderPath().resolve("resources").resolve(packFolder); + if (!Files.exists(path)) { + handleFeedback(context, MessageConstants.COMMAND_RESOURCE_ENABLE_FAILURE, Component.text(packFolder)); + return; + } + Path packMetaPath = path.resolve("pack.yml"); + if (!Files.exists(packMetaPath)) { + try { + Files.createFile(packMetaPath); + } catch (IOException e) { + plugin().logger().warn("Could not create pack.yml file: " + packMetaPath); + return; + } + } + YamlDocument document = plugin().config().loadYamlData(packMetaPath.toFile()); + document.set("enable", true); + try { + document.save(packMetaPath.toFile()); + } catch (IOException e) { + plugin().logger().warn("Could not save pack.yml file: " + packMetaPath); + return; + } + handleFeedback(context, MessageConstants.COMMAND_RESOURCE_ENABLE_SUCCESS, Component.text(packFolder)); + }); + } + + @Override + public String getFeatureID() { + return "enable_resource"; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ListResourceCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ListResourceCommand.java new file mode 100644 index 000000000..48a14bb3e --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/ListResourceCommand.java @@ -0,0 +1,80 @@ +package net.momirealms.craftengine.bukkit.plugin.command.feature; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; +import net.momirealms.craftengine.core.plugin.locale.MessageConstants; +import net.momirealms.craftengine.core.util.AdventureHelper; +import org.bukkit.command.CommandSender; +import org.incendo.cloud.Command; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class ListResourceCommand extends BukkitCommandFeature { + + public ListResourceCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { + super(commandManager, plugin); + } + + @Override + public Command.Builder assembleCommand(org.incendo.cloud.CommandManager manager, Command.Builder builder) { + return builder + .handler(context -> { + Collection packs = plugin().packManager().loadedPacks(); + List enabled = new ArrayList<>(); + List disabled = new ArrayList<>(); + for (Pack pack : packs) { + if (pack.enabled()) { + enabled.add(pack); + } else { + disabled.add(pack); + } + } + handleFeedback(context, MessageConstants.COMMAND_RESOURCE_LIST, Component.text(enabled.size()), Component.empty().children(getChildComponents(enabled)), Component.text(disabled.size()), Component.empty().children(getChildComponents(disabled))); + }); + } + + private List getChildComponents(List disabled) { + List components = new ArrayList<>(); + for (int i = 0; i < disabled.size(); i++) { + Pack pack = disabled.get(i); + components.add(getPackComponent(pack)); + if (i != disabled.size() - 1) { + components.add(Component.text(", ")); + } + } + if (components.isEmpty()) { + return List.of(Component.text("[]")); + } + return components; + } + + private Component getPackComponent(Pack pack) { + String description = pack.meta().description(); + String version = pack.meta().version(); + String author = pack.meta().author(); + String text = version == null ? pack.name() : pack.name() + " v" + version; + Component base = Component.text("[" + text + "]"); + if (author != null || description != null) { + if (author != null && description != null) { + base = base.hoverEvent(HoverEvent.showText(Component.empty().children(List.of(Component.text("by: " + author).color(NamedTextColor.YELLOW), Component.newline(), AdventureHelper.miniMessage().deserialize(description))))); + } else if (author != null) { + base = base.hoverEvent(HoverEvent.showText(Component.text("by: " + author))); + } else { + base = base.hoverEvent(HoverEvent.showText(AdventureHelper.miniMessage().deserialize(description))); + } + } + return base; + } + + @Override + public String getFeatureID() { + return "list_resource"; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java index 834abb7b1..3bdeee1f3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TestCommand.java @@ -1,17 +1,16 @@ package net.momirealms.craftengine.bukkit.plugin.command.feature; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature; +import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager; -import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Entity; import org.bukkit.entity.Player; -import org.bukkit.entity.Shulker; +import org.bukkit.inventory.ItemStack; import org.incendo.cloud.Command; -import java.util.Collection; - public class TestCommand extends BukkitCommandFeature { public TestCommand(CraftEngineCommandManager commandManager, CraftEngine plugin) { @@ -24,16 +23,10 @@ public class TestCommand extends BukkitCommandFeature { .senderType(Player.class) .handler(context -> { Player player = context.sender(); - Location location = player.getLocation(); - try { - Collection entities = player.getLocation().getNearbyEntities(2,2,2); - for (Entity entity : entities) { - if (entity instanceof Shulker) { - } - } - } catch (Exception e) { - e.printStackTrace(); - } + ItemStack itemStack = new ItemStack(Material.STONE); + Item wrapped = BukkitItemManager.instance().wrap(itemStack); + wrapped.lore(null); + player.getInventory().addItem(wrapped.load()); }); } 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 052bab0d8..ec32770cb 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 @@ -28,6 +28,7 @@ import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.plugin.network.NetworkManager; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.BlockPos; +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.MCSection; @@ -221,7 +222,7 @@ public class PacketConsumers { try { FriendlyByteBuf buf = event.getBuffer(); int eventId = buf.readInt(); - if (eventId != 2001) return; + if (eventId != WorldEvents.BLOCK_BREAK_EFFECT) return; BlockPos blockPos = buf.readBlockPos(buf); int state = buf.readInt(); boolean global = buf.readBoolean(); @@ -1118,38 +1119,32 @@ public class PacketConsumers { if (Config.enableSoundSystem()) { Object blockOwner = Reflections.field$StateHolder$owner.get(blockState); if (BukkitBlockManager.instance().isBlockSoundRemoved(blockOwner)) { - player.startMiningBlock(world, pos, blockState, false, null); + player.startMiningBlock(pos, blockState, null); return; } } - if (player.isMiningBlock() || player.shouldSyncAttribute()) { + if (player.isMiningBlock()) { player.stopMiningBlock(); + } else { + player.setClientSideCanBreakBlock(true); } return; } if (player.isAdventureMode()) { - Object itemStack = FastNMS.INSTANCE.method$CraftItemStack$asNMSCopy(player.platformPlayer().getInventory().getItemInMainHand()); - Object blockPos = LocationUtils.toBlockPos(pos); - Object blockInWorld = Reflections.constructor$BlockInWorld.newInstance(serverLevel, blockPos, false); - if (VersionHelper.isVersionNewerThan1_20_5()) { - if (Reflections.method$ItemStack$canBreakBlockInAdventureMode != null - && !(boolean) Reflections.method$ItemStack$canBreakBlockInAdventureMode.invoke( - itemStack, blockInWorld - )) { + if (Config.simplifyAdventureBreakCheck()) { + ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); + if (!player.canBreak(pos, state.vanillaBlockState().handle())) { player.preventMiningBlock(); return; } } else { - if (Reflections.method$ItemStack$canDestroy != null - && !(boolean) Reflections.method$ItemStack$canDestroy.invoke( - itemStack, Reflections.instance$BuiltInRegistries$BLOCK, blockInWorld - )) { + if (!player.canBreak(pos, null)) { player.preventMiningBlock(); return; } } } - player.startMiningBlock(world, pos, blockState, true, BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId)); + player.startMiningBlock(pos, blockState, BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId)); } else if (action == Reflections.instance$ServerboundPlayerActionPacket$Action$ABORT_DESTROY_BLOCK) { if (player.isMiningBlock()) { player.abortMiningBlock(); @@ -1272,7 +1267,7 @@ public class PacketConsumers { if (slot - 36 != bukkitPlayer.getInventory().getHeldItemSlot()) { return; } - double interactionRange = player.getInteractionRange(); + double interactionRange = player.getCachedInteractionRange(); // do ray trace to get current block RayTraceResult result = bukkitPlayer.rayTraceBlocks(interactionRange, FluidCollisionMode.NEVER); if (result == null) return; @@ -1566,9 +1561,11 @@ public class PacketConsumers { if (actionType == null) return; Location location = furniture.baseEntity().getLocation(); BukkitServerPlayer serverPlayer = (BukkitServerPlayer) user; - if (serverPlayer.isSpectatorMode() || serverPlayer.isAdventureMode()) return; + if (serverPlayer.isSpectatorMode()) return; BukkitCraftEngine.instance().scheduler().sync().run(() -> { if (actionType == Reflections.instance$ServerboundInteractPacket$ActionType$ATTACK) { + // todo 冒险模式破坏工具白名单 + if (serverPlayer.isAdventureMode()) return; if (furniture.isValid()) { if (!BukkitCraftEngine.instance().antiGrief().canBreak(player, location)) { return; 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 398b5528b..1fe42faf8 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 @@ -9,26 +9,27 @@ import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; 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.ImmutableBlockState; import net.momirealms.craftengine.core.block.PackedBlockState; 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.item.ItemKeys; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.network.ConnectionState; import net.momirealms.craftengine.core.util.Direction; 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.World; -import net.momirealms.craftengine.core.world.*; +import net.momirealms.craftengine.core.world.WorldEvents; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.util.RayTraceResult; -import org.bukkit.util.Vector; import org.jetbrains.annotations.Nullable; import java.lang.ref.Reference; @@ -39,20 +40,22 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; public class BukkitServerPlayer extends Player { - private final Channel channel; private final BukkitCraftEngine plugin; - + // connection state + private final Channel channel; private ConnectionState decoderState; private ConnectionState encoderState; - + // some references private Reference playerRef; private Reference serverPlayerRef; - + // client side dimension info private int sectionCount; - private int lastSuccessfulInteraction; - private long lastAttributeSyncTime; private Key clientSideDimension; - + // check main hand/offhand interaction + private int lastSuccessfulInteraction; + // re-sync attribute timely to prevent some bugs + private long lastAttributeSyncTime; + // for breaking blocks private int lastSentState = -1; private int lastHitBlockTime; private BlockPos destroyPos; @@ -61,15 +64,25 @@ public class BukkitServerPlayer extends Player { private boolean isDestroyingCustomBlock; private boolean swingHandAck; private float miningProgress; - + // for client visual sync private int resentSoundTick; private int resentSwingTick; - + // cache used recipe private Key lastUsedRecipe = null; - + // has fabric client mod or not private boolean hasClientMod = false; + // cache if player can break blocks + private boolean clientSideCanBreak = true; + // prevent AFK players from consuming too much CPU resource on predicting + private Location previousEyeLocation; + // a cooldown for better breaking experience + private int lastSuccessfulBreak; + // player's game tick + private int gameTicks; + // cache interaction range here + private int lastUpdateInteractionRangeTick; + private double cachedInteractionRange; // for better fake furniture visual sync - // TODO CLEAR ENTITY VIEW private final Map> furnitureView = new ConcurrentHashMap<>(); private final Map entityTypeView = new ConcurrentHashMap<>(); @@ -79,8 +92,15 @@ public class BukkitServerPlayer extends Player { } public void setPlayer(org.bukkit.entity.Player player) { - playerRef = new WeakReference<>(player); - serverPlayerRef = new WeakReference<>(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)); + this.playerRef = new WeakReference<>(player); + this.serverPlayerRef = new WeakReference<>(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player)); + if (Reflections.method$CraftPlayer$setSimplifyContainerDesyncCheck != null) { + try { + Reflections.method$CraftPlayer$setSimplifyContainerDesyncCheck.invoke(player, true); + } catch (Exception e) { + this.plugin.logger().warn("Failed to setSimplifyContainerDesyncCheck", e); + } + } } @Override @@ -108,8 +128,8 @@ public class BukkitServerPlayer extends Player { @Override public boolean shouldSyncAttribute() { - long current = System.currentTimeMillis(); - if (current - this.lastAttributeSyncTime > 10000) { + long current = gameTicks(); + if (current - this.lastAttributeSyncTime > 100) { this.lastAttributeSyncTime = current; return true; } @@ -136,6 +156,16 @@ public class BukkitServerPlayer extends Player { return platformPlayer().getGameMode() == GameMode.ADVENTURE; } + @Override + public boolean canBreak(BlockPos pos, @Nullable Object state) { + return AdventureModeUtils.canBreak(platformPlayer().getInventory().getItemInMainHand(), new Location(platformPlayer().getWorld(), pos.x(), pos.y(), pos.z()), state); + } + + @Override + public boolean canPlace(BlockPos pos, @Nullable Object state) { + return AdventureModeUtils.canPlace(platformPlayer().getInventory().getItemInMainHand(), new Location(platformPlayer().getWorld(), pos.x(), pos.y(), pos.z()), state); + } + @Override public void sendActionBar(Component text) { try { @@ -156,15 +186,14 @@ public class BukkitServerPlayer extends Player { } } + @Override + public int lastSuccessfulInteractionTick() { + return lastSuccessfulInteraction; + } + @Override public int gameTicks() { - try { - Object serverPlayer = serverPlayer(); - Object gameMode = Reflections.field$ServerPlayer$gameMode.get(serverPlayer); - return (int) Reflections.field$ServerPlayerGameMode$gameTicks.get(gameMode); - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Failed to get current tick", e); - } + return this.gameTicks; } @Override @@ -210,36 +239,16 @@ public class BukkitServerPlayer extends Player { platformPlayer().closeInventory(); } - // TODO DO NOT USE BUKKIT API - @Override - public BlockHitResult rayTrace(double distance, FluidCollisionRule collisionRule) { - RayTraceResult result = platformPlayer().rayTraceBlocks(distance, FluidUtils.toCollisionRule(collisionRule)); - if (result == null) { - Location eyeLocation = platformPlayer().getEyeLocation(); - Location targetLocation = eyeLocation.clone(); - targetLocation.add(eyeLocation.getDirection().multiply(distance)); - return BlockHitResult.miss(new Vec3d(eyeLocation.getX(), eyeLocation.getY(), eyeLocation.getZ()), - Direction.getApproximateNearest(eyeLocation.getX() - targetLocation.getX(), eyeLocation.getY() - targetLocation.getY(), eyeLocation.getZ() - targetLocation.getZ()), - new BlockPos(targetLocation.getBlockX(), targetLocation.getBlockY(), targetLocation.getBlockZ()) - ); - } else { - Vector hitPos = result.getHitPosition(); - Block hitBlock = result.getHitBlock(); - Location hitBlockLocation = hitBlock.getLocation(); - return new BlockHitResult( - new Vec3d(hitPos.getX(), hitPos.getY(), hitPos.getZ()), - DirectionUtils.toDirection(result.getHitBlockFace()), - new BlockPos(hitBlockLocation.getBlockX(), hitBlockLocation.getBlockY(), hitBlockLocation.getBlockZ()), - false - ); - } - } - @Override public void sendPacket(Object packet, boolean immediately) { this.plugin.networkManager().sendPacket(this, packet, immediately); } + @Override + public void sendPackets(List packet, boolean immediately) { + this.plugin.networkManager().sendPackets(this, packet, immediately); + } + @Override public void receivePacket(Object packet) { this.plugin.networkManager().receivePacket(this, packet); @@ -290,48 +299,89 @@ public class BukkitServerPlayer extends Player { public void tick() { // not fully online if (serverPlayer() == null) return; + this.gameTicks = FastNMS.INSTANCE.field$MinecraftServer$currentTick(); if (this.isDestroyingBlock) { this.tickBlockDestroy(); } + if (Config.predictBreaking() && !this.isDestroyingCustomBlock) { + // if it's not destroying blocks, we do predict + if ((gameTicks() + entityID()) % Config.predictBreakingInterval() == 0) { + Location eyeLocation = platformPlayer().getEyeLocation(); + if (eyeLocation.equals(this.previousEyeLocation)) { + return; + } + this.previousEyeLocation = eyeLocation; + this.predictNextBlockToMine(); + } + } } @Override public float getDestroyProgress(Object blockState, BlockPos pos) { - try { - Object serverPlayer = serverPlayer(); - Object blockPos = LocationUtils.toBlockPos(pos.x(), pos.y(), pos.z()); - return (float) Reflections.method$BlockStateBase$getDestroyProgress.invoke(blockState, serverPlayer, Reflections.method$Entity$level.invoke(serverPlayer), blockPos); - } catch (ReflectiveOperationException e) { - this.plugin.logger().warn("Failed to get destroy progress for player " + platformPlayer().getName()); - return 0f; + return FastNMS.INSTANCE.method$BlockStateBase$getDestroyProgress(blockState, serverPlayer(), FastNMS.INSTANCE.field$CraftWorld$ServerLevel(platformPlayer().getWorld()), LocationUtils.toBlockPos(pos)); + } + + private void predictNextBlockToMine() { + double range = getCachedInteractionRange() + Config.extendedInteractionRange(); + RayTraceResult result = platformPlayer().rayTraceBlocks(range, FluidCollisionMode.NEVER); + if (result == null) { + if (!this.clientSideCanBreak) { + setClientSideCanBreakBlock(true); + } + return; + } + Block hitBlock = result.getHitBlock(); + if (hitBlock == null) { + if (!this.clientSideCanBreak) { + setClientSideCanBreakBlock(true); + } + return; + } + int stateId = BlockStateUtils.blockDataToId(hitBlock.getBlockData()); + if (BlockStateUtils.isVanillaBlock(stateId)) { + if (!this.clientSideCanBreak) { + setClientSideCanBreakBlock(true); + } + return; + } + if (this.clientSideCanBreak) { + setClientSideCanBreakBlock(false); } } - public void startMiningBlock(org.bukkit.World world, BlockPos pos, Object state, boolean custom, @Nullable ImmutableBlockState immutableBlockState) { + public void startMiningBlock(BlockPos pos, Object state, @Nullable ImmutableBlockState immutableBlockState) { // instant break + boolean custom = immutableBlockState != null; if (custom && getDestroyProgress(state, pos) >= 1f) { - assert immutableBlockState != null; - // not an instant break on client side PackedBlockState 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) { - try { - Object levelEventPacket = Reflections.constructor$ClientboundLevelEventPacket.newInstance(2001, LocationUtils.toBlockPos(pos), BlockStateUtils.blockStateToId(this.destroyedState), false); - sendPacket(levelEventPacket, false); - } catch (ReflectiveOperationException e) { - this.plugin.logger().warn("Failed to send level event packet", e); - } + Object levelEventPacket = FastNMS.INSTANCE.constructor$ClientboundLevelEventPacket( + WorldEvents.BLOCK_BREAK_EFFECT, LocationUtils.toBlockPos(pos), BlockStateUtils.blockStateToId(state), false); + sendPacket(levelEventPacket, false); } - //ParticleUtils.addBlockBreakParticles(world, LocationUtils.toBlockPos(pos), state); return; } - setCanBreakBlock(!custom); + if (!custom && !this.clientSideCanBreak && getDestroyProgress(state, pos) >= 1f) { + Object levelEventPacket = FastNMS.INSTANCE.constructor$ClientboundLevelEventPacket( + WorldEvents.BLOCK_BREAK_EFFECT, LocationUtils.toBlockPos(pos), BlockStateUtils.blockStateToId(state), false); + sendPacket(levelEventPacket, false); + } + // if it's a custom one, we prevent it, otherwise we allow it + setClientSideCanBreakBlock(!custom); + // set some base info setDestroyPos(pos); setDestroyedState(state); setIsDestroyingBlock(true, custom); } - private void setCanBreakBlock(boolean canBreak) { + @Override + public void setClientSideCanBreakBlock(boolean canBreak) { try { + if (this.clientSideCanBreak == canBreak && !shouldSyncAttribute()) { + return; + } + this.clientSideCanBreak = canBreak; if (canBreak) { if (VersionHelper.isVersionNewerThan1_20_5()) { Object serverPlayer = serverPlayer(); @@ -353,8 +403,7 @@ public class BukkitServerPlayer extends Player { } else { Object fatiguePacket = MobEffectUtils.createPacket(Reflections.instance$MobEffecr$mining_fatigue, entityID(), (byte) 9, -1, false, false, false); Object hastePacket = MobEffectUtils.createPacket(Reflections.instance$MobEffecr$haste, entityID(), (byte) 0, -1, false, false, false); - sendPacket(fatiguePacket, true); - sendPacket(hastePacket, true); + sendPackets(List.of(fatiguePacket, hastePacket), true); } } } catch (ReflectiveOperationException e) { @@ -364,17 +413,26 @@ public class BukkitServerPlayer extends Player { @Override public void stopMiningBlock() { - setCanBreakBlock(true); + setClientSideCanBreakBlock(true); setIsDestroyingBlock(false, false); } @Override public void preventMiningBlock() { - setCanBreakBlock(false); + setClientSideCanBreakBlock(false); setIsDestroyingBlock(false, false); abortMiningBlock(); } + @Override + public void abortMiningBlock() { + this.swingHandAck = false; + this.miningProgress = 0; + if (this.destroyPos != null) { + this.broadcastDestroyProgress(platformPlayer(), this.destroyPos, LocationUtils.toBlockPos(this.destroyPos), -1); + } + } + private void resetEffect(Object mobEffect) throws ReflectiveOperationException { Object effectInstance = Reflections.method$ServerPlayer$getEffect.invoke(serverPlayer(), mobEffect); Object packet; @@ -386,27 +444,16 @@ public class BukkitServerPlayer extends Player { sendPacket(packet, true); } - @Override - public void abortMiningBlock() { - abortDestroyProgress(); - } - private void tickBlockDestroy() { - // prevent server from taking over breaking blocks - if (this.isDestroyingCustomBlock) { - try { - Object serverPlayer = serverPlayer(); - Object gameMode = Reflections.field$ServerPlayer$gameMode.get(serverPlayer); - Reflections.field$ServerPlayerGameMode$isDestroyingBlock.set(gameMode, false); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } + // if player swings hand is this tick if (!this.swingHandAck) return; this.swingHandAck = false; + int currentTick = gameTicks(); + // optimize break speed, otherwise it would be too fast + if (currentTick - this.lastSuccessfulBreak <= 5) return; try { org.bukkit.entity.Player player = platformPlayer(); - double range = getInteractionRange(); + double range = getCachedInteractionRange(); RayTraceResult result = player.rayTraceBlocks(range, FluidCollisionMode.NEVER); if (result == null) return; Block hitBlock = result.getHitBlock(); @@ -418,8 +465,8 @@ public class BukkitServerPlayer extends Player { } Object blockPos = LocationUtils.toBlockPos(hitPos); Object serverPlayer = serverPlayer(); - Object gameMode = Reflections.field$ServerPlayer$gameMode.get(serverPlayer); - int currentTick = (int) Reflections.field$ServerPlayerGameMode$gameTicks.get(gameMode); + + // send hit sound if the sound is removed if (currentTick - this.lastHitBlockTime > 3) { Object blockOwner = Reflections.field$StateHolder$owner.get(this.destroyedState); Object soundType = Reflections.field$BlockBehaviour$soundType.get(blockOwner); @@ -431,9 +478,14 @@ public class BukkitServerPlayer extends Player { // accumulate progress (custom blocks only) if (this.isDestroyingCustomBlock) { + // prevent server from taking over breaking custom blocks + Object gameMode = FastNMS.INSTANCE.field$ServerPlayer$gameMode(serverPlayer); + Reflections.field$ServerPlayerGameMode$isDestroyingBlock.set(gameMode, false); + // check item in hand Item item = this.getItemInHand(InteractionHand.MAIN_HAND); if (item != null) { Material itemMaterial = item.getItem().getType(); + // creative mode + invalid item in hand if (canInstabuild() && (itemMaterial == Material.DEBUG_STICK || itemMaterial == Material.TRIDENT || (VersionHelper.isVersionNewerThan1_20_5() && itemMaterial == MaterialUtils.MACE) @@ -442,27 +494,66 @@ public class BukkitServerPlayer extends Player { } } - float progressToAdd = (float) Reflections.method$BlockStateBase$getDestroyProgress.invoke(this.destroyedState, serverPlayer, Reflections.method$Entity$level.invoke(serverPlayer), blockPos); + float progressToAdd = getDestroyProgress(this.destroyedState, hitPos); int id = BlockStateUtils.blockStateToId(this.destroyedState); ImmutableBlockState customState = BukkitBlockManager.instance().getImmutableBlockState(id); - if (customState != null && !customState.isEmpty() - && !customState.settings().isCorrectTool(item == null ? ItemKeys.AIR : item.id())) { - progressToAdd *= customState.settings().incorrectToolSpeed(); - } + // double check custom block + if (customState != null && !customState.isEmpty()) { + BlockSettings blockSettings = customState.settings(); + if (blockSettings.requireCorrectTool()) { + if (item != null) { + // it's correct on plugin side + if (blockSettings.isCorrectTool(item.id())) { + // but not on serverside + if (!FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(item.getLiteralObject(), this.destroyedState)) { + // we fix the speed + progressToAdd = progressToAdd * (10f / 3f); + } + } else { + // not a correct tool on plugin side and not a correct tool on serverside + if (!blockSettings.respectToolComponent() || !FastNMS.INSTANCE.method$ItemStack$isCorrectToolForDrops(item.getLiteralObject(), this.destroyedState)) { + progressToAdd = progressToAdd * (10f / 3f) * blockSettings.incorrectToolSpeed(); + } + } + } else { + // item is null, but it requires correct tool, then we reset the speed + progressToAdd = progressToAdd * (10f / 3f) * blockSettings.incorrectToolSpeed(); + } + } - this.miningProgress = progressToAdd + miningProgress; - int packetStage = (int) (this.miningProgress * 10.0F); - if (packetStage != this.lastSentState) { - this.lastSentState = packetStage; - broadcastDestroyProgress(player, hitPos, blockPos, packetStage); - } + // accumulate progress + this.miningProgress = progressToAdd + miningProgress; + int packetStage = (int) (this.miningProgress * 10.0F); + if (packetStage != this.lastSentState) { + this.lastSentState = packetStage; + // broadcast changes + broadcastDestroyProgress(player, hitPos, blockPos, packetStage); + } - if (this.miningProgress >= 1f) { - //Reflections.method$ServerLevel$levelEvent.invoke(Reflections.field$CraftWorld$ServerLevel.get(player.getWorld()), null, 2001, blockPos, BlockStateUtils.blockStateToId(this.destroyedState)); - Reflections.method$ServerPlayerGameMode$destroyBlock.invoke(gameMode, blockPos); - Object levelEventPacket = Reflections.constructor$ClientboundLevelEventPacket.newInstance(2001, blockPos, id, false); - sendPacket(levelEventPacket, false); - this.stopMiningBlock(); + // can break now + if (this.miningProgress >= 1f) { + // for simplified adventure break, switch mayBuild temporarily + if (isAdventureMode() && Config.simplifyAdventureBreakCheck()) { + // check the appearance state + if (canBreak(hitPos, customState.vanillaBlockState().handle())) { + // Error might occur so we use try here + try { + FastNMS.INSTANCE.setMayBuild(serverPlayer, true); + Reflections.method$ServerPlayerGameMode$destroyBlock.invoke(gameMode, blockPos); + } finally { + FastNMS.INSTANCE.setMayBuild(serverPlayer, false); + } + } + } else { + // normal break check + Reflections.method$ServerPlayerGameMode$destroyBlock.invoke(gameMode, blockPos); + } + // send break particle + (removed sounds) + sendPacket(FastNMS.INSTANCE.constructor$ClientboundLevelEventPacket(WorldEvents.BLOCK_BREAK_EFFECT, blockPos, id, false), false); + this.lastSuccessfulBreak = currentTick; + this.destroyPos = null; + this.setIsDestroyingBlock(false, false); + } } } } catch (Exception e) { @@ -470,8 +561,8 @@ public class BukkitServerPlayer extends Player { } } - private void broadcastDestroyProgress(org.bukkit.entity.Player player, BlockPos hitPos, Object blockPos, int stage) throws ReflectiveOperationException { - Object packet = Reflections.constructor$ClientboundBlockDestructionPacket.newInstance(Integer.MAX_VALUE - entityID(), blockPos, stage); + 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()) { Location otherLocation = other.getLocation(); double d0 = (double) hitPos.x() - otherLocation.getX(); @@ -484,53 +575,28 @@ public class BukkitServerPlayer extends Player { } @Override - public double getInteractionRange() { - try { - if (VersionHelper.isVersionNewerThan1_20_5()) { - Object attributeInstance = Reflections.method$ServerPlayer$getAttribute.invoke(serverPlayer(), Reflections.instance$Holder$Attribute$block_interaction_range); - if (attributeInstance == null) return 4.5d; - return (double) Reflections.method$AttributeInstance$getValue.invoke(attributeInstance); - } else { - return 4.5d; - } - } catch (ReflectiveOperationException e) { - plugin.logger().warn("Failed to get interaction range for player " + platformPlayer().getName(), e); - return 4.5d; + public double getCachedInteractionRange() { + if (this.lastUpdateInteractionRangeTick + 20 > gameTicks()) { + return this.cachedInteractionRange; } + this.cachedInteractionRange = FastNMS.INSTANCE.getInteractionRange(serverPlayer()); + this.lastUpdateInteractionRangeTick = gameTicks(); + return this.cachedInteractionRange; } - public void setIsDestroyingBlock(boolean value, boolean custom) { - if (value) { - this.isDestroyingBlock = true; - this.isDestroyingCustomBlock = custom; - this.swingHandAck = true; - this.miningProgress = 0; - } else { - this.isDestroyingBlock = false; - this.swingHandAck = false; - if (this.destroyPos != null) { - try { - this.broadcastDestroyProgress(platformPlayer(), this.destroyPos, LocationUtils.toBlockPos(this.destroyPos), -1); - } catch (ReflectiveOperationException e) { - plugin.logger().warn("Failed to set isDestroyingCustomBlock", e); - } - } - this.destroyPos = null; - this.miningProgress = 0; - this.destroyedState = null; - this.isDestroyingCustomBlock = false; - } - } - - @Override - public void abortDestroyProgress() { - this.swingHandAck = false; + public void setIsDestroyingBlock(boolean is, boolean custom) { this.miningProgress = 0; - if (this.destroyPos == null) return; - try { - this.broadcastDestroyProgress(platformPlayer(), this.destroyPos, LocationUtils.toBlockPos(this.destroyPos), -1); - } catch (ReflectiveOperationException e) { - plugin.logger().warn("Failed to abort destroyProgress", e); + this.isDestroyingBlock = is; + this.isDestroyingCustomBlock = custom && is; + if (is) { + this.swingHandAck = true; + } else { + this.swingHandAck = false; + this.destroyedState = null; + if (this.destroyPos != null) { + this.broadcastDestroyProgress(platformPlayer(), this.destroyPos, LocationUtils.toBlockPos(this.destroyPos), -1); + this.destroyPos = null; + } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/AdventureModeUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/AdventureModeUtils.java new file mode 100644 index 000000000..6beabbfe9 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/AdventureModeUtils.java @@ -0,0 +1,62 @@ +package net.momirealms.craftengine.bukkit.util; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.World; +import org.bukkit.Location; +import org.bukkit.inventory.ItemStack; + +@SuppressWarnings("DuplicatedCode") +public class AdventureModeUtils { + + private AdventureModeUtils() {} + + public static boolean canBreak(ItemStack itemStack, Location pos) { + return canPlace(itemStack, pos, null); + } + + public static boolean canBreak(ItemStack itemStack, Location pos, Object state) { + Object blockPos = LocationUtils.toBlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + Object blockInWorld = FastNMS.INSTANCE.constructor$BlockInWorld(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(pos.getWorld()), blockPos, false); + if (state != null) { + try { + Reflections.field$BlockInWorld$state.set(blockInWorld, state); + } catch (ReflectiveOperationException e) { + CraftEngine.instance().logger().warn("Failed to set field$BlockInWorld$state", e); + return false; + } + } + return FastNMS.INSTANCE.canBreakInAdventureMode(FastNMS.INSTANCE.field$CraftItemStack$handle(itemStack), blockInWorld); + } + + public static boolean canPlace(Item itemStack, World world, BlockPos pos, Object state) { + Object blockPos = LocationUtils.toBlockPos(pos); + Object item = itemStack == null ? Reflections.instance$ItemStack$Air : itemStack.getLiteralObject(); + Object blockInWorld = FastNMS.INSTANCE.constructor$BlockInWorld(FastNMS.INSTANCE.field$CraftWorld$ServerLevel((org.bukkit.World) world.platformWorld()), blockPos, false); + if (state != null) { + try { + Reflections.field$BlockInWorld$state.set(blockInWorld, state); + } catch (ReflectiveOperationException e) { + CraftEngine.instance().logger().warn("Failed to set field$BlockInWorld$state", e); + return false; + } + } + return FastNMS.INSTANCE.canPlaceInAdventureMode(item, blockInWorld); + } + + public static boolean canPlace(ItemStack itemStack, Location pos, Object state) { + Object blockPos = LocationUtils.toBlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); + Object blockInWorld = FastNMS.INSTANCE.constructor$BlockInWorld(FastNMS.INSTANCE.field$CraftWorld$ServerLevel(pos.getWorld()), blockPos, false); + if (state != null) { + try { + Reflections.field$BlockInWorld$state.set(blockInWorld, state); + } catch (ReflectiveOperationException e) { + CraftEngine.instance().logger().warn("Failed to set field$BlockInWorld$state", e); + return false; + } + } + return FastNMS.INSTANCE.canPlaceInAdventureMode(FastNMS.INSTANCE.field$CraftItemStack$handle(itemStack), blockInWorld); + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java index c5fe84760..89f66906d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java @@ -1,6 +1,7 @@ package net.momirealms.craftengine.bukkit.util; import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.BlockPos; import org.bukkit.Location; import org.bukkit.World; @@ -9,6 +10,8 @@ import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.entity.CreatureSpawnEvent; +import java.util.function.Consumer; + public class EntityUtils { private EntityUtils() {} @@ -23,12 +26,11 @@ public class EntityUtils { } } - @SuppressWarnings("deprecation") - public static Entity spawnEntity(World world, Location loc, EntityType type, org.bukkit.util.Consumer function) { - try { - return (Entity) Reflections.method$World$spawnEntity.invoke(world, loc, type, CreatureSpawnEvent.SpawnReason.CUSTOM, function); - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Failed to spawn entity", e); + public static Entity spawnEntity(World world, Location loc, EntityType type, Consumer function) { + if (VersionHelper.isVersionNewerThan1_20_2()) { + return world.spawnEntity(loc, type, CreatureSpawnEvent.SpawnReason.CUSTOM, function); + } else { + return LegacyEntityUtils.spawnEntity(world, loc, type, function); } } } 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 43cb6780e..af1e49bae 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 @@ -14,17 +14,12 @@ 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 org.bukkit.Location; import org.bukkit.NamespacedKey; -import org.bukkit.World; import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; import org.bukkit.entity.HumanEntity; import org.bukkit.event.block.BlockPhysicsEvent; import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.inventory.*; import org.jetbrains.annotations.Nullable; import sun.misc.Unsafe; @@ -2418,6 +2413,12 @@ public class Reflections { ) ); + public static final Field field$BlockStateBase$requiresCorrectToolForDrops = requireNonNull( + ReflectionUtils.getDeclaredField( + clazz$BlockStateBase, boolean.class, 5 + ) + ); + public static final Field field$BlockStateBase$canOcclude = requireNonNull( ReflectionUtils.getDeclaredField( clazz$BlockStateBase, boolean.class, 6 @@ -2925,11 +2926,11 @@ public class Reflections { ) ); - public static final Field field$ServerPlayer$gameMode = requireNonNull( - ReflectionUtils.getDeclaredField( - clazz$ServerPlayer, clazz$ServerPlayerGameMode, 0 - ) - ); +// public static final Field field$ServerPlayer$gameMode = requireNonNull( +// ReflectionUtils.getDeclaredField( +// clazz$ServerPlayer, clazz$ServerPlayerGameMode, 0 +// ) +// ); public static final Field field$ServerPlayerGameMode$destroyProgressStart = requireNonNull( ReflectionUtils.getDeclaredField( @@ -2937,11 +2938,11 @@ public class Reflections { ) ); - public static final Field field$ServerPlayerGameMode$gameTicks = requireNonNull( - ReflectionUtils.getDeclaredField( - clazz$ServerPlayerGameMode, int.class, 1 - ) - ); +// public static final Field field$ServerPlayerGameMode$gameTicks = requireNonNull( +// ReflectionUtils.getDeclaredField( +// clazz$ServerPlayerGameMode, int.class, 1 +// ) +// ); public static final Field field$ServerPlayerGameMode$delayedTickStart = requireNonNull( ReflectionUtils.getDeclaredField( @@ -3009,11 +3010,11 @@ public class Reflections { ) ); - public static final Method method$BlockStateBase$getDestroyProgress = requireNonNull( - ReflectionUtils.getDeclaredMethod( - clazz$BlockStateBase, float.class, clazz$Player, clazz$BlockGetter, clazz$BlockPos - ) - ); +// public static final Method method$BlockStateBase$getDestroyProgress = requireNonNull( +// ReflectionUtils.getDeclaredMethod( +// clazz$BlockStateBase, float.class, clazz$Player, clazz$BlockGetter, clazz$BlockPos +// ) +// ); public static final Class clazz$ClientboundBlockDestructionPacket = requireNonNull( ReflectionUtils.getClazz( @@ -3022,11 +3023,11 @@ public class Reflections { ) ); - public static final Constructor constructor$ClientboundBlockDestructionPacket = requireNonNull( - ReflectionUtils.getConstructor( - clazz$ClientboundBlockDestructionPacket, int.class, clazz$BlockPos, int.class - ) - ); +// public static final Constructor constructor$ClientboundBlockDestructionPacket = requireNonNull( +// ReflectionUtils.getConstructor( +// clazz$ClientboundBlockDestructionPacket, int.class, clazz$BlockPos, int.class +// ) +// ); public static final Class clazz$ServerboundSwingPacket = requireNonNull( ReflectionUtils.getClazz( @@ -3994,12 +3995,12 @@ public class Reflections { ) ); - @SuppressWarnings("deprecation") - public static final Method method$World$spawnEntity = requireNonNull( - VersionHelper.isVersionNewerThan1_20_2() ? - ReflectionUtils.getMethod(World.class, Entity.class, Location.class, EntityType.class, CreatureSpawnEvent.SpawnReason.class, Consumer.class) : - ReflectionUtils.getMethod(World.class, Entity.class, Location.class, EntityType.class, CreatureSpawnEvent.SpawnReason.class, org.bukkit.util.Consumer.class) - ); +// @SuppressWarnings("deprecation") +// public static final Method method$World$spawnEntity = requireNonNull( +// VersionHelper.isVersionNewerThan1_20_2() ? +// ReflectionUtils.getMethod(World.class, Entity.class, Location.class, EntityType.class, CreatureSpawnEvent.SpawnReason.class, Consumer.class) : +// ReflectionUtils.getMethod(World.class, Entity.class, Location.class, EntityType.class, CreatureSpawnEvent.SpawnReason.class, org.bukkit.util.Consumer.class) +// ); // 1.21.4+ public static final Class clazz$ServerboundPickItemFromEntityPacket = @@ -4955,11 +4956,11 @@ public class Reflections { ) ); - public static final Constructor constructor$ClientboundLevelEventPacket = requireNonNull( - ReflectionUtils.getConstructor( - clazz$ClientboundLevelEventPacket, int.class, clazz$BlockPos, int.class, boolean.class - ) - ); +// public static final Constructor constructor$ClientboundLevelEventPacket = requireNonNull( +// ReflectionUtils.getConstructor( +// clazz$ClientboundLevelEventPacket, int.class, clazz$BlockPos, int.class, boolean.class +// ) +// ); public static final Field field$ClientboundLevelEventPacket$eventId = requireNonNull( ReflectionUtils.getDeclaredField( @@ -5768,24 +5769,30 @@ public class Reflections { ) ); - public static final Constructor constructor$BlockInWorld = requireNonNull( - ReflectionUtils.getConstructor( - clazz$BlockInWorld, 0 + public static final Field field$BlockInWorld$state = requireNonNull( + ReflectionUtils.getDeclaredField( + clazz$BlockInWorld, clazz$BlockState, 0 ) ); - // 1.20.5+ - public static final Method method$ItemStack$canBreakBlockInAdventureMode = - ReflectionUtils.getMethod( - clazz$ItemStack, new String[]{"canBreakBlockInAdventureMode"}, clazz$BlockInWorld - ); +// public static final Constructor constructor$BlockInWorld = requireNonNull( +// ReflectionUtils.getConstructor( +// clazz$BlockInWorld, 0 +// ) +// ); - // 1.20 ~ 1.20.4 - // instance$BuiltInRegistries$BLOCK - public static final Method method$ItemStack$canDestroy = - ReflectionUtils.getMethod( - clazz$ItemStack,new String[]{"b"}, clazz$Registry, clazz$BlockInWorld - ); +// // 1.20.5+ +// public static final Method method$ItemStack$canBreakBlockInAdventureMode = +// ReflectionUtils.getMethod( +// clazz$ItemStack, new String[]{"canBreakBlockInAdventureMode"}, clazz$BlockInWorld +// ); + +// // 1.20 ~ 1.20.4 +// // instance$BuiltInRegistries$BLOCK +// public static final Method method$ItemStack$canDestroy = +// ReflectionUtils.getMethod( +// clazz$ItemStack,new String[]{"b"}, clazz$Registry, clazz$BlockInWorld +// ); public static final Method method$BlockStateBase$getBlock = requireNonNull( ReflectionUtils.getMethod( @@ -6432,4 +6439,9 @@ public class Reflections { BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayOutScoreboardScore") ) ); + + public static final Method method$CraftPlayer$setSimplifyContainerDesyncCheck = + ReflectionUtils.getMethod( + clazz$CraftPlayer, new String[]{"setSimplifyContainerDesyncCheck"}, boolean.class + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldBlock.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldBlock.java index c1addb16d..105ac4443 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldBlock.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorldBlock.java @@ -55,7 +55,7 @@ public class BukkitWorldBlock implements WorldBlock { Object serverLevel = FastNMS.INSTANCE.field$CraftWorld$ServerLevel(block.getWorld()); Object fluidData = Reflections.method$Level$getFluidState.invoke(serverLevel, LocationUtils.toBlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ())); if (fluidData == null) return false; - return (boolean) Reflections.method$FluidState$isSource.invoke(fluidData); + return Reflections.method$FluidState$getType.invoke(fluidData) == Reflections.instance$Fluids$WATER; } catch (ReflectiveOperationException e) { CraftEngine.instance().logger().warn("Failed to check if water source is available", e); return false; 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 75711f4e9..b4c5efafc 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 @@ -17,6 +17,8 @@ public class BlockSettings { float resistance = 2f; boolean canOcclude; boolean fluidState; + boolean requireCorrectTools; + boolean respectToolComponent; Tristate isRedstoneConductor = Tristate.UNDEFINED; Tristate isSuffocating = Tristate.UNDEFINED; Tristate isViewBlocking = Tristate.UNDEFINED; @@ -74,6 +76,8 @@ public class BlockSettings { newSettings.itemId = settings.itemId; newSettings.tags = settings.tags; newSettings.burnChance = settings.burnChance; + newSettings.requireCorrectTools = settings.requireCorrectTools; + newSettings.respectToolComponent = settings.respectToolComponent; newSettings.fireSpreadChance = settings.fireSpreadChance; newSettings.isRedstoneConductor = settings.isRedstoneConductor; newSettings.isSuffocating = settings.isSuffocating; @@ -130,6 +134,10 @@ public class BlockSettings { return incorrectToolSpeed; } + public boolean requireCorrectTool() { + return requireCorrectTools || !correctTools.isEmpty(); + } + public String name() { return name; } @@ -175,10 +183,13 @@ public class BlockSettings { } public boolean isCorrectTool(Key key) { - if (this.correctTools.isEmpty()) return true; return this.correctTools.contains(key); } + public boolean respectToolComponent() { + return respectToolComponent; + } + public BlockSettings correctTools(Set correctTools) { this.correctTools = correctTools; return this; @@ -249,6 +260,16 @@ public class BlockSettings { return this; } + public BlockSettings requireCorrectTool(boolean requireCorrectTool) { + this.requireCorrectTools = requireCorrectTool; + return this; + } + + public BlockSettings respectToolComponent(boolean respectToolComponent) { + this.respectToolComponent = respectToolComponent; + return this; + } + public BlockSettings incorrectToolSpeed(float incorrectToolSpeed) { this.incorrectToolSpeed = incorrectToolSpeed; return this; @@ -392,6 +413,14 @@ public class BlockSettings { List tools = MiscUtils.getAsStringList(value); return settings -> settings.correctTools(tools.stream().map(Key::of).collect(Collectors.toSet())); })); + registerFactory("require-correct-tools", (value -> { + boolean booleanValue = (boolean) value; + return settings -> settings.requireCorrectTool(booleanValue); + })); + registerFactory("respect-tool-component", (value -> { + boolean booleanValue = (boolean) value; + return settings -> settings.respectToolComponent(booleanValue); + })); registerFactory("incorrect-tool-dig-speed", (value -> { float floatValue = MiscUtils.getAsFloat(value); return settings -> settings.incorrectToolSpeed(floatValue); 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 d083ef8f3..c0814beaf 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 @@ -5,11 +5,11 @@ import net.momirealms.craftengine.core.entity.Entity; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.world.BlockHitResult; import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.FluidCollisionRule; import org.jetbrains.annotations.Nullable; +import java.util.List; + public abstract class Player extends Entity implements NetWorkUser { public abstract boolean isSecondaryUseActive(); @@ -23,17 +23,19 @@ public abstract class Player extends Entity implements NetWorkUser { @Override public abstract Object serverPlayer(); + public abstract void sendPackets(List packet, boolean immediately); + public abstract float getDestroyProgress(Object blockState, BlockPos pos); + public abstract void setClientSideCanBreakBlock(boolean canBreak); + public abstract void stopMiningBlock(); public abstract void preventMiningBlock(); public abstract void abortMiningBlock(); - public abstract double getInteractionRange(); - - public abstract void abortDestroyProgress(); + public abstract double getCachedInteractionRange(); public abstract void onSwingHand(); @@ -49,10 +51,16 @@ public abstract class Player extends Entity implements NetWorkUser { public abstract boolean isAdventureMode(); + public abstract boolean canBreak(BlockPos pos, Object state); + + public abstract boolean canPlace(BlockPos pos, Object state); + public abstract void sendActionBar(Component text); public abstract boolean updateLastSuccessfulInteractionTick(int tick); + public abstract int lastSuccessfulInteractionTick(); + public abstract int gameTicks(); public abstract void swingHand(InteractionHand hand); @@ -73,7 +81,5 @@ public abstract class Player extends Entity implements NetWorkUser { public abstract void closeInventory(); - public abstract BlockHitResult rayTrace(double distance, FluidCollisionRule collisionRule); - public abstract void clearView(); } 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 2ccb72aa8..072010f25 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 @@ -51,7 +51,7 @@ public abstract class AbstractFontManager implements FontManager { @Override public void load() { - this.offsetFont = Optional.ofNullable(plugin.config().settings().getSection("offset-characters")) + this.offsetFont = Optional.ofNullable(plugin.config().settings().getSection("image.offset-characters")) .map(OffsetFont::new) .orElse(null); } 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 1d5ffbcbd..d45202853 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,5 +1,6 @@ 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; @@ -95,6 +96,17 @@ public class AbstractItem, I> implements Item { return this; } + @Override + public Item trim(Trim trim) { + this.factory.trim(this.item, trim); + return this; + } + + @Override + public Optional trim() { + return this.factory.trim(this.item); + } + @Override public Item customModelData(Integer data) { this.factory.customModelData(this.item, data); @@ -224,25 +236,40 @@ public class AbstractItem, I> implements Item { } @Override - public boolean hasComponent(String type) { + public boolean hasComponent(Key type) { return this.factory.hasComponent(this.item, type); } @Override - public void removeComponent(String type) { + public void removeComponent(Key type) { this.factory.removeComponent(this.item, type); } @Override - public Object getComponent(String type) { + public Object getComponent(Key type) { return this.factory.getComponent(this.item, type); } @Override - public void setComponent(String type, Object value) { + public Object getJavaTypeComponent(Key type) { + return this.factory.encodeJava(type, getComponent(type)); + } + + @Override + public JsonElement getJsonTypeComponent(Key type) { + return this.factory.encodeJson(type, getComponent(type)); + } + + @Override + public void setComponent(Key type, Object value) { this.factory.setComponent(this.item, type, value); } + @Override + public void resetComponent(Key type) { + this.factory.resetComponent(this.item, type); + } + @Override public I getItem() { return this.factory.getItem(this.item); 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 12c56a597..bcc2ffa4a 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 @@ -24,7 +24,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl protected static final Map>> VANILLA_ITEM_TAGS = new HashMap<>(); protected final Map> externalItemProviders = new HashMap<>(); - protected final Map>> dataFunctions = new HashMap<>(); + protected final Map>> dataFunctions = new HashMap<>(); protected final Map> customItems = new HashMap<>(); protected final Map>> customItemTags; protected final Map> cmdConflictChecker; @@ -37,7 +37,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl protected final List cachedSuggestions = new ArrayList<>(); protected final List cachedTotemSuggestions = new ArrayList<>(); - protected void registerDataFunction(Function> function, String... alias) { + protected void registerDataFunction(Function> function, String... alias) { for (String a : alias) { dataFunctions.put(a, function); } 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 07779468a..30e2be9ce 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 @@ -1,23 +1,25 @@ package net.momirealms.craftengine.core.item; -import net.kyori.adventure.key.Key; +import net.momirealms.craftengine.core.util.Key; public class ComponentKeys { - public static final String CUSTOM_MODEL_DATA = Key.key("minecraft", "custom_model_data").asString(); - public static final String CUSTOM_NAME = Key.key("minecraft", "custom_name").asString(); - public static final String ITEM_NAME = Key.key("minecraft", "item_name").asString(); - public static final String LORE = Key.key("minecraft", "lore").asString(); - public static final String DAMAGE = Key.key("minecraft", "damage").asString(); - public static final String MAX_DAMAGE = Key.key("minecraft", "max_damage").asString(); - public static final String ENCHANTMENT_GLINT_OVERRIDE = Key.key("minecraft", "enchantment_glint_override").asString(); - public static final String ENCHANTMENTS = Key.key("minecraft", "enchantments").asString(); - public static final String STORED_ENCHANTMENTS = Key.key("minecraft", "stored_enchantments").asString(); - public static final String UNBREAKABLE = Key.key("minecraft", "unbreakable").asString(); - public static final String MAX_STACK_SIZE = Key.key("minecraft", "max_stack_size").asString(); - public static final String EQUIPPABLE = Key.key("minecraft", "equippable").asString(); - public static final String ITEM_MODEL = Key.key("minecraft", "item_model").asString(); - public static final String TOOLTIP_STYLE = Key.key("minecraft", "tooltip_style").asString(); - public static final String JUKEBOX_PLAYABLE = Key.key("minecraft", "jukebox_playable").asString(); - public static final String TRIM = Key.key("minecraft", "trim").asString(); - public static final String REPAIR_COST = Key.key("minecraft", "repair_cost").asString(); + 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"); + public static final Key LORE = Key.of("minecraft", "lore"); + public static final Key DAMAGE = Key.of("minecraft", "damage"); + public static final Key MAX_DAMAGE = Key.of("minecraft", "max_damage"); + public static final Key ENCHANTMENT_GLINT_OVERRIDE = Key.of("minecraft", "enchantment_glint_override"); + public static final Key ENCHANTMENTS = Key.of("minecraft", "enchantments"); + public static final Key STORED_ENCHANTMENTS = Key.of("minecraft", "stored_enchantments"); + public static final Key UNBREAKABLE = Key.of("minecraft", "unbreakable"); + public static final Key MAX_STACK_SIZE = Key.of("minecraft", "max_stack_size"); + public static final Key EQUIPPABLE = Key.of("minecraft", "equippable"); + public static final Key ITEM_MODEL = Key.of("minecraft", "item_model"); + public static final Key TOOLTIP_STYLE = Key.of("minecraft", "tooltip_style"); + public static final Key JUKEBOX_PLAYABLE = Key.of("minecraft", "jukebox_playable"); + public static final Key TRIM = Key.of("minecraft", "trim"); + public static final Key REPAIR_COST = Key.of("minecraft", "repair_cost"); + public static final Key CUSTOM_DATA = Key.of("minecraft", "custom_data"); + public static final Key PROFILE = Key.of("minecraft", "profile"); } 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 916f7a510..6d72163f1 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 @@ -2,12 +2,13 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; -import net.momirealms.craftengine.core.item.modifier.ItemModifier; +import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.context.ContextHolder; import org.jetbrains.annotations.NotNull; import java.util.List; +import java.util.Map; public interface CustomItem extends BuildableItem { @@ -15,7 +16,17 @@ public interface CustomItem extends BuildableItem { Key material(); - List> modifiers(); + NetworkItemDataProcessor[] networkItemDataProcessors(); + + ItemDataModifier[] dataModifiers(); + + Map> dataModifierMap(); + + boolean hasClientBoundDataModifier(); + + ItemDataModifier[] clientBoundDataModifiers(); + + Map> clientBoundDataModifierMap(); ItemSettings settings(); @@ -24,7 +35,7 @@ public interface CustomItem extends BuildableItem { } default Item buildItem(Player player) { - return buildItem(new ItemBuildContext(player, ContextHolder.EMPTY)); + return buildItem(ItemBuildContext.of(player, ContextHolder.EMPTY)); } Item buildItem(ItemBuildContext context); @@ -37,13 +48,17 @@ public interface CustomItem extends BuildableItem { Builder material(Key material); - Builder modifiers(List> modifiers); + Builder dataModifier(ItemDataModifier modifier); - Builder modifier(ItemModifier modifier); + Builder dataModifiers(List> modifiers); + + Builder clientBoundDataModifier(ItemDataModifier modifier); + + Builder clientBoundDataModifiers(List> modifiers); Builder behavior(ItemBehavior behavior); - Builder behavior(List behaviors); + Builder behaviors(List behaviors); Builder settings(ItemSettings settings); 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 3895da0db..6ec67c438 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,5 +1,6 @@ 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; @@ -33,6 +34,10 @@ public interface Item { Item count(int amount); + Item trim(Trim trim); + + Optional trim(); + Item customModelData(Integer data); Optional customModelData(); @@ -91,13 +96,19 @@ public interface Item { boolean removeTag(Object... path); - boolean hasComponent(String type); + boolean hasComponent(Key type); - void removeComponent(String type); + void removeComponent(Key type); - Object getComponent(String type); + Object getComponent(Key type); - void setComponent(String type, Object value); + Object getJavaTypeComponent(Key type); + + JsonElement getJsonTypeComponent(Key type); + + void setComponent(Key type, Object value); + + void resetComponent(Key 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 c65a15740..ab36b86ad 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,5 +1,6 @@ package net.momirealms.craftengine.core.item; +import com.google.gson.JsonElement; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.util.Key; import org.jetbrains.annotations.Nullable; @@ -22,6 +23,8 @@ public abstract class ItemFactory

, I> public abstract Object encodeJava(Key componentType, @Nullable Object component); + protected abstract JsonElement encodeJson(Key type, Object component); + protected abstract ItemWrapper wrapInternal(I item); protected abstract Object getTag(ItemWrapper item, Object... path); @@ -32,13 +35,15 @@ public abstract class ItemFactory

, I> protected abstract boolean removeTag(ItemWrapper item, Object... path); - protected abstract void setComponent(ItemWrapper item, String type, Object value); + protected abstract void setComponent(ItemWrapper item, Key type, Object value); - protected abstract Object getComponent(ItemWrapper item, String type); + protected abstract Object getComponent(ItemWrapper item, Key type); - protected abstract boolean hasComponent(ItemWrapper item, String type); + protected abstract boolean hasComponent(ItemWrapper item, Key type); - protected abstract void removeComponent(ItemWrapper item, String type); + protected abstract void removeComponent(ItemWrapper item, Key type); + + protected abstract void resetComponent(ItemWrapper item, Key type); protected abstract void update(ItemWrapper item); @@ -112,6 +117,10 @@ public abstract class ItemFactory

, I> protected abstract Optional repairCost(ItemWrapper item); + protected abstract void trim(ItemWrapper item, Trim trim); + + protected abstract Optional trim(ItemWrapper item); + protected abstract ItemWrapper mergeCopy(ItemWrapper item1, ItemWrapper item2); protected abstract void merge(ItemWrapper item1, ItemWrapper item2); 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 9a2106eaf..3e5897b11 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 @@ -1,7 +1,7 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.item.modifier.EquippableModifier; -import net.momirealms.craftengine.core.item.modifier.ItemModifier; +import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; import net.momirealms.craftengine.core.pack.misc.EquipmentGeneration; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; @@ -19,13 +19,14 @@ public class ItemSettings { boolean canRepair = true; List anvilRepairItems = List.of(); boolean renameable = true; + boolean canPlaceRelatedVanillaBlock = false; private ItemSettings() {} - public List> modifiers() { - ArrayList> modifiers = new ArrayList<>(); + public List> modifiers() { + ArrayList> modifiers = new ArrayList<>(); if (VersionHelper.isVersionNewerThan1_21_2() && this.equipment != null && this.equipment.modernData() != null) modifiers.add(new EquippableModifier<>(this.equipment.modernData())); - // TODO 1.20 + // TODO 1.20 leather armor return modifiers; } @@ -45,6 +46,7 @@ public class ItemSettings { newSettings.canRepair = settings.canRepair; newSettings.anvilRepairItems = settings.anvilRepairItems; newSettings.renameable = settings.renameable; + newSettings.canPlaceRelatedVanillaBlock = settings.canPlaceRelatedVanillaBlock; return newSettings; } @@ -60,6 +62,10 @@ public class ItemSettings { return settings; } + public boolean canPlaceRelatedVanillaBlock() { + return canPlaceRelatedVanillaBlock; + } + public boolean canRepair() { return canRepair; } @@ -100,6 +106,11 @@ public class ItemSettings { return this; } + public ItemSettings canPlaceRelatedVanillaBlock(boolean canPlaceRelatedVanillaBlock) { + this.canPlaceRelatedVanillaBlock = canPlaceRelatedVanillaBlock; + return this; + } + public ItemSettings fuelTime(int fuelTime) { this.fuelTime = fuelTime; return this; @@ -175,6 +186,10 @@ public class ItemSettings { ); return settings -> settings.equipment(equipment); })); + registerFactory("can-place", (value -> { + boolean bool = (boolean) value; + return settings -> settings.canPlaceRelatedVanillaBlock(bool); + })); } private static void registerFactory(String id, ItemSettings.Modifier.Factory factory) { 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 new file mode 100644 index 000000000..352559f86 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/NetworkItemDataProcessor.java @@ -0,0 +1,27 @@ +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/Trim.java b/core/src/main/java/net/momirealms/craftengine/core/item/Trim.java new file mode 100644 index 000000000..4832e81fe --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/Trim.java @@ -0,0 +1,4 @@ +package net.momirealms.craftengine.core.item; + +public record Trim(String pattern, String material) { +} 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 af953405c..216c7740c 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 @@ -1,15 +1,57 @@ package net.momirealms.craftengine.core.item.modifier; +import com.google.gson.JsonElement; +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.util.GsonHelper; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.Pair; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -public class ComponentModifier implements ItemModifier { - private final Map arguments; +public class ComponentModifier implements ItemDataModifier { + private final List> arguments; + private JsonObject customData = null; public ComponentModifier(Map arguments) { - this.arguments = arguments; + List> pairs = new ArrayList<>(arguments.size()); + for (Map.Entry entry : arguments.entrySet()) { + Key key = Key.of(entry.getKey()); + if (key.equals(ComponentKeys.CUSTOM_DATA)) { + this.customData = parseJsonObjectValue(entry.getValue()); + } else { + pairs.add(new Pair<>(key, parseValue(entry.getValue()))); + } + } + this.arguments = pairs; + } + + public List> arguments() { + return arguments; + } + + private Object parseValue(Object value) { + if (value instanceof String string) { + if (string.startsWith("(json) ")) { + return GsonHelper.get().fromJson(string.substring("(json) ".length()), JsonElement.class); + } + } + return value; + } + + private JsonObject parseJsonObjectValue(Object value) { + if (value instanceof String string) { + if (string.startsWith("(json) ")) { + return GsonHelper.get().fromJson(string.substring("(json) ".length()), JsonObject.class); + } + } else if (value instanceof Map map) { + return (JsonObject) GsonHelper.get().toJsonTree(map, Map.class); + } + throw new UnsupportedOperationException("Invalid minecraft:custom_data value: " + value.toString()); } @Override @@ -19,10 +61,33 @@ public class ComponentModifier implements ItemModifier { @Override public void apply(Item item, ItemBuildContext context) { - for (Map.Entry entry : arguments.entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - item.setComponent(key, value); + for (Pair entry : this.arguments) { + item.setComponent(entry.left(), entry.right()); + } + if (this.customData != null) { + JsonObject tag = (JsonObject) item.getJsonTypeComponent(ComponentKeys.CUSTOM_DATA); + if (tag != null) { + item.setComponent(ComponentKeys.CUSTOM_DATA, GsonHelper.shallowMerge(this.customData, tag)); + } else { + item.setComponent(ComponentKeys.CUSTOM_DATA, this.customData); + } + } + } + + @Override + public void remove(Item item) { + 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); + } } } } 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 806faf77e..68a71ed3d 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 @@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.item.modifier; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; -public class CustomModelDataModifier implements ItemModifier { +public class CustomModelDataModifier implements ItemDataModifier { private final int argument; public CustomModelDataModifier(int argument) { @@ -19,4 +19,9 @@ public class CustomModelDataModifier implements ItemModifier { public void apply(Item item, ItemBuildContext context) { item.customModelData(argument); } + + @Override + public void remove(Item item) { + item.customModelData(null); + } } 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 index b0c1a95cd..cf468756c 100644 --- 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 @@ -5,7 +5,7 @@ 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 ItemModifier { +public class DisplayNameModifier implements ItemDataModifier { private final String argument; public DisplayNameModifier(String argument) { @@ -21,4 +21,9 @@ public class DisplayNameModifier implements ItemModifier { 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 ba9aefb51..15c2137b6 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 @@ -7,7 +7,7 @@ import net.momirealms.craftengine.core.item.ItemKeys; import java.util.List; -public class EnchantmentModifier implements ItemModifier { +public class EnchantmentModifier implements ItemDataModifier { private final List enchantments; public EnchantmentModifier(List enchantments) { @@ -24,4 +24,10 @@ public class EnchantmentModifier implements ItemModifier { if (item.vanillaId().equals(ItemKeys.ENCHANTED_BOOK)) item.setStoredEnchantments(enchantments); else item.setEnchantments(enchantments); } + + @Override + public void remove(Item item) { + if (item.vanillaId().equals(ItemKeys.ENCHANTED_BOOK)) item.setStoredEnchantments(null); + else item.setEnchantments(null); + } } 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 375ae9646..331fd6942 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 @@ -5,7 +5,7 @@ import net.momirealms.craftengine.core.item.EquipmentData; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; -public class EquippableModifier implements ItemModifier { +public class EquippableModifier implements ItemDataModifier { private final EquipmentData data; public EquippableModifier(EquipmentData data) { @@ -21,4 +21,9 @@ public class EquippableModifier implements ItemModifier { public void apply(Item item, ItemBuildContext context) { item.setComponent(ComponentKeys.EQUIPPABLE, this.data.toMap()); } + + @Override + public void remove(Item item) { + item.removeComponent(ComponentKeys.EQUIPPABLE); + } } 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 296810e37..76219096e 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 @@ -5,7 +5,7 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.plugin.CraftEngine; -public class ExternalModifier implements ItemModifier { +public class ExternalModifier implements ItemDataModifier { private final String id; private final ExternalItemProvider provider; @@ -30,4 +30,9 @@ public class ExternalModifier implements ItemModifier { 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 ba00849b2..2983e9fec 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 @@ -1,11 +1,29 @@ package net.momirealms.craftengine.core.item.modifier; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +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.util.Key; +import net.momirealms.craftengine.core.util.VersionHelper; -public class IdModifier implements ItemModifier { +import java.util.Map; +import java.util.function.BiConsumer; + +public class IdModifier implements ItemDataModifier { public static final String CRAFT_ENGINE_ID = "craftengine:id"; + private static final BiConsumer, String> ID_SETTER = VersionHelper.isVersionNewerThan1_20_5() ? + ((item, id) -> { + JsonElement element = item.getJsonTypeComponent(ComponentKeys.CUSTOM_DATA); + if (element instanceof JsonObject jo) { + jo.add(CRAFT_ENGINE_ID, new JsonPrimitive(id)); + item.setComponent(ComponentKeys.CUSTOM_DATA, jo); + } else { + item.setComponent(ComponentKeys.CUSTOM_DATA, Map.of(CRAFT_ENGINE_ID, id)); + } + }) : ((item, id) -> item.setTag(id, CRAFT_ENGINE_ID)); private final Key argument; public IdModifier(Key argument) { @@ -19,6 +37,11 @@ public class IdModifier implements ItemModifier { @Override public void apply(Item item, ItemBuildContext context) { - item.setTag(argument.toString(), CRAFT_ENGINE_ID); + ID_SETTER.accept(item, argument.toString()); + } + + @Override + public void remove(Item item) { + item.removeTag(CRAFT_ENGINE_ID); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifier.java similarity index 77% rename from core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemModifier.java rename to core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifier.java index d98c3992a..46258a96f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifier.java @@ -3,9 +3,11 @@ package net.momirealms.craftengine.core.item.modifier; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; -public interface ItemModifier { +public interface ItemDataModifier { String name(); void apply(Item item, ItemBuildContext context); + + void remove(Item item); } 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 973badeee..7d857de0b 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 @@ -5,7 +5,7 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.util.Key; -public class ItemModelModifier implements ItemModifier { +public class ItemModelModifier implements ItemDataModifier { private final Key data; public ItemModelModifier(Key data) { @@ -19,6 +19,11 @@ public class ItemModelModifier implements ItemModifier { @Override public void apply(Item item, ItemBuildContext context) { - item.setComponent(ComponentKeys.ITEM_MODEL, this.data.toString()); + item.setComponent(ComponentKeys.ITEM_MODEL, this.data.toString()); + } + + @Override + public void remove(Item item) { + item.removeComponent(ComponentKeys.ITEM_MODEL); } } 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 624941d51..8ba7e0967 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 @@ -5,7 +5,7 @@ import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.AdventureHelper; -public class ItemNameModifier implements ItemModifier { +public class ItemNameModifier implements ItemDataModifier { private final String argument; public ItemNameModifier(String argument) { @@ -21,4 +21,9 @@ public class ItemNameModifier implements ItemModifier { public void apply(Item item, ItemBuildContext context) { item.itemName(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(this.argument, context.tagResolvers()))); } + + @Override + public void remove(Item item) { + item.itemName(null); + } } 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 6c4aa4080..158ff690e 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 @@ -8,7 +8,7 @@ import net.momirealms.craftengine.core.util.VersionHelper; import java.util.Map; -public class JukeboxSongModifier implements ItemModifier { +public class JukeboxSongModifier implements ItemDataModifier { private final Key song; public JukeboxSongModifier(Key song) { @@ -31,4 +31,9 @@ public class JukeboxSongModifier implements ItemModifier { )); } } + + @Override + public void remove(Item item) { + item.removeComponent(ComponentKeys.JUKEBOX_PLAYABLE); + } } 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 7d8e5d1ae..40ef6c2c5 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 @@ -7,7 +7,7 @@ import net.momirealms.craftengine.core.util.AdventureHelper; import java.util.List; -public class LoreModifier implements ItemModifier { +public class LoreModifier implements ItemDataModifier { private final List argument; public LoreModifier(List argument) { @@ -24,4 +24,9 @@ public class LoreModifier implements ItemModifier { item.lore(argument.stream().map(it -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize( it, context.tagResolvers()))).toList()); } + + @Override + public void remove(Item item) { + item.lore(null); + } } 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 06942fa79..69c29c859 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 @@ -8,13 +8,17 @@ import net.momirealms.craftengine.core.util.TypeUtils; import java.util.LinkedHashMap; import java.util.Map; -public class TagsModifier implements ItemModifier { +public class TagsModifier implements ItemDataModifier { private final Map arguments; public TagsModifier(Map arguments) { this.arguments = mapToMap(arguments); } + public Map arguments() { + return arguments; + } + @Override public String name() { return "tags"; @@ -29,6 +33,13 @@ public class TagsModifier implements ItemModifier { } } + @Override + public void remove(Item item) { + for (Map.Entry entry : arguments.entrySet()) { + item.removeTag(entry.getKey()); + } + } + private static Map mapToMap(final Map source) { Map resultMap = new LinkedHashMap<>(); recursiveMapProcessing(source, resultMap); 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 dafcde51e..753b8ae8e 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 @@ -5,7 +5,7 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.util.Key; -public class TooltipStyleModifier implements ItemModifier { +public class TooltipStyleModifier implements ItemDataModifier { private final Key argument; public TooltipStyleModifier(Key argument) { @@ -21,4 +21,9 @@ public class TooltipStyleModifier implements ItemModifier { public void apply(Item item, ItemBuildContext context) { item.setComponent(ComponentKeys.TOOLTIP_STYLE, argument.toString()); } + + @Override + public void remove(Item item) { + item.removeComponent(ComponentKeys.TOOLTIP_STYLE); + } } 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 09086a956..89777acd2 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 @@ -3,11 +3,10 @@ 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.Trim; import net.momirealms.craftengine.core.util.VersionHelper; -import java.util.Map; - -public class TrimModifier implements ItemModifier { +public class TrimModifier implements ItemDataModifier { private final String material; private final String pattern; @@ -23,14 +22,22 @@ public class TrimModifier implements ItemModifier { @Override public void apply(Item item, ItemBuildContext context) { + item.trim(new Trim(this.material, this.pattern)); + if (VersionHelper.isVersionNewerThan1_20_5()) { - item.setComponent(ComponentKeys.TRIM, Map.of( - "pattern", this.pattern, - "material", this.material - )); + } else { - item.setTag(this.material, "Trim", "material"); - item.setTag(this.pattern, "Trim", "pattern"); + + } + } + + @Override + public void remove(Item item) { + item.trim(null); + if (VersionHelper.isVersionNewerThan1_20_5()) { + item.removeComponent(ComponentKeys.TRIM); + } else { + item.removeTag("Trim"); } } } 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 90cc19fba..c673c82a4 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 @@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.item.modifier; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemBuildContext; -public class UnbreakableModifier implements ItemModifier { +public class UnbreakableModifier implements ItemDataModifier { private final boolean argument; public UnbreakableModifier(boolean argument) { @@ -17,6 +17,13 @@ public class UnbreakableModifier implements ItemModifier { @Override public void apply(Item item, ItemBuildContext context) { - item.unbreakable(argument); + item.unbreakable(this.argument); + } + + @Override + public void remove(Item item) { + if (this.argument) { + item.unbreakable(false); + } } } 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 46f9e9909..309a5e41f 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 @@ -217,9 +217,9 @@ 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.toString()); + Object componentObj = item1.getComponent(component); if (componentObj != null) { - item3.setComponent(component.toString(), CraftEngine.instance().itemManager().encodeJava(component, componentObj)); + item3.setComponent(component, componentObj); } } } 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 24d1cda5a..b8ff6f265 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 @@ -52,17 +52,7 @@ public abstract class AbstractPackManager implements PackManager { public static final Set VANILLA_ITEM_TEXTURES = new HashSet<>(); public static final Set VANILLA_BLOCK_TEXTURES = new HashSet<>(); public static final Set VANILLA_FONT_TEXTURES = new HashSet<>(); - - private final CraftEngine plugin; - private final BiConsumer eventDispatcher; - private final Map loadedPacks = new HashMap<>(); - private final Map sectionParsers = new HashMap<>(); - private final TreeMap> cachedConfigs = new TreeMap<>(); - private static final byte[] emptyImage; - protected BiConsumer zipGenerator; - protected String packHash; - protected UUID packUUID; - + private static final byte[] EMPTY_IMAGE; static { var stream = new ByteArrayOutputStream(); try { @@ -70,9 +60,19 @@ public abstract class AbstractPackManager implements PackManager { } catch (IOException e) { throw new RuntimeException(e); } - emptyImage = stream.toByteArray(); + EMPTY_IMAGE = stream.toByteArray(); } + private final CraftEngine plugin; + private final BiConsumer eventDispatcher; + private final Map loadedPacks = new HashMap<>(); + private final Map sectionParsers = new HashMap<>(); + private final TreeMap> cachedConfigs = new TreeMap<>(); + + protected BiConsumer zipGenerator; + protected String packHash; + protected UUID packUUID; + public AbstractPackManager(CraftEngine plugin, BiConsumer eventDispatcher) { this.plugin = plugin; this.eventDispatcher = eventDispatcher; @@ -233,7 +233,6 @@ public abstract class AbstractPackManager implements PackManager { Files.createDirectories(resourcesFolder); this.saveDefaultConfigs(); } - try (DirectoryStream paths = Files.newDirectoryStream(resourcesFolder)) { for (Path path : paths) { if (!Files.isDirectory(path)) { @@ -245,14 +244,16 @@ public abstract class AbstractPackManager implements PackManager { String description = null; String version = null; String author = null; + boolean enable = true; if (Files.exists(metaFile) && Files.isRegularFile(metaFile)) { YamlDocument metaYML = Config.instance().loadYamlData(metaFile.toFile()); + enable = metaYML.getBoolean("enable", true); namespace = metaYML.getString("namespace", namespace); description = metaYML.getString("description"); version = metaYML.getString("version"); author = metaYML.getString("author"); } - Pack pack = new Pack(path, new PackMeta(author, description, version, namespace)); + Pack pack = new Pack(path, new PackMeta(author, description, version, namespace), enable); this.loadedPacks.put(path.getFileName().toString(), pack); this.plugin.logger().info("Loaded pack: " + pack.folder().getFileName() + ". Default namespace: " + namespace); } @@ -396,6 +397,7 @@ public abstract class AbstractPackManager implements PackManager { private void loadResourceConfigs(Predicate predicate) { long o1 = System.nanoTime(); for (Pack pack : loadedPacks()) { + if (!pack.enabled()) continue; Pair, List> files = FileUtils.getConfigsDeeply(pack.configurationFolder()); for (Path path : files.left()) { try (InputStreamReader inputStream = new InputStreamReader(new FileInputStream(path.toFile()), StandardCharsets.UTF_8)) { @@ -571,7 +573,7 @@ public abstract class AbstractPackManager implements PackManager { } try { GsonHelper.writeJsonFile(json, jsonPath); - Files.write(pngPath, emptyImage); + Files.write(pngPath, EMPTY_IMAGE); } catch (IOException e) { this.plugin.logger().severe("Error writing particles file", e); } 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 09b70fffe..3953b2076 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 @@ -15,16 +15,26 @@ import java.nio.file.Path; public class Pack { private final Path folder; private final PackMeta meta; + private final boolean enabled; - public Pack(Path folder, PackMeta meta) { + public Pack(Path folder, PackMeta meta, boolean enabled) { this.folder = folder; this.meta = meta; + this.enabled = enabled; + } + + public String name() { + return folder.getFileName().toString(); } public String namespace() { return meta.namespace(); } + public boolean enabled() { + return enabled; + } + public PackMeta meta() { return meta; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/model/CompositeItemModel.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/CompositeItemModel.java index f37e3aeed..58487f35e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/model/CompositeItemModel.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/CompositeItemModel.java @@ -53,6 +53,9 @@ public class CompositeItemModel implements ItemModel { @Override public ItemModel create(Map arguments) { List> models = (List>) arguments.get("models"); + if (models == null || models.isEmpty()) { + throw new IllegalArgumentException("No 'models' specified for 'minecraft:composite'"); + } List modelList = new ArrayList<>(); for (Map model : models) { modelList.add(ItemModels.fromMap(model)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/model/ConditionItemModel.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/ConditionItemModel.java index 17a962483..f30d9878f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/model/ConditionItemModel.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/ConditionItemModel.java @@ -64,8 +64,8 @@ public class ConditionItemModel implements ItemModel { @Override public ItemModel create(Map arguments) { ConditionProperty property = ConditionProperties.fromMap(arguments); - Map onTrue = Objects.requireNonNull((Map) arguments.get("on-true")); - Map onFalse = Objects.requireNonNull((Map) arguments.get("on-false")); + Map onTrue = Objects.requireNonNull((Map) arguments.get("on-true"), "No 'on-true' set for 'minecraft:condition'"); + Map onFalse = Objects.requireNonNull((Map) arguments.get("on-false"), "No 'on-false' set for 'minecraft:condition'"); return new ConditionItemModel(property, ItemModels.fromMap(onTrue), ItemModels.fromMap(onFalse)); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/model/RangeDispatchItemModel.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/RangeDispatchItemModel.java index 505059880..1efe99241 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/model/RangeDispatchItemModel.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/RangeDispatchItemModel.java @@ -108,7 +108,7 @@ public class RangeDispatchItemModel implements ItemModel { } return new RangeDispatchItemModel(property, scale, fallback == null ? null : ItemModels.fromMap(fallback), entryMap); } - throw new IllegalArgumentException("No entries set for range dispatch"); + throw new IllegalArgumentException("No 'entries' set for 'minecraft:range_dispatch'"); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/model/SelectItemModel.java b/core/src/main/java/net/momirealms/craftengine/core/pack/model/SelectItemModel.java index d11397212..38ed5829a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/model/SelectItemModel.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/model/SelectItemModel.java @@ -117,7 +117,7 @@ public class SelectItemModel implements ItemModel { } return new SelectItemModel(property, whenMap, fallback == null ? null : ItemModels.fromMap(fallback)); } - throw new IllegalArgumentException("No cases set for select"); + throw new IllegalArgumentException("No 'cases' set for 'minecraft:select'"); } } } 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 4c9540d7a..0987a2c35 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 @@ -32,6 +32,7 @@ import net.momirealms.craftengine.core.sound.SoundManager; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.WorldManager; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Logger; import java.util.ArrayList; import java.util.List; @@ -90,8 +91,8 @@ public abstract class CraftEngine implements Plugin { } public void onPluginLoad() { - ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addFilter(new LogFilter()); - ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addFilter(new DisconnectLogFilter()); + ((Logger) LogManager.getRootLogger()).addFilter(new LogFilter()); + ((Logger) LogManager.getRootLogger()).addFilter(new DisconnectLogFilter()); this.dependencyManager = new DependencyManagerImpl(this); ArrayList dependenciesToLoad = new ArrayList<>(); dependenciesToLoad.addAll(commonDependencies()); @@ -115,72 +116,77 @@ public abstract class CraftEngine implements Plugin { public CompletableFuture reloadPlugin(Executor asyncExecutor, Executor syncExecutor, boolean reloadRecipe) { CompletableFuture future = new CompletableFuture<>(); asyncExecutor.execute(() -> { - if (this.isReloading) { - future.complete(ReloadResult.failure()); - return; - } - this.isReloading = true; - long time1 = System.currentTimeMillis(); - // firstly reload main config - this.config.load(); - // reset debugger - this.debugger = Config.debug() ? (s) -> logger.info("[Debug] " + s.get()) : (s) -> {}; - // now we reload the translations - this.translationManager.reload(); - // clear the outdated cache by reloading the managers - this.templateManager.reload(); - this.furnitureManager.reload(); - this.fontManager.reload(); - this.itemManager.reload(); - this.soundManager.reload(); - this.itemBrowserManager.reload(); - this.blockManager.reload(); - this.worldManager.reload(); - this.vanillaLootManager.reload(); - this.guiManager.reload(); - this.packManager.reload(); - if (reloadRecipe) { - this.recipeManager.reload(); - } + long asyncTime = -1; try { - // now we load resources - this.packManager.loadResources(reloadRecipe); - } catch (Exception e) { - this.logger().warn("Failed to load resources folder", e); - } - // init suggestions and packet mapper - this.blockManager.delayedLoad(); - // handle some special client lang for instance block_name - this.translationManager.delayedLoad(); - // init suggestions - this.furnitureManager.delayedLoad(); - // sort the categories - this.itemBrowserManager.delayedLoad(); - // collect illegal characters from minecraft:default font - this.fontManager.delayedLoad(); - if (reloadRecipe) { - // convert data pack recipes - this.recipeManager.delayedLoad(); - } - long time2 = System.currentTimeMillis(); - long asyncTime = time2 - time1; - syncExecutor.execute(() -> { - try { - long time3 = System.currentTimeMillis(); - // register songs - this.soundManager.runDelayedSyncTasks(); - // register recipes - if (reloadRecipe) { - this.recipeManager.runDelayedSyncTasks(); - } - long time4 = System.currentTimeMillis(); - long syncTime = time4 - time3; - this.reloadEventDispatcher.accept(this); - future.complete(ReloadResult.success(asyncTime, syncTime)); - } finally { - this.isReloading = false; + if (this.isReloading) { + future.complete(ReloadResult.failure()); + return; } - }); + this.isReloading = true; + long time1 = System.currentTimeMillis(); + // firstly reload main config + this.config.load(); + // reset debugger + this.debugger = Config.debug() ? (s) -> logger.info("[Debug] " + s.get()) : (s) -> {}; + // now we reload the translations + this.translationManager.reload(); + // clear the outdated cache by reloading the managers + this.templateManager.reload(); + this.furnitureManager.reload(); + this.fontManager.reload(); + this.itemManager.reload(); + this.soundManager.reload(); + this.itemBrowserManager.reload(); + this.blockManager.reload(); + this.worldManager.reload(); + this.vanillaLootManager.reload(); + this.guiManager.reload(); + this.packManager.reload(); + if (reloadRecipe) { + this.recipeManager.reload(); + } + try { + // now we load resources + this.packManager.loadResources(reloadRecipe); + } catch (Exception e) { + this.logger().warn("Failed to load resources folder", e); + } + // init suggestions and packet mapper + this.blockManager.delayedLoad(); + // handle some special client lang for instance block_name + this.translationManager.delayedLoad(); + // init suggestions + this.furnitureManager.delayedLoad(); + // sort the categories + this.itemBrowserManager.delayedLoad(); + // collect illegal characters from minecraft:default font + this.fontManager.delayedLoad(); + if (reloadRecipe) { + // convert data pack recipes + this.recipeManager.delayedLoad(); + } + long time2 = System.currentTimeMillis(); + asyncTime = time2 - time1; + } finally { + long finalAsyncTime = asyncTime; + syncExecutor.execute(() -> { + try { + long time3 = System.currentTimeMillis(); + // register songs + this.soundManager.runDelayedSyncTasks(); + // register recipes + if (reloadRecipe) { + this.recipeManager.runDelayedSyncTasks(); + } + long time4 = System.currentTimeMillis(); + long syncTime = time4 - time3; + this.reloadEventDispatcher.accept(this); + future.complete(ReloadResult.success(finalAsyncTime, syncTime)); + } finally { + this.isReloading = false; + } + }); + } }); return future; } 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 13f81cb36..df095afe5 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 @@ -111,6 +111,11 @@ public class Config { protected boolean furniture$hide_base_entity; protected boolean block$sound_system$enable; + protected boolean block$simplify_adventure_break_check; + protected boolean block$simplify_adventure_place_check; + protected boolean block$predict_breaking; + protected int block$predict_breaking_interval; + protected double block$extended_interaction_range; protected boolean recipe$enable; protected boolean recipe$disable_vanilla_recipes$all; @@ -281,6 +286,11 @@ public class Config { // block block$sound_system$enable = config.getBoolean("block.sound-system.enable", true); + block$simplify_adventure_break_check = config.getBoolean("block.simplify-adventure-break-check", false); + block$simplify_adventure_place_check = config.getBoolean("block.simplify-adventure-place-check", false); + block$predict_breaking = config.getBoolean("block.predict-breaking.enable", true); + block$predict_breaking_interval = Math.max(config.getInt("block.predict-breaking.interval", 10), 1); + block$extended_interaction_range = Math.max(config.getDouble("block.predict-breaking.extended-interaction-range", 0.5), 0.0); // recipe recipe$enable = config.getBoolean("recipe.enable", true); @@ -390,6 +400,14 @@ public class Config { return instance.block$sound_system$enable; } + public static boolean simplifyAdventureBreakCheck() { + return instance.block$simplify_adventure_break_check; + } + + public static boolean simplifyAdventurePlaceCheck() { + return instance.block$simplify_adventure_place_check; + } + public static boolean enableRecipeSystem() { return instance.recipe$enable; } @@ -662,6 +680,18 @@ public class Config { return instance.image$intercept_packets$set_score; } + public static boolean predictBreaking() { + return instance.block$predict_breaking; + } + + public static int predictBreakingInterval() { + return instance.block$predict_breaking_interval; + } + + public static double extendedInteractionRange() { + return instance.block$extended_interaction_range; + } + public YamlDocument loadOrCreateYamlData(String fileName) { File file = new File(this.plugin.dataFolderFile(), fileName); if (!file.exists()) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java index 1151d1fe2..3d61517ab 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/locale/MessageConstants.java @@ -21,4 +21,9 @@ public interface MessageConstants { TranslatableComponent.Builder COMMAND_SEARCH_USAGE_NOT_FOUND = Component.translatable().key("command.search_usage.not_found"); TranslatableComponent.Builder COMMAND_SEARCH_USAGE_NO_ITEM = Component.translatable().key("command.search_usage.no_item"); TranslatableComponent.Builder COMMAND_TOTEM_NOT_TOTEM = Component.translatable().key("command.totem_animation.failure.not_totem"); + TranslatableComponent.Builder COMMAND_RESOURCE_ENABLE_SUCCESS = Component.translatable().key("command.resource.enable.success"); + TranslatableComponent.Builder COMMAND_RESOURCE_ENABLE_FAILURE = Component.translatable().key("command.resource.enable.failure.unknown"); + TranslatableComponent.Builder COMMAND_RESOURCE_DISABLE_SUCCESS = Component.translatable().key("command.resource.disable.success"); + TranslatableComponent.Builder COMMAND_RESOURCE_DISABLE_FAILURE = Component.translatable().key("command.resource.disable.failure.unknown"); + TranslatableComponent.Builder COMMAND_RESOURCE_LIST = Component.translatable().key("command.resource.list"); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/IndexedArgumentTag.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/IndexedArgumentTag.java index 5b8c768dc..098b4f6e0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/IndexedArgumentTag.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/text/minimessage/IndexedArgumentTag.java @@ -34,7 +34,7 @@ public class IndexedArgumentTag implements TagResolver { throw ctx.newException("Invalid argument number", arguments); } - return Tag.inserting(argumentComponents.get(index)); + return Tag.selfClosingInserting(argumentComponents.get(index)); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ArrayUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ArrayUtils.java index e5bf13550..85e8beddd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ArrayUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ArrayUtils.java @@ -3,6 +3,7 @@ package net.momirealms.craftengine.core.util; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; public class ArrayUtils { @@ -59,4 +60,14 @@ public class ArrayUtils { public static boolean isEmpty(Object[] array) { return array == null || array.length == 0; } + + public static T[] collectionToArray(Collection array, Class clazz) { + @SuppressWarnings("unchecked") + T[] res = (T[]) Array.newInstance(clazz, array.size()); + int i = 0; + for (T item : array) { + res[i++] = item; + } + return res; + } } 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 d6fda7904..283afc968 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 @@ -21,7 +21,7 @@ public class MiscUtils { if (obj instanceof Map map) { return (Map) map; } - throw new IllegalArgumentException("Expected Map, got: " + obj.getClass().getSimpleName()); + throw new IllegalArgumentException("Expected Map, got: " + (obj == null ? null : obj.getClass().getSimpleName())); } @SuppressWarnings("unchecked") diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/WorldEvents.java b/core/src/main/java/net/momirealms/craftengine/core/world/WorldEvents.java new file mode 100644 index 000000000..255b53afa --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/WorldEvents.java @@ -0,0 +1,87 @@ +package net.momirealms.craftengine.core.world; + +public class WorldEvents { + public static final int DISPENSER_DISPENSES = 1000; + public static final int DISPENSER_FAILS_TO_DISPENSE = 1001; + public static final int DISPENSER_SHOOTS = 1002; + public static final int FIREWORK_SHOT = 1004; + public static final int FIRE_EXTINGUISHED = 1009; + public static final int PLAY_RECORD = 1010; + public static final int STOP_RECORD = 1011; + public static final int GHAST_WARNS = 1015; + public static final int GHAST_SHOOTS = 1016; + public static final int ENDER_DRAGON_SHOOTS = 1017; + public static final int BLAZE_SHOOTS = 1018; + public static final int ZOMBIE_ATTACKS_WOODEN_DOOR = 1019; + public static final int ZOMBIE_ATTACKS_IRON_DOOR = 1020; + public static final int ZOMBIE_BREAKS_WOODEN_DOOR = 1021; + public static final int WITHER_BREAKS_BLOCK = 1022; + public static final int WITHER_SPAWNED = 1023; + public static final int WITHER_SHOOTS = 1024; + public static final int BAT_TAKES_OFF = 1025; + public static final int ZOMBIE_INFECTS = 1026; + public static final int ZOMBIE_VILLAGER_CONVERTED = 1027; + public static final int ENDER_DRAGON_DIES = 1028; + public static final int ANVIL_DESTROYED = 1029; + public static final int ANVIL_USED = 1030; + public static final int ANVIL_LANDS = 1031; + public static final int PORTAL_TRAVEL = 1032; + public static final int CHORUS_FLOWER_GROWS = 1033; + public static final int CHORUS_FLOWER_DIES = 1034; + public static final int BREWING_STAND_BREWS = 1035; + public static final int END_PORTAL_CREATED = 1038; + public static final int PHANTOM_BITES = 1039; + public static final int ZOMBIE_CONVERTS_TO_DROWNED = 1040; + public static final int HUSK_CONVERTS_TO_ZOMBIE_BY_DROWNING = 1041; + public static final int GRINDSTONE_USED = 1042; + public static final int BOOK_PAGE_TURNED = 1043; + public static final int SMITHING_TABLE_USED = 1044; + public static final int POINTED_DRIPSTONE_LANDING = 1045; + public static final int LAVA_DRIPPING_ON_CAULDRON_FROM_DRIPSTONE = 1046; + public static final int WATER_DRIPPING_ON_CAULDRON_FROM_DRIPSTONE = 1047; + public static final int SKELETON_CONVERTS_TO_STRAY = 1048; + public static final int CRAFTER_SUCCESSFULLY_CRAFTS_ITEM = 1049; + public static final int CRAFTER_FAILS_TO_CRAFT_ITEM = 1050; + public static final int COMPOSTER_COMPOSTS = 1500; + public static final int LAVA_CONVERTS_BLOCK = 1501; + public static final int REDSTONE_TORCH_BURNS_OUT = 1502; + public static final int ENDER_EYE_PLACED_IN_END_PORTAL_FRAME = 1503; + public static final int FLUID_DRIPS_FROM_DRIPSTONE = 1504; + public static final int BONE_MEAL_PARTICLES_AND_SOUND = 1505; + public static final int DISPENSER_ACTIVATION_SMOKE = 2000; + public static final int BLOCK_BREAK_EFFECT = 2001; + public static final int SPLASH_POTION_EFFECT = 2002; + public static final int ENDER_EYE_ENTITY_BREAK_ANIMATION = 2003; + public static final int SPAWNER_SPAWNS_MOB = 2004; + public static final int DRAGON_BREATH = 2006; + public static final int INSTANT_SPLASH_POTION = 2007; + public static final int ENDER_DRAGON_DESTROYS_BLOCK = 2008; + public static final int WET_SPONGE_VAPORIZES = 2009; + public static final int CRAFTER_ACTIVATION_SMOKE = 2010; + public static final int BEE_FERTILIZES_PLANT = 2011; + public static final int TURTLE_EGG_PLACED = 2012; + public static final int SMASH_ATTACK = 2013; + public static final int END_GATEWAY_SPAWNS = 3000; + public static final int ENDER_DRAGON_RESURRECTED = 3001; + public static final int ELECTRIC_SPARK = 3002; + public static final int COPPER_APPLY_WAX = 3003; + public static final int COPPER_REMOVE_WAX = 3004; + public static final int COPPER_SCRAPE_OXIDATION = 3005; + public static final int SCULK_CHARGE = 3006; + public static final int SCULK_SHRIEKER_SHRIEK = 3007; + public static final int BLOCK_FINISHED_BRUSHING = 3008; + public static final int SNIFFER_EGG_CRACKS = 3009; + public static final int TRIAL_SPAWNER_SPAWNS_MOB_AT_SPAWNER = 3011; + public static final int TRIAL_SPAWNER_SPAWNS_MOB_AT_LOCATION = 3012; + public static final int TRIAL_SPAWNER_DETECTS_PLAYER = 3013; + public static final int TRIAL_SPAWNER_EJECTS_ITEM = 3014; + public static final int VAULT_ACTIVATES = 3015; + public static final int VAULT_DEACTIVATES = 3016; + public static final int VAULT_EJECTS_ITEM = 3017; + public static final int COBWEB_WEAVED = 3018; + public static final int OMINOUS_TRIAL_SPAWNER_DETECTS_PLAYER = 3019; + public static final int TRIAL_SPAWNER_TURNS_OMINOUS = 3020; + public static final int OMINOUS_ITEM_SPAWNER_SPAWNS_ITEM = 3021; + + private WorldEvents() {} +} diff --git a/gradle.properties b/gradle.properties index d069cdc54..eab4ffe19 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,18 +2,18 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.47.1 -config_version=25 +project_version=0.0.48 +config_version=26 lang_version=4 project_group=net.momirealms latest_supported_version=1.21.5 -latest_minecraft_version=1.21.4 +latest_minecraft_version=1.21.5 # Supported languages supported_languages=en,zh_cn,zh_tw,es # Dependency settings -paper_version=1.21.4 +paper_version=1.21.5 jetbrains_annotations_version=26.0.2 slf4j_version=2.0.17 log4j_version=2.24.3 @@ -51,7 +51,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 anti_grief_version=0.13 -nms_helper_version=0.56.2 +nms_helper_version=0.58.8 # Ignite Dependencies mixinextras_version=0.4.1 mixin_version=0.15.2+mixin.0.8.7 diff --git a/server-mod/build.gradle.kts b/server-mod/v1_20_5/build.gradle.kts similarity index 86% rename from server-mod/build.gradle.kts rename to server-mod/v1_20_5/build.gradle.kts index dc788e3d7..272257de9 100644 --- a/server-mod/build.gradle.kts +++ b/server-mod/v1_20_5/build.gradle.kts @@ -15,7 +15,7 @@ repositories { dependencies { implementation(project(":shared")) remapper("net.fabricmc:tiny-remapper:${rootProject.properties["tiny_remapper_version"]}:fat") - paperweightDevelopmentBundle("io.papermc.paper:dev-bundle:${rootProject.properties["latest_minecraft_version"]}-R0.1-SNAPSHOT") + paperweightDevelopmentBundle("io.papermc.paper:dev-bundle:1.21.4-R0.1-SNAPSHOT") compileOnly("space.vectrix.ignite:ignite-api:${rootProject.properties["ignite_version"]}") compileOnly("net.fabricmc:sponge-mixin:${rootProject.properties["mixin_version"]}") compileOnly("io.github.llamalad7:mixinextras-common:${rootProject.properties["mixinextras_version"]}") @@ -44,7 +44,7 @@ artifacts { tasks { shadowJar { archiveClassifier = "" - archiveFileName = "${rootProject.name}-ignite-mod-${rootProject.properties["project_version"]}+${rootProject.properties["latest_minecraft_version"]}.jar" + archiveFileName = "${rootProject.name}-ignite-mod-${rootProject.properties["project_version"]}+mc1.20.5-1.21.4-mojmap.jar" destinationDirectory.set(file("$rootDir/target")) } } diff --git a/server-mod/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java similarity index 100% rename from server-mod/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java rename to server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java diff --git a/server-mod/src/main/java/net/momirealms/craftengine/mod/CraftEngineBlock.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java similarity index 99% rename from server-mod/src/main/java/net/momirealms/craftengine/mod/CraftEngineBlock.java rename to server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java index f7d481ee7..8b3039c7b 100644 --- a/server-mod/src/main/java/net/momirealms/craftengine/mod/CraftEngineBlock.java +++ b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.mod; +package net.momirealms.craftengine.mod.block; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -14,6 +14,7 @@ import net.minecraft.world.level.block.*; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; +import net.momirealms.craftengine.mod.CraftEnginePlugin; import net.momirealms.craftengine.mod.util.NoteBlockUtils; import net.momirealms.craftengine.shared.ObjectHolder; import net.momirealms.craftengine.shared.block.*; diff --git a/server-mod/src/main/java/net/momirealms/craftengine/mod/mixin/MixinBlocks.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/CustomBlocks.java similarity index 67% rename from server-mod/src/main/java/net/momirealms/craftengine/mod/mixin/MixinBlocks.java rename to server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/CustomBlocks.java index ebae61b28..36af40eab 100644 --- a/server-mod/src/main/java/net/momirealms/craftengine/mod/mixin/MixinBlocks.java +++ b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/CustomBlocks.java @@ -1,6 +1,5 @@ -package net.momirealms.craftengine.mod.mixin; +package net.momirealms.craftengine.mod.block; -import com.mojang.brigadier.StringReader; import net.minecraft.commands.arguments.blocks.BlockStateParser; import net.minecraft.core.Registry; import net.minecraft.core.registries.BuiltInRegistries; @@ -8,54 +7,54 @@ import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; -import net.momirealms.craftengine.mod.CraftEngineBlock; import net.momirealms.craftengine.mod.CraftEnginePlugin; import net.momirealms.craftengine.mod.util.NoteBlockUtils; +import net.momirealms.craftengine.mod.util.Reflections; import org.bukkit.configuration.file.YamlConfiguration; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -@Mixin(value = Blocks.class) -public abstract class MixinBlocks { +public class CustomBlocks { - @Inject(method = "", at = @At("RETURN")) - private static void onBlocksInit(CallbackInfo ci) { + public static void register() { CraftEnginePlugin.setVanillaRegistrySize(Block.BLOCK_STATE_REGISTRY.size()); ResourceLocation noteBlock = ResourceLocation.fromNamespaceAndPath("minecraft", "note_block"); Map map = loadMappingsAndAdditionalBlocks(); for (Map.Entry entry : map.entrySet()) { ResourceLocation replacedBlockId = entry.getKey(); boolean isNoteBlock = replacedBlockId.equals(noteBlock); - Block replacedBlock = BuiltInRegistries.BLOCK.getValue(replacedBlockId); - for (int i = 0; i < entry.getValue(); i++) { - ResourceLocation location = ResourceLocation.fromNamespaceAndPath("craftengine", replacedBlockId.getPath() + "_" + i); - ResourceKey resourceKey = ResourceKey.create(Registries.BLOCK, location); - BlockBehaviour.Properties properties = BlockBehaviour.Properties.of() - .setId(resourceKey); - if (!replacedBlock.hasCollision) { - properties.noCollission(); + try { + Block replacedBlock = (Block) Reflections.method$DefaultedRegistry$get.invoke(BuiltInRegistries.BLOCK, replacedBlockId); + for (int i = 0; i < entry.getValue(); i++) { + ResourceLocation location = ResourceLocation.fromNamespaceAndPath("craftengine", replacedBlockId.getPath() + "_" + i); + ResourceKey resourceKey = ResourceKey.create(Registries.BLOCK, location); + BlockBehaviour.Properties properties = BlockBehaviour.Properties.of(); + if (Reflections.field$BlockBehaviour$Properties$id != null) { + Reflections.field$BlockBehaviour$Properties$id.set(properties, resourceKey); + } + if (!replacedBlock.hasCollision) { + properties.noCollission(); + } + CraftEngineBlock block = new CraftEngineBlock(properties); + if (isNoteBlock) { + block.setNoteBlock(true); + NoteBlockUtils.CLIENT_SIDE_NOTE_BLOCKS.add(block.defaultBlockState()); + } + Registry.register(BuiltInRegistries.BLOCK, location, block); + Block.BLOCK_STATE_REGISTRY.add(block.defaultBlockState()); } - CraftEngineBlock block = new CraftEngineBlock(properties); - if (isNoteBlock) { - block.setNoteBlock(true); - NoteBlockUtils.CLIENT_SIDE_NOTE_BLOCKS.add(block.defaultBlockState()); - } - Registry.register(BuiltInRegistries.BLOCK, location, block); - Block.BLOCK_STATE_REGISTRY.add(block.defaultBlockState()); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); } } - NoteBlockUtils.CLIENT_SIDE_NOTE_BLOCKS.addAll(Blocks.NOTE_BLOCK.getStateDefinition().getPossibleStates()); + NoteBlockUtils.CLIENT_SIDE_NOTE_BLOCKS.addAll(net.minecraft.world.level.block.Blocks.NOTE_BLOCK.getStateDefinition().getPossibleStates()); if (!map.isEmpty()) { CraftEnginePlugin.setIsSuccessfullyRegistered(true); } @@ -121,10 +120,14 @@ public abstract class MixinBlocks { private static BlockState createBlockData(String blockState) { try { - StringReader reader = new StringReader(blockState); - BlockStateParser.BlockResult arg = BlockStateParser.parseForBlock(BuiltInRegistries.BLOCK, reader, false); - return arg.blockState(); + Object holderLookUp = BuiltInRegistries.BLOCK; + if (Reflections.method$Registry$asLookup != null) { + holderLookUp = Reflections.method$Registry$asLookup.invoke(holderLookUp); + } + BlockStateParser.BlockResult result = (BlockStateParser.BlockResult) Reflections.method$BlockStateParser$parseForBlock.invoke(null, holderLookUp, blockState, false); + return result.blockState(); } catch (Exception e) { + e.printStackTrace(); return null; } } diff --git a/server-mod/src/main/java/net/momirealms/craftengine/mod/StoneBlockShape.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java similarity index 93% rename from server-mod/src/main/java/net/momirealms/craftengine/mod/StoneBlockShape.java rename to server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java index be7f4aa76..6ebc977a6 100644 --- a/server-mod/src/main/java/net/momirealms/craftengine/mod/StoneBlockShape.java +++ b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.mod; +package net.momirealms.craftengine.mod.block; import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockGetter; 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 new file mode 100644 index 000000000..2d1a47861 --- /dev/null +++ b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/item/CustomStreamCodec.java @@ -0,0 +1,41 @@ +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/BlocksMixin.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/mixin/BlocksMixin.java new file mode 100644 index 000000000..a14af758c --- /dev/null +++ b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/mixin/BlocksMixin.java @@ -0,0 +1,17 @@ +package net.momirealms.craftengine.mod.mixin; + +import net.minecraft.world.level.block.Blocks; +import net.momirealms.craftengine.mod.block.CustomBlocks; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = Blocks.class) +public abstract class BlocksMixin { + + @Inject(method = "", at = @At("RETURN")) + private static void onBlocksInit(CallbackInfo ci) { + CustomBlocks.register(); + } +} 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 new file mode 100644 index 000000000..726e9981d --- /dev/null +++ b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/mixin/ItemStackMixin.java @@ -0,0 +1,48 @@ +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/java/net/momirealms/craftengine/mod/util/BukkitReflectionUtils.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/util/BukkitReflectionUtils.java new file mode 100644 index 000000000..5353b5e8d --- /dev/null +++ b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/util/BukkitReflectionUtils.java @@ -0,0 +1,68 @@ +package net.momirealms.craftengine.mod.util; + +import org.bukkit.Bukkit; + +import java.lang.reflect.Method; +import java.util.Objects; + +public final class BukkitReflectionUtils { + private static final String PREFIX_MC = "net.minecraft."; + private static final String PREFIX_CRAFTBUKKIT = "org.bukkit.craftbukkit"; + private static final String CRAFT_SERVER = "CraftServer"; + private static final String CB_PKG_VERSION; + public static final int MAJOR_REVISION; + + private BukkitReflectionUtils() {} + + static { + final Class serverClass; + if (Bukkit.getServer() == null) { + // Paper plugin Bootstrapper 1.20.6+ + serverClass = Objects.requireNonNull(ReflectionUtils.getClazz("org.bukkit.craftbukkit.CraftServer")); + } else { + serverClass = Bukkit.getServer().getClass(); + } + final String pkg = serverClass.getPackage().getName(); + final String nmsVersion = pkg.substring(pkg.lastIndexOf(".") + 1); + if (!nmsVersion.contains("_")) { + int fallbackVersion = -1; + if (Bukkit.getServer() != null) { + try { + final Method getMinecraftVersion = serverClass.getDeclaredMethod("getMinecraftVersion"); + fallbackVersion = Integer.parseInt(getMinecraftVersion.invoke(Bukkit.getServer()).toString().split("\\.")[1]); + } catch (final Exception ignored) { + } + } else { + // Paper plugin bootstrapper 1.20.6+ + try { + final Class sharedConstants = Objects.requireNonNull(ReflectionUtils.getClazz("net.minecraft.SharedConstants")); + final Method getCurrentVersion = sharedConstants.getDeclaredMethod("getCurrentVersion"); + final Object currentVersion = getCurrentVersion.invoke(null); + final Method getName = currentVersion.getClass().getDeclaredMethod("getName"); + final String versionName = (String) getName.invoke(currentVersion); + try { + fallbackVersion = Integer.parseInt(versionName.split("\\.")[1]); + } catch (final Exception ignored) { + } + } catch (final ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + MAJOR_REVISION = fallbackVersion; + } else { + MAJOR_REVISION = Integer.parseInt(nmsVersion.split("_")[1]); + } + String name = serverClass.getName(); + name = name.substring(PREFIX_CRAFTBUKKIT.length()); + name = name.substring(0, name.length() - CRAFT_SERVER.length()); + CB_PKG_VERSION = name; + } + + public static String assembleCBClass(String className) { + return PREFIX_CRAFTBUKKIT + CB_PKG_VERSION + className; + } + + public static String assembleMCClass(String className) { + return PREFIX_MC + className; + } +} diff --git a/server-mod/src/main/java/net/momirealms/craftengine/mod/util/NoteBlockUtils.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/util/NoteBlockUtils.java similarity index 100% rename from server-mod/src/main/java/net/momirealms/craftengine/mod/util/NoteBlockUtils.java rename to server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/util/NoteBlockUtils.java diff --git a/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/util/ReflectionUtils.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/util/ReflectionUtils.java new file mode 100644 index 000000000..f76d59ebc --- /dev/null +++ b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/util/ReflectionUtils.java @@ -0,0 +1,489 @@ +package net.momirealms.craftengine.mod.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.reflect.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ReflectionUtils { + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private ReflectionUtils() {} + + public static Class getClazz(String... classes) { + for (String className : classes) { + Class clazz = getClazz(className); + if (clazz != null) { + return clazz; + } + } + return null; + } + + public static Class getClazz(String clazz) { + try { + return Class.forName(clazz); + } catch (Throwable e) { + return null; + } + } + + public static boolean classExists(@NotNull final String clazz) { + try { + Class.forName(clazz); + return true; + } catch (Throwable e) { + return false; + } + } + + public static boolean methodExists(@NotNull final Class clazz, @NotNull final String method, @NotNull final Class... parameterTypes) { + try { + clazz.getMethod(method, parameterTypes); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + @Nullable + public static Field getDeclaredField(final Class clazz, final String field) { + try { + return setAccessible(clazz.getDeclaredField(field)); + } catch (NoSuchFieldException e) { + return null; + } + } + + @NotNull + public static Field getDeclaredField(@NotNull Class clazz, @NotNull String... possibleNames) { + List possibleNameList = Arrays.asList(possibleNames); + for (Field field : clazz.getDeclaredFields()) { + if (possibleNameList.contains(field.getName())) { + return field; + } + } + throw new RuntimeException("Class " + clazz.getName() + " does not contain a field with possible names " + Arrays.toString(possibleNames)); + } + + @Nullable + public static Field getDeclaredField(final Class clazz, final int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (index == i) { + return setAccessible(field); + } + i++; + } + return null; + } + + @Nullable + public static Field getInstanceDeclaredField(final Class clazz, final int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers())) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + return null; + } + + @Nullable + public static Field getDeclaredField(final Class clazz, final Class type, int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (field.getType() == type) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + return null; + } + + @Nullable + public static Field getDeclaredFieldBackwards(final Class clazz, final Class type, int index) { + int i = 0; + Field[] fields = clazz.getDeclaredFields(); + for (int j = fields.length - 1; j >= 0; j--) { + Field field = fields[j]; + if (field.getType() == type) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + return null; + } + + @Nullable + public static Field getInstanceDeclaredField(@NotNull Class clazz, final Class type, int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (field.getType() == type && !Modifier.isStatic(field.getModifiers())) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + return null; + } + + @NotNull + public static List getDeclaredFields(final Class clazz) { + List fields = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + fields.add(setAccessible(field)); + } + return fields; + } + + @NotNull + public static List getInstanceDeclaredFields(@NotNull Class clazz) { + List list = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers())) { + list.add(setAccessible(field)); + } + } + return list; + } + + @NotNull + public static List getDeclaredFields(@NotNull final Class clazz, @NotNull final Class type) { + List fields = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.getType() == type) { + fields.add(setAccessible(field)); + } + } + return fields; + } + + @NotNull + public static List getInstanceDeclaredFields(@NotNull Class clazz, @NotNull Class type) { + List list = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.getType() == type && !Modifier.isStatic(field.getModifiers())) { + list.add(setAccessible(field)); + } + } + return list; + } + + @Nullable + public static Method getMethod(final Class clazz, Class returnType, final String[] possibleMethodNames, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + for (String name : possibleMethodNames) { + if (name.equals(method.getName())) { + if (returnType.isAssignableFrom(method.getReturnType())) { + return method; + } + } + } + } + return null; + } + + @Nullable + public static Method getMethod(final Class clazz, final String[] possibleMethodNames, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + for (String name : possibleMethodNames) { + if (name.equals(method.getName())) return method; + } + } + return null; + } + + @Nullable + public static Method getMethod(final Class clazz, Class returnType, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) return method; + } + return null; + } + + @Nullable + public static Method getDeclaredMethod(final Class clazz, Class returnType, final String[] possibleMethodNames, final Class... parameterTypes) { + outer: + for (Method method : clazz.getDeclaredMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + for (String name : possibleMethodNames) { + if (name.equals(method.getName())) { + if (returnType.isAssignableFrom(method.getReturnType())) { + return setAccessible(method); + } + } + } + } + return null; + } + + @Nullable + public static Method getDeclaredMethod(final Class clazz, Class returnType, final Class... parameterTypes) { + outer: + for (Method method : clazz.getDeclaredMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) return setAccessible(method); + } + return null; + } + + @Nullable + public static Method getMethod(final Class clazz, Class returnType, int index) { + int i = 0; + for (Method method : clazz.getMethods()) { + if (returnType.isAssignableFrom(method.getReturnType())) { + if (i == index) { + return setAccessible(method); + } + i++; + } + } + return null; + } + + @Nullable + public static Method getStaticMethod(final Class clazz, Class returnType, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + if (!Modifier.isStatic(method.getModifiers())) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) + return setAccessible(method); + } + return null; + } + + @Nullable + public static Method getStaticMethod(final Class clazz, Class returnType, String[] possibleNames, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + if (!Modifier.isStatic(method.getModifiers())) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) { + for (String name : possibleNames) { + if (name.equals(method.getName())) { + return setAccessible(method); + } + } + } + } + return null; + } + + public static Method getStaticMethod(final Class clazz, int index) { + int i = 0; + for (Method method : clazz.getMethods()) { + if (Modifier.isStatic(method.getModifiers())) { + if (i == index) { + return setAccessible(method); + } + i++; + } + } + return null; + } + + @Nullable + public static Method getMethod(final Class clazz, int index) { + int i = 0; + for (Method method : clazz.getMethods()) { + if (i == index) { + return setAccessible(method); + } + i++; + } + return null; + } + + public static Method getMethodOrElseThrow(final Class clazz, final String[] possibleMethodNames, final Class[] parameterTypes) throws NoSuchMethodException { + Method method = getMethod(clazz, possibleMethodNames, parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No method found with possible names " + Arrays.toString(possibleMethodNames) + " with parameters " + + Arrays.toString(parameterTypes) + " in class " + clazz.getName()); + } + return method; + } + + @NotNull + public static List getMethods(@NotNull Class clazz, @NotNull Class returnType, @NotNull Class... parameterTypes) { + List list = new ArrayList<>(); + for (Method method : clazz.getMethods()) { + if (!returnType.isAssignableFrom(method.getReturnType()) // check type + || method.getParameterCount() != parameterTypes.length // check length + ) continue; + Class[] types = method.getParameterTypes(); + outer: { + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + break outer; + } + } + list.add(method); + } + } + return list; + } + + @NotNull + public static T setAccessible(@NotNull final T o) { + o.setAccessible(true); + return o; + } + + @Nullable + public static Constructor getConstructor(Class clazz, Class... parameterTypes) { + try { + return clazz.getConstructor(parameterTypes); + } catch (NoSuchMethodException | SecurityException ignore) { + return null; + } + } + + @Nullable + public static Constructor getDeclaredConstructor(Class clazz, Class... parameterTypes) { + try { + return setAccessible(clazz.getDeclaredConstructor(parameterTypes)); + } catch (NoSuchMethodException | SecurityException ignore) { + return null; + } + } + + @Nullable + public static Constructor getConstructor(Class clazz, int index) { + try { + Constructor[] constructors = clazz.getDeclaredConstructors(); + if (index < 0 || index >= constructors.length) { + throw new IndexOutOfBoundsException("Invalid constructor index: " + index); + } + return setAccessible(constructors[index]); + } catch (SecurityException e) { + return null; + } + } + + @NotNull + public static Constructor getTheOnlyConstructor(Class clazz) { + Constructor[] constructors = clazz.getConstructors(); + if (constructors.length != 1) { + throw new RuntimeException("This class is expected to have only one constructor but it has " + constructors.length); + } + return constructors[0]; + } + + public static MethodHandle unreflectGetter(Field field) throws IllegalAccessException { + try { + return LOOKUP.unreflectGetter(field); + } catch (IllegalAccessException e) { + field.setAccessible(true); + return LOOKUP.unreflectGetter(field); + } + } + + public static MethodHandle unreflectMethod(Method method) throws IllegalAccessException { + try { + return LOOKUP.unreflect(method); + } catch (IllegalAccessException e) { + method.setAccessible(true); + return LOOKUP.unreflect(method); + } + } + + public static VarHandle findVarHandle(Class clazz, String name, Class type) { + try { + return MethodHandles.privateLookupIn(clazz, LOOKUP) + .findVarHandle(clazz, name, type); + } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) { + return null; + } + } + + public static VarHandle findVarHandle(Field field) { + try { + return MethodHandles.privateLookupIn(field.getDeclaringClass(), LOOKUP) + .findVarHandle(field.getDeclaringClass(), field.getName(), field.getType()); + } catch (IllegalAccessException | NoSuchFieldException e) { + return null; + } + } +} diff --git a/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/util/Reflections.java b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/util/Reflections.java new file mode 100644 index 000000000..001f3a736 --- /dev/null +++ b/server-mod/v1_20_5/src/main/java/net/momirealms/craftengine/mod/util/Reflections.java @@ -0,0 +1,37 @@ +package net.momirealms.craftengine.mod.util; + +import net.minecraft.commands.arguments.blocks.BlockStateParser; +import net.minecraft.core.DefaultedRegistry; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import static java.util.Objects.requireNonNull; + +public class Reflections { + + public static final Method method$DefaultedRegistry$get = requireNonNull( + ReflectionUtils.getMethod( + DefaultedRegistry.class, Object.class, ResourceLocation.class + ) + ); + + public static final Field field$BlockBehaviour$Properties$id = ReflectionUtils.getDeclaredField( + BlockBehaviour.Properties.class, ResourceKey.class, 0 + ); + + public static final Method method$BlockStateParser$parseForBlock = requireNonNull( + ReflectionUtils.getStaticMethod( + BlockStateParser.class, BlockStateParser.BlockResult.class, new String[]{"parseForBlock"}, HolderLookup.class, String.class, boolean.class + ) + ); + + public static final Method method$Registry$asLookup = ReflectionUtils.getMethod( + Registry.class, new String[]{"asLookup"} + ); +} diff --git a/server-mod/src/main/resources/ignite.mod.json b/server-mod/v1_20_5/src/main/resources/ignite.mod.json similarity index 100% rename from server-mod/src/main/resources/ignite.mod.json rename to server-mod/v1_20_5/src/main/resources/ignite.mod.json diff --git a/server-mod/src/main/resources/mixins.craftengine.json b/server-mod/v1_20_5/src/main/resources/mixins.craftengine.json similarity index 86% rename from server-mod/src/main/resources/mixins.craftengine.json rename to server-mod/v1_20_5/src/main/resources/mixins.craftengine.json index 2e9a8087a..288b6501f 100644 --- a/server-mod/src/main/resources/mixins.craftengine.json +++ b/server-mod/v1_20_5/src/main/resources/mixins.craftengine.json @@ -6,6 +6,7 @@ "target": "@env(DEFAULT)", "compatibilityLevel": "JAVA_21", "server": [ - "MixinBlocks" + "BlocksMixin", + "ItemStackMixin" ] } diff --git a/server-mod/v1_21_5/build.gradle.kts b/server-mod/v1_21_5/build.gradle.kts new file mode 100644 index 000000000..0be3a008a --- /dev/null +++ b/server-mod/v1_21_5/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + id("java-library") + id("com.gradleup.shadow") version "9.0.0-beta11" + id("io.papermc.paperweight.userdev") version "2.0.0-beta.16" +} + +repositories { + mavenCentral() + maven("https://maven.fabricmc.net/") + maven("https://oss.sonatype.org/content/groups/public/") + maven("https://repo.papermc.io/repository/maven-public/") + maven("https://repo.spongepowered.org/maven/") +} + +dependencies { + implementation(project(":shared")) + remapper("net.fabricmc:tiny-remapper:${rootProject.properties["tiny_remapper_version"]}:fat") + paperweightDevelopmentBundle("io.papermc.paper:dev-bundle:1.21.5-R0.1-SNAPSHOT") + compileOnly("space.vectrix.ignite:ignite-api:${rootProject.properties["ignite_version"]}") + compileOnly("net.fabricmc:sponge-mixin:${rootProject.properties["mixin_version"]}") + compileOnly("io.github.llamalad7:mixinextras-common:${rootProject.properties["mixinextras_version"]}") +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +tasks.withType { + options.encoding = "UTF-8" + options.release.set(21) + dependsOn(tasks.clean) +} + +paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.MOJANG_PRODUCTION + +artifacts { + archives(tasks.shadowJar) +} + +tasks { + shadowJar { + archiveClassifier = "" + archiveFileName = "${rootProject.name}-ignite-mod-${rootProject.properties["project_version"]}+mc1.21.5-mojmap.jar" + destinationDirectory.set(file("$rootDir/target")) + } +} diff --git a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java new file mode 100644 index 000000000..6cb356a90 --- /dev/null +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/CraftEnginePlugin.java @@ -0,0 +1,103 @@ +package net.momirealms.craftengine.mod; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.List; +import java.util.Set; +import java.util.logging.Logger; + +public final class CraftEnginePlugin implements IMixinConfigPlugin { + public static final Logger LOGGER = Logger.getLogger(CraftEnginePlugin.class.getName()); + private static int vanillaRegistrySize; + private static boolean isSuccessfullyRegistered = false; + private static int maxChainUpdate = 32; + + public static void setVanillaRegistrySize(int vanillaRegistrySize) { + CraftEnginePlugin.vanillaRegistrySize = vanillaRegistrySize; + } + + public static void setIsSuccessfullyRegistered(boolean isSuccessfullyRegistered) { + CraftEnginePlugin.isSuccessfullyRegistered = isSuccessfullyRegistered; + } + + public static int maxChainUpdate() { + return maxChainUpdate; + } + + public static void setMaxChainUpdate(int maxChainUpdate) { + CraftEnginePlugin.maxChainUpdate = maxChainUpdate; + } + + @Override + public void onLoad(final @NotNull String mixinPackage) { + } + + @Override + public @Nullable String getRefMapperConfig() { + return null; + } + + @Override + public boolean shouldApplyMixin(@NotNull String targetClassName, + @NotNull String mixinClassName) { + return true; + } + + @Override + public void acceptTargets(@NotNull Set myTargets, + @NotNull Set otherTargets) { + } + + @Override + public @Nullable List getMixins() { + return null; + } + + @Override + public void preApply(@NotNull String targetClassName, + @NotNull ClassNode targetClass, + @NotNull String mixinClassName, + @NotNull IMixinInfo mixinInfo) { + } + + @Override + public void postApply(@NotNull String targetClassName, + @NotNull ClassNode targetClass, + @NotNull String mixinClassName, + @NotNull IMixinInfo mixinInfo) { + } + + public static Path getPluginFolderPath() { + ProtectionDomain protectionDomain = CraftEnginePlugin.class.getProtectionDomain(); + CodeSource codeSource = protectionDomain.getCodeSource(); + URL jarUrl = codeSource.getLocation(); + try { + return Paths.get(jarUrl.toURI()).getParent().getParent().resolve("plugins"); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return null; + } + + public static Path getCraftEngineMappingsPath() { + return getPluginFolderPath() + .resolve("CraftEngine") + .resolve("mappings.yml"); + } + + public static Path getCraftEngineAdditionalBlocksPath() { + return getPluginFolderPath() + .resolve("CraftEngine") + .resolve("additional-real-blocks.yml"); + } +} 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 new file mode 100644 index 000000000..8b3039c7b --- /dev/null +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/CraftEngineBlock.java @@ -0,0 +1,270 @@ +package net.momirealms.craftengine.mod.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.item.FallingBlockEntity; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +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.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.momirealms.craftengine.mod.CraftEnginePlugin; +import net.momirealms.craftengine.mod.util.NoteBlockUtils; +import net.momirealms.craftengine.shared.ObjectHolder; +import net.momirealms.craftengine.shared.block.*; +import org.jetbrains.annotations.NotNull; + +public class CraftEngineBlock + extends Block + implements BehaviorHolder, ShapeHolder, NoteBlockIndicator, Fallable, BonemealableBlock + //TODO , SimpleWaterloggedBlock +{ + private static final StoneBlockShape STONE = new StoneBlockShape(Blocks.STONE.defaultBlockState()); + private boolean isNoteBlock; + public ObjectHolder behaviorHolder; + public ObjectHolder shapeHolder; + public boolean isClientSideNoteBlock; + + public CraftEngineBlock(Properties properties) { + super(properties); + this.behaviorHolder = new ObjectHolder<>(EmptyBlockBehavior.INSTANCE); + this.shapeHolder = new ObjectHolder<>(STONE); + } + + public void setNoteBlock(boolean noteBlock) { + isNoteBlock = noteBlock; + } + + @Override + public ObjectHolder getBehaviorHolder() { + return behaviorHolder; + } + + @Override + public ObjectHolder getShapeHolder() { + return shapeHolder; + } + + @Override + public boolean isNoteBlock() { + return isClientSideNoteBlock; + } + + @Override + protected @NotNull VoxelShape getShape(@NotNull BlockState state, @NotNull BlockGetter level, @NotNull BlockPos pos, @NotNull CollisionContext context) { + try { + return (VoxelShape) shapeHolder.value().getShape(this, new Object[]{state, level, pos, context}); + } catch (Exception e) { + e.printStackTrace(); + return super.getShape(state, level, pos, context); + } + } + + @Override + protected @NotNull BlockState rotate(@NotNull BlockState state, @NotNull Rotation rotation) { + try { + return (BlockState) this.behaviorHolder.value().rotate(this, new Object[]{state, rotation}, () -> super.rotate(state, rotation)); + } catch (Exception e) { + e.printStackTrace(); + return super.rotate(state, rotation); + } + } + + @Override + protected @NotNull BlockState mirror(@NotNull BlockState state, @NotNull Mirror mirror) { + try { + return (BlockState) this.behaviorHolder.value().mirror(this, new Object[]{state, mirror}, () -> super.mirror(state, mirror)); + } catch (Exception e) { + e.printStackTrace(); + return super.mirror(state, mirror); + } + } + + @Override + protected void tick(@NotNull BlockState state, @NotNull ServerLevel level, @NotNull BlockPos pos, @NotNull RandomSource random) { + try { + this.behaviorHolder.value().tick(this, new Object[]{state, level, pos, random}, () -> { + super.tick(state, level, pos, random); + return null; + }); + } catch (Exception e) { + e.printStackTrace(); + super.tick(state, level, pos, random); + } + } + + @Override + protected void randomTick(@NotNull BlockState state, @NotNull ServerLevel level, @NotNull BlockPos pos, @NotNull RandomSource random) { + try { + behaviorHolder.value().randomTick(this, new Object[]{state, level, pos, random}, () -> { + super.randomTick(state, level, pos, random); + return null; + }); + } catch (Exception e) { + e.printStackTrace(); + super.randomTick(state, level, pos, random); + } + } + + @Override + protected void onPlace(@NotNull BlockState state, @NotNull Level level, @NotNull BlockPos pos, @NotNull BlockState oldState, boolean movedByPiston) { + try { + behaviorHolder.value().onPlace(this, new Object[]{state, level, pos, oldState, movedByPiston}, () -> { + super.onPlace(state, level, pos, oldState, movedByPiston); + return null; + }); + } catch (Exception e) { + e.printStackTrace(); + super.onPlace(state, level, pos, oldState, movedByPiston); + } + } + + @Override + public void onBrokenAfterFall(@NotNull Level level, @NotNull BlockPos pos, @NotNull FallingBlockEntity fallingBlock) { + try { + behaviorHolder.value().onBrokenAfterFall(this, new Object[]{level, pos, fallingBlock}); + } catch (Exception e) { + e.printStackTrace(); + Fallable.super.onBrokenAfterFall(level, pos, fallingBlock); + } + } + + @Override + protected boolean canSurvive(@NotNull BlockState state, @NotNull LevelReader level, @NotNull BlockPos pos) { + try { + return behaviorHolder.value().canSurvive(this, new Object[]{state, level, pos}, () -> super.canSurvive(state, level, pos)); + } catch (Exception e) { + e.printStackTrace(); + return super.canSurvive(state, level, pos); + } + } + + @Override + protected BlockState updateShape(@NotNull BlockState state, + @NotNull LevelReader level, + @NotNull ScheduledTickAccess scheduledTickAccess, + @NotNull BlockPos pos, + @NotNull Direction direction, + @NotNull BlockPos neighborPos, + @NotNull BlockState neighborState, + @NotNull RandomSource random) { + try { + if (isNoteBlock && level instanceof ServerLevel serverLevel) { + startNoteBlockChain(direction, serverLevel, pos); + } + return (BlockState) behaviorHolder.value().updateShape(this, new Object[]{state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random}, () -> super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random)); + } catch (Exception e) { + e.printStackTrace(); + return super.updateShape(state, level, scheduledTickAccess, pos, direction, neighborPos, neighborState, random); + } + } + + private static void startNoteBlockChain(Direction direction, ServerLevel serverLevel, BlockPos blockPos) { + int id = direction.get3DDataValue(); + // Y axis + if (id == 0 || id == 1) { + ServerChunkCache chunkSource = serverLevel.chunkSource; + chunkSource.blockChanged(blockPos); + if (id == 1) { + noteBlockChainUpdate(serverLevel, chunkSource, Direction.DOWN, blockPos, 0); + } else { + noteBlockChainUpdate(serverLevel, chunkSource, Direction.UP, blockPos, 0); + } + } + } + + public static void noteBlockChainUpdate(ServerLevel level, ServerChunkCache chunkSource, Direction direction, BlockPos blockPos, int times) { + if (times >= CraftEnginePlugin.maxChainUpdate()) return; + BlockPos relativePos = blockPos.relative(direction); + BlockState state = level.getBlockState(relativePos); + if (NoteBlockUtils.CLIENT_SIDE_NOTE_BLOCKS.contains(state)) { + chunkSource.blockChanged(relativePos); + noteBlockChainUpdate(level, chunkSource, direction, relativePos, times+1); + } + } + +// @Override +// protected @NotNull FluidState getFluidState(@NotNull BlockState state) { +// try { +// return (FluidState) behaviorHolder.value().getFluidState(this, new Object[]{state}, () -> super.getFluidState(state)); +// } catch (Exception e) { +// e.printStackTrace(); +// return super.getFluidState(state); +// } +// } + + @Override + public boolean isValidBonemealTarget(@NotNull LevelReader levelReader, @NotNull BlockPos blockPos, @NotNull BlockState blockState) { + try { + return behaviorHolder.value().isValidBoneMealTarget(this, new Object[]{levelReader, blockPos, blockState}); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public boolean isBonemealSuccess(@NotNull Level level, @NotNull RandomSource randomSource, @NotNull BlockPos blockPos, @NotNull BlockState blockState) { + try { + return behaviorHolder.value().isBoneMealSuccess(this, new Object[]{level, randomSource, blockPos, blockState}); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public void performBonemeal(@NotNull ServerLevel serverLevel, @NotNull RandomSource randomSource, @NotNull BlockPos blockPos, @NotNull BlockState blockState) { + try { + behaviorHolder.value().performBoneMeal(this, new Object[]{serverLevel, randomSource, blockPos, blockState}); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onLand(@NotNull Level level, @NotNull BlockPos pos, @NotNull BlockState state, @NotNull BlockState replaceableState, @NotNull FallingBlockEntity fallingBlock) { + try { + behaviorHolder.value().onLand(this, new Object[]{level, pos, state, replaceableState, fallingBlock}); + } catch (Exception e) { + e.printStackTrace(); + } + } + +// @Override +// public boolean canPlaceLiquid(@Nullable Player player, @NotNull BlockGetter level, @NotNull BlockPos pos, @NotNull BlockState state, @NotNull Fluid fluid) { +// try { +// return behaviorHolder.value().canPlaceLiquid(this, new Object[]{player, level, pos, state, fluid}, () -> SimpleWaterloggedBlock.super.canPlaceLiquid(player, level, pos, state, fluid)); +// } catch (Exception e) { +// e.printStackTrace(); +// return SimpleWaterloggedBlock.super.canPlaceLiquid(player, level, pos, state, fluid); +// } +// } +// +// @Override +// public boolean placeLiquid(@NotNull LevelAccessor level, @NotNull BlockPos pos, @NotNull BlockState state, @NotNull FluidState fluidState) { +// try { +// return behaviorHolder.value().placeLiquid(this, new Object[]{level, pos, state, fluidState}, () -> SimpleWaterloggedBlock.super.placeLiquid(level, pos, state, fluidState)); +// } catch (Exception e) { +// e.printStackTrace(); +// return SimpleWaterloggedBlock.super.placeLiquid(level, pos, state, fluidState); +// } +// } +// +// @NotNull +// @Override +// public ItemStack pickupBlock(@Nullable Player player, @NotNull LevelAccessor level, @NotNull BlockPos pos, @NotNull BlockState state) { +// try { +// return (ItemStack) behaviorHolder.value().pickupBlock(this, new Object[]{player, level, pos, state}, () -> SimpleWaterloggedBlock.super.pickupBlock(player, level, pos, state)); +// } catch (Exception e) { +// e.printStackTrace(); +// return SimpleWaterloggedBlock.super.pickupBlock(player, level, pos, state); +// } +// } +} diff --git a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/CustomBlocks.java b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/CustomBlocks.java new file mode 100644 index 000000000..36af40eab --- /dev/null +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/CustomBlocks.java @@ -0,0 +1,134 @@ +package net.momirealms.craftengine.mod.block; + +import net.minecraft.commands.arguments.blocks.BlockStateParser; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.momirealms.craftengine.mod.CraftEnginePlugin; +import net.momirealms.craftengine.mod.util.NoteBlockUtils; +import net.momirealms.craftengine.mod.util.Reflections; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +public class CustomBlocks { + + public static void register() { + CraftEnginePlugin.setVanillaRegistrySize(Block.BLOCK_STATE_REGISTRY.size()); + ResourceLocation noteBlock = ResourceLocation.fromNamespaceAndPath("minecraft", "note_block"); + Map map = loadMappingsAndAdditionalBlocks(); + for (Map.Entry entry : map.entrySet()) { + ResourceLocation replacedBlockId = entry.getKey(); + boolean isNoteBlock = replacedBlockId.equals(noteBlock); + try { + Block replacedBlock = (Block) Reflections.method$DefaultedRegistry$get.invoke(BuiltInRegistries.BLOCK, replacedBlockId); + for (int i = 0; i < entry.getValue(); i++) { + ResourceLocation location = ResourceLocation.fromNamespaceAndPath("craftengine", replacedBlockId.getPath() + "_" + i); + ResourceKey resourceKey = ResourceKey.create(Registries.BLOCK, location); + BlockBehaviour.Properties properties = BlockBehaviour.Properties.of(); + if (Reflections.field$BlockBehaviour$Properties$id != null) { + Reflections.field$BlockBehaviour$Properties$id.set(properties, resourceKey); + } + if (!replacedBlock.hasCollision) { + properties.noCollission(); + } + CraftEngineBlock block = new CraftEngineBlock(properties); + if (isNoteBlock) { + block.setNoteBlock(true); + NoteBlockUtils.CLIENT_SIDE_NOTE_BLOCKS.add(block.defaultBlockState()); + } + Registry.register(BuiltInRegistries.BLOCK, location, block); + Block.BLOCK_STATE_REGISTRY.add(block.defaultBlockState()); + } + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + } + NoteBlockUtils.CLIENT_SIDE_NOTE_BLOCKS.addAll(net.minecraft.world.level.block.Blocks.NOTE_BLOCK.getStateDefinition().getPossibleStates()); + if (!map.isEmpty()) { + CraftEnginePlugin.setIsSuccessfullyRegistered(true); + } + } + + private static Map loadMappingsAndAdditionalBlocks() { + Path mappingPath = CraftEnginePlugin.getCraftEngineMappingsPath(); + if (!Files.exists(mappingPath)) return Map.of(); + YamlConfiguration mappings = YamlConfiguration.loadConfiguration(mappingPath.toFile()); + Map blockStateMappings = loadBlockStateMappings(mappings); + validateBlockStateMappings(blockStateMappings); + Map blockTypeCounter = new LinkedHashMap<>(); + Map appearanceMapper = new HashMap<>(); + for (Map.Entry entry : blockStateMappings.entrySet()) { + processBlockStateMapping(entry, appearanceMapper, blockTypeCounter); + } + YamlConfiguration additionalYaml = YamlConfiguration.loadConfiguration(CraftEnginePlugin.getCraftEngineAdditionalBlocksPath().toFile()); + return buildRegisteredRealBlockSlots(blockTypeCounter, additionalYaml); + } + + private static Map loadBlockStateMappings(YamlConfiguration mappings) { + Map blockStateMappings = new LinkedHashMap<>(); + for (Map.Entry entry : mappings.getValues(false).entrySet()) { + if (entry.getValue() instanceof String afterValue) { + blockStateMappings.put(entry.getKey(), afterValue); + } + } + return blockStateMappings; + } + + private static void validateBlockStateMappings(Map blockStateMappings) { + Map temp = new HashMap<>(blockStateMappings); + for (Map.Entry entry : temp.entrySet()) { + String state = entry.getValue(); + blockStateMappings.remove(state); + } + } + + private static LinkedHashMap buildRegisteredRealBlockSlots(Map counter, YamlConfiguration additionalYaml) { + LinkedHashMap map = new LinkedHashMap<>(); + for (Map.Entry entry : counter.entrySet()) { + String id = entry.getKey().toString(); + int additionalStates = additionalYaml.getInt(id, 0); + int internalIds = entry.getValue() + additionalStates; + map.put(entry.getKey(), internalIds); + } + return map; + } + + private static void processBlockStateMapping(Map.Entry entry, Map mapper, Map counter) { + BlockState before = createBlockData(entry.getKey()); + BlockState after = createBlockData(entry.getValue()); + if (before == null || after == null) return; + + int beforeId = Block.BLOCK_STATE_REGISTRY.getId(before); + int afterId = Block.BLOCK_STATE_REGISTRY.getId(after); + + Integer previous = mapper.put(beforeId, afterId); + if (previous == null) { + counter.compute(BuiltInRegistries.BLOCK.getKey(before.getBlock()), (k, count) -> count == null ? 1 : count + 1); + } + } + + private static BlockState createBlockData(String blockState) { + try { + Object holderLookUp = BuiltInRegistries.BLOCK; + if (Reflections.method$Registry$asLookup != null) { + holderLookUp = Reflections.method$Registry$asLookup.invoke(holderLookUp); + } + BlockStateParser.BlockResult result = (BlockStateParser.BlockResult) Reflections.method$BlockStateParser$parseForBlock.invoke(null, holderLookUp, blockState, false); + return result.blockState(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} 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 new file mode 100644 index 000000000..6ebc977a6 --- /dev/null +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/block/StoneBlockShape.java @@ -0,0 +1,20 @@ +package net.momirealms.craftengine.mod.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.momirealms.craftengine.shared.block.BlockShape; + +public class StoneBlockShape implements BlockShape { + private final BlockState rawBlockState; + + public StoneBlockShape(BlockState rawBlockState) { + this.rawBlockState = rawBlockState; + } + + @Override + public Object getShape(Object thisObj, Object[] args) { + return rawBlockState.getShape((BlockGetter) args[1], (BlockPos) args[2], (CollisionContext) args[3]); + } +} 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 new file mode 100644 index 000000000..2d1a47861 --- /dev/null +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/item/CustomStreamCodec.java @@ -0,0 +1,41 @@ +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/BlocksMixin.java b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/mixin/BlocksMixin.java new file mode 100644 index 000000000..a14af758c --- /dev/null +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/mixin/BlocksMixin.java @@ -0,0 +1,17 @@ +package net.momirealms.craftengine.mod.mixin; + +import net.minecraft.world.level.block.Blocks; +import net.momirealms.craftengine.mod.block.CustomBlocks; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = Blocks.class) +public abstract class BlocksMixin { + + @Inject(method = "", at = @At("RETURN")) + private static void onBlocksInit(CallbackInfo ci) { + CustomBlocks.register(); + } +} 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 new file mode 100644 index 000000000..88bc5d916 --- /dev/null +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/mixin/ItemStackMixin.java @@ -0,0 +1,66 @@ +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/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/util/BukkitReflectionUtils.java b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/util/BukkitReflectionUtils.java new file mode 100644 index 000000000..5353b5e8d --- /dev/null +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/util/BukkitReflectionUtils.java @@ -0,0 +1,68 @@ +package net.momirealms.craftengine.mod.util; + +import org.bukkit.Bukkit; + +import java.lang.reflect.Method; +import java.util.Objects; + +public final class BukkitReflectionUtils { + private static final String PREFIX_MC = "net.minecraft."; + private static final String PREFIX_CRAFTBUKKIT = "org.bukkit.craftbukkit"; + private static final String CRAFT_SERVER = "CraftServer"; + private static final String CB_PKG_VERSION; + public static final int MAJOR_REVISION; + + private BukkitReflectionUtils() {} + + static { + final Class serverClass; + if (Bukkit.getServer() == null) { + // Paper plugin Bootstrapper 1.20.6+ + serverClass = Objects.requireNonNull(ReflectionUtils.getClazz("org.bukkit.craftbukkit.CraftServer")); + } else { + serverClass = Bukkit.getServer().getClass(); + } + final String pkg = serverClass.getPackage().getName(); + final String nmsVersion = pkg.substring(pkg.lastIndexOf(".") + 1); + if (!nmsVersion.contains("_")) { + int fallbackVersion = -1; + if (Bukkit.getServer() != null) { + try { + final Method getMinecraftVersion = serverClass.getDeclaredMethod("getMinecraftVersion"); + fallbackVersion = Integer.parseInt(getMinecraftVersion.invoke(Bukkit.getServer()).toString().split("\\.")[1]); + } catch (final Exception ignored) { + } + } else { + // Paper plugin bootstrapper 1.20.6+ + try { + final Class sharedConstants = Objects.requireNonNull(ReflectionUtils.getClazz("net.minecraft.SharedConstants")); + final Method getCurrentVersion = sharedConstants.getDeclaredMethod("getCurrentVersion"); + final Object currentVersion = getCurrentVersion.invoke(null); + final Method getName = currentVersion.getClass().getDeclaredMethod("getName"); + final String versionName = (String) getName.invoke(currentVersion); + try { + fallbackVersion = Integer.parseInt(versionName.split("\\.")[1]); + } catch (final Exception ignored) { + } + } catch (final ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + MAJOR_REVISION = fallbackVersion; + } else { + MAJOR_REVISION = Integer.parseInt(nmsVersion.split("_")[1]); + } + String name = serverClass.getName(); + name = name.substring(PREFIX_CRAFTBUKKIT.length()); + name = name.substring(0, name.length() - CRAFT_SERVER.length()); + CB_PKG_VERSION = name; + } + + public static String assembleCBClass(String className) { + return PREFIX_CRAFTBUKKIT + CB_PKG_VERSION + className; + } + + public static String assembleMCClass(String className) { + return PREFIX_MC + className; + } +} diff --git a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/util/NoteBlockUtils.java b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/util/NoteBlockUtils.java new file mode 100644 index 000000000..8deebf538 --- /dev/null +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/util/NoteBlockUtils.java @@ -0,0 +1,11 @@ +package net.momirealms.craftengine.mod.util; + +import net.minecraft.world.level.block.state.BlockState; + +import java.util.HashSet; +import java.util.Set; + +public class NoteBlockUtils { + + public static final Set CLIENT_SIDE_NOTE_BLOCKS = new HashSet<>(); +} diff --git a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/util/ReflectionUtils.java b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/util/ReflectionUtils.java new file mode 100644 index 000000000..f76d59ebc --- /dev/null +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/util/ReflectionUtils.java @@ -0,0 +1,489 @@ +package net.momirealms.craftengine.mod.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.reflect.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ReflectionUtils { + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private ReflectionUtils() {} + + public static Class getClazz(String... classes) { + for (String className : classes) { + Class clazz = getClazz(className); + if (clazz != null) { + return clazz; + } + } + return null; + } + + public static Class getClazz(String clazz) { + try { + return Class.forName(clazz); + } catch (Throwable e) { + return null; + } + } + + public static boolean classExists(@NotNull final String clazz) { + try { + Class.forName(clazz); + return true; + } catch (Throwable e) { + return false; + } + } + + public static boolean methodExists(@NotNull final Class clazz, @NotNull final String method, @NotNull final Class... parameterTypes) { + try { + clazz.getMethod(method, parameterTypes); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + @Nullable + public static Field getDeclaredField(final Class clazz, final String field) { + try { + return setAccessible(clazz.getDeclaredField(field)); + } catch (NoSuchFieldException e) { + return null; + } + } + + @NotNull + public static Field getDeclaredField(@NotNull Class clazz, @NotNull String... possibleNames) { + List possibleNameList = Arrays.asList(possibleNames); + for (Field field : clazz.getDeclaredFields()) { + if (possibleNameList.contains(field.getName())) { + return field; + } + } + throw new RuntimeException("Class " + clazz.getName() + " does not contain a field with possible names " + Arrays.toString(possibleNames)); + } + + @Nullable + public static Field getDeclaredField(final Class clazz, final int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (index == i) { + return setAccessible(field); + } + i++; + } + return null; + } + + @Nullable + public static Field getInstanceDeclaredField(final Class clazz, final int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers())) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + return null; + } + + @Nullable + public static Field getDeclaredField(final Class clazz, final Class type, int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (field.getType() == type) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + return null; + } + + @Nullable + public static Field getDeclaredFieldBackwards(final Class clazz, final Class type, int index) { + int i = 0; + Field[] fields = clazz.getDeclaredFields(); + for (int j = fields.length - 1; j >= 0; j--) { + Field field = fields[j]; + if (field.getType() == type) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + return null; + } + + @Nullable + public static Field getInstanceDeclaredField(@NotNull Class clazz, final Class type, int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (field.getType() == type && !Modifier.isStatic(field.getModifiers())) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + return null; + } + + @NotNull + public static List getDeclaredFields(final Class clazz) { + List fields = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + fields.add(setAccessible(field)); + } + return fields; + } + + @NotNull + public static List getInstanceDeclaredFields(@NotNull Class clazz) { + List list = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers())) { + list.add(setAccessible(field)); + } + } + return list; + } + + @NotNull + public static List getDeclaredFields(@NotNull final Class clazz, @NotNull final Class type) { + List fields = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.getType() == type) { + fields.add(setAccessible(field)); + } + } + return fields; + } + + @NotNull + public static List getInstanceDeclaredFields(@NotNull Class clazz, @NotNull Class type) { + List list = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.getType() == type && !Modifier.isStatic(field.getModifiers())) { + list.add(setAccessible(field)); + } + } + return list; + } + + @Nullable + public static Method getMethod(final Class clazz, Class returnType, final String[] possibleMethodNames, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + for (String name : possibleMethodNames) { + if (name.equals(method.getName())) { + if (returnType.isAssignableFrom(method.getReturnType())) { + return method; + } + } + } + } + return null; + } + + @Nullable + public static Method getMethod(final Class clazz, final String[] possibleMethodNames, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + for (String name : possibleMethodNames) { + if (name.equals(method.getName())) return method; + } + } + return null; + } + + @Nullable + public static Method getMethod(final Class clazz, Class returnType, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) return method; + } + return null; + } + + @Nullable + public static Method getDeclaredMethod(final Class clazz, Class returnType, final String[] possibleMethodNames, final Class... parameterTypes) { + outer: + for (Method method : clazz.getDeclaredMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + for (String name : possibleMethodNames) { + if (name.equals(method.getName())) { + if (returnType.isAssignableFrom(method.getReturnType())) { + return setAccessible(method); + } + } + } + } + return null; + } + + @Nullable + public static Method getDeclaredMethod(final Class clazz, Class returnType, final Class... parameterTypes) { + outer: + for (Method method : clazz.getDeclaredMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) return setAccessible(method); + } + return null; + } + + @Nullable + public static Method getMethod(final Class clazz, Class returnType, int index) { + int i = 0; + for (Method method : clazz.getMethods()) { + if (returnType.isAssignableFrom(method.getReturnType())) { + if (i == index) { + return setAccessible(method); + } + i++; + } + } + return null; + } + + @Nullable + public static Method getStaticMethod(final Class clazz, Class returnType, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + if (!Modifier.isStatic(method.getModifiers())) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) + return setAccessible(method); + } + return null; + } + + @Nullable + public static Method getStaticMethod(final Class clazz, Class returnType, String[] possibleNames, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + if (!Modifier.isStatic(method.getModifiers())) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) { + for (String name : possibleNames) { + if (name.equals(method.getName())) { + return setAccessible(method); + } + } + } + } + return null; + } + + public static Method getStaticMethod(final Class clazz, int index) { + int i = 0; + for (Method method : clazz.getMethods()) { + if (Modifier.isStatic(method.getModifiers())) { + if (i == index) { + return setAccessible(method); + } + i++; + } + } + return null; + } + + @Nullable + public static Method getMethod(final Class clazz, int index) { + int i = 0; + for (Method method : clazz.getMethods()) { + if (i == index) { + return setAccessible(method); + } + i++; + } + return null; + } + + public static Method getMethodOrElseThrow(final Class clazz, final String[] possibleMethodNames, final Class[] parameterTypes) throws NoSuchMethodException { + Method method = getMethod(clazz, possibleMethodNames, parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No method found with possible names " + Arrays.toString(possibleMethodNames) + " with parameters " + + Arrays.toString(parameterTypes) + " in class " + clazz.getName()); + } + return method; + } + + @NotNull + public static List getMethods(@NotNull Class clazz, @NotNull Class returnType, @NotNull Class... parameterTypes) { + List list = new ArrayList<>(); + for (Method method : clazz.getMethods()) { + if (!returnType.isAssignableFrom(method.getReturnType()) // check type + || method.getParameterCount() != parameterTypes.length // check length + ) continue; + Class[] types = method.getParameterTypes(); + outer: { + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + break outer; + } + } + list.add(method); + } + } + return list; + } + + @NotNull + public static T setAccessible(@NotNull final T o) { + o.setAccessible(true); + return o; + } + + @Nullable + public static Constructor getConstructor(Class clazz, Class... parameterTypes) { + try { + return clazz.getConstructor(parameterTypes); + } catch (NoSuchMethodException | SecurityException ignore) { + return null; + } + } + + @Nullable + public static Constructor getDeclaredConstructor(Class clazz, Class... parameterTypes) { + try { + return setAccessible(clazz.getDeclaredConstructor(parameterTypes)); + } catch (NoSuchMethodException | SecurityException ignore) { + return null; + } + } + + @Nullable + public static Constructor getConstructor(Class clazz, int index) { + try { + Constructor[] constructors = clazz.getDeclaredConstructors(); + if (index < 0 || index >= constructors.length) { + throw new IndexOutOfBoundsException("Invalid constructor index: " + index); + } + return setAccessible(constructors[index]); + } catch (SecurityException e) { + return null; + } + } + + @NotNull + public static Constructor getTheOnlyConstructor(Class clazz) { + Constructor[] constructors = clazz.getConstructors(); + if (constructors.length != 1) { + throw new RuntimeException("This class is expected to have only one constructor but it has " + constructors.length); + } + return constructors[0]; + } + + public static MethodHandle unreflectGetter(Field field) throws IllegalAccessException { + try { + return LOOKUP.unreflectGetter(field); + } catch (IllegalAccessException e) { + field.setAccessible(true); + return LOOKUP.unreflectGetter(field); + } + } + + public static MethodHandle unreflectMethod(Method method) throws IllegalAccessException { + try { + return LOOKUP.unreflect(method); + } catch (IllegalAccessException e) { + method.setAccessible(true); + return LOOKUP.unreflect(method); + } + } + + public static VarHandle findVarHandle(Class clazz, String name, Class type) { + try { + return MethodHandles.privateLookupIn(clazz, LOOKUP) + .findVarHandle(clazz, name, type); + } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) { + return null; + } + } + + public static VarHandle findVarHandle(Field field) { + try { + return MethodHandles.privateLookupIn(field.getDeclaringClass(), LOOKUP) + .findVarHandle(field.getDeclaringClass(), field.getName(), field.getType()); + } catch (IllegalAccessException | NoSuchFieldException e) { + return null; + } + } +} diff --git a/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/util/Reflections.java b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/util/Reflections.java new file mode 100644 index 000000000..001f3a736 --- /dev/null +++ b/server-mod/v1_21_5/src/main/java/net/momirealms/craftengine/mod/util/Reflections.java @@ -0,0 +1,37 @@ +package net.momirealms.craftengine.mod.util; + +import net.minecraft.commands.arguments.blocks.BlockStateParser; +import net.minecraft.core.DefaultedRegistry; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import static java.util.Objects.requireNonNull; + +public class Reflections { + + public static final Method method$DefaultedRegistry$get = requireNonNull( + ReflectionUtils.getMethod( + DefaultedRegistry.class, Object.class, ResourceLocation.class + ) + ); + + public static final Field field$BlockBehaviour$Properties$id = ReflectionUtils.getDeclaredField( + BlockBehaviour.Properties.class, ResourceKey.class, 0 + ); + + public static final Method method$BlockStateParser$parseForBlock = requireNonNull( + ReflectionUtils.getStaticMethod( + BlockStateParser.class, BlockStateParser.BlockResult.class, new String[]{"parseForBlock"}, HolderLookup.class, String.class, boolean.class + ) + ); + + public static final Method method$Registry$asLookup = ReflectionUtils.getMethod( + Registry.class, new String[]{"asLookup"} + ); +} diff --git a/server-mod/v1_21_5/src/main/resources/ignite.mod.json b/server-mod/v1_21_5/src/main/resources/ignite.mod.json new file mode 100644 index 000000000..a353e9e4d --- /dev/null +++ b/server-mod/v1_21_5/src/main/resources/ignite.mod.json @@ -0,0 +1,7 @@ +{ + "id": "craftengine", + "version": "${project_version}", + "mixins": [ + "mixins.craftengine.json" + ] +} diff --git a/server-mod/v1_21_5/src/main/resources/mixins.craftengine.json b/server-mod/v1_21_5/src/main/resources/mixins.craftengine.json new file mode 100644 index 000000000..288b6501f --- /dev/null +++ b/server-mod/v1_21_5/src/main/resources/mixins.craftengine.json @@ -0,0 +1,12 @@ +{ + "required": true, + "minVersion": "0.8.7", + "package": "net.momirealms.craftengine.mod.mixin", + "plugin": "net.momirealms.craftengine.mod.CraftEnginePlugin", + "target": "@env(DEFAULT)", + "compatibilityLevel": "JAVA_21", + "server": [ + "BlocksMixin", + "ItemStackMixin" + ] +} diff --git a/settings.gradle.kts b/settings.gradle.kts index e222e92cd..a06e0cb2c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,7 +5,8 @@ include(":bukkit") include(":bukkit:legacy") include(":bukkit:compatibility") include(":bukkit:loader") -include(":server-mod") +include(":server-mod:v1_20_5") +include(":server-mod:v1_21_5") include(":client-mod") pluginManagement { plugins {