9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-27 10:59:07 +00:00

Merge pull request #114 from Xiao-MoMi/dev

0.0.48
This commit is contained in:
XiaoMoMi
2025-04-16 04:03:18 +08:00
committed by GitHub
112 changed files with 3940 additions and 674 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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<Entity> function) {
return world.spawnEntity(loc, type, CreatureSpawnEvent.SpawnReason.CUSTOM, function::accept);
}
}

View File

@@ -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"))

View File

@@ -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

View File

@@ -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 <image:...> <shift:...> in other plugins
# Turning off some unused options would help reduce CPU usage on async threads
# Allow <image:...> and <shift:...> 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 <shift:xxx> 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'
sync-custom-blocks-on-chunk-load: false

View File

@@ -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

View File

@@ -54,6 +54,11 @@ command.search_usage.not_found: "<red>No usage found for this item</red>"
command.search_recipe.no_item: "<red>Please hold an item before running this command</red>"
command.search_usage.no_item: "<red>Please hold an item before running this command</red>"
command.totem_animation.failure.not_totem: "<red>Item '<arg:0>' is not minecraft:totem_of_undying</red>"
command.resource.enable.success: "<white>Enabled resource <arg:0>. Run <click:run_command:/ce reload all><u>/ce reload all</u></click> to apply changes</white>"
command.resource.enable.failure.unknown: "<red>Unknown resource <arg:0></red>"
command.resource.disable.success: "<white>Disabled resource <arg:0>. Run <click:run_command:/ce reload all><u>/ce reload all</u></click> to apply changes</white>"
command.resource.disable.failure.unknown: "<red>Unknown resource <arg:0></red>"
command.resource.list: "<white>Enabled resources(<arg:0>): <green><arg:1></green><newline>Disabled resources(<arg:2>): <red><arg:3></red></white>"
warning.config.image.duplicated: "<yellow>Issue found in file <arg:0> - Duplicated image '<arg:1>'.</yellow>"
warning.config.image.lack_height: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' is missing the required 'height' argument.</yellow>"
warning.config.image.height_smaller_than_ascent: "<yellow>Issue found in file <arg:0> - The image '<arg:1>' violates the bitmap image rule: 'height' should be no lower than 'ascent'.</yellow>"

View File

@@ -54,6 +54,11 @@ command.search_usage.not_found: "<red>找不到此物品的用途</red>"
command.search_recipe.no_item: "<red>请手持物品后再执行此命令</red>"
command.search_usage.no_item: "<red>请手持物品后再执行此命令</red>"
command.totem_animation.failure.not_totem: "<red>'<arg:0>' 不是 totem_of_undying 类型</red>"
command.resource.enable.success: "<white>已启用 <arg:0>. 执行 <click:run_command:/ce reload all><u>/ce reload all</u></click> 以应用更改</white>"
command.resource.enable.failure.unknown: "<red>未知资源 <arg:0></red>"
command.resource.disable.success: "<white>已禁用 <arg:0>. 执行 <click:run_command:/ce reload all><u>/ce reload all</u></click> 以应用更改</white>"
command.resource.disable.failure.unknown: "<red>未知资源 <arg:0></red>"
command.resource.list: "<white>启用的资源(<arg:0>): <green><arg:1></green><newline>禁用的资源(<arg:2>): <red><arg:3></red></white>"
warning.config.image.duplicated: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 重复定义</yellow>"
warning.config.image.lack_height: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 缺少必要的 'height' 高度参数</yellow>"
warning.config.image.height_smaller_than_ascent: "<yellow>在文件 <arg:0> 中发现问题 - 图片 '<arg:1>' 违反位图规则:'height' 高度值不应小于 'ascent' 基准线高度</yellow>"

View File

@@ -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;

View File

@@ -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<ItemStack> 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();

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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<ItemStack> {
private final Key id;
private final Key materialKey;
private final Material material;
private final List<ItemModifier<ItemStack>> modifiers;
private final List<ItemBehavior> behavior;
private final ItemDataModifier<ItemStack>[] modifiers;
private final Map<String, ItemDataModifier<ItemStack>> modifierMap;
private final ItemDataModifier<ItemStack>[] clientBoundModifiers;
private final Map<String, ItemDataModifier<ItemStack>> clientBoundModifierMap;
private final NetworkItemDataProcessor<ItemStack>[] networkItemDataProcessors;
private final List<ItemBehavior> behaviors;
private final ItemSettings settings;
public BukkitCustomItem(Key id, Key materialKey, Material material, List<ItemModifier<ItemStack>> modifiers, List<ItemBehavior> behavior, ItemSettings settings) {
@SuppressWarnings("unchecked")
public BukkitCustomItem(Key id,
Key materialKey,
Material material,
List<ItemDataModifier<ItemStack>> modifiers,
List<ItemDataModifier<ItemStack>> clientBoundModifiers,
List<ItemBehavior> 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<String, ItemDataModifier<ItemStack>> modifierMapBuilder = ImmutableMap.builder();
for (ItemDataModifier<ItemStack> modifier : modifiers) {
modifierMapBuilder.put(modifier.name(), modifier);
}
this.modifierMap = modifierMapBuilder.build();
ImmutableMap.Builder<String, ItemDataModifier<ItemStack>> clientSideModifierMapBuilder = ImmutableMap.builder();
List<NetworkItemDataProcessor<ItemStack>> networkItemDataProcessors = new ArrayList<>();
for (ItemDataModifier<ItemStack> 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<ItemModifier<ItemStack>> modifiers() {
return modifiers;
public NetworkItemDataProcessor<ItemStack>[] networkItemDataProcessors() {
return this.networkItemDataProcessors;
}
@Override
public ItemDataModifier<ItemStack>[] dataModifiers() {
return this.modifiers;
}
@Override
public Map<String, ItemDataModifier<ItemStack>> dataModifierMap() {
return this.modifierMap;
}
@Override
public boolean hasClientBoundDataModifier() {
return this.clientBoundModifiers.length != 0;
}
@Override
public ItemDataModifier<ItemStack>[] clientBoundDataModifiers() {
return this.clientBoundModifiers;
}
@Override
public Map<String, ItemDataModifier<ItemStack>> 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<ItemStack> wrapped = BukkitCraftEngine.instance().itemManager().wrap(item);
wrapped.count(count);
for (ItemModifier<ItemStack> modifier : this.modifiers) {
for (ItemDataModifier<ItemStack> modifier : this.modifiers) {
modifier.apply(wrapped, context);
}
return wrapped.load();
@@ -65,14 +118,14 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
@Override
public ItemSettings settings() {
return settings;
return this.settings;
}
@Override
public Item<ItemStack> buildItem(ItemBuildContext context) {
ItemStack item = new ItemStack(material);
ItemStack item = new ItemStack(this.material);
Item<ItemStack> wrapped = BukkitCraftEngine.instance().itemManager().wrap(item);
for (ItemModifier<ItemStack> modifier : modifiers()) {
for (ItemDataModifier<ItemStack> modifier : dataModifiers()) {
modifier.apply(wrapped, context);
}
wrapped.load();
@@ -81,7 +134,7 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
@Override
public @NotNull List<ItemBehavior> behaviors() {
return this.behavior;
return this.behaviors;
}
public static Builder<ItemStack> builder() {
@@ -92,9 +145,10 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
private Key id;
private Material material;
private Key materialKey;
private List<ItemBehavior> behavior = List.of();
private ItemSettings settings = ItemSettings.of();
private final List<ItemModifier<ItemStack>> modifiers = new ArrayList<>();
private ItemSettings settings;
private final List<ItemBehavior> behaviors = new ArrayList<>();
private final List<ItemDataModifier<ItemStack>> modifiers = new ArrayList<>();
private final List<ItemDataModifier<ItemStack>> clientBoundModifiers = new ArrayList<>();
@Override
public Builder<ItemStack> id(Key id) {
@@ -110,26 +164,38 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
}
@Override
public Builder<ItemStack> modifier(ItemModifier<ItemStack> modifier) {
public Builder<ItemStack> dataModifier(ItemDataModifier<ItemStack> modifier) {
this.modifiers.add(modifier);
return this;
}
@Override
public Builder<ItemStack> modifiers(List<ItemModifier<ItemStack>> list) {
this.modifiers.addAll(list);
public Builder<ItemStack> dataModifiers(List<ItemDataModifier<ItemStack>> modifiers) {
this.modifiers.addAll(modifiers);
return this;
}
@Override
public Builder<ItemStack> clientBoundDataModifier(ItemDataModifier<ItemStack> modifier) {
this.clientBoundModifiers.add(modifier);
return this;
}
@Override
public Builder<ItemStack> clientBoundDataModifiers(List<ItemDataModifier<ItemStack>> modifiers) {
this.clientBoundModifiers.addAll(modifiers);
return null;
}
@Override
public Builder<ItemStack> behavior(ItemBehavior behavior) {
this.behavior= List.of(behavior);
this.behaviors.add(behavior);
return this;
}
@Override
public Builder<ItemStack> behavior(List<ItemBehavior> behaviors) {
this.behavior = behaviors;
public Builder<ItemStack> behaviors(List<ItemBehavior> behaviors) {
this.behaviors.addAll(behaviors);
return this;
}
@@ -142,7 +208,7 @@ public class BukkitCustomItem implements CustomItem<ItemStack> {
@Override
public CustomItem<ItemStack> 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);
}
}
}

View File

@@ -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<ItemStack> {
static {
@@ -70,13 +69,63 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
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<Object, Object> s2c = (raw) -> {
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(raw);
Item<ItemStack> wrapped = this.wrap(itemStack.clone());
Optional<CustomItem<ItemStack>> customItem = wrapped.getCustomItem();
if (customItem.isEmpty()) {
return raw;
}
CustomItem<ItemStack> custom = customItem.get();
if (!custom.hasClientBoundDataModifier()) {
return raw;
}
for (NetworkItemDataProcessor<ItemStack> processor : custom.networkItemDataProcessors()) {
processor.toClient(wrapped, ItemBuildContext.EMPTY);
}
wrapped.load();
return wrapped.getLiteralObject();
};
Function<Object, Object> c2s = (raw) -> {
ItemStack itemStack = FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(raw);
Item<ItemStack> wrapped = this.wrap(itemStack);
Optional<CustomItem<ItemStack>> customItem = wrapped.getCustomItem();
if (customItem.isEmpty()) {
return raw;
}
CustomItem<ItemStack> custom = customItem.get();
if (!custom.hasClientBoundDataModifier()) {
return raw;
}
for (NetworkItemDataProcessor<ItemStack> 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<ItemStack> {
.orElseGet(() -> ((WritableRegistry<Key>) 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<ItemStack> {
Key itemModelKey = null;
CustomItem.Builder<ItemStack> 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<ItemStack> {
// 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<ItemStack> {
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<ItemStack> {
for (Map<String, Object> behaviorMap : behavior) {
behaviors.add(ItemBehaviors.fromMap(pack, path, id, behaviorMap));
}
itemBuilder.behavior(behaviors);
itemBuilder.behaviors(behaviors);
} else if (behaviorConfig instanceof Map<?, ?>) {
Map<String, Object> behaviorSection = MiscUtils.castToMap(section.get("behavior"), true);
if (behaviorSection != null) {
@@ -284,7 +334,7 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
for (Map.Entry<String, Object> 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<ItemStack> {
}
}
// Add it here to make sure that ce id is always applied
if (!isVanillaItem)
itemBuilder.dataModifier(new IdModifier<>(id));
// Get item data
Map<String, Object> clientSideDataSection = MiscUtils.castToMap(section.get("client-bound-data"), true);
if (clientSideDataSection != null) {
for (Map.Entry<String, Object> 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<String, Object> 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<ItemStack> customItem = itemBuilder.build();
customItems.put(id, customItem);

View File

@@ -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<CustomItem<ItemStack>> 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;
}

View File

@@ -53,6 +53,10 @@ public class AxeItemBehavior extends ItemBehavior {
}
Player player = context.getPlayer();
// no adventure mode
if (player.isAdventureMode()) {
return InteractionResult.PASS;
}
Item<ItemStack> offHandItem = (Item<ItemStack>) player.getItemInHand(InteractionHand.OFF_HAND);
// is using a shield

View File

@@ -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());

View File

@@ -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()));

View File

@@ -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()));

View File

@@ -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;

View File

@@ -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();

View File

@@ -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<CraftEngine, RTagIte
@Override
protected Key id(ItemWrapper<ItemStack> 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<CraftEngine, RTagIte
return item.remove(path);
}
@Override
protected void setComponent(ItemWrapper<ItemStack> item, String type, Object value) {
item.setComponent(type, value);
}
@Override
protected Object getComponent(ItemWrapper<ItemStack> item, String type) {
return item.getComponent(type);
}
@Override
protected boolean hasComponent(ItemWrapper<ItemStack> item, String type) {
return item.hasComponent(type);
}
@Override
protected void removeComponent(ItemWrapper<ItemStack> item, String type) {
item.removeComponent(type);
}
@Override
protected void update(ItemWrapper<ItemStack> item) {
item.update();

View File

@@ -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<ItemStack> item, Key type, Object value) {
if (value instanceof JsonElement jsonElement) {
setJsonComponentDirectly(item, type, jsonElement);
} else {
setJavaComponentDirectly(item, type, value);
}
}
@Override
protected void resetComponent(ItemWrapper<ItemStack> item, Key type) {
FastNMS.INSTANCE.resetComponent(item.getLiteralObject(), KeyUtils.toResourceLocation(type));
}
@Override
protected Object getComponent(ItemWrapper<ItemStack> item, Key type) {
return item.getComponent(type);
}
@Override
protected boolean hasComponent(ItemWrapper<ItemStack> item, Key type) {
return item.hasComponent(type);
}
@Override
protected void removeComponent(ItemWrapper<ItemStack> item, Key type) {
FastNMS.INSTANCE.removeComponent(item.getLiteralObject(), KeyUtils.toResourceLocation(type));
}
protected void setJavaComponentDirectly(ItemWrapper<ItemStack> item, Key type, Object value) {
ComponentType.parseJava(type, value).ifPresent(it -> FastNMS.INSTANCE.setComponent(item.getLiteralObject(), KeyUtils.toResourceLocation(type), it));
}
protected void setJsonComponentDirectly(ItemWrapper<ItemStack> item, Key type, JsonElement value) {
ComponentType.parseJson(type, value).ifPresent(it -> FastNMS.INSTANCE.setComponent(item.getLiteralObject(), KeyUtils.toResourceLocation(type), it));
}
protected void setNBTComponentDirectly(ItemWrapper<ItemStack> 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<ItemStack> 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<ItemStack> 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<ItemStack> 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<ItemStack> item, String skullData) {
final Map<String, Object> 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<String, Object> 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<ItemStack> item, List<String> 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<ItemStack> 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<ItemStack> 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<ItemStack> 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<ItemStack> 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<ItemStack> item, List<Enchantment> enchantments) {
Map<String, Integer> 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<String, Integer> 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<ItemStack> item, List<Enchantment> enchantments) {
Map<String, Integer> 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<String, Integer> 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<String, Integer> 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<String, Integer> 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<ItemStack> 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<ItemStack> 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<ItemStack> item1, ItemWrapper<ItemStack> 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<ItemStack> 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> trim(ItemWrapper<ItemStack> item) {
if (!item.hasComponent(ComponentKeys.TRIM)) return Optional.empty();
Optional<Object> trim = ComponentType.encodeJava(ComponentKeys.TRIM, item.getComponent(ComponentKeys.TRIM));
if (trim.isEmpty()) {
return Optional.empty();
}
@SuppressWarnings("unchecked")
Map<String, String> trimMap = (Map<String, String>) trim.get();
return Optional.of(new Trim(trimMap.get("pattern"), trimMap.get("material")));
}
}

View File

@@ -33,9 +33,9 @@ public class ComponentItemFactory1_21_4 extends ComponentItemFactory {
@Override
protected void customModelData(ItemWrapper<ItemStack> 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())));
}
}
}

View File

@@ -26,9 +26,9 @@ public class ComponentItemFactory1_21_5 extends ComponentItemFactory1_21_4 {
@Override
protected void customName(ItemWrapper<ItemStack> 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<ItemStack> 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<ItemStack> item, List<String> lore) {
if (lore == null || lore.isEmpty()) {
item.removeComponent(ComponentKeys.LORE);
resetComponent(item, ComponentKeys.LORE);
} else {
List<Object> 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));
}
}
}

View File

@@ -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<ItemStack> 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<ItemStack> item, Key type, Object value) {
throw new UnsupportedOperationException("This feature is only available on 1.20.5+");
}
@Override
protected Object getComponent(ItemWrapper<ItemStack> item, Key type) {
throw new UnsupportedOperationException("This feature is only available on 1.20.5+");
}
@Override
protected boolean hasComponent(ItemWrapper<ItemStack> item, Key type) {
throw new UnsupportedOperationException("This feature is only available on 1.20.5+");
}
@Override
protected void removeComponent(ItemWrapper<ItemStack> 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<ItemStack> item, List<Enchantment> enchantments) {
if (enchantments == null || enchantments.isEmpty()) {
item.remove("Enchantments");
return;
}
ArrayList<Object> 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<ItemStack> item, List<Enchantment> enchantments) {
if (enchantments == null || enchantments.isEmpty()) {
item.remove("StoredEnchantments");
return;
}
ArrayList<Object> 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<ItemStack> 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> trim(ItemWrapper<ItemStack> 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));
}
}

View File

@@ -46,7 +46,10 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
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<CommandSender> manager = (LegacyPaperCommandManager<CommandSender>) getCommandManager();
manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true);

View File

@@ -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<CommandSender> {
public DisableResourceCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.flag(manager.flagBuilder("silent").withAliases("s"))
.required("pack", StringParser.stringComponent(StringParser.StringMode.GREEDY).suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> 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";
}
}

View File

@@ -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<CommandSender> {
public EnableResourceCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.flag(manager.flagBuilder("silent").withAliases("s"))
.required("pack", StringParser.stringComponent(StringParser.StringMode.GREEDY).suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> 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";
}
}

View File

@@ -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<CommandSender> {
public ListResourceCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.handler(context -> {
Collection<Pack> packs = plugin().packManager().loadedPacks();
List<Pack> enabled = new ArrayList<>();
List<Pack> 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<Component> getChildComponents(List<Pack> disabled) {
List<Component> 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";
}
}

View File

@@ -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<CommandSender> {
public TestCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
@@ -24,16 +23,10 @@ public class TestCommand extends BukkitCommandFeature<CommandSender> {
.senderType(Player.class)
.handler(context -> {
Player player = context.sender();
Location location = player.getLocation();
try {
Collection<Entity> 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<ItemStack> wrapped = BukkitItemManager.instance().wrap(itemStack);
wrapped.lore(null);
player.getInventory().addItem(wrapped.load());
});
}

View File

@@ -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;

View File

@@ -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<org.bukkit.entity.Player> playerRef;
private Reference<Object> 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<Integer, List<Integer>> furnitureView = new ConcurrentHashMap<>();
private final Map<Integer, Object> 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<Object> 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<ItemStack> 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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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<Entity> 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<Entity> function) {
if (VersionHelper.isVersionNewerThan1_20_2()) {
return world.spawnEntity(loc, type, CreatureSpawnEvent.SpawnReason.CUSTOM, function);
} else {
return LegacyEntityUtils.spawnEntity(world, loc, type, function);
}
}
}

View File

@@ -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
);
}

View File

@@ -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;

View File

@@ -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<Key> 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<String> 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);

View File

@@ -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<Object> 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();
}

View File

@@ -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);
}

View File

@@ -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<W extends ItemWrapper<I>, I> implements Item<I> {
return this;
}
@Override
public Item<I> trim(Trim trim) {
this.factory.trim(this.item, trim);
return this;
}
@Override
public Optional<Trim> trim() {
return this.factory.trim(this.item);
}
@Override
public Item<I> customModelData(Integer data) {
this.factory.customModelData(this.item, data);
@@ -224,25 +236,40 @@ public class AbstractItem<W extends ItemWrapper<I>, I> implements Item<I> {
}
@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);

View File

@@ -24,7 +24,7 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
protected static final Map<Key, List<Holder<Key>>> VANILLA_ITEM_TAGS = new HashMap<>();
protected final Map<String, ExternalItemProvider<I>> externalItemProviders = new HashMap<>();
protected final Map<String, Function<Object, ItemModifier<I>>> dataFunctions = new HashMap<>();
protected final Map<String, Function<Object, ItemDataModifier<I>>> dataFunctions = new HashMap<>();
protected final Map<Key, CustomItem<I>> customItems = new HashMap<>();
protected final Map<Key, List<Holder<Key>>> customItemTags;
protected final Map<Key, Map<Integer, Key>> cmdConflictChecker;
@@ -37,7 +37,7 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
protected final List<Suggestion> cachedSuggestions = new ArrayList<>();
protected final List<Suggestion> cachedTotemSuggestions = new ArrayList<>();
protected void registerDataFunction(Function<Object, ItemModifier<I>> function, String... alias) {
protected void registerDataFunction(Function<Object, ItemDataModifier<I>> function, String... alias) {
for (String a : alias) {
dataFunctions.put(a, function);
}

View File

@@ -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");
}

View File

@@ -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<I> extends BuildableItem<I> {
@@ -15,7 +16,17 @@ public interface CustomItem<I> extends BuildableItem<I> {
Key material();
List<ItemModifier<I>> modifiers();
NetworkItemDataProcessor<I>[] networkItemDataProcessors();
ItemDataModifier<I>[] dataModifiers();
Map<String, ItemDataModifier<I>> dataModifierMap();
boolean hasClientBoundDataModifier();
ItemDataModifier<I>[] clientBoundDataModifiers();
Map<String, ItemDataModifier<I>> clientBoundDataModifierMap();
ItemSettings settings();
@@ -24,7 +35,7 @@ public interface CustomItem<I> extends BuildableItem<I> {
}
default Item<I> buildItem(Player player) {
return buildItem(new ItemBuildContext(player, ContextHolder.EMPTY));
return buildItem(ItemBuildContext.of(player, ContextHolder.EMPTY));
}
Item<I> buildItem(ItemBuildContext context);
@@ -37,13 +48,17 @@ public interface CustomItem<I> extends BuildableItem<I> {
Builder<I> material(Key material);
Builder<I> modifiers(List<ItemModifier<I>> modifiers);
Builder<I> dataModifier(ItemDataModifier<I> modifier);
Builder<I> modifier(ItemModifier<I> modifier);
Builder<I> dataModifiers(List<ItemDataModifier<I>> modifiers);
Builder<I> clientBoundDataModifier(ItemDataModifier<I> modifier);
Builder<I> clientBoundDataModifiers(List<ItemDataModifier<I>> modifiers);
Builder<I> behavior(ItemBehavior behavior);
Builder<I> behavior(List<ItemBehavior> behaviors);
Builder<I> behaviors(List<ItemBehavior> behaviors);
Builder<I> settings(ItemSettings settings);

View File

@@ -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<I> {
Item<I> count(int amount);
Item<I> trim(Trim trim);
Optional<Trim> trim();
Item<I> customModelData(Integer data);
Optional<Integer> customModelData();
@@ -91,13 +96,19 @@ public interface Item<I> {
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();

View File

@@ -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<P extends Plugin, W extends ItemWrapper<I>, I>
public abstract Object encodeJava(Key componentType, @Nullable Object component);
protected abstract JsonElement encodeJson(Key type, Object component);
protected abstract ItemWrapper<I> wrapInternal(I item);
protected abstract Object getTag(ItemWrapper<I> item, Object... path);
@@ -32,13 +35,15 @@ public abstract class ItemFactory<P extends Plugin, W extends ItemWrapper<I>, I>
protected abstract boolean removeTag(ItemWrapper<I> item, Object... path);
protected abstract void setComponent(ItemWrapper<I> item, String type, Object value);
protected abstract void setComponent(ItemWrapper<I> item, Key type, Object value);
protected abstract Object getComponent(ItemWrapper<I> item, String type);
protected abstract Object getComponent(ItemWrapper<I> item, Key type);
protected abstract boolean hasComponent(ItemWrapper<I> item, String type);
protected abstract boolean hasComponent(ItemWrapper<I> item, Key type);
protected abstract void removeComponent(ItemWrapper<I> item, String type);
protected abstract void removeComponent(ItemWrapper<I> item, Key type);
protected abstract void resetComponent(ItemWrapper<I> item, Key type);
protected abstract void update(ItemWrapper<I> item);
@@ -112,6 +117,10 @@ public abstract class ItemFactory<P extends Plugin, W extends ItemWrapper<I>, I>
protected abstract Optional<Integer> repairCost(ItemWrapper<I> item);
protected abstract void trim(ItemWrapper<I> item, Trim trim);
protected abstract Optional<Trim> trim(ItemWrapper<I> item);
protected abstract ItemWrapper<I> mergeCopy(ItemWrapper<I> item1, ItemWrapper<I> item2);
protected abstract void merge(ItemWrapper<I> item1, ItemWrapper<I> item2);

View File

@@ -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<AnvilRepairItem> anvilRepairItems = List.of();
boolean renameable = true;
boolean canPlaceRelatedVanillaBlock = false;
private ItemSettings() {}
public <I> List<ItemModifier<I>> modifiers() {
ArrayList<ItemModifier<I>> modifiers = new ArrayList<>();
public <I> List<ItemDataModifier<I>> modifiers() {
ArrayList<ItemDataModifier<I>> 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) {

View File

@@ -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<I>(@Nullable ItemDataModifier<I> server, @NotNull ItemDataModifier<I> client) {
public static <I> NetworkItemDataProcessor<I> clientOnly(ItemDataModifier<I> client) {
return new NetworkItemDataProcessor<>(null, client);
}
public static <I> NetworkItemDataProcessor<I> both(ItemDataModifier<I> server, ItemDataModifier<I> client) {
return new NetworkItemDataProcessor<>(server, client);
}
public void toClient(Item<I> item, ItemBuildContext context) {
this.client.apply(item, context);
}
public void toServer(Item<I> item, ItemBuildContext context) {
this.client.remove(item);
if (this.server != null) {
this.server.apply(item, context);
}
}
}

View File

@@ -0,0 +1,4 @@
package net.momirealms.craftengine.core.item;
public record Trim(String pattern, String material) {
}

View File

@@ -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<I> implements ItemModifier<I> {
private final Map<String, Object> arguments;
public class ComponentModifier<I> implements ItemDataModifier<I> {
private final List<Pair<Key, Object>> arguments;
private JsonObject customData = null;
public ComponentModifier(Map<String, Object> arguments) {
this.arguments = arguments;
List<Pair<Key, Object>> pairs = new ArrayList<>(arguments.size());
for (Map.Entry<String, Object> 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<Pair<Key, Object>> 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<I> implements ItemModifier<I> {
@Override
public void apply(Item<I> item, ItemBuildContext context) {
for (Map.Entry<String, Object> entry : arguments.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
item.setComponent(key, value);
for (Pair<Key, Object> 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<I> item) {
for (Pair<Key, Object> 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);
}
}
}
}

View File

@@ -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<I> implements ItemModifier<I> {
public class CustomModelDataModifier<I> implements ItemDataModifier<I> {
private final int argument;
public CustomModelDataModifier(int argument) {
@@ -19,4 +19,9 @@ public class CustomModelDataModifier<I> implements ItemModifier<I> {
public void apply(Item<I> item, ItemBuildContext context) {
item.customModelData(argument);
}
@Override
public void remove(Item<I> item) {
item.customModelData(null);
}
}

View File

@@ -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<I> implements ItemModifier<I> {
public class DisplayNameModifier<I> implements ItemDataModifier<I> {
private final String argument;
public DisplayNameModifier(String argument) {
@@ -21,4 +21,9 @@ public class DisplayNameModifier<I> implements ItemModifier<I> {
public void apply(Item<I> item, ItemBuildContext context) {
item.customName(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(this.argument, context.tagResolvers())));
}
@Override
public void remove(Item<I> item) {
item.customName(null);
}
}

View File

@@ -7,7 +7,7 @@ import net.momirealms.craftengine.core.item.ItemKeys;
import java.util.List;
public class EnchantmentModifier<I> implements ItemModifier<I> {
public class EnchantmentModifier<I> implements ItemDataModifier<I> {
private final List<Enchantment> enchantments;
public EnchantmentModifier(List<Enchantment> enchantments) {
@@ -24,4 +24,10 @@ public class EnchantmentModifier<I> implements ItemModifier<I> {
if (item.vanillaId().equals(ItemKeys.ENCHANTED_BOOK)) item.setStoredEnchantments(enchantments);
else item.setEnchantments(enchantments);
}
@Override
public void remove(Item<I> item) {
if (item.vanillaId().equals(ItemKeys.ENCHANTED_BOOK)) item.setStoredEnchantments(null);
else item.setEnchantments(null);
}
}

View File

@@ -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<I> implements ItemModifier<I> {
public class EquippableModifier<I> implements ItemDataModifier<I> {
private final EquipmentData data;
public EquippableModifier(EquipmentData data) {
@@ -21,4 +21,9 @@ public class EquippableModifier<I> implements ItemModifier<I> {
public void apply(Item<I> item, ItemBuildContext context) {
item.setComponent(ComponentKeys.EQUIPPABLE, this.data.toMap());
}
@Override
public void remove(Item<I> item) {
item.removeComponent(ComponentKeys.EQUIPPABLE);
}
}

View File

@@ -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<I> implements ItemModifier<I> {
public class ExternalModifier<I> implements ItemDataModifier<I> {
private final String id;
private final ExternalItemProvider<I> provider;
@@ -30,4 +30,9 @@ public class ExternalModifier<I> implements ItemModifier<I> {
Item<I> anotherWrapped = (Item<I>) CraftEngine.instance().itemManager().wrap(another);
item.merge(anotherWrapped);
}
@Override
public void remove(Item<I> item) {
// cannot remove
}
}

View File

@@ -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<I> implements ItemModifier<I> {
import java.util.Map;
import java.util.function.BiConsumer;
public class IdModifier<I> implements ItemDataModifier<I> {
public static final String CRAFT_ENGINE_ID = "craftengine:id";
private static final BiConsumer<Item<?>, 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<I> implements ItemModifier<I> {
@Override
public void apply(Item<I> item, ItemBuildContext context) {
item.setTag(argument.toString(), CRAFT_ENGINE_ID);
ID_SETTER.accept(item, argument.toString());
}
@Override
public void remove(Item<I> item) {
item.removeTag(CRAFT_ENGINE_ID);
}
}

View File

@@ -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<I> {
public interface ItemDataModifier<I> {
String name();
void apply(Item<I> item, ItemBuildContext context);
void remove(Item<I> item);
}

View File

@@ -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<I> implements ItemModifier<I> {
public class ItemModelModifier<I> implements ItemDataModifier<I> {
private final Key data;
public ItemModelModifier(Key data) {
@@ -19,6 +19,11 @@ public class ItemModelModifier<I> implements ItemModifier<I> {
@Override
public void apply(Item<I> item, ItemBuildContext context) {
item.setComponent(ComponentKeys.ITEM_MODEL, this.data.toString());
item.setComponent(ComponentKeys.ITEM_MODEL, this.data.toString());
}
@Override
public void remove(Item<I> item) {
item.removeComponent(ComponentKeys.ITEM_MODEL);
}
}

View File

@@ -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<I> implements ItemModifier<I> {
public class ItemNameModifier<I> implements ItemDataModifier<I> {
private final String argument;
public ItemNameModifier(String argument) {
@@ -21,4 +21,9 @@ public class ItemNameModifier<I> implements ItemModifier<I> {
public void apply(Item<I> item, ItemBuildContext context) {
item.itemName(AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(this.argument, context.tagResolvers())));
}
@Override
public void remove(Item<I> item) {
item.itemName(null);
}
}

View File

@@ -8,7 +8,7 @@ import net.momirealms.craftengine.core.util.VersionHelper;
import java.util.Map;
public class JukeboxSongModifier<I> implements ItemModifier<I> {
public class JukeboxSongModifier<I> implements ItemDataModifier<I> {
private final Key song;
public JukeboxSongModifier(Key song) {
@@ -31,4 +31,9 @@ public class JukeboxSongModifier<I> implements ItemModifier<I> {
));
}
}
@Override
public void remove(Item<I> item) {
item.removeComponent(ComponentKeys.JUKEBOX_PLAYABLE);
}
}

View File

@@ -7,7 +7,7 @@ import net.momirealms.craftengine.core.util.AdventureHelper;
import java.util.List;
public class LoreModifier<I> implements ItemModifier<I> {
public class LoreModifier<I> implements ItemDataModifier<I> {
private final List<String> argument;
public LoreModifier(List<String> argument) {
@@ -24,4 +24,9 @@ public class LoreModifier<I> implements ItemModifier<I> {
item.lore(argument.stream().map(it -> AdventureHelper.componentToJson(AdventureHelper.miniMessage().deserialize(
it, context.tagResolvers()))).toList());
}
@Override
public void remove(Item<I> item) {
item.lore(null);
}
}

View File

@@ -8,13 +8,17 @@ import net.momirealms.craftengine.core.util.TypeUtils;
import java.util.LinkedHashMap;
import java.util.Map;
public class TagsModifier<I> implements ItemModifier<I> {
public class TagsModifier<I> implements ItemDataModifier<I> {
private final Map<String, Object> arguments;
public TagsModifier(Map<String, Object> arguments) {
this.arguments = mapToMap(arguments);
}
public Map<String, Object> arguments() {
return arguments;
}
@Override
public String name() {
return "tags";
@@ -29,6 +33,13 @@ public class TagsModifier<I> implements ItemModifier<I> {
}
}
@Override
public void remove(Item<I> item) {
for (Map.Entry<String, Object> entry : arguments.entrySet()) {
item.removeTag(entry.getKey());
}
}
private static Map<String, Object> mapToMap(final Map<String, Object> source) {
Map<String, Object> resultMap = new LinkedHashMap<>();
recursiveMapProcessing(source, resultMap);

View File

@@ -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<I> implements ItemModifier<I> {
public class TooltipStyleModifier<I> implements ItemDataModifier<I> {
private final Key argument;
public TooltipStyleModifier(Key argument) {
@@ -21,4 +21,9 @@ public class TooltipStyleModifier<I> implements ItemModifier<I> {
public void apply(Item<I> item, ItemBuildContext context) {
item.setComponent(ComponentKeys.TOOLTIP_STYLE, argument.toString());
}
@Override
public void remove(Item<I> item) {
item.removeComponent(ComponentKeys.TOOLTIP_STYLE);
}
}

View File

@@ -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<I> implements ItemModifier<I> {
public class TrimModifier<I> implements ItemDataModifier<I> {
private final String material;
private final String pattern;
@@ -23,14 +22,22 @@ public class TrimModifier<I> implements ItemModifier<I> {
@Override
public void apply(Item<I> 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<I> item) {
item.trim(null);
if (VersionHelper.isVersionNewerThan1_20_5()) {
item.removeComponent(ComponentKeys.TRIM);
} else {
item.removeTag("Trim");
}
}
}

View File

@@ -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<I> implements ItemModifier<I> {
public class UnbreakableModifier<I> implements ItemDataModifier<I> {
private final boolean argument;
public UnbreakableModifier(boolean argument) {
@@ -17,6 +17,13 @@ public class UnbreakableModifier<I> implements ItemModifier<I> {
@Override
public void apply(Item<I> item, ItemBuildContext context) {
item.unbreakable(argument);
item.unbreakable(this.argument);
}
@Override
public void remove(Item<I> item) {
if (this.argument) {
item.unbreakable(false);
}
}
}

View File

@@ -217,9 +217,9 @@ public class CustomSmithingTransformRecipe<T> implements Recipe<T> {
@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);
}
}
}

View File

@@ -52,17 +52,7 @@ public abstract class AbstractPackManager implements PackManager {
public static final Set<Key> VANILLA_ITEM_TEXTURES = new HashSet<>();
public static final Set<Key> VANILLA_BLOCK_TEXTURES = new HashSet<>();
public static final Set<Key> VANILLA_FONT_TEXTURES = new HashSet<>();
private final CraftEngine plugin;
private final BiConsumer<Path, Path> eventDispatcher;
private final Map<String, Pack> loadedPacks = new HashMap<>();
private final Map<String, ConfigSectionParser> sectionParsers = new HashMap<>();
private final TreeMap<ConfigSectionParser, List<CachedConfig>> cachedConfigs = new TreeMap<>();
private static final byte[] emptyImage;
protected BiConsumer<Path, Path> 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<Path, Path> eventDispatcher;
private final Map<String, Pack> loadedPacks = new HashMap<>();
private final Map<String, ConfigSectionParser> sectionParsers = new HashMap<>();
private final TreeMap<ConfigSectionParser, List<CachedConfig>> cachedConfigs = new TreeMap<>();
protected BiConsumer<Path, Path> zipGenerator;
protected String packHash;
protected UUID packUUID;
public AbstractPackManager(CraftEngine plugin, BiConsumer<Path, Path> eventDispatcher) {
this.plugin = plugin;
this.eventDispatcher = eventDispatcher;
@@ -233,7 +233,6 @@ public abstract class AbstractPackManager implements PackManager {
Files.createDirectories(resourcesFolder);
this.saveDefaultConfigs();
}
try (DirectoryStream<Path> 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<ConfigSectionParser> predicate) {
long o1 = System.nanoTime();
for (Pack pack : loadedPacks()) {
if (!pack.enabled()) continue;
Pair<List<Path>, List<Path>> 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);
}

View File

@@ -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;
}

View File

@@ -53,6 +53,9 @@ public class CompositeItemModel implements ItemModel {
@Override
public ItemModel create(Map<String, Object> arguments) {
List<Map<String, Object>> models = (List<Map<String, Object>>) arguments.get("models");
if (models == null || models.isEmpty()) {
throw new IllegalArgumentException("No 'models' specified for 'minecraft:composite'");
}
List<ItemModel> modelList = new ArrayList<>();
for (Map<String, Object> model : models) {
modelList.add(ItemModels.fromMap(model));

View File

@@ -64,8 +64,8 @@ public class ConditionItemModel implements ItemModel {
@Override
public ItemModel create(Map<String, Object> arguments) {
ConditionProperty property = ConditionProperties.fromMap(arguments);
Map<String, Object> onTrue = Objects.requireNonNull((Map<String, Object>) arguments.get("on-true"));
Map<String, Object> onFalse = Objects.requireNonNull((Map<String, Object>) arguments.get("on-false"));
Map<String, Object> onTrue = Objects.requireNonNull((Map<String, Object>) arguments.get("on-true"), "No 'on-true' set for 'minecraft:condition'");
Map<String, Object> onFalse = Objects.requireNonNull((Map<String, Object>) arguments.get("on-false"), "No 'on-false' set for 'minecraft:condition'");
return new ConditionItemModel(property, ItemModels.fromMap(onTrue), ItemModels.fromMap(onFalse));
}
}

View File

@@ -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'");
}
}
}

View File

@@ -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'");
}
}
}

View File

@@ -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<Dependency> dependenciesToLoad = new ArrayList<>();
dependenciesToLoad.addAll(commonDependencies());
@@ -115,72 +116,77 @@ public abstract class CraftEngine implements Plugin {
public CompletableFuture<ReloadResult> reloadPlugin(Executor asyncExecutor, Executor syncExecutor, boolean reloadRecipe) {
CompletableFuture<ReloadResult> 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;
}

View File

@@ -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()) {

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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> T[] collectionToArray(Collection<T> array, Class<T> clazz) {
@SuppressWarnings("unchecked")
T[] res = (T[]) Array.newInstance(clazz, array.size());
int i = 0;
for (T item : array) {
res[i++] = item;
}
return res;
}
}

View File

@@ -21,7 +21,7 @@ public class MiscUtils {
if (obj instanceof Map<?, ?> map) {
return (Map<String, Object>) map;
}
throw new IllegalArgumentException("Expected Map, got: " + obj.getClass().getSimpleName());
throw new IllegalArgumentException("Expected Map, got: " + (obj == null ? null : obj.getClass().getSimpleName()));
}
@SuppressWarnings("unchecked")

View File

@@ -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() {}
}

View File

@@ -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

View File

@@ -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"))
}
}

View File

@@ -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.*;

View File

@@ -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 = "<clinit>", 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<ResourceLocation, Integer> map = loadMappingsAndAdditionalBlocks();
for (Map.Entry<ResourceLocation, Integer> 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<Block> 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<Block> 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;
}
}

View File

@@ -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;

View File

@@ -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<RegistryFriendlyByteBuf, ItemStack> {
public static Function<ItemStack, ItemStack> clientBoundDataProcessor;
public static Function<ItemStack, ItemStack> serverBoundDataProcessor;
private final StreamCodec<RegistryFriendlyByteBuf, ItemStack> original;
public CustomStreamCodec(StreamCodec<RegistryFriendlyByteBuf, ItemStack> 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);
}
}

View File

@@ -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 = "<clinit>", at = @At("RETURN"))
private static void onBlocksInit(CallbackInfo ci) {
CustomBlocks.register();
}
}

View File

@@ -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<RegistryFriendlyByteBuf, ItemStack> OPTIONAL_STREAM_CODEC;
@Shadow(remap = false)
@Mutable
public static StreamCodec<RegistryFriendlyByteBuf, List<ItemStack>> OPTIONAL_LIST_STREAM_CODEC;
private static StreamCodec<RegistryFriendlyByteBuf, ItemStack> ORIGINAL_OPTIONAL_STREAM_CODEC;
@Inject(
method = "<clinit>",
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 = "<clinit>", 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));
}
}

View File

@@ -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;
}
}

View File

@@ -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<String> 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<Field> getDeclaredFields(final Class<?> clazz) {
List<Field> fields = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
fields.add(setAccessible(field));
}
return fields;
}
@NotNull
public static List<Field> getInstanceDeclaredFields(@NotNull Class<?> clazz) {
List<Field> list = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers())) {
list.add(setAccessible(field));
}
}
return list;
}
@NotNull
public static List<Field> getDeclaredFields(@NotNull final Class<?> clazz, @NotNull final Class<?> type) {
List<Field> fields = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (field.getType() == type) {
fields.add(setAccessible(field));
}
}
return fields;
}
@NotNull
public static List<Field> getInstanceDeclaredFields(@NotNull Class<?> clazz, @NotNull Class<?> type) {
List<Field> 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<Method> getMethods(@NotNull Class<?> clazz, @NotNull Class<?> returnType, @NotNull Class<?>... parameterTypes) {
List<Method> 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 extends AccessibleObject> 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;
}
}
}

View File

@@ -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"}
);
}

View File

@@ -6,6 +6,7 @@
"target": "@env(DEFAULT)",
"compatibilityLevel": "JAVA_21",
"server": [
"MixinBlocks"
"BlocksMixin",
"ItemStackMixin"
]
}

View File

@@ -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<JavaCompile> {
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"))
}
}

View File

@@ -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<String> myTargets,
@NotNull Set<String> otherTargets) {
}
@Override
public @Nullable List<String> 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");
}
}

View File

@@ -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<BlockBehavior> behaviorHolder;
public ObjectHolder<BlockShape> 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<BlockBehavior> getBehaviorHolder() {
return behaviorHolder;
}
@Override
public ObjectHolder<BlockShape> 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);
// }
// }
}

Some files were not shown because too many files have changed in this diff Show More